/*
* FindBugs - Find bugs in Java programs
* 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.detect;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import edu.umd.cs.findbugs.ResourceCreationPoint;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.RepositoryLookupFailureCallback;
import edu.umd.cs.findbugs.ba.ResourceValue;
import edu.umd.cs.findbugs.ba.ResourceValueFrame;
/**
* A Stream object marks the location in the code where a stream is created. It
* also is responsible for determining some aspects of how the stream state is
* tracked by the ResourceValueAnalysis, such as when the stream is opened or
* closed, and whether implicit exception edges are significant.
* <p/>
* <p>
* TODO: change streamClass and streamBase to ObjectType
* <p/>
* <p>
* TODO: isStreamOpen() and isStreamClose() should probably be abstract, so we
* can customize how they work for different kinds of streams
*/
public class Stream extends ResourceCreationPoint implements Comparable<Stream> {
private String streamBase;
private boolean isUninteresting;
private boolean isOpenOnCreation;
private Location openLocation;
private boolean ignoreImplicitExceptions;
private String bugType;
private int instanceParam;
private boolean isClosed;
@Override
public String toString() {
return streamBase + ":" + openLocation;
}
/**
* Constructor. By default, Stream objects are marked as uninteresting.
* setInteresting("BUG_TYPE") must be called explicitly to mark the Stream
* as interesting.
*
* @param location
* where the stream is created
* @param streamClass
* type of Stream
* @param streamBase
* highest class in the class hierarchy through which stream's
* close() method could be called
*/
public Stream(Location location, String streamClass, String streamBase) {
super(location, streamClass);
this.streamBase = streamBase;
isUninteresting = true;
instanceParam = -1;
}
/**
* Mark this Stream as interesting.
*
* @param bugType
* the bug type that should be reported if the stream is not
* closed on all paths out of the method
*/
public Stream setInteresting(String bugType) {
this.isUninteresting = false;
this.bugType = bugType;
return this;
}
/**
* Mark whether or not implicit exception edges should be ignored by
* ResourceValueAnalysis when determining whether or not stream is closed on
* all paths out of method.
*/
public Stream setIgnoreImplicitExceptions(boolean enable) {
ignoreImplicitExceptions = enable;
return this;
}
/**
* Mark whether or not Stream is open as soon as it is created, or whether a
* later method or constructor must explicitly open it.
*/
public Stream setIsOpenOnCreation(boolean enable) {
isOpenOnCreation = enable;
return this;
}
/**
* Set the number of the parameter which passes the stream instance.
*
* @param instanceParam
* number of the parameter passing the stream instance
*/
public void setInstanceParam(int instanceParam) {
this.instanceParam = instanceParam;
}
/**
* Set this Stream has having been closed on all paths out of the method.
*/
public void setClosed() {
isClosed = true;
}
public String getStreamBase() {
return streamBase;
}
public boolean isUninteresting() {
return isUninteresting;
}
public boolean isOpenOnCreation() {
return isOpenOnCreation;
}
public void setOpenLocation(Location openLocation) {
this.openLocation = openLocation;
}
public Location getOpenLocation() {
return openLocation;
}
public boolean ignoreImplicitExceptions() {
return ignoreImplicitExceptions;
}
public int getInstanceParam() {
return instanceParam;
}
public String getBugType() {
return bugType;
}
/**
* Return whether or not the Stream is closed on all paths out of the
* method.
*/
public boolean isClosed() {
return isClosed;
}
public boolean isStreamOpen(BasicBlock basicBlock, InstructionHandle handle, ConstantPoolGen cpg, ResourceValueFrame frame) {
if (isOpenOnCreation)
return false;
Instruction ins = handle.getInstruction();
if (!(ins instanceof INVOKESPECIAL))
return false;
// Does this instruction open the stream?
INVOKESPECIAL inv = (INVOKESPECIAL) ins;
return frame.isValid() && getInstanceValue(frame, inv, cpg).isInstance()
&& matchMethod(inv, cpg, this.getResourceClass(), "<init>");
}
public static boolean mightCloseStream(BasicBlock basicBlock, InstructionHandle handle, ConstantPoolGen cpg) {
Instruction ins = handle.getInstruction();
if ((ins instanceof INVOKEVIRTUAL) || (ins instanceof INVOKEINTERFACE)) {
// Does this instruction close the stream?
InvokeInstruction inv = (InvokeInstruction) ins;
// It's a close if the invoked class is any subtype of the stream
// base class.
// (Basically, we may not see the exact original stream class,
// even though it's the same instance.)
return inv.getName(cpg).equals("close") && inv.getSignature(cpg).equals("()V");
}
return false;
}
public boolean isStreamClose(BasicBlock basicBlock, InstructionHandle handle, ConstantPoolGen cpg, ResourceValueFrame frame,
RepositoryLookupFailureCallback lookupFailureCallback) {
if (!mightCloseStream(basicBlock, handle, cpg))
return false;
Instruction ins = handle.getInstruction();
if ((ins instanceof INVOKEVIRTUAL) || (ins instanceof INVOKEINTERFACE)) {
// Does this instruction close the stream?
InvokeInstruction inv = (InvokeInstruction) ins;
if (!frame.isValid() || !getInstanceValue(frame, inv, cpg).isInstance())
return false;
// It's a close if the invoked class is any subtype of the stream
// base class.
// (Basically, we may not see the exact original stream class,
// even though it's the same instance.)
try {
return Hierarchy.isSubtype(inv.getClassName(cpg), streamBase);
} catch (ClassNotFoundException e) {
lookupFailureCallback.reportMissingClass(e);
return false;
}
}
return false;
}
private ResourceValue getInstanceValue(ResourceValueFrame frame, InvokeInstruction inv, ConstantPoolGen cpg) {
int numConsumed = inv.consumeStack(cpg);
if (numConsumed == Constants.UNPREDICTABLE)
throw new IllegalStateException();
return frame.getValue(frame.getNumSlots() - numConsumed);
}
private boolean matchMethod(InvokeInstruction inv, ConstantPoolGen cpg, String className, String methodName) {
return inv.getClassName(cpg).equals(className) && inv.getName(cpg).equals(methodName);
}
@Override
public int hashCode() {
return getLocation().hashCode() + 3 * streamBase.hashCode() + 7 * getResourceClass().hashCode() + 11 * instanceParam;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Stream))
return false;
Stream other = (Stream) o;
if (!getLocation().equals(other.getLocation()))
return false;
if (!streamBase.equals(other.streamBase))
return false;
if (!getResourceClass().equals(other.getResourceClass()))
return false;
if (instanceParam != other.instanceParam)
return false;
return true;
}
public int compareTo(Stream other) {
int cmp;
// The main idea in comparing streams is that
// if they can't be differentiated by location
// and base/stream class, then we should try
// instanceParam. This allows streams passed in
// different parameters to be distinguished.
cmp = getLocation().compareTo(other.getLocation());
if (cmp != 0)
return cmp;
cmp = streamBase.compareTo(other.streamBase);
if (cmp != 0)
return cmp;
cmp = getResourceClass().compareTo(other.getResourceClass());
if (cmp != 0)
return cmp;
cmp = instanceParam - other.instanceParam;
if (cmp != 0)
return cmp;
return 0;
}
}
// vim:ts=3