diff --git a/config.defpy b/config.defpy index 05ebe5d..c2b4d2a 100644 --- a/config.defpy +++ b/config.defpy @@ -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. diff --git a/diff_fuzz.py b/diff_fuzz.py index 13df860..54a0be2 100644 --- a/diff_fuzz.py +++ b/diff_fuzz.py @@ -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, @@ -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))) @@ -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: diff --git a/grammar.py b/mutations.py similarity index 90% rename from grammar.py rename to mutations.py index 526cae8..fe65219 100644 --- a/grammar.py +++ b/mutations.py @@ -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 @@ -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: @@ -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":" @@ -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, +]