Syntax Symphony is a powerful fuzzer designed to automatically generate test inputs for various applications based on user-defined grammars. The fuzzer leverages the grammar rules to create meaningful and diverse input data, facilitating robust testing of applications. In order to achieve high diversity and coverage of grammar rules, it uses k-coverage, as discussed by Havrikov et al.. This work has been greatly influenced by the concepts and ideas outlined in the Fuzzing Book.
With Syntax Symphony, you can enhance the quality and reliability of your software by generating a comprehensive set of test cases effortlessly. Start fuzzing today and make your software more robust against unexpected inputs!
- Python 3.12 or higher
- uv for local development
pip install syntax-symphony- Clone the repository:
git clone https://github.com/StanimirIglev/syntax-symphony.git
cd syntax-symphony- Install the project and development dependencies with uv:
uv syncThis creates a .venv virtual environment and installs the package in editable mode along with dev tools (pytest, pytest-cov, ruff, mypy, pre-commit, build, twine).
- Run the CLI via uv:
uv run ssfuzz -g examples/expr_grammar.json -c 100Alternatively, activate the virtual environment and use commands directly:
source .venv/bin/activate
ssfuzz -g examples/expr_grammar.json -c 100- To build the package:
uv build
uv run twine check dist/*Artifacts are written to the dist/ directory.
Install git hooks (lint/format/typecheck on commit; tests on push):
uv run pre-commit installRun the quality checks locally (same as CI):
uv run pre-commit run --all-files
uv run pytestSyntax Symphony provides a command-line interface (CLI) to interact with the fuzzer. The CLI allows you to specify the grammar file, the number of test cases to generate, and the output directory to save the generated test cases among others.
# Generate 100 test cases using the grammar file examples/expr_grammar.json
ssfuzz -g examples/expr_grammar.json -c 100
# Save the output in the directory out/
ssfuzz -g examples/expr_grammar.json -c 100 -d out
# Set the start symbol
ssfuzz -g examples/expr_grammar.json -c 100 --start begin
# Set the file extension
ssfuzz -g examples/expr_grammar.json -c 100 -e json
# Reproduce the same output sequence across runs
ssfuzz -g examples/expr_grammar.json -c 100 --seed 42ssfuzz [-h] -g FILE [-s SYMBOL] -c NUMBER [-d DIR] [-e EXT] [--max-depth NUMBER] [--min-depth NUMBER] [-k NUMBER] [--seed NUMBER]
Syntax Symphony Fuzzer
options:
-h, --help show this help message and exit
-g FILE, --grammar FILE
Path to the grammar file
-s SYMBOL, --start SYMBOL
Start symbol of the grammar (without <...>). Default: start
-c NUMBER, --count NUMBER
Number of strings to generate
-d DIR, --dir DIR Output directory for the generated strings. Default: out
-e EXT, --file-extension EXT
The file extension to be used for the produced documents. Default: txt
--max-depth NUMBER Depth at which minimum-cost completion begins. Default: 10
--min-depth NUMBER Minimum depth for the derivation trees. Default: 0
-k NUMBER, --kcov NUMBER
Number of strings to generate for k-cov. Default: 1
--seed NUMBER Random seed for reproducible fuzzing. Default: non-deterministic
Syntax Symphony can also be used as a library in your Python projects. The API provides a simple interface to generate test inputs using the fuzzer.
The public API is exported from the top-level syntax_symphony package:
Grammar— context-free grammar definition and validationSyntaxSymphony— k-path coverage fuzzerDT— derivation tree nodes (returned bySyntaxSymphony.fuzz_tree())load_grammar_from_file— load a grammar dictionary from a JSON file
from syntax_symphony import Grammar, SyntaxSymphony
# Define the grammar
grammar = Grammar({
"<start>": ["<expr>"],
"<expr>": ["<term> + <expr>", "<term> - <expr>", "<term>"],
"<term>": ["<factor> * <term>", "<factor> / <term>", "<factor>"],
"<factor>": ["<number>", "(<expr>)"],
"<number>": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
})
# Create the fuzzer (pass seed=42 for reproducible output)
fuzzer = SyntaxSymphony(grammar, seed=42)
# Generate 10 test cases
for i in range(10):
test_case = fuzzer.fuzz()
print(test_case)Pass seed to get the same sequence of outputs across runs. The fuzzer uses a private random.Random instance, so it does not affect global random state. Omit seed (or pass None) for non-deterministic fuzzing.
This version removes implementation details from the public API:
| Earlier API | Supported API in 0.5.0 |
|---|---|
tree_fuzz(DT(start_symbol, None)) |
fuzz_tree(); no starting tree argument is needed |
A cost mapping returned by grammar.validate() |
validate() returns None or raises ValueError; is_valid() returns a Boolean |
symbol_cost, expansion_cost, compute_cost, costs, symbol_costs, seen arguments |
Removed; completion costs are private analysis results |
compute_biased_grammar, minimizing_grammar, maximizing_grammar, symbol_to_tree |
Private generation machinery |
compute_k_paths, k_paths, uncovered_k_paths |
Private; use remaining_k_paths() to inspect progress |
complete_tree or supplying partial trees for generation |
Removed from the supported interface; use fuzz_tree() for a new complete tree |
normalize, is_nonterminal, schema objects, Grammar.extract_nonterminals |
Private helpers; construction normalizes input automatically |
Grammar mapping operations, to_dict(), from_dict(), reachable_nonterminals(),
and unreachable_nonterminals() remain available for definition and inspection.
Use validation and reachability methods on the grammar instance. Derivation-tree
inspection, serialization, validation, and traversal remain supported.
We welcome contributions from the community. If you have ideas for improvements, new features, or bug fixes, please submit a pull request or open an issue on our GitHub repository.
This project is licensed under the MIT License. See LICENSE for more details.