Skip to content
Merged
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
77 changes: 44 additions & 33 deletions python/py-fuzzer/fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import enum
import subprocess
import tempfile
from collections.abc import Callable
from dataclasses import KW_ONLY, dataclass
from functools import partial
from pathlib import Path
Expand All @@ -50,14 +51,12 @@
def redknot_contains_bug(code: str, *, red_knot_executable: Path) -> bool:
"""Return `True` if the code triggers a panic in type-checking code."""
with tempfile.TemporaryDirectory() as tempdir:
Path(tempdir, "pyproject.toml").write_text('[project]\n\tname = "fuzz-input"')
Path(tempdir, "input.py").write_text(code)
input_file = Path(tempdir, "input.py")
input_file.write_text(code)
completed_process = subprocess.run(
[red_knot_executable, "check", "--project", tempdir],
capture_output=True,
text=True,
[red_knot_executable, "check", input_file], capture_output=True, text=True
)
return completed_process.returncode != 0 and completed_process.returncode != 1
return completed_process.returncode not in {0, 1, 2}


def ruff_contains_bug(code: str, *, ruff_executable: Path) -> bool:
Expand Down Expand Up @@ -150,39 +149,51 @@ def print_description(self, index: int, num_seeds: int) -> None:
def fuzz_code(seed: Seed, args: ResolvedCliArgs) -> FuzzResult:
"""Return a `FuzzResult` instance describing the fuzzing result from this seed."""
code = generate_random_code(seed)
has_bug = (
contains_new_bug(
code,
bug_found = False
minimizer_callback: Callable[[str], bool] | None = None

if args.baseline_executable_path is None:
if contains_bug(
code, executable=args.executable, executable_path=args.test_executable_path
):
bug_found = True
minimizer_callback = partial(
contains_bug,
executable=args.executable,
executable_path=args.test_executable_path,
)
elif contains_new_bug(
code,
executable=args.executable,
test_executable_path=args.test_executable_path,
baseline_executable_path=args.baseline_executable_path,
):
bug_found = True
minimizer_callback = partial(
contains_new_bug,
executable=args.executable,
test_executable_path=args.test_executable_path,
baseline_executable_path=args.baseline_executable_path,
)
if args.baseline_executable_path is not None
else contains_bug(
code, executable=args.executable, executable_path=args.test_executable_path
)
)
if has_bug:
callback = partial(
contains_bug,
executable=args.executable,
executable_path=args.test_executable_path,
)

if not bug_found:
return FuzzResult(seed, None, args.executable)

assert minimizer_callback is not None

try:
maybe_bug = MinimizedSourceCode(minimize_repro(code, minimizer_callback))
except CouldNotMinimize as e:
# This is to double-check that there isn't a bug in
# `pysource-minimize`/`pysource-codegen`.
# `pysource-minimize` *should* never produce code that's invalid syntax.
try:
maybe_bug = MinimizedSourceCode(minimize_repro(code, callback))
except CouldNotMinimize as e:
# This is to double-check that there isn't a bug in
# `pysource-minimize`/`pysource-codegen`.
# `pysource-minimize` *should* never produce code that's invalid syntax.
try:
ast.parse(code)
except SyntaxError:
raise e from None
else:
maybe_bug = MinimizedSourceCode(code)
ast.parse(code)
except SyntaxError:
raise e from None
else:
maybe_bug = MinimizedSourceCode(code)

else:
maybe_bug = None
return FuzzResult(seed, maybe_bug, args.executable)


Expand Down
Loading