Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/apidocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ jobs:
restore-keys: |
${{ runner.os }}-maven-

# Aggregate every library module. Excluding examples and the Maven plugin keeps the
# generators (quickcheck, instancio, jetcheck) and the JUnit 4/5 adapters in, and picks
# up any future module automatically.
- name: Build JavaDoc
run: "mvn -B javadoc:aggregate -pl :jqf-instrument,:jqf-fuzz,:jqf"
run: "mvn -B -ntp javadoc:aggregate -pl '!:jqf-examples,!:jqf-maven-plugin'"

- name: Deploy to gh-pages
uses: JamesIves/github-pages-deploy-action@4.1.4
Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[ICSE'21 paper]: https://rohan.padhye.org/files/bonsai-icse21.pdf
[ISSTA'23 paper]: https://dx.doi.org/10.1145/3597926.3598107

JQF is a feedback-directed fuzz testing platform for Java (think: AFL/LibFuzzer but for JVM bytecode). JQF uses the abstraction of *property-based testing*, which makes it nice to write fuzz drivers as parameteric JUnit test methods. JQF is built on top of [junit-quickcheck](https://github.com/pholser/junit-quickcheck). JQF enables running junit-quickcheck style parameterized unit tests with the power of **coverage-guided** fuzzing algorithms such as **Zest**.
JQF is a feedback-directed fuzz testing platform for Java (think: AFL/LibFuzzer but for JVM bytecode). JQF uses the abstraction of *property-based testing*, which makes it nice to write fuzz drivers as parameteric JUnit test methods. Fuzz tests run under **JUnit 4** or **JUnit 5**, and test arguments are produced by a pluggable generator provider ([junit-quickcheck](https://github.com/pholser/junit-quickcheck) by default); the engine itself depends on neither. JQF enables running these parameterized unit tests with the power of **coverage-guided** fuzzing algorithms such as **Zest**.

[Zest][ISSTA'19 paper] is an algorithm that biases coverage-guided fuzzing towards producing *semantically valid* inputs; that is, inputs that satisfy structural and semantic properties while maximizing code coverage. Zest's goal is to find deep semantic bugs that cannot be found by conventional fuzzing tools, which mostly stress error-handling logic only. By default, JQF runs Zest via the simple command: `mvn jqf:fuzz`.

Expand Down Expand Up @@ -53,10 +53,12 @@ A `Generator<T>` provides a method for producing random instances of type `T`. F
[JavaScript programs](examples/src/main/java/edu/berkeley/cs/jqf/examples/js/JavaScriptCodeGenerator.java),
[JVM class files](examples/src/main/java/edu/berkeley/cs/jqf/examples/bcel/JavaClassGenerator.java), SQL queries, HTTP requests, and [many more](https://github.com/pholser/junit-quickcheck/tree/master/examples/src/test/java/com/pholser/junit/quickcheck/examples) -- this is **generator-based fuzzing**. However, simply sampling random inputs of type `T` is not usually very effective, since the generator does not know if the inputs that it produces are any good.

The generator layer is pluggable: [junit-quickcheck](https://github.com/pholser/junit-quickcheck) is the default provider, and other providers such as [Instancio](https://www.instancio.org/) and [jetCheck](https://github.com/JetBrains/jetCheck) plug in through the `ArgumentsGeneratorFactory` SPI without changes to the engine.


### What is *semantic fuzzing* (Zest)?

JQF supports the **[*Zest algorithm*][ISSTA'19 paper], which uses code-coverage and input-validity feedback to bias a QuickCheck-style generator** towards generating structured inputs that can reveal deep semantic bugs. JQF extracts code coverage using bytecode instrumentation, and input validity using JUnit's [`Assume`](https://junit.org/junit4/javadoc/4.12/org/junit/Assume.html) API. An input is valid if no assumptions are violated.
JQF supports the **[*Zest algorithm*][ISSTA'19 paper], which uses code-coverage and input-validity feedback to bias a QuickCheck-style generator** towards generating structured inputs that can reveal deep semantic bugs. JQF extracts code coverage using bytecode instrumentation, and input validity using JUnit's assumption API (`org.junit.Assume` on JUnit 4, `org.junit.jupiter.api.Assumptions` on JUnit 5). An input is valid if no assumptions are violated.

## Example

Expand Down Expand Up @@ -84,6 +86,26 @@ Running `mvn jqf:fuzz` causes JQF to invoke the `testMap2Trie()` method repeated

In the above example, the generators for `Map` and `String` were synthesized automatically by JUnitQuickCheck. It is also possible to specify generators for structured inputs manually. See the [tutorials](#tutorials) below.

### Running under JUnit 5

The same driver runs as a JUnit 5 test: replace `@RunWith(JQF.class)` and `@Fuzz` with a single `@FuzzTest`.

```java
class PatriciaTrieTest {

@FuzzTest
void testMap2Trie(Map<String, Integer> map, String key) {
assumeTrue(map.containsKey(key));

Trie trie = new PatriciaTrie(map);

assertTrue(trie.containsKey(key));
}
}
```

With `-Djqf.fuzz=true` (or `mvn jqf:fuzz`) the method runs a full Zest campaign; a plain `mvn test` replays the saved corpus and seed inputs as a bounded regression. Add a generator provider such as `jqf-generator-quickcheck` to the test classpath for the junit-quickcheck generators used above.


## Documentation

Expand Down
8 changes: 8 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-fuzz</artifactId>
</dependency>
<!-- The JUnit 5 run path for the @FuzzTest examples. jqf-junit5 brings only
jqf-core and Jupiter; the argument generators still come from
jqf-generator-quickcheck (transitively via jqf-fuzz). -->
<dependency>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2026 Vladimir Sitnikov and JQF Contributors
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.berkeley.cs.jqf.examples.junit5;

import com.pholser.junit.quickcheck.From;
import com.pholser.junit.quickcheck.generator.GenerationStatus;
import com.pholser.junit.quickcheck.generator.Generator;
import com.pholser.junit.quickcheck.random.SourceOfRandomness;
import edu.berkeley.cs.jqf.junit5.FuzzTest;

/**
* A JUnit 5 {@code @FuzzTest} with a planted bug, used to show that the Maven
* plugin can fuzz and replay {@code @FuzzTest} methods.
*
* <p>The method fails for any negative input. The empty (all-zero) regression
* input decodes to {@code 0}, which passes, so only a fuzzing campaign reaches
* the bug. The {@code int} comes from a junit-quickcheck generator, supplied by
* the {@code jqf-generator-quickcheck} provider on the test classpath.
*/
public class PlantedBugFuzzTest {

public static class IntGenerator extends Generator<Integer> {
public IntGenerator() {
super(Integer.class);
}

@Override
public Integer generate(SourceOfRandomness random, GenerationStatus status) {
return random.nextInt();
}
}

@FuzzTest
public void mustBeNonNegative(@From(IntGenerator.class) Integer value) {
if (value < 0) {
throw new AssertionError("planted bug: negative input " + value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2026 Vladimir Sitnikov and JQF Contributors
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.berkeley.cs.jqf.examples.junit5;

import com.pholser.junit.quickcheck.From;
import com.pholser.junit.quickcheck.generator.GenerationStatus;
import com.pholser.junit.quickcheck.generator.Generator;
import com.pholser.junit.quickcheck.random.SourceOfRandomness;
import edu.berkeley.cs.jqf.examples.simple.SimpleClass;
import edu.berkeley.cs.jqf.junit5.FuzzTest;

/**
* The JUnit 5 counterpart of
* {@link edu.berkeley.cs.jqf.examples.simple.SimpleClassTest}.
*
* <p>The JUnit 4 version uses {@code @RunWith(JQF.class)} on the class and
* {@code @Fuzz} on the method. The migration to JUnit 5 is small: drop the
* class-level runner and mark the method {@link FuzzTest @FuzzTest}. The argument
* generators are unchanged ({@code @From} still resolves a junit-quickcheck
* {@link Generator}), supplied by the {@code jqf-generator-quickcheck} provider on
* the test classpath.
*
* <p>How it runs:
* <ul>
* <li>A plain {@code mvn test} replays the saved corpus and seeds (bounded and
* fast) under the normal Jupiter lifecycle.</li>
* <li>{@code mvn test -Djqf.fuzz=true} runs a coverage-guided Zest campaign and
* fails the test on the first failing input.</li>
* </ul>
*/
public class SimpleFuzzTest {

public static class SimpleGenerator extends Generator<Integer> {
public SimpleGenerator() {
super(Integer.class);
}

@Override
public Integer generate(SourceOfRandomness sourceOfRandomness, GenerationStatus generationStatus) {
return sourceOfRandomness.nextInt();
}
}

@FuzzTest
public void testWithGenerator(@From(SimpleGenerator.class) Integer a) {
SimpleClass.test(a);
}
}
94 changes: 31 additions & 63 deletions fuzz/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,41 @@
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>

<artifactId>jqf-fuzz</artifactId>

<name>jqf-fuzz</name>
<description>JQF: Feedback-directed Quickcheck for Java - Guided fuzzing interface</description>
<description>JQF: Feedback-directed Quickcheck for Java - Aggregator of the engine, JUnit 4 runner, and junit-quickcheck generators</description>
<packaging>jar</packaging>

<!--
This module keeps the edu.berkeley.cs.jqf:jqf-fuzz coordinates for backwards compatibility.
It carries no sources of its own; it re-exports the engine and the JUnit 4 / junit-quickcheck
adapters so existing users still get JQF, @Fuzz, ZestGuidance, GuidedFuzzing, and the rest
through a single dependency. The zest-cli assembly is built here because it needs every module
and SPI provider on one classpath.
-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>com.pholser</groupId>
<artifactId>junit-quickcheck-generators</artifactId>
</dependency>
<dependency>
<groupId>com.pholser</groupId>
<artifactId>junit-quickcheck-core</artifactId>
</dependency>
<dependency>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-instrument</artifactId>
<artifactId>jqf-core</artifactId>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.report</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections</artifactId>
<version>10.4.0</version>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-generator-quickcheck</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.3</version>
<scope>compile</scope>
<groupId>edu.berkeley.cs.jqf</groupId>
<artifactId>jqf-junit4</artifactId>
</dependency>
</dependencies>


<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
Expand All @@ -82,22 +50,22 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<excludeArtifactIds>jqf-instrument,asm</excludeArtifactIds>
</configuration>
</execution>
</executions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
Expand Down Expand Up @@ -127,4 +95,4 @@
</plugins>
</build>

</project>
</project>

This file was deleted.

Loading
Loading