diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-02 22:54:18 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-02 22:54:18 -0800 |
| commit | 6dcac3deb3c19dc634470eb30b2daedf2b201bd4 (patch) | |
| tree | 2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /dx | |
| parent | 077f9d75d9701778830742b9c2afad4742635b58 (diff) | |
| download | android_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.java | 7 | ||||
| -rw-r--r-- | dx/src/com/android/dx/dex/code/LocalList.java | 947 | ||||
| -rw-r--r-- | dx/src/com/android/dx/dex/file/DebugInfoDecoder.java | 340 | ||||
| -rw-r--r-- | dx/src/com/android/dx/dex/file/DebugInfoEncoder.java | 251 | ||||
| -rw-r--r-- | dx/src/com/android/dx/dex/file/DebugInfoItem.java | 40 | ||||
| -rw-r--r-- | dx/src/com/android/dx/rop/code/LocalItem.java | 13 | ||||
| -rw-r--r-- | dx/src/com/android/dx/rop/code/RegisterSpec.java | 114 | ||||
| -rw-r--r-- | dx/src/com/android/dx/rop/code/RegisterSpecSet.java | 57 |
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 >= 0; address at which the scope starts - * (inclusive) - * @param endAddress > 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 >= 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 >= 0, < size(); which index - * @param start >= 0; start address - * @param end > 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 >= 0, < 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 { - /** >= 0; start address */ - private final int start; + public static class Entry implements Comparable<Entry> { + /** >= 0; address */ + private final int address; - /** > 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 >= 0; start address - * @param end > start; end address (exclusive) + * @param address >= 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 >= 0; the start address + * @return >= 0; the address */ - public int getStart() { - return start; + public int getAddress() { + return address; } /** - * Gets the end address (exclusive). + * Gets the disposition. * - * @return > 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 > 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; + + /** + * >= 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; + + /** >= 0; last address seen */ + private int lastAddress; + + /** + * >= 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 >= 0; address about to be processed + * @param reg >= 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 >= 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 >= 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 >= 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 >= 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 >= 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 >= 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 |
