summaryrefslogtreecommitdiff
path: root/dx/src/com/android/dx/cf/code/Simulator.java
diff options
context:
space:
mode:
Diffstat (limited to 'dx/src/com/android/dx/cf/code/Simulator.java')
-rw-r--r--dx/src/com/android/dx/cf/code/Simulator.java772
1 files changed, 772 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/cf/code/Simulator.java b/dx/src/com/android/dx/cf/code/Simulator.java
new file mode 100644
index 0000000..c097831
--- /dev/null
+++ b/dx/src/com/android/dx/cf/code/Simulator.java
@@ -0,0 +1,772 @@
+/*
+ * 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.cf.code;
+
+import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.CstFieldRef;
+import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstInterfaceMethodRef;
+import com.android.dx.rop.cst.CstMethodRef;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Prototype;
+import com.android.dx.rop.type.Type;
+import com.android.dx.rop.code.LocalItem;
+import com.android.dx.util.Hex;
+
+import java.util.ArrayList;
+
+/**
+ * Class which knows how to simulate the effects of executing bytecode.
+ *
+ * <p><b>Note:</b> This class is not thread-safe. If multiple threads
+ * need to use a single instance, they must synchronize access explicitly
+ * between themselves.</p>
+ */
+public class Simulator {
+ /**
+ * {@code non-null;} canned error message for local variable
+ * table mismatches
+ */
+ private static final String LOCAL_MISMATCH_ERROR =
+ "This is symptomatic of .class transformation tools that ignore " +
+ "local variable information.";
+
+ /** {@code non-null;} machine to use when simulating */
+ private final Machine machine;
+
+ /** {@code non-null;} array of bytecode */
+ private final BytecodeArray code;
+
+ /** {@code non-null;} local variable information */
+ private final LocalVariableList localVariables;
+
+ /** {@code non-null;} visitor instance to use */
+ private final SimVisitor visitor;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param machine {@code non-null;} machine to use when simulating
+ * @param method {@code non-null;} method data to use
+ */
+ public Simulator(Machine machine, ConcreteMethod method) {
+ if (machine == null) {
+ throw new NullPointerException("machine == null");
+ }
+
+ if (method == null) {
+ throw new NullPointerException("method == null");
+ }
+
+ this.machine = machine;
+ this.code = method.getCode();
+ this.localVariables = method.getLocalVariables();
+ this.visitor = new SimVisitor();
+ }
+
+ /**
+ * Simulates the effect of executing the given basic block. This modifies
+ * the passed-in frame to represent the end result.
+ *
+ * @param bb {@code non-null;} the basic block
+ * @param frame {@code non-null;} frame to operate on
+ */
+ public void simulate(ByteBlock bb, Frame frame) {
+ int end = bb.getEnd();
+
+ visitor.setFrame(frame);
+
+ try {
+ for (int off = bb.getStart(); off < end; /*off*/) {
+ int length = code.parseInstruction(off, visitor);
+ visitor.setPreviousOffset(off);
+ off += length;
+ }
+ } catch (SimException ex) {
+ frame.annotate(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * Simulates the effect of the instruction at the given offset, by
+ * making appropriate calls on the given frame.
+ *
+ * @param offset {@code >= 0;} offset of the instruction to simulate
+ * @param frame {@code non-null;} frame to operate on
+ * @return the length of the instruction, in bytes
+ */
+ public int simulate(int offset, Frame frame) {
+ visitor.setFrame(frame);
+ return code.parseInstruction(offset, visitor);
+ }
+
+ /**
+ * Constructs an "illegal top-of-stack" exception, for the stack
+ * manipulation opcodes.
+ */
+ private static SimException illegalTos() {
+ return new SimException("stack mismatch: illegal " +
+ "top-of-stack for opcode");
+ }
+
+ /**
+ * Returns the required array type for an array load or store
+ * instruction, based on a given implied type and an observed
+ * actual array type.
+ *
+ * <p>The interesting cases here have to do with object arrays,
+ * <code>byte[]</code>s, <code>boolean[]</code>s, and
+ * known-nulls.</p>
+ *
+ * <p>In the case of arrays of objects, we want to narrow the type
+ * to the actual array present on the stack, as long as what is
+ * present is an object type. Similarly, due to a quirk of the
+ * original bytecode representation, the instructions for dealing
+ * with <code>byte[]</code> and <code>boolean[]</code> are
+ * undifferentiated, and we aim here to return whichever one was
+ * actually present on the stack.</p>
+ *
+ * <p>In the case where there is a known-null on the stack where
+ * an array is expected, we just fall back to the implied type of
+ * the instruction. Due to the quirk described above, this means
+ * that source code that uses <code>boolean[]</code> might get
+ * translated surprisingly -- but correctly -- into an instruction
+ * that specifies a <code>byte[]</code>. It will be correct,
+ * because should the code actually execute, it will necessarily
+ * throw a <code>NullPointerException</code>, and it won't matter
+ * what opcode variant is used to achieve that result.</p>
+ *
+ * @param impliedType {@code non-null;} type implied by the
+ * instruction; is <i>not</i> an array type
+ * @param foundArrayType {@code non-null;} type found on the
+ * stack; is either an array type or a known-null
+ * @return {@code non-null;} the array type that should be
+ * required in this context
+ */
+ private static Type requiredArrayTypeFor(Type impliedType,
+ Type foundArrayType) {
+ if (foundArrayType == Type.KNOWN_NULL) {
+ return impliedType.getArrayType();
+ }
+
+ if ((impliedType == Type.OBJECT)
+ && foundArrayType.isArray()
+ && foundArrayType.getComponentType().isReference()) {
+ return foundArrayType;
+ }
+
+ if ((impliedType == Type.BYTE)
+ && (foundArrayType == Type.BOOLEAN_ARRAY)) {
+ /*
+ * Per above, an instruction with implied byte[] is also
+ * allowed to be used on boolean[].
+ */
+ return Type.BOOLEAN_ARRAY;
+ }
+
+ return impliedType.getArrayType();
+ }
+
+ /**
+ * Bytecode visitor used during simulation.
+ */
+ private class SimVisitor implements BytecodeArray.Visitor {
+ /**
+ * {@code non-null;} machine instance to use (just to avoid excessive
+ * cross-object field access)
+ */
+ private final Machine machine;
+
+ /**
+ * {@code null-ok;} frame to use; set with each call to
+ * {@link Simulator#simulate}
+ */
+ private Frame frame;
+
+ /** offset of the previous bytecode */
+ private int previousOffset;
+
+ /**
+ * Constructs an instance.
+ */
+ public SimVisitor() {
+ this.machine = Simulator.this.machine;
+ this.frame = null;
+ }
+
+ /**
+ * Sets the frame to act on.
+ *
+ * @param frame {@code non-null;} the frame
+ */
+ public void setFrame(Frame frame) {
+ if (frame == null) {
+ throw new NullPointerException("frame == null");
+ }
+
+ this.frame = frame;
+ }
+
+ /** {@inheritDoc} */
+ public void visitInvalid(int opcode, int offset, int length) {
+ throw new SimException("invalid opcode " + Hex.u1(opcode));
+ }
+
+ /** {@inheritDoc} */
+ public void visitNoArgs(int opcode, int offset, int length,
+ Type type) {
+ switch (opcode) {
+ case ByteOps.NOP: {
+ machine.clearArgs();
+ break;
+ }
+ case ByteOps.INEG: {
+ machine.popArgs(frame, type);
+ break;
+ }
+ case ByteOps.I2L:
+ case ByteOps.I2F:
+ case ByteOps.I2D:
+ case ByteOps.I2B:
+ case ByteOps.I2C:
+ case ByteOps.I2S: {
+ machine.popArgs(frame, Type.INT);
+ break;
+ }
+ case ByteOps.L2I:
+ case ByteOps.L2F:
+ case ByteOps.L2D: {
+ machine.popArgs(frame, Type.LONG);
+ break;
+ }
+ case ByteOps.F2I:
+ case ByteOps.F2L:
+ case ByteOps.F2D: {
+ machine.popArgs(frame, Type.FLOAT);
+ break;
+ }
+ case ByteOps.D2I:
+ case ByteOps.D2L:
+ case ByteOps.D2F: {
+ machine.popArgs(frame, Type.DOUBLE);
+ break;
+ }
+ case ByteOps.RETURN: {
+ machine.clearArgs();
+ checkReturnType(Type.VOID);
+ break;
+ }
+ case ByteOps.IRETURN: {
+ Type checkType = type;
+ if (type == Type.OBJECT) {
+ /*
+ * For an object return, use the best-known
+ * type of the popped value.
+ */
+ checkType = frame.getStack().peekType(0);
+ }
+ machine.popArgs(frame, type);
+ checkReturnType(checkType);
+ break;
+ }
+ case ByteOps.POP: {
+ Type peekType = frame.getStack().peekType(0);
+ if (peekType.isCategory2()) {
+ throw illegalTos();
+ }
+ machine.popArgs(frame, 1);
+ break;
+ }
+ case ByteOps.ARRAYLENGTH: {
+ Type arrayType = frame.getStack().peekType(0);
+ if (!arrayType.isArrayOrKnownNull()) {
+ throw new SimException("type mismatch: expected " +
+ "array type but encountered " +
+ arrayType.toHuman());
+ }
+ machine.popArgs(frame, Type.OBJECT);
+ break;
+ }
+ case ByteOps.ATHROW:
+ case ByteOps.MONITORENTER:
+ case ByteOps.MONITOREXIT: {
+ machine.popArgs(frame, Type.OBJECT);
+ break;
+ }
+ case ByteOps.IALOAD: {
+ /*
+ * See comment on requiredArrayTypeFor() for explanation
+ * about what's going on here.
+ */
+ Type foundArrayType = frame.getStack().peekType(1);
+ Type requiredArrayType =
+ requiredArrayTypeFor(type, foundArrayType);
+
+ // Make type agree with the discovered requiredArrayType.
+ type = requiredArrayType.getComponentType();
+
+ machine.popArgs(frame, requiredArrayType, Type.INT);
+ break;
+ }
+ case ByteOps.IADD:
+ case ByteOps.ISUB:
+ case ByteOps.IMUL:
+ case ByteOps.IDIV:
+ case ByteOps.IREM:
+ case ByteOps.IAND:
+ case ByteOps.IOR:
+ case ByteOps.IXOR: {
+ machine.popArgs(frame, type, type);
+ break;
+ }
+ case ByteOps.ISHL:
+ case ByteOps.ISHR:
+ case ByteOps.IUSHR: {
+ machine.popArgs(frame, type, Type.INT);
+ break;
+ }
+ case ByteOps.LCMP: {
+ machine.popArgs(frame, Type.LONG, Type.LONG);
+ break;
+ }
+ case ByteOps.FCMPL:
+ case ByteOps.FCMPG: {
+ machine.popArgs(frame, Type.FLOAT, Type.FLOAT);
+ break;
+ }
+ case ByteOps.DCMPL:
+ case ByteOps.DCMPG: {
+ machine.popArgs(frame, Type.DOUBLE, Type.DOUBLE);
+ break;
+ }
+ case ByteOps.IASTORE: {
+ /*
+ * See comment on requiredArrayTypeFor() for
+ * explanation about what's going on here. In
+ * addition to that, the category 1 vs. 2 thing
+ * below is to deal with the fact that, if the
+ * element type is category 2, we have to skip
+ * over one extra stack slot to find the array.
+ */
+ ExecutionStack stack = frame.getStack();
+ int peekDepth = type.isCategory1() ? 2 : 3;
+ Type foundArrayType = stack.peekType(peekDepth);
+ boolean foundArrayLocal = stack.peekLocal(peekDepth);
+
+ Type requiredArrayType =
+ requiredArrayTypeFor(type, foundArrayType);
+
+ /*
+ * Make type agree with the discovered requiredArrayType
+ * if it has local info.
+ */
+ if (foundArrayLocal) {
+ type = requiredArrayType.getComponentType();
+ }
+
+ machine.popArgs(frame, requiredArrayType, Type.INT, type);
+ break;
+ }
+ case ByteOps.POP2:
+ case ByteOps.DUP2: {
+ ExecutionStack stack = frame.getStack();
+ int pattern;
+
+ if (stack.peekType(0).isCategory2()) {
+ // "form 2" in vmspec-2
+ machine.popArgs(frame, 1);
+ pattern = 0x11;
+ } else if (stack.peekType(1).isCategory1()) {
+ // "form 1"
+ machine.popArgs(frame, 2);
+ pattern = 0x2121;
+ } else {
+ throw illegalTos();
+ }
+
+ if (opcode == ByteOps.DUP2) {
+ machine.auxIntArg(pattern);
+ }
+ break;
+ }
+ case ByteOps.DUP: {
+ Type peekType = frame.getStack().peekType(0);
+
+ if (peekType.isCategory2()) {
+ throw illegalTos();
+ }
+
+ machine.popArgs(frame, 1);
+ machine.auxIntArg(0x11);
+ break;
+ }
+ case ByteOps.DUP_X1: {
+ ExecutionStack stack = frame.getStack();
+
+ if (!(stack.peekType(0).isCategory1() &&
+ stack.peekType(1).isCategory1())) {
+ throw illegalTos();
+ }
+
+ machine.popArgs(frame, 2);
+ machine.auxIntArg(0x212);
+ break;
+ }
+ case ByteOps.DUP_X2: {
+ ExecutionStack stack = frame.getStack();
+
+ if (stack.peekType(0).isCategory2()) {
+ throw illegalTos();
+ }
+
+ if (stack.peekType(1).isCategory2()) {
+ // "form 2" in vmspec-2
+ machine.popArgs(frame, 2);
+ machine.auxIntArg(0x212);
+ } else if (stack.peekType(2).isCategory1()) {
+ // "form 1"
+ machine.popArgs(frame, 3);
+ machine.auxIntArg(0x3213);
+ } else {
+ throw illegalTos();
+ }
+ break;
+ }
+ case ByteOps.DUP2_X1: {
+ ExecutionStack stack = frame.getStack();
+
+ if (stack.peekType(0).isCategory2()) {
+ // "form 2" in vmspec-2
+ if (stack.peekType(2).isCategory2()) {
+ throw illegalTos();
+ }
+ machine.popArgs(frame, 2);
+ machine.auxIntArg(0x212);
+ } else {
+ // "form 1"
+ if (stack.peekType(1).isCategory2() ||
+ stack.peekType(2).isCategory2()) {
+ throw illegalTos();
+ }
+ machine.popArgs(frame, 3);
+ machine.auxIntArg(0x32132);
+ }
+ break;
+ }
+ case ByteOps.DUP2_X2: {
+ ExecutionStack stack = frame.getStack();
+
+ if (stack.peekType(0).isCategory2()) {
+ if (stack.peekType(2).isCategory2()) {
+ // "form 4" in vmspec-2
+ machine.popArgs(frame, 2);
+ machine.auxIntArg(0x212);
+ } else if (stack.peekType(3).isCategory1()) {
+ // "form 2"
+ machine.popArgs(frame, 3);
+ machine.auxIntArg(0x3213);
+ } else {
+ throw illegalTos();
+ }
+ } else if (stack.peekType(1).isCategory1()) {
+ if (stack.peekType(2).isCategory2()) {
+ // "form 3"
+ machine.popArgs(frame, 3);
+ machine.auxIntArg(0x32132);
+ } else if (stack.peekType(3).isCategory1()) {
+ // "form 1"
+ machine.popArgs(frame, 4);
+ machine.auxIntArg(0x432143);
+ } else {
+ throw illegalTos();
+ }
+ } else {
+ throw illegalTos();
+ }
+ break;
+ }
+ case ByteOps.SWAP: {
+ ExecutionStack stack = frame.getStack();
+
+ if (!(stack.peekType(0).isCategory1() &&
+ stack.peekType(1).isCategory1())) {
+ throw illegalTos();
+ }
+
+ machine.popArgs(frame, 2);
+ machine.auxIntArg(0x12);
+ break;
+ }
+ default: {
+ visitInvalid(opcode, offset, length);
+ return;
+ }
+ }
+
+ machine.auxType(type);
+ machine.run(frame, offset, opcode);
+ }
+
+ /**
+ * Checks whether the prototype is compatible with returning the
+ * given type, and throws if not.
+ *
+ * @param encountered {@code non-null;} the encountered return type
+ */
+ private void checkReturnType(Type encountered) {
+ Type returnType = machine.getPrototype().getReturnType();
+
+ /*
+ * Check to see if the prototype's return type is
+ * possibly assignable from the type we encountered. This
+ * takes care of all the salient cases (types are the same,
+ * they're compatible primitive types, etc.).
+ */
+ if (!Merger.isPossiblyAssignableFrom(returnType, encountered)) {
+ throw new SimException("return type mismatch: prototype " +
+ "indicates " + returnType.toHuman() +
+ ", but encountered type " + encountered.toHuman());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void visitLocal(int opcode, int offset, int length,
+ int idx, Type type, int value) {
+ /*
+ * Note that the "type" parameter is always the simplest
+ * type based on the original opcode, e.g., "int" for
+ * "iload" (per se) and "Object" for "aload". So, when
+ * possible, we replace the type with the one indicated in
+ * the local variable table, though we still need to check
+ * to make sure it's valid for the opcode.
+ *
+ * The reason we use (offset + length) for the localOffset
+ * for a store is because it is only after the store that
+ * the local type becomes valid. On the other hand, the
+ * type associated with a load is valid at the start of
+ * the instruction.
+ */
+ int localOffset =
+ (opcode == ByteOps.ISTORE) ? (offset + length) : offset;
+ LocalVariableList.Item local =
+ localVariables.pcAndIndexToLocal(localOffset, idx);
+ Type localType;
+
+ if (local != null) {
+ localType = local.getType();
+ if (localType.getBasicFrameType() !=
+ type.getBasicFrameType()) {
+ BaseMachine.throwLocalMismatch(type, localType);
+ return;
+ }
+ } else {
+ localType = type;
+ }
+
+ switch (opcode) {
+ case ByteOps.ILOAD:
+ case ByteOps.RET: {
+ machine.localArg(frame, idx);
+ machine.localInfo(local != null);
+ machine.auxType(type);
+ break;
+ }
+ case ByteOps.ISTORE: {
+ LocalItem item
+ = (local == null) ? null : local.getLocalItem();
+ machine.popArgs(frame, type);
+ machine.auxType(type);
+ machine.localTarget(idx, localType, item);
+ break;
+ }
+ case ByteOps.IINC: {
+ LocalItem item
+ = (local == null) ? null : local.getLocalItem();
+ machine.localArg(frame, idx);
+ machine.localTarget(idx, localType, item);
+ machine.auxType(type);
+ machine.auxIntArg(value);
+ machine.auxCstArg(CstInteger.make(value));
+ break;
+ }
+ default: {
+ visitInvalid(opcode, offset, length);
+ return;
+ }
+ }
+
+ machine.run(frame, offset, opcode);
+ }
+
+ /** {@inheritDoc} */
+ public void visitConstant(int opcode, int offset, int length,
+ Constant cst, int value) {
+ switch (opcode) {
+ case ByteOps.ANEWARRAY: {
+ machine.popArgs(frame, Type.INT);
+ break;
+ }
+ case ByteOps.PUTSTATIC: {
+ Type fieldType = ((CstFieldRef) cst).getType();
+ machine.popArgs(frame, fieldType);
+ break;
+ }
+ case ByteOps.GETFIELD:
+ case ByteOps.CHECKCAST:
+ case ByteOps.INSTANCEOF: {
+ machine.popArgs(frame, Type.OBJECT);
+ break;
+ }
+ case ByteOps.PUTFIELD: {
+ Type fieldType = ((CstFieldRef) cst).getType();
+ machine.popArgs(frame, Type.OBJECT, fieldType);
+ break;
+ }
+ case ByteOps.INVOKEINTERFACE: {
+ /*
+ * Convert the interface method ref into a normal
+ * method ref.
+ */
+ cst = ((CstInterfaceMethodRef) cst).toMethodRef();
+ // and fall through...
+ }
+ case ByteOps.INVOKEVIRTUAL:
+ case ByteOps.INVOKESPECIAL: {
+ /*
+ * Get the instance prototype, and use it to direct
+ * the machine.
+ */
+ Prototype prototype =
+ ((CstMethodRef) cst).getPrototype(false);
+ machine.popArgs(frame, prototype);
+ break;
+ }
+ case ByteOps.INVOKESTATIC: {
+ /*
+ * Get the static prototype, and use it to direct
+ * the machine.
+ */
+ Prototype prototype =
+ ((CstMethodRef) cst).getPrototype(true);
+ machine.popArgs(frame, prototype);
+ break;
+ }
+ case ByteOps.MULTIANEWARRAY: {
+ /*
+ * The "value" here is the count of dimensions to
+ * create. Make a prototype of that many "int"
+ * types, and tell the machine to pop them. This
+ * isn't the most efficient way in the world to do
+ * this, but then again, multianewarray is pretty
+ * darn rare and so not worth much effort
+ * optimizing for.
+ */
+ Prototype prototype =
+ Prototype.internInts(Type.VOID, value);
+ machine.popArgs(frame, prototype);
+ break;
+ }
+ default: {
+ machine.clearArgs();
+ break;
+ }
+ }
+
+ machine.auxIntArg(value);
+ machine.auxCstArg(cst);
+ machine.run(frame, offset, opcode);
+ }
+
+ /** {@inheritDoc} */
+ public void visitBranch(int opcode, int offset, int length,
+ int target) {
+ switch (opcode) {
+ case ByteOps.IFEQ:
+ case ByteOps.IFNE:
+ case ByteOps.IFLT:
+ case ByteOps.IFGE:
+ case ByteOps.IFGT:
+ case ByteOps.IFLE: {
+ machine.popArgs(frame, Type.INT);
+ break;
+ }
+ case ByteOps.IFNULL:
+ case ByteOps.IFNONNULL: {
+ machine.popArgs(frame, Type.OBJECT);
+ break;
+ }
+ case ByteOps.IF_ICMPEQ:
+ case ByteOps.IF_ICMPNE:
+ case ByteOps.IF_ICMPLT:
+ case ByteOps.IF_ICMPGE:
+ case ByteOps.IF_ICMPGT:
+ case ByteOps.IF_ICMPLE: {
+ machine.popArgs(frame, Type.INT, Type.INT);
+ break;
+ }
+ case ByteOps.IF_ACMPEQ:
+ case ByteOps.IF_ACMPNE: {
+ machine.popArgs(frame, Type.OBJECT, Type.OBJECT);
+ break;
+ }
+ case ByteOps.GOTO:
+ case ByteOps.JSR:
+ case ByteOps.GOTO_W:
+ case ByteOps.JSR_W: {
+ machine.clearArgs();
+ break;
+ }
+ default: {
+ visitInvalid(opcode, offset, length);
+ return;
+ }
+ }
+
+ machine.auxTargetArg(target);
+ machine.run(frame, offset, opcode);
+ }
+
+ /** {@inheritDoc} */
+ public void visitSwitch(int opcode, int offset, int length,
+ SwitchList cases, int padding) {
+ machine.popArgs(frame, Type.INT);
+ machine.auxIntArg(padding);
+ machine.auxSwitchArg(cases);
+ machine.run(frame, offset, opcode);
+ }
+
+ /** {@inheritDoc} */
+ public void visitNewarray(int offset, int length, CstType type,
+ ArrayList<Constant> initValues) {
+ machine.popArgs(frame, Type.INT);
+ machine.auxInitValues(initValues);
+ machine.auxCstArg(type);
+ machine.run(frame, offset, ByteOps.NEWARRAY);
+ }
+
+ /** {@inheritDoc} */
+ public void setPreviousOffset(int offset) {
+ previousOffset = offset;
+ }
+
+ /** {@inheritDoc} */
+ public int getPreviousOffset() {
+ return previousOffset;
+ }
+ }
+}