All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.dx.cf.code.Simulator Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
/*
 * 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.code.LocalItem;
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.util.Hex;
import java.util.ArrayList;

/**
 * Class which knows how to simulate the effects of executing bytecode.
 *
 * 

Note: This class is not thread-safe. If multiple threads * need to use a single instance, they must synchronize access explicitly * between themselves.

*/ 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. * *

The interesting cases here have to do with object arrays, * byte[]s, boolean[]s, and * known-nulls.

* *

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 byte[] and boolean[] are * undifferentiated, and we aim here to return whichever one was * actually present on the stack.

* *

In the case where there is a known-null on the stack where * an array is expected, our behavior depends on the implied type * of the instruction. When the implied type is a reference, we * don't attempt to infer anything, as we don't know the dimension * of the null constant and thus any explicit inferred type could * be wrong. When the implied type is a primitive, we fall back to * the implied type of the instruction. Due to the quirk described * above, this means that source code that uses * boolean[] might get translated surprisingly -- but * correctly -- into an instruction that specifies a * byte[]. It will be correct, because should the * code actually execute, it will necessarily throw a * NullPointerException, and it won't matter what * opcode variant is used to achieve that result.

* * @param impliedType {@code non-null;} type implied by the * instruction; is not 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.isReference() ? Type.KNOWN_NULL : 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 == Type.KNOWN_NULL) ? Type.KNOWN_NULL : 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 == Type.KNOWN_NULL) ? Type.KNOWN_NULL : 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 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy