| /* |
| * Copyright (C) 2011 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.google.dexmaker; |
| |
| import com.android.dx.rop.code.BasicBlockList; |
| import com.android.dx.rop.code.Insn; |
| import com.android.dx.rop.code.PlainCstInsn; |
| import com.android.dx.rop.code.PlainInsn; |
| import com.android.dx.rop.code.RegisterSpecList; |
| import com.android.dx.rop.code.Rop; |
| import static com.android.dx.rop.code.Rop.BRANCH_GOTO; |
| import static com.android.dx.rop.code.Rop.BRANCH_NONE; |
| import static com.android.dx.rop.code.Rop.BRANCH_RETURN; |
| import com.android.dx.rop.code.Rops; |
| import com.android.dx.rop.code.SourcePosition; |
| import com.android.dx.rop.code.ThrowingCstInsn; |
| import com.android.dx.rop.code.ThrowingInsn; |
| import com.android.dx.rop.cst.CstInteger; |
| import com.android.dx.rop.type.StdTypeList; |
| import static com.android.dx.rop.type.Type.BT_BYTE; |
| import static com.android.dx.rop.type.Type.BT_CHAR; |
| import static com.android.dx.rop.type.Type.BT_INT; |
| import static com.android.dx.rop.type.Type.BT_SHORT; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * Builds a sequence of instructions. |
| * |
| * <h3>Locals</h3> |
| * All data manipulation takes place in local variables. Each parameter gets its |
| * own local by default; access these using {@link #getParameter |
| * getParameter()}. Non-static methods and constructors also have a {@code this} |
| * parameter; it's available as {@link #getThis getThis()}. Allocate a new local |
| * variable using {@link #newLocal newLocal()}, and assign a default value to it |
| * with {@link #loadConstant loadConstant()}. Copy a value from one local to |
| * another with {@link #move move()}. |
| * |
| * <p>Every local variable has a fixed type. This is either a primitive type (of |
| * any size) or a reference type. This class emits instructions appropriate to |
| * the types they operate on. Not all operations are local on all types; |
| * attempting to emit such an operation will fail with an unchecked exception. |
| * |
| * <h3>Math and Bit Operations</h3> |
| * Transform a single value into another related value using {@link |
| * #op(UnaryOp,Local,Local) op(UnaryOp, Local, Local)}. Transform two values |
| * into a third value using {@link #op(BinaryOp,Local,Local,Local) op(BinaryOp, |
| * Local, Local, Local)}. In either overload the first {@code Local} parameter |
| * is where the result will be sent; the other {@code Local} parameters are the |
| * inputs. |
| * |
| * <h3>Comparisons</h3> |
| * There are three different comparison operations each with different |
| * constraints: |
| * <ul> |
| * <li>{@link #compareLongs compareLongs()} compares two locals each |
| * containing a {@code long} primitive. This is the only operation that |
| * can compare longs. The result of the comparison is written to another |
| * {@code int} local.</li> |
| * <li>{@link #compareFloatingPoint compareFloatingPoint()} compares two |
| * locals; both {@code float} primitives or both {@code double} |
| * primitives. This is the only operation that can compare floating |
| * point values. This comparison takes an extra parameter that sets |
| * the desired result if either parameter is {@code NaN}. The result of |
| * the comparison is wrtten to another {@code int} local. |
| * <li>{@link #compare compare()} compares two locals. The {@link |
| * Comparison#EQ} and {@link Comparison#NE} options compare either |
| * {@code int} primitives or references. The other options compare only |
| * {@code int} primitives. This comparison takes a {@link Label} that |
| * will be jumped to if the comparison is true. If the comparison is |
| * false the next instruction in sequence will be executed. |
| * </ul> |
| * There's no single operation to compare longs and jump, or to compare ints and |
| * store the result in a local. Accomplish these goals by chaining multiple |
| * operations together. |
| * |
| * <h3>Branches, Labels and Returns</h3> |
| * Basic control flow is expressed using jumps and labels. Each label must be |
| * marked exactly once and may be jumped to any number of times. Create a label |
| * using its constructor: {@code new Label()}, and mark it using {@link #mark |
| * mark(Label)}. All jumps to a label will execute instructions starting from |
| * that label. You can jump to a label that hasn't yet been marked (jumping |
| * forward) or to a label that has already been marked (jumping backward). Jump |
| * unconditionally with {@link #jump jump(Label)} or conditionally based on a |
| * comparison using {@link #compare compare()}. |
| * |
| * <p>Most methods should contain either a return instruction. Void methods |
| * should use {@link #returnVoid()}; non-void methods should use {@link |
| * #returnValue returnValue()} with a local whose return type matches the |
| * method's return type. Constructors are considered void methods and should |
| * call {@link #returnVoid()}. Methods may make multiple returns. Methods |
| * containing no return statements must either loop infinitely or throw |
| * unconditionally; it is not legal to end a sequence of instructions without a |
| * jump, return or throw. |
| * |
| * <h3>Throwing and Catching</h3> |
| * This API uses labels to handle thrown exceptions, errors and throwables. Call |
| * {@link #addCatchClause addCatchClause()} to register the target label and |
| * throwable class. All statements that follow will jump to that catch clause if |
| * they throw a {@link Throwable} assignable to that type. Use {@link |
| * #removeCatchClause removeCatchClause()} to unregister the throwable class. |
| * |
| * <p>Throw an throwable by first assigning it to a local and then calling |
| * {@link #throwValue throwValue()}. Control flow will jump to the nearest label |
| * assigned to a type assignable to the thrown type. In this context, "nearest" |
| * means the label requiring the fewest stack frames to be popped. |
| * |
| * <h3>Calling methods</h3> |
| * A method's caller must know its return type, name, parameters, and invoke |
| * kind. Lookup a method on a type using {@link TypeId#getMethod |
| * TypeId.getMethod()}. This is more onerous than Java language invokes, which |
| * can infer the target method using the target object and parameters. There are |
| * four invoke kinds: |
| * <ul> |
| * <li>{@link #invokeStatic invokeStatic()} is used for static methods.</li> |
| * <li>{@link #invokeDirect invokeDirect()} is used for private instance |
| * methods and for constructors to call their superclass's |
| * constructor.</li> |
| * <li>{@link #invokeInterface invokeInterface()} is used to invoke a method |
| * whose declaring type is an interface.</li> |
| * <li>{@link #invokeVirtual invokeVirtual()} is used to invoke any other |
| * method. The target must not be static, private, a constructor, or an |
| * interface method.</li> |
| * <li>{@link #invokeSuper invokeSuper()} is used to invoke the closest |
| * superclass's virtual method. The target must not be static, private, |
| * a constructor method, or an interface method.</li> |
| * <li>{@link #newInstance newInstance()} is used to invoke a |
| * constructor.</li> |
| * </ul> |
| * All invoke methods take a local for the return value. For void methods this |
| * local is unused and may be null. |
| * |
| * <h3>Field Access</h3> |
| * Read static fields using {@link #sget sget()}; write them using {@link |
| * #sput sput()}. For instance values you'll need to specify the declaring |
| * instance; use {@link #getThis getThis()} in an instance method to use {@code |
| * this}. Read instance values using {@link #iget iget()} and write them with |
| * {@link #iput iput()}. |
| * |
| * <h3>Array Access</h3> |
| * Allocate an array using {@link #newArray newArray()}. Read an array's length |
| * with {@link #arrayLength arrayLength()} and its elements with {@link #aget |
| * aget()}. Write an array's elements with {@link #aput aput()}. |
| * |
| * <h3>Types</h3> |
| * Use {@link #cast cast()} to perform either a <strong>numeric cast</strong> or |
| * a <strong>type cast</strong>. Interrogate the type of a value in a local |
| * using {@link #instanceOfType instanceOfType()}. |
| * |
| * <h3>Synchronization</h3> |
| * Acquire a monitor using {@link #monitorEnter monitorEnter()}; release it with |
| * {@link #monitorExit monitorExit()}. It is the caller's responsibility to |
| * guarantee that enter and exit calls are balanced, even in the presence of |
| * exceptions thrown. |
| * |
| * <strong>Warning:</strong> Even if a method has the {@code synchronized} flag, |
| * dex requires instructions to acquire and release monitors manually. A method |
| * declared with {@link java.lang.reflect.Modifier#SYNCHRONIZED SYNCHRONIZED} |
| * but without manual calls to {@code monitorEnter()} and {@code monitorExit()} |
| * will not be synchronized when executed. |
| */ |
| public final class Code { |
| private final MethodId<?, ?> method; |
| /** |
| * All allocated labels. Although the order of the labels in this list |
| * shouldn't impact behavior, it is used to determine basic block indices. |
| */ |
| private final List<Label> labels = new ArrayList<Label>(); |
| |
| /** |
| * The label currently receiving instructions. This is null if the most |
| * recent instruction was a return or goto. |
| */ |
| private Label currentLabel; |
| |
| /** true once we've fixed the positions of the parameter registers */ |
| private boolean localsInitialized; |
| |
| private final Local<?> thisLocal; |
| |
| /** |
| * The parameters on this method. If this is non-static, the first parameter |
| * is 'thisLocal' and we have to offset the user's indices by one. |
| */ |
| private final List<Local<?>> parameters = new ArrayList<Local<?>>(); |
| private final List<Local<?>> locals = new ArrayList<Local<?>>(); |
| private SourcePosition sourcePosition = SourcePosition.NO_INFO; |
| private final List<TypeId<?>> catchTypes = new ArrayList<TypeId<?>>(); |
| private final List<Label> catchLabels = new ArrayList<Label>(); |
| private StdTypeList catches = StdTypeList.EMPTY; |
| |
| Code(DexMaker.MethodDeclaration methodDeclaration) { |
| this.method = methodDeclaration.method; |
| if (methodDeclaration.isStatic()) { |
| thisLocal = null; |
| } else { |
| thisLocal = Local.get(this, method.declaringType); |
| parameters.add(thisLocal); |
| } |
| for (TypeId<?> parameter : method.parameters.types) { |
| parameters.add(Local.get(this, parameter)); |
| } |
| this.currentLabel = new Label(); |
| adopt(this.currentLabel); |
| this.currentLabel.marked = true; |
| } |
| |
| /** |
| * Allocates a new local variable of type {@code type}. It is an error to |
| * allocate a local after instructions have been emitted. |
| */ |
| public <T> Local<T> newLocal(TypeId<T> type) { |
| if (localsInitialized) { |
| throw new IllegalStateException("Cannot allocate locals after adding instructions"); |
| } |
| Local<T> result = Local.get(this, type); |
| locals.add(result); |
| return result; |
| } |
| |
| /** |
| * Returns the local for the parameter at index {@code index} and of type |
| * {@code type}. |
| */ |
| public <T> Local<T> getParameter(int index, TypeId<T> type) { |
| if (thisLocal != null) { |
| index++; // adjust for the hidden 'this' parameter |
| } |
| return coerce(parameters.get(index), type); |
| } |
| |
| /** |
| * Returns the local for {@code this} of type {@code type}. It is an error |
| * to call {@code getThis()} if this is a static method. |
| */ |
| public <T> Local<T> getThis(TypeId<T> type) { |
| if (thisLocal == null) { |
| throw new IllegalStateException("static methods cannot access 'this'"); |
| } |
| return coerce(thisLocal, type); |
| } |
| |
| @SuppressWarnings("unchecked") // guarded by an equals check |
| private <T> Local<T> coerce(Local<?> local, TypeId<T> expectedType) { |
| if (!local.type.equals(expectedType)) { |
| throw new IllegalArgumentException( |
| "requested " + expectedType + " but was " + local.type); |
| } |
| return (Local<T>) local; |
| } |
| |
| /** |
| * Assigns registers to locals. From the spec: |
| * "the N arguments to a method land in the last N registers of the |
| * method's invocation frame, in order. Wide arguments consume two |
| * registers. Instance methods are passed a this reference as their |
| * first argument." |
| * |
| * In addition to assigning registers to each of the locals, this creates |
| * instructions to move parameters into their initial registers. These |
| * instructions are inserted before the code's first real instruction. |
| */ |
| void initializeLocals() { |
| if (localsInitialized) { |
| throw new AssertionError(); |
| } |
| localsInitialized = true; |
| |
| int reg = 0; |
| for (Local<?> local : locals) { |
| reg += local.initialize(reg); |
| } |
| int firstParamReg = reg; |
| List<Insn> moveParameterInstructions = new ArrayList<Insn>(); |
| for (Local<?> local : parameters) { |
| CstInteger paramConstant = CstInteger.make(reg - firstParamReg); |
| reg += local.initialize(reg); |
| moveParameterInstructions.add(new PlainCstInsn(Rops.opMoveParam(local.type.ropType), |
| sourcePosition, local.spec(), RegisterSpecList.EMPTY, paramConstant)); |
| } |
| labels.get(0).instructions.addAll(0, moveParameterInstructions); |
| } |
| |
| /** |
| * Returns the number of registers to hold the parameters. This includes the |
| * 'this' parameter if it exists. |
| */ |
| int paramSize() { |
| int result = 0; |
| for (Local<?> local : parameters) { |
| result += local.size(); |
| } |
| return result; |
| } |
| |
| // labels |
| |
| /** |
| * Assigns {@code target} to this code. |
| */ |
| private void adopt(Label target) { |
| if (target.code == this) { |
| return; // already adopted |
| } |
| if (target.code != null) { |
| throw new IllegalArgumentException("Cannot adopt label; it belongs to another Code"); |
| } |
| target.code = this; |
| labels.add(target); |
| } |
| |
| /** |
| * Start defining instructions for the named label. |
| */ |
| public void mark(Label label) { |
| adopt(label); |
| if (label.marked) { |
| throw new IllegalStateException("already marked"); |
| } |
| label.marked = true; |
| if (currentLabel != null) { |
| jump(label); // blocks must end with a branch, return or throw |
| } |
| currentLabel = label; |
| } |
| |
| /** |
| * Transfers flow control to the instructions at {@code target}. It is an |
| * error to jump to a label not marked on this {@code Code}. |
| */ |
| public void jump(Label target) { |
| adopt(target); |
| addInstruction(new PlainInsn(Rops.GOTO, sourcePosition, null, RegisterSpecList.EMPTY), |
| target); |
| } |
| |
| /** |
| * Registers {@code catchClause} as a branch target for all instructions |
| * in this frame that throw a class assignable to {@code toCatch}. This |
| * includes methods invoked from this frame. Deregister the clause using |
| * {@link #removeCatchClause removeCatchClause()}. It is an error to |
| * register a catch clause without also {@link #mark marking it} in the same |
| * {@code Code} instance. |
| */ |
| public void addCatchClause(TypeId<? extends Throwable> toCatch, Label catchClause) { |
| if (catchTypes.contains(toCatch)) { |
| throw new IllegalArgumentException("Already caught: " + toCatch); |
| } |
| adopt(catchClause); |
| catchTypes.add(toCatch); |
| catches = toTypeList(catchTypes); |
| catchLabels.add(catchClause); |
| } |
| |
| /** |
| * Deregisters the catch clause label for {@code toCatch} and returns it. |
| */ |
| public Label removeCatchClause(TypeId<? extends Throwable> toCatch) { |
| int index = catchTypes.indexOf(toCatch); |
| if (index == -1) { |
| throw new IllegalArgumentException("No catch clause: " + toCatch); |
| } |
| catchTypes.remove(index); |
| catches = toTypeList(catchTypes); |
| return catchLabels.remove(index); |
| } |
| |
| /** |
| * Throws the throwable in {@code toThrow}. |
| */ |
| public void throwValue(Local<? extends Throwable> toThrow) { |
| addInstruction(new ThrowingInsn(Rops.THROW, sourcePosition, |
| RegisterSpecList.make(toThrow.spec()), catches)); |
| } |
| |
| private StdTypeList toTypeList(List<TypeId<?>> types) { |
| StdTypeList result = new StdTypeList(types.size()); |
| for (int i = 0; i < types.size(); i++) { |
| result.set(i, types.get(i).ropType); |
| } |
| return result; |
| } |
| |
| private void addInstruction(Insn insn) { |
| addInstruction(insn, null); |
| } |
| |
| /** |
| * @param branch the branches to follow; interpretation depends on the |
| * instruction's branchingness. |
| */ |
| private void addInstruction(Insn insn, Label branch) { |
| if (currentLabel == null || !currentLabel.marked) { |
| throw new IllegalStateException("no current label"); |
| } |
| currentLabel.instructions.add(insn); |
| |
| switch (insn.getOpcode().getBranchingness()) { |
| case BRANCH_NONE: |
| if (branch != null) { |
| throw new IllegalArgumentException("unexpected branch: " + branch); |
| } |
| return; |
| |
| case BRANCH_RETURN: |
| if (branch != null) { |
| throw new IllegalArgumentException("unexpected branch: " + branch); |
| } |
| currentLabel = null; |
| break; |
| |
| case BRANCH_GOTO: |
| if (branch == null) { |
| throw new IllegalArgumentException("branch == null"); |
| } |
| currentLabel.primarySuccessor = branch; |
| currentLabel = null; |
| break; |
| |
| case Rop.BRANCH_IF: |
| if (branch == null) { |
| throw new IllegalArgumentException("branch == null"); |
| } |
| splitCurrentLabel(branch, Collections.<Label>emptyList()); |
| break; |
| |
| case Rop.BRANCH_THROW: |
| if (branch != null) { |
| throw new IllegalArgumentException("unexpected branch: " + branch); |
| } |
| splitCurrentLabel(null, new ArrayList<Label>(catchLabels)); |
| break; |
| |
| default: |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| /** |
| * Closes the current label and starts a new one. |
| * |
| * @param catchLabels an immutable list of catch labels |
| */ |
| private void splitCurrentLabel(Label alternateSuccessor, List<Label> catchLabels) { |
| Label newLabel = new Label(); |
| adopt(newLabel); |
| currentLabel.primarySuccessor = newLabel; |
| currentLabel.alternateSuccessor = alternateSuccessor; |
| currentLabel.catchLabels = catchLabels; |
| currentLabel = newLabel; |
| currentLabel.marked = true; |
| } |
| |
| // instructions: locals |
| |
| /** |
| * Copies the constant value {@code value} to {@code target}. The constant |
| * must be a primitive, String, Class, TypeId, or null. |
| */ |
| public <T> void loadConstant(Local<T> target, T value) { |
| Rop rop = value == null |
| ? Rops.CONST_OBJECT_NOTHROW |
| : Rops.opConst(target.type.ropType); |
| if (rop.getBranchingness() == BRANCH_NONE) { |
| addInstruction(new PlainCstInsn(rop, sourcePosition, target.spec(), |
| RegisterSpecList.EMPTY, Constants.getConstant(value))); |
| } else { |
| addInstruction(new ThrowingCstInsn(rop, sourcePosition, |
| RegisterSpecList.EMPTY, catches, Constants.getConstant(value))); |
| moveResult(target, true); |
| } |
| } |
| |
| /** |
| * Copies the value in {@code source} to {@code target}. |
| */ |
| public <T> void move(Local<T> target, Local<T> source) { |
| addInstruction(new PlainInsn(Rops.opMove(source.type.ropType), |
| sourcePosition, target.spec(), source.spec())); |
| } |
| |
| // instructions: unary and binary |
| |
| /** |
| * Executes {@code op} and sets {@code target} to the result. |
| */ |
| public <T> void op(UnaryOp op, Local<T> target, Local<T> source) { |
| addInstruction(new PlainInsn(op.rop(source.type), sourcePosition, |
| target.spec(), source.spec())); |
| } |
| |
| /** |
| * Executes {@code op} and sets {@code target} to the result. |
| */ |
| public <T> void op(BinaryOp op, Local<T> target, Local<T> a, Local<T> b) { |
| Rop rop = op.rop(StdTypeList.make(a.type.ropType, b.type.ropType)); |
| RegisterSpecList sources = RegisterSpecList.make(a.spec(), b.spec()); |
| |
| if (rop.getBranchingness() == BRANCH_NONE) { |
| addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), sources)); |
| } else { |
| addInstruction(new ThrowingInsn(rop, sourcePosition, sources, catches)); |
| moveResult(target, true); |
| } |
| } |
| |
| // instructions: branches |
| |
| /** |
| * Compare ints or references. If the comparison is true, execution jumps to |
| * {@code trueLabel}. If it is false, execution continues to the next |
| * instruction. |
| */ |
| public <T> void compare(Comparison comparison, Label trueLabel, Local<T> a, Local<T> b) { |
| adopt(trueLabel); |
| // TODO: ops to compare with zero/null: just omit the 2nd local in StdTypeList.make() |
| Rop rop = comparison.rop(StdTypeList.make(a.type.ropType, b.type.ropType)); |
| addInstruction(new PlainInsn(rop, sourcePosition, null, |
| RegisterSpecList.make(a.spec(), b.spec())), trueLabel); |
| } |
| |
| /** |
| * Compare floats or doubles. This stores -1 in {@code target} if {@code |
| * a < b}, 0 in {@code target} if {@code a == b} and 1 in target if {@code |
| * a > b}. This stores {@code nanValue} in {@code target} if either value |
| * is {@code NaN}. |
| */ |
| public <T extends Number> void compareFloatingPoint( |
| Local<Integer> target, Local<T> a, Local<T> b, int nanValue) { |
| Rop rop; |
| if (nanValue == 1) { |
| rop = Rops.opCmpg(a.type.ropType); |
| } else if (nanValue == -1) { |
| rop = Rops.opCmpl(a.type.ropType); |
| } else { |
| throw new IllegalArgumentException("expected 1 or -1 but was " + nanValue); |
| } |
| addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), |
| RegisterSpecList.make(a.spec(), b.spec()))); |
| } |
| |
| /** |
| * Compare longs. This stores -1 in {@code target} if {@code |
| * a < b}, 0 in {@code target} if {@code a == b} and 1 in target if {@code |
| * a > b}. |
| */ |
| public void compareLongs(Local<Integer> target, Local<Long> a, Local<Long> b) { |
| addInstruction(new PlainInsn(Rops.CMPL_LONG, sourcePosition, target.spec(), |
| RegisterSpecList.make(a.spec(), b.spec()))); |
| } |
| |
| // instructions: fields |
| |
| /** |
| * Copies the value in instance field {@code fieldId} of {@code instance} to |
| * {@code target}. |
| */ |
| public <D, V> void iget(FieldId<D, V> fieldId, Local<V> target, Local<D> instance) { |
| addInstruction(new ThrowingCstInsn(Rops.opGetField(target.type.ropType), sourcePosition, |
| RegisterSpecList.make(instance.spec()), catches, fieldId.constant)); |
| moveResult(target, true); |
| } |
| |
| /** |
| * Copies the value in {@code source} to the instance field {@code fieldId} |
| * of {@code instance}. |
| */ |
| public <D, V> void iput(FieldId<D, V> fieldId, Local<D> instance, Local<V> source) { |
| addInstruction(new ThrowingCstInsn(Rops.opPutField(source.type.ropType), sourcePosition, |
| RegisterSpecList.make(source.spec(), instance.spec()), catches, fieldId.constant)); |
| } |
| |
| /** |
| * Copies the value in {@code target} to the static field {@code fieldId}. |
| */ |
| public <V> void sget(FieldId<?, V> fieldId, Local<V> target) { |
| addInstruction(new ThrowingCstInsn(Rops.opGetStatic(target.type.ropType), sourcePosition, |
| RegisterSpecList.EMPTY, catches, fieldId.constant)); |
| moveResult(target, true); |
| } |
| |
| /** |
| * Copies the value in {@code source} to the static field {@code fieldId}. |
| */ |
| public <V> void sput(FieldId<?, V> fieldId, Local<V> source) { |
| addInstruction(new ThrowingCstInsn(Rops.opPutStatic(source.type.ropType), sourcePosition, |
| RegisterSpecList.make(source.spec()), catches, fieldId.constant)); |
| } |
| |
| // instructions: invoke |
| |
| /** |
| * Calls the constructor {@code constructor} using {@code args} and assigns |
| * the new instance to {@code target}. |
| */ |
| public <T> void newInstance(Local<T> target, MethodId<T, Void> constructor, Local<?>... args) { |
| if (target == null) { |
| throw new IllegalArgumentException(); |
| } |
| addInstruction(new ThrowingCstInsn(Rops.NEW_INSTANCE, sourcePosition, |
| RegisterSpecList.EMPTY, catches, constructor.declaringType.constant)); |
| moveResult(target, true); |
| invokeDirect(constructor, null, target, args); |
| } |
| |
| /** |
| * Calls the static method {@code method} using {@code args} and assigns the |
| * result to {@code target}. |
| * |
| * @param target the local to receive the method's return value, or {@code |
| * null} if the return type is {@code void} or if its value not needed. |
| */ |
| public <R> void invokeStatic(MethodId<?, R> method, Local<? super R> target, Local<?>... args) { |
| invoke(Rops.opInvokeStatic(method.prototype(true)), method, target, null, args); |
| } |
| |
| /** |
| * Calls the non-private instance method {@code method} of {@code instance} |
| * using {@code args} and assigns the result to {@code target}. |
| * |
| * @param method a non-private, non-static, method declared on a class. May |
| * not be an interface method or a constructor. |
| * @param target the local to receive the method's return value, or {@code |
| * null} if the return type is {@code void} or if its value not needed. |
| */ |
| public <D, R> void invokeVirtual(MethodId<D, R> method, Local<? super R> target, |
| Local<? extends D> instance, Local<?>... args) { |
| invoke(Rops.opInvokeVirtual(method.prototype(true)), method, target, instance, args); |
| } |
| |
| /** |
| * Calls {@code method} of {@code instance} using {@code args} and assigns |
| * the result to {@code target}. |
| * |
| * @param method either a private method or the superclass's constructor in |
| * a constructor's call to {@code super()}. |
| * @param target the local to receive the method's return value, or {@code |
| * null} if the return type is {@code void} or if its value not needed. |
| */ |
| public <D, R> void invokeDirect(MethodId<D, R> method, Local<? super R> target, |
| Local<? extends D> instance, Local<?>... args) { |
| invoke(Rops.opInvokeDirect(method.prototype(true)), method, target, instance, args); |
| } |
| |
| /** |
| * Calls the closest superclass's virtual method {@code method} of {@code |
| * instance} using {@code args} and assigns the result to {@code target}. |
| * |
| * @param target the local to receive the method's return value, or {@code |
| * null} if the return type is {@code void} or if its value not needed. |
| */ |
| public <D, R> void invokeSuper(MethodId<D, R> method, Local<? super R> target, |
| Local<? extends D> instance, Local<?>... args) { |
| invoke(Rops.opInvokeSuper(method.prototype(true)), method, target, instance, args); |
| } |
| |
| /** |
| * Calls the interface method {@code method} of {@code instance} using |
| * {@code args} and assigns the result to {@code target}. |
| * |
| * @param method a method declared on an interface. |
| * @param target the local to receive the method's return value, or {@code |
| * null} if the return type is {@code void} or if its value not needed. |
| */ |
| public <D, R> void invokeInterface(MethodId<D, R> method, Local<? super R> target, |
| Local<? extends D> instance, Local<?>... args) { |
| invoke(Rops.opInvokeInterface(method.prototype(true)), method, target, instance, args); |
| } |
| |
| private <D, R> void invoke(Rop rop, MethodId<D, R> method, Local<? super R> target, |
| Local<? extends D> object, Local<?>... args) { |
| addInstruction(new ThrowingCstInsn(rop, sourcePosition, concatenate(object, args), |
| catches, method.constant)); |
| if (target != null) { |
| moveResult(target, false); |
| } |
| } |
| |
| // instructions: types |
| |
| /** |
| * Tests if the value in {@code source} is assignable to {@code type}. If it |
| * is, {@code target} is assigned to 1; otherwise {@code target} is assigned |
| * to 0. |
| */ |
| public void instanceOfType(Local<?> target, Local<?> source, TypeId<?> type) { |
| addInstruction(new ThrowingCstInsn(Rops.INSTANCE_OF, sourcePosition, |
| RegisterSpecList.make(source.spec()), catches, type.constant)); |
| moveResult(target, true); |
| } |
| |
| /** |
| * Performs either a numeric cast or a type cast. |
| * |
| * <h3>Numeric Casts</h3> |
| * Converts a primitive to a different representation. Numeric casts may |
| * be lossy. For example, converting the double {@code 1.8d} to an integer |
| * yields {@code 1}, losing the fractional part. Converting the integer |
| * {@code 0x12345678} to a short yields {@code 0x5678}, losing the high |
| * bytes. The following numeric casts are supported: |
| * |
| * <p><table border="1"> |
| * <tr><th>From</th><th>To</th></tr> |
| * <tr><td>int</td><td>byte, char, short, long, float, double</td></tr> |
| * <tr><td>long</td><td>int, float, double</td></tr> |
| * <tr><td>float</td><td>int, long, double</td></tr> |
| * <tr><td>double</td><td>int, long, float</td></tr> |
| * </table> |
| * |
| * <p>For some primitive conversions it will be necessary to chain multiple |
| * cast operations. For example, to go from float to short one would first |
| * cast float to int and then int to short. |
| * |
| * <p>Numeric casts never throw {@link ClassCastException}. |
| * |
| * <h3>Type Casts</h3> |
| * Checks that a reference value is assignable to the target type. If it is |
| * assignable it is copied to the target local. If it is not assignable a |
| * {@link ClassCastException} is thrown. |
| */ |
| public void cast(Local<?> target, Local<?> source) { |
| if (source.getType().ropType.isReference()) { |
| addInstruction(new ThrowingCstInsn(Rops.CHECK_CAST, sourcePosition, |
| RegisterSpecList.make(source.spec()), catches, target.type.constant)); |
| moveResult(target, true); |
| } else { |
| addInstruction(new PlainInsn(getCastRop(source.type.ropType, target.type.ropType), |
| sourcePosition, target.spec(), source.spec())); |
| } |
| } |
| |
| private Rop getCastRop(com.android.dx.rop.type.Type sourceType, |
| com.android.dx.rop.type.Type targetType) { |
| if (sourceType.getBasicType() == BT_INT) { |
| switch (targetType.getBasicType()) { |
| case BT_SHORT: |
| return Rops.TO_SHORT; |
| case BT_CHAR: |
| return Rops.TO_CHAR; |
| case BT_BYTE: |
| return Rops.TO_BYTE; |
| } |
| } |
| return Rops.opConv(targetType, sourceType); |
| } |
| |
| // instructions: arrays |
| |
| /** |
| * Sets {@code target} to the length of the array in {@code array}. |
| */ |
| public <T> void arrayLength(Local<Integer> target, Local<T> array) { |
| addInstruction(new ThrowingInsn(Rops.ARRAY_LENGTH, sourcePosition, |
| RegisterSpecList.make(array.spec()), catches)); |
| moveResult(target, true); |
| } |
| |
| /** |
| * Assigns {@code target} to a newly allocated array of length {@code |
| * length}. The array's type is the same as {@code target}'s type. |
| */ |
| public <T> void newArray(Local<T> target, Local<Integer> length) { |
| addInstruction(new ThrowingCstInsn(Rops.opNewArray(target.type.ropType), sourcePosition, |
| RegisterSpecList.make(length.spec()), catches, target.type.constant)); |
| moveResult(target, true); |
| } |
| |
| /** |
| * Assigns {@code target} to the element of {@code array} at index {@code |
| * index}. |
| */ |
| public void aget(Local<?> target, Local<?> array, Local<Integer> index) { |
| addInstruction(new ThrowingInsn(Rops.opAget(target.type.ropType), sourcePosition, |
| RegisterSpecList.make(array.spec(), index.spec()), catches)); |
| moveResult(target, true); |
| } |
| |
| /** |
| * Sets the element at {@code index} in {@code array} the value in {@code |
| * source}. |
| */ |
| public void aput(Local<?> array, Local<Integer> index, Local<?> source) { |
| addInstruction(new ThrowingInsn(Rops.opAput(source.type.ropType), sourcePosition, |
| RegisterSpecList.make(source.spec(), array.spec(), index.spec()), catches)); |
| } |
| |
| // instructions: return |
| |
| /** |
| * Returns from a {@code void} method. After a return it is an error to |
| * define further instructions after a return without first {@link #mark |
| * marking} an existing unmarked label. |
| */ |
| public void returnVoid() { |
| if (!method.returnType.equals(TypeId.VOID)) { |
| throw new IllegalArgumentException("declared " + method.returnType |
| + " but returned void"); |
| } |
| addInstruction(new PlainInsn(Rops.RETURN_VOID, sourcePosition, null, |
| RegisterSpecList.EMPTY)); |
| } |
| |
| /** |
| * Returns the value in {@code result} to the calling method. After a return |
| * it is an error to define further instructions after a return without |
| * first {@link #mark marking} an existing unmarked label. |
| */ |
| public void returnValue(Local<?> result) { |
| if (!result.type.equals(method.returnType)) { |
| // TODO: this is probably too strict. |
| throw new IllegalArgumentException("declared " + method.returnType |
| + " but returned " + result.type); |
| } |
| addInstruction(new PlainInsn(Rops.opReturn(result.type.ropType), sourcePosition, |
| null, RegisterSpecList.make(result.spec()))); |
| } |
| |
| private void moveResult(Local<?> target, boolean afterNonInvokeThrowingInsn) { |
| Rop rop = afterNonInvokeThrowingInsn |
| ? Rops.opMoveResultPseudo(target.type.ropType) |
| : Rops.opMoveResult(target.type.ropType); |
| addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), RegisterSpecList.EMPTY)); |
| } |
| |
| // instructions; synchronized |
| |
| /** |
| * Awaits the lock on {@code monitor}, and acquires it. |
| */ |
| public void monitorEnter(Local<?> monitor) { |
| addInstruction(new ThrowingInsn(Rops.MONITOR_ENTER, sourcePosition, |
| RegisterSpecList.make(monitor.spec()), catches)); |
| } |
| |
| /** |
| * Releases the held lock on {@code monitor}. |
| */ |
| public void monitorExit(Local<?> monitor) { |
| addInstruction(new ThrowingInsn(Rops.MONITOR_ENTER, sourcePosition, |
| RegisterSpecList.make(monitor.spec()), catches)); |
| } |
| |
| // produce BasicBlocks for dex |
| |
| BasicBlockList toBasicBlocks() { |
| if (!localsInitialized) { |
| initializeLocals(); |
| } |
| |
| cleanUpLabels(); |
| |
| BasicBlockList result = new BasicBlockList(labels.size()); |
| for (int i = 0; i < labels.size(); i++) { |
| result.set(i, labels.get(i).toBasicBlock()); |
| } |
| return result; |
| } |
| |
| /** |
| * Removes empty labels and assigns IDs to non-empty labels. |
| */ |
| private void cleanUpLabels() { |
| int id = 0; |
| for (Iterator<Label> i = labels.iterator(); i.hasNext();) { |
| Label label = i.next(); |
| if (label.isEmpty()) { |
| i.remove(); |
| } else { |
| label.compact(); |
| label.id = id++; |
| } |
| } |
| } |
| |
| private static RegisterSpecList concatenate(Local<?> first, Local<?>[] rest) { |
| int offset = (first != null) ? 1 : 0; |
| RegisterSpecList result = new RegisterSpecList(offset + rest.length); |
| if (first != null) { |
| result.set(0, first.spec()); |
| } |
| for (int i = 0; i < rest.length; i++) { |
| result.set(i + offset, rest[i].spec()); |
| } |
| return result; |
| } |
| } |