| /* |
| * 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.RegisterSpec; |
| import com.android.dx.rop.code.LocalItem; |
| import com.android.dx.rop.cst.Constant; |
| import com.android.dx.rop.type.Prototype; |
| import com.android.dx.rop.type.StdTypeList; |
| import com.android.dx.rop.type.Type; |
| import com.android.dx.rop.type.TypeBearer; |
| import java.util.ArrayList; |
| |
| /** |
| * Base implementation of {@link Machine}. |
| * |
| * <p><b>Note:</b> For the most part, the documentation for this class |
| * ignores the distinction between {@link Type} and {@link |
| * TypeBearer}.</p> |
| */ |
| public abstract class BaseMachine implements Machine { |
| /* {@code non-null;} the prototype for the associated method */ |
| private final Prototype prototype; |
| |
| /** {@code non-null;} primary arguments */ |
| private TypeBearer[] args; |
| |
| /** {@code >= 0;} number of primary arguments */ |
| private int argCount; |
| |
| /** {@code null-ok;} type of the operation, if salient */ |
| private Type auxType; |
| |
| /** auxiliary {@code int} argument */ |
| private int auxInt; |
| |
| /** {@code null-ok;} auxiliary constant argument */ |
| private Constant auxCst; |
| |
| /** auxiliary branch target argument */ |
| private int auxTarget; |
| |
| /** {@code null-ok;} auxiliary switch cases argument */ |
| private SwitchList auxCases; |
| |
| /** {@code null-ok;} auxiliary initial value list for newarray */ |
| private ArrayList<Constant> auxInitValues; |
| |
| /** {@code >= -1;} last local accessed */ |
| private int localIndex; |
| |
| /** specifies if local has info in the local variable table */ |
| private boolean localInfo; |
| |
| /** {@code null-ok;} local target spec, if salient and calculated */ |
| private RegisterSpec localTarget; |
| |
| /** {@code non-null;} results */ |
| private TypeBearer[] results; |
| |
| /** |
| * {@code >= -1;} count of the results, or {@code -1} if no results |
| * have been set |
| */ |
| private int resultCount; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param prototype {@code non-null;} the prototype for the |
| * associated method |
| */ |
| public BaseMachine(Prototype prototype) { |
| if (prototype == null) { |
| throw new NullPointerException("prototype == null"); |
| } |
| |
| this.prototype = prototype; |
| args = new TypeBearer[10]; |
| results = new TypeBearer[6]; |
| clearArgs(); |
| } |
| |
| /** {@inheritDoc} */ |
| public Prototype getPrototype() { |
| return prototype; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void clearArgs() { |
| argCount = 0; |
| auxType = null; |
| auxInt = 0; |
| auxCst = null; |
| auxTarget = 0; |
| auxCases = null; |
| auxInitValues = null; |
| localIndex = -1; |
| localInfo = false; |
| localTarget = null; |
| resultCount = -1; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void popArgs(Frame frame, int count) { |
| ExecutionStack stack = frame.getStack(); |
| |
| clearArgs(); |
| |
| if (count > args.length) { |
| // Grow args, and add a little extra room to grow even more. |
| args = new TypeBearer[count + 10]; |
| } |
| |
| for (int i = count - 1; i >= 0; i--) { |
| args[i] = stack.pop(); |
| } |
| |
| argCount = count; |
| } |
| |
| /** {@inheritDoc} */ |
| public void popArgs(Frame frame, Prototype prototype) { |
| StdTypeList types = prototype.getParameterTypes(); |
| int size = types.size(); |
| |
| // Use the above method to do the actual popping... |
| popArgs(frame, size); |
| |
| // ...and then verify the popped types. |
| |
| for (int i = 0; i < size; i++) { |
| if (! Merger.isPossiblyAssignableFrom(types.getType(i), args[i])) { |
| throw new SimException("at stack depth " + (size - 1 - i) + |
| ", expected type " + types.getType(i).toHuman() + |
| " but found " + args[i].getType().toHuman()); |
| } |
| } |
| } |
| |
| public final void popArgs(Frame frame, Type type) { |
| // Use the above method to do the actual popping... |
| popArgs(frame, 1); |
| |
| // ...and then verify the popped type. |
| if (! Merger.isPossiblyAssignableFrom(type, args[0])) { |
| throw new SimException("expected type " + type.toHuman() + |
| " but found " + args[0].getType().toHuman()); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public final void popArgs(Frame frame, Type type1, Type type2) { |
| // Use the above method to do the actual popping... |
| popArgs(frame, 2); |
| |
| // ...and then verify the popped types. |
| |
| if (! Merger.isPossiblyAssignableFrom(type1, args[0])) { |
| throw new SimException("expected type " + type1.toHuman() + |
| " but found " + args[0].getType().toHuman()); |
| } |
| |
| if (! Merger.isPossiblyAssignableFrom(type2, args[1])) { |
| throw new SimException("expected type " + type2.toHuman() + |
| " but found " + args[1].getType().toHuman()); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public final void popArgs(Frame frame, Type type1, Type type2, |
| Type type3) { |
| // Use the above method to do the actual popping... |
| popArgs(frame, 3); |
| |
| // ...and then verify the popped types. |
| |
| if (! Merger.isPossiblyAssignableFrom(type1, args[0])) { |
| throw new SimException("expected type " + type1.toHuman() + |
| " but found " + args[0].getType().toHuman()); |
| } |
| |
| if (! Merger.isPossiblyAssignableFrom(type2, args[1])) { |
| throw new SimException("expected type " + type2.toHuman() + |
| " but found " + args[1].getType().toHuman()); |
| } |
| |
| if (! Merger.isPossiblyAssignableFrom(type3, args[2])) { |
| throw new SimException("expected type " + type3.toHuman() + |
| " but found " + args[2].getType().toHuman()); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public final void localArg(Frame frame, int idx) { |
| clearArgs(); |
| args[0] = frame.getLocals().get(idx); |
| argCount = 1; |
| localIndex = idx; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void localInfo(boolean local) { |
| localInfo = local; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void auxType(Type type) { |
| auxType = type; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void auxIntArg(int value) { |
| auxInt = value; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void auxCstArg(Constant cst) { |
| if (cst == null) { |
| throw new NullPointerException("cst == null"); |
| } |
| |
| auxCst = cst; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void auxTargetArg(int target) { |
| auxTarget = target; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void auxSwitchArg(SwitchList cases) { |
| if (cases == null) { |
| throw new NullPointerException("cases == null"); |
| } |
| |
| auxCases = cases; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void auxInitValues(ArrayList<Constant> initValues) { |
| auxInitValues = initValues; |
| } |
| |
| /** {@inheritDoc} */ |
| public final void localTarget(int idx, Type type, LocalItem local) { |
| localTarget = RegisterSpec.makeLocalOptional(idx, type, local); |
| } |
| |
| /** |
| * Gets the number of primary arguments. |
| * |
| * @return {@code >= 0;} the number of primary arguments |
| */ |
| protected final int argCount() { |
| return argCount; |
| } |
| |
| /** |
| * Gets the width of the arguments (where a category-2 value counts as |
| * two). |
| * |
| * @return {@code >= 0;} the argument width |
| */ |
| protected final int argWidth() { |
| int result = 0; |
| |
| for (int i = 0; i < argCount; i++) { |
| result += args[i].getType().getCategory(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Gets the {@code n}th primary argument. |
| * |
| * @param n {@code >= 0, < argCount();} which argument |
| * @return {@code non-null;} the indicated argument |
| */ |
| protected final TypeBearer arg(int n) { |
| if (n >= argCount) { |
| throw new IllegalArgumentException("n >= argCount"); |
| } |
| |
| try { |
| return args[n]; |
| } catch (ArrayIndexOutOfBoundsException ex) { |
| // Translate the exception. |
| throw new IllegalArgumentException("n < 0"); |
| } |
| } |
| |
| /** |
| * Gets the type auxiliary argument. |
| * |
| * @return {@code null-ok;} the salient type |
| */ |
| protected final Type getAuxType() { |
| return auxType; |
| } |
| |
| /** |
| * Gets the {@code int} auxiliary argument. |
| * |
| * @return the argument value |
| */ |
| protected final int getAuxInt() { |
| return auxInt; |
| } |
| |
| /** |
| * Gets the constant auxiliary argument. |
| * |
| * @return {@code null-ok;} the argument value |
| */ |
| protected final Constant getAuxCst() { |
| return auxCst; |
| } |
| |
| /** |
| * Gets the branch target auxiliary argument. |
| * |
| * @return the argument value |
| */ |
| protected final int getAuxTarget() { |
| return auxTarget; |
| } |
| |
| /** |
| * Gets the switch cases auxiliary argument. |
| * |
| * @return {@code null-ok;} the argument value |
| */ |
| protected final SwitchList getAuxCases() { |
| return auxCases; |
| } |
| |
| /** |
| * Gets the init values auxiliary argument. |
| * |
| * @return {@code null-ok;} the argument value |
| */ |
| protected final ArrayList<Constant> getInitValues() { |
| return auxInitValues; |
| } |
| /** |
| * Gets the last local index accessed. |
| * |
| * @return {@code >= -1;} the salient local index or {@code -1} if none |
| * was set since the last time {@link #clearArgs} was called |
| */ |
| protected final int getLocalIndex() { |
| return localIndex; |
| } |
| |
| /** |
| * Gets whether the loaded local has info in the local variable table. |
| * |
| * @return {@code true} if local arg has info in the local variable table |
| */ |
| protected final boolean getLocalInfo() { |
| return localInfo; |
| } |
| |
| /** |
| * Gets the target local register spec of the current operation, if any. |
| * The local target spec is the combination of the values indicated |
| * by a previous call to {@link #localTarget} with the type of what |
| * should be the sole result set by a call to {@link #setResult} (or |
| * the combination {@link #clearResult} then {@link #addResult}. |
| * |
| * @param isMove {@code true} if the operation being performed on the |
| * local is a move. This will cause constant values to be propagated |
| * to the returned local |
| * @return {@code null-ok;} the salient register spec or {@code null} if no |
| * local target was set since the last time {@link #clearArgs} was |
| * called |
| */ |
| protected final RegisterSpec getLocalTarget(boolean isMove) { |
| if (localTarget == null) { |
| return null; |
| } |
| |
| if (resultCount != 1) { |
| throw new SimException("local target with " + |
| ((resultCount == 0) ? "no" : "multiple") + " results"); |
| } |
| |
| TypeBearer result = results[0]; |
| Type resultType = result.getType(); |
| Type localType = localTarget.getType(); |
| |
| if (resultType == localType) { |
| /* |
| * If this is to be a move operation and the result is a |
| * known value, make the returned localTarget embody that |
| * value. |
| */ |
| if (isMove) { |
| return localTarget.withType(result); |
| } else { |
| return localTarget; |
| } |
| } |
| |
| if (! Merger.isPossiblyAssignableFrom(localType, resultType)) { |
| // The result and local types are inconsistent. Complain! |
| throwLocalMismatch(resultType, localType); |
| return null; |
| } |
| |
| if (localType == Type.OBJECT) { |
| /* |
| * The result type is more specific than the local type, |
| * so use that instead. |
| */ |
| localTarget = localTarget.withType(result); |
| } |
| |
| return localTarget; |
| } |
| |
| /** |
| * Clears the results. |
| */ |
| protected final void clearResult() { |
| resultCount = 0; |
| } |
| |
| /** |
| * Sets the results list to be the given single value. |
| * |
| * <p><b>Note:</b> If there is more than one result value, the |
| * others may be added by using {@link #addResult}.</p> |
| * |
| * @param result {@code non-null;} result value |
| */ |
| protected final void setResult(TypeBearer result) { |
| if (result == null) { |
| throw new NullPointerException("result == null"); |
| } |
| |
| results[0] = result; |
| resultCount = 1; |
| } |
| |
| /** |
| * Adds an additional element to the list of results. |
| * |
| * @see #setResult |
| * |
| * @param result {@code non-null;} result value |
| */ |
| protected final void addResult(TypeBearer result) { |
| if (result == null) { |
| throw new NullPointerException("result == null"); |
| } |
| |
| results[resultCount] = result; |
| resultCount++; |
| } |
| |
| /** |
| * Gets the count of results. This throws an exception if results were |
| * never set. (Explicitly clearing the results counts as setting them.) |
| * |
| * @return {@code >= 0;} the count |
| */ |
| protected final int resultCount() { |
| if (resultCount < 0) { |
| throw new SimException("results never set"); |
| } |
| |
| return resultCount; |
| } |
| |
| /** |
| * Gets the width of the results (where a category-2 value counts as |
| * two). |
| * |
| * @return {@code >= 0;} the result width |
| */ |
| protected final int resultWidth() { |
| int width = 0; |
| |
| for (int i = 0; i < resultCount; i++) { |
| width += results[i].getType().getCategory(); |
| } |
| |
| return width; |
| } |
| |
| /** |
| * Gets the {@code n}th result value. |
| * |
| * @param n {@code >= 0, < resultCount();} which result |
| * @return {@code non-null;} the indicated result value |
| */ |
| protected final TypeBearer result(int n) { |
| if (n >= resultCount) { |
| throw new IllegalArgumentException("n >= resultCount"); |
| } |
| |
| try { |
| return results[n]; |
| } catch (ArrayIndexOutOfBoundsException ex) { |
| // Translate the exception. |
| throw new IllegalArgumentException("n < 0"); |
| } |
| } |
| |
| /** |
| * Stores the results of the latest operation into the given frame. If |
| * there is a local target (see {@link #localTarget}), then the sole |
| * result is stored to that target; otherwise any results are pushed |
| * onto the stack. |
| * |
| * @param frame {@code non-null;} frame to operate on |
| */ |
| protected final void storeResults(Frame frame) { |
| if (resultCount < 0) { |
| throw new SimException("results never set"); |
| } |
| |
| if (resultCount == 0) { |
| // Nothing to do. |
| return; |
| } |
| |
| if (localTarget != null) { |
| /* |
| * Note: getLocalTarget() doesn't necessarily return |
| * localTarget directly. |
| */ |
| frame.getLocals().set(getLocalTarget(false)); |
| } else { |
| ExecutionStack stack = frame.getStack(); |
| for (int i = 0; i < resultCount; i++) { |
| if (localInfo) { |
| stack.setLocal(); |
| } |
| stack.push(results[i]); |
| } |
| } |
| } |
| |
| /** |
| * Throws an exception that indicates a mismatch in local variable |
| * types. |
| * |
| * @param found {@code non-null;} the encountered type |
| * @param local {@code non-null;} the local variable's claimed type |
| */ |
| public static void throwLocalMismatch(TypeBearer found, |
| TypeBearer local) { |
| throw new SimException("local variable type mismatch: " + |
| "attempt to set or access a value of type " + |
| found.toHuman() + |
| " using a local variable of type " + |
| local.toHuman() + |
| ". This is symptomatic of .class transformation tools " + |
| "that ignore local variable information."); |
| } |
| } |