[Fb-contrib-commit] SF.net SVN: fb-contrib:[1772] trunk/fb-contrib
Brought to you by:
dbrosius
From: <dbr...@us...> - 2014-05-24 04:08:33
|
Revision: 1772 http://sourceforge.net/p/fb-contrib/code/1772 Author: dbrosius Date: 2014-05-24 04:08:28 +0000 (Sat, 24 May 2014) Log Message: ----------- prepared for version 5.2.1 release Modified Paths: -------------- trunk/fb-contrib/.classpath trunk/fb-contrib/build.properties trunk/fb-contrib/build.xml trunk/fb-contrib/etc/bugrank.txt trunk/fb-contrib/etc/findbugs.xml trunk/fb-contrib/etc/messages.xml trunk/fb-contrib/htdocs/index.shtml trunk/fb-contrib/pom.xml trunk/fb-contrib/samples/LSYC_Sample.java trunk/fb-contrib/samples/ROOM_Sample.java trunk/fb-contrib/samples/samples.fbp trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/debug/OCSDebugger.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/InvalidConstantArgument.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/LiteralStringComparison.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/LocalSynchronizedCollection.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/PossiblyRedundantMethodCalls.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/SillynessPotPourri.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/UnusedParameter.java trunk/fb-contrib/yank.xls Added Paths: ----------- trunk/fb-contrib/samples/CTU_Sample.java trunk/fb-contrib/samples/HES_Sample.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/ConflictingTimeUnits.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/HangingExecutors.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/LocalTypeDetector.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/Unjitable.java trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/utils/OpcodeUtils.java Modified: trunk/fb-contrib/.classpath =================================================================== --- trunk/fb-contrib/.classpath 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/.classpath 2014-05-24 04:08:28 UTC (rev 1772) @@ -15,8 +15,8 @@ <classpathentry kind="lib" path="lib/findbugs-2.0.3.jar" sourcepath="/home/dave/.m2/repository/com/google/code/findbugs/findbugs/2.0.3/findbugs-2.0.3-sources.jar"/> <classpathentry kind="lib" path="lib/annotations-2.0.3.jar"/> <classpathentry kind="lib" path="lib/javax.servlet.jsp-api-2.3.1.jar"/> - <classpathentry kind="lib" path="lib/guava-16.0.1.jar" sourcepath="lib/guava-16.0.1-sources.jar"/> <classpathentry kind="lib" path="lib/commons-lang3-3.3.jar"/> <classpathentry kind="lib" path="lib/slf4j-api-1.7.6.jar"/> + <classpathentry kind="lib" path="lib/guava-17.0.jar"/> <classpathentry kind="output" path="classes"/> </classpath> Modified: trunk/fb-contrib/build.properties =================================================================== --- trunk/fb-contrib/build.properties 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/build.properties 2014-05-24 04:08:28 UTC (rev 1772) @@ -9,3 +9,5 @@ output.. = classes/ proxy.server = + +findbugs.dir=/home/dave/dev/findbugs/findbugs Modified: trunk/fb-contrib/build.xml =================================================================== --- trunk/fb-contrib/build.xml 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/build.xml 2014-05-24 04:08:28 UTC (rev 1772) @@ -18,7 +18,7 @@ <property name="javac.deprecation" value="on" /> <property name="javac.debug" value="on" /> - <property name="fb-contrib.version" value="5.3.0" /> + <property name="fb-contrib.version" value="5.2.1" /> <property name="sonatype.dir" value="${user.home}/.fb-contrib-${fb-contrib.version}-sonatype" /> @@ -38,7 +38,7 @@ <target name="yank" xmlns:yank="antlib:com.mebigfatguy.yank"> <mkdir dir="${lib.dir}" /> - <yank:yank yankFile="${basedir}/yank.xls" destination="${lib.dir}" proxyServer="${proxy.server}" source="true"> + <yank:yank yankFile="${basedir}/yank.xls" destination="${lib.dir}" proxyServer="${proxy.server}" source="true" separateClassifierTypes="true"> <server url="http://repo1.maven.org/maven2" /> <generateVersions propertyFileName="${basedir}/version.properties" /> </yank:yank> @@ -91,7 +91,8 @@ <target name="compile_samples" depends="-init" description="compiles sample problem files"> <javac srcdir="${samples.dir}" destdir="${samples.dir}" source="1.7" target="1.7" deprecation="${javac.deprecation}" debug="${javac.debug}" includeantruntime="false"> - <classpath refid="fb-contrib.classpath" /> + <compilerarg value="-XDignore.symbol.file"/> + <classpath refid="fb-contrib.classpath" /> <classpath refid="fb-contrib.samples.classpath" /> </javac> <delete file="${samples.dir}/SJVU_Sample.class" /> @@ -164,8 +165,7 @@ </target> <target name="install" depends="build" description="installs the plugin into FindBugs"> - <property environment="env" /> - <copy todir="${env.FINDBUGS_HOME}/plugin"> + <copy todir="${findbugs.dir}/plugin"> <fileset dir="${basedir}"> <include name="fb-contrib-${fb-contrib.version}.jar" /> </fileset> Modified: trunk/fb-contrib/etc/bugrank.txt =================================================================== --- trunk/fb-contrib/etc/bugrank.txt 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/etc/bugrank.txt 2014-05-24 04:08:28 UTC (rev 1772) @@ -126,4 +126,7 @@ 0 BugPattern NSE_NON_SYMMETRIC_EQUALS 0 BugPattern CVAA_CONTRAVARIANT_ARRAY_ASSIGNMENT 0 BugPattern NFF_NON_FUNCTIONAL_FIELD -0 BugPattern CEBE_COMMONS_EQUAL_BUILDER_TOEQUALS \ No newline at end of file +0 BugPattern CEBE_COMMONS_EQUAL_BUILDER_TOEQUALS +-2 BugPattern HE_LOCAL_EXECUTOR_SERVICE +-4 BugPattern HE_EXECUTOR_NEVER_SHUTDOWN +-2 BugPattern HE_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN \ No newline at end of file Modified: trunk/fb-contrib/etc/findbugs.xml =================================================================== --- trunk/fb-contrib/etc/findbugs.xml 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/etc/findbugs.xml 2014-05-24 04:08:28 UTC (rev 1772) @@ -20,7 +20,7 @@ <!-- Detectors --> -<!-- COMMENT OUT FOR RELEASE --> +<!-- COMMENT OUT FOR RELEASE <Detector class="com.mebigfatguy.fbcontrib.debug.OCSDebugger" speed="fast"/> @@ -34,7 +34,7 @@ <Detector class="com.mebigfatguy.fbcontrib.detect.BloatedAssignmentScope" speed="fast" reports="BAS_BLOATED_ASSIGNMENT_SCOPE" hidden="true" /> -<!-- COMMENT OUT FOR RELEASE --> + COMMENT OUT FOR RELEASE --> <Detector class="com.mebigfatguy.fbcontrib.collect.CollectStatistics" speed="fast" reports="" hidden="true" /> @@ -264,14 +264,21 @@ <Detector class="com.mebigfatguy.fbcontrib.collect.CollectMethodsReturningImmutableCollections" speed="fast" reports="" hidden="true" /> <Detector class="com.mebigfatguy.fbcontrib.detect.ModifyingUnmodifiableCollection" speed="fast" reports="MUC_MODIFYING_UNMODIFIABLE_COLLECTION"/> + - <!-- COMMENT OUT FOR POINT RELEASE --> + <!-- COMMENT OUT FOR POINT RELEASE <Detector class="com.mebigfatguy.fbcontrib.detect.PresizeCollections" speed="fast" reports="PSC_PRESIZE_COLLECTIONS" /> <Detector class="com.mebigfatguy.fbcontrib.detect.ArrayIndexOutOfBounds" speed="fast" reports="AIOB_ARRAY_INDEX_OUT_OF_BOUNDS,AIOB_ARRAY_STORE_TO_NULL_REFERENCE" /> - <!-- COMMENT OUT FOR POINT RELEASE --> + <Detector class="com.mebigfatguy.fbcontrib.detect.Unjitable" speed="fast" reports="UJM_UNJITABLE_METHOD" /> + + <Detector class="com.mebigfatguy.fbcontrib.detect.HangingExecutors" speed="fast" reports="HES_EXECUTOR_NEVER_SHUTDOWN,HES_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN,HES_LOCAL_EXECUTOR_SERVICE"/> + <Detector class="com.mebigfatguy.fbcontrib.detect.ConflictingTimeUnits" speed="fast" reports="CTU_CONFLICTING_TIME_UNITS" /> + + COMMENT OUT FOR POINT RELEASE --> + <!-- BugPattern --> <BugPattern abbrev="ISB" type="ISB_INEFFICIENT_STRING_BUFFERING" category="PERFORMANCE" /> @@ -474,4 +481,9 @@ <BugPattern abbrev="UP" type="UP_UNUSED_PARAMETER" category="STYLE" experimental="true" /> <BugPattern abbrev="CD" type="CD_CIRCULAR_DEPENDENCY" category="CORRECTNESS" /> <BugPattern abbrev="MUC" type="MUC_MODIFYING_UNMODIFIABLE_COLLECTION" category="CORRECTNESS" experimental="true" /> + <BugPattern abbrev="UJM" type="UJM_UNJITABLE_METHOD" category="PERFORMANCE" experimental="true" /> + <BugPattern abbrev="HES" type="HES_EXECUTOR_NEVER_SHUTDOWN" category="CORRECTNESS" experimental="true" /> + <BugPattern abbrev="HES" type="HES_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN" category="CORRECTNESS" experimental="true" /> + <BugPattern abbrev="HES" type="HES_LOCAL_EXECUTOR_SERVICE" category="CORRECTNESS" experimental="true" /> + <BugPattern abbrev="CTU" type="CTU_CONFLICTING_TIME_UNITS" category="CORRECTNESS" experimental="true" /> </FindbugsPlugin> Modified: trunk/fb-contrib/etc/messages.xml =================================================================== --- trunk/fb-contrib/etc/messages.xml 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/etc/messages.xml 2014-05-24 04:08:28 UTC (rev 1772) @@ -178,6 +178,12 @@ <p>Looks for methods that correctly do not write to a parameter. To help document this, and to perhaps help the JVM optimize the invocation of this method, you should consider defining these parameters as final.</p> + + <p>Performance gains are debatable as "the final keyword does not appear in the class file for + local variables and parameters, thus it cannot impact the runtime performance. It's only use + is to clarify the coders intent that the variable not be changed (which many consider dubious + reason for its usage), and dealing with anonymous inner classes." - http://stackoverflow.com/a/266981/1447621</p> + <p>It is a slow detector</p> ]]> </Details> @@ -1384,7 +1390,7 @@ <Details> <![CDATA[ <p>Looks for private or static methods that have parameters that aren't used. These parameters - can be removed.</p> + can be removed, assuming the method isn't used through reflection.</p> <p>It is fast detector</p> ]]> </Details> @@ -1409,6 +1415,38 @@ </Details> </Detector> + <Detector class="com.mebigfatguy.fbcontrib.detect.HangingExecutors"> + <Details> + <![CDATA[ + <p>Single detector for hanging ExecutorServices, that is, ExecutorServices that never get a call to shutdown, which + can potentially cause the JVM to not exit.</p> + <p>It is a fast detector</p> + ]]> + </Details> + </Detector> + + <Detector class="com.mebigfatguy.fbcontrib.detect.Unjitable"> + <Details> + <![CDATA[ + <p>This detector looks for methods that are longer than 8000 bytes. Methods this + long are automatically disqualified by the jit for compilation and will always be + emulated. Consider breaking this method up to avoid this, if performance is important.</p> + <p>It is a fast detector.</p> + ]]> + </Details> + </Detector> + + <Detector class="com.mebigfatguy.fbcontrib.detect.ConflictingTimeUnits"> + <Details> + <![CDATA[ + <p>looks for methods that perform arithmetic operations on values representing time + where the time unit is incompatible, ie adding a millisecond value to a nanosecond value. + </p> + <p>It is a fast detector</p> + ]]> + </Details> + </Detector> + <Detector class="com.mebigfatguy.fbcontrib.debug.OCSDebugger"> <Details></Details> </Detector> @@ -1592,22 +1630,43 @@ <LongDescription>Class {0} defines two or more one for one associated lists or arrays</LongDescription> <Details> <![CDATA[ - <p>This class appears to maintain two or more lists or arrays who's contains is related one-for-one - through the index of the list or array. Consider creating a separate class to hold all the related + <p>This class appears to maintain two or more lists or arrays whose contents are related in a parallel way. That is, + you have something like:<br/> + <code> + List<String> words = new ArrayList<String>();<br/> + List<Integer> wordCounts = new ArrayList<String>();<br/> + </code> + where the elements of the list at index 0 are related, the elements at index 1 are related and so on. </p> + <p> + Consider creating a separate class to hold all the related pieces of information, and adding instances of this class to just one list or array, or if just two values, use - a Map to associate one value with the other.</p> + a Map to associate one value with the other like:<br/> + <code> + private class WordAndCount{public String word; public int count} + List<WordAndCount> wordsAndCounts = new ArrayList<WordAndCount>();<br/> + <br/> + //or, for just two elements<br/> + Map<String,Integer> wordCounts = new HashMap<String,Integer>();<br/> + </code> + + </p> ]]> </Details> </BugPattern> <BugPattern type="FP_FINAL_PARAMETERS"> <ShortDescription>Method does not define a parameter as final, but could</ShortDescription> - <LongDescription>Method {1} does not define a parameter as final, but could</LongDescription> + <LongDescription>Method {1} does not define one or more parameters as final, but could</LongDescription> <Details> <![CDATA[ - <p>This method correctly does not write to a parameter. To help document this, and to perhaps + <p>This method does not write to a parameter. To help document this, and to perhaps help the JVM optimize the invocation of this method, you should consider defining these parameters as final.</p> + + <p>Performance gains are debatable as "the final keyword does not appear in the class file for + local variables and parameters, thus it cannot impact the runtime performance. It's only use + is to clarify the coders intent that the variable not be changed (which many consider dubious + reason for its usage), and dealing with anonymous inner classes." - http://stackoverflow.com/a/266981/1447621 </p> ]]> </Details> </BugPattern> @@ -2041,13 +2100,33 @@ </BugPattern> <BugPattern type="PMB_POSSIBLE_MEMORY_BLOAT"> - <ShortDescription>Class defines static field that appears to allow memory bloat</ShortDescription> - <LongDescription>Class {0} defines static field that appears to allow memory bloat</LongDescription> + <ShortDescription>Potential memory bloat in static field</ShortDescription> + <LongDescription>Class {0} defines static field "{2}" which appears to allow memory bloat</LongDescription> <Details> <![CDATA[ - <p>This class defines static fields that are collections or StringBuffers that do not - appear to have any way to clear or reduce their size. This is a potential cause of - memory bloat.</p> + <p>This class defines static fields that are <code>Collection</code>s, <code>StringBuffer</code>s, or <code>StringBuilder</code>s + that do not appear to have any way to clear or reduce their size. That is, a collection is defined + and has method calls like <br/> + {<code>add()</code>, <code>append()</code>, <code>offer()</code>, <code>put()</code>, ...} <br/> + with no method calls to removal methods like<br/> + {<code>clear()</code>, <code>delete()</code>, <code>pop()</code>, <code>remove()</code>, ...}<br/> + This means that the collection in question can only ever increase in size, which is + a potential cause of memory bloat.</p> + + <p> + If this collection is a list, set or otherwise of static things (e.g. a List<String> for month names), consider + adding all of the elements in a static initializer, which can only be called once:<br/> + <code> + private static List<String> monthNames = new ArrayList<String>();<br/> + static {<br/> + monthNames.add("January");<br/> + monthNames.add("February");<br/> + monthNames.add("March");<br/> + ...<br/> + } + </code> + </p> + ]]> </Details> </BugPattern> @@ -3954,7 +4033,110 @@ ]]> </Details> </BugPattern> + + <BugPattern type="HES_EXECUTOR_NEVER_SHUTDOWN"> + <ShortDescription>ExecutorService field doesn't ever get shutdown</ShortDescription> + <LongDescription>ExecutorService {2} is instantiated, but never shutdown, potentially preventing the entire JVM from shutting down</LongDescription> + <Details> + <![CDATA[ + <p>Most <code>ExecutorService</code> objects must be explicitly shutdown, + otherwise, their internal threads can prolong the running of the JVM, even when everything + else has stopped.</p> + + <p>FindBugs has detected that there are no calls to either the <code>shutdown()</code> or <code>shutdownNow()</code> + method, and thus, the <code>ExecutorService</code> is not guaranteed to ever terminate. This is especially + problematic for <code>Executors.newFixedThreadPool()</code> and most of the other convenience methods in + the <code>Executors</code> class.</p> + + <p>Even though there are some exceptions to this, particularly when a custom <code>ThreadFactory</code> is + provided, or for <code>ThreadPoolExecutor</code>s with <code>allowsCoreThreadTimeOut()</code> set to true, + it is good practice to explictly shutdown the <code>ExecutorService</code> when its utility is done.</p> + ]]> + </Details> + </BugPattern> + + <BugPattern type="HES_LOCAL_EXECUTOR_SERVICE"> + <ShortDescription>Suspicious Local Executor Service</ShortDescription> + <LongDescription>ExecutorService is created as a local variable, which is unusual</LongDescription> + <Details> + <![CDATA[ + <p><code>ExecutorService</code>s are typically instantiated as fields so that many tasks can be executed on a controlled number of <code>Thread</code>s across many method calls. Therefore, it is unusual for <code>ExecutorService</code>s to be a local variable, where tasks will be added only one time, in the enclosing method. </p> + + <p>Furthermore, when a local <code>ExecutorService</code> reaches the end of scope and goes up for garbage collection, the internal <code>Thread</code>s are not necessarily terminated and can prevent the JVM from ever shutting down.</p> + + <p>Consider making this local variable a field and create a method that will explicitly shutdown the <code>ExecutorService</code></p> + ]]> + </Details> + </BugPattern> +<BugPattern type="HES_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN"> + <ShortDescription>An ExecutorService isn't shutdown before the reference to it is lost</ShortDescription> + <LongDescription>ExecutorService {2} is replaced with another ExecutorService without being shutdown, potentially preventing the entire JVM from shutting down</LongDescription> + <Details> + <![CDATA[ + <p>Most <code>ExecutorService</code> objects must be explicitly shutdown, otherwise, their internal threads can prevent the JVM from ever shutting down, even when everything else has stopped.</p> + + <p>FindBugs has detected that something like the following is happening:<br/> + <code> + ExecutorService executor = ... //e.g. Executors.newCachedThreadPool();<br/> + ...<br/> + public void reset() {<br/> + this.executor = Executors.newCachedThreadPool(); <br/> + this.executor.execute(new SampleExecutable()); <br/> + }<br/> + </code> + For normal objects, losing the last reference to them like this would trigger the object to be cleaned up + in garbage collection. For <code>ExecutorService</code>s, this isn't enough to terminate the internal threads in the + thread pool, and the <code>ExecutorService</code> isn't guaranteed to shutdown, causing the JVM to never stop. <br/> + To fix this, simply add a call to <code>shutdown()</code> like this:<br/> + <code> + ExecutorService executor = ... //e.g. Executors.newCachedThreadPool();<br/> + ...<br/> + public void reset() {<br/> + <b> this.executor.shutDown();</b> <br/> + this.executor = Executors.newCachedThreadPool(); <br/> + this.executor.execute(new SampleExecutable()); <br/> + } + </code> + </p> + + <p>Even though there are some exceptions to this, particularly when a custom <code>ThreadFactory</code> is + provided, or for <code>ThreadPoolExecutor</code>s with <code>allowsCoreThreadTimeOut()</code> set to true, + it is good practice to explictly shutdown the <code>ExecutorService</code> at the end of execution, or + when it is being replaced.</p> + + <p><b>Note:</b> <code>ExecutorService</code>s are generally created once in a program's lifecycle. If you find yourself + replacing the <code>ExecutorService</code>, perhaps you may consider restructuring your code to use calls like + <code>awaitTermination()</code> or <code>Future</code>s/<code>Callable</code>s to avoid recreating the <code>ExecutorService</code>.</p> + ]]> + </Details> + </BugPattern> + + <BugPattern type="UJM_UNJITABLE_METHOD"> + <ShortDescription>This method is too long to be compiled by the JIT</ShortDescription> + <LongDescription>This method {1} is too long to be compiled by the JIT</LongDescription> + <Details> + <![CDATA[ + <p>This method is longer than 8000 bytes. By default the JIT will not attempt to compile this method no matter + how hot it is, and so this method will always be interpreted. If performance is important, you should consider + breaking this method up in smaller chunks. (And probably a good idea for readability too!). + ]]> + </Details> + </BugPattern> + + <BugPattern type="CTU_CONFLICTING_TIME_UNITS"> + <ShortDescription>This method performs arithmetic operations on time values with different units</ShortDescription> + <LongDescription>This method performs arithmetic operations on time values with different units</LongDescription> + <Details> + <![CDATA[ + <p>This method takes two values that appear to be representing time, and performs arithmetic operations on this + two values directly, even though it appears that the two values are representing different time units, such as + adding a millisecond value to a nanosecond value. You should convert the two values to the same time unit before + performing this calculation in order for it to be meaningful. + ]]> + </Details> + </BugPattern> + <!-- BugCode --> <BugCode abbrev="ISB">Inefficient String Buffering</BugCode> @@ -4074,4 +4256,7 @@ <BugCode abbrev="UP">Unused Parameter</BugCode> <BugCode abbrev="CD">Circular Dependencies</BugCode> <BugCode abbrev="MUC">Modifying Unmodifiable Collection</BugCode> + <BugCode abbrev="UJM">Unjitable method</BugCode> + <BugCode abbrev="HES">Hanging ExecutorService</BugCode> + <BugCode abbrev="CTU">Conflicting Time Units</BugCode> </MessageCollection> Modified: trunk/fb-contrib/htdocs/index.shtml =================================================================== --- trunk/fb-contrib/htdocs/index.shtml 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/htdocs/index.shtml 2014-05-24 04:08:28 UTC (rev 1772) @@ -89,7 +89,8 @@ on the garbage collector. </li> <li><b>[AIOB] Array Index Out of Bounds</b><br/> - Looks for questionable load/stores to array elements.<ul> + Looks for questionable load/stores to array elements. + <ul> <li> Looks for accesses to array elements using literal values that are known to be outside the bounds of the array. This mistake will cause an ArrayIndexOutOfBoundsException to occur at runtime.</li> @@ -98,6 +99,17 @@ </li> </ul> </li> + <li><b>[UJM] Unjitable Methods</b><br/> + Looks for methods that are too big that the JIT will not compile them no matter how often they are run + </li> + <li><b>[HE] Hangable Executors</b><br/> + Looks for executors that are never shutdown, which will not allow the application to terminate<br/> + <span style="color: #0000FF;">--contributed by Kevin Lubick - THANKS!</span> + </li> + <li><b>[CTU] Conflicting Time Units</b><br/> + Looks for methods that perform arithmetic operations on values representing time + where the time unit is incompatible, ie adding a millisecond value to a nanosecond value. + </li> </ul> </div> <hr/> Modified: trunk/fb-contrib/pom.xml =================================================================== --- trunk/fb-contrib/pom.xml 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/pom.xml 2014-05-24 04:08:28 UTC (rev 1772) @@ -8,7 +8,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.mebigfatguy.fb-contrib</groupId> <artifactId>fb-contrib</artifactId> - <version>5.3.0</version> + <version>5.2.1</version> <parent> <groupId>org.sonatype.oss</groupId> Added: trunk/fb-contrib/samples/CTU_Sample.java =================================================================== --- trunk/fb-contrib/samples/CTU_Sample.java (rev 0) +++ trunk/fb-contrib/samples/CTU_Sample.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -0,0 +1,26 @@ +import java.util.concurrent.TimeUnit; + + +public class CTU_Sample { + + public long simpleMillisAndNanos() { + long millis = System.currentTimeMillis(); + long nanos = System.nanoTime(); + + return millis + nanos; + } + + public long badUseOfConvert() { + long secs = TimeUnit.SECONDS.convert(1000, TimeUnit.MILLISECONDS); + long millis = System.currentTimeMillis(); + + return secs + millis; + } + + public long fpHandConversions() { + long millis = System.currentTimeMillis(); + long secs = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS); + + return millis * 1000 + secs; + } +} Property changes on: trunk/fb-contrib/samples/CTU_Sample.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: trunk/fb-contrib/samples/HES_Sample.java =================================================================== --- trunk/fb-contrib/samples/HES_Sample.java (rev 0) +++ trunk/fb-contrib/samples/HES_Sample.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -0,0 +1,370 @@ +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +//Expected bug count: 12 +//5 HE_EXECUTOR_NEVER_SHUTDOWN +//4 HE_LOCAL_EXECUTOR_SERVICE +//3 HE_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN +public class HES_Sample { + + + public static void main(String[] args) { + LocalExecutorProblem p = new LocalExecutorProblem(); + p.task(); + + System.out.println("Should end"); + } +} + +class SampleExecutable implements Runnable { + @Override + public void run() { + System.out.println("Hello"); + } + + //Dummy method with throws to simulate something potentially throwing exception + public static void methodThrows() throws Exception{ + if (Math.random()<.5) + throw new Exception("There was a problem with the RNG"); + } + +} + +class SingleThreadExecutorProblem { + //tag + private ExecutorService executor; + + public SingleThreadExecutorProblem() { + this.executor = Executors.newSingleThreadExecutor(); + } + + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class SingleThreadExecutorGood { + //no tag + private ExecutorService executor; + + public SingleThreadExecutorGood() { + this.executor = Executors.newSingleThreadExecutor(); + } + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + public void shutDown() { + executor.shutdown(); + } +} +class SingleThreadExecutorGood1 { + //no tag + private ExecutorService executor; + + public SingleThreadExecutorGood1() { + this.executor = Executors.newSingleThreadExecutor(); + } + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + public void shutDown() { + executor.shutdownNow(); + } +} +class SingleThreadExecutorGood2 { + //no tag + private ExecutorService executor; + + public SingleThreadExecutorGood2() { + this.executor = Executors.newSingleThreadExecutor(); + } + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + public void shutDown() { + try { + executor.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + executor.shutdown(); + } +} + +class SingleThreadExecutorTryProblem { + //this won't get tagged as of version 2.2 If given more thought, this could be implemented + private ExecutorService executor; + + public SingleThreadExecutorTryProblem() { + this.executor = Executors.newSingleThreadExecutor(); + } + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + public void shutDown() { + try { + executor.awaitTermination(30, TimeUnit.SECONDS); + executor.shutdown(); //this doesn't count as shutdown, so it should be tagged. + //probably with a different bug + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } +} + +class FixedThreadPoolProblem { + //tag + private ExecutorService executor; + + public FixedThreadPoolProblem() { + this.executor = Executors.newFixedThreadPool(3); + } + + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class CachedThreadPoolMehProblem { + //tag - this is bad practice, even though JVM will exit after 60 seconds + private ExecutorService executor; + + public CachedThreadPoolMehProblem() { + this.executor = Executors.newCachedThreadPool(); + } + + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class SingleThreadExecutorThreadFactoryMehProblem { + //tag - this is bad practice, even though the threads will terminate + private ExecutorService executor; + + public SingleThreadExecutorThreadFactoryMehProblem() { + this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable arg0) { + Thread t = new Thread(arg0); + t.setDaemon(true); + return t; + } + }); + } + + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class ScheduledThreadPoolProblem { + //tag + private ExecutorService executor; + + public ScheduledThreadPoolProblem() { + this.executor = Executors.newScheduledThreadPool(1); + } + + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class ReplacementExecutorProblem { + private ExecutorService executor; + + public ReplacementExecutorProblem() { + this.executor = Executors.newScheduledThreadPool(1); + executor.execute(new SampleExecutable()); + } + + public void reset() { + + executor.execute(new SampleExecutable()); + //tag (the old executor won't get picked up for garbage collection) + this.executor = Executors.newScheduledThreadPool(1); + executor.execute(new SampleExecutable()); + try { + executor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void test() { + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + + public void shutDown() { + executor.shutdownNow(); + } + +} + +class ReplacementExecutorGood { + private ExecutorService executor; + + public ReplacementExecutorGood() { + this.executor = Executors.newScheduledThreadPool(1); + executor.execute(new SampleExecutable()); + } + + public void reset() { + //no tag + this.executor.shutdown(); + + this.executor = Executors.newScheduledThreadPool(1); + executor.execute(new SampleExecutable()); + try { + executor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + executor.shutdown(); + } + +} + +class ReplacementExecutorBad2 { + private ExecutorService executor; + + public ReplacementExecutorBad2() { + this.executor = Executors.newScheduledThreadPool(1); + executor.execute(new SampleExecutable()); + } + + public void reset() { + //tag, because shutdown in other method isn't forced to be called + this.executor = Executors.newScheduledThreadPool(1); + + } + + public void shutDown() { + this.executor.shutdown(); + } + + public void task() { + executor.execute(new SampleExecutable()); + try { + executor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} + +class ReplacementExecutorGood2 { + private ExecutorService executor; + + public ReplacementExecutorGood2() { + this.executor = Executors.newScheduledThreadPool(1); + executor.execute(new SampleExecutable()); + } + + public void reset() { + System.out.println("Pretest"); + if (executor == null) { + //no tag, the null check indicates some thought that another threadpool won't get left behind + this.executor = Executors.newScheduledThreadPool(1); + } + //tag (this one is no long under the "good graces" of the null check + this.executor = Executors.newCachedThreadPool(); + } + + public void shutDown() { + this.executor.shutdown(); + //no tag + this.executor = null; + } + + public void task() { + executor.execute(new SampleExecutable()); + try { + executor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} + + +class LocalExecutorProblem { + + public void task() { + //tag + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class LocalExecutorProblem1 { + + public void task() { + //tag + ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { + + @Override + public Thread newThread(Runnable arg0) { + return new Thread(arg0); + } + }); + executor.execute(new SampleExecutable()); + executor.execute(new SampleExecutable()); + } + +} + +class LocalExecutorProblem2 { + + public void task() { + //tag (checking for mislabeled objects) + Object executor = Executors.newCachedThreadPool(new ThreadFactory() { + + @Override + public Thread newThread(Runnable arg0) { + return new Thread(arg0); + } + }); + + System.out.println(executor); + } + +} + +class LocalExecutorProblem3 { + + public void task() { + //tag + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); + + System.out.println(executor); + } + +} + + + Property changes on: trunk/fb-contrib/samples/HES_Sample.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: trunk/fb-contrib/samples/LSYC_Sample.java =================================================================== --- trunk/fb-contrib/samples/LSYC_Sample.java 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/samples/LSYC_Sample.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -1,3 +1,4 @@ +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; @@ -28,6 +29,12 @@ List<String> a = Collections.synchronizedList(ls); syncfield = a; } + + public List<String> getList() { + // don't report + return Collections.synchronizedList(new ArrayList<String>()); + + } public Map<String, Map<String, String>> test4() { // report as low @@ -47,5 +54,10 @@ buffer.append("Findbugs "); return buffer.toString(); } + + public String printString2() { + //no tag, but probably should. + return new StringBuffer().append("Hello").append("World").toString(); + } } Modified: trunk/fb-contrib/samples/ROOM_Sample.java =================================================================== --- trunk/fb-contrib/samples/ROOM_Sample.java 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/samples/ROOM_Sample.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -1,32 +1,32 @@ import java.lang.reflect.Method; public class ROOM_Sample { - public static final Class[] STATIC_NO_ARGS = new Class[0]; - public final Class[] NO_ARGS = new Class[0]; + public static final Class<?>[] STATIC_NO_ARGS = new Class[0]; + public final Class<?>[] NO_ARGS = new Class[0]; public void testRoomWithLocals() throws Exception { - Class c = Class.forName("java.lang.Object"); + Class<?> c = Class.forName("java.lang.Object"); Method m = c.getMethod("equals", Object.class); String s = (String) m.invoke(this, new ROOM_Sample()); } public void testRoomWithField() throws Exception { - Class c = Class.forName("java.lang.Object"); + Class<?> c = Class.forName("java.lang.Object"); Method m = c.getMethod("toString", NO_ARGS); String s = (String) m.invoke(this, (Object[]) null); } public void testRoomWithStatic() throws Exception { - Class c = Class.forName("java.lang.Object"); + Class<?> c = Class.forName("java.lang.Object"); Method m = c.getMethod("hashCode", STATIC_NO_ARGS); String s = (String) m.invoke(this, (Object[]) null); } public void testRoomWithNull() throws Exception { - Class c = Class.forName("java.lang.Object"); + Class<?> c = Class.forName("java.lang.Object"); Method m = c.getMethod("notify", (Class[]) null); String s = (String) m.invoke(this, (Object[]) null); Modified: trunk/fb-contrib/samples/samples.fbp =================================================================== --- trunk/fb-contrib/samples/samples.fbp 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/samples/samples.fbp 2014-05-24 04:08:28 UTC (rev 1772) @@ -1,15 +1,15 @@ <Project projectName="sample"> - <Jar>././.</Jar> - <AuxClasspathEntry>./../lib/backport-util-concurrent-3.1.jar</AuxClasspathEntry> - <AuxClasspathEntry>./../lib/commons-collections-3.2.1.jar</AuxClasspathEntry> - <AuxClasspathEntry>./../lib/guava-16.0.1.jar</AuxClasspathEntry> - <AuxClasspathEntry>./../lib/javax.servlet-api-3.1.0.jar</AuxClasspathEntry> - <AuxClasspathEntry>./../lib/javax.servlet.jsp-api-2.3.1.jar</AuxClasspathEntry> - <AuxClasspathEntry>./../lib/junit-4.11.jar</AuxClasspathEntry> - <AuxClasspathEntry>./../lib/log4j-1.2.17.jar</AuxClasspathEntry> + <Jar>.</Jar> + <AuxClasspathEntry>../lib/backport-util-concurrent-3.1.jar</AuxClasspathEntry> + <AuxClasspathEntry>../lib/commons-collections-3.2.1.jar</AuxClasspathEntry> + <AuxClasspathEntry>../lib/guava-16.0.1.jar</AuxClasspathEntry> + <AuxClasspathEntry>../lib/javax.servlet-api-3.1.0.jar</AuxClasspathEntry> + <AuxClasspathEntry>../lib/javax.servlet.jsp-api-2.3.1.jar</AuxClasspathEntry> + <AuxClasspathEntry>../lib/junit-4.11.jar</AuxClasspathEntry> + <AuxClasspathEntry>../lib/log4j-1.2.17.jar</AuxClasspathEntry> <AuxClasspathEntry>../lib/commons-lang3-3.3.jar</AuxClasspathEntry> <AuxClasspathEntry>../lib/slf4j-api-1.7.6.jar</AuxClasspathEntry> - <SrcDir>././.</SrcDir> + <SrcDir>.</SrcDir> <SuppressionFilter> <LastVersion value="-1" relOp="NEQ"/> </SuppressionFilter> Modified: trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java =================================================================== --- trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/collect/CollectStatistics.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -28,7 +28,6 @@ { private int numMethodCalls; - @SuppressWarnings("unused") public CollectStatistics(BugReporter bugReporter) { Statistics.getStatistics().clear(); } Modified: trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/debug/OCSDebugger.java =================================================================== --- trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/debug/OCSDebugger.java 2014-04-13 01:38:42 UTC (rev 1771) +++ trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/debug/OCSDebugger.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -42,7 +42,6 @@ private OpcodeStack stack = new OpcodeStack(); private PrintWriter pw = null; - @SuppressWarnings("unused") public OCSDebugger(BugReporter bugReporter) { } Added: trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/ConflictingTimeUnits.java =================================================================== --- trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/ConflictingTimeUnits.java (rev 0) +++ trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/ConflictingTimeUnits.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -0,0 +1,198 @@ +/* + * fb-contrib - Auxiliary detectors for Java programs + * Copyright (C) 2005-2014 Dave Brosius + * + * 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 com.mebigfatguy.fbcontrib.detect; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.bcel.classfile.Code; +import org.apache.bcel.generic.Type; + +import edu.umd.cs.findbugs.BugInstance; +import edu.umd.cs.findbugs.BugReporter; +import edu.umd.cs.findbugs.BytecodeScanningDetector; +import edu.umd.cs.findbugs.OpcodeStack; +import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue; +import edu.umd.cs.findbugs.ba.ClassContext; + +/** + * looks for methods that perform arithmetic operations on values representing time + * where the time unit is incompatible, ie adding a millisecond value to a nanosecond value. + */ +@CustomUserValue +public class ConflictingTimeUnits extends BytecodeScanningDetector { + + private enum Units { NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, DAYS, CALLER }; + + private static Map<String, Units> TIME_UNIT_GENERATING_METHODS = new HashMap<String, Units>(); + static { + TIME_UNIT_GENERATING_METHODS.put("java/lang/System.currentTimeMillis()J", Units.MILLIS); + TIME_UNIT_GENERATING_METHODS.put("java/lang/System.nanoTime()J", Units.NANOS); + TIME_UNIT_GENERATING_METHODS.put("java/sql/Timestamp.getTime()J", Units.MILLIS); + TIME_UNIT_GENERATING_METHODS.put("java/sql/Timestamp.getNanos()I", Units.NANOS); + TIME_UNIT_GENERATING_METHODS.put("java/util/Date.getTime()J", Units.MILLIS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.toNanos(J)J", Units.NANOS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.toMicros(J)J", Units.MICROS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.toSeconds(J)J", Units.SECONDS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.toMinutes(J)J", Units.MINUTES); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.toHours(J)J", Units.HOURS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.toDays(J)J", Units.DAYS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.excessNanos(JJ)I", Units.NANOS); + TIME_UNIT_GENERATING_METHODS.put("java/util/concurrent/TimeUnit.convert(JLjava/util/concurrent/TimeUnit;)J", Units.CALLER); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.toNanos(J)J", Units.NANOS); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.toMicros(J)J", Units.MICROS); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.toSeconds(J)J", Units.SECONDS); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.toMinutes(J)J", Units.MINUTES); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.toHours(J)J", Units.HOURS); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.toDays(J)J", Units.DAYS); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.excessNanos(JJ)I", Units.NANOS); + TIME_UNIT_GENERATING_METHODS.put("edu/emory/matchcs/backport/java/util/concurrent/TimeUnit.convert(JLjava/util/concurrent/TimeUnit;)J", Units.CALLER); + TIME_UNIT_GENERATING_METHODS.put("org/joda/time/base/BaseDuration.getMillis()J", Units.MILLIS); + TIME_UNIT_GENERATING_METHODS.put("org/joda/time/base/BaseInterval.getEndMillis()J", Units.MILLIS); + TIME_UNIT_GENERATING_METHODS.put("org/joda/time/base/BaseInterval.getStartMillis()J", Units.MILLIS); + } + + private static Map<String, Units> TIMEUNIT_TO_UNITS = new HashMap<String, Units>(); + static { + TIMEUNIT_TO_UNITS.put("NANOSECONDS", Units.NANOS); + TIMEUNIT_TO_UNITS.put("MICROSECONDS", Units.MICROS); + TIMEUNIT_TO_UNITS.put("MILLISECONDS", Units.MILLIS); + TIMEUNIT_TO_UNITS.put("SECONDS", Units.SECONDS); + TIMEUNIT_TO_UNITS.put("MINUTES", Units.MINUTES); + TIMEUNIT_TO_UNITS.put("HOURS", Units.HOURS); + TIMEUNIT_TO_UNITS.put("DAYS", Units.DAYS); + } + + private BugReporter bugReporter; + private OpcodeStack stack; + + /** + * constructs a CTU detector given the reporter to report bugs on + * @param bugReporter the sync of bug reports + */ + public ConflictingTimeUnits(BugReporter bugReporter) { + this.bugReporter = bugReporter; + } + + /** + * overrides the visitor to reset the stack + * + * @param classContext the context object of the currently parsed class + */ + @Override + public void visitClassContext(ClassContext classContext) { + try { + stack = new OpcodeStack(); + super.visitClassContext(classContext); + } finally { + stack = null; + } + } + + /** + * overrides the visitor to resets the stack for this method. + * + * @param obj the context object for the currently parsed code block + */ + @Override + public void visitCode(Code obj) { + stack.resetForMethodEntry(this); + super.visitCode(obj); + } + + /** + * overrides the visitor to look for operations on two time unit values that are conflicting + */ + @Override + public void sawOpcode(int seen) { + Units unit = null; + try { + stack.precomputation(this); + + switch (seen) { + case INVOKEVIRTUAL: + case INVOKEINTERFACE: + case INVOKESTATIC: + String methodCall = getClassConstantOperand() + "." + getNameConstantOperand() + getSigConstantOperand(); + unit = TIME_UNIT_GENERATING_METHODS.get(methodCall); + if (unit == Units.CALLER) { + int offset = Type.getArgumentTypes(getSigConstantOperand()).length; + if (stack.getStackDepth() > offset) { + OpcodeStack.Item item = stack.getStackItem(offset); + unit = (Units) item.getUserValue(); + } else { + unit = null; + } + } + break; + + case GETSTATIC: + String clsName = getClassConstantOperand(); + if ("java/util/concurrent/TimeUnit".equals(clsName) + || "edu/emory/matchcs/backport/java/util/concurrent/TimeUnit".equals(clsName)) { + unit = TIMEUNIT_TO_UNITS.get(getNameConstantOperand()); + } + break; + + + case L2I: + case I2L: + if (stack.getStackDepth() > 0) { + OpcodeStack.Item item = stack.getStackItem(0); + unit = (Units) item.getUserValue(); + } + break; + + case IADD: + case ISUB: + case IMUL: + case IDIV: + case IREM: + case LADD: + case LSUB: + case LMUL: + case LDIV: + case LREM: + if (stack.getStackDepth() > 1) { + OpcodeStack.Item arg1 = stack.getStackItem(0); + OpcodeStack.Item arg2 = stack.getStackItem(1); + + Units u1 = (Units) arg1.getUserValue(); + Units u2 = (Units) arg2.getUserValue(); + + if ((u1 != null) && (u2 != null) && (u1 != u2)) { + bugReporter.reportBug(new BugInstance(this, "CTU_CONFLICTING_TIME_UNITS", NORMAL_PRIORITY) + .addClass(this) + .addMethod(this) + .addSourceLine(this) + .addString(u1.toString()) + .addString(u2.toString())); + } + } + break; + } + } finally { + stack.sawOpcode(this, seen); + if ((unit != null) && (stack.getStackDepth() > 0)) { + OpcodeStack.Item item = stack.getStackItem(0); + item.setUserValue(unit); + } + } + } +} Property changes on: trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/ConflictingTimeUnits.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/HangingExecutors.java =================================================================== --- trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/HangingExecutors.java (rev 0) +++ trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/HangingExecutors.java 2014-05-24 04:08:28 UTC (rev 1772) @@ -0,0 +1,312 @@ +/* + * fb-contrib - Auxiliary detectors for Java programs + * Copyright (C) 2005-2014 Kevin Lubick + * Copyright (C) 2005-2014 Dave Brosius + * + * 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 com.mebigfatguy.fbcontrib.detect; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.bcel.Constants; +import org.apache.bcel.classfile.Code; +import org.apache.bcel.classfile.Field; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.Type; + +import edu.umd.cs.findbugs.BugInstance; +import edu.umd.cs.findbugs.BugReporter; +import edu.umd.cs.findbugs.BytecodeScanningDetector; +import edu.umd.cs.findbugs.Detector; +import edu.umd.cs.findbugs.FieldAnnotation; +import edu.umd.cs.findbugs.OpcodeStack; +import edu.umd.cs.findbugs.Priorities; +import edu.umd.cs.findbugs.ba.ClassContext; +import edu.umd.cs.findbugs.ba.XFactory; +import edu.umd.cs.findbugs.ba.XField; + +/** + * looks for executors that are never shutdown, which will not allow the application to terminate + */ +public class HangingExecutors extends BytecodeScanningDetector { + + private static final Set<String> hangableSig = new HashSet<String>(); + + static { + hangableSig.add("Ljava/util/concurrent/ExecutorService;"); + } + + + private static final Set<String> shutdownMethods = new HashSet<String>(); + + static { + shutdownMethods.add("shutdown"); + shutdownMethods.add("shutdownNow"); + } + + + private final BugReporter bugReporter; + private Map<XField, FieldAnnotation> hangingFieldCandidates; + private Map<XField, Integer> exemptExecutors; + private OpcodeStack stack; + private String methodName; + + private LocalHangingExecutor localHEDetector; + + + + public HangingExecutors(BugReporter reporter) { + this.bugReporter=reporter; + this.localHEDetector = new LocalHangingExecutor(this, reporter); + } + + + /** + * finds ExecutorService objects that don't get a call to the terminating methods, + * and thus, never appear to be shutdown properly (the threads exist until shutdown is called) + * + * @param classContext the class context object of the currently parsed java class + */ + @Override + public void visitClassContext(ClassContext classContext) { + localHEDetector.visitClassContext(classContext); + try { + hangingFieldCandidates = new HashMap<XField, FieldAnnotation>(); + exemptExecutors = new HashMap<XField, Integer>(); + parseFieldsForHangingCandidates(classContext); + + if (!hangingFieldCandidates.isEmpty()) { + stack = new OpcodeStack(); + super.visitClassContext(classContext); + + reportHangingExecutorFieldBugs(); + } + } finally { + stack = null; + hangingFieldCandidates = null; + exemptExecutors = null; + } + + } + + private void parseFieldsForHangingCandidates(ClassContext classContext) { + JavaClass cls = classContext.getJavaClass(); + Field[] fields = cls.getFields(); + for (Field f : fields) { + String sig = f.getSignature(); + if (hangableSig.contains(sig)) { + hangingFieldCandidates.put(XFactory.createXField(cls.getClassName(), f.getName(), f.getSignature(), f.isStatic()), FieldAnnotation.fromBCELField(cls, f)); + } + } + } + + private void reportHangingExecutorFieldBugs() { + for (Entry<XField, FieldAnnotation> entry : hangingFieldCandidates.entrySet()) { + FieldAnnotation fieldAn = entry.getValue(); + if (fieldAn != null) { + bugReporter.reportBug(new BugInstance(this, "HES_EXECUTOR_NEVER_SHUTDOWN", NORMAL_PRIORITY) + .addClass(this) + .addField(fieldAn) + .addField(entry.getKey())); + } + } + } + + /** + * implements the visitor to reset the opcode stack + * + * @param obj the context object of the currently parsed code block + */ + @Override + public void visitCode(Code obj) { + stack.resetForMethodEntry(this); + exemptExecutors.clear(); + if ("<clinit>".equals(methodName) || "<init>".equals(methodName)) + return; + + if (!hangingFieldCandidates.isEmpty()) + super.visitCode(obj); + } + + /** + * implements the visitor to collect the method name + * + * @param obj the context object of the currently parsed method + */ + @Override + public void visitMethod(Method obj) { + methodName = obj.getName(); + } + + /** + * Browses for calls to shutdown() and shutdownNow(), and if they happen, remove + * the hanging candidate, as there is a chance it will be called. + * + * @param seen the opcode of the currently parsed instruction + */ + @Override + public void sawOpcode(int seen) { + try { + stack.precomputation(this); + + if ((seen == INVOKEVIRTUAL) || (seen == INVOKEINTERFACE)) { + String sig = getSigConstantOperand(); + int argCount = Type.getArgumentTypes(sig).length; + if (stack.getStackDepth() > argCount) { + OpcodeStack.Item invokeeItem = stack.getStackItem(argCount); + XField fieldOnWhichMethodIsInvoked = invokeeItem.getXField(); + if (fieldOnWhichMethodIsInvoked != null) { + removeCandidateIfShutdownCalled(fieldOnWhichMethodIsInvoked); + addExemptionIfShutdownCalled(fieldOnWhichMethodIsInvoked); + } + } + } + //TODO Should not include private methods + else if (seen == ARETURN) { + removeFieldsThatGetReturned(); + } + else if (seen == PUTFIELD) { + XField f = getXFieldOperand(); + if ("Ljava/util/concurrent/ExecutorService;".equals(f.getSignature()) && !checkException(f)) { + bugReporter.reportBug(new BugInstance(this, "HES_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN", Priorities.HIGH_PRIORITY) + .addClass(this) + .addMethod(this) + .addField(f) + .addSourceLine(this)); + } + //after it's been replaced, it no longer uses its exemption. + exemptExecutors.remove(f); + } + else if (seen == IFNONNULL) { + OpcodeStack.Item nullCheckItem = stack.getStackItem(0); + XField fieldWhichWasNullChecked = nullCheckItem.getXField(); + if (fieldWhichWasNullChecked != null) { + exemptExecutors.put(fieldWhichWasNullChecked, getPC() + getBranchOffset()); + } + } + } + finally { + stack.sawOpcode(this, seen); + } + } + + + private boolean checkException(XField f) { + if (!exemptExecutors.containsKey(f)) + return false; + int i = exemptExecutors.get(f).intValue(); + + return i == -1 || getPC() < i; + } + + private void removeFieldsThatGetReturned() { + if (stack.getStackDepth() > 0) { + OpcodeStack.Item returnItem = stack.getStackItem(0); + XField field = returnItem.getXField(); + if (field != null) { + hangingFieldCandidates.remove(field); + } + } + } + + private void addExemptionIfShutdownCalled(XField fieldOnWhichMethodIsInvoked) { + String methodBeingInvoked = getNameConstantOperand(); + if (shutdownMethods.contains(methodBeingInvoked)) { + exemptExecutors.put(fieldOnWhichMethodIsInvoked, -1); + } + } + + + private void removeCandidateIfShutdownCalled(XField fieldOnWhichMethodIsInvoked) { + if (hangingFieldCandidates.containsKey(fieldOnWhichMethodIsInvoked)) { + String methodBeingInvoked = getNameConstantOperand(); + if (shutdownMethods.contains(methodBeingInvoked)) { + hangingFieldCandidates.remove(fieldOnWhichMethodIsInvoked); + } + } + } + +} + + +class LocalHangingExecutor extends LocalTypeDetector { + + private static final Map<String, Set<String>> watchedClassMethods = new HashMap<String, Set<String>>(); + private static final Map<String, Integer> syncCtors = new HashMap<String, Integer>(); + static { + Set<String> forExecutors = new HashSet<String>(); + forExecutors.add("newCachedThreadPool"); + forExecutors.add("newFixedThreadPool"); + forExecutors.add("newScheduledThreadPool"); + forExecutors.add("newSingleThreadExecutor"); + + + watchedClassMethods.put("java/util/concurrent/Executors", forExecutors); + + + syncCtors.put("java/util/concurrent/ThreadPoolExecutor", Integer.valueOf(Constants.MAJOR_1_5)); + syncCtors.put("java/util/concurrent/ScheduledThreadPoolExecutor", Integer.valueOf(Constants.MAJOR_1_5)); + } + + private BugReporter bugReporter; + private Detector delegatingDetector; + + public LocalHangingExecutor(Detector delegatingDetector, BugReporter reporter) { + this.bugReporter = reporter; + this.delegatingDetector = delegatingDetector; + } + + @Override + protected Map<String, Integer> getWatchedConstructors() { + return syncCtors; + } + + @Override + protected Map<String, Set<String>> getWatchedClassMethods() { + return watchedClassMethods; + } + + @Override + protected void reportBug(RegisterInfo cri) { + //very important to report the bug under the top, parent detector, otherwise it gets filtered out + bugReporter.reportBug(new BugInstance(delegatingDetector, "HES_LOCAL_EXECUTOR_SERVICE", Priorities.HIGH_PRIORITY) + .addClass(this) + .addMethod(this) + .addSourceLine(cri.getSourceLineAnnotation())); + + + } + @Override + public void visitClassContext(ClassContext classContext) { + super.visi... [truncated message content] |