DebugInfoBuilder.java revision aaf4c4062adff8a83b2d590a5fd162e5e8ab7b26
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2009 Ben Gruver
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.dexlib.Util;
30
31import org.jf.dexlib.DebugInfoItem;
32import org.jf.dexlib.DexFile;
33import org.jf.dexlib.StringIdItem;
34import org.jf.dexlib.TypeIdItem;
35import org.jf.dexlib.Debug.*;
36
37import java.util.ArrayList;
38import java.util.List;
39
40/**
41 * This class is intended to provide an easy to use container to build up a method's debug info. You can easily add
42 * an "event" at a specific address, where an event is something like a line number, start/end local, etc.
43 * The events must be added such that the code addresses increase monotonically. This matches how a parser would
44 * generally behave, and is intended to increase performance.
45 */
46public class DebugInfoBuilder
47{
48    private static final int LINE_BASE = -4;
49    private static final int LINE_RANGE = 15;
50    private static final int FIRST_SPECIAL = 0x0a;
51
52    private int lineStart = 0;
53    private ArrayList<String> parameterNames = new ArrayList<String>();
54    private ArrayList<Event> events = new ArrayList<Event>();
55    private int lastAddress = 0;
56
57    private boolean hasData;
58
59    private int currentAddress;
60    private int currentLine;
61
62    public DebugInfoBuilder() {
63    }
64
65    private void checkAddress(int address) {
66        if (lastAddress > address) {
67            throw new RuntimeException("Cannot add an event with an address before the address of the prior event");
68        }
69    }
70
71    public void addParameterName(String parameterName) {
72        if (parameterName != null) {
73            hasData = true;
74        }
75
76        parameterNames.add(parameterName);
77    }
78
79    public void addLine(int address, int line) {
80        hasData = true;
81
82        checkAddress(address);
83
84        if (lineStart == 0) {
85            lineStart = line;
86        }
87
88        events.add(new LineEvent(address, line));
89    }
90
91    public void addLocal(int address, int registerNumber, String localName, String localType) {
92        hasData = true;
93
94        checkAddress(address);
95
96        events.add(new StartLocalEvent(address, registerNumber, localName, localType));
97    }
98
99    public void addLocalExtended(int address, int registerNumber, String localName, String localType,
100                                String signature) {
101        hasData = true;
102
103        checkAddress(address);
104
105        events.add(new StartLocalExtendedEvent(address, registerNumber, localName, localType, signature));
106    }
107
108    public void addEndLocal(int address, int registerNumber) {
109        hasData = true;
110
111        checkAddress(address);
112
113        events.add(new EndLocalEvent(address, registerNumber));
114    }
115
116    public void addRestartLocal(int address, int registerNumber) {
117        hasData = true;
118
119        checkAddress(address);
120
121        events.add(new RestartLocalEvent(address, registerNumber));
122    }
123
124    public void addPrologue(int address) {
125        hasData = true;
126
127        checkAddress(address);
128
129        events.add(new PrologueEvent(address));
130    }
131
132    public void addEpilogue(int address) {
133        hasData = true;
134
135        checkAddress(address);
136
137        events.add(new EpilogueEvent(address));
138    }
139
140    public void addSetFile(int address, String fileName) {
141        hasData = true;
142
143        checkAddress(address);
144
145        events.add(new SetFileEvent(address, fileName));
146    }
147
148    public int getParameterNameCount() {
149        return parameterNames.size();
150    }
151
152    public DebugInfoItem encodeDebugInfo(DexFile dexFile) {
153        if (!hasData) {
154            return null;
155        }
156
157        ArrayList<DebugInstruction> debugInstructions = new ArrayList<DebugInstruction>();
158        ArrayList<StringIdItem> parameterNameReferences = new ArrayList<StringIdItem>();
159
160        if (lineStart == 0) {
161            lineStart = 1;
162        }
163
164        currentLine = lineStart;
165
166        for (Event event: events) {
167            event.emit(dexFile, debugInstructions);
168        }
169        debugInstructions.add(new EndSequence());
170
171        for (String parameterName: parameterNames) {
172            if (parameterName == null) {
173                parameterNameReferences.add(null);
174            } else {
175                parameterNameReferences.add(new StringIdItem(dexFile, parameterName));
176            }
177        }
178
179        return new DebugInfoItem(dexFile, lineStart, parameterNameReferences, debugInstructions);
180    }
181
182    private interface Event
183    {
184        int getAddress();
185        void emit(DexFile dexFile, List<DebugInstruction> debugInstructions);
186    }
187
188    private void emitAdvancePC(int address, List<DebugInstruction> debugInstructions) {
189        int addressDelta = address-currentAddress;
190
191        if (addressDelta > 0) {
192            debugInstructions.add(new AdvancePC(addressDelta));
193            currentAddress = address;
194        }
195    }
196
197    private class LineEvent implements Event
198    {
199        private final int address;
200        private final int line;
201
202        public LineEvent(int address, int line) {
203            this.address = address;
204            this.line = line;
205        }
206
207        public int getAddress() {
208            return address;
209        }
210
211        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
212            int lineDelta = line - currentLine;
213            int addressDelta = address - currentAddress;
214
215            if (lineDelta < -4 || lineDelta > 10) {
216                debugInstructions.add(new AdvanceLine(lineDelta));
217                lineDelta = 0;
218            }
219            if (lineDelta < 2 && addressDelta > 16 || lineDelta > 1 && addressDelta > 15) {
220                debugInstructions.add(new AdvancePC(addressDelta));
221                addressDelta = 0;
222            }
223
224            //TODO: need to handle the case when the line delta is larger than a signed int
225            debugInstructions.add(new SpecialOpcode(calculateSpecialOpcode(lineDelta, addressDelta)));
226
227
228            currentAddress = address;
229            currentLine = line;
230        }
231
232        private byte calculateSpecialOpcode(int lineDelta, int addressDelta) {
233            return (byte)(FIRST_SPECIAL + (addressDelta * LINE_RANGE) + (lineDelta - LINE_BASE));
234        }
235    }
236
237    private class StartLocalEvent implements Event
238    {
239        private final int address;
240        private final int registerNum;
241        private final String localName;
242        private final String localType;
243
244        public StartLocalEvent(int address, int registerNum, String localName, String localType) {
245            this.address = address;
246            this.registerNum = registerNum;
247            this.localName = localName;
248            this.localType = localType;
249        }
250
251        public int getAddress() {
252            return address;
253        }
254
255        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
256            emitAdvancePC(address, debugInstructions);
257
258            debugInstructions.add(new StartLocal(dexFile, registerNum, new StringIdItem(dexFile, localName),
259                    new TypeIdItem(dexFile, localType)));
260        }
261    }
262
263    private class StartLocalExtendedEvent implements Event
264    {
265        private final int address;
266        private final int registerNum;
267        private final String localName;
268        private final String localType;
269        private final String signature;
270
271        public StartLocalExtendedEvent(int address, int registerNum, String localName, String localType,
272                                       String signature) {
273            this.address = address;
274            this.registerNum = registerNum;
275            this.localName = localName;
276            this.localType = localType;
277            this.signature = signature;
278        }
279
280        public int getAddress() {
281            return address;
282        }
283
284        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
285            emitAdvancePC(address, debugInstructions);
286
287            debugInstructions.add(new StartLocalExtended(dexFile, registerNum, new StringIdItem(dexFile, localName),
288                    new TypeIdItem(dexFile, localType), new StringIdItem(dexFile, signature)));
289        }
290    }
291
292    private class EndLocalEvent implements Event
293    {
294        private final int address;
295        private final int registerNum;
296
297        public EndLocalEvent(int address, int registerNum) {
298            this.address = address;
299            this.registerNum = registerNum;
300        }
301
302        public int getAddress() {
303            return address;
304        }
305
306        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
307            emitAdvancePC(address, debugInstructions);
308
309            debugInstructions.add(new EndLocal(registerNum));
310        }
311    }
312
313    private class RestartLocalEvent implements Event
314    {
315        private final int address;
316        private final int registerNum;
317
318        public RestartLocalEvent(int address, int registerNum) {
319            this.address = address;
320            this.registerNum = registerNum;
321        }
322
323        public int getAddress() {
324            return address;
325        }
326
327        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
328            emitAdvancePC(address, debugInstructions);
329
330            debugInstructions.add(new RestartLocal(registerNum));
331        }
332    }
333
334    private class PrologueEvent implements Event
335    {
336        private final int address;
337
338        public PrologueEvent(int address) {
339            this.address = address;
340        }
341
342        public int getAddress() {
343            return address;
344        }
345
346        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
347            emitAdvancePC(address, debugInstructions);
348
349            debugInstructions.add(new SetPrologueEnd());
350        }
351    }
352
353    private class EpilogueEvent implements Event
354    {
355        private final int address;
356
357        public EpilogueEvent(int address) {
358            this.address = address;
359        }
360
361        public int getAddress() {
362            return address;
363        }
364
365        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
366            emitAdvancePC(address, debugInstructions);
367
368            debugInstructions.add(new SetEpilogueBegin());
369        }
370    }
371
372    private class SetFileEvent implements Event
373    {
374        private final int address;
375        private final String fileName;
376
377        public SetFileEvent(int address, String fileName) {
378            this.address = address;
379            this.fileName = fileName;
380        }
381
382        public int getAddress() {
383            return address;
384        }
385
386        public void emit(DexFile dexFile, List<DebugInstruction> debugInstructions) {
387            emitAdvancePC(address, debugInstructions);
388
389            debugInstructions.add(new SetFile(dexFile, new StringIdItem(dexFile, fileName)));
390        }
391    }
392}
393