done all fixed, done report on false positives, wip on true positives

This commit is contained in:
Claudio Maggioni 2023-04-12 13:21:34 +02:00
parent 0dfad5fe1d
commit 2a35025e5a
12 changed files with 179 additions and 122 deletions

View File

@ -3,4 +3,4 @@
set -e
cd /tools/home
/tools/infer/bin/infer -- mvn compile -Drat.skip=true
/tools/infer/bin/infer -- mvn compile test -Drat.skip=true -Danimal.sniffer.skip=true

Binary file not shown.

View File

@ -1 +1 @@
{"run_sequence":[{"date":"2023-04-11 09:38:59.468025+02:00","command":"run","infer_version":{"major":1,"minor":1,"patch":0,"commit":"f9b6f2b"}}],"results_dir_format":"db_filename: infer-out/results.db\ndb_schema: \n CREATE TABLE IF NOT EXISTS procedures\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , attr_kind INTEGER NOT NULL\n , source_file TEXT NOT NULL\n , proc_attributes BLOB NOT NULL\n , cfg BLOB\n , callees BLOB NOT NULL\n )\n ;\n\n CREATE TABLE IF NOT EXISTS source_files\n ( source_file TEXT PRIMARY KEY\n , type_environment BLOB NOT NULL\n , integer_type_widths BLOB\n , procedure_names BLOB NOT NULL\n , freshly_captured INT NOT NULL )\n ;\n\n CREATE TABLE IF NOT EXISTS specs\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , analysis_summary BLOB NOT NULL\n , report_summary BLOB NOT NULL\n )\n ;\n\n CREATE TABLE IF NOT EXISTS model_specs\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , analysis_summary BLOB NOT NULL\n , report_summary BLOB NOT NULL\n )\n ","should_merge_capture":false}
{"run_sequence":[{"date":"2023-04-12 13:04:24.635363+02:00","command":"run","infer_version":{"major":1,"minor":1,"patch":0,"commit":"f9b6f2b"}}],"results_dir_format":"db_filename: infer-out/results.db\ndb_schema: \n CREATE TABLE IF NOT EXISTS procedures\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , attr_kind INTEGER NOT NULL\n , source_file TEXT NOT NULL\n , proc_attributes BLOB NOT NULL\n , cfg BLOB\n , callees BLOB NOT NULL\n )\n ;\n\n CREATE TABLE IF NOT EXISTS source_files\n ( source_file TEXT PRIMARY KEY\n , type_environment BLOB NOT NULL\n , integer_type_widths BLOB\n , procedure_names BLOB NOT NULL\n , freshly_captured INT NOT NULL )\n ;\n\n CREATE TABLE IF NOT EXISTS specs\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , analysis_summary BLOB NOT NULL\n , report_summary BLOB NOT NULL\n )\n ;\n\n CREATE TABLE IF NOT EXISTS model_specs\n ( proc_uid TEXT PRIMARY KEY NOT NULL\n , proc_name BLOB NOT NULL\n , analysis_summary BLOB NOT NULL\n , report_summary BLOB NOT NULL\n )\n ","should_merge_capture":false}

File diff suppressed because one or more lines are too long

View File

@ -1,22 +1,4 @@
#0 DONE
src/main/java/org/apache/commons/lang3/AnnotationUtils.java:72: error: Null Dereference
object returned by `getAllInterfaces(cls)` could be null and is dereferenced at line 72.
70. @Override
71. protected String getShortClassName(final Class<?> cls) {
72. > for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) {
73. if (Annotation.class.isAssignableFrom(iface)) {
74. return "@" + iface.getName();
#1 DONE
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:126: error: Null Dereference
object returned by `getAllInterfaces(cls)` could be null and is dereferenced at line 126.
124. // superclass field.
125. Field match = null;
126. > for (final Class<?> class1 : ClassUtils.getAllInterfaces(cls)) {
127. try {
128. final Field test = class1.getField(fieldName);
#2
#0
src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java:131: error: Null Dereference
object `null` is dereferenced by call to `toString(...)` at line 131.
129. */
@ -25,7 +7,7 @@ src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java:13
132. }
133.
#3 false positive, LONG_TO_INT_RANGE.fit never returns null (may throw NPE when param is null, never the case though as actual param is boxed primitive long)
#1
src/main/java/org/apache/commons/lang3/time/DurationUtils.java:142: error: Null Dereference
object returned by `org.apache.commons.lang3.time.DurationUtils.LONG_TO_INT_RANGE.fit(valueOf(duration.toMillis()))` could be null and is dereferenced at line 142.
140. Objects.requireNonNull(duration, "duration");
@ -34,7 +16,7 @@ src/main/java/org/apache/commons/lang3/time/DurationUtils.java:142: error: Null
143. }
144.
#4 false positive, java.lang.String.toCharArray() always returns a non-null char[] object
#2
src/main/java/org/apache/commons/lang3/CharSetUtils.java:181: error: Null Dereference
object `chars` last assigned on line 177 could be null and is dereferenced at line 181.
179. final char[] chrs = str.toCharArray();
@ -43,7 +25,7 @@ src/main/java/org/apache/commons/lang3/CharSetUtils.java:181: error: Null Derefe
182. buffer.append(chr);
183. }
#5
#3
src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java:223: error: Null Dereference
object `null` is dereferenced by call to `ToStringBuilder(...)` at line 223.
221. */
@ -52,71 +34,51 @@ src/main/java/org/apache/commons/lang3/builder/ToStringBuilder.java:223: error:
224. }
225.
#6 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:341: error: Null Dereference
object `field` last assigned on line 338 could be null and is dereferenced by call to `readStaticField(...)` at line 341.
339. Validate.notNull(field, "Cannot locate field '%s' on %s", fieldName, cls);
340. // already forced access above, don't repeat it here:
341. > return readStaticField(field, false);
342. }
343.
#4
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:347: error: Null Dereference
object `field` last assigned on line 344 could be null and is dereferenced by call to `readStaticField(...)` at line 347.
345. Validate.notNull(field, "Cannot locate field '%s' on %s", fieldName, cls);
346. // already forced access above, don't repeat it here:
347. > return readStaticField(field, false);
348. }
349.
#7 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:385: error: Null Dereference
object `field` last assigned on line 382 could be null and is dereferenced by call to `readStaticField(...)` at line 385.
383. Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
384. // already forced access above, don't repeat it here:
385. > return readStaticField(field, false);
386. }
387.
#5
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:391: error: Null Dereference
object `field` last assigned on line 388 could be null and is dereferenced by call to `readStaticField(...)` at line 391.
389. Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
390. // already forced access above, don't repeat it here:
391. > return readStaticField(field, false);
392. }
393.
#8 DONE
src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java:486: error: Null Dereference
object returned by `primitiveToWrapper(varArgComponentType)` could be null and is dereferenced by call to `newInstance(...)` at line 486.
484. final int varArgLength = args.length - methodParameterTypes.length + 1;
485.
486. > Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
487. // Copy the variadic arguments into the varargs array.
488. System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);
#6
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:605: error: Null Dereference
object `field` last assigned on line 602 could be null and is dereferenced by call to `writeStaticField(...)` at line 605.
603. Validate.notNull(field, "Cannot locate field %s on %s", fieldName, cls);
604. // already forced access above, don't repeat it here:
605. > writeStaticField(field, value, false);
606. }
607.
#9 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:599: error: Null Dereference
object `field` last assigned on line 596 could be null and is dereferenced by call to `writeStaticField(...)` at line 599.
597. Validate.notNull(field, "Cannot locate field %s on %s", fieldName, cls);
598. // already forced access above, don't repeat it here:
599. > writeStaticField(field, value, false);
600. }
601.
#7
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:650: error: Null Dereference
object `field` last assigned on line 647 could be null and is dereferenced by call to `writeField(...)` at line 650.
648. Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
649. // already forced access above, don't repeat it here:
650. > writeField(field, (Object) null, value, false);
651. }
652.
#10 false positive, Validate.notNull(field, ...) throws NPE if field is null thus if the method is call field is non-null
src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java:644: error: Null Dereference
object `field` last assigned on line 641 could be null and is dereferenced by call to `writeField(...)` at line 644.
642. Validate.notNull(field, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
643. // already forced access above, don't repeat it here:
644. > writeField(field, (Object) null, value, false);
645. }
646.
#8
src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java:983: error: Null Dereference
object `classes` last assigned on line 982 could be null and is dereferenced at line 983.
981. final Class<?> mcls = method.getDeclaringClass();
982. final List<Class<?>> classes = getAllSuperclassesAndInterfaces(mcls);
983. > for (final Class<?> acls : classes) {
984. final Method equivalentMethod = (ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes())
985. : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes()));
#11 false positive, getAllSuperclassesAndInterfaces(mcls) is non-null if mcls is non-null, mcls is non-null as java.lang.reflect.Method.getDeclaringClass() is always non-null
src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java:987: error: Null Dereference
object `classes` last assigned on line 986 could be null and is dereferenced at line 987.
985. final Class<?> mcls = method.getDeclaringClass();
986. final List<Class<?>> classes = getAllSuperclassesAndInterfaces(mcls);
987. > for (final Class<?> acls : classes) {
988. final Method equivalentMethod = (ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes())
989. : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes()));
#12
src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java:160: warning: Thread Safety Violation
Read/Write race. Non-private method `MultiBackgroundInitializer.getTaskCount()` reads without synchronization from container `this.childInitializers` via call to `Map.values()`. Potentially races with write in method `MultiBackgroundInitializer.addInitializer(...)`.
Reporting because another access to the same memory occurs on a background thread, although this access may not.
158. int result = 1;
159.
160. > for (final BackgroundInitializer<?> bi : childInitializers.values()) {
161. result += bi.getTaskCount();
162. }
Found 13 issues
Issue Type(ISSUED_TYPE_ID): #
Null Dereference(NULL_DEREFERENCE): 12
Thread Safety Violation(THREAD_SAFETY_VIOLATION): 1
Found 9 issues
Issue Type(ISSUED_TYPE_ID): #
Null Dereference(NULL_DEREFERENCE): 9

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,4 +3,4 @@
set -e
cd /tools/home
/tools/infer/bin/infer -- mvn compile -Drat.skip=true
/tools/infer/bin/infer -- mvn compile test -Drat.skip=true -Danimal.sniffer.skip=true

Binary file not shown.

View File

@ -69,7 +69,7 @@ Assignment 1 -- Software Analysis \\\vspace{0.5cm}
\section{Project selection}
Given that this assignment draws parallels with the class of Software Design and
Modelling of last semester, specifically regarding static analyzers, I choose to
analyze the same project I analyzed in the past with PMD and SonarQube
analyze the same project I analyzed in the past with PMD and SonarQube using
Infer\footnote{\url{https://fbinfer.com/}} to make
for an interesting comparison between static analysis paradigms.
@ -85,8 +85,8 @@ Commons Lang project focuses on classes that would have fitted in the
All the source and test classes are contained within in the package
\textit{org.apache.commons.lang3} or in a sub-package of that package. For the
sake of brevity, this prefix is omitted from now on when mentioning packages and
classes in the project.
sake of brevity, this prefix is omitted from now on when mentioning file paths
and classes in the project.
I choose to analyze version 3.12.0 of the library (i.e.\ the code under the
\textit{git} tag \textit{rel/commons-lang-3.12.0}) because it is the same
@ -94,9 +94,9 @@ version analyzed during the SDM class.
To verify that the project satisfies the 5000 lines of code requirement, I run
the \textit{cloc} tool. Results are shown in table \ref{tab:cloc}. Given the
project has more than 118,000 lines of code, this requirement is satisfied.
project has more than 86,000 lines of Java code, this requirement is satisfied.
\begin{table}[ht]
\begin{table}[H]
\centering
\begin{tabular}{lrrrr}
\toprule
@ -118,8 +118,8 @@ project has more than 118,000 lines of code, this requirement is satisfied.
Total & 518 & 19,270 & 61,280 & 118,526 \\
\bottomrule
\end{tabular}
\caption{Output of the \textit{cloc} tool for the Apache Commons Lang project at
tag \textit{rel/commons-lang-3.12.0} (before fixes are applied).}
\caption{Output of the \textit{cloc} tool for the Apache Commons Lang project
at tag \textit{rel/commons-lang-3.12.0} (before fixes are applied).}
\label{tab:cloc}
\end{table}
@ -132,9 +132,21 @@ The relevant source code to analyze has been copied to the directory
\href{https://gitlab.com/usi-si-teaching/msde/2022-2023/software-analysis/maggioni/assignment-2}{\textit{usi-si-teaching/msde/2022-2023/software-analysis/maggioni/assignment-2}}
\end{center}
on \textit{gitlab.com}. The script \textit{docker-infer.sh} can be ran to
on \textit{gitlab.com}. The script \textit{docker-infer.sh} can be executed to
automatically run the Infer tool using default options through the course
tools docker image \textit{bugcounting/satools:y23}. The analysis outputs
tools docker image \textit{bugcounting/satools:y23}.
The script selects Java 17 to compile the project as this is required to not
make the Animal Sniffer Maven build plugin fail with the message ``This feature
requires ASM7''.
The script executes Infer in Maven capture mode executing the \textit{compile}
and \textit{test} targets while disabling the Apache RAT software license
checker (which fails for this release). Since unit tests are executed, running
the script before and after the warning guided refactoring ensures the fixes I
introduce do not introduce regressions.
The analysis outputs
are located in \textit{before/infer-out/report.txt}.
\section{Results}
@ -143,47 +155,130 @@ Table \ref{tab:infer} shows the results of the analysis performed by Infer
providing comments on true and false positives and the actions taken for each
result.
In total
\begin{table}[]
\begin{table}[H]
\small
\begin{tabular}{@{}llp{1.2cm}p{0.8cm}p{6cm}@{}}
\begin{tabular}{@{}llp{1.2cm}p{0.65cm}p{6cm}@{}}
\toprule
\textbf{File} & \textbf{Line} & \textbf{Kind} & \textbf{True Pos.} &
\textbf{Comment} \\ \midrule
AnnotationUtils.java & 72 & Null & Yes & -- \\
reflect/MethodUtils.java & 486 & Null & Yes & -- \\
reflect/FieldUtils.java & 126 & Null & Yes & -- \\
concurrent/MultiBackgroundInitializer.java & 160 & Thread Safety & Yes & -- \\
\textbf{File} & \textbf{Line} & \textbf{Kind} & \textbf{True Pos.} &
\textbf{Reason why flagged expression is a false positive} \\ \midrule
AnnotationUtils.java & 72 & Null & Yes & \multirow{4}{6cm}{--} \\
reflect/MethodUtils.java & 486 & Null & Yes & \\
reflect/FieldUtils.java & 126 & Null & Yes & \\
concurrent/MultiBackgroundInitializer.java & 160 & Thread Safety & Yes & \\
\midrule
builder/ToStringBuilder.java & 223 & Null & ?? & \multirow{2}{6cm}{??} \\
builder/ToStringBuilder.java & 223 & Null & No & \multirow{2}{6cm}{Infer flags
the value \texttt{null} when used as a nullable method argument} \\
builder/ReflectionToStringBuilder.java & 131 & Null & No & \\
\midrule
time/DurationUtils.java & 142 & Null & No & The method which may return a null
value returns a non-null value if its parameter is non-null, and a
non-null parameter is given \\
value returns a non-null value if its parameter is non-null, and a
non-null parameter is given \\
\midrule
CharSetUtils.java & 181 & Null & No & According to \textit{java.lang} documentation, the
method always returns a non-null value \\
CharSetUtils.java & 181 & Null & No & According to \textit{java.lang}
documentation, the method always returns a non-null value \\
\midrule
reflect/FieldUtils.java & 341 & Null & No &
\multirow{4}{6cm}{a utility method is used to guard the dereference reported
with an exception throw} \\
reflect/FieldUtils.java & 385 & Null & No & \\
reflect/FieldUtils.java & 599 & Null & No & \\
reflect/FieldUtils.java & 644 & Null & No & \\
\multirow{4}{6cm}{A utility method is used to guard the dereference reported
with an exception throw} \\
reflect/FieldUtils.java & 385 & Null & No & \\
reflect/FieldUtils.java & 599 & Null & No & \\
reflect/FieldUtils.java & 644 & Null & No & \\
\midrule
reflect/MethodUtils.java & 987 & Null & No & The method which may return a null
value returns a non-null value if its parameter is non-null, and a
non-null parameter is always given according to the \textit{java.lang}
documentation for the inner nested method \\ \bottomrule
value returns a non-null value if its parameter is non-null, and a
non-null parameter is always given according to the \textit{java.lang}
documentation for the inner nested method \\ \bottomrule
\end{tabular}
\caption{Results of the Infer static analysis tool execution with default
options. \textit{True Pos.} denotes whether a result is a true positive,
while \textit{Kind} denotes with \textit{Null} and \textit{Thread Safety}
respectively null dereference issues and thread safety violations.}
respectively null dereference warnings and thread safety violations.}
\label{tab:infer}
\end{table}
In total Infer reports 13 warnings, 12 of which are null dereference warnings
and 1 is a thread safety violation. Of all warnings, 4 are true positives and 9
are false positives, resulting in a false positive ratio of over 69\%. These
values are summarized in table \ref{tab:num}.
\begin{table}[H]
\centering
\begin{tabular}{@{}ll@{}}
\toprule
Total number of warnings: & 13 \\ \midrule
Null dereference warnings: & 12 \\
Thread safety violations: & 1 \\ \midrule
True positives: & 4 \\
False positives: & 9 \\ \bottomrule
\end{tabular}
\caption{Quantitative results of the static analysis performed by Infer.}
\label{tab:num}
\end{table}
\subsection{False Positives}
As can be deduced from table \ref{tab:infer}, Infer especially struggles at
determining Vthe null safety of nested method calls or non-trivial data flow
paths. This is sometimes caused by insufficient knowledge of the nullability
contracts of the Java standard library (e.g. \textit{java.lang} package).
In other cases, the flagged expression is guarded by an utility method throwing
an exception if its value is indeed \texttt{null}. One of such guards is the
\mintinline{java}{static Validate.notNull(Object, String, Object...)} method,
which checks if its first argument is null and throws a
\texttt{NullPointerException} if so.
Additionally, Infer seems to struggle with boxed primitives and not
understanding that their value is always non-null by construction. As an example
I provide the warning reported in the file \textit{time/DurationUtils.java}:
\begin{listing}[H]
\begin{minted}[linenos,firstnumber=139]{java}
public static int toMillisInt(final Duration duration) {
Objects.requireNonNull(duration, "duration");
// intValue() does not do a narrowing conversion here
return LONG_TO_INT_RANGE.fit(Long.valueOf(duration.toMillis())).intValue();
}
\end{minted}
\caption{Method \textit{toMillisInt(Duration)} of class
\textit{time.DurationUtils} in Apache Commons Lang 3.12.0.}
\end{listing}
Here Infer reports that the first argument of
\mintinline{java}{LONG_TO_INT_RANGE.fit(Long)} may be null. However, the return
value of \mintinline{java}{Long.valueOf(long)} is always non-null since the
method simply boxes its \mintinline{java}{long} argument.
Finally, some warnings are caused by Infer flagging the use of the \texttt{null}
keyword as a method argument for methods that would accept a nullable argument
in that position without causing null dereferences. This warning could point to
a potential ambiguity is selecting the right method to call at
compile time given the argument types (i.e. static dispatching), as
\texttt{null} is a valid expression of all object types. For example, in the
given class:
\begin{minted}{java}
class C {
m(String s) {}
m(Object o) {}
}
\end{minted}
A call to \mintinline{java}{C.m(null)} would be ambiguous as \texttt{null} is
both a \texttt{String} and an \texttt{Object}, thus a cast to either type woule
be required to make the code compile. However, the warnings reported by Infer do
not present such ambiguity as in those cases overloaded methods have different
numbers of parameters. Additionally, even introducing explicit casts for all
\texttt{null} arguments does not extinguish the warning. Therefore, I can not
find a conclusive explaination on the nature of the false positive, however I
can attest to these instances being indeed false positives by having manually
verified that the methods in question indeed can accept a null value without
causing null dereferences.
\subsection{True Positives}
In this section I now cover the warnings that are true positives and thus are
causes for refactoring.
\end{document}