edu.umd.cs.findbugs.ba.Frame Maven / Gradle / Ivy
Show all versions of spotbugs Show documentation
/*
* Bytecode Analysis Framework
* Copyright (C) 2003,2004 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.ba;
import static edu.umd.cs.findbugs.ba.Debug.VERIFY_INTEGRITY;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import org.apache.bcel.Const;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.StackConsumer;
import edu.umd.cs.findbugs.SystemProperties;
/**
* Generic class for representing a Java stack frame as a dataflow value. A
* frame consists of "slots", which represent the local variables and values on
* the Java operand stack. Slots 0 .. getNumLocals() - 1
represent
* the local variables. Slots getNumLocals()
..
* getNumSlots() - 1
represent the Java operand stack.
*
*
* Frame is parametized by "ValueType", which is the type of value to be stored
* in the Frame's slots. This type must form a lattice, according to the
* abstract mergeValues() operation in the corresponding analysis class (which
* should be derived from FrameDataflowAnalysis). When a Frame is constructed,
* all of its slots will contain null. The analysis is responsible for
* initializing created Frames with default values at the appropriate time.
* Typically, only initEntryFact() will need to do this.
*
*
* A Frame may have the special "TOP" value. Such frames serve as the identity
* element for the meet operation operation.
*
*
* A Frame may have the special "BOTTOM" value. The result of merging any frame
* with BOTTOM is BOTTOM.
*
* @author David Hovemeyer
* @see FrameDataflowAnalysis
*/
public abstract class Frame {
// //////////////////////////////////////////////////////////////////////////////////
// Instance variables
// //////////////////////////////////////////////////////////////////////////////////
private int lastUpdateTimestamp;
/**
* Number of local variables in the method.
*/
private final int numLocals;
/**
* Array storing the values of local variables and operand stack slots.
*/
private ArrayList slotList;
/**
* Flag marking this frame as a special "TOP" value. Such Frames serve as
* the identity element when merging.
*/
private boolean isTop;
/**
* Flag marking this frame as a special "BOTTOM" value. Such Frames arise
* when merging two frames of different size.
*/
private boolean isBottom;
/**
* Default number of stack slots to preallocate space for.
*/
private static final int DEFAULT_STACK_CAPACITY = 4;
// //////////////////////////////////////////////////////////////////////////////////
// Methods
// //////////////////////////////////////////////////////////////////////////////////
/**
* Constructor. This version of the constructor is for subclasses for which
* it is always safe to call getDefaultValue(), even when the object is not
* fully initialized.
*
* @param numLocals
* number of local variable slots in the method
*/
public Frame(int numLocals) {
this.numLocals = numLocals;
this.slotList = new ArrayList<>(numLocals + DEFAULT_STACK_CAPACITY);
for (int i = 0; i < numLocals; ++i) {
slotList.add(null);
}
}
/**
* Return whether or not this object the special "TOP" value for Frames.
* Such Frames are the identity element of the meet operation.
*/
public boolean isTop() {
return isTop;
}
/**
* Make this frame the special "TOP" value. Such Frames are the identity
* element of the meet operation.
*/
public void setTop() {
isTop = true;
isBottom = false;
lastUpdateTimestamp = 0;
}
/**
* Return whether or not this object is the special "BOTTOM" value for
* Frames. Such Frames arise when merging two frames of different size.
*/
public boolean isBottom() {
return isBottom;
}
/**
* Make this Frame the special "BOTTOM" value. Such Frames arise when
* merging two frames of different size.
*/
public void setBottom() {
isBottom = true;
isTop = false;
}
/**
* Set the Frame to be valid (neither TOP nor BOTTOM).
*/
public void setValid() {
isTop = isBottom = false;
}
/**
* Is the frame valid (meaning it is not TOP or BOTTOM)?
*/
public boolean isValid() {
return !isTop() && !isBottom();
}
/**
* Push a value onto the Java operand stack.
*
* @param value
* the ValueType to push
*/
public void pushValue(ValueType value) {
if (VERIFY_INTEGRITY && value == null) {
throw new IllegalArgumentException();
}
if (!isValid()) {
throw new IllegalStateException("accessing top or bottom frame");
}
slotList.add(value);
}
/**
* Pop a value off of the Java operand stack.
*
* @return the value that was popped
* @throws DataflowAnalysisException
* if the Java operand stack is empty
*/
public ValueType popValue() throws DataflowAnalysisException {
if (!isValid()) {
throw new DataflowAnalysisException("accessing top or bottom frame");
}
if (slotList.size() == numLocals) {
throw new DataflowAnalysisException("operand stack empty");
}
return slotList.remove(slotList.size() - 1);
}
/**
* Get the value on the top of the Java operand stack.
*
* @throws DataflowAnalysisException
* if the Java operand stack is empty
*/
public ValueType getTopValue() throws DataflowAnalysisException {
if (!isValid()) {
throw new DataflowAnalysisException("accessing top or bottom frame");
}
assert slotList.size() >= numLocals;
if (slotList.size() == numLocals) {
throw new DataflowAnalysisException("operand stack is empty");
}
return slotList.get(slotList.size() - 1);
}
/**
* Get the values on the top of the Java operand stack. The top stack item
* is placed at the end of the array, so that to restore the values to the
* stack, you would push them in the order they appear in the array.
*/
public void getTopStackWords(ValueType[] valueList) throws DataflowAnalysisException {
int stackDepth = getStackDepth();
if (valueList.length > stackDepth) {
throw new DataflowAnalysisException("not enough values on stack");
}
int numSlots = slotList.size();
for (int i = numSlots - valueList.length, j = 0; i < numSlots; ++i, ++j) {
valueList[j] = slotList.get(i);
}
}
/**
* Get a value on the operand stack.
*
* @param loc
* the stack location, counting downwards from the top (location
* 0)
*/
public ValueType getStackValue(int loc) throws DataflowAnalysisException {
if (!isValid()) {
throw new DataflowAnalysisException("Accessing TOP or BOTTOM frame!");
}
int stackDepth = getStackDepth();
if (loc >= stackDepth) {
throw new DataflowAnalysisException("not enough values on stack: access=" + loc + ", avail=" + stackDepth);
}
if (loc < 0) {
throw new DataflowAnalysisException("can't get position " + loc + " of stack");
}
int pos = slotList.size() - (loc + 1);
return slotList.get(pos);
}
/**
* Get a the location in the frame of a value on the operand stack.
*
* @param loc
* the stack location, counting downwards from the top (location
* 0)
*/
public int getStackLocation(int loc) throws DataflowAnalysisException {
int stackDepth = getStackDepth();
if (loc >= stackDepth) {
throw new DataflowAnalysisException("not enough values on stack: access=" + loc + ", avail=" + stackDepth);
}
return slotList.size() - (loc + 1);
}
/**
* Get the value corresponding to the object instance used in the given
* instruction. This relies on the observation that in instructions which
* use an object instance (such as getfield, invokevirtual, etc.), the
* object instance is the first operand used by the instruction.
*
* @param ins
* the instruction
* @param cpg
* the ConstantPoolGen for the method
*/
public ValueType getInstance(Instruction ins, ConstantPoolGen cpg) throws DataflowAnalysisException {
return getStackValue(getInstanceStackLocation(ins, cpg));
}
/**
* Get the stack location (counting down from top of stack, starting at 0)
* containing the object instance referred to by given instruction. This
* relies on the observation that in instructions which use an object
* instance (such as getfield, invokevirtual, etc.), the object instance is
* the first operand used by the instruction.
*
*
* The value returned may be passed to getStackValue(int).
*
*
* @param ins
* the Instruction
* @param cpg
* the ConstantPoolGen for the method
* @return stack location (counting down from top of stack, starting at 0)
* containing the object instance
* @throws DataflowAnalysisException
*/
public int getInstanceStackLocation(Instruction ins, ConstantPoolGen cpg) throws DataflowAnalysisException {
int numConsumed = ins.consumeStack(cpg);
if (numConsumed == Const.UNPREDICTABLE) {
throw new DataflowAnalysisException("Unpredictable stack consumption in " + ins);
}
return numConsumed - 1;
}
/**
* Get the slot the object instance referred to by given instruction is
* located in.
*
* @param ins
* the Instruction
* @param cpg
* the ConstantPoolGen for the method
* @return stack slot the object instance is in
* @throws DataflowAnalysisException
*/
public int getInstanceSlot(Instruction ins, ConstantPoolGen cpg) throws DataflowAnalysisException {
if (!isValid()) {
throw new DataflowAnalysisException("Accessing invalid frame at " + ins);
}
int numConsumed = ins.consumeStack(cpg);
if (numConsumed == Const.UNPREDICTABLE) {
throw new DataflowAnalysisException("Unpredictable stack consumption in " + ins);
}
if (numConsumed > getStackDepth()) {
throw new DataflowAnalysisException("Stack underflow " + ins);
}
return getNumSlots() - numConsumed;
}
/**
* Get the number of arguments passed to given method invocation.
*
* @param ins
* the method invocation instruction
* @param cpg
* the ConstantPoolGen for the class containing the method
* @return number of arguments; note that this excludes the object instance
* for instance methods
*/
public int getNumArguments(InvokeInstruction ins, ConstantPoolGen cpg) {
SignatureParser parser = new SignatureParser(ins.getSignature(cpg));
return parser.getNumParameters();
}
/**
* Get the number of arguments passed to given method invocation, including
* the object instance if the call is to an instance method.
*
* @param ins
* the method invocation instruction
* @param cpg
* the ConstantPoolGen for the class containing the method
* @return number of arguments, including object instance if appropriate
* @throws DataflowAnalysisException
*/
public int getNumArgumentsIncludingObjectInstance(InvokeInstruction ins, ConstantPoolGen cpg)
throws DataflowAnalysisException {
int numConsumed = ins.consumeStack(cpg);
if (numConsumed == Const.UNPREDICTABLE) {
throw new DataflowAnalysisException("Unpredictable stack consumption in " + ins);
}
return numConsumed;
}
/**
* Get the ith argument passed to given method invocation.
*
* @param ins
* the method invocation instruction
* @param cpg
* the ConstantPoolGen for the class containing the method
* @param i
* index of the argument; 0 for the first argument, etc.
* @param numArguments
* total number of arguments to the method
* @return the ith argument
* @throws DataflowAnalysisException
*/
@Deprecated
public ValueType getArgument(InvokeInstruction ins, ConstantPoolGen cpg, int i, int numArguments)
throws DataflowAnalysisException {
SignatureParser sigParser = new SignatureParser(ins.getSignature(cpg));
return getArgument(ins, cpg, i, sigParser);
}
/**
* Get the ith argument passed to given method invocation.
*
* @param ins
* the method invocation instruction
* @param cpg
* the ConstantPoolGen for the class containing the method
* @param i
* index of the argument; 0 for the first argument, etc.
* @return the ith argument
* @throws DataflowAnalysisException
*/
public ValueType getArgument(InvokeInstruction ins, ConstantPoolGen cpg, int i, SignatureParser sigParser)
throws DataflowAnalysisException {
if (i >= sigParser.getNumParameters()) {
throw new IllegalArgumentException("requesting parameter # " + i + " of " + sigParser);
}
return getStackValue(sigParser.getSlotsFromTopOfStackForParameter(i));
}
/**
* Get the stack slot that will contain given method argument. Assumes that
* this frame is at the location (just before) a method invocation
* instruction.
*
* @param i
* the argument index: 0 for first arg, etc.
* @param numArguments
* total number of arguments to the called method
* @return slot containing the argument value
*/
public int getArgumentSlot(int i, int numArguments) {
if (i >= numArguments) {
throw new IllegalArgumentException();
}
return (slotList.size() - numArguments) + i;
}
/**
* Get the ith operand used by given instruction.
*
* @param ins
* the instruction, which must be a StackConsumer
* @param cpg
* the ConstantPoolGen
* @param i
* index of operand to get: 0 for the first operand, etc.
* @return the ith operand used by the given instruction
* @throws DataflowAnalysisException
*/
public ValueType getOperand(StackConsumer ins, ConstantPoolGen cpg, int i) throws DataflowAnalysisException {
int numOperands = ins.consumeStack(cpg);
if (numOperands == Const.UNPREDICTABLE) {
throw new DataflowAnalysisException("Unpredictable stack consumption in " + ins);
}
return getStackValue((numOperands - 1) - i);
}
/**
* Get set of arguments passed to a method invocation which match given
* predicate.
*
* @param invokeInstruction
* the InvokeInstruction
* @param cpg
* the ConstantPoolGen
* @param chooser
* predicate to choose which argument values should be in the
* returned set
* @return BitSet specifying which arguments match the predicate, indexed by
* argument number (starting from 0)
* @throws DataflowAnalysisException
*/
public BitSet getArgumentSet(InvokeInstruction invokeInstruction, ConstantPoolGen cpg, DataflowValueChooser chooser)
throws DataflowAnalysisException {
BitSet chosenArgSet = new BitSet();
SignatureParser sigParser = new SignatureParser(invokeInstruction.getSignature(cpg));
for (int i = 0; i < sigParser.getNumParameters(); ++i) {
ValueType value = getArgument(invokeInstruction, cpg, i, sigParser);
if (chooser.choose(value)) {
chosenArgSet.set(i);
}
}
return chosenArgSet;
}
/**
* Clear the Java operand stack. Only local variable slots will remain in
* the frame.
*/
public void clearStack() {
if (!isValid()) {
throw new IllegalStateException("accessing top or bottom frame");
}
assert slotList.size() >= numLocals;
if (slotList.size() > numLocals) {
slotList.subList(numLocals, slotList.size()).clear();
}
}
/**
* Get the depth of the Java operand stack.
*/
public int getStackDepth() {
return slotList.size() - numLocals;
}
/**
* Get the number of locals.
*/
public int getNumLocals() {
return numLocals;
}
/**
* Get the number of slots (locals plus stack values).
*/
public int getNumSlots() {
return slotList.size();
}
public boolean contains(ValueType value) {
if (!isValid()) {
throw new IllegalStateException("accessing top or bottom frame");
}
for (ValueType v : slotList) {
if (v.equals(value)) {
return true;
}
}
return false;
}
/**
* Get the value at the nth slot.
*
* @param n
* the slot to get the value of
* @return the value in the slot
*/
public ValueType getValue(int n) {
if (!isValid()) {
throw new IllegalStateException("accessing top or bottom frame");
}
return slotList.get(n);
}
/**
* Set the value at the nth slot.
*
* @param n
* the slot in which to set a new value
* @param value
* the value to set
*/
public void setValue(int n, ValueType value) {
if (VERIFY_INTEGRITY && value == null) {
throw new IllegalArgumentException();
}
if (!isValid()) {
throw new IllegalStateException("accessing top or bottom frame");
}
slotList.set(n, value);
}
/**
* Return true if this stack frame is the same as the one given as a
* parameter.
*
* @param other
* the other Frame
* @return true if the frames are the same, false otherwise
*/
public boolean sameAs(Frame other) {
if (isTop != other.isTop) {
return false;
}
if (isTop && other.isTop) {
return true;
}
if (isBottom != other.isBottom) {
return false;
}
if (isBottom && other.isBottom) {
return true;
}
if (getNumSlots() != other.getNumSlots()) {
return false;
}
for (int i = 0; i < getNumSlots(); ++i) {
if (!getValue(i).equals(other.getValue(i))) {
return false;
}
}
return true;
}
/**
* Make this Frame exactly the same as the one given as a parameter.
*
* @param other
* the Frame to make this object the same as
*/
public void copyFrom(Frame other) {
lastUpdateTimestamp = other.lastUpdateTimestamp;
slotList = new ArrayList<>(other.slotList);
isTop = other.isTop;
isBottom = other.isBottom;
}
private static final boolean STACK_ONLY = SystemProperties.getBoolean("dataflow.stackonly");
/**
* Convert to string.
*/
@Override
public String toString() {
if (isTop()) {
return "[TOP]";
}
if (isBottom()) {
return "[BOTTOM]";
}
StringBuilder buf = new StringBuilder();
buf.append('[');
int numSlots = getNumSlots();
int start = STACK_ONLY ? getNumLocals() : 0;
for (int i = start; i < numSlots; ++i) {
if (!STACK_ONLY && i == getNumLocals()) {
// Use a "|" character to visually separate locals from
// the operand stack.
int last = buf.length() - 1;
if (last >= 0 && buf.charAt(last) == ',') {
buf.deleteCharAt(last);
}
buf.append('|');
}
String value = valueToString(getValue(i));
if (i == numSlots - 1 && value.endsWith(",")) {
value = value.substring(0, value.length() - 1);
}
buf.append(value);
// buf.append(' ');
}
buf.append(']');
return buf.toString();
}
/**
* Subclasses may override this if they want to do something special to
* convert Value objects to Strings. By default, we just call toString() on
* the values.
*/
protected String valueToString(ValueType value) {
if (value == null) {
return "null";
}
return value.toString();
}
/**
* @return an unmodifiable Collection of the local variable and operand
* stack slots
*/
public Collection allSlots() {
if (slotList == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(slotList);
}
/**
* @param lastUpdateTimestamp
* The lastUpdateTimestamp to set.
*/
public void setLastUpdateTimestamp(int lastUpdateTimestamp) {
this.lastUpdateTimestamp = lastUpdateTimestamp;
}
/**
* @return Returns the lastUpdateTimestamp.
*/
public int getLastUpdateTimestamp() {
return lastUpdateTimestamp;
}
}