diff options
Diffstat (limited to 'dx/src/com/android/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, 559 insertions, 1210 deletions
diff --git a/dx/src/com/android/dx/dex/code/LocalEnd.java b/dx/src/com/android/dx/dex/code/LocalEnd.java index c19a8dcec..87934dbd4 100644 --- a/dx/src/com/android/dx/dex/code/LocalEnd.java +++ b/dx/src/com/android/dx/dex/code/LocalEnd.java @@ -29,10 +29,9 @@ 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> 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. + * 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 */ 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 4614fc41e..d963fca08 100644 --- a/dx/src/com/android/dx/dex/code/LocalList.java +++ b/dx/src/com/android/dx/dex/code/LocalList.java @@ -23,9 +23,7 @@ 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 @@ -36,9 +34,174 @@ public final class LocalList extends FixedSizeList { /** non-null; empty instance */ public static final LocalList EMPTY = new LocalList(0); - /** whether to run the self-check code */ - private static final boolean DEBUG = false; - + /** + * 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); + } + /** * Constructs an instance. All indices initially contain <code>null</code>. * @@ -64,86 +227,54 @@ public final class LocalList extends FixedSizeList { * Sets the entry at the given index. * * @param n >= 0, < size(); which index - * @param entry non-null; the entry to set at <code>n</code> + * @param start >= 0; start address + * @param end > start; end address (exclusive) + * @param spec non-null; register spec representing the variable */ - public void set(int n, Entry entry) { - set0(n, entry); + public void set(int n, int start, int end, RegisterSpec spec) { + set0(n, new Entry(start, end, spec)); } /** - * Does a human-friendly dump of this instance. + * Sets the entry at the given index. * - * @param out non-null; where to dump - * @param prefix non-null; prefix to attach to each line of output - */ - 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. + * @param n >= 0, < size(); which index + * @param entry non-null; the entry to set at <code>n</code> */ - 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; + public void set(int n, Entry entry) { + set0(n, entry); } /** * Entry in a local list. */ - public static class Entry implements Comparable<Entry> { - /** >= 0; address */ - private final int address; + public static class Entry { + /** >= 0; start address */ + private final int start; - /** non-null; disposition of the local */ - private final Disposition disposition; + /** > start; end address (exclusive) */ + private final int end; /** non-null; register spec representing the variable */ private final RegisterSpec spec; - /** non-null; variable type (derived from {@code spec}) */ + /** non-null; variable type */ private final CstType type; - + /** * Constructs an instance. * - * @param address >= 0; address - * @param disposition non-null; disposition of the local + * @param start >= 0; start address + * @param end > start; end address (exclusive) * @param spec non-null; register spec representing the variable */ - public Entry(int address, Disposition disposition, RegisterSpec spec) { - if (address < 0) { - throw new IllegalArgumentException("address < 0"); + public Entry(int start, int end, RegisterSpec spec) { + if (start < 0) { + throw new IllegalArgumentException("start < 0"); } - if (disposition == null) { - throw new NullPointerException("disposition == null"); + if (end <= start) { + throw new IllegalArgumentException("end <= start"); } try { @@ -156,78 +287,37 @@ public final class LocalList extends FixedSizeList { throw new NullPointerException("spec == null"); } - this.address = address; - this.disposition = disposition; + this.start = start; + this.end = end; this.spec = spec; - 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; + 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()); } - - return spec.compareTo(other.spec); } /** - * Gets the address. + * Gets the start address. * - * @return >= 0; the address + * @return >= 0; the start address */ - public int getAddress() { - return address; + public int getStart() { + return start; } /** - * Gets the disposition. + * Gets the end address (exclusive). * - * @return non-null; the disposition + * @return > start; the end address (exclusive) */ - 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; + public int getEnd() { + return end; } /** @@ -282,8 +372,8 @@ public final class LocalList extends FixedSizeList { * @return <code>true</code> iff this instance matches * <code>spec</code> */ - public boolean matches(RegisterSpec otherSpec) { - return spec.equalsUsingSimpleType(otherSpec); + public boolean matches(RegisterSpec spec) { + return spec.equals(this.spec); } /** @@ -295,619 +385,18 @@ public final class LocalList extends FixedSizeList { * <code>other</code> */ public boolean matches(Entry other) { - return matches(other.spec); + return other.spec.equals(this.spec); } /** - * Returns an instance just like this one but with the disposition - * set as given + * Returns an instance just like this one, except with the end + * address altered to be the one given. * - * @param disposition non-null; the new disposition + * @param newEnd > getStart(); the end address of the new instance * @return non-null; an appropriately-constructed instance */ - 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); + public Entry withEnd(int newEnd) { + return new Entry(start, newEnd, spec); } - - 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 3ffd27696..519452b64 100644 --- a/dx/src/com/android/dx/dex/file/DebugInfoDecoder.java +++ b/dx/src/com/android/dx/dex/file/DebugInfoDecoder.java @@ -16,8 +16,6 @@ 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; @@ -43,28 +41,20 @@ 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 @@ -91,11 +81,8 @@ 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; @@ -103,37 +90,24 @@ public class DebugInfoDecoder { this.file = file; this.regSize = regSize; - positions = new ArrayList<PositionEntry>(); - locals = new ArrayList<LocalEntry>(); + positions = new ArrayList(); + locals = new ArrayList(); this.codesize = codesize; lastEntryForReg = new LocalEntry[regSize]; - 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; + thisStringIdx = file.getStringIds().indexOf(new CstUtf8("this")); } /** * An entry in the resulting postions table */ - static private class PositionEntry { + static class PositionEntry { /** bytecode address */ - public int address; - + int address; /** line number */ - public int line; + int line; - public PositionEntry(int address, int line) { + PositionEntry(int address, int line) { this.address = address; this.line = line; } @@ -142,40 +116,37 @@ public class DebugInfoDecoder { /** * An entry in the resulting locals table */ - static private class LocalEntry { - /** address of event */ - public int address; + 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; + } - /** {@code true} iff it's a local start */ - public boolean isStart; + /** 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; /** register number */ - public int reg; + int reg; /** index of name in strings table */ - public int nameIndex; + int nameIndex; /** index of type in types table */ - public int typeIndex; + int typeIndex; /** index of type signature in strings table */ - public int signatureIndex; + 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); - } } /** @@ -195,6 +166,13 @@ 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; } @@ -251,8 +229,8 @@ public class DebugInfoDecoder { if (!isStatic) { // Start off with implicit 'this' entry - LocalEntry thisEntry = - new LocalEntry(0, true, curReg, thisStringIdx, 0, 0); + LocalEntry thisEntry + = new LocalEntry(0, curReg, thisStringIdx, 0, 0); locals.add(thisEntry); lastEntryForReg[curReg] = thisEntry; curReg++; @@ -264,19 +242,15 @@ public class DebugInfoDecoder { int nameIdx = readStringIndex(bs); - 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); + if(nameIdx == -1) { + // unnamed parameter } else { - // TODO: Final 0 should be idx of paramType.getDescriptor(). - le = new LocalEntry(0, true, curReg, nameIdx, 0, 0); + // final '0' should be idx of paramType.getDescriptor() + le = new LocalEntry(0, curReg, nameIdx, 0, 0); + locals.add(le); + lastEntryForReg[curReg] = le; } - locals.add(le); - lastEntryForReg[curReg] = le; curReg += paramType.getCategory(); } @@ -295,7 +269,15 @@ public class DebugInfoDecoder { int nameIdx = readStringIndex(bs); int typeIdx = readStringIndex(bs); LocalEntry le = new LocalEntry( - address, true, reg, nameIdx, typeIdx, 0); + 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; + } locals.add(le); lastEntryForReg[reg] = le; @@ -308,7 +290,21 @@ public class DebugInfoDecoder { int typeIdx = readStringIndex(bs); int sigIdx = readStringIndex(bs); LocalEntry le = new LocalEntry( - address, true, reg, nameIdx, typeIdx, sigIdx); + 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]); + } + } locals.add(le); lastEntryForReg[reg] = le; @@ -323,17 +319,16 @@ public class DebugInfoDecoder { try { prevle = lastEntryForReg[reg]; - if (prevle.isStart) { - throw new RuntimeException("nonsensical " - + "RESTART_LOCAL on live register v" - + reg); + if (lastEntryForReg[reg].end == Integer.MAX_VALUE) { + throw new RuntimeException ("nonsensical " + + "RESTART_LOCAL on live register v"+reg); } - - le = new LocalEntry(address, true, reg, + le = new LocalEntry(address, 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); @@ -343,27 +338,20 @@ public class DebugInfoDecoder { case DBG_END_LOCAL: { int reg = readUnsignedLeb128(bs); - LocalEntry prevle; - LocalEntry le; - - try { - prevle = lastEntryForReg[reg]; - - if (!prevle.isStart) { - throw new RuntimeException("nonsensical " - + "END_LOCAL on dead register v" + reg); + 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; } - - le = new LocalEntry(address, false, reg, - prevle.nameIndex, prevle.typeIndex, - prevle.signatureIndex); - } catch (NullPointerException ex) { - throw new RuntimeException( - "Encountered END_LOCAL on new v" + reg); } - locals.add(le); - lastEntryForReg[reg] = le; + if (!found) { + throw new RuntimeException( + "Encountered LOCAL_END without local start: v" + + reg); + } } break; @@ -415,48 +403,40 @@ public class DebugInfoDecoder { * throwing an exception if they do not match. Used to validate the * encoder. * - * @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 + * @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. */ - 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(); - + public static void validateEncode(byte[] linecodes, int codeSize, + int countRegisters, PositionList pl, LocalList ll, + boolean isStatic, CstMethodRef ref, DexFile file) { + try { - validateEncode0(info, codeSize, countRegisters, + validateEncode0(linecodes, codeSize, countRegisters, isStatic, ref, file, pl, ll); } catch (RuntimeException ex) { - System.err.println("instructions:"); - insns.debugPrint(System.err, " ", true); - System.err.println("local list:"); - ll.debugPrint(System.err, " "); +// System.err.println(ex.toString() +// + " while processing " + ref.toHuman()); throw ExceptionWithContext.withContext(ex, "while processing " + ref.toHuman()); } } + - private static void validateEncode0(byte[] info, int codeSize, + private static void validateEncode0(byte[] linecodes, int codeSize, int countRegisters, boolean isStatic, CstMethodRef ref, DexFile file, PositionList pl, LocalList ll) { DebugInfoDecoder decoder - = new DebugInfoDecoder(info, codeSize, countRegisters, + = new DebugInfoDecoder(linecodes, codeSize, countRegisters, isStatic, ref, file); decoder.decode(); - /* - * Go through the decoded position entries, matching up - * with original entries. - */ + List<PositionEntry> decodedEntries; - List<PositionEntry> decodedEntries = decoder.getPositionList(); + decodedEntries = decoder.getPositionList(); if (decodedEntries.size() != pl.size()) { throw new RuntimeException( @@ -464,7 +444,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); @@ -482,111 +462,41 @@ public class DebugInfoDecoder { } } - /* - * Go through the original local list, in order, matching up - * with decoded entries. - */ + List<LocalEntry> decodedLocals; + + decodedLocals = decoder.getLocals(); - List<LocalEntry> decodedLocals = decoder.getLocals(); - int thisStringIdx = decoder.thisStringIdx; - int decodedSz = decodedLocals.size(); int paramBase = decoder.getParamBase(); - /* - * 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; + int matchedLocalsEntries = 0; - for (int i = 0; i < origSz; i++) { - LocalList.Entry origEntry = ll.get(i); + for (LocalEntry entry: decodedLocals) { + boolean found = false; + for (int i = ll.size() - 1; i >= 0; i--) { + LocalList.Entry le = ll.get(i); - if (origEntry.getDisposition() - == LocalList.Disposition.END_REPLACED) { /* - * The encoded list doesn't represent replacements, so - * ignore them for the sake of comparison. + * 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. */ - continue; - } - - LocalEntry decodedEntry; - - do { - decodedEntry = decodedLocals.get(decodeAt); - if (decodedEntry.nameIndex >= 0) { + if ((entry.start == le.getStart() + || (entry.start == 0 && entry.reg >= paramBase)) + && entry.end == le.getEnd() + && entry.reg == le.getRegister()) { + found = true; + matchedLocalsEntries++; 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; } - /* - * 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; + if (!found) { + throw new RuntimeException("Could not match local entry"); } - - decodeAt++; } - if (problem) { - System.err.println("decoded locals:"); - for (LocalEntry e : decodedLocals) { - System.err.println(" " + e); - } - throw new RuntimeException("local table problem"); + if (matchedLocalsEntries != ll.size()) { + throw new RuntimeException("Locals tables did not match"); } } diff --git a/dx/src/com/android/dx/dex/file/DebugInfoEncoder.java b/dx/src/com/android/dx/dex/file/DebugInfoEncoder.java index 780e18d5b..49781bdd8 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 positions; + private final PositionList positionlist; /** null-ok; local variables to encode */ - private final LocalList locals; + private final LocalList locallist; 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.positions = pl; - this.locals = ll; + this.positionlist = pl; + this.locallist = ll; this.file = file; output = new ByteArrayAnnotatedOutput(); this.desc = ref.getPrototype(); @@ -193,11 +193,18 @@ public final class DebugInfoEncoder { private byte[] convert0() throws IOException { ArrayList<PositionList.Entry> sortedPositions = buildSortedPositions(); - ArrayList<LocalList.Entry> methodArgs = extractMethodArguments(); + ArrayList<LocalList.Entry> sortedLocalsStart = buildLocalsStart(); + + // Parameter locals are removed from sortedLocalsStart here. + ArrayList<LocalList.Entry> methodArgs + = extractMethodArguments(sortedLocalsStart); + + ArrayList<LocalList.Entry> sortedLocalsEnd + = buildLocalsEnd(sortedLocalsStart); emitHeader(sortedPositions, methodArgs); - // TODO: Make this mark be the actual prologue end. + // TODO: Make this mark the actual prologue end. output.writeByte(DBG_SET_PROLOGUE_END); if (annotateTo != null || debugPrint != null) { @@ -205,37 +212,56 @@ public final class DebugInfoEncoder { } int szp = sortedPositions.size(); - int szl = locals.size(); + int szl = sortedLocalsStart.size(); // Current index in sortedPositions int curp = 0; - // Current index in locals - int curl = 0; + // Current index in sortedLocalsStart + int curls = 0; + // Current index in sortedLocalsEnd + int curle = 0; for (;;) { /* * Emit any information for the current address. */ - curl = emitLocalsAtAddress(curl); + 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); + curp = emitPositionsAtAddress(curp, sortedPositions); /* * Figure out what the next important address is. */ - int nextAddrL = Integer.MAX_VALUE; // local variable - int nextAddrP = Integer.MAX_VALUE; // position (line number) + 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(); + } - if (curl < szl) { - nextAddrL = locals.get(curl).getAddress(); + if (curle < szl) { + nextAddrLE = sortedLocalsEnd.get(curle).getEnd(); } if (curp < szp) { nextAddrP = sortedPositions.get(curp).getAddress(); } - int next = Math.min(nextAddrP, nextAddrL); + int next = Math.min(nextAddrP, Math.min(nextAddrLS, nextAddrLE)); // No next important address == done. if (next == Integer.MAX_VALUE) { @@ -247,7 +273,7 @@ public final class DebugInfoEncoder { * block, stop here. Those are implied anyway. */ if (next == codeSize - && nextAddrL == Integer.MAX_VALUE + && nextAddrLS == Integer.MAX_VALUE && nextAddrP == Integer.MAX_VALUE) { break; } @@ -266,24 +292,76 @@ public final class DebugInfoEncoder { } /** - * 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. + * Emits all local ends that occur at the current <code>address</code> * - * @param curl Current index in locals - * @return new value for <code>curl</code> + * @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> * @throws IOException */ - private int emitLocalsAtAddress(int curl) + private int emitLocalEndsAtAddress(int curle, + ArrayList<LocalList.Entry> sortedLocalsEnd, int curls, + ArrayList<LocalList.Entry> sortedLocalsStart) throws IOException { - int sz = locals.size(); - // TODO: Don't emit ends implied by starts. + int szl = sortedLocalsEnd.size(); - while ((curl < sz) - && (locals.get(curl).getAddress() == address)) { - LocalList.Entry lle = locals.get(curl++); + // 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++); int reg = lle.getRegister(); LocalList.Entry prevlle = lastEntryForReg[reg]; @@ -296,45 +374,26 @@ public final class DebugInfoEncoder { continue; } - // At this point we have a new entry one way or another. + // At this point we have a new live entry one way or another. lastEntryForReg[reg] = lle; - if (lle.isStart()) { - if ((prevlle != null) && lle.matches(prevlle)) { + if ((prevlle != null) && lle.matches(prevlle)) { + if (prevlle.getEnd() == lle.getStart()) { /* - * 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. + * 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. */ - 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); + emitLocalRestart(lle); } } else { - /* - * 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); - } + emitLocalStart(lle); } } - - return curl; + return curls; } /** @@ -465,7 +524,7 @@ public final class DebugInfoEncoder { * a LOCAL_RESTART_EXTENDED */ - for (LocalList.Entry arg : lastEntryForReg) { + for (LocalList.Entry arg: lastEntryForReg) { if (arg == null) { continue; } @@ -484,11 +543,11 @@ public final class DebugInfoEncoder { * @return A sorted positions list */ private ArrayList<PositionList.Entry> buildSortedPositions() { - int sz = (positions == null) ? 0 : positions.size(); + int sz = (positionlist == null) ? 0 : positionlist.size(); ArrayList<PositionList.Entry> result = new ArrayList(sz); for (int i = 0; i < sz; i++) { - result.add(positions.get(i)); + result.add(positionlist.get(i)); } // Sort ascending by address. @@ -505,6 +564,58 @@ 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> @@ -521,18 +632,24 @@ 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() { + private ArrayList<LocalList.Entry> extractMethodArguments ( + ArrayList<LocalList.Entry> sortedLocals) { + 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 = locals.get(i); + LocalList.Entry e = sortedLocals.get(i); int reg = e.getRegister(); if (reg < argBase) { @@ -549,12 +666,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 0e4329b14..b0745935d 100644 --- a/dx/src/com/android/dx/dex/file/DebugInfoItem.java +++ b/dx/src/com/android/dx/dex/file/DebugInfoItem.java @@ -141,37 +141,6 @@ 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(); @@ -187,8 +156,13 @@ public class DebugInfoItem extends OffsettedItem { if ((debugPrint == null) && (out == null)) { result = encoder.convert(); } else { - result = encoder.convertAndAnnotate(prefix, debugPrint, out, - consume); + result = encoder.convertAndAnnotate( + prefix, debugPrint, out, consume); + } + + if (ENABLE_ENCODER_SELF_CHECK && (file != null)) { + DebugInfoDecoder.validateEncode(encoded, codeSize, + regSize, positions, locals, isStatic, ref, file); } 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 bac6ce2a4..b1e1a4be6 100644 --- a/dx/src/com/android/dx/rop/code/LocalItem.java +++ b/dx/src/com/android/dx/rop/code/LocalItem.java @@ -22,6 +22,7 @@ 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; @@ -37,7 +38,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; } @@ -51,14 +52,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; } @@ -88,7 +89,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); @@ -105,7 +106,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()); } @@ -120,7 +121,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 73af91fbc..09f7f1829 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, Comparable<RegisterSpec> { + implements TypeBearer, ToHuman { /** non-null; string to prefix register numbers with */ public static final String PREFIX = "v"; @@ -97,8 +97,7 @@ 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"); } @@ -173,43 +172,6 @@ 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. * @@ -226,35 +188,6 @@ 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() { @@ -359,8 +292,6 @@ 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() { @@ -368,30 +299,6 @@ 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 @@ -416,16 +323,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) { @@ -439,8 +346,7 @@ 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 adc77c35e..4eb2f65e8 100644 --- a/dx/src/com/android/dx/rop/code/RegisterSpecSet.java +++ b/dx/src/com/android/dx/rop/code/RegisterSpecSet.java @@ -187,47 +187,16 @@ 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 - * local (name and signature), or {@code null} if there is none. + * name, or null if there is none. * * @param local non-null; local item to search for - * @return null-ok; first register found with matching name and signature + * @return null-ok; first register found with name. */ public RegisterSpec localItemToSpec(LocalItem local) { - int length = specs.length; - - for (int reg = 0; reg < length; reg++) { - RegisterSpec spec = specs[reg]; - - if ((spec != null) && local.equals(spec.getLocalItem())) { - return spec; + for (int reg = 0; reg < specs.length; reg++) { + if (specs[reg] != null && local.equals(specs[reg].getLocalItem())) { + return specs[reg]; } } @@ -291,22 +260,6 @@ 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 |