summaryrefslogtreecommitdiffstats
path: root/dx
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-02 22:54:18 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-02 22:54:18 -0800
commit6dcac3deb3c19dc634470eb30b2daedf2b201bd4 (patch)
tree2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /dx
parent077f9d75d9701778830742b9c2afad4742635b58 (diff)
downloadandroid_dalvik-6dcac3deb3c19dc634470eb30b2daedf2b201bd4.tar.gz
android_dalvik-6dcac3deb3c19dc634470eb30b2daedf2b201bd4.tar.bz2
android_dalvik-6dcac3deb3c19dc634470eb30b2daedf2b201bd4.zip
auto import from //depot/cupcake/@137055
Diffstat (limited to 'dx')
-rw-r--r--dx/src/com/android/dx/dex/code/LocalEnd.java7
-rw-r--r--dx/src/com/android/dx/dex/code/LocalList.java947
-rw-r--r--dx/src/com/android/dx/dex/file/DebugInfoDecoder.java340
-rw-r--r--dx/src/com/android/dx/dex/file/DebugInfoEncoder.java251
-rw-r--r--dx/src/com/android/dx/dex/file/DebugInfoItem.java40
-rw-r--r--dx/src/com/android/dx/rop/code/LocalItem.java13
-rw-r--r--dx/src/com/android/dx/rop/code/RegisterSpec.java114
-rw-r--r--dx/src/com/android/dx/rop/code/RegisterSpecSet.java57
8 files changed, 1210 insertions, 559 deletions
diff --git a/dx/src/com/android/dx/dex/code/LocalEnd.java b/dx/src/com/android/dx/dex/code/LocalEnd.java
index 87934dbd4..c19a8dcec 100644
--- a/dx/src/com/android/dx/dex/code/LocalEnd.java
+++ b/dx/src/com/android/dx/dex/code/LocalEnd.java
@@ -29,9 +29,10 @@ import com.android.dx.rop.code.SourcePosition;
public final class LocalEnd extends ZeroSizeInsn {
/**
* non-null; register spec representing the local variable ended
- * by this instance. <b>Note:</b> The only salient part of the spec
- * is the register number; the rest of the info may be useful for
- * debugging but shouldn't affect any actual processing
+ * by this instance. <b>Note:</b> Technically, only the register
+ * number needs to be recorded here as the rest of the information
+ * is implicit in the ambient local variable state, but other code
+ * will check the other info for consistency.
*/
private final RegisterSpec local;
diff --git a/dx/src/com/android/dx/dex/code/LocalList.java b/dx/src/com/android/dx/dex/code/LocalList.java
index d963fca08..4614fc41e 100644
--- a/dx/src/com/android/dx/dex/code/LocalList.java
+++ b/dx/src/com/android/dx/dex/code/LocalList.java
@@ -23,7 +23,9 @@ import com.android.dx.rop.cst.CstUtf8;
import com.android.dx.rop.type.Type;
import com.android.dx.util.FixedSizeList;
+import java.io.PrintStream;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* List of local variables. Each local variable entry indicates a
@@ -34,174 +36,9 @@ public final class LocalList extends FixedSizeList {
/** non-null; empty instance */
public static final LocalList EMPTY = new LocalList(0);
- /**
- * Constructs an instance for the given method, based on the given
- * block order and intermediate local information.
- *
- * @param insns non-null; instructions to convert
- * @return non-null; the constructed list
- */
- public static LocalList make(DalvInsnList insns) {
- ArrayList<Entry> result = new ArrayList<Entry>(100);
- int codeSize = insns.codeSize();
- int sz = insns.size();
- RegisterSpecSet state = null;
- int stateMax = 0;
-
- for (int i = 0; i < sz; i++) {
- DalvInsn insn = insns.get(i);
-
- if (insn instanceof LocalSnapshot) {
- RegisterSpecSet newState = ((LocalSnapshot) insn).getLocals();
- boolean first = (state == null);
-
- if (first) {
- stateMax = newState.getMaxSize();
- }
-
- for (int j = 0; j < stateMax; j++) {
- RegisterSpec oldSpec = first ? null : state.get(j);
- RegisterSpec newSpec = newState.get(j);
- boolean oldEnds = false;
- boolean newStarts = false;
-
- if (oldSpec == null) {
- if (newSpec != null) {
- /*
- * This is a newly-introduced local, not
- * replacing an existing local.
- */
- newStarts = true;
- }
- } else if (newSpec == null) {
- /*
- * This is a local going out of scope, with no
- * replacement.
- */
- oldEnds = true;
- } else if (!oldSpec.equals(newSpec)) {
- /*
- * This is a local going out of scope, immediately
- * replaced by a different local.
- */
- oldEnds = true;
- newStarts = true;
- }
-
- if (oldEnds) {
- endScope(result, oldSpec, insn.getAddress());
- }
-
- if (newStarts) {
- startScope(result, newSpec, insn.getAddress(),
- codeSize);
- }
- }
-
- state = newState;
- } else if (insn instanceof LocalStart) {
- RegisterSpec newSpec = ((LocalStart) insn).getLocal();
- RegisterSpec oldSpec = state.get(newSpec);
-
- boolean oldEnds = false;
- boolean newStarts = false;
-
- if (oldSpec == null) {
- /*
- * This is a newly-introduced local, not replacing an
- * existing local.
- */
- newStarts = true;
- } else if (!oldSpec.equals(newSpec)) {
- /*
- * This is a local going out of scope, immediately
- * replaced by a different local.
- */
- oldEnds = true;
- newStarts = true;
- }
-
- if (newStarts) {
- int address = insn.getAddress();
-
- if (oldEnds) {
- endScope(result, oldSpec, address);
- }
-
- startScope(result, newSpec, address, codeSize);
-
- if (state.isImmutable()) {
- state = state.mutableCopy();
- }
-
- state.put(newSpec);
- }
- }
- }
-
- int resultSz = result.size();
-
- if (resultSz == 0) {
- return EMPTY;
- }
-
- LocalList resultList = new LocalList(resultSz);
-
- for (int i = 0; i < resultSz; i++) {
- resultList.set(i, result.get(i));
- }
-
- resultList.setImmutable();
- return resultList;
- }
-
- /**
- * Helper for {@link #make}, to indicate that the given variable has
- * been introduced.
- *
- * @param result non-null; result in-progress
- * @param spec non-null; register spec for the variable in question
- * @param startAddress &gt;= 0; address at which the scope starts
- * (inclusive)
- * @param endAddress &gt; startAddress; initial scope end address
- * (exclusive)
- */
- private static void startScope(ArrayList<Entry> result, RegisterSpec spec,
- int startAddress, int endAddress) {
- result.add(new Entry(startAddress, endAddress, spec));
- }
-
- /**
- * Helper for {@link #make}, to indicate that the given variable's
- * scope has closed.
- *
- * @param result non-null; result in-progress
- * @param spec non-null; register spec for the variable in question
- * @param endAddress &gt;= 0; address at which the scope ends (exclusive)
- */
- private static void endScope(ArrayList<Entry> result, RegisterSpec spec,
- int endAddress) {
- int sz = result.size();
-
- for (int i = sz - 1; i >= 0; i--) {
- Entry e = result.get(i);
- if (e.matches(spec)) {
- if (e.getStart() == endAddress) {
- /*
- * It turns out that the indicated entry doesn't actually
- * cover any code.
- */
- result.remove(i);
- } else {
- result.set(i, e.withEnd(endAddress));
- }
- return;
- }
- }
-
- throw new RuntimeException("unmatched variable: " + spec);
- }
-
+ /** whether to run the self-check code */
+ private static final boolean DEBUG = false;
+
/**
* Constructs an instance. All indices initially contain <code>null</code>.
*
@@ -227,54 +64,86 @@ public final class LocalList extends FixedSizeList {
* Sets the entry at the given index.
*
* @param n &gt;= 0, &lt; size(); which index
- * @param start &gt;= 0; start address
- * @param end &gt; start; end address (exclusive)
- * @param spec non-null; register spec representing the variable
+ * @param entry non-null; the entry to set at <code>n</code>
*/
- public void set(int n, int start, int end, RegisterSpec spec) {
- set0(n, new Entry(start, end, spec));
+ public void set(int n, Entry entry) {
+ set0(n, entry);
}
/**
- * Sets the entry at the given index.
+ * Does a human-friendly dump of this instance.
*
- * @param n &gt;= 0, &lt; size(); which index
- * @param entry non-null; the entry to set at <code>n</code>
+ * @param out non-null; where to dump
+ * @param prefix non-null; prefix to attach to each line of output
*/
- public void set(int n, Entry entry) {
- set0(n, entry);
+ public void debugPrint(PrintStream out, String prefix) {
+ int sz = size();
+
+ for (int i = 0; i < sz; i++) {
+ out.print(prefix);
+ out.println(get(i));
+ }
+ }
+
+ /**
+ * Disposition of a local entry.
+ */
+ public static enum Disposition {
+ /** local started (introduced) */
+ START,
+
+ /** local ended without being replaced */
+ END_SIMPLY,
+
+ /** local ended because it was directly replaced */
+ END_REPLACED,
+
+ /** local ended because it was moved to a different register */
+ END_MOVED,
+
+ /**
+ * local ended because the previous local clobbered this one
+ * (because it is category-2)
+ */
+ END_CLOBBERED_BY_PREV,
+
+ /**
+ * local ended because the next local clobbered this one
+ * (because this one is a category-2)
+ */
+ END_CLOBBERED_BY_NEXT;
}
/**
* Entry in a local list.
*/
- public static class Entry {
- /** &gt;= 0; start address */
- private final int start;
+ public static class Entry implements Comparable<Entry> {
+ /** &gt;= 0; address */
+ private final int address;
- /** &gt; start; end address (exclusive) */
- private final int end;
+ /** non-null; disposition of the local */
+ private final Disposition disposition;
/** non-null; register spec representing the variable */
private final RegisterSpec spec;
- /** non-null; variable type */
+ /** non-null; variable type (derived from {@code spec}) */
private final CstType type;
-
+
/**
* Constructs an instance.
*
- * @param start &gt;= 0; start address
- * @param end &gt; start; end address (exclusive)
+ * @param address &gt;= 0; address
+ * @param disposition non-null; disposition of the local
* @param spec non-null; register spec representing the variable
*/
- public Entry(int start, int end, RegisterSpec spec) {
- if (start < 0) {
- throw new IllegalArgumentException("start < 0");
+ public Entry(int address, Disposition disposition, RegisterSpec spec) {
+ if (address < 0) {
+ throw new IllegalArgumentException("address < 0");
}
- if (end <= start) {
- throw new IllegalArgumentException("end <= start");
+ if (disposition == null) {
+ throw new NullPointerException("disposition == null");
}
try {
@@ -287,37 +156,78 @@ public final class LocalList extends FixedSizeList {
throw new NullPointerException("spec == null");
}
- this.start = start;
- this.end = end;
+ this.address = address;
+ this.disposition = disposition;
this.spec = spec;
+ this.type = CstType.intern(spec.getType());
+ }
- if (spec.getType() == Type.KNOWN_NULL) {
- /*
- * KNOWN_NULL's descriptor is '<null>', which we do
- * not want to emit. Everything else is as expected.
- */
- this.type = CstType.OBJECT;
- } else {
- this.type = CstType.intern(spec.getType());
+ /** {@inheritDoc} */
+ public String toString() {
+ return Integer.toHexString(address) + " " + disposition + " " +
+ spec;
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals(Object other) {
+ if (!(other instanceof Entry)) {
+ return false;
}
+
+ return (compareTo((Entry) other) == 0);
+ }
+
+ /**
+ * Compares by (in priority order) address, end then start
+ * disposition (variants of end are all consistered
+ * equivalent), and spec.
+ *
+ * @param other non-null; entry to compare to
+ * @return {@code -1..1}; standard result of comparison
+ */
+ public int compareTo(Entry other) {
+ if (address < other.address) {
+ return -1;
+ } else if (address > other.address) {
+ return 1;
+ }
+
+ boolean thisIsStart = isStart();
+ boolean otherIsStart = other.isStart();
+
+ if (thisIsStart != otherIsStart) {
+ return thisIsStart ? 1 : -1;
+ }
+
+ return spec.compareTo(other.spec);
}
/**
- * Gets the start address.
+ * Gets the address.
*
- * @return &gt;= 0; the start address
+ * @return &gt;= 0; the address
*/
- public int getStart() {
- return start;
+ public int getAddress() {
+ return address;
}
/**
- * Gets the end address (exclusive).
+ * Gets the disposition.
*
- * @return &gt; start; the end address (exclusive)
+ * @return non-null; the disposition
*/
- public int getEnd() {
- return end;
+ public Disposition getDisposition() {
+ return disposition;
+ }
+
+ /**
+ * Gets whether this is a local start. This is just shorthand for
+ * {@code getDisposition() == Disposition.START}.
+ *
+ * @return {@code true} iff this is a start
+ */
+ public boolean isStart() {
+ return disposition == Disposition.START;
}
/**
@@ -372,8 +282,8 @@ public final class LocalList extends FixedSizeList {
* @return <code>true</code> iff this instance matches
* <code>spec</code>
*/
- public boolean matches(RegisterSpec spec) {
- return spec.equals(this.spec);
+ public boolean matches(RegisterSpec otherSpec) {
+ return spec.equalsUsingSimpleType(otherSpec);
}
/**
@@ -385,18 +295,619 @@ public final class LocalList extends FixedSizeList {
* <code>other</code>
*/
public boolean matches(Entry other) {
- return other.spec.equals(this.spec);
+ return matches(other.spec);
}
/**
- * Returns an instance just like this one, except with the end
- * address altered to be the one given.
+ * Returns an instance just like this one but with the disposition
+ * set as given
*
- * @param newEnd &gt; getStart(); the end address of the new instance
+ * @param disposition non-null; the new disposition
* @return non-null; an appropriately-constructed instance
*/
- public Entry withEnd(int newEnd) {
- return new Entry(start, newEnd, spec);
+ public Entry withDisposition(Disposition disposition) {
+ if (disposition == this.disposition) {
+ return this;
+ }
+
+ return new Entry(address, disposition, spec);
+ }
+ }
+
+ /**
+ * Constructs an instance for the given method, based on the given
+ * block order and intermediate local information.
+ *
+ * @param insns non-null; instructions to convert
+ * @return non-null; the constructed list
+ */
+ public static LocalList make(DalvInsnList insns) {
+ int sz = insns.size();
+
+ /*
+ * Go through the insn list, looking for all the local
+ * variable pseudoinstructions, splitting out LocalSnapshots
+ * into separate per-variable starts, adding explicit ends
+ * wherever a variable is replaced or moved, and collecting
+ * these and all the other local variable "activity"
+ * together into an output list (without the other insns).
+ *
+ * Note: As of this writing, this method won't be handed any
+ * insn lists that contain local ends, but I (danfuzz) expect
+ * that to change at some point, when we start feeding that
+ * info explicitly into the rop layer rather than only trying
+ * to infer it. So, given that expectation, this code is
+ * written to deal with them.
+ */
+
+ MakeState state = new MakeState(sz);
+
+ for (int i = 0; i < sz; i++) {
+ DalvInsn insn = insns.get(i);
+
+ if (insn instanceof LocalSnapshot) {
+ RegisterSpecSet snapshot =
+ ((LocalSnapshot) insn).getLocals();
+ state.snapshot(insn.getAddress(), snapshot);
+ } else if (insn instanceof LocalStart) {
+ RegisterSpec local = ((LocalStart) insn).getLocal();
+ state.startLocal(insn.getAddress(), local);
+ } else if (insn instanceof LocalEnd) {
+ RegisterSpec local = ((LocalEnd) insn).getLocal();
+ state.endLocal(insn.getAddress(), local);
+ }
+ }
+
+ LocalList result = state.finish();
+
+ if (DEBUG) {
+ debugVerify(result);
}
+
+ return result;
+ }
+
+ /**
+ * Debugging helper that verifies the constraint that a list doesn't
+ * contain any redundant local starts and that local ends that are
+ * due to replacements are properly annotated.
+ */
+ private static void debugVerify(LocalList locals) {
+ try {
+ debugVerify0(locals);
+ } catch (RuntimeException ex) {
+ int sz = locals.size();
+ for (int i = 0; i < sz; i++) {
+ System.err.println(locals.get(i));
+ }
+ throw ex;
+ }
+
}
+
+ /**
+ * Helper for {@link #debugVerify} which does most of the work.
+ */
+ private static void debugVerify0(LocalList locals) {
+ int sz = locals.size();
+ Entry[] active = new Entry[65536];
+
+ for (int i = 0; i < sz; i++) {
+ Entry e = locals.get(i);
+ int reg = e.getRegister();
+
+ if (e.isStart()) {
+ Entry already = active[reg];
+
+ if ((already != null) && e.matches(already)) {
+ throw new RuntimeException("redundant start at " +
+ Integer.toHexString(e.getAddress()) + ": got " +
+ e + "; had " + already);
+ }
+
+ active[reg] = e;
+ } else {
+ if (active[reg] == null) {
+ throw new RuntimeException("redundant end at " +
+ Integer.toHexString(e.getAddress()));
+ }
+
+ int addr = e.getAddress();
+ boolean foundStart = false;
+
+ for (int j = i + 1; j < sz; j++) {
+ Entry test = locals.get(j);
+ if (test.getAddress() != addr) {
+ break;
+ }
+ if (test.getRegisterSpec().getReg() == reg) {
+ if (test.isStart()) {
+ if (e.getDisposition()
+ != Disposition.END_REPLACED) {
+ throw new RuntimeException(
+ "improperly marked end at " +
+ Integer.toHexString(addr));
+ }
+ foundStart = true;
+ } else {
+ throw new RuntimeException(
+ "redundant end at " +
+ Integer.toHexString(addr));
+ }
+ }
+ }
+
+ if (!foundStart &&
+ (e.getDisposition() == Disposition.END_REPLACED)) {
+ throw new RuntimeException(
+ "improper end replacement claim at " +
+ Integer.toHexString(addr));
+ }
+
+ active[reg] = null;
+ }
+ }
+ }
+
+ /**
+ * Intermediate state when constructing a local list.
+ */
+ public static class MakeState {
+ /** non-null; result being collected */
+ private final ArrayList<Entry> result;
+
+ /**
+ * &gt;= 0; running count of nulled result entries, to help with
+ * sizing the final list
+ */
+ private int nullResultCount;
+
+ /** null-ok; current register mappings */
+ private RegisterSpecSet regs;
+
+ /** null-ok; result indices where local ends are stored */
+ private int[] endIndices;
+
+ /** &gt;= 0; last address seen */
+ private int lastAddress;
+
+ /**
+ * &gt;= 0; result index where the first element for the most
+ * recent address is stored
+ */
+ private int startIndexForAddress;
+
+ /**
+ * Constructs an instance.
+ */
+ public MakeState(int initialSize) {
+ result = new ArrayList<Entry>(initialSize);
+ nullResultCount = 0;
+ regs = null;
+ endIndices = null;
+ lastAddress = 0;
+ startIndexForAddress = 0;
+ }
+
+ /**
+ * Checks the address and other vitals as a prerequisite to
+ * further processing.
+ *
+ * @param address &gt;= 0; address about to be processed
+ * @param reg &gt;= 0; register number about to be processed
+ */
+ private void aboutToProcess(int address, int reg) {
+ boolean first = (endIndices == null);
+
+ if ((address == lastAddress) && !first) {
+ return;
+ }
+
+ if (address < lastAddress) {
+ throw new RuntimeException("shouldn't happen");
+ }
+
+ if (first || (reg >= endIndices.length)) {
+ /*
+ * This is the first allocation of the state set and
+ * index array, or we need to grow. (The latter doesn't
+ * happen much; in fact, we have only ever observed
+ * it happening in test cases, never in "real" code.)
+ */
+ int newSz = reg + 1;
+ RegisterSpecSet newRegs = new RegisterSpecSet(newSz);
+ int[] newEnds = new int[newSz];
+ Arrays.fill(newEnds, -1);
+
+ if (!first) {
+ newRegs.putAll(regs);
+ System.arraycopy(endIndices, 0, newEnds, 0,
+ endIndices.length);
+ }
+
+ regs = newRegs;
+ endIndices = newEnds;
+ }
+ }
+
+ /**
+ * Sets the local state at the given address to the given snapshot.
+ * The first call on this instance must be to this method, so that
+ * the register state can be properly sized.
+ *
+ * @param address &gt;= 0; the address
+ * @param specs non-null; spec set representing the locals
+ */
+ public void snapshot(int address, RegisterSpecSet specs) {
+ int sz = specs.getMaxSize();
+ aboutToProcess(address, sz - 1);
+
+ for (int i = 0; i < sz; i++) {
+ RegisterSpec oldSpec = regs.get(i);
+ RegisterSpec newSpec = filterSpec(specs.get(i));
+
+ if (oldSpec == null) {
+ if (newSpec != null) {
+ startLocal(address, newSpec);
+ }
+ } else if (newSpec == null) {
+ endLocal(address, oldSpec);
+ } else if (! newSpec.equalsUsingSimpleType(oldSpec)) {
+ endLocal(address, oldSpec);
+ startLocal(address, newSpec);
+ }
+ }
+ }
+
+ /**
+ * Starts a local at the given address.
+ *
+ * @param address &gt;= 0; the address
+ * @param startedLocal non-null; spec representing the started local
+ */
+ public void startLocal(int address, RegisterSpec startedLocal) {
+ int regNum = startedLocal.getReg();
+
+ startedLocal = filterSpec(startedLocal);
+ aboutToProcess(address, regNum);
+
+ RegisterSpec existingLocal = regs.get(regNum);
+
+ if (startedLocal.equalsUsingSimpleType(existingLocal)) {
+ // Silently ignore a redundant start.
+ return;
+ }
+
+ RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal);
+ if (movedLocal != null) {
+ /*
+ * The same variable was moved from one register to another.
+ * So add an end for its old location.
+ */
+ addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal);
+ }
+
+ int endAt = endIndices[regNum];
+
+ if (existingLocal != null) {
+ /*
+ * There is an existing (but non-matching) local.
+ * Add an explicit end for it.
+ */
+ add(address, Disposition.END_REPLACED, existingLocal);
+ } else if (endAt >= 0) {
+ /*
+ * Look for an end local for the same register at the
+ * same address. If found, then update it or delete
+ * it, depending on whether or not it represents the
+ * same variable as the one being started.
+ */
+ Entry endEntry = result.get(endAt);
+ if (endEntry.getAddress() == address) {
+ if (endEntry.matches(startedLocal)) {
+ /*
+ * There was already an end local for the same
+ * variable at the same address. This turns
+ * out to be superfluous, as we are starting
+ * up the exact same local. This situation can
+ * happen when a single local variable got
+ * somehow "split up" during intermediate
+ * processing. In any case, rather than represent
+ * the end-then-start, just remove the old end.
+ */
+ result.set(endAt, null);
+ nullResultCount++;
+ regs.put(startedLocal);
+ endIndices[regNum] = -1;
+ return;
+ } else {
+ /*
+ * There was a different variable ended at the
+ * same address. Update it to indicate that
+ * it was ended due to a replacement (rather than
+ * ending for no particular reason).
+ */
+ endEntry = endEntry.withDisposition(
+ Disposition.END_REPLACED);
+ result.set(endAt, endEntry);
+ }
+ }
+ }
+
+ /*
+ * The code above didn't find and remove an unnecessary
+ * local end, so we now have to add one or more entries to
+ * the output to capture the transition.
+ */
+
+ /*
+ * If the local just below (in the register set at reg-1)
+ * is of category-2, then it is ended by this new start.
+ */
+ if (regNum > 0) {
+ RegisterSpec justBelow = regs.get(regNum - 1);
+ if ((justBelow != null) && justBelow.isCategory2()) {
+ addOrUpdateEnd(address,
+ Disposition.END_CLOBBERED_BY_NEXT,
+ justBelow);
+ }
+ }
+
+ /*
+ * Similarly, if this local is category-2, then the local
+ * just above (if any) is ended by the start now being
+ * emitted.
+ */
+ if (startedLocal.isCategory2()) {
+ RegisterSpec justAbove = regs.get(regNum + 1);
+ if (justAbove != null) {
+ addOrUpdateEnd(address,
+ Disposition.END_CLOBBERED_BY_PREV,
+ justAbove);
+ }
+ }
+
+ /*
+ * TODO: Add an end for the same local in a different reg,
+ * if any (that is, if the local migrates from vX to vY,
+ * we should note that as a local end in vX).
+ */
+
+ add(address, Disposition.START, startedLocal);
+ }
+
+ /**
+ * Ends a local at the given address.
+ *
+ * @param address &gt;= 0; the address
+ * @param endedLocal non-null; spec representing the local being ended
+ */
+ public void endLocal(int address, RegisterSpec endedLocal) {
+ int regNum = endedLocal.getReg();
+
+ endedLocal = filterSpec(endedLocal);
+ aboutToProcess(address, regNum);
+
+ int endAt = endIndices[regNum];
+
+ if (endAt >= 0) {
+ /*
+ * The local in the given register is already ended.
+ * Silently return without adding anything to the result.
+ */
+ return;
+ }
+
+ // Check for start and end at the same address.
+ if (checkForEmptyRange(address, endedLocal)) {
+ return;
+ }
+
+ add(address, Disposition.END_SIMPLY, endedLocal);
+ }
+
+ /**
+ * Helper for {@link #endLocal}, which handles the cases where
+ * and end local is issued at the same address as a start local
+ * for the same register. If this case is found, then this
+ * method will remove the start (as the local was never actually
+ * active), update the {@link #endIndices} to be accurate, and
+ * if needed update the newly-active end to reflect an altered
+ * disposition.
+ *
+ * @param address &gt;= 0; the address
+ * @param endedLocal non-null; spec representing the local being ended
+ * @return {@code true} iff this method found the case in question
+ * and adjusted things accordingly
+ */
+ private boolean checkForEmptyRange(int address,
+ RegisterSpec endedLocal) {
+ int at = result.size() - 1;
+ Entry entry;
+
+ // Look for a previous entry at the same address.
+ for (/*at*/; at >= 0; at--) {
+ entry = result.get(at);
+
+ if (entry == null) {
+ continue;
+ }
+
+ if (entry.getAddress() != address) {
+ // We didn't find any match at the same address.
+ return false;
+ }
+
+ if (entry.matches(endedLocal)) {
+ break;
+ }
+ }
+
+ /*
+ * In fact, we found that the endedLocal had started at the
+ * same address, so do all the requisite cleanup.
+ */
+
+ regs.remove(endedLocal);
+ result.set(at, null);
+ nullResultCount++;
+
+ int regNum = endedLocal.getReg();
+ boolean found = false;
+ entry = null;
+
+ // Now look back further to update where the register ended.
+ for (at--; at >= 0; at--) {
+ entry = result.get(at);
+
+ if (entry == null) {
+ continue;
+ }
+
+ if (entry.getRegisterSpec().getReg() == regNum) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ // We found an end for the same register.
+ endIndices[regNum] = at;
+
+ if (entry.getAddress() == address) {
+ /*
+ * It's still the same address, so update the
+ * disposition.
+ */
+ result.set(at,
+ entry.withDisposition(Disposition.END_SIMPLY));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Converts a given spec into the form acceptable for use in a
+ * local list. This, in particular, transforms the "known
+ * null" type into simply {@code Object}. This method needs to
+ * be called for any spec that is on its way into a locals
+ * list.
+ *
+ * <p>This isn't necessarily the cleanest way to achieve the
+ * goal of not representing known nulls in a locals list, but
+ * it gets the job done.</p>
+ *
+ * @param orig null-ok; the original spec
+ * @return null-ok; an appropriately modified spec, or the
+ * original if nothing needs to be done
+ */
+ private static RegisterSpec filterSpec(RegisterSpec orig) {
+ if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) {
+ return orig.withType(Type.OBJECT);
+ }
+
+ return orig;
+ }
+
+ /**
+ * Adds an entry to the result, updating the adjunct tables
+ * accordingly.
+ *
+ * @param address &gt;= 0; the address
+ * @param disposition non-null; the disposition
+ * @param spec non-null; spec representing the local
+ */
+ private void add(int address, Disposition disposition,
+ RegisterSpec spec) {
+ int regNum = spec.getReg();
+
+ result.add(new Entry(address, disposition, spec));
+
+ if (disposition == Disposition.START) {
+ regs.put(spec);
+ endIndices[regNum] = -1;
+ } else {
+ regs.remove(spec);
+ endIndices[regNum] = result.size() - 1;
+ }
+ }
+
+ /**
+ * Adds or updates an end local (changing its disposition).
+ *
+ * @param address &gt;= 0; the address
+ * @param disposition non-null; the disposition
+ * @param spec non-null; spec representing the local
+ */
+ private void addOrUpdateEnd(int address, Disposition disposition,
+ RegisterSpec spec) {
+ if (disposition == Disposition.START) {
+ throw new RuntimeException("shouldn't happen");
+ }
+
+ int regNum = spec.getReg();
+ int endAt = endIndices[regNum];
+
+ if (endAt >= 0) {
+ Entry endEntry = result.get(endAt);
+ if ((endEntry.getAddress() == address) &&
+ endEntry.getRegisterSpec().equals(spec)) {
+ result.set(endAt, endEntry.withDisposition(disposition));
+ regs.remove(spec);
+ return;
+ }
+ }
+
+ add(address, disposition, spec);
+ }
+
+ /**
+ * Finishes processing altogether and gets the result.
+ *
+ * @return non-null; the result list
+ */
+ public LocalList finish() {
+ aboutToProcess(Integer.MAX_VALUE, 0);
+
+ int resultSz = result.size();
+ int finalSz = resultSz - nullResultCount;
+
+ if (finalSz == 0) {
+ return EMPTY;
+ }
+
+ /*
+ * Collect an array of only the non-null entries, and then
+ * sort it to get a consistent order for everything: Local
+ * ends and starts for a given address could come in any
+ * order, but we want ends before starts as well as
+ * registers in order (within ends or starts).
+ */
+
+ Entry[] resultArr = new Entry[finalSz];
+
+ if (resultSz == finalSz) {
+ result.toArray(resultArr);
+ } else {
+ int at = 0;
+ for (Entry e : result) {
+ if (e != null) {
+ resultArr[at++] = e;
+ }
+ }
+ }
+
+ Arrays.sort(resultArr);
+
+ LocalList resultList = new LocalList(finalSz);
+
+ for (int i = 0; i < finalSz; i++) {
+ resultList.set(i, resultArr[i]);
+ }
+
+ resultList.setImmutable();
+ return resultList;
+ }
+ }
}
diff --git a/dx/src/com/android/dx/dex/file/DebugInfoDecoder.java b/dx/src/com/android/dx/dex/file/DebugInfoDecoder.java
index 519452b64..3ffd27696 100644
--- a/dx/src/com/android/dx/dex/file/DebugInfoDecoder.java
+++ b/dx/src/com/android/dx/dex/file/DebugInfoDecoder.java
@@ -16,6 +16,8 @@
package com.android.dx.dex.file;
+import com.android.dx.dex.code.DalvCode;
+import com.android.dx.dex.code.DalvInsnList;
import com.android.dx.dex.code.LocalList;
import com.android.dx.dex.code.PositionList;
import com.android.dx.rop.cst.CstMethodRef;
@@ -41,20 +43,28 @@ import static com.android.dx.dex.file.DebugInfoConstants.*;
public class DebugInfoDecoder {
/** encoded debug info */
private final byte[] encoded;
+
/** positions decoded */
private final ArrayList<PositionEntry> positions;
+
/** locals decoded */
private final ArrayList<LocalEntry> locals;
+
/** size of code block in code units */
private final int codesize;
+
/** indexed by register, the last local variable live in a reg */
private final LocalEntry[] lastEntryForReg;
+
/** method descriptor of method this debug info is for */
private final Prototype desc;
+
/** true if method is static */
private final boolean isStatic;
+
/** dex file this debug info will be stored in */
private final DexFile file;
+
/**
* register size, in register units, of the register space
* used by this method
@@ -81,8 +91,11 @@ public class DebugInfoDecoder {
* @param ref method descriptor of method this debug info is for
* @param file dex file this debug info will be stored in
*/
- DebugInfoDecoder (byte[] encoded, int codesize, int regSize,
+ DebugInfoDecoder(byte[] encoded, int codesize, int regSize,
boolean isStatic, CstMethodRef ref, DexFile file) {
+ if (encoded == null) {
+ throw new NullPointerException("encoded == null");
+ }
this.encoded = encoded;
this.isStatic = isStatic;
@@ -90,24 +103,37 @@ public class DebugInfoDecoder {
this.file = file;
this.regSize = regSize;
- positions = new ArrayList();
- locals = new ArrayList();
+ positions = new ArrayList<PositionEntry>();
+ locals = new ArrayList<LocalEntry>();
this.codesize = codesize;
lastEntryForReg = new LocalEntry[regSize];
- thisStringIdx = file.getStringIds().indexOf(new CstUtf8("this"));
+ int idx = -1;
+
+ try {
+ idx = file.getStringIds().indexOf(new CstUtf8("this"));
+ } catch (IllegalArgumentException ex) {
+ /*
+ * Silently tolerate not finding "this". It just means that
+ * no method has local variable info that looks like
+ * a standard instance method.
+ */
+ }
+
+ thisStringIdx = idx;
}
/**
* An entry in the resulting postions table
*/
- static class PositionEntry {
+ static private class PositionEntry {
/** bytecode address */
- int address;
+ public int address;
+
/** line number */
- int line;
+ public int line;
- PositionEntry(int address, int line) {
+ public PositionEntry(int address, int line) {
this.address = address;
this.line = line;
}
@@ -116,37 +142,40 @@ public class DebugInfoDecoder {
/**
* An entry in the resulting locals table
*/
- static class LocalEntry {
- LocalEntry(int start, int reg, int nameIndex, int typeIndex,
- int signatureIndex) {
- this.start = start;
- this.reg = reg;
- this.nameIndex = nameIndex;
- this.typeIndex = typeIndex;
- this.signatureIndex = signatureIndex;
- }
+ static private class LocalEntry {
+ /** address of event */
+ public int address;
- /** start of address range */
- int start;
-
- /**
- * End of address range. Initialized to MAX_VALUE here but will
- * be set to no more than 1 + max bytecode address of method.
- */
- int end = Integer.MAX_VALUE;
+ /** {@code true} iff it's a local start */
+ public boolean isStart;
/** register number */
- int reg;
+ public int reg;
/** index of name in strings table */
- int nameIndex;
+ public int nameIndex;
/** index of type in types table */
- int typeIndex;
+ public int typeIndex;
/** index of type signature in strings table */
- int signatureIndex;
+ public int signatureIndex;
+ public LocalEntry(int address, boolean isStart, int reg, int nameIndex,
+ int typeIndex, int signatureIndex) {
+ this.address = address;
+ this.isStart = isStart;
+ this.reg = reg;
+ this.nameIndex = nameIndex;
+ this.typeIndex = typeIndex;
+ this.signatureIndex = signatureIndex;
+ }
+
+ public String toString() {
+ return String.format("[%x %s v%d %04x %04x %04x]",
+ address, isStart ? "start" : "end", reg,
+ nameIndex, typeIndex, signatureIndex);
+ }
}
/**
@@ -166,13 +195,6 @@ public class DebugInfoDecoder {
* @return locals list in ascending address order.
*/
public List<LocalEntry> getLocals() {
- // TODO move this loop:
- // Any variable that didnt end ends now
- for (LocalEntry local: locals) {
- if (local.end == Integer.MAX_VALUE) {
- local.end = codesize;
- }
- }
return locals;
}
@@ -229,8 +251,8 @@ public class DebugInfoDecoder {
if (!isStatic) {
// Start off with implicit 'this' entry
- LocalEntry thisEntry
- = new LocalEntry(0, curReg, thisStringIdx, 0, 0);
+ LocalEntry thisEntry =
+ new LocalEntry(0, true, curReg, thisStringIdx, 0, 0);
locals.add(thisEntry);
lastEntryForReg[curReg] = thisEntry;
curReg++;
@@ -242,15 +264,19 @@ public class DebugInfoDecoder {
int nameIdx = readStringIndex(bs);
- if(nameIdx == -1) {
- // unnamed parameter
+ if (nameIdx == -1) {
+ /*
+ * Unnamed parameter; often but not always filled in by an
+ * extended start op after the prologue
+ */
+ le = new LocalEntry(0, true, curReg, -1, 0, 0);
} else {
- // final '0' should be idx of paramType.getDescriptor()
- le = new LocalEntry(0, curReg, nameIdx, 0, 0);
- locals.add(le);
- lastEntryForReg[curReg] = le;
+ // TODO: Final 0 should be idx of paramType.getDescriptor().
+ le = new LocalEntry(0, true, curReg, nameIdx, 0, 0);
}
+ locals.add(le);
+ lastEntryForReg[curReg] = le;
curReg += paramType.getCategory();
}
@@ -269,15 +295,7 @@ public class DebugInfoDecoder {
int nameIdx = readStringIndex(bs);
int typeIdx = readStringIndex(bs);
LocalEntry le = new LocalEntry(
- address, reg, nameIdx, typeIdx, 0);
-
- // a "start" is implicitly the "end" of whatever was
- // previously defined in the register
- if (lastEntryForReg[reg] != null
- && lastEntryForReg[reg].end == Integer.MAX_VALUE) {
-
- lastEntryForReg[reg].end = address;
- }
+ address, true, reg, nameIdx, typeIdx, 0);
locals.add(le);
lastEntryForReg[reg] = le;
@@ -290,21 +308,7 @@ public class DebugInfoDecoder {
int typeIdx = readStringIndex(bs);
int sigIdx = readStringIndex(bs);
LocalEntry le = new LocalEntry(
- address, reg, nameIdx, typeIdx, sigIdx);
-
- // a "start" is implicitly the "end" of whatever was
- // previously defined in the register
- if (lastEntryForReg[reg] != null
- && lastEntryForReg[reg].end == Integer.MAX_VALUE) {
-
- lastEntryForReg[reg].end = address;
-
- // A 0-length entry. Almost certainly a "this"
- // with a signature.
- if (lastEntryForReg[reg].start == address) {
- locals.remove(lastEntryForReg[reg]);
- }
- }
+ address, true, reg, nameIdx, typeIdx, sigIdx);
locals.add(le);
lastEntryForReg[reg] = le;
@@ -319,16 +323,17 @@ public class DebugInfoDecoder {
try {
prevle = lastEntryForReg[reg];
- if (lastEntryForReg[reg].end == Integer.MAX_VALUE) {
- throw new RuntimeException ("nonsensical "
- + "RESTART_LOCAL on live register v"+reg);
+ if (prevle.isStart) {
+ throw new RuntimeException("nonsensical "
+ + "RESTART_LOCAL on live register v"
+ + reg);
}
- le = new LocalEntry(address, reg,
- prevle.nameIndex, prevle.typeIndex, 0);
+ le = new LocalEntry(address, true, reg,
+ prevle.nameIndex, prevle.typeIndex, 0);
} catch (NullPointerException ex) {
- throw new RuntimeException
- ("Encountered RESTART_LOCAL on new v" +reg);
+ throw new RuntimeException(
+ "Encountered RESTART_LOCAL on new v" + reg);
}
locals.add(le);
@@ -338,20 +343,27 @@ public class DebugInfoDecoder {
case DBG_END_LOCAL: {
int reg = readUnsignedLeb128(bs);
- boolean found = false;
- for (int i = locals.size() - 1; i >= 0; i--) {
- if (locals.get(i).reg == reg) {
- locals.get(i).end = address;
- found = true;
- break;
- }
- }
+ LocalEntry prevle;
+ LocalEntry le;
- if (!found) {
+ try {
+ prevle = lastEntryForReg[reg];
+
+ if (!prevle.isStart) {
+ throw new RuntimeException("nonsensical "
+ + "END_LOCAL on dead register v" + reg);
+ }
+
+ le = new LocalEntry(address, false, reg,
+ prevle.nameIndex, prevle.typeIndex,
+ prevle.signatureIndex);
+ } catch (NullPointerException ex) {
throw new RuntimeException(
- "Encountered LOCAL_END without local start: v"
- + reg);
+ "Encountered END_LOCAL on new v" + reg);
}
+
+ locals.add(le);
+ lastEntryForReg[reg] = le;
}
break;
@@ -403,40 +415,48 @@ public class DebugInfoDecoder {
* throwing an exception if they do not match. Used to validate the
* encoder.
*
- * @param linecodes encoded debug info
- * @param codeSize size of insn block in code units
- * @param countRegisters size of used register block in register units
- * @param pl position list to verify against
- * @param ll locals list to verify against.
+ * @param info encoded debug info
+ * @param file non-null; file to refer to during decoding
+ * @param ref non-null; method whose info is being decoded
+ * @param code non-null; original code object that was encoded
+ * @param isStatic whether the method is static
*/
- public static void validateEncode(byte[] linecodes, int codeSize,
- int countRegisters, PositionList pl, LocalList ll,
- boolean isStatic, CstMethodRef ref, DexFile file) {
-
+ public static void validateEncode(byte[] info, DexFile file,
+ CstMethodRef ref, DalvCode code, boolean isStatic) {
+ PositionList pl = code.getPositions();
+ LocalList ll = code.getLocals();
+ DalvInsnList insns = code.getInsns();
+ int codeSize = insns.codeSize();
+ int countRegisters = insns.getRegistersSize();
+
try {
- validateEncode0(linecodes, codeSize, countRegisters,
+ validateEncode0(info, codeSize, countRegisters,
isStatic, ref, file, pl, ll);
} catch (RuntimeException ex) {
-// System.err.println(ex.toString()
-// + " while processing " + ref.toHuman());
+ System.err.println("instructions:");
+ insns.debugPrint(System.err, " ", true);
+ System.err.println("local list:");
+ ll.debugPrint(System.err, " ");
throw ExceptionWithContext.withContext(ex,
"while processing " + ref.toHuman());
}
}
-
- private static void validateEncode0(byte[] linecodes, int codeSize,
+ private static void validateEncode0(byte[] info, int codeSize,
int countRegisters, boolean isStatic, CstMethodRef ref,
DexFile file, PositionList pl, LocalList ll) {
DebugInfoDecoder decoder
- = new DebugInfoDecoder(linecodes, codeSize, countRegisters,
+ = new DebugInfoDecoder(info, codeSize, countRegisters,
isStatic, ref, file);
decoder.decode();
- List<PositionEntry> decodedEntries;
+ /*
+ * Go through the decoded position entries, matching up
+ * with original entries.
+ */
- decodedEntries = decoder.getPositionList();
+ List<PositionEntry> decodedEntries = decoder.getPositionList();
if (decodedEntries.size() != pl.size()) {
throw new RuntimeException(
@@ -444,7 +464,7 @@ public class DebugInfoDecoder {
+ decodedEntries.size() + " expected " + pl.size());
}
- for (PositionEntry entry: decodedEntries) {
+ for (PositionEntry entry : decodedEntries) {
boolean found = false;
for (int i = pl.size() - 1; i >= 0; i--) {
PositionList.Entry ple = pl.get(i);
@@ -462,41 +482,111 @@ public class DebugInfoDecoder {
}
}
- List<LocalEntry> decodedLocals;
-
- decodedLocals = decoder.getLocals();
+ /*
+ * Go through the original local list, in order, matching up
+ * with decoded entries.
+ */
+ List<LocalEntry> decodedLocals = decoder.getLocals();
+ int thisStringIdx = decoder.thisStringIdx;
+ int decodedSz = decodedLocals.size();
int paramBase = decoder.getParamBase();
- int matchedLocalsEntries = 0;
+ /*
+ * Preflight to fill in any parameters that were skipped in
+ * the prologue (including an implied "this") but then
+ * identified by full signature.
+ */
+ for (int i = 0; i < decodedSz; i++) {
+ LocalEntry entry = decodedLocals.get(i);
+ int idx = entry.nameIndex;
+
+ if ((idx < 0) || (idx == thisStringIdx)) {
+ for (int j = i + 1; j < decodedSz; j++) {
+ LocalEntry e2 = decodedLocals.get(j);
+ if (e2.address != 0) {
+ break;
+ }
+ if ((entry.reg == e2.reg) && e2.isStart) {
+ decodedLocals.set(i, e2);
+ decodedLocals.remove(j);
+ decodedSz--;
+ break;
+ }
+ }
+ }
+ }
+
+ int origSz = ll.size();
+ int decodeAt = 0;
+ boolean problem = false;
- for (LocalEntry entry: decodedLocals) {
- boolean found = false;
- for (int i = ll.size() - 1; i >= 0; i--) {
- LocalList.Entry le = ll.get(i);
+ for (int i = 0; i < origSz; i++) {
+ LocalList.Entry origEntry = ll.get(i);
+ if (origEntry.getDisposition()
+ == LocalList.Disposition.END_REPLACED) {
/*
- * If an entry is a method parameter, then the original
- * entry may not be marked as starting at 0. However, the
- * end address should still match.
+ * The encoded list doesn't represent replacements, so
+ * ignore them for the sake of comparison.
*/
- if ((entry.start == le.getStart()
- || (entry.start == 0 && entry.reg >= paramBase))
- && entry.end == le.getEnd()
- && entry.reg == le.getRegister()) {
- found = true;
- matchedLocalsEntries++;
+ continue;
+ }
+
+ LocalEntry decodedEntry;
+
+ do {
+ decodedEntry = decodedLocals.get(decodeAt);
+ if (decodedEntry.nameIndex >= 0) {
break;
}
+ /*
+ * A negative name index means this is an anonymous
+ * parameter, and we shouldn't expect to see it in the
+ * original list. So, skip it.
+ */
+ decodeAt++;
+ } while (decodeAt < decodedSz);
+
+ int decodedAddress = decodedEntry.address;
+
+ if (decodedEntry.reg != origEntry.getRegister()) {
+ System.err.println("local register mismatch at orig " + i +
+ " / decoded " + decodeAt);
+ problem = true;
+ break;
+ }
+
+ if (decodedEntry.isStart != origEntry.isStart()) {
+ System.err.println("local start/end mismatch at orig " + i +
+ " / decoded " + decodeAt);
+ problem = true;
+ break;
}
- if (!found) {
- throw new RuntimeException("Could not match local entry");
+ /*
+ * The secondary check here accounts for the fact that a
+ * parameter might not be marked as starting at 0 in the
+ * original list.
+ */
+ if ((decodedAddress != origEntry.getAddress())
+ && !((decodedAddress == 0)
+ && (decodedEntry.reg >= paramBase))) {
+ System.err.println("local address mismatch at orig " + i +
+ " / decoded " + decodeAt);
+ problem = true;
+ break;
}
+
+ decodeAt++;
}
- if (matchedLocalsEntries != ll.size()) {
- throw new RuntimeException("Locals tables did not match");
+ if (problem) {
+ System.err.println("decoded locals:");
+ for (LocalEntry e : decodedLocals) {
+ System.err.println(" " + e);
+ }
+ throw new RuntimeException("local table problem");
}
}
diff --git a/dx/src/com/android/dx/dex/file/DebugInfoEncoder.java b/dx/src/com/android/dx/dex/file/DebugInfoEncoder.java
index 49781bdd8..780e18d5b 100644
--- a/dx/src/com/android/dx/dex/file/DebugInfoEncoder.java
+++ b/dx/src/com/android/dx/dex/file/DebugInfoEncoder.java
@@ -56,10 +56,10 @@ public final class DebugInfoEncoder {
private static final boolean DEBUG = false;
/** null-ok; positions (line numbers) to encode */
- private final PositionList positionlist;
+ private final PositionList positions;
/** null-ok; local variables to encode */
- private final LocalList locallist;
+ private final LocalList locals;
private final ByteArrayAnnotatedOutput output;
private final DexFile file;
@@ -108,8 +108,8 @@ public final class DebugInfoEncoder {
public DebugInfoEncoder(PositionList pl, LocalList ll,
DexFile file, int codeSize, int regSize,
boolean isStatic, CstMethodRef ref) {
- this.positionlist = pl;
- this.locallist = ll;
+ this.positions = pl;
+ this.locals = ll;
this.file = file;
output = new ByteArrayAnnotatedOutput();
this.desc = ref.getPrototype();
@@ -193,18 +193,11 @@ public final class DebugInfoEncoder {
private byte[] convert0() throws IOException {
ArrayList<PositionList.Entry> sortedPositions = buildSortedPositions();
- ArrayList<LocalList.Entry> sortedLocalsStart = buildLocalsStart();
-
- // Parameter locals are removed from sortedLocalsStart here.
- ArrayList<LocalList.Entry> methodArgs
- = extractMethodArguments(sortedLocalsStart);
-
- ArrayList<LocalList.Entry> sortedLocalsEnd
- = buildLocalsEnd(sortedLocalsStart);
+ ArrayList<LocalList.Entry> methodArgs = extractMethodArguments();
emitHeader(sortedPositions, methodArgs);
- // TODO: Make this mark the actual prologue end.
+ // TODO: Make this mark be the actual prologue end.
output.writeByte(DBG_SET_PROLOGUE_END);
if (annotateTo != null || debugPrint != null) {
@@ -212,56 +205,37 @@ public final class DebugInfoEncoder {
}
int szp = sortedPositions.size();
- int szl = sortedLocalsStart.size();
+ int szl = locals.size();
// Current index in sortedPositions
int curp = 0;
- // Current index in sortedLocalsStart
- int curls = 0;
- // Current index in sortedLocalsEnd
- int curle = 0;
+ // Current index in locals
+ int curl = 0;
for (;;) {
/*
* Emit any information for the current address.
*/
- curle = emitLocalEndsAtAddress(curle, sortedLocalsEnd, curls,
- sortedLocalsStart);
-
- /*
- * Our locals-sorted-by-range-end has reached the end
- * of the code block. Ignore everything else.
- */
- if (address == codeSize) {
- curle = szl;
- }
-
- curls = emitLocalStartsAtAddress(curls, sortedLocalsStart);
-
+ curl = emitLocalsAtAddress(curl);
curp = emitPositionsAtAddress(curp, sortedPositions);
/*
* Figure out what the next important address is.
*/
- int nextAddrLS = Integer.MAX_VALUE; // local start
- int nextAddrLE = Integer.MAX_VALUE; // local end
- int nextAddrP = Integer.MAX_VALUE; // position (line number)
-
- if (curls < szl) {
- nextAddrLS = sortedLocalsStart.get(curls).getStart();
- }
+ int nextAddrL = Integer.MAX_VALUE; // local variable
+ int nextAddrP = Integer.MAX_VALUE; // position (line number)
- if (curle < szl) {
- nextAddrLE = sortedLocalsEnd.get(curle).getEnd();
+ if (curl < szl) {
+ nextAddrL = locals.get(curl).getAddress();
}
if (curp < szp) {
nextAddrP = sortedPositions.get(curp).getAddress();
}
- int next = Math.min(nextAddrP, Math.min(nextAddrLS, nextAddrLE));
+ int next = Math.min(nextAddrP, nextAddrL);
// No next important address == done.
if (next == Integer.MAX_VALUE) {
@@ -273,7 +247,7 @@ public final class DebugInfoEncoder {
* block, stop here. Those are implied anyway.
*/
if (next == codeSize
- && nextAddrLS == Integer.MAX_VALUE
+ && nextAddrL == Integer.MAX_VALUE
&& nextAddrP == Integer.MAX_VALUE) {
break;
}
@@ -292,76 +266,24 @@ public final class DebugInfoEncoder {
}
/**
- * Emits all local ends that occur at the current <code>address</code>
+ * Emits all local variable activity that occurs at the current
+ * {@link #address} starting at the given index into {@code
+ * locals} and including all subsequent activity at the same
+ * address.
*
- * @param curle Current index in sortedLocalsEnd
- * @param sortedLocalsEnd Locals, sorted by ascending end address
- * @param curls Current index in sortedLocalsStart
- * @param sortedLocalsStart Locals, sorted by ascending start address
- * @return new value for <code>curle</code>
+ * @param curl Current index in locals
+ * @return new value for <code>curl</code>
* @throws IOException
*/
- private int emitLocalEndsAtAddress(int curle,
- ArrayList<LocalList.Entry> sortedLocalsEnd, int curls,
- ArrayList<LocalList.Entry> sortedLocalsStart)
+ private int emitLocalsAtAddress(int curl)
throws IOException {
+ int sz = locals.size();
- int szl = sortedLocalsEnd.size();
+ // TODO: Don't emit ends implied by starts.
- // Ignore "local ends" at end of code.
- while (curle < szl
- && sortedLocalsEnd.get(curle).getEnd() == address
- && address != codeSize) {
-
- boolean skipLocalEnd = false;
-
- /*
- * Check to see if there's a range-start that appears at
- * the same address for the same register. If so, the
- * end-range is implicit so skip it.
- */
- for (int j = curls; j < szl
- && sortedLocalsStart.get(j).getStart() == address
- ; j++) {
-
- if (sortedLocalsStart.get(j).getRegister()
- == sortedLocalsEnd.get(curle).getRegister()) {
- skipLocalEnd = true;
-
- if (DEBUG) {
- System.err.printf("skip local end v%d\n",
- sortedLocalsEnd.get(curle).getRegister());
- }
- break;
- }
- }
-
- if (!skipLocalEnd) {
- emitLocalEnd(sortedLocalsEnd.get(curle));
- }
-
- curle++;
- }
- return curle;
- }
-
- /**
- * Emits all local starts that occur at the current <code>address</code>
- *
- * @param curls Current index in sortedLocalsStart
- * @param sortedLocalsStart Locals, sorted by ascending start address
- * @return new value for <code>curls</code>
- * @throws IOException
- */
- private int emitLocalStartsAtAddress(int curls,
- ArrayList<LocalList.Entry> sortedLocalsStart)
- throws IOException {
-
- int szl = sortedLocalsStart.size();
-
- while (curls < szl
- && sortedLocalsStart.get(curls).getStart() == address) {
- LocalList.Entry lle = sortedLocalsStart.get(curls++);
+ while ((curl < sz)
+ && (locals.get(curl).getAddress() == address)) {
+ LocalList.Entry lle = locals.get(curl++);
int reg = lle.getRegister();
LocalList.Entry prevlle = lastEntryForReg[reg];
@@ -374,26 +296,45 @@ public final class DebugInfoEncoder {
continue;
}
- // At this point we have a new live entry one way or another.
+ // At this point we have a new entry one way or another.
lastEntryForReg[reg] = lle;
- if ((prevlle != null) && lle.matches(prevlle)) {
- if (prevlle.getEnd() == lle.getStart()) {
+ if (lle.isStart()) {
+ if ((prevlle != null) && lle.matches(prevlle)) {
/*
- * There is nothing more to do in this case: It's
- * an adjacent range with the same register. The
- * previous emitLocalEndsAtAddress() call skipped
- * this local end, so we'll skip this local start
- * as well.
+ * The previous local in this register has the same
+ * name and type as the one being introduced now, so
+ * use the more efficient "restart" form.
*/
- } else {
+ if (prevlle.isStart()) {
+ /*
+ * We should never be handed a start when a
+ * a matching local is already active.
+ */
+ throw new RuntimeException("shouldn't happen");
+ }
emitLocalRestart(lle);
+ } else {
+ emitLocalStart(lle);
}
} else {
- emitLocalStart(lle);
+ /*
+ * Only emit a local end if it is *not* due to a direct
+ * replacement. Direct replacements imply an end of the
+ * previous local in the same register.
+ *
+ * TODO: Make sure the runtime can deal with implied
+ * local ends from category-2 interactions, and when so,
+ * also stop emitting local ends for those cases.
+ */
+ if (lle.getDisposition()
+ != LocalList.Disposition.END_REPLACED) {
+ emitLocalEnd(lle);
+ }
}
}
- return curls;
+
+ return curl;
}
/**
@@ -524,7 +465,7 @@ public final class DebugInfoEncoder {
* a LOCAL_RESTART_EXTENDED
*/
- for (LocalList.Entry arg: lastEntryForReg) {
+ for (LocalList.Entry arg : lastEntryForReg) {
if (arg == null) {
continue;
}
@@ -543,11 +484,11 @@ public final class DebugInfoEncoder {
* @return A sorted positions list
*/
private ArrayList<PositionList.Entry> buildSortedPositions() {
- int sz = (positionlist == null) ? 0 : positionlist.size();
+ int sz = (positions == null) ? 0 : positions.size();
ArrayList<PositionList.Entry> result = new ArrayList(sz);
for (int i = 0; i < sz; i++) {
- result.add(positionlist.get(i));
+ result.add(positions.get(i));
}
// Sort ascending by address.
@@ -564,58 +505,6 @@ public final class DebugInfoEncoder {
}
/**
- * Builds a list of locals entries sorted by ascending start address.
- *
- * @return A sorted locals list list
- */
- private ArrayList<LocalList.Entry> buildLocalsStart() {
- int sz = (locallist == null) ? 0 : locallist.size();
- ArrayList<LocalList.Entry> result = new ArrayList(sz);
-
- // Add all the entries
- for (int i = 0; i < sz; i++) {
- LocalList.Entry e = locallist.get(i);
- result.add(locallist.get(i));
- }
-
- // Sort ascending by start address.
- Collections.sort (result, new Comparator<LocalList.Entry>() {
- public int compare (LocalList.Entry a, LocalList.Entry b) {
- return a.getStart() - b.getStart();
- }
-
- public boolean equals (Object obj) {
- return obj == this;
- }
- });
- return result;
- }
-
- /**
- * Builds a list of locals entries sorted by ascending end address.
- *
- * @param list locals list in any order
- * @return a sorted locals list
- */
- private ArrayList<LocalList.Entry> buildLocalsEnd(
- ArrayList<LocalList.Entry> list) {
-
- ArrayList<LocalList.Entry> sortedLocalsEnd = new ArrayList(list);
-
- // Sort ascending by end address.
- Collections.sort (sortedLocalsEnd, new Comparator<LocalList.Entry>() {
- public int compare (LocalList.Entry a, LocalList.Entry b) {
- return a.getEnd() - b.getEnd();
- }
-
- public boolean equals (Object obj) {
- return obj == this;
- }
- });
- return sortedLocalsEnd;
- }
-
- /**
* Gets the register that begins the method's parameter range (including
* the 'this' parameter for non-static methods). The range continues until
* <code>regSize</code>
@@ -632,24 +521,18 @@ public final class DebugInfoEncoder {
* from the input list and sorted by ascending register in the
* returned list.
*
- * @param sortedLocals locals list, sorted by ascending start address,
- * to process; left unmodified
* @return list of non-<code>this</code> method argument locals,
* sorted by ascending register
*/
- private ArrayList<LocalList.Entry> extractMethodArguments (
- ArrayList<LocalList.Entry> sortedLocals) {
-
+ private ArrayList<LocalList.Entry> extractMethodArguments() {
ArrayList<LocalList.Entry> result
= new ArrayList(desc.getParameterTypes().size());
-
int argBase = getParamBase();
-
BitSet seen = new BitSet(regSize - argBase);
+ int sz = locals.size();
- int sz = sortedLocals.size();
for (int i = 0; i < sz; i++) {
- LocalList.Entry e = sortedLocals.get(i);
+ LocalList.Entry e = locals.get(i);
int reg = e.getRegister();
if (reg < argBase) {
@@ -666,12 +549,12 @@ public final class DebugInfoEncoder {
}
// Sort by ascending register.
- Collections.sort (result, new Comparator<LocalList.Entry>() {
- public int compare (LocalList.Entry a, LocalList.Entry b) {
+ Collections.sort(result, new Comparator<LocalList.Entry>() {
+ public int compare(LocalList.Entry a, LocalList.Entry b) {
return a.getRegister() - b.getRegister();
}
- public boolean equals (Object obj) {
+ public boolean equals(Object obj) {
return obj == this;
}
});
diff --git a/dx/src/com/android/dx/dex/file/DebugInfoItem.java b/dx/src/com/android/dx/dex/file/DebugInfoItem.java
index b0745935d..0e4329b14 100644
--- a/dx/src/com/android/dx/dex/file/DebugInfoItem.java
+++ b/dx/src/com/android/dx/dex/file/DebugInfoItem.java
@@ -141,6 +141,37 @@ public class DebugInfoItem extends OffsettedItem {
*/
private byte[] encode(DexFile file, String prefix, PrintWriter debugPrint,
AnnotatedOutput out, boolean consume) {
+ byte[] result = encode0(file, prefix, debugPrint, out, consume);
+
+ if (ENABLE_ENCODER_SELF_CHECK && (file != null)) {
+ try {
+ DebugInfoDecoder.validateEncode(result, file, ref, code,
+ isStatic);
+ } catch (RuntimeException ex) {
+ // Reconvert, annotating to System.err.
+ encode0(file, "", new PrintWriter(System.err, true), null,
+ false);
+ throw ex;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Helper for {@link #encode} to do most of the work.
+ *
+ * @param file null-ok; file to refer to during encoding
+ * @param prefix null-ok; prefix to attach to each line of output
+ * @param debugPrint null-ok; if specified, an alternate output for
+ * annotations
+ * @param out null-ok; if specified, where annotations should go
+ * @param consume whether to claim to have consumed output for
+ * <code>out</code>
+ * @return non-null; the encoded array
+ */
+ private byte[] encode0(DexFile file, String prefix, PrintWriter debugPrint,
+ AnnotatedOutput out, boolean consume) {
PositionList positions = code.getPositions();
LocalList locals = code.getLocals();
DalvInsnList insns = code.getInsns();
@@ -156,13 +187,8 @@ public class DebugInfoItem extends OffsettedItem {
if ((debugPrint == null) && (out == null)) {
result = encoder.convert();
} else {
- result = encoder.convertAndAnnotate(
- prefix, debugPrint, out, consume);
- }
-
- if (ENABLE_ENCODER_SELF_CHECK && (file != null)) {
- DebugInfoDecoder.validateEncode(encoded, codeSize,
- regSize, positions, locals, isStatic, ref, file);
+ result = encoder.convertAndAnnotate(prefix, debugPrint, out,
+ consume);
}
return result;
diff --git a/dx/src/com/android/dx/rop/code/LocalItem.java b/dx/src/com/android/dx/rop/code/LocalItem.java
index b1e1a4be6..bac6ce2a4 100644
--- a/dx/src/com/android/dx/rop/code/LocalItem.java
+++ b/dx/src/com/android/dx/rop/code/LocalItem.java
@@ -22,7 +22,6 @@ import com.android.dx.rop.cst.CstUtf8;
* A local variable item: either a name or a signature or both.
*/
public class LocalItem implements Comparable<LocalItem> {
-
/** null-ok; local variable name */
private final CstUtf8 name;
@@ -38,7 +37,7 @@ public class LocalItem implements Comparable<LocalItem> {
* @param signature null-ok; local variable signature
* @return non-null; appropriate instance.
*/
- public static LocalItem make (CstUtf8 name, CstUtf8 signature) {
+ public static LocalItem make(CstUtf8 name, CstUtf8 signature) {
if (name == null && signature == null) {
return null;
}
@@ -52,14 +51,14 @@ public class LocalItem implements Comparable<LocalItem> {
* @param name null-ok; local variable name
* @param signature null-ok; local variable signature
*/
- private LocalItem (CstUtf8 name, CstUtf8 signature) {
+ private LocalItem(CstUtf8 name, CstUtf8 signature) {
this.name = name;
this.signature = signature;
}
/** {@inheritDoc} */
@Override
- public boolean equals (Object other) {
+ public boolean equals(Object other) {
if (!(other instanceof LocalItem)) {
return false;
}
@@ -89,7 +88,7 @@ public class LocalItem implements Comparable<LocalItem> {
}
/** {@inheritDoc} */
- public int compareTo (LocalItem local) {
+ public int compareTo(LocalItem local) {
int ret;
ret = compareHandlesNulls(name, local.name);
@@ -106,7 +105,7 @@ public class LocalItem implements Comparable<LocalItem> {
/** {@inheritDoc} */
@Override
- public int hashCode () {
+ public int hashCode() {
return (name == null ? 0 : name.hashCode()) * 31
+ (signature == null ? 0 : signature.hashCode());
}
@@ -121,7 +120,7 @@ public class LocalItem implements Comparable<LocalItem> {
}
return "[" + (name == null ? "" : name.toQuoted())
- + "|" + (signature == null ? "" : signature.toQuoted());
+ + "|" + (signature == null ? "" : signature.toQuoted());
}
/**
diff --git a/dx/src/com/android/dx/rop/code/RegisterSpec.java b/dx/src/com/android/dx/rop/code/RegisterSpec.java
index 09f7f1829..73af91fbc 100644
--- a/dx/src/com/android/dx/rop/code/RegisterSpec.java
+++ b/dx/src/com/android/dx/rop/code/RegisterSpec.java
@@ -29,7 +29,7 @@ import java.util.HashMap;
* destinations of register-based operations.
*/
public final class RegisterSpec
- implements TypeBearer, ToHuman {
+ implements TypeBearer, ToHuman, Comparable<RegisterSpec> {
/** non-null; string to prefix register numbers with */
public static final String PREFIX = "v";
@@ -97,7 +97,8 @@ public final class RegisterSpec
* @param local non-null; the associated local variable
* @return non-null; an appropriately-constructed instance
*/
- public static RegisterSpec make(int reg, TypeBearer type, LocalItem local) {
+ public static RegisterSpec make(int reg, TypeBearer type,
+ LocalItem local) {
if (local == null) {
throw new NullPointerException("local == null");
}
@@ -172,6 +173,43 @@ public final class RegisterSpec
}
/**
+ * Like {@code equals}, but only consider the simple types of the
+ * registers. That is, this compares {@code getType()} on the types
+ * to ignore whatever arbitrary extra stuff might be carried around
+ * by an outer {@link TypeBearer}.
+ *
+ * @param other null-ok; spec to compare to
+ * @return {@code true} iff {@code this} and {@code other} are equal
+ * in the stated way
+ */
+ public boolean equalsUsingSimpleType(RegisterSpec other) {
+ if (!matchesVariable(other)) {
+ return false;
+ }
+
+ return (reg == other.reg);
+ }
+
+ /**
+ * Like {@link #equalsUsingSimpleType} but ignoring the register number.
+ * This is useful to determine if two instances refer to the "same"
+ * local variable.
+ *
+ * @param other null-ok; spec to compare to
+ * @return {@code true} iff {@code this} and {@code other} are equal
+ * in the stated way
+ */
+ public boolean matchesVariable(RegisterSpec other) {
+ if (other == null) {
+ return false;
+ }
+
+ return type.getType().equals(other.type.getType())
+ && ((local == other.local)
+ || ((local != null) && local.equals(other.local)));
+ }
+
+ /**
* Helper for {@link #equals} and {@link #ForComparison.equals},
* which actually does the test.
*
@@ -188,6 +226,35 @@ public final class RegisterSpec
|| ((this.local != null) && this.local.equals(local)));
}
+ /**
+ * Compares by (in priority order) register number, unwrapped type
+ * (that is types not {@link TypeBearer}s, and local info.
+ *
+ * @param other non-null; spec to compare to
+ * @return {@code -1..1}; standard result of comparison
+ */
+ public int compareTo(RegisterSpec other) {
+ if (this.reg < other.reg) {
+ return -1;
+ } else if (this.reg > other.reg) {
+ return 1;
+ }
+
+ int compare = type.getType().compareTo(other.type.getType());
+
+ if (compare != 0) {
+ return compare;
+ }
+
+ if (this.local == null) {
+ return (other.local == null) ? 0 : -1;
+ } else if (other.local == null) {
+ return 1;
+ }
+
+ return this.local.compareTo(other.local);
+ }
+
/** {@inheritDoc} */
@Override
public int hashCode() {
@@ -292,6 +359,8 @@ public final class RegisterSpec
* Gets the category of this instance's type. This is just a convenient
* shorthand for <code>getType().getCategory()</code>.
*
+ * @see #isCategory1
+ * @see #isCategory2
* @return 1..2; the category of this instance's type
*/
public int getCategory() {
@@ -299,6 +368,30 @@ public final class RegisterSpec
}
/**
+ * Gets whether this instance's type is category 1. This is just a
+ * convenient shorthand for <code>getType().isCategory1()</code>.
+ *
+ * @see #getCategory
+ * @see #isCategory2
+ * @return whether or not this instance's type is of category 1
+ */
+ public boolean isCategory1() {
+ return type.getType().isCategory1();
+ }
+
+ /**
+ * Gets whether this instance's type is category 2. This is just a
+ * convenient shorthand for <code>getType().isCategory2()</code>.
+ *
+ * @see #getCategory
+ * @see #isCategory1
+ * @return whether or not this instance's type is of category 2
+ */
+ public boolean isCategory2() {
+ return type.getType().isCategory2();
+ }
+
+ /**
* Gets the string form for just the register number of this instance.
*
* @return non-null; the register string form
@@ -323,16 +416,16 @@ public final class RegisterSpec
* are <code>equals()</code>, then the intersection's type bearer
* is the one from this instance. Otherwise, the intersection's
* type bearer is the <code>getType()</code> of this instance.</li>
- * <li>If the locals are <code>equals()</code>, then the local info of the
- * intersection is the local info of this instance. Otherwise, the local info
- * of the intersection is <code>null</code>.</li>
+ * <li>If the locals are <code>equals()</code>, then the local info
+ * of the intersection is the local info of this instance. Otherwise,
+ * the local info of the intersection is <code>null</code>.</li>
* </ul>
*
* @param other null-ok; instance to intersect with (or <code>null</code>)
- * @param localPrimary whether local variables are primary to
- * the intersection; if <code>true</code>, then the only non-null
- * results occur when registers being intersected have equal local infos (or
- * both have <code>null</code> local infos)
+ * @param localPrimary whether local variables are primary to the
+ * intersection; if <code>true</code>, then the only non-null
+ * results occur when registers being intersected have equal local
+ * infos (or both have <code>null</code> local infos)
* @return null-ok; the intersection
*/
public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) {
@@ -346,7 +439,8 @@ public final class RegisterSpec
}
LocalItem resultLocal =
- ((local == null) || !local.equals(other.getLocalItem())) ? null : local;
+ ((local == null) || !local.equals(other.getLocalItem()))
+ ? null : local;
boolean sameName = (resultLocal == local);
if (localPrimary && !sameName) {
diff --git a/dx/src/com/android/dx/rop/code/RegisterSpecSet.java b/dx/src/com/android/dx/rop/code/RegisterSpecSet.java
index 4eb2f65e8..adc77c35e 100644
--- a/dx/src/com/android/dx/rop/code/RegisterSpecSet.java
+++ b/dx/src/com/android/dx/rop/code/RegisterSpecSet.java
@@ -187,16 +187,47 @@ public final class RegisterSpecSet
}
/**
+ * Returns the spec in this set that's currently associated with a
+ * given local (type, name, and signature), or {@code null} if there is
+ * none. This ignores the register number of the given spec but
+ * matches on everything else.
+ *
+ * @param spec non-null; local to look for
+ * @return null-ok; first register found that matches, if any
+ */
+ public RegisterSpec findMatchingLocal(RegisterSpec spec) {
+ int length = specs.length;
+
+ for (int reg = 0; reg < length; reg++) {
+ RegisterSpec s = specs[reg];
+
+ if (s == null) {
+ continue;
+ }
+
+ if (spec.matchesVariable(s)) {
+ return s;
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Returns the spec in this set that's currently associated with a given
- * name, or null if there is none.
+ * local (name and signature), or {@code null} if there is none.
*
* @param local non-null; local item to search for
- * @return null-ok; first register found with name.
+ * @return null-ok; first register found with matching name and signature
*/
public RegisterSpec localItemToSpec(LocalItem local) {
- for (int reg = 0; reg < specs.length; reg++) {
- if (specs[reg] != null && local.equals(specs[reg].getLocalItem())) {
- return specs[reg];
+ int length = specs.length;
+
+ for (int reg = 0; reg < length; reg++) {
+ RegisterSpec spec = specs[reg];
+
+ if ((spec != null) && local.equals(spec.getLocalItem())) {
+ return spec;
}
}
@@ -260,6 +291,22 @@ public final class RegisterSpecSet
}
/**
+ * Put the entire contents of the given set into this one.
+ *
+ * @param set non-null; the set to put into this instance
+ */
+ public void putAll(RegisterSpecSet set) {
+ int max = set.getMaxSize();
+
+ for (int i = 0; i < max; i++) {
+ RegisterSpec spec = set.get(i);
+ if (spec != null) {
+ put(spec);
+ }
+ }
+ }
+
+ /**
* Intersects this instance with the given one, modifying this
* instance. The intersection consists of the pairwise
* {@link RegisterSpec#intersect} of corresponding elements from