| /* |
| * 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 : |
| * <pre> |
| * 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. |
| * |
| * </pre> |
| * |
| */ |
| 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> T getReference(int idx, Class<T> 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> T nextReference(Class<T> 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; |
| } |
| } |
| } |