Skip to content

Allow supplying a custom org.instancio.Random for stream-driven generation (coverage-guided fuzzing) #1770

Description

@vlsi

Use case

Coverage-guided fuzzers such as JQF/Zest drive a generator from a mutable byte stream: the fuzzer mutates the raw bytes, the generator turns them into a structured object, and coverage feedback steers the next mutation. For this to work well, the generator has to consume the stream incrementally, so that a small byte change produces a small, local change in the object (one field, one list element) rather than a completely different graph.

Current state

Instancio derives all randomness from a single seed via withSeed(long). That is ideal for reproducibility, but coarse for a fuzzer: one seed determines the entire object graph, so flipping one byte of the fuzzer's input reshapes everything at once instead of one field. Coverage-guided search cannot map input regions onto structure.

There is no public way to supply a custom source of randomness:

  • InstancioApi exposes only withSeed(long) and withSettings(Settings).
  • org.instancio.spi.InstancioServiceProvider has providers for generators, type resolution, instantiation, setter resolution, and annotations — but none for randomness.
  • org.instancio.Random is a public interface, yet nothing lets a caller inject an implementation of it; the internal Random is always built from the seed.

I integrated Instancio as a pluggable generator provider in JQF using the seed approach, and documented the trade-off in the module README (linked below): Zest can only steer generation through the eight-byte seed.

What would help

org.instancio.Random already exposes exactly the right shape for a byte-driven implementation — intRange, longRange, stringOf, characterRange, oneOf, trueOrFalse, and so on. Each call naturally consumes a few bytes from a stream. The only missing piece is a way to inject a custom implementation. For example:

// Option A: a builder hook, parallel to withSeed/withSettings
public InstancioApi<T> withRandom(org.instancio.Random random);

// Option B: a provider in the existing SPI
interface InstancioServiceProvider {
    default RandomProvider getRandomProvider() { return null; }
}

With either, a fuzzer supplies a Random backed by its byte stream, and Instancio's generation becomes byte-structured and steerable, while withSeed stays the default for everyone else. This is the property that lets a coverage-guided fuzzer exploit the generator, and it is the one thing keeping Instancio a coarser fit than a byte-structured library for this use case.

Links

Thanks for Instancio — the public Random interface is already most of the way there; a way to plug it in would make Instancio a first-class option for coverage-guided fuzzing.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions