-
Notifications
You must be signed in to change notification settings - Fork 116
Fuzzing a Compiler
This tutorial walks through the process of fuzzing a non-trivial application: the Google Closure Compiler. The closure compiler optimizes JavaScript source code; therefore, we will write a generator for strings that produces syntactically valid inputs.
Unlike the Fuzzing with Zest tutorial, we will use the JQF Maven Plugin to invoke the fuzzing engine via Apache Maven. We will also use Maven to resolve build and test dependencies.
This tutorial assumes you know how to write a simple Generator<T>
for any T
, how to write a JQF test driver with @Fuzz
annotations, and how to interpret the status screen when running the Zest algorithm. If not, check out the tutorial on Fuzzing with Zest.
You will need Java 8+ and Apache Maven 3.5+.
Save the following file as pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>examples</groupId>
<artifactId>zest-tutorial</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- Google Closure: we want to test this library -->
<dependency>
<groupId>com.google.javascript</groupId>
<artifactId>closure-compiler</artifactId>
<version>v20180204</version>
<scope>test</scope>
</dependency>
<!--
JQF: test dependency for @Fuzz annotation
-->
<dependency>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-fuzz</artifactId>
<!-- confirm the latest version at: https://mvnrepository.com/artifact/edu.berkeley.cs.jqf -->
<version>1.0-beta-1</version>
<scope>test</scope>
</dependency>
<!--
JUnit-QuickCheck: API to write generators
-->
<dependency>
<groupId>com.pholser</groupId>
<artifactId>junit-quickcheck-generators</artifactId>
<version>0.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- The JQF plugin, for invoking the command `mvn jqf:fuzz` -->
<plugin>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-maven-plugin</artifactId>
<!-- confirm the latest version at: https://mvnrepository.com/artifact/edu.berkeley.cs.jqf -->
<version>1.0-beta-1</version>
</plugin>
</plugins>
</build>
</project>
Create the following file as src/test/java/examples/CompilerTest.java
:
package examples;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;
import edu.berkeley.cs.jqf.fuzz.Fuzz;
import edu.berkeley.cs.jqf.fuzz.JQF;
import org.junit.Before;
import org.junit.runner.RunWith;
import static org.junit.Assume.*;
@RunWith(JQF.class)
public class CompilerTest {
static {
// Disable all logging by Closure passes, to speed fuzzing
java.util.logging.LogManager.getLogManager().reset();
}
private Compiler compiler = new Compiler(new PrintStream(new ByteArrayOutputStream(), false));
private CompilerOptions options = new CompilerOptions();
private SourceFile externs = SourceFile.fromCode("externs", "");
@Before
public void initCompiler() {
// Don't use threads
compiler.disableThreads();
// Don't print things
options.setPrintConfig(false);
// Enable all safe optimizations
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
}
private Result compile(SourceFile input) {
Result result = compiler.compile(externs, input, options);
assumeTrue(result.success); // Semantic validity check
return result;
}
@Fuzz
public void testWithString(String code) {
SourceFile input = SourceFile.fromCode("input", code);
compile(input);
}
}
The method annotated with @Fuzz will be the entry point to the fuzzing engine. The rest of the methods are primarily set-up code to initialize the Google closure compiler and set some of its configuration options.
We now fuzz using the JQF Maven Plugin, as follows:
mvn jqf:fuzz -Dclass=examples.CompilerTest -Dmethod=testWithString
Zest: Validity Fuzzing with Parametric Generators
----------------------------------------------
Test name: examples.CompilerTest#testWithString
Results directory: /path/to/tutorial/target/fuzz-results/examples.CompilerTest/testWithString
Elapsed time: 5m 0s (no time limit)
Number of executions: 34,312
Valid inputs: 12,414 (36.18%)
Cycles completed: 0
Unique failures: 0
Queue size: 221 (0 favored last cycle)
Current parent input: 120 (favored) {735/740 mutations}
Execution speed: 85/sec now | 114/sec overall
Total coverage: 4,615 (7.04% of map)
Valid coverage: 4,247 (6.48% of map)
After fuzzing for a while, you'll notice some modest code coverage (we saw ~4,600 branches in 5 minutes), and no failures found. This is because by default, the string inputs to the testWithString
method are generated at random, without any knowledge of the syntax of JavaScript. Most of these inputs lead to execution paths corresponding to syntax errors. Although Zest tries its best to bias input generation towards semantic validity, most of these inputs will be trivially valid (e.g. 1+1
or x
).
TODO: This article is under construction
The source code examples in the wiki pages can be freely re-used under the same license as the rest of JQF.