Files
cs420/tests/fuzz.py
2020-03-20 05:02:58 +00:00

130 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Fuzzes KECC.
For customization, one may restrict/loosen the replacement rule by adding/deleting the pair into
below `REPLACE_DICT`.
"""
import os
import subprocess
import itertools
import argparse
import sys
import re
REPLACE_DICT = {
"#include \"csmith.h\"": "",
"volatile ": "",
"uint16_t": "unsigned int",
"uint32_t": "unsigned int",
"int16_t": "int",
"int32_t": "int",
"uint": "unsigned int",
"static ": "",
}
CSMITH_DIR = "csmith-2.3.0"
def install_csmith(tests_dir, bin_file):
global CSMITH_DIR
csmith_root_dir = os.path.join(tests_dir, CSMITH_DIR)
if not os.path.exists(bin_file):
subprocess.Popen(["curl", "https://embed.cs.utah.edu/csmith/" + CSMITH_DIR + ".tar.gz", "-o", CSMITH_DIR + ".tar.gz"], cwd=tests_dir).communicate()
subprocess.Popen(["tar", "xzvf", CSMITH_DIR + ".tar.gz"], cwd=tests_dir).communicate()
subprocess.Popen("cmake .; make -j", shell=True, cwd=csmith_root_dir).communicate()
else:
print("Using the existing csmith...")
def generate(tests_dir, bin_file, runtime, file_name):
"""Feeding options to built Csmith to randomly generate test case.
For generality, I disabled most of the features that are enabled by default.
FYI, please take a look at `-h` flag. By adding or deleting one of `--blah-blah...`
in `options` list below, csmith will be able to generate corresponding test case.
A developer may customize the options to meet one's needs for testing.
"""
global CSMITH_DIR
options = [
"--no-argc", "--no-arrays", "--no-checksum",
"--no-jumps", "--no-longlong", "--no-int8",
"--no-uint8", "--no-safe-math", "--no-pointers",
"--no-structs", "--no-unions", "--no-builtins"
]
args = [bin_file] + options
dst_path = os.path.join(runtime, file_name)
with open(dst_path, 'w') as f_dst:
subprocess.Popen(args, cwd=tests_dir, stdout=f_dst).wait()
f_dst.flush()
return dst_path
def preprocess(src_path, file_name):
"""Preprocessing test case to fit in kecc parser specification.
It resolves an issue that arbitrarily included header file to hinder parsing.
"""
global REPLACE_DICT, CSMITH_DIR
with open(src_path, 'r') as src:
src = str(src.read())
for _from, _to in REPLACE_DICT.items():
src = src.replace(_from, _to)
with open(os.path.join(os.path.dirname(src_path), file_name), 'w') as dst:
dst.write(str(src))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Fuzzing KECC.')
parser.add_argument('-n', '--num', type=int, help='The number of tests')
parser.add_argument('-p', '--print', action='store_true', help='Fuzzing C AST printer')
args = parser.parse_args()
if args.print:
cargo_arg = "-p"
else:
raise "Specify fuzzing argument"
tests_dir = os.path.abspath(os.path.dirname(__file__))
csmith_bin = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "src/csmith"))
csmith_runtime = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "runtime/"))
install_csmith(tests_dir, csmith_bin)
# Run cargo test infinitely
raw_test_file = "raw_test.c"
test_file = "test.c"
try:
print("Building KECC..")
try:
proc = subprocess.Popen(["cargo", "build", "--release"], cwd=tests_dir)
proc.communicate()
except subprocess.TimeoutExpired as e:
proc.kill()
raise e
if args.num is None:
print("Fuzzing with infinitely many test cases. Please press [ctrl+C] to break.")
iterator = itertools.count(0)
else:
print("Fuzzing with {} test cases.".format(args.num))
iterator = range(args.num)
for i in iterator:
print("Test case #{}".format(i))
preprocess(generate(tests_dir, csmith_bin, csmith_runtime, raw_test_file), test_file)
args = ["cargo", "run", "--release", "--bin", "fuzz", "--", cargo_arg, os.path.join(csmith_runtime, test_file)]
try:
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir)
(out, err) = proc.communicate(timeout=10)
if proc.returncode != 0:
raise Exception("Test `{}` failed with exit code {}.".format(" ".join(args), proc.returncode))
except subprocess.TimeoutExpired as e:
proc.kill()
raise e
except KeyboardInterrupt:
proc.terminate()
print("\n[Ctrl+C] interrupted")