615 lines
21 KiB
Java
615 lines
21 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|