report done
This commit is contained in:
parent
9511c2303d
commit
b9ad3e3509
3 changed files with 190 additions and 30 deletions
|
@ -473,11 +473,10 @@ static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameter
|
||||||
|
|
||||||
// Construct a new array for the variadic parameters
|
// Construct a new array for the variadic parameters
|
||||||
final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
|
final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
|
||||||
final int varArgLength = args.length - methodParameterTypes.length + 1;
|
|
||||||
|
|
||||||
if (varArgComponentType == null) {
|
if (varArgComponentType == null) {
|
||||||
throw new IllegalArgumentException("last method parameter type is not an array");
|
throw new IllegalArgumentException("last method parameter type is not an array");
|
||||||
}
|
}
|
||||||
|
final int varArgLength = args.length - methodParameterTypes.length + 1;
|
||||||
|
|
||||||
Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
|
Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
|
||||||
// Copy the variadic arguments into the varargs array.
|
// Copy the variadic arguments into the varargs array.
|
||||||
|
|
BIN
report.pdf
BIN
report.pdf
Binary file not shown.
217
report.tex
217
report.tex
|
@ -159,25 +159,24 @@ result.
|
||||||
\textbf{File} & \textbf{Line} & \textbf{Kind} & \textbf{True Pos.} &
|
\textbf{File} & \textbf{Line} & \textbf{Kind} & \textbf{True Pos.} &
|
||||||
\textbf{Reason why flagged expression is a false positive} \\ \midrule
|
\textbf{Reason why flagged expression is a false positive} \\ \midrule
|
||||||
AnnotationUtils.java & 72 & Null & Yes & \\
|
AnnotationUtils.java & 72 & Null & Yes & \\
|
||||||
reflect/FieldUtils.java & 126 & Null & Yes & \\
|
reflect/MethodUtils.java & 486 & Null & Yes & \\
|
||||||
concurrent/MultiBackgroundInitializer.java & 160 & Thread Safety & Yes & \\
|
concurrent/MultiBackgroundInitializer.java & 160 & Thread Safety & Yes & \\
|
||||||
\midrule
|
\midrule
|
||||||
builder/ToStringBuilder.java & 223 & Null & No & \multirow{2}{6cm}{Infer flags
|
builder/ToStringBuilder.java & 223 & Null & No & \multirow{2}{6cm}{Infer flags
|
||||||
the value \texttt{null} when used as a nullable method argument} \\
|
the value \texttt{null} when used as a nullable method argument} \\
|
||||||
builder/ReflectionToStringBuilder.java & 131 & Null & No & \\
|
builder/ReflectionToStringBuilder.java & 131 & Null & No & \\
|
||||||
\midrule
|
\midrule
|
||||||
time/DurationUtils.java & 142 & Null & No & \multirow{3}{6.2cm}{The method which
|
time/DurationUtils.java & 142 & Null & No & The method which
|
||||||
may return a null value returns a non-null value if its parameter is
|
may return a null value returns a non-null value if its parameter is
|
||||||
non-null, and a non-null parameter is given} \\
|
non-null, and a non-null parameter is given \\
|
||||||
reflect/MethodUtils.java & 486 & Null & No & \\
|
|
||||||
\\ \hspace{1cm} \\
|
|
||||||
\midrule
|
\midrule
|
||||||
CharSetUtils.java & 181 & Null & No & According to \textit{java.lang}
|
CharSetUtils.java & 181 & Null & No & According to \textit{java.lang}
|
||||||
documentation, the method always returns a non-null value \\
|
documentation, the method always returns a non-null value \\
|
||||||
\midrule
|
\midrule
|
||||||
reflect/FieldUtils.java & 341 & Null & No &
|
reflect/FieldUtils.java & 126 & Null & No &
|
||||||
\multirow{4}{6cm}{A utility method is used to guard the dereference reported
|
\multirow{5}{6cm}{A utility method is used to guard the dereference reported
|
||||||
with an exception throw} \\
|
with an exception throw} \\
|
||||||
|
reflect/FieldUtils.java & 341 & Null & No & \\
|
||||||
reflect/FieldUtils.java & 385 & Null & No & \\
|
reflect/FieldUtils.java & 385 & Null & No & \\
|
||||||
reflect/FieldUtils.java & 599 & Null & No & \\
|
reflect/FieldUtils.java & 599 & Null & No & \\
|
||||||
reflect/FieldUtils.java & 644 & Null & No & \\
|
reflect/FieldUtils.java & 644 & Null & No & \\
|
||||||
|
@ -279,37 +278,199 @@ causing null dereferences.
|
||||||
In this section I now cover the warnings that are true positives and thus are
|
In this section I now cover the warnings that are true positives and thus are
|
||||||
causes for refactoring.
|
causes for refactoring.
|
||||||
|
|
||||||
\begin{center}
|
A copy of the entire code base where refactoring has been applied can be found
|
||||||
\color{red}
|
in the \textit{after} directory of the assignment's repository.
|
||||||
- field utils violation of contract
|
|
||||||
|
|
||||||
- missing synchronized in multibackground
|
\begin{listing}[H]
|
||||||
|
\begin{minted}[linenos,firstnumber=71]{java}
|
||||||
|
@Override
|
||||||
|
protected String getShortClassName(final Class<?> cls) {
|
||||||
|
for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) {
|
||||||
|
if (Annotation.class.isAssignableFrom(iface)) {
|
||||||
|
return "@" + iface.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.EMPTY;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Method \textit{getShortClassName(Class<?>)} of class
|
||||||
|
\textit{AnnotationUtils} in Apache Commons Lang 3.12.0.}
|
||||||
|
\label{lst:ann}
|
||||||
|
\end{listing}
|
||||||
|
|
||||||
- Annotation utils protected method no contract on field fixed by returning
|
Listing \ref{lst:ann} shows the location of the first true positive warning.
|
||||||
"empty" value
|
Here, \mintinline{java}{ClassUtils.getAllInterfaces(cls)} at line 73 may
|
||||||
|
return a null value thus potentially causing a null dereference when iterating
|
||||||
|
in the enhanced for loop. Indeed, the method's implementation shows that while
|
||||||
|
for non-null \texttt{cls} values the return value is non-null, if \texttt{cls}
|
||||||
|
is null \texttt{null} is returned. Since the method is protected its signature
|
||||||
|
is visible to all child classes, even ones potentially outside of the Apache
|
||||||
|
Commons Lang library. As the method's documentation does not specify anything
|
||||||
|
about the nullability of the argument \texttt{cls}, its value may indeed be null
|
||||||
|
and thus a refactor is needed to avoid the potential null dereference.
|
||||||
|
|
||||||
- no additional warnings appear after fix
|
I choose to simply return \mintinline{java}{StringUtils.EMPTY} when \texttt{cls}
|
||||||
|
is null. The refactored implementation can be found in listing \ref{lst:ann2}.
|
||||||
|
|
||||||
- cool that a contract violation has been found
|
\begin{listing}[H]
|
||||||
|
\begin{minted}[linenos,firstnumber=71]{java}
|
||||||
|
@Override
|
||||||
|
protected String getShortClassName(final Class<?> cls) {
|
||||||
|
final List<Class<?>> interfaces = ClassUtils.getAllInterfaces(cls);
|
||||||
|
if (interfaces == null) {
|
||||||
|
return StringUtils.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
- tests pass after fixes so no regression
|
for (final Class<?> iface : interfaces) {
|
||||||
\end{center}
|
if (Annotation.class.isAssignableFrom(iface)) {
|
||||||
|
return "@" + iface.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.EMPTY;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Refactor of method \textit{getShortClassName(Class$<$?$>$)} of
|
||||||
|
class \textit{AnnotationUtils}.}
|
||||||
|
\label{lst:ann2}
|
||||||
|
\end{listing}
|
||||||
|
|
||||||
|
Next, class \textit{reflect.MethodUtils} contains a bug due to insufficient
|
||||||
|
defensiveness when accepting the method parameters.
|
||||||
|
|
||||||
|
\begin{listing}[H]
|
||||||
|
\small
|
||||||
|
\begin{minted}[linenos,firstnumber=461,breaklines]{java}
|
||||||
|
static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {
|
||||||
|
if (args.length == methodParameterTypes.length && (args[args.length - 1] == null ||
|
||||||
|
args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) {
|
||||||
|
// The args array is already in the canonical form for the method.
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a new array matching the method's declared parameter types.
|
||||||
|
final Object[] newArgs = new Object[methodParameterTypes.length];
|
||||||
|
|
||||||
|
// Copy the normal (non-varargs) parameters
|
||||||
|
System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);
|
||||||
|
|
||||||
|
// Construct a new array for the variadic parameters
|
||||||
|
final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
|
||||||
|
final int varArgLength = args.length - methodParameterTypes.length + 1;
|
||||||
|
|
||||||
|
Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
|
||||||
|
// Copy the variadic arguments into the varargs array.
|
||||||
|
System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);
|
||||||
|
|
||||||
|
if (varArgComponentType.isPrimitive()) {
|
||||||
|
// unbox from wrapper type to primitive type
|
||||||
|
varArgsArray = ArrayUtils.toPrimitive(varArgsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the varargs array in the last position of the array to return
|
||||||
|
newArgs[methodParameterTypes.length - 1] = varArgsArray;
|
||||||
|
|
||||||
|
// Return the canonical varargs array.
|
||||||
|
return newArgs;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Method \textit{getVarArgs(Object[], Class$<$?$>$[])} of class
|
||||||
|
\textit{reflect.MethodUtils} in Apache Commons Lang 3.12.0.}
|
||||||
|
\label{lst:met}
|
||||||
|
\end{listing}
|
||||||
|
|
||||||
|
Line 482 of the class (listed in listing \ref{lst:met}) is correctly flagged as
|
||||||
|
a potential null dereference as \texttt{varArgComponentType} may be null.
|
||||||
|
Indeed, according to the documentation of the method generating its value, the
|
||||||
|
variable is null when the last value in the array \texttt{methodParameterTypes}
|
||||||
|
is a class object for a non-array class. Such value would violate the method's
|
||||||
|
contract, which specifies that the array should correspond to class types for
|
||||||
|
the arguments of a variadic method, and the last argument in a variadic method
|
||||||
|
is always an array. However, this is not checked defensively, and we can fix
|
||||||
|
this by introducing a check after line 475 like this:
|
||||||
|
|
||||||
|
\begin{minted}{java}
|
||||||
|
if (varArgComponentType == null) {
|
||||||
|
throw new IllegalArgumentException("last method parameter type is not an array");
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
It is quite interesting Infer was able to detect this null dereference as it
|
||||||
|
stems from a lack of sufficient defensiveness. Improper use of the method would
|
||||||
|
trigger a \textit{NullPointerException} without any useful message indicating
|
||||||
|
the precondition violation.
|
||||||
|
|
||||||
|
Finally, the last warning involves class
|
||||||
|
\textit{concurrent.MultiBackgroundInitializer}. The relevant method source code
|
||||||
|
is shown in listing \ref{lst:mbi}.
|
||||||
|
|
||||||
|
\begin{listing}[H]
|
||||||
|
\begin{minted}[linenos,firstnumber=157]{java}
|
||||||
|
@Override
|
||||||
|
protected int getTaskCount() {
|
||||||
|
int result = 1;
|
||||||
|
|
||||||
|
for (final BackgroundInitializer<?> bi : childInitializers.values()) {
|
||||||
|
result += bi.getTaskCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Method \textit{getVarArgs(Object[], Class$<$?$>$[])} of class
|
||||||
|
\textit{reflect.MethodUtils} in Apache Commons Lang 3.12.0.}
|
||||||
|
\label{lst:met}
|
||||||
|
\end{listing}
|
||||||
|
|
||||||
|
Here, the object pointed by \texttt{childInitializers} is accessed without using
|
||||||
|
any lock, while other methods of the class use the implicit lock of the instance
|
||||||
|
(i.e. \mintinline{java}{synchronized(this)}). Simply adding a
|
||||||
|
\mintinline{java}{synchronized} block around the for loop extinguishes the
|
||||||
|
warning.
|
||||||
|
|
||||||
|
The refactoring performed for all three warnings has not produced further
|
||||||
|
warnings, and it has not caused the failure of any unit test.
|
||||||
|
|
||||||
\section{Conclusions}
|
\section{Conclusions}
|
||||||
|
|
||||||
\begin{center}
|
Given that Apache Commons Lang is a very mature project (the code still contains
|
||||||
\color{red}
|
workarounds targeting Java 1.3) it is not surprising that Infer has detected a
|
||||||
- project is mature, few detections but still a couple for null values which
|
relatively limited number of warnings. If anything this should be a good
|
||||||
shows how insidious they are. (Null and thread safety are popular and
|
testament to the skills and discipline of all project contributors. However, 3
|
||||||
relevant to the project)
|
true positives were found in this production-ready release version. This is a
|
||||||
|
testament to the insidiousness of some programming mistakes like lack of
|
||||||
|
defensiveness when dealing with possibly null values and improper use of lock
|
||||||
|
constructs, a fact which applies to this project as well.
|
||||||
|
|
||||||
- infer compared to pmd gives warnings about potential execution paths (data
|
The high false positive ratio of the analysis highlights the tradeoff between
|
||||||
flow analysis) while pmd is style only and bound to the language
|
soundness and completeness software analysis tecniques face. Given that the true
|
||||||
|
positives were quite non-trivial, nevertheless Infer proves itself as a useful
|
||||||
|
tool even with mature and production-ready software.
|
||||||
|
|
||||||
- sonarqube kind of covers some ground infer does. it combines the powers of
|
To draw some conclusions between this analysis and the one performed last
|
||||||
pmd (patterns common to the language to avoid/pursue) with some data flow
|
semester during the SDM class, I hereby compare the current results with the
|
||||||
analysis (some nullability check). No overlap with Infer results
|
ones formerly found.
|
||||||
\end{center}
|
|
||||||
|
When compared to the PMD source code analyzer and SonarQube, Infer reports
|
||||||
|
issues closer to external quality factors (i.e. user satisfaction, and
|
||||||
|
consequently runtime behaviour) rather than internal quality ones, such as the
|
||||||
|
adherence to code style rules and presence of adequate documentation.
|
||||||
|
|
||||||
|
Some
|
||||||
|
overlap is present in the scope of the analysis of both tools, namely with
|
||||||
|
respect to thread safety (and null dereference with SonarQube). It is worth to
|
||||||
|
note that all three tool reported warnings with distinct locations in the source
|
||||||
|
code, and no overlap was found between them.
|
||||||
|
|
||||||
|
However, due to
|
||||||
|
its control and data flow analysis capabilities, Infer is a superior tool to
|
||||||
|
understand and check possible runtime behavior while analyzing only the source
|
||||||
|
code.
|
||||||
|
|
||||||
|
We can also say both PMD and SonarQube have superior awareness regarding the
|
||||||
|
behaviour of Java and its standard library. For example, they are aware of
|
||||||
|
boxing and unboxing of primitive types (and SonarQube even produces warnings
|
||||||
|
about misuse of the feature), where as hinted by this analysis Infer seems not
|
||||||
|
to be aware of, at least while searching null dereferences.
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
||||||
|
|
||||||
|
|
Reference in a new issue