Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
/_*
/tests/benchmark_alternatives/results/
/tests/benchmark/results/
# Fuzz testing artifacts
/fuzz/crash-*
/fuzz/minimized-*
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"php": "^8.2"
},
"require-dev": {
"nikic/php-fuzzer": "^0.0.11",
"php-collective/code-sniffer": "dev-master",
"phpstan/phpstan": "^2.1.32",
"phpunit/phpunit": "^11.0 || ^12.0"
Expand All @@ -35,6 +36,8 @@
],
"cs-check": "phpcs --colors --parallel=16",
"cs-fix": "phpcbf --colors --parallel=16",
"fuzz": "php-fuzzer fuzz fuzz/target.php fuzz/corpus/",
"fuzz-strict": "php-fuzzer fuzz fuzz/target-strict.php fuzz/corpus/",
"stan": "phpstan analyze",
"test": "phpunit"
},
Expand Down
63 changes: 63 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Fuzz Testing

This directory contains fuzz testing infrastructure for the djot-php parser using [nikic/php-fuzzer](https://github.com/nikic/PHP-Fuzzer).

## Setup

```bash
composer install
```

## Running Fuzz Tests

### Basic fuzzing (default mode):
```bash
composer fuzz
# or directly:
php vendor/bin/php-fuzzer fuzz fuzz/target.php fuzz/corpus/
```

### Fuzzing with warnings enabled:
```bash
composer fuzz-strict
# or directly:
php vendor/bin/php-fuzzer fuzz fuzz/target-strict.php fuzz/corpus/
```

## How It Works

The fuzzer generates random inputs based on:
1. Initial seed corpus in `corpus/`
2. Dictionary of djot syntax fragments in `djot.dict`
3. Mutations of discovered interesting inputs

It looks for:
- Uncaught `Error` exceptions (bugs)
- Timeouts (infinite loops)
- Warnings/notices converted to errors

## Files

- `target.php` - Main fuzz target for DjotConverter
- `target-strict.php` - Fuzz target with warning collection enabled
- `djot.dict` - Dictionary of djot syntax fragments
- `corpus/` - Initial seed inputs

## Crash Investigation

When a crash is found:

```bash
# Minimize the crash to smallest reproducing input
php vendor/bin/php-fuzzer minimize-crash fuzz/target.php crash-HASH.txt

# Run single input for debugging
php vendor/bin/php-fuzzer run-single fuzz/target.php minimized-HASH.txt
```

## Coverage Report

Generate a coverage report:
```bash
php vendor/bin/php-fuzzer report-coverage fuzz/target.php fuzz/corpus/ coverage/
```
6 changes: 6 additions & 0 deletions fuzz/corpus/attributes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{.class #id key=value}
# Header with attributes

Paragraph{.special}

[Link]{target=_blank}(https://example.com)
3 changes: 3 additions & 0 deletions fuzz/corpus/basic_text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hello world. This is a paragraph.

This is another paragraph with *emphasis* and _more emphasis_.
11 changes: 11 additions & 0 deletions fuzz/corpus/code_blocks.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Inline `code` here.

```php
function test() {
return true;
}
```

````
Nested ``` backticks
````
8 changes: 8 additions & 0 deletions fuzz/corpus/definition_list.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
: Term 1

Definition for term 1.

: Term 2

Definition for term 2.
With multiple lines.
7 changes: 7 additions & 0 deletions fuzz/corpus/headers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Header 1

## Header 2

### Header 3

Paragraph after header.
11 changes: 11 additions & 0 deletions fuzz/corpus/links_images.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Link text](https://example.com)

![Alt text](image.png)

[ref]: https://example.com

A [reference link][ref].

[^note]: This is a footnote.

See footnote[^note].
9 changes: 9 additions & 0 deletions fuzz/corpus/lists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- Item 1
- Item 2
- Nested item
- Another nested
- Item 3

1. First
2. Second
3. Third
4 changes: 4 additions & 0 deletions fuzz/corpus/tables.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
| Col 1 | Col 2 | Col 3 |
|-------|:-----:|------:|
| Left | Center| Right |
| A | B | C |
115 changes: 115 additions & 0 deletions fuzz/djot.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Djot syntax dictionary for fuzzing
# Emphasis and strong
"_"
"*"
"__"
"**"
"_*"
"*_"

# Links and images
"["
"]"
"("
")"
"[link](url)"
"![alt](url)"
"[^"
"]:"

# Code
"`"
"``"
"```"
"````"
"~~~"
"~~~~"

# Headers
"#"
"##"
"###"
"####"
"#####"
"######"

# Lists
"-"
"+"
"1."
"2."
"- [ ]"
"- [x]"
":"
" "

# Block elements
">"
"---"
"***"
":::"
"{."
"}"
"{#"
"{-"
"{+"

# Escapes and special
"\\"
"&"
"&"
"<"
">"
"&#"
"&#x"

# Attributes
"{.class}"
"{#id}"
"{key=value}"

# Tables
"|"
"|-"
"|:"
":|"
":--:"

# Math
"$"
"$$"

# Footnotes
"[^note]"
"[^1]"

# Smart punctuation
"--"
"..."
"'"

# Superscript/subscript
"^"
"~"

# Highlighting
"{="
"=}"
"+}"
"-}"

# Whitespace (using hex escapes)
"\x0a\x0a"
"\x0d\x0a"
"\x09"
" "
" "

# Nested structures
"[[["
"]]]"
"((("
")))"

# Simple space
" "
33 changes: 33 additions & 0 deletions fuzz/target-strict.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

/**
* Fuzz testing target for DjotConverter in strict mode
*
* Tests with warnings collection enabled.
* Run with: php vendor/bin/php-fuzzer fuzz fuzz/target-strict.php fuzz/corpus/
*
* @var PhpFuzzer\Config $config
*/

require __DIR__ . '/../vendor/autoload.php';

use Djot\DjotConverter;

$config->setTarget(function (string $input): void {
// Create converter with warning collection
$converter = new DjotConverter(warnings: true);

// Parse and convert the input
$converter->convert($input);

// Also get warnings to exercise that code path
$converter->getWarnings();
});

// Limit input length
$config->setMaxLen(8192);

// Add dictionary
$config->addDictionary(__DIR__ . '/djot.dict');
30 changes: 30 additions & 0 deletions fuzz/target.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/**
* Fuzz testing target for DjotConverter
*
* This file is used by nikic/php-fuzzer to find bugs in the parser.
* Run with: php vendor/bin/php-fuzzer fuzz fuzz/target.php fuzz/corpus/
*
* @var PhpFuzzer\Config $config
*/

require __DIR__ . '/../vendor/autoload.php';

use Djot\DjotConverter;

$converter = new DjotConverter();

$config->setTarget(function (string $input) use ($converter): void {
// Parse and convert the input
// Exceptions are expected for malformed input, but Error exceptions indicate bugs
$converter->convert($input);
});

// Limit input length - most parser bugs are found with smaller inputs
$config->setMaxLen(8192);

// Add dictionary of common djot syntax fragments
$config->addDictionary(__DIR__ . '/djot.dict');
Loading