blob: 4fa76772b90b29e0c2be3e3c4dd73fe87ddafcf3 [file] [log] [blame]
/*
* 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;
}
}
}