Skip to content

Commit 204e400

Browse files
committed
docs: improve ValuePool documentation
1 parent 0745dac commit 204e400

2 files changed

Lines changed: 71 additions & 57 deletions

File tree

docs/mutation-framework.md

Lines changed: 66 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,14 @@ You can apply `@ValuePool` in two places:
342342
```java
343343
@FuzzTest
344344
void fuzzTest(Map<@ValuePool(value = {"mySupplier"}) String, Integer> foo) {
345-
// Strings from mySupplier feed the Map's String mutator
345+
// Strings from mySupplier feed the Map's String mutator
346346
}
347347

348348
@FuzzTest
349349
void anotherFuzzTest(@ValuePool(value = {"mySupplier"}) Map<String, Integer> foo) {
350350
// Strings from mySupplier feed the Map's String mutator
351351
// Integers from mySupplier feed the Map's Integer mutator
352+
// Map mutator would use supplier values if it contained any Map<String, Integer> objects
352353
}
353354

354355
@FuzzTest
@@ -358,6 +359,7 @@ void yetAnotherFuzzTest(Map<String, Integer> foo, String bar) {
358359
// - String mutator for Map keys in 'foo'
359360
// - String mutator for 'bar'
360361
// - Integer mutator for Map values in 'foo'
362+
// - Map mutator would use supplier values if it contained any Map<String, Integer> objects
361363
}
362364

363365
static Stream<?> mySupplier() {
@@ -372,7 +374,7 @@ Jazzer automatically routes values to mutators based on type:
372374
- Integers in your value pool → Integer mutators
373375
- Byte arrays in your value pool → byte[] mutators
374376

375-
**Type propagation happens recursively by default**, so a `@ValuePool` on a `Map<String, Integer>` will feed both the String mutator (for keys) and Integer mutator (for values).
377+
**Type propagation happens recursively by default**, so a `@ValuePool` on a `Map<String, Integer>` will feed three mutators: the String mutator (for keys), the Integer mutator (for values), and the `Map<String, Integer>` mutator.
376378

377379
---
378380

@@ -382,7 +384,12 @@ Jazzer automatically routes values to mutators based on type:
382384

383385
Provide the names of static methods that return `Stream<?>`:
384386
```java
385-
@ValuePool(value = {"mySupplier", "anotherSupplier"})
387+
// The supplier methods mySupplier and anotherSupplier should be in the class of the fuzz test method
388+
// Supplier methods from other classes can be used by giving fully qualified names:
389+
// com.example.MyClass#mySupplierMethod and com.example.OuterClass$InnerClass#mySupplierMethod
390+
@ValuePool(value = {"mySupplier", "anotherSupplier",
391+
"com.example.MyClass#mySupplierMethod",
392+
"com.example.OuterClass$InnerClass#mySupplierMethod"})
386393
```
387394

388395
**Requirements:**
@@ -414,82 +421,84 @@ Load files as `byte[]` arrays using glob patterns:
414421
#### Mutation Probability (`p` field)
415422
Controls how often values from the pool are used versus other mutation strategies.
416423
```java
417-
@ValuePool(value = {"mySupplier"}, p = 0.3) // Use pool values 30% of the time
424+
@ValuePool(value = {"mySupplier"}, p = 0.3) T // Use pool values 30% of the time
418425
```
419426

420-
**Default:** `p = 0.1` (10% of mutations use pool values)
421-
**Range:** 0.0 to 1.0
427+
**Default:** `p = 0.1` - 10% of mutations use pool values
428+
429+
**Range:** `[0.0; 1.0]`
430+
431+
432+
#### Max Mutations (`maxMutations` field)
433+
After selecting a value from the pool, the underlying type mutator can additionaly apply a randomly chosen number of mutations in the inclusive interval of [0; `maxMutations`] to the value.
434+
Setting `maxMutations = 0` means that no additional mutations are applied, and the values from the pool are passed directly to the fuzz test method.
435+
436+
**Default:** `maxMutations = 1` - mutates at most one time after selecting a value from the pool
437+
438+
**Range:** `[0; Integer.MAX_VALUE]`
439+
422440

423441
#### Type Propagation (`constraint` field)
424442

425443
Controls whether the annotation affects nested types:
426444
```java
427-
// Default: RECURSIVE - applies to all nested types
428-
@ValuePool(value = {"mySupplier"}, constraint = Constraint.RECURSIVE)
445+
// With constraint=RECURSIVE (default): supplier values propagate to both Map keys AND values
446+
@ValuePool(value = {"valuesSupplier"}) Map<String, Integer> data, ...
429447

430-
// DECLARATION - applies only to the annotated type, not subtypes
431-
@ValuePool(value = {"mySupplier"}, constraint = Constraint.DECLARATION)
448+
// With constraint=DECLARATION: supplier values only propagate to the Map; NOT keys or values---the supplier should return Map instances to have effect
449+
@ValuePool(value = {"valuesSupplier"}, constraint = DECLARATION) Map<String, Integer> data, ...
432450
```
433451

434-
**Example of the difference:**
435-
```java
436-
// With RECURSIVE (default):
437-
@ValuePool(value = {"valuesSupplier"}) Map<String, Integer> data
438-
// The supplier feed both Map keys AND values
452+
**Default:** `constraint = RECURSIVE` - values propagate to all matching types recursively
439453

440-
// With DECLARATION:
441-
@ValuePool(value = {"valuesSupplier"}, constraint = DECLARATION) Map<String, Integer> data
442-
// The supplier only feeds the Map, NOT keys or values---it should contain Map instances to have effect
443-
```
454+
**Range:** `{RECURSIVE, DECLARATION}`
444455

445456
---
446457

447458
### Complete Example
448459
```java
449460
class MyFuzzTest {
450-
static Stream<?> edgeCases() {
451-
Map<String, Integer> map = new HashMap<>();
452-
map.put("one", 1);
453-
map.put("two", 2);
454-
return Stream.of(
455-
"", "null", "alert('xss')", // Strings
456-
0, -1, Integer.MAX_VALUE, // Integers
457-
new byte[]{0x00, 0x7F}, // A byte array
458-
map // A Map
459-
);
460-
}
461-
462-
@FuzzTest
463-
@ValuePool(value = {"edgeCases"},
464-
files = {"test-inputs/*.bin"},
465-
p = 0.25) // Use pool values 25% of the time
466-
void testParser(String input, Map<String, Integer> config, byte[] data) {
467-
// All three parameters get values from the pool:
468-
// - 'input' gets Strings
469-
// - 'config' keys get Strings, values get Integers, Map itself gets the `map` object
470-
// - 'data' gets bytes from both edgeCases() and *.bin files
471-
}
461+
static Stream<?> edgeCases() {
462+
Map<String, Integer> map = new HashMap<>();
463+
map.put("one", 1);
464+
map.put("two", 2);
465+
return Stream.of(
466+
"", // Strings
467+
"null",
468+
"alert('xss')",
469+
0, // Integers
470+
-1,
471+
Integer.MAX_VALUE,
472+
new byte[] {0x00, 0x7F}, // A byte array
473+
map); // A Map
474+
}
475+
476+
static Stream<String> justStrings() {
477+
return Stream.of("{\"hello\": \"json\"}", "{\"__proto__\": {\"test\": \"value\"}}");
478+
}
479+
480+
@FuzzTest
481+
@ValuePool(
482+
value = {"edgeCases"},
483+
files = {"test-inputs/*.bin"},
484+
p = 0.25) // Use pool values 25% of the time
485+
void testParser(
486+
@ValuePool("justStrings") String input,
487+
Map<String, @ValuePool(p = 0.01, maxMutations = 10) Integer> config,
488+
byte[] data) {
489+
// All three parameters get values from the pool:
490+
// - 'input' gets Strings from two suppliers: 'edgeCases()' and 'justStrings()'
491+
// - 'config' keys get Strings, values get Integers, Map itself gets the `map` objects, all from supplier method 'edgeCases()'
492+
// - 'data' gets byte arrays from both edgeCases() and *.bin files
493+
// In addition, the Integer values of the Map 'config' have a different configuration:
494+
// the values from the value pool will be taken with probability 0.01,
495+
// and at most 10 mutations will be applied on top of those values.
496+
}
472497
}
473498
```
474499

475500
---
476501

477-
#### Max Mutations (`maxMutations` field)
478-
479-
After selecting a value from the pool, the mutator can apply additional random mutations to it.
480-
```java
481-
@ValuePool(value = {"mySupplier"}, maxMutations = 5)
482-
```
483-
484-
**Default:** `maxMutations = 1` (at most one additional mutation applied)
485-
**Range:** 0 or higher
486-
487-
**How it works:** If `maxMutations = 5`, and Jazzer selects the value pool as mutation strategy, Jazzer will:
488-
1. Select a random value from your pool (e.g., `"alert('xss')"`)
489-
2. Apply up to 5 random mutations in a row (e.g., `"alert('xss')"``"alert(x"``"AAAt(x"` → ...)
490-
491-
This helps explore variations of your seed values while staying close to realistic inputs.
492-
493502

494503
## FuzzedDataProvider
495504

src/main/java/com/code_intelligence/jazzer/mutation/annotation/ValuePool.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
* specified supplier methods must be static and return a {@code Stream<?>} of values. The values
7272
* don't need to match the type of the annotated method or parameter. The mutation framework will
7373
* extract only the values that are compatible with the target type.
74+
*
75+
* <p>Suppliers in the fuzz test class can be referenced by their method name, while suppliers in
76+
* other classes must be referenced by their fully qualified method name (e.g. {@code
77+
* com.example.MyClass#mySupplierMethod}), or for nested classes: {@code
78+
* com.example.OuterClass$InnerClass#mySupplierMethod}.
7479
*/
7580
String[] value() default {};
7681

0 commit comments

Comments
 (0)