Skip to content

Conversation

@kyakdan
Copy link
Member

@kyakdan kyakdan commented Feb 3, 2026

Summary

Add a hill-climbing maximize() API to Jazzer that guides the fuzzer toward maximizing a value over time. This enables fuzzing scenarios where standard code coverage provides insufficient guidance, such as finding inputs that maximize some computed metric.

Changes

Jazzer.maximize() API

// Guide fuzzer to maximize 'value' within [minValue, maxValue]
Jazzer.maximize(value, id, minValue, maxValue);

// Convenience overload with auto-generated call-site ID (requires instrumentation)
Jazzer.maximize(value, minValue, maxValue);

How it works: For each observed value v, sets coverage counters [0, v-minValue] to 1. This creates incremental progress feedback - higher values trigger more "coverage," guiding the fuzzer toward the maximum. Corpus minimization naturally retains only the input producing the highest value.

Example

Added ReactorFuzzTest demonstrating the API on a chaotic feedback system where standard coverage is constant but the fuzzer needs to maximize a computed temperature value.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a maximize() API to Jazzer that enables hill-climbing fuzzing scenarios where standard code coverage is insufficient. The API guides the fuzzer to maximize a value by setting coverage counters for all values from the minimum up to the observed value, creating incremental progress feedback.

Changes:

  • Added CountersTracker infrastructure (Java and C++) to manage extra coverage counters separate from regular code coverage
  • Added Jazzer.maximize() API with automatic call-site ID generation via instrumentation hooks
  • Added comprehensive test coverage for the new APIs
  • Added ReactorFuzzTest example demonstrating the maximize API on a chaotic feedback system

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/main/native/com/code_intelligence/jazzer/driver/counters_tracker.h Refactored header from CoverageTracker to CountersTracker, adding support for separate extra counters region
src/main/native/com/code_intelligence/jazzer/driver/counters_tracker.cpp New implementation managing both coverage and extra counters with libFuzzer registration
src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel Updated build dependencies to reference counters_tracker instead of coverage_tracker
src/main/java/com/code_intelligence/jazzer/runtime/CountersTracker.java New Java class providing thread-safe counter allocation and management API
src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel Added CountersTracker build target and dependencies
src/main/java/com/code_intelligence/jazzer/runtime/JazzerApiHooks.java Added instrumentation hook to auto-generate call-site IDs for maximize() calls
src/main/java/com/code_intelligence/jazzer/api/Jazzer.java Added maximize() API methods with documentation
src/test/java/com/code_intelligence/jazzer/runtime/CountersTrackerTest.java Comprehensive unit tests for CountersTracker including concurrency tests
src/test/java/com/code_intelligence/jazzer/api/MaximizeTest.java Unit tests for the maximize() API covering edge cases
examples/junit/src/test/java/com/example/ReactorFuzzTest.java Example demonstrating maximize() on a temperature maximization problem

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

CountersTracker provides a flexible API for mapping program state to
coverage counters, enabling incremental progress feedback to libFuzzer.

Key features:
- ensureCountersAllocated(id, numCounters): allocate counter range
- setCounter/setCounterRange: set counter values by ID and offset
- Thread-safe allocation via ConcurrentHashMap
- Separate memory region from main coverage map

This lays the foundation for the maximize() hill-climbing API.
Add Jazzer.maximize(value, id, minValue, maxValue) for guiding the
fuzzer to maximize a value over time. For each observed value v in
[minValue, maxValue], sets counters [0, v-minValue] to signal progress.

Features:
- Enables corpus minimization (only max-value input retained)
- Convenience overload without explicit ID (uses instrumentation hook)
- Delegates to CountersTracker for counter management
- No state in Jazzer.java - all managed by CountersTracker
Example shows how maximize() helps fuzz a chaotic feedback system
where standard coverage provides no guidance. The fuzzer is guided
to increase "temperature" through complex state-dependent logic.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@oetr oetr self-requested a review February 6, 2026 07:22
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@oetr oetr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition, thanks!

// THE GOAL: MAXIMIZATION
// We need to drive 'temperature' to an extreme value.
// Standard coverage is 100% constant here (it just loops).
Jazzer.maximize(temperature, 500, 4500);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues here:

  1. This suggests that users can dynamically set the values minValue and maxValue, whereas we assume them to be constant. I think, we should enforce that by double-checking that the values remain constant each time we call Jazzer.maximize() and error out if they do not.

  2. Also, the exceptions thrown in the hooked method don't stop the fuzzer:

  @FuzzTest(maxExecutions = 10)
  public void test(@InRange(min=10, max=20) int high, boolean dummy) {
    Jazzer.maximize(2, 0, high);
  }

Results in exceptions thrown, but the fuzzing process not stopped. Maybe it's because they are thrown in the hook?

INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: seed corpus: files: 1 min: 5b max: 5b total: 5b rss: 5622Mb
#2	INITED cov: 4 ft: 4 corp: 1/5b exec/s: 0 rss: 5622Mb
java.lang.IllegalArgumentException: ensureCountersAllocated() called with different numCounters for id -2041319264: existing=21, requested=19
	at com.code_intelligence.jazzer.runtime.CountersTracker.ensureCountersAllocated(CountersTracker.java:123)
	at com.code_intelligence.jazzer.api.Jazzer.maximize(Jazzer.java:289)
...
java.lang.IllegalArgumentException: ensureCountersAllocated() called with different numCounters for id -2041319264: existing=21, requested=14
...
java.lang.IllegalArgumentException: ensureCountersAllocated() called with different numCounters for id -2041319264: existing=21, requested=12
...
#10	DONE   cov: 4 ft: 4 corp: 1/5b lim: 4096 exec/s: 0 rss: 5622Mb
Done 10 runs in 0 second(s)

// THE GOAL: MAXIMIZATION
// We need to drive 'temperature' to an extreme value.
// Standard coverage is 100% constant here (it just loops).
Jazzer.maximize(temperature, 500, 4500);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea: From the user perspective, would it make more sense to give them an object they can initialize beforehand and then call later in their code? Something like this:

static {
  CoverageAmplifier reactorAmplifier = new CoverageAmplifier(2000, 3000);
}

@FuzzTest
public void test(@InRange(min=10, max=20) int data) {
  coverageAmplifier.maximize(data);
  // or
  coverageAmplifier.minimize(data);
}

This might also improve performance quite a bit, because at the moment, we call ensureCountersAllocated every time in minimize, and the bound checks for the range are always performed, which only need to be done once.

}
try {
int parsed = Integer.parseInt(value.trim());
if (parsed <= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A negative ENV_MAX_COUNTERS is a mistake and surely not what the user wanted---we should err out.

// THE GOAL: MAXIMIZATION
// We need to drive 'temperature' to an extreme value.
// Standard coverage is 100% constant here (it just loops).
Jazzer.maximize(temperature, 500, 4500);
Copy link
Contributor

@oetr oetr Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea: the current user range is a 1:1 mapping from values to the number of coverage counters.
A range [0, 2^20] takes up all the counters.
Why not map the user range to a fixed number of extra counters, say N=128 for now for ranges > 128, and 1:1 for ranges less than that? We can determine a good N by using the fuzzing benchmark.

This will make sure that a casual user doesn't flood the corpus with a million inputs by accident.
We could also add an option to specify how many counters should be used, for users who know what they are doing.

For example, this fuzz test gives me 60 exec/s:

 @FuzzTest(maxDuration = "0m")
  public void decompressRoundtrip(@InRange(min=0, max=20000) int high) {
    Jazzer.maximize(high, 0, 100000);
  }


#454	REDUCE cov: 20002 ft: 20002 corp: 7/29b lim: 4096 exec/s: 56 rss: 5761Mb L: 4/5 MS: 2 CopyPart-Custom-
#512	pulse  cov: 20002 ft: 20002 corp: 7/29b lim: 4096 exec/s: 56 rss: 5761Mb

Maybe the user can add the number of counters explicitly: Jazzer.maximize(high, 0, 100000, 1024);

COUNTERS_TRACKER_SET_RANGE.invokeExact(id, toOffset);
}
} catch (Throwable e) {
e.printStackTrace();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is what floods the output when I am fuzzing the new maximize function 😆
Is there a reason to swallow all exceptions here? And then why print the stack traces?

Comment on lines +93 to +118
idToRange.computeIfAbsent(
id,
key -> {
// Allocate space - only runs once per ID
int startOffset;
int endOffset;
do {
startOffset = nextOffset.get();
if (startOffset > MAX_COUNTERS - numCounters) {
throw new IllegalStateException(
String.format(
"Counter space exhausted: requested %d counters at offset %d, "
+ "but only %d total counters available. "
+ "Increase via %s environment variable or use smaller ranges.",
numCounters, startOffset, MAX_COUNTERS, ENV_MAX_COUNTERS));
}
endOffset = startOffset + numCounters;
} while (!nextOffset.compareAndSet(startOffset, endOffset));

CounterRange newRange = new CounterRange(startOffset, numCounters);

// Register the new counters with libFuzzer
registerCounters(startOffset, endOffset);

return newRange;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use getAndAdd without the while loop? When we ask for more counters than available, we error out anyway.

idToRange.computeIfAbsent(
    id,
    ignored -> {
        // Allocate space - only runs once per ID
        int startOffset = nextOffset.getAndAdd(numCounters);
        if (startOffset > MAX_COUNTERS - numCounters) {
            throw new IllegalStateException(
                String.format(
                    "Counter space exhausted: requested %d counters at offset %d, "
                        + "but only %d total counters available. "
                        + "Increase via %s environment variable or use smaller ranges.",
                    numCounters, startOffset, MAX_COUNTERS, ENV_MAX_COUNTERS));
        }
        CounterRange newRange = new CounterRange(startOffset, numCounters);
        int endOffset = startOffset + numCounters;
        registerCounters(startOffset, endOffset);
        return newRange;
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants