Overview
OPAL is an OPen, extensible Analysis Library for Java bytecode which is written in Scala and which consist of multiple projects which build on top of each other. At the core is a newly developed, highly configurable and adaptable bytecode toolkit. On top of that we provide you with a framework for the abstract interpretation of Java Bytecode. These two frameworks are the foundation for various tools targeted towards developers who want to improve the quality of their software. In particular tools forTools
BugPicker
The BugPicker is a tool for finding data-flow dependent issues in your source code.Software Quality Assurance using BugPicker
BugPicker helps you to identify data-flow dependent bugs and issues in your source code and was successfully used to identify numerous issues in industrial-strength, mature source code.
First Example
One trivial issue found in the Open JDK 8/9:public int getGlyphCharIndex(int ix) {
if (ix < 0 && ix >= glyphs.length) {
throw new IndexOutOfBoundsException("" + ix);
}
...
}
In this case the condition will never evaluate to true
and, hence, the IndexOutOtBoundsException
will never be thrown.Here, the developer probably wanted to use the logical or operator (
||
) instead of the logical and operator (&&
).
Second Example
A more complex example found in the Open JDK 8://com.sun.imageio.plugins.png.PNGMetadata.mergeStandardTree(org.w3c.dom.Node)
if (maxBits > 4 || maxBits < 8) {
maxBits = 8;
}
if (maxBits > 8) {
maxBits = 16;
}
Here, the developer probably wanted to use the logical and operator (&&
) in the first line instead of the logical or operator (||
) and also wanted to write maxBits <= 8
. However, given the current code the condition of the first if statement will always evaluate to true
and, hence, the condition of the second if statement will always evaluate to false
. That is, maxBits
will always be 8.
Read more...
The Core Frameworks
Bytecode Representation
OPAL's native Java bytecode representation makes it easy to write concise, expressive and powerful analyses.
Building on top of Scala, OPAL provides an API that offers extensive support for pattern matching on Java bytecode. This greatly facilitates writing analyses that identify typical bug patterns.
For example, let's write an analysis to find instances of the following issue:
The complete(!) analysis to find the described bug pattern is shown next.
Read more about OPAL's bytecode representation...
double value = 1.0d;
//...
int i = (new Double(value)).intValue // << the issue
//Intended: int i = (int) value
I.e., let's find code where we wrap a primitive value and immediately unwrap it. (Such code generally hinders comprehensibility and wastes CPU cycles;
a real instance can be found in the OpenJDK 8.)The complete(!) analysis to find the described bug pattern is shown next.
import org.opalj.br._
import org.opalj.br.instructions._
import org.opalj.br.reader.Java8Framework.ClassFiles
val project = ClassFiles(new java.io.File("/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib"))
val theMethods = Set(
"booleanValue","byteValue","charValue","shortValue",
"intValue","longValue","floatValue","doubleValue")
for {
classFile ← project.par.map(_._1) // for all classes (let's do it in parallel)
method @ MethodWithBody(body) ← classFile.methods // for all non-abstract, non-native methods
pc ← body.matchPair({ // find a sequence of two instructions where...
case (
INVOKESPECIAL(receiver1, _, TheArgument(parameterType: BaseType)),
INVOKEVIRTUAL(receiver2, name, NoArgumentMethodDescriptor(returnType: BaseType))
) ⇒ { (receiver1 eq receiver2) &&
receiver1.isPrimitiveTypeWrapper &&
theMethods.contains(name) }
case _ ⇒ false
})
} println (classFile.fqn +"{"+ method.toJava+"::"+ method.body.get.lineNumber(pc)+"}")
You can paste the analysis in the Scala REPL to directly execute it. In this case, you'll get – for the OpenJDK 8 – the following result:
com/sun/org/apache/xalan/internal/lib/ExsltMath{double constant(java.lang.String,double)::Some(379)}On a notebook (Core i7, 2,3 GHz, 8GB, SSD) loading the JDK takes 2 to 3 seconds, the analysis itself takes roughly 0.04 seconds for the entire JDK.
Read more about OPAL's bytecode representation...
Abstract Interpretation
OPAL implements a scalable, highly customizable framework for the abstract interpretation of Java bytecode.
The abstract interpretation framework is an easily useable and also extensible framework that greatly facilitates the development of static analyses that require data-flow information.
For example, let's assume that we want to develop an analysis that finds conditions (e.g., a < b
) that always result in the same value (i.e., we can statically determine that the condition is always true
or false
).
if (maxBits > 4 || maxBits < 8) {
maxBits = 8;
}
if (maxBits > 8) { // [Identified:] always evaluates to false
maxBits = 16;
}
In this case, the developer probably wanted to use the && operator in the first line. However, the condition of the second if
will always evaluate to false.
The complete code of the analysis is shown next and demonstrates the most basic usage of the abstract interpretation framework. The idea is to iterate over all methods and to perform a simple, abstract interpretation of each method. After the abstract interpretation check for each condition if it always compares two simple values and – if so – reports that case.
package org.opalj
package ai
import java.net.URL
import org.opalj.br._
import org.opalj.br.analyses._
import org.opalj.br.instructions.IFICMPInstruction
object UselessComputationsMinimal extends AnalysisExecutor with Analysis[URL, BasicReport] {
val analysis = this
class AnalysisDomain(val project: Project[URL], val method: Method)
extends Domain
with domain.DefaultDomainValueBinding
with domain.DefaultHandlingOfMethodResults
with domain.IgnoreSynchronization
with domain.ThrowAllPotentialExceptionsConfiguration
with domain.l0.DefaultTypeLevelFloatValues
with domain.l0.DefaultTypeLevelDoubleValues
with domain.l0.TypeLevelFieldAccessInstructions
with domain.l0.TypeLevelInvokeInstructions
with domain.l1.DefaultReferenceValuesBinding
with domain.l1.DefaultIntegerRangeValues
with domain.l1.DefaultLongValues
with domain.l1.DefaultConcretePrimitiveValuesConversions
with domain.l1.LongValuesShiftOperators
with domain.TheProject[java.net.URL]
with domain.TheMethod
with domain.ProjectBasedClassHierarchy
def analyze(theProject: Project[URL], parameters: Seq[String]) = {
val results = (for {
clazz ← theProject.classFiles.par
method @ MethodWithBody(body) ← clazz.methods
result = BaseAI(clazz, method, new AnalysisDomain(theProject, method))
} yield {
import result.domain.ConcreteIntegerValue
collectWithOperandsAndIndex(result.domain)(body, result.operandsArray) {
case (
pc, _: IFICMPInstruction, Seq(ConcreteIntegerValue(a), ConcreteIntegerValue(b), _*)
) ⇒
s"${clazz.thisType.toJava}{${method.toJava}{/*pc=$pc:*/ useless comp.: $a and $b}}"
}
}).flatten.seq
BasicReport(results.mkString(s"${results.size} Useless code:\n", "\n", "\n"))
}
}
Read more...
Sourcecode Dependencies
OPAL supports the extraction of dependencies between source code elements to facilitate the writing of software quality tools.Dependencies between the top-level packages of the OpenJDK 8.
If you move your mouse over a connection you'll get further information about the direction of the dependencies. Read more...Goal
The overall goal of OPAL is to provide a library and tools that help to improve- OPAL provides ready-to-use, open-source software tools that leverage newest research results.
- OPAL provides a library for researchers and developers alike to implement cutting-edge static analyses.
Wanted
We are always searching for motivated individuals who want to develop cool static analyses.We are in particular looking for
If you want to contribute to OPAL contact me or come to my office TU Darmstadt, Building S2/02, Software Technology Group, Room A206.