com.android.dx.dex.code.LocalList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications.
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dx.dex.code;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecSet;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
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
* range of code which it is valid for, a register number, a name,
* and a type.
*/
public final class LocalList extends FixedSizeList {
/** {@code 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. All indices initially contain {@code null}.
*
* @param size {@code >= 0;} the size of the list
*/
public LocalList(int size) {
super(size);
}
/**
* Gets the element at the given index. It is an error to call
* this with the index for an element which was never set; if you
* do that, this will throw {@code NullPointerException}.
*
* @param n {@code >= 0, < size();} which index
* @return {@code non-null;} element at that index
*/
public Entry get(int n) {
return (Entry) get0(n);
}
/**
* Sets the entry at the given index.
*
* @param n {@code >= 0, < size();} which index
* @param entry {@code non-null;} the entry to set at {@code n}
*/
public void set(int n, Entry entry) {
set0(n, entry);
}
/**
* Does a human-friendly dump of this instance.
*
* @param out {@code non-null;} where to dump
* @param prefix {@code 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.
*/
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 implements Comparable {
/** {@code >= 0;} address */
private final int address;
/** {@code non-null;} disposition of the local */
private final Disposition disposition;
/** {@code non-null;} register spec representing the variable */
private final RegisterSpec spec;
/** {@code non-null;} variable type (derived from {@code spec}) */
private final CstType type;
/**
* Constructs an instance.
*
* @param address {@code >= 0;} address
* @param disposition {@code non-null;} disposition of the local
* @param spec {@code non-null;} register spec representing
* the variable
*/
public Entry(int address, Disposition disposition, RegisterSpec spec) {
if (address < 0) {
throw new IllegalArgumentException("address < 0");
}
if (disposition == null) {
throw new NullPointerException("disposition == null");
}
try {
if (spec.getLocalItem() == null) {
throw new NullPointerException(
"spec.getLocalItem() == null");
}
} catch (NullPointerException ex) {
// Elucidate the exception.
throw new NullPointerException("spec == null");
}
this.address = address;
this.disposition = disposition;
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 {@code 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 address.
*
* @return {@code >= 0;} the address
*/
public int getAddress() {
return address;
}
/**
* Gets the disposition.
*
* @return {@code non-null;} the disposition
*/
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;
}
/**
* Gets the variable name.
*
* @return {@code null-ok;} the variable name
*/
public CstString getName() {
return spec.getLocalItem().getName();
}
/**
* Gets the variable signature.
*
* @return {@code null-ok;} the variable signature
*/
public CstString getSignature() {
return spec.getLocalItem().getSignature();
}
/**
* Gets the variable's type.
*
* @return {@code non-null;} the type
*/
public CstType getType() {
return type;
}
/**
* Gets the number of the register holding the variable.
*
* @return {@code >= 0;} the number of the register holding
* the variable
*/
public int getRegister() {
return spec.getReg();
}
/**
* Gets the RegisterSpec of the register holding the variable.
*
* @return {@code non-null;} RegisterSpec of the holding register.
*/
public RegisterSpec getRegisterSpec() {
return spec;
}
/**
* Returns whether or not this instance matches the given spec.
*
* @param otherSpec {@code non-null;} the spec in question
* @return {@code true} iff this instance matches
* {@code spec}
*/
public boolean matches(RegisterSpec otherSpec) {
return spec.equalsUsingSimpleType(otherSpec);
}
/**
* Returns whether or not this instance matches the spec in
* the given instance.
*
* @param other {@code non-null;} another entry
* @return {@code true} iff this instance's spec matches
* {@code other}
*/
public boolean matches(Entry other) {
return matches(other.spec);
}
/**
* Returns an instance just like this one but with the disposition
* set as given.
*
* @param disposition {@code non-null;} the new disposition
* @return {@code 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 {@code non-null;} instructions to convert
* @return {@code 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);
}
}
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 {
/** {@code non-null;} result being collected */
private final ArrayList result;
/**
* {@code >= 0;} running count of nulled result entries, to help with
* sizing the final list
*/
private int nullResultCount;
/** {@code null-ok;} current register mappings */
private RegisterSpecSet regs;
/** {@code null-ok;} result indices where local ends are stored */
private int[] endIndices;
/** {@code >= 0;} last address seen */
private int lastAddress;
/**
* Constructs an instance.
*/
public MakeState(int initialSize) {
result = new ArrayList(initialSize);
nullResultCount = 0;
regs = null;
endIndices = null;
lastAddress = 0;
}
/**
* Checks the address and other vitals as a prerequisite to
* further processing.
*
* @param address {@code >= 0;} address about to be processed
* @param reg {@code >= 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 {@code >= 0;} the address
* @param specs {@code non-null;} spec set representing the locals
*/
public void snapshot(int address, RegisterSpecSet specs) {
if (DEBUG) {
System.err.printf("%04x snapshot %s\n", address, 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);
}
}
if (DEBUG) {
System.err.printf("%04x snapshot done\n", address);
}
}
/**
* Starts a local at the given address.
*
* @param address {@code >= 0;} the address
* @param startedLocal {@code non-null;} spec representing the
* started local
*/
public void startLocal(int address, RegisterSpec startedLocal) {
if (DEBUG) {
System.err.printf("%04x start %s\n", address, 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, using the disposition
* {@code END_SIMPLY}.
*
* @param address {@code >= 0;} the address
* @param endedLocal {@code non-null;} spec representing the
* local being ended
*/
public void endLocal(int address, RegisterSpec endedLocal) {
endLocal(address, endedLocal, Disposition.END_SIMPLY);
}
/**
* Ends a local at the given address.
*
* @param address {@code >= 0;} the address
* @param endedLocal {@code non-null;} spec representing the
* local being ended
* @param disposition reason for the end
*/
public void endLocal(int address, RegisterSpec endedLocal,
Disposition disposition) {
if (DEBUG) {
System.err.printf("%04x end %s\n", address, 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, 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 {@code >= 0;} the address
* @param endedLocal {@code 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.
*
* 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.
*
* @param orig {@code null-ok;} the original spec
* @return {@code 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 {@code >= 0;} the address
* @param disposition {@code non-null;} the disposition
* @param spec {@code 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). If
* this would cause an empty range for a local, this instead
* removes the local entirely.
*
* @param address {@code >= 0;} the address
* @param disposition {@code non-null;} the disposition
* @param spec {@code 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) {
// There is a previous end.
Entry endEntry = result.get(endAt);
if ((endEntry.getAddress() == address) &&
endEntry.getRegisterSpec().equals(spec)) {
/*
* The end is for the right address and variable, so
* update it.
*/
result.set(endAt, endEntry.withDisposition(disposition));
regs.remove(spec); // TODO: Is this line superfluous?
return;
}
}
endLocal(address, spec, disposition);
}
/**
* Finishes processing altogether and gets the result.
*
* @return {@code 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;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy