/* * Copyright (C) 2016 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 dalvik.system; import sun.invoke.util.Wrapper; import java.lang.invoke.MethodType; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Provides typed (read-only) access to method arguments and a slot to store a return value. * * Used to implement method handle transforms. See {@link java.lang.invoke.Transformers}. * * @hide */ public class EmulatedStackFrame { /** * The type of this stack frame, i.e, the types of its arguments and the type of its * return value. */ private final MethodType type; /** * All reference arguments and reference return values that belong to this argument array. * * If the return type is a reference, it will be the last element of this array. */ private final Object[] references; /** * Contains all primitive values on the stack. Primitive values always take 4 or 8 bytes of * space and all {@code short}, {@code char} and {@code boolean} arguments are promoted to ints. * * Reference values do not appear on the stack frame but they appear (in order) * in the {@code references} array. No additional slots or space for reference arguments or * return values are reserved in the stackFrame. * * By convention, if the return value is a primitive, it will occupy the last 4 or 8 bytes * of the stack frame, depending on the type. * * The size of this array is known at the time of creation of this {@code EmulatedStackFrame} * and is determined by the {@code MethodType} of the frame. * * Example : *
     *     Function : String foo(String a, String b, int c, long d) { }
     *
     *     EmulatedStackFrame :
     *     references = { a, b, [return_value] }
     *     stackFrame = { c0, c1, c2, c3, d0, d1, d2, d3, d4, d5, d6, d7 }
     *
     *     Function : int foo(String a)
     *
     *     EmulatedStackFrame :
     *     references = { a }
     *     stackFrame = { rv0, rv1, rv2, rv3 }  // rv is the return value.
     *
     * 
* */ private final byte[] stackFrame; private EmulatedStackFrame(MethodType type, Object[] references, byte[] stackFrame) { this.type = type; this.references = references; this.stackFrame = stackFrame; } /** * Returns the {@code MethodType} that the frame was created for. */ public final MethodType getMethodType() { return type; } /** * Represents a range of arguments on an {@code EmulatedStackFrame}. * * @hide */ public static final class Range { public final int referencesStart; public final int numReferences; public final int stackFrameStart; public final int numBytes; private static Range EMPTY_RANGE = new Range(0, 0, 0, 0); private Range(int referencesStart, int numReferences, int stackFrameStart, int numBytes) { this.referencesStart = referencesStart; this.numReferences = numReferences; this.stackFrameStart = stackFrameStart; this.numBytes = numBytes; } /** Creates a {@code Range} spanning all arguments. * @param frameType the type of the frame. */ public static Range all(MethodType frameType) { return of(frameType, 0, frameType.parameterCount()); } /** Creates a {@code Range} spanning specified arguments. * @param frameType the type of the frame. * @param startArg the first argument in the range to be created. * @param endArg the argument ending the range to be created. */ public static Range of(MethodType frameType, int startArg, int endArg) { if (startArg >= endArg) { return EMPTY_RANGE; } int referencesStart = 0; int numReferences = 0; int stackFrameStart = 0; int numBytes = 0; final Class[] ptypes = frameType.ptypes(); for (int i = 0; i < startArg; ++i) { Class cl = ptypes[i]; if (!cl.isPrimitive()) { referencesStart++; } else { stackFrameStart += getSize(cl); } } for (int i = startArg; i < endArg; ++i) { Class cl = ptypes[i]; if (!cl.isPrimitive()) { numReferences++; } else { numBytes += getSize(cl); } } return new Range(referencesStart, numReferences, stackFrameStart, numBytes); } /** Creates a {@code Range} covering all arguments starting from specified position. * @param frameType the type of the frame. * @param startArg the first argument in the range to be created. */ public static Range from(MethodType frameType, int startArg) { return of(frameType, startArg, frameType.parameterCount()); } } /** * Creates an emulated stack frame for a given {@code MethodType}. */ public static EmulatedStackFrame create(MethodType frameType) { int numRefs = 0; int frameSize = 0; for (Class ptype : frameType.ptypes()) { if (!ptype.isPrimitive()) { numRefs++; } else { frameSize += getSize(ptype); } } final Class rtype = frameType.rtype(); if (!rtype.isPrimitive()) { numRefs++; } else { frameSize += getSize(rtype); } return new EmulatedStackFrame(frameType, new Object[numRefs], new byte[frameSize]); } /** * Convert parameter index to index within references array. */ int getReferenceIndex(int parameterIndex) { final Class [] ptypes = type.ptypes(); int refIndex = 0; for (int i = 0; i < parameterIndex; ++i) { if (!ptypes[i].isPrimitive()) { refIndex += 1; } } return refIndex; } /** * Sets the {@code idx} to {@code reference}. Type checks are performed. */ public void setReference(int idx, Object reference) { final Class[] ptypes = type.ptypes(); if (idx < 0 || idx >= ptypes.length) { throw new IllegalArgumentException("Invalid index: " + idx); } if (reference != null && !ptypes[idx].isInstance(reference)) { throw new IllegalStateException("reference is not of type: " + type.ptypes()[idx]); } int referenceIndex = getReferenceIndex(idx); references[referenceIndex] = reference; } /** * Gets the reference at {@code idx}, checking that it's of type {@code referenceType}. */ public T getReference(int idx, Class referenceType) { if (referenceType != type.ptypes()[idx]) { throw new IllegalArgumentException("Argument: " + idx + " is of type " + type.ptypes()[idx] + " expected " + referenceType + ""); } int referenceIndex = getReferenceIndex(idx); return (T) references[referenceIndex]; } /** * Copies a specified range of arguments, given by {@code fromRange} to a specified * EmulatedStackFrame {@code other}, with references starting at {@code referencesStart} * and primitives starting at {@code primitivesStart}. */ public void copyRangeTo(EmulatedStackFrame other, Range fromRange, int referencesStart, int primitivesStart) { if (fromRange.numReferences > 0) { System.arraycopy(references, fromRange.referencesStart, other.references, referencesStart, fromRange.numReferences); } if (fromRange.numBytes > 0) { System.arraycopy(stackFrame, fromRange.stackFrameStart, other.stackFrame, primitivesStart, fromRange.numBytes); } } /** * Copies the return value from this stack frame to {@code other}. */ public void copyReturnValueTo(EmulatedStackFrame other) { final Class returnType = type.returnType(); if (!returnType.isPrimitive()) { other.references[other.references.length - 1] = references[references.length - 1]; } else if (!is64BitPrimitive(returnType)) { System.arraycopy(stackFrame, stackFrame.length - 4, other.stackFrame, other.stackFrame.length - 4, 4); } else { System.arraycopy(stackFrame, stackFrame.length - 8, other.stackFrame, other.stackFrame.length - 8, 8); } } public void setReturnValueTo(Object reference) { final Class returnType = type.returnType(); if (returnType.isPrimitive()) { throw new IllegalStateException("return type is not a reference type: " + returnType); } if (reference != null && !returnType.isInstance(reference)) { throw new IllegalArgumentException("reference is not of type " + returnType); } references[references.length - 1] = reference; } /** * Returns true iff. the input {@code type} needs 64 bits (8 bytes) of storage on an * {@code EmulatedStackFrame}. */ private static boolean is64BitPrimitive(Class type) { return type == double.class || type == long.class; } /** * Returns the size (in bytes) occupied by a given primitive type on an * {@code EmulatedStackFrame}. */ public static int getSize(Class type) { if (!type.isPrimitive()) { throw new IllegalArgumentException("type.isPrimitive() == false: " + type); } if (is64BitPrimitive(type)) { return 8; } else { return 4; } } /** * Base class for readers and writers to stack frames. * * @hide */ public static class StackFrameAccessor { /** * The current offset into the references array. */ protected int referencesOffset; /** * The index of the current argument being processed. For a function of arity N, * values [0, N) correspond to input arguments, and the special index {@code -2} * maps to the return value. All other indices are invalid. */ protected int argumentIdx; /** * Wrapper for {@code EmulatedStackFrame.this.stackFrame}. */ protected ByteBuffer frameBuf; /** * The number of arguments that this stack frame expects. */ private int numArgs; /** * The stack frame we're currently accessing. */ protected EmulatedStackFrame frame; /** * The value of {@code argumentIdx} when this accessor's cursor is pointing to the * frame's return value. */ private static final int RETURN_VALUE_IDX = -2; protected StackFrameAccessor() { referencesOffset = 0; argumentIdx = 0; frameBuf = null; numArgs = 0; } /** * Attaches this accessor to a given {@code EmulatedStackFrame} to read or write * values to it. Also resets all state associated with the current accessor. */ public StackFrameAccessor attach(EmulatedStackFrame stackFrame) { return attach(stackFrame, 0 /* argumentIdx */, 0 /* referencesOffset */, 0 /* frameOffset */); } public StackFrameAccessor attach(EmulatedStackFrame stackFrame, int argumentIdx, int referencesOffset, int frameOffset) { if (frame != stackFrame) { // Re-initialize storage if not re-attaching to the same stackFrame. frame = stackFrame; frameBuf = ByteBuffer.wrap(frame.stackFrame).order(ByteOrder.LITTLE_ENDIAN); numArgs = frame.type.ptypes().length; } frameBuf.position(frameOffset); this.referencesOffset = referencesOffset; this.argumentIdx = argumentIdx; return this; } private Class getCurrentArgumentType() { if (argumentIdx >= numArgs || argumentIdx == (RETURN_VALUE_IDX + 1)) { throw new IllegalArgumentException("Invalid argument index: " + argumentIdx); } return (argumentIdx == RETURN_VALUE_IDX) ? frame.type.rtype() : frame.type.ptypes()[argumentIdx]; } private static void checkAssignable(Class expectedType, Class actualType) { if (!expectedType.isAssignableFrom(actualType)) { throw new IllegalArgumentException("Incorrect type: " + actualType + ", expected: " + expectedType); } } protected void checkWriteType(Class type) { checkAssignable(getCurrentArgumentType(), type); } protected void checkReadType(Class expectedType) { checkAssignable(expectedType, getCurrentArgumentType()); } /** * Positions the cursor at the return value location, either in the references array * or in the stack frame array. The next put* or next* call will result in a read or * write to the return value. */ public void makeReturnValueAccessor() { Class rtype = frame.type.rtype(); argumentIdx = RETURN_VALUE_IDX; // Position the cursor appropriately. The return value is either the last element // of the references array, or the last 4 or 8 bytes of the stack frame. if (rtype.isPrimitive()) { frameBuf.position(frameBuf.capacity() - getSize(rtype)); } else { referencesOffset = frame.references.length - 1; } } public static void copyNext( StackFrameReader reader, StackFrameWriter writer, Class type) { switch (Wrapper.basicTypeChar(type)) { case 'L': writer.putNextReference(reader.nextReference(type), type); break; case 'Z': writer.putNextBoolean(reader.nextBoolean()); break; case 'B': writer.putNextByte(reader.nextByte()); break; case 'C': writer.putNextChar(reader.nextChar()); break; case 'S': writer.putNextShort(reader.nextShort()); break; case 'I': writer.putNextInt(reader.nextInt()); break; case 'J': writer.putNextLong(reader.nextLong()); break; case 'F': writer.putNextFloat(reader.nextFloat()); break; case 'D': writer.putNextDouble(reader.nextDouble()); break; } } } /** * Provides sequential write access to an emulated stack frame. Allows writes to * argument slots as well as return value slots. */ public static class StackFrameWriter extends StackFrameAccessor { public void putNextByte(byte value) { checkWriteType(byte.class); argumentIdx++; frameBuf.putInt(value); } public void putNextInt(int value) { checkWriteType(int.class); argumentIdx++; frameBuf.putInt(value); } public void putNextLong(long value) { checkWriteType(long.class); argumentIdx++; frameBuf.putLong(value); } public void putNextChar(char value) { checkWriteType(char.class); argumentIdx++; frameBuf.putInt((int) value); } public void putNextBoolean(boolean value) { checkWriteType(boolean.class); argumentIdx++; frameBuf.putInt(value ? 1 : 0); } public void putNextShort(short value) { checkWriteType(short.class); argumentIdx++; frameBuf.putInt((int) value); } public void putNextFloat(float value) { checkWriteType(float.class); argumentIdx++; frameBuf.putFloat(value); } public void putNextDouble(double value) { checkWriteType(double.class); argumentIdx++; frameBuf.putDouble(value); } public void putNextReference(Object value, Class expectedType) { checkWriteType(expectedType); argumentIdx++; frame.references[referencesOffset++] = value; } } /** * Provides sequential read access to an emulated stack frame. Allows reads to * argument slots as well as to return value slots. */ public static class StackFrameReader extends StackFrameAccessor { public byte nextByte() { checkReadType(byte.class); argumentIdx++; return (byte) frameBuf.getInt(); } public int nextInt() { checkReadType(int.class); argumentIdx++; return frameBuf.getInt(); } public long nextLong() { checkReadType(long.class); argumentIdx++; return frameBuf.getLong(); } public char nextChar() { checkReadType(char.class); argumentIdx++; return (char) frameBuf.getInt(); } public boolean nextBoolean() { checkReadType(boolean.class); argumentIdx++; return (frameBuf.getInt() != 0); } public short nextShort() { checkReadType(short.class); argumentIdx++; return (short) frameBuf.getInt(); } public float nextFloat() { checkReadType(float.class); argumentIdx++; return frameBuf.getFloat(); } public double nextDouble() { checkReadType(double.class); argumentIdx++; return frameBuf.getDouble(); } public T nextReference(Class expectedType) { checkReadType(expectedType); argumentIdx++; return (T) frame.references[referencesOffset++]; } } /** * Provides sequential and non-sequential read access to an emulated stack frame. Allows reads * to argument slots as well as to return value slots. */ public static class RandomOrderStackFrameReader extends StackFrameReader { int [] frameOffsets; int [] referencesOffsets; private void buildTables(MethodType methodType) { final Class [] ptypes = methodType.parameterArray(); frameOffsets = new int [ptypes.length]; referencesOffsets = new int [ptypes.length]; int frameOffset = 0; int referenceOffset = 0; for (int i = 0; i < ptypes.length; ++i) { frameOffsets[i] = frameOffset; referencesOffsets[i] = referenceOffset; final Class ptype = ptypes[i]; if (ptype.isPrimitive()) { frameOffset += getSize(ptype); } else { referenceOffset += 1; } } } @Override public StackFrameAccessor attach(EmulatedStackFrame stackFrame, int argumentIdx, int referencesOffset, int frameOffset) { super.attach(stackFrame, argumentIdx, referencesOffset, frameOffset); buildTables(stackFrame.getMethodType()); return this; } /** * Position to read argument at specific index. * @param argumentIndex the index of the next argument to be read. * @return this reader. */ public RandomOrderStackFrameReader moveTo(int argumentIndex) { referencesOffset = referencesOffsets[argumentIndex]; frameBuf.position(frameOffsets[argumentIndex]); argumentIdx = argumentIndex; return this; } } }