001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2003,2004 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.detect;
021:
022: import org.apache.bcel.Constants;
023: import org.apache.bcel.generic.ConstantPoolGen;
024: import org.apache.bcel.generic.INVOKEINTERFACE;
025: import org.apache.bcel.generic.INVOKESPECIAL;
026: import org.apache.bcel.generic.INVOKEVIRTUAL;
027: import org.apache.bcel.generic.Instruction;
028: import org.apache.bcel.generic.InstructionHandle;
029: import org.apache.bcel.generic.InvokeInstruction;
030:
031: import edu.umd.cs.findbugs.ResourceCreationPoint;
032: import edu.umd.cs.findbugs.ba.BasicBlock;
033: import edu.umd.cs.findbugs.ba.Hierarchy;
034: import edu.umd.cs.findbugs.ba.Location;
035: import edu.umd.cs.findbugs.ba.RepositoryLookupFailureCallback;
036: import edu.umd.cs.findbugs.ba.ResourceValue;
037: import edu.umd.cs.findbugs.ba.ResourceValueFrame;
038:
039: /**
040: * A Stream object marks the location in the code where a
041: * stream is created. It also is responsible for determining
042: * some aspects of how the stream state is tracked
043: * by the ResourceValueAnalysis, such as when the stream
044: * is opened or closed, and whether implicit exception
045: * edges are significant.
046: * <p/>
047: * <p> TODO: change streamClass and streamBase to ObjectType
048: * <p/>
049: * <p> TODO: isStreamOpen() and isStreamClose() should
050: * probably be abstract, so we can customize how they work
051: * for different kinds of streams
052: */
053: public class Stream extends ResourceCreationPoint implements
054: Comparable<Stream> {
055: private String streamBase;
056: private boolean isUninteresting;
057: private boolean isOpenOnCreation;
058: private Location openLocation;
059: private boolean ignoreImplicitExceptions;
060: private String bugType;
061: private int instanceParam;
062: private boolean isClosed;
063:
064: @Override
065: public String toString() {
066: return streamBase + ":" + openLocation;
067: }
068:
069: /**
070: * Constructor.
071: * By default, Stream objects are marked as uninteresting.
072: * setInteresting("BUG_TYPE") must be called explicitly to mark
073: * the Stream as interesting.
074: *
075: * @param location where the stream is created
076: * @param streamClass type of Stream
077: * @param streamBase highest class in the class hierarchy through which
078: * stream's close() method could be called
079: */
080: public Stream(Location location, String streamClass,
081: String streamBase) {
082: super (location, streamClass);
083: this .streamBase = streamBase;
084: isUninteresting = true;
085: instanceParam = -1;
086: }
087:
088: /**
089: * Mark this Stream as interesting.
090: *
091: * @param bugType the bug type that should be reported if
092: * the stream is not closed on all paths out of the method
093: */
094: public Stream setInteresting(String bugType) {
095: this .isUninteresting = false;
096: this .bugType = bugType;
097: return this ;
098: }
099:
100: /**
101: * Mark whether or not implicit exception edges should be
102: * ignored by ResourceValueAnalysis when determining whether or
103: * not stream is closed on all paths out of method.
104: */
105: public Stream setIgnoreImplicitExceptions(boolean enable) {
106: ignoreImplicitExceptions = enable;
107: return this ;
108: }
109:
110: /**
111: * Mark whether or not Stream is open as soon as it is created,
112: * or whether a later method or constructor must explicitly
113: * open it.
114: */
115: public Stream setIsOpenOnCreation(boolean enable) {
116: isOpenOnCreation = enable;
117: return this ;
118: }
119:
120: /**
121: * Set the number of the parameter which passes the
122: * stream instance.
123: *
124: * @param instanceParam number of the parameter passing the stream instance
125: */
126: public void setInstanceParam(int instanceParam) {
127: this .instanceParam = instanceParam;
128: }
129:
130: /**
131: * Set this Stream has having been closed on all
132: * paths out of the method.
133: */
134: public void setClosed() {
135: isClosed = true;
136: }
137:
138: public String getStreamBase() {
139: return streamBase;
140: }
141:
142: public boolean isUninteresting() {
143: return isUninteresting;
144: }
145:
146: public boolean isOpenOnCreation() {
147: return isOpenOnCreation;
148: }
149:
150: public void setOpenLocation(Location openLocation) {
151: this .openLocation = openLocation;
152: }
153:
154: public Location getOpenLocation() {
155: return openLocation;
156: }
157:
158: public boolean ignoreImplicitExceptions() {
159: return ignoreImplicitExceptions;
160: }
161:
162: public int getInstanceParam() {
163: return instanceParam;
164: }
165:
166: public String getBugType() {
167: return bugType;
168: }
169:
170: /**
171: * Return whether or not the Stream is closed on all paths
172: * out of the method.
173: */
174: public boolean isClosed() {
175: return isClosed;
176: }
177:
178: public boolean isStreamOpen(BasicBlock basicBlock,
179: InstructionHandle handle, ConstantPoolGen cpg,
180: ResourceValueFrame frame) {
181: if (isOpenOnCreation)
182: return false;
183:
184: Instruction ins = handle.getInstruction();
185: if (!(ins instanceof INVOKESPECIAL))
186: return false;
187:
188: // Does this instruction open the stream?
189: INVOKESPECIAL inv = (INVOKESPECIAL) ins;
190:
191: return frame.isValid()
192: && getInstanceValue(frame, inv, cpg).isInstance()
193: && matchMethod(inv, cpg, this .getResourceClass(),
194: "<init>");
195: }
196:
197: public boolean isStreamClose(BasicBlock basicBlock,
198: InstructionHandle handle, ConstantPoolGen cpg,
199: ResourceValueFrame frame,
200: RepositoryLookupFailureCallback lookupFailureCallback) {
201:
202: Instruction ins = handle.getInstruction();
203:
204: if ((ins instanceof INVOKEVIRTUAL)
205: || (ins instanceof INVOKEINTERFACE)) {
206: // Does this instruction close the stream?
207: InvokeInstruction inv = (InvokeInstruction) ins;
208:
209: if (!frame.isValid()
210: || !getInstanceValue(frame, inv, cpg).isInstance())
211: return false;
212:
213: // It's a close if the invoked class is any subtype of the stream base class.
214: // (Basically, we may not see the exact original stream class,
215: // even though it's the same instance.)
216: try {
217: return inv.getName(cpg).equals("close")
218: && inv.getSignature(cpg).equals("()V")
219: && Hierarchy.isSubtype(inv.getClassName(cpg),
220: streamBase);
221: } catch (ClassNotFoundException e) {
222: lookupFailureCallback.reportMissingClass(e);
223: return false;
224: }
225: }
226:
227: return false;
228: }
229:
230: private ResourceValue getInstanceValue(ResourceValueFrame frame,
231: InvokeInstruction inv, ConstantPoolGen cpg) {
232: int numConsumed = inv.consumeStack(cpg);
233: if (numConsumed == Constants.UNPREDICTABLE)
234: throw new IllegalStateException();
235: return frame.getValue(frame.getNumSlots() - numConsumed);
236: }
237:
238: private boolean matchMethod(InvokeInstruction inv,
239: ConstantPoolGen cpg, String className, String methodName) {
240: return inv.getClassName(cpg).equals(className)
241: && inv.getName(cpg).equals(methodName);
242: }
243:
244: @Override
245: public int hashCode() {
246: return getLocation().hashCode() + 3 * streamBase.hashCode() + 7
247: * getResourceClass().hashCode() + 11 * instanceParam;
248: }
249:
250: @Override
251: public boolean equals(Object o) {
252: if (!(o instanceof Stream))
253: return false;
254: Stream other = (Stream) o;
255: if (!getLocation().equals(other.getLocation()))
256: return false;
257: if (!streamBase.equals(other.streamBase))
258: return false;
259: if (!getResourceClass().equals(other.getResourceClass()))
260: return false;
261: if (instanceParam != other.instanceParam)
262: return false;
263: return true;
264: }
265:
266: public int compareTo(Stream other) {
267: int cmp;
268:
269: // The main idea in comparing streams is that
270: // if they can't be differentiated by location
271: // and base/stream class, then we should try
272: // instanceParam. This allows streams passed in
273: // different parameters to be distinguished.
274:
275: cmp = getLocation().compareTo(other.getLocation());
276: if (cmp != 0)
277: return cmp;
278: cmp = streamBase.compareTo(other.streamBase);
279: if (cmp != 0)
280: return cmp;
281: cmp = getResourceClass().compareTo(other.getResourceClass());
282: if (cmp != 0)
283: return cmp;
284: cmp = instanceParam - other.instanceParam;
285: if (cmp != 0)
286: return cmp;
287:
288: return 0;
289: }
290: }
291:
292: // vim:ts=3
|