How is EK9 fuzzing different from AFL or libFuzzer?

← Fuzzing and Mutation Testing · Ref: Q758

EK9 fuzzing differs from traditional fuzzers in three fundamental ways: it operates at source level, understands types, and provides three modes instead of one.

SOURCE-LEVEL VS BYTE-LEVEL

AFL and libFuzzer mutate raw bytes and feed them to a compiled binary. Most mutated inputs are immediately rejected as invalid. EK9 generates valid source programs from the grammar, so every generated file is syntactically correct and exercises deeper compiler phases.

TYPE-AWARE VS TYPE-BLIND

AFL has no concept of types. It might flip a bit in a pointer, producing a segfault rather than testing logic. EK9 understands Integer, Float, String, and custom types. Generated programs use correct types, operators, and calling conventions.

THREE MODES VS ONE

AFL does one thing: coverage-guided binary fuzzing. EK9 provides:

  1. Compiler fuzzing (-fuzz): stress-test the compiler itself.
  2. Mutation testing (-fuzzmutate): assess test quality.
  3. Test generation (-fuzztest): create edge-case tests.

These three modes cover different quality dimensions.

NO HARNESS REQUIRED

AFL and libFuzzer require writing a harness function that accepts byte input and converts it to the target format. EK9 fuzzing works on source files directly with zero setup.

COVERAGE-GUIDED VS GRAMMAR-GUIDED

AFL uses code coverage feedback to guide mutations toward new execution paths. EK9 uses grammar rules and type information to generate structurally diverse programs. Both approaches find different kinds of bugs.

WHEN TO USE WHICH

  EK9 fuzzing: for EK9 projects, always use the built-in tools.
  AFL/libFuzzer: for C/C++ libraries or when testing binary interfaces.
  Both: complementary approaches for different layers of the stack.

See Q749 for fuzzing overview. See Q750 for running the fuzzer. See Q753 for mutation testing. See Q754 for test generation.

Example

defines module qa.fuzzingandmutation.fuzzvsafl

  <?-
    Demonstrates type-aware code that a byte-level fuzzer
    would struggle with but EK9 source-level fuzzing handles
    naturally. The type system ensures generated inputs are meaningful.
  -?>

  defines type

    Priority
      Low
      Normal
      High
      Critical

  defines class

    Task
      taskName as String: String()
      taskPriority as Priority: Priority()
      completed as Boolean: false

      Task()
        ->
          initialName as String
          initialPriority as Priority
        this.taskName :=? initialName
        this.taskPriority :=? initialPriority

      complete()
        this.completed: true

      isComplete() as pure
        <- done as Boolean: this.completed

      priority() as pure
        <- rtn as Priority: this.taskPriority

      operator $ as pure
        <- rtn as String: `${this.taskName} [${this.taskPriority}]`

      default operator ?

  defines function

    countByPriority() as pure
      ->
        tasks as List of Task
        targetPriority as Priority
      <- count as Integer: 0

      for task in tasks
        if task.priority() == targetPriority
          count: count + 1

  defines program

    FuzzVsAflDemo()
      stdout <- Stdout()

      tasks <- List() of Task
      tasks += Task("Write docs", Priority.Low)
      tasks += Task("Fix login", Priority.Critical)
      tasks += Task("Add tests", Priority.High)
      tasks += Task("Update deps", Priority.Normal)
      tasks += Task("Review PR", Priority.High)

      for task in tasks
        stdout.println($task)

      highCount <- countByPriority(tasks, Priority.High)
      criticalCount <- countByPriority(tasks, Priority.Critical)
      stdout.println(`High priority: ${highCount}`)
      stdout.println(`Critical: ${criticalCount}`)
Other ways to ask this
  • Why is EK9 fuzzing source-level instead of byte-level?
  • How does EK9 fuzzing compare to coverage-guided fuzzing?
  • What advantages does type-aware fuzzing give EK9?

Coming from another language?

Java: Jazzer (coverage-guided, byte-level, requires harness). Python: Atheris (coverage-guided via libFuzzer, byte-level, requires harness). Rust: cargo-fuzz (coverage-guided via libFuzzer, byte-level, requires harness). Go: go test -fuzz (coverage-guided, byte-level, requires Fuzz* functions). AFL/libFuzzer: coverage-guided, byte-level, external tools. EK9: grammar-guided, source-level, type-aware, three modes, zero harness, built-in.

Keywords: harness, AFL, modes, grammar, byte-level, comparison, coverage, type-aware, source-level, guided, three, libFuzzer