/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.verifier;
import java.util.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.classfile.*;
import com.sun.max.vm.classfile.stackmap.*;
import com.sun.max.vm.type.*;
import com.sun.max.vm.verifier.types.*;
/**
* A frame represents the type state of the operand stack and local variables during the bytecode abstract
* interpretation performed by the {@linkplain TypeCheckingVerifier type checking verifier}.
*/
public class Frame implements FrameModel {
final VerificationType[] locals;
final MethodVerifier methodVerifier;
final VerificationType[] stack;
int stackSize;
int activeLocals;
/**
* Creates the initial stack and local variables frame for a method.
*/
public Frame(MethodActor classMethodActor, MethodVerifier methodVerifier) {
this.methodVerifier = methodVerifier;
final CodeAttribute codeAttribute = methodVerifier.codeAttribute();
final int maxLocals = codeAttribute.maxLocals;
final int maxStack = codeAttribute.maxStack;
this.locals = new VerificationType[maxLocals];
this.stack = new VerificationType[maxStack];
initializeEntryFrame(classMethodActor);
}
/**
* Initializes the state of the stack and locals from the signature of a given method.
*/
protected void initializeEntryFrame(MethodActor classMethodActor) {
final ClassActor classActor = classMethodActor.holder();
// this reference
if (classMethodActor.isStatic()) {
if (classMethodActor.isInstanceInitializer()) {
verifyError("Can't have a static <init> method");
}
} else {
if (!classMethodActor.isInstanceInitializer()) {
store(methodVerifier.getObjectType(classActor.typeDescriptor), 0);
} else {
if (classActor.equals(ClassRegistry.OBJECT)) {
store(VerificationType.OBJECT, 0);
} else {
store(VerificationType.UNINITIALIZED_THIS, 0);
}
}
}
// parameters
final SignatureDescriptor signature = classMethodActor.descriptor();
for (int i = 0; i < signature.numberOfParameters(); i++) {
final TypeDescriptor parameterType = signature.parameterDescriptorAt(i);
final VerificationType type = methodVerifier.getVerificationType(parameterType);
store(type, activeLocals);
}
for (int i = activeLocals; i < locals.length; i++) {
locals[i] = VerificationType.TOP;
}
}
Frame(Frame from) {
methodVerifier = from.methodVerifier;
locals = from.locals.clone();
stack = from.stack.clone();
activeLocals = from.activeLocals;
stackSize = from.stackSize;
}
/**
* Creates a frame that has the same state as this frame.
*/
public Frame copy() {
return new Frame(this);
}
public void verifyError(String errorMessage) {
methodVerifier.verifyError(errorMessage);
}
public VerifyError fatalVerifyError(String errorMessage) {
return methodVerifier.fatalVerifyError(errorMessage);
}
public void verifyIsAssignable(VerificationType fromType, VerificationType toType, String errorMessage) {
methodVerifier.verifyIsAssignable(fromType, toType, errorMessage);
}
/**
* Loads a value from a local variable.
*
* @param expectedType the type of the local variable at {@code index} must be assignable to this type
* @param index an index into the local variables array
* @return the type of the local variable
* @throws VerifyError if {@code index} is out of the bounds of the local variable array, denotes an undefined local
* variable or denotes a local variable of a type not assignable to {@code expectedType}
*/
public VerificationType load(VerificationType expectedType, int index) {
try {
if (index < activeLocals) {
final VerificationType type = locals[index];
verifyIsAssignable(type, expectedType, "Invalid load of local variable");
if (type.isCategory2()) {
verifyIsAssignable(locals[index + 1], type.secondWordType(), "Invalid load of two word value");
}
return type;
}
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
// index < 0
}
throw fatalVerifyError("Load from invalid local variable index " + index);
}
/**
* Stores a value to a local variable.
*
* @param type the type of the value being stored to the local variable at {@code index}
* @param index an index into the local variables array
* @throws VerifyError if {@code index} is out of the bounds of the local variable array
*/
public void store(VerificationType type, int index) {
try {
locals[index] = type;
if (type.isCategory2()) {
locals[index + 1] = type.secondWordType();
activeLocals = Math.max(activeLocals, index + 2);
} else {
activeLocals = Math.max(activeLocals, index + 1);
}
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
// index < 0 || index >= _locals.length
verifyError("Store to invalid local variable index " + index);
}
}
/**
* Adjusts the number of defined locals down by a given amount.
*
* @param numberOfLocals the number local variables whose definitions are to be killed
* @throws VerifyError if {@code numberOfLocals > activeLocals()}
*/
public void chopLocals(int numberOfLocals) {
try {
for (int i = 0; i < numberOfLocals; i++) {
final VerificationType local = locals[activeLocals - 1];
if (local.isSecondWordType()) {
locals[--activeLocals] = VerificationType.TOP;
}
locals[--activeLocals] = VerificationType.TOP;
}
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
verifyError("Chopping more locals than currently active");
}
}
/**
* Gets the maximum number of locals that have live values. That is, {@code activeLocals() - 1} is the highest index
* of a local variable in this frame whose value is not {@linkplain VerificationType#TOP}.
*/
public int activeLocals() {
return activeLocals;
}
/**
* Pushes a value to the stack.
*
* @param type the type of the value being pushed
* @throws VerifyError if the stack overflows
*/
public void push(VerificationType type) {
assert !type.isSecondWordType();
try {
stack[stackSize++] = type;
if (type.isCategory2()) {
stack[stackSize++] = type.secondWordType();
}
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
verifyError("Stack overflow");
}
}
/**
* Pops a value from the top of the stack.
*
* @param expectedType the type of the value on the top of the stack must be assignable to this type
* @return the type of the value popped from the stack
* @throws VerifyError if the stack underflows or if the value on the top of the stack is not assignable to {@code
* expectedType}
*/
public VerificationType pop(VerificationType expectedType) {
try {
final VerificationType type;
if (expectedType.isCategory2()) {
type = stack[stackSize - 2];
verifyIsAssignable(stack[stackSize - 1], type.secondWordType(), "Invalid pop of a two word value from the stack");
stackSize -= 2;
} else {
type = stack[--stackSize];
}
verifyIsAssignable(type, expectedType, "Invalid pop of a value from the stack");
return type;
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
throw fatalVerifyError("Stack underflow");
}
}
/**
* Gets the value on the top of the stack without adjusting the size of the stack.
*
* @throws VerifyError if the stack is emtpy
*/
public VerificationType top() {
try {
final VerificationType top = stack[stackSize - 1];
if (!top.isSecondWordType()) {
return top;
}
assert stack[stackSize - 2].secondWordType() == top;
return stack[stackSize - 2];
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
throw fatalVerifyError("Stack underflow");
}
}
public boolean isTypeOnStack(VerificationType type) {
for (int i = 0; i < stackSize; i++) {
if (stack[i].equals(type)) {
return true;
}
}
return false;
}
public void clearLocals() {
for (int i = 0; i < locals.length; i++) {
locals[i] = VerificationType.TOP;
}
activeLocals = 0;
}
public void replaceLocals(VerificationType oldType, VerificationType newType) {
for (int i = 0; i < activeLocals; i++) {
if (locals[i].equals(oldType)) {
locals[i] = newType;
}
}
}
public void replaceStack(VerificationType oldType, VerificationType newType) {
for (int i = 0; i < stackSize; i++) {
if (stack[i].equals(oldType)) {
stack[i] = newType;
}
}
}
public void clearStack() {
stackSize = 0;
}
public void clear() {
clearLocals();
clearStack();
}
/**
* Merges the state of a given frame into this frame.
*
* @param fromFrame the frame to merge from
* @param thisBCI the BCI of this frame
* @param catchTypeIndex if -1, then this value is ignored and the operand stack state of {@code fromFrame} is
* merged into the operand stack state of this frame. Otherwise this value is a constant pool index of
* the exception type caught by the exception handler whose entry state is represented by this frame.
*/
public void mergeFrom(Frame fromFrame, int thisBCI, int catchTypeIndex) {
if (catchTypeIndex == -1) {
verifyStackIsAssignableFrom(fromFrame, thisBCI);
}
verifyLocalsAreAssignableFrom(fromFrame, thisBCI);
}
static String inconsistentFramesMessageSuffix(Frame targetFrame, Frame derivedFrame) {
final String nl = System.getProperty("line.separator", "\n");
return nl + "Derived frame:" + nl + derivedFrame + nl + "Recorded frame:" + nl + targetFrame;
}
/**
* Verifies that this frame's operand stack height is the same as a given frame's operand stack height and that each
* slot in this frame's operand stack {@linkplain VerificationType#isAssignableFrom(VerificationType) is assignable
* from} the same slot in the given frame.
*
* @param fromFrame a frame that must be assignable to this frame
* @param thisBCI the BCI of this frame
*/
public final void verifyStackIsAssignableFrom(Frame fromFrame, int thisBCI) {
if (stackSize != fromFrame.stackSize) {
verifyError("Inconsistent stackmap frame for BCI " + thisBCI + " (stack sizes differ)" +
inconsistentFramesMessageSuffix(fromFrame, this));
}
for (int i = 0; i < stackSize; i++) {
if (!stack[i].isAssignableFrom(fromFrame.stack[i])) {
if (!VerificationType.isTypeIncompatibilityBetweenPointerAndAccessor(fromFrame.stack[i], stack[i])) {
verifyError("Stack slot " + i + " is incompatible with stackmap frame for BCI " + thisBCI +
inconsistentFramesMessageSuffix(this, fromFrame));
}
}
}
}
/**
* Verifies that each local variable in this frame {@linkplain VerificationType#isAssignableFrom(VerificationType)
* is assignable from} the same variable in a given frame.
*
* @param fromFrame a frame that must be assignable to this frame
* @param thisBCI the BCI of this frame
*/
public final void verifyLocalsAreAssignableFrom(Frame fromFrame, int thisBCI) {
if (fromFrame.activeLocals < activeLocals) {
verifyError("Inconsistent stackmap frame for BCI " + thisBCI + " (less live locals than implied by stackmap frame)" +
inconsistentFramesMessageSuffix(fromFrame, this));
}
for (int i = 0; i < fromFrame.activeLocals; i++) {
if (!locals[i].isAssignableFrom(fromFrame.locals[i])) {
if (!VerificationType.isTypeIncompatibilityBetweenPointerAndAccessor(fromFrame.locals[i], locals[i])) {
verifyError("Local variable " + i + " is incompatible with stackmap frame for BCI " + thisBCI +
inconsistentFramesMessageSuffix(this, fromFrame));
}
}
}
}
public void reset(Frame fromFrame) {
resetStack(fromFrame);
resetLocals(fromFrame);
}
/**
* Resets the stack state in this frame to be the same as a given frame.
*/
public void resetStack(Frame fromFrame) {
stackSize = fromFrame.stackSize;
System.arraycopy(fromFrame.stack, 0, stack, 0, stackSize);
for (int i = stackSize; i != stack.length; ++i) {
stack[i] = VerificationType.TOP;
}
}
/**
* Resets the local variable state in this frame to be the same as a given frame.
*/
public void resetLocals(Frame fromFrame) {
activeLocals = fromFrame.activeLocals;
System.arraycopy(fromFrame.locals, 0, locals, 0, activeLocals);
for (int i = activeLocals; i != locals.length; ++i) {
locals[i] = VerificationType.TOP;
}
}
/**
* Gets a copy of the stack state in this frame.
*
* @return an array of the types on the stack. The length of this array gives depth of the stack in this frame.
*/
public VerificationType[] stack() {
if (stackSize == 0) {
return VerificationType.NO_TYPES;
}
return Arrays.copyOf(stack, stackSize);
}
/**
* Gets a copy of the local variable state in this frame.
*
* @return an array of the types in the local variables. The length of this array gives {@linkplain #activeLocals()
* the maximum number of locals that have live values}.
*/
public VerificationType[] locals() {
if (activeLocals == 0) {
return VerificationType.NO_TYPES;
}
return Arrays.copyOf(locals, activeLocals);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
if (stackSize > 0) {
String prefix = "stack[";
for (int i = 0; i != stackSize; ++i) {
sb.append(prefix).append(i).append("] = ").append(stack[i]).append('\n');
prefix = " [";
}
}
if (activeLocals > 0) {
String prefix = "local[";
for (int i = 0; i != activeLocals; ++i) {
sb.append(prefix).append(i).append("] = ").append(locals[i]).append('\n');
prefix = " [";
}
}
// Remove last newline
final int length = sb.length();
if (length != 0) {
assert sb.charAt(length - 1) == '\n';
sb.deleteCharAt(length - 1);
}
return sb.toString();
}
}