• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 package com.android.tools.r8.graph;
5 
6 import com.android.tools.r8.dex.Constants;
7 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
8 import com.android.tools.r8.ir.code.Argument;
9 import com.android.tools.r8.ir.code.DebugLocalsChange;
10 import com.android.tools.r8.ir.code.DebugPosition;
11 import com.android.tools.r8.ir.code.Instruction;
12 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
13 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
14 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
15 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
16 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
17 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
18 import java.util.ArrayList;
19 import java.util.List;
20 
21 /**
22  * Builder for constructing a list of debug events suitable for DexDebugInfo.
23  *
24  * This builder is intended to be very pedantic and ensure a well-formed structure of the resulting
25  * event stream.
26  */
27 public class DexDebugEventBuilder {
28 
29   private static final int NO_PC_INFO = -1;
30   private static final int NO_LINE_INFO = -1;
31 
32   private final DexEncodedMethod method;
33   private final DexItemFactory factory;
34 
35   // In order list of non-this argument locals.
36   private ArrayList<DebugLocalInfo> arguments;
37 
38   // Mapping from register to the last known local in that register (See DBG_RESTART_LOCAL).
39   private Int2ReferenceMap<DebugLocalInfo> lastKnownLocals;
40 
41   // Mapping from register to local for currently open/visible locals.
42   private Int2ReferenceMap<DebugLocalInfo> pendingLocals = null;
43 
44   // Conservative pending-state of locals to avoid some equality checks on locals.
45   // pendingLocalChanges == true ==> localsEqual(emittedLocals, pendingLocals).
46   private boolean pendingLocalChanges = false;
47 
48   // State of pc, line, file and locals in the emitted event stream.
49   private int emittedPc = NO_PC_INFO;
50   private int emittedLine = NO_LINE_INFO;
51   private DexString emittedFile = null;
52   private Int2ReferenceMap<DebugLocalInfo> emittedLocals;
53 
54   // If lastMoveInstructionPc != NO_PC_INFO, then the last pc-advancing instruction was a
55   // move-exception at lastMoveInstructionPc. This is needed to maintain the art/dx specific
56   // behaviour that the move-exception pc is associated with the catch-declaration line.
57   // See debug.ExceptionTest.testStepOnCatch().
58   private int lastMoveInstructionPc = NO_PC_INFO;
59 
60   // Emitted events.
61   private final List<DexDebugEvent> events = new ArrayList<>();
62 
63   // Initial known line for the method.
64   private int startLine = NO_LINE_INFO;
65 
DexDebugEventBuilder(DexEncodedMethod method, DexItemFactory factory)66   public DexDebugEventBuilder(DexEncodedMethod method, DexItemFactory factory) {
67     this.method = method;
68     this.factory = factory;
69   }
70 
71   // Public method for the DebugStripper.
setPosition(int pc, int line)72   public void setPosition(int pc, int line) {
73     emitDebugPosition(pc, line, null);
74   }
75 
76   /** Add events at pc for instruction. */
add(int pc, Instruction instruction)77   public void add(int pc, Instruction instruction) {
78     // Initialize locals state on block entry.
79     if (instruction.getBlock().entry() == instruction) {
80       updateBlockEntry(instruction);
81     }
82     assert pendingLocals != null;
83 
84     // If this is a position emit and exit as it always emits events.
85     if (instruction.isDebugPosition()) {
86       emitDebugPosition(pc, instruction.asDebugPosition());
87       return;
88     }
89 
90     if (instruction.isArgument()) {
91       startArgument(instruction.asArgument());
92     } else if (instruction.isDebugLocalsChange()) {
93       updateLocals(instruction.asDebugLocalsChange());
94     } else if (instruction.getBlock().exit() == instruction) {
95       // If this is the end of the block clear out the pending state and exit.
96       pendingLocals = null;
97       pendingLocalChanges = false;
98       return;
99     } else if (instruction.isMoveException()) {
100       lastMoveInstructionPc = pc;
101     } else {
102       // For non-exit / pc-advancing instructions emit any pending changes.
103       emitLocalChanges(pc);
104     }
105   }
106 
107   /** Build the resulting DexDebugInfo object. */
build()108   public DexDebugInfo build() {
109     assert pendingLocals == null;
110     assert !pendingLocalChanges;
111     if (startLine == NO_LINE_INFO) {
112       return null;
113     }
114     DexString[] params = new DexString[method.method.proto.parameters.values.length];
115     if (arguments != null) {
116       assert params.length == arguments.size();
117       for (int i = 0; i < arguments.size(); i++) {
118         DebugLocalInfo local = arguments.get(i);
119         params[i] = (local == null || local.signature != null) ? null : local.name;
120       }
121     }
122     return new DexDebugInfo(startLine, params, events.toArray(new DexDebugEvent[events.size()]));
123   }
124 
updateBlockEntry(Instruction instruction)125   private void updateBlockEntry(Instruction instruction) {
126     assert pendingLocals == null;
127     assert !pendingLocalChanges;
128     Int2ReferenceMap<DebugLocalInfo> locals = instruction.getBlock().getLocalsAtEntry();
129     if (locals == null) {
130       pendingLocals = Int2ReferenceMaps.emptyMap();
131     } else {
132       pendingLocals = new Int2ReferenceOpenHashMap<>(locals);
133       pendingLocalChanges = true;
134     }
135     if (emittedLocals == null) {
136       initialize(locals);
137     }
138   }
139 
initialize(Int2ReferenceMap<DebugLocalInfo> locals)140   private void initialize(Int2ReferenceMap<DebugLocalInfo> locals) {
141     assert arguments == null;
142     assert emittedLocals == null;
143     assert lastKnownLocals == null;
144     assert startLine == NO_LINE_INFO;
145     if (locals == null) {
146       emittedLocals = Int2ReferenceMaps.emptyMap();
147       lastKnownLocals = Int2ReferenceMaps.emptyMap();
148       return;
149     }
150     // Implicitly open all unparameterized arguments.
151     emittedLocals = new Int2ReferenceOpenHashMap<>();
152     for (Entry<DebugLocalInfo> entry : locals.int2ReferenceEntrySet()) {
153       if (entry.getValue().signature == null) {
154         emittedLocals.put(entry.getIntKey(), entry.getValue());
155       }
156     }
157     lastKnownLocals = new Int2ReferenceOpenHashMap<>(emittedLocals);
158   }
159 
startArgument(Argument argument)160   private void startArgument(Argument argument) {
161     if (arguments == null) {
162       arguments = new ArrayList<>(method.method.proto.parameters.values.length);
163     }
164     if (!argument.outValue().isThis()) {
165       arguments.add(argument.getLocalInfo());
166     }
167   }
168 
updateLocals(DebugLocalsChange change)169   private void updateLocals(DebugLocalsChange change) {
170     pendingLocalChanges = true;
171     for (Entry<DebugLocalInfo> end : change.getEnding().int2ReferenceEntrySet()) {
172       assert pendingLocals.get(end.getIntKey()) == end.getValue();
173       pendingLocals.remove(end.getIntKey());
174     }
175     for (Entry<DebugLocalInfo> start : change.getStarting().int2ReferenceEntrySet()) {
176       assert !pendingLocals.containsKey(start.getIntKey());
177       pendingLocals.put(start.getIntKey(), start.getValue());
178     }
179   }
180 
localsChanged()181   private boolean localsChanged() {
182     if (!pendingLocalChanges) {
183       return false;
184     }
185     pendingLocalChanges = !localsEqual(emittedLocals, pendingLocals);
186     return pendingLocalChanges;
187   }
188 
emitDebugPosition(int pc, DebugPosition position)189   private void emitDebugPosition(int pc, DebugPosition position) {
190     emitDebugPosition(pc, position.line, position.file);
191   }
192 
emitDebugPosition(int pc, int line, DexString file)193   private void emitDebugPosition(int pc, int line, DexString file) {
194     int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
195     lastMoveInstructionPc = NO_PC_INFO;
196     // The position requires a pc change event and possible events for line, file and local changes.
197     // Verify that we do not ever produce two subsequent positions at the same pc.
198     assert emittedPc != emitPc;
199     if (startLine == NO_LINE_INFO) {
200       assert emittedLine == NO_LINE_INFO;
201       startLine = line;
202       emittedLine = line;
203     }
204     emitAdvancementEvents(emittedPc, emittedLine, emittedFile, emitPc, line, file, events, factory);
205     emittedPc = emitPc;
206     emittedLine = line;
207     emittedFile = file;
208     if (localsChanged()) {
209       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
210       assert localsEqual(emittedLocals, pendingLocals);
211     }
212     pendingLocalChanges = false;
213   }
214 
emitLocalChanges(int pc)215   private void emitLocalChanges(int pc) {
216     // If pc advanced since the locals changed and locals indeed have changed, emit the changes.
217     if (localsChanged()) {
218       int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
219       lastMoveInstructionPc = NO_PC_INFO;
220       emitAdvancementEvents(
221           emittedPc, emittedLine, emittedFile, emitPc, emittedLine, emittedFile, events, factory);
222       emittedPc = emitPc;
223       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
224       pendingLocalChanges = false;
225       assert localsEqual(emittedLocals, pendingLocals);
226     }
227   }
228 
emitAdvancementEvents( int previousPc, int previousLine, DexString previousFile, int nextPc, int nextLine, DexString nextFile, List<DexDebugEvent> events, DexItemFactory factory)229   private static void emitAdvancementEvents(
230       int previousPc,
231       int previousLine,
232       DexString previousFile,
233       int nextPc,
234       int nextLine,
235       DexString nextFile,
236       List<DexDebugEvent> events,
237       DexItemFactory factory) {
238     int pcDelta = previousPc == NO_PC_INFO ? nextPc : nextPc - previousPc;
239     int lineDelta = nextLine == NO_LINE_INFO ? 0 : nextLine - previousLine;
240     assert pcDelta >= 0;
241     if (nextFile != previousFile) {
242       events.add(factory.createSetFile(nextFile));
243     }
244     if (lineDelta < Constants.DBG_LINE_BASE
245         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
246       events.add(factory.createAdvanceLine(lineDelta));
247       // TODO(herhut): To be super clever, encode only the part that is above limit.
248       lineDelta = 0;
249     }
250     if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
251       events.add(factory.createAdvancePC(pcDelta));
252       pcDelta = 0;
253     }
254     // TODO(herhut): Maybe only write this one if needed (would differ from DEX).
255     int specialOpcode =
256         0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
257     assert specialOpcode >= 0x0a;
258     assert specialOpcode <= 0xff;
259     events.add(factory.createDefault(specialOpcode));
260   }
261 
emitLocalChangeEvents( Int2ReferenceMap<DebugLocalInfo> previousLocals, Int2ReferenceMap<DebugLocalInfo> nextLocals, Int2ReferenceMap<DebugLocalInfo> lastKnownLocals, List<DexDebugEvent> events, DexItemFactory factory)262   public static void emitLocalChangeEvents(
263       Int2ReferenceMap<DebugLocalInfo> previousLocals,
264       Int2ReferenceMap<DebugLocalInfo> nextLocals,
265       Int2ReferenceMap<DebugLocalInfo> lastKnownLocals,
266       List<DexDebugEvent> events,
267       DexItemFactory factory) {
268     Int2ReferenceSortedMap<DebugLocalInfo> ending = new Int2ReferenceAVLTreeMap<>();
269     Int2ReferenceSortedMap<DebugLocalInfo> starting = new Int2ReferenceAVLTreeMap<>();
270     for (Entry<DebugLocalInfo> entry : previousLocals.int2ReferenceEntrySet()) {
271       int register = entry.getIntKey();
272       DebugLocalInfo local = entry.getValue();
273       if (nextLocals.get(register) != local) {
274         ending.put(register, local);
275       }
276     }
277     for (Entry<DebugLocalInfo> entry : nextLocals.int2ReferenceEntrySet()) {
278       int register = entry.getIntKey();
279       DebugLocalInfo local = entry.getValue();
280       if (previousLocals.get(register) != local) {
281         starting.put(register, local);
282       }
283     }
284     assert !ending.isEmpty() || !starting.isEmpty();
285     for (Entry<DebugLocalInfo> end : ending.int2ReferenceEntrySet()) {
286       int register = end.getIntKey();
287       if (!starting.containsKey(register)) {
288         previousLocals.remove(register);
289         events.add(factory.createEndLocal(register));
290       }
291     }
292     for (Entry<DebugLocalInfo> start : starting.int2ReferenceEntrySet()) {
293       int register = start.getIntKey();
294       DebugLocalInfo local = start.getValue();
295       previousLocals.put(register, local);
296       if (lastKnownLocals.get(register) == local) {
297         events.add(factory.createRestartLocal(register));
298       } else {
299         events.add(new StartLocal(register, local));
300         lastKnownLocals.put(register, local);
301       }
302     }
303   }
304 
localsEqual( Int2ReferenceMap<DebugLocalInfo> locals1, Int2ReferenceMap<DebugLocalInfo> locals2)305   private static boolean localsEqual(
306       Int2ReferenceMap<DebugLocalInfo> locals1, Int2ReferenceMap<DebugLocalInfo> locals2) {
307     if (locals1 == locals2) {
308       return true;
309     }
310     if (locals1.size() != locals2.size()) {
311       return false;
312     }
313     for (Int2ReferenceMap.Entry<DebugLocalInfo> entry : locals1.int2ReferenceEntrySet()) {
314       if (locals2.get(entry.getIntKey()) != entry.getValue()) {
315         return false;
316       }
317     }
318     return true;
319   }
320 }
321