blob: 26349c29851f0422833cd49f3534ec7725392c98 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
import com.android.tools.r8.graph.DexDebugEvent.Default;
import com.android.tools.r8.graph.DexDebugEvent.EndLocal;
import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
import com.android.tools.r8.graph.DexDebugEvent.SetFile;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.ir.code.DebugPosition;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Builder for constructing a list of debug events suitable for DexDebugInfo.
*
* This builder is intended to be very pedantic and ensure a well-formed structure of the resulting
* event stream.
*/
public class DexDebugEventBuilder {
private static final int NO_PC_INFO = -1;
private static final int NO_LINE_INFO = -1;
private static class PositionState {
int pc = NO_PC_INFO;
int line = NO_LINE_INFO;
DexString file = null;
ImmutableMap<Integer, DebugLocalInfo> locals = null;
}
private final DexMethod method;
private final DexItemFactory dexItemFactory;
// Previous and current position info to delay emitting position changes.
private final PositionState previous;
private final PositionState current;
// In order list of non-this argument locals.
private int lastArgumentRegister = -1;
private final List<DebugLocalInfo> arguments;
// Mapping from register to local for currently open/visible locals.
private final Map<Integer, DebugLocalInfo> openLocals = new HashMap<>();
// Mapping from register to the last known local in that register (See DBG_RESTART_LOCAL).
private final Map<Integer, DebugLocalInfo> lastKnownLocals = new HashMap<>();
// Flushed events.
private final List<DexDebugEvent> events = new ArrayList<>();
private int startLine = NO_LINE_INFO;
public DexDebugEventBuilder(DexMethod method, DexItemFactory dexItemFactory) {
this.method = method;
this.dexItemFactory = dexItemFactory;
arguments = new ArrayList<>(method.proto.parameters.values.length);
current = new PositionState();
previous = new PositionState();
}
public void startArgument(int register, DebugLocalInfo local, boolean isThis) {
// Verify that arguments are started in order.
assert register > lastArgumentRegister;
lastArgumentRegister = register;
// If this is an actual argument record it for header information.
if (!isThis) {
arguments.add(local);
}
// If the argument does not have a parametrized type, implicitly open it.
if (local != null && local.signature == null) {
openLocals.put(register, local);
lastKnownLocals.put(register, local);
}
}
/** Emits a positions entry if the position has changed and associates any local changes. */
public void setPosition(int pc, DebugPosition position) {
setPosition(pc, position.line, position.file, position.getLocals());
}
public void setPosition(
int pc, int line, DexString file, ImmutableMap<Integer, DebugLocalInfo> locals) {
// If we have a pending position and the next differs from it flush the pending one.
if (previous.pc != current.pc && positionChanged(current, pc, line, file)) {
flushCurrentPosition();
}
current.pc = pc;
current.line = line;
current.file = file;
current.locals = locals;
}
private void flushCurrentPosition() {
// If this is the first emitted possition, initialize previous state: start-line is forced to be
// the first actual line, in-effect, causing the first position to be a zero-delta line change.
if (startLine == NO_LINE_INFO) {
assert events.isEmpty();
assert previous.pc == NO_PC_INFO;
assert previous.line == NO_LINE_INFO;
startLine = current.line;
previous.line = current.line;
previous.pc = 0;
}
// Emit position change (which might result in additional advancement events).
emitAdvancementEvents();
// Emit local changes for new current position (they relate to the already emitted position).
// Locals are either defined on all positions or on none.
assert current.locals != null || previous.locals == null;
if (current.locals != null) {
emitLocalChanges();
}
}
/** Build the resulting DexDebugInfo object. */
public DexDebugInfo build() {
if (previous.pc != current.pc) {
flushCurrentPosition();
}
if (startLine == NO_LINE_INFO) {
return null;
}
DexString[] params = new DexString[method.proto.parameters.values.length];
assert arguments.isEmpty() || params.length == arguments.size();
for (int i = 0; i < arguments.size(); i++) {
DebugLocalInfo local = arguments.get(i);
params[i] = (local == null || local.signature != null) ? null : local.name;
}
return new DexDebugInfo(startLine, params, events.toArray(new DexDebugEvent[events.size()]));
}
private static boolean positionChanged(
PositionState current, int nextPc, int nextLine, DexString nextFile) {
return nextPc != current.pc && (nextLine != current.line || nextFile != current.file);
}
private void emitAdvancementEvents() {
int pcDelta = current.pc - previous.pc;
int lineDelta = current.line - previous.line;
assert pcDelta >= 0;
if (current.file != previous.file) {
assert current.file == null || !current.file.equals(previous.file);
events.add(dexItemFactory.createSetFile(current.file));
}
if (lineDelta < Constants.DBG_LINE_BASE
|| lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
events.add(dexItemFactory.createAdvanceLine(lineDelta));
// TODO(herhut): To be super clever, encode only the part that is above limit.
lineDelta = 0;
}
if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
events.add(dexItemFactory.createAdvancePC(pcDelta));
pcDelta = 0;
}
// TODO(herhut): Maybe only write this one if needed (would differ from DEX).
int specialOpcode =
0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
assert specialOpcode >= 0x0a;
assert specialOpcode <= 0xff;
events.add(dexItemFactory.createDefault(specialOpcode));
previous.pc = current.pc;
previous.line = current.line;
previous.file = current.file;
}
private void emitLocalChanges() {
if (previous.locals == current.locals) {
return;
}
SortedSet<Integer> currentRegisters = new TreeSet<>(openLocals.keySet());
SortedSet<Integer> positionRegisters = new TreeSet<>(current.locals.keySet());
for (Integer register : currentRegisters) {
if (!positionRegisters.contains(register)) {
events.add(dexItemFactory.createEndLocal(register));
openLocals.put(register, null);
}
}
for (Integer register : positionRegisters) {
DebugLocalInfo positionLocal = current.locals.get(register);
DebugLocalInfo currentLocal = openLocals.get(register);
if (currentLocal != positionLocal) {
openLocals.put(register, positionLocal);
if (currentLocal == null && lastKnownLocals.get(register) == positionLocal) {
events.add(dexItemFactory.createRestartLocal(register));
} else {
events.add(new StartLocal(register, positionLocal));
lastKnownLocals.put(register, positionLocal);
}
}
}
previous.locals = current.locals;
}
}