1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.writer;
33
34import org.jf.dexlib2.DebugItemType;
35import org.jf.util.ExceptionWithContext;
36
37import javax.annotation.Nonnull;
38import javax.annotation.Nullable;
39import java.io.IOException;
40
41public class DebugWriter<StringKey extends CharSequence, TypeKey extends CharSequence> {
42    @Nonnull private final StringSection<StringKey, ?> stringSection;
43    @Nonnull private final TypeSection<StringKey, TypeKey, ?> typeSection;
44    @Nonnull private final DexDataWriter writer;
45    private int currentAddress;
46    private int currentLine;
47
48    DebugWriter(@Nonnull StringSection<StringKey, ?> stringSection,
49                @Nonnull TypeSection<StringKey, TypeKey, ?> typeSection,
50                @Nonnull DexDataWriter writer) {
51        this.stringSection = stringSection;
52        this.typeSection = typeSection;
53        this.writer = writer;
54    }
55
56    void reset(int startLine) {
57        this.currentAddress = 0;
58        this.currentLine = startLine;
59    }
60
61    public void writeStartLocal(int codeAddress, int register,
62                                @Nullable StringKey name,
63                                @Nullable TypeKey type,
64                                @Nullable StringKey signature) throws IOException {
65        int nameIndex = stringSection.getNullableItemIndex(name);
66        int typeIndex = typeSection.getNullableItemIndex(type);
67        int signatureIndex = stringSection.getNullableItemIndex(signature);
68
69        writeAdvancePC(codeAddress);
70        if (signatureIndex == DexWriter.NO_INDEX) {
71            writer.write(DebugItemType.START_LOCAL);
72            writer.writeUleb128(register);
73            writer.writeUleb128(nameIndex + 1);
74            writer.writeUleb128(typeIndex + 1);
75        } else {
76            writer.write(DebugItemType.START_LOCAL_EXTENDED);
77            writer.writeUleb128(register);
78            writer.writeUleb128(nameIndex + 1);
79            writer.writeUleb128(typeIndex + 1);
80            writer.writeUleb128(signatureIndex + 1);
81        }
82    }
83
84    public void writeEndLocal(int codeAddress, int register) throws IOException {
85        writeAdvancePC(codeAddress);
86        writer.write(DebugItemType.END_LOCAL);
87        writer.writeUleb128(register);
88    }
89
90    public void writeRestartLocal(int codeAddress, int register) throws IOException {
91        writeAdvancePC(codeAddress);
92        writer.write(DebugItemType.RESTART_LOCAL);
93        writer.writeUleb128(register);
94    }
95
96    public void writePrologueEnd(int codeAddress) throws IOException {
97        writeAdvancePC(codeAddress);
98        writer.write(DebugItemType.PROLOGUE_END);
99    }
100
101    public void writeEpilogueBegin(int codeAddress) throws IOException {
102        writeAdvancePC(codeAddress);
103        writer.write(DebugItemType.EPILOGUE_BEGIN);
104    }
105
106    public void writeLineNumber(int codeAddress, int lineNumber) throws IOException {
107        int lineDelta = lineNumber - currentLine;
108        int addressDelta = codeAddress - currentAddress;
109
110        if (addressDelta < 0) {
111            throw new ExceptionWithContext("debug info items must have non-decreasing code addresses");
112        }
113        if (lineDelta < -4 || lineDelta > 10) {
114            writeAdvanceLine(lineNumber);
115            lineDelta = 0;
116        } // no else is intentional here. we might need to advance the PC as well as the line
117        if ((lineDelta < 2 && addressDelta > 16) || (lineDelta > 1 && addressDelta > 15)) {
118            writeAdvancePC(codeAddress);
119            addressDelta = 0;
120        }
121
122        // we need to emit the special opcode even if both lineDelta and addressDelta are 0, otherwise a positions
123        // entry isn't generated
124        writeSpecialOpcode(lineDelta, addressDelta);
125    }
126
127    public void writeSetSourceFile(int codeAddress, @Nullable StringKey sourceFile) throws IOException {
128        writeAdvancePC(codeAddress);
129        writer.write(DebugItemType.SET_SOURCE_FILE);
130        writer.writeUleb128(stringSection.getNullableItemIndex(sourceFile) + 1);
131    }
132
133    private void writeAdvancePC(int address) throws IOException {
134        int addressDelta = address - currentAddress;
135
136        if (addressDelta > 0) {
137            writer.write(1);
138            writer.writeUleb128(addressDelta);
139            currentAddress = address;
140        } /*else if (addressDelta < 0) {
141            throw new ExceptionWithContext("debug info items must have non-decreasing code addresses");
142        }*/
143    }
144
145    private void writeAdvanceLine(int line) throws IOException {
146        int lineDelta = line - currentLine;
147        if (lineDelta != 0) {
148            writer.write(2);
149            writer.writeSleb128(lineDelta);
150            currentLine = line;
151        }
152    }
153
154    private static final int LINE_BASE     = -4;
155    private static final int LINE_RANGE    = 15;
156    private static final int FIRST_SPECIAL = 0x0a;
157
158    private void writeSpecialOpcode(int lineDelta, int addressDelta) throws IOException {
159        writer.write((byte)(FIRST_SPECIAL + (addressDelta * LINE_RANGE) + (lineDelta - LINE_BASE)));
160        currentLine += lineDelta;
161        currentAddress += addressDelta;
162    }
163}
164