Skip to content
Open
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
4 changes: 0 additions & 4 deletions config.defpy
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ TIMEOUT_TIME: int = 10000
# (i.e. the programs you're testing aren't expected to have identical output on stdout)
DETECT_OUTPUT_DIFFERENTIALS: bool = True

# Set this to True if you want to use grammar mutations.
# (Requires a grammar.py with the appropriate interface)
USE_GRAMMAR_MUTATIONS: bool = True

# When this is True, a differential is registered if two targets exit with different status codes.
# When it's False, a differential is registered only when one target exits with status 0 and another
# exits with nonzero status.
Expand Down
37 changes: 1 addition & 36 deletions diff_fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
import time
from pathlib import PosixPath
from typing import Callable


from tqdm import tqdm # type: ignore

from mutations import MUTATORS
from config import (
compare_parse_trees,
get_replacement_byte,
Expand All @@ -39,18 +38,8 @@
DELETION_LENGTHS,
RESULTS_DIR,
REPORTS_DIR,
USE_GRAMMAR_MUTATIONS,
)

if USE_GRAMMAR_MUTATIONS:
try:
from grammar import GRAMMAR_MUTATORS # type: ignore[import]
except ModuleNotFoundError:
print(
"`grammar.py` not found. Either make one or set USE_GRAMMAR_MUTATIONS to False", file=sys.stderr
)
sys.exit(1)

assert SEED_DIR.is_dir()
SEED_INPUTS: list[PosixPath] = list(map(lambda s: SEED_DIR.joinpath(PosixPath(s)), os.listdir(SEED_DIR)))

Expand All @@ -64,30 +53,6 @@
json_t = None | bool | str | int | float | dict[str, "json_t"] | list["json_t"]


def byte_replace(b: bytes) -> bytes:
if len(b) == 0:
raise ValueError("Mutation precondition didn't hold.")
index: int = random.randint(0, len(b) - 1)
return b[:index] + bytes([random.randint(0, 255)]) + b[index + 1 :]


def byte_insert(b: bytes) -> bytes:
index: int = random.randint(0, len(b))
return b[:index] + bytes([random.randint(0, 255)]) + b[index:]


def byte_delete(b: bytes) -> bytes:
if len(b) <= 1:
raise ValueError("Mutation precondition didn't hold.")
index: int = random.randint(0, len(b) - 1)
return b[:index] + b[index + 1 :]


MUTATORS: list[Callable[[bytes], bytes]] = [byte_replace, byte_insert, byte_delete] + (
GRAMMAR_MUTATORS if USE_GRAMMAR_MUTATIONS else []
)


def mutate(b: bytes) -> bytes:
mutators: list[Callable[[bytes], bytes]] = MUTATORS.copy()
while len(mutators) != 0:
Expand Down
35 changes: 33 additions & 2 deletions grammar.py → mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
from typing import Callable
import re_generate


def byte_replace(b: bytes) -> bytes:
if len(b) == 0:
raise ValueError("Mutation precondition didn't hold.")
index: int = random.randint(0, len(b) - 1)
return b[:index] + bytes([random.randint(0, 255)]) + b[index + 1 :]


def byte_insert(b: bytes) -> bytes:
index: int = random.randint(0, len(b))
return b[:index] + bytes([random.randint(0, 255)]) + b[index:]


def byte_delete(b: bytes) -> bytes:
if len(b) <= 1:
raise ValueError("Mutation precondition didn't hold.")
index: int = random.randint(0, len(b) - 1)
return b[:index] + b[index + 1 :]


# A grammar maps rule names either a string or a sequence of rule names
# A terminal always maps to a regex
# A nonterminal always maps to a list of rule names
Expand Down Expand Up @@ -175,7 +195,7 @@ def grammar_insert(b: bytes) -> bytes:
rules_to_fill: list[str] = [
s
for s in INSERTABLE_RULES + (INSERTABLE_RULES_WITH_HOST if match["host"] is not None else [])
if match[s] is None
if s not in match.groupdict() or match[s] is None
]

if len(rules_to_fill) == 0:
Expand Down Expand Up @@ -216,6 +236,10 @@ def serialize(match: dict[str, bytes | None] | re.Match[bytes]) -> bytes:
if isinstance(match, re.Match):
match = match.groupdict()

for rule_name in grammar_rules:
if rule_name not in match:
match[rule_name] = None

result = b""
if match["scheme"] is not None:
result += match["scheme"] + b":"
Expand All @@ -242,4 +266,11 @@ def serialize(match: dict[str, bytes | None] | re.Match[bytes]) -> bytes:
return result


GRAMMAR_MUTATORS: list[Callable[[bytes], bytes]] = [grammar_delete, grammar_insert, grammar_replace]
MUTATORS: list[Callable[[bytes], bytes]] = [
byte_delete,
byte_insert,
byte_replace,
grammar_delete,
grammar_insert,
grammar_replace,
]