Fix installation of csmith.

Co-authored-by: Mountagha <muntaghaba@gmail.com>
This commit is contained in:
Janggun Lee
2025-02-11 16:08:28 +09:00
parent 00c64a5297
commit 0758005eac

View File

@@ -9,9 +9,7 @@ below `REPLACE_DICT`.
import os import os
import subprocess import subprocess
import itertools
import argparse import argparse
import sys
import re import re
import random import random
from pathlib import Path from pathlib import Path
@@ -26,40 +24,47 @@ REPLACE_DICT = {
r"__attribute__\s*\(\(.*\)\)": "", r"__attribute__\s*\(\(.*\)\)": "",
"_Float128": "double", "_Float128": "double",
"long double": "double", "long double": "double",
"(\+[0-9^FL]*)L": r"\1", r"(\+[0-9^FL]*)L": r"\1",
"union": "struct", "union": "struct",
r"enum[\w\s]*\{[^\}]*\};": "", r"enum[\w\s]*\{[^\}]*\};": "",
r"typedef enum[\w\s]*\{[^;]*;[\s_A-Z]*;": "", r"typedef enum[\w\s]*\{[^;]*;[\s_A-Z]*;": "",
"const char \*const sys_errlist\[\];": "", # ArraySize::Unknown 삭제 r"const char \*const sys_errlist\[\];": "", # ArraySize::Unknown 삭제
r"[^\n]*printf[^;]*;": "", r"[^\n]*printf[^;]*;": "",
r"[^\n]*scanf[^;]*;": "", r"[^\n]*scanf[^;]*;": "",
" restrict": "", " restrict": "",
"inline ": "", "inline ": "",
"_Nullable": "", "_Nullable": "",
"\"g_\w*\", ": "", # transparent_crc에서 프린트 목적으로 받은 StringLiteral 삭제 r'"g_\w*", ': "", # transparent_crc에서 프린트 목적으로 받은 StringLiteral 삭제
"char\* vname, ": "", # transparent_crc에서 사용하지 않는 파라미터 삭제 r"char\* vname, ": "", # transparent_crc에서 사용하지 않는 파라미터 삭제
r"transparent_crc_bytes\s*\([^;]*\);": "", # transparent_crc_bytes 삭제 r"transparent_crc_bytes\s*\([^;]*\);": "", # transparent_crc_bytes 삭제
r"[^\n]*_IO_2_1_[^;]*;": "", # extern을 지우면서 생긴 size를 알 수 없는 struct 삭제 r"[^\n]*_IO_2_1_[^;]*;": "", # extern을 지우면서 생긴 size를 알 수 없는 struct 삭제
r"__asm\s*\([^\)]*\)": "", # asm extension in mac r"__asm\s*\([^\)]*\)": "", # asm extension in mac
r"__asm__\s*\([^\)]*\)": "", # asm extension in linux r"__asm__\s*\([^\)]*\)": "", # asm extension in linux
"typedef __builtin_va_list __gnuc_va_list;": "", "typedef __builtin_va_list __gnuc_va_list;": "",
"typedef __gnuc_va_list va_list;": "", "typedef __gnuc_va_list va_list;": "",
r"\(fabsf\(": "((", r"\(fabsf\(": "((",
# todo: need to consider the case below in the future: # todo: need to consider the case below in the future:
# avoid compile-time constant expressed as complex expression such as `1 + 1` # avoid compile-time constant expressed as complex expression such as `1 + 1`
"char _unused2[^;]*;": "char _unused2[10];", "char _unused2[^;]*;": "char _unused2[10];",
} }
CSMITH_DIR = "csmith-2.3.0" CSMITH_DIR = "csmith-2.3.0"
SKIP_TEST = 102 SKIP_TEST = 102
class ProgressBar: class ProgressBar:
def __init__(self): def __init__(self):
self.stage = 0 self.stage = 0
self.stage_indicators = ["-", "\\", "|", "/", ] self.stage_indicators = [
self.pbar = tqdm.tqdm(total=1, bar_format="{l_bar}{bar}| [Elapsed:{elapsed}, <ETA:{remaining}]") "-",
"\\",
"|",
"/",
]
self.pbar = tqdm.tqdm(
total=1, bar_format="{l_bar}{bar}| [Elapsed:{elapsed}, <ETA:{remaining}]"
)
self.last_progress = 0 self.last_progress = 0
def print_progressbar(self, progress): def print_progressbar(self, progress):
indicator = self.stage_indicators[self.stage % len(self.stage_indicators)] indicator = self.stage_indicators[self.stage % len(self.stage_indicators)]
self.stage += 1 self.stage += 1
@@ -67,50 +72,57 @@ class ProgressBar:
self.pbar.update(progress - self.last_progress) self.pbar.update(progress - self.last_progress)
self.last_progress = progress self.last_progress = progress
def install_csmith(tests_dir):
global CSMITH_DIR
usr_bin_path = "/usr/bin/csmith" def execute_command(command, cwd=None):
usr_inc_path = "/usr/include/csmith" try:
process = subprocess.Popen(
command, shell=True, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
stdout, stderr = process.communicate()
if process.returncode != 0:
raise subprocess.CalledProcessError(
process.returncode, command, stderr.decode("utf-8")
)
print(stdout.decode("utf-8"))
return True
except subprocess.CalledProcessError as e:
print(f"Error executing command: {e.cmd}")
print(e.output)
return False
except Exception as e:
print(f"Unexpected error: {str(e)}")
return False
def install_csmith():
"""
Installation based on the provided in the github repos
of the package. Make sure to have sudo privileges.
"""
usr_bin_path = "/usr/local/bin/csmith"
usr_inc_path = "/usr/local/include/" # cmake dumps the include files here.
if os.path.exists(usr_bin_path): if os.path.exists(usr_bin_path):
assert os.path.exists(usr_inc_path)
return usr_bin_path, usr_inc_path return usr_bin_path, usr_inc_path
bin_path = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "src/csmith")) csmith_dir = os.path.join(os.getcwd(), "csmith")
inc_path = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "runtime")) if not os.path.exists(csmith_dir):
if not os.path.exists(bin_path): if not execute_command(
csmith_filename = "{}.tar.gz".format(CSMITH_DIR) "git clone https://github.com/csmith-project/csmith.git"
try: ):
args = ["curl", "https://embed.cs.utah.edu/csmith/{}".format(csmith_filename), "-o", csmith_filename] raise Exception("Unable to clone the Csmith repository")
proc = subprocess.Popen(args, cwd=tests_dir)
proc.communicate()
if proc.returncode != 0:
raise Exception("Failed to download Csmith (exit code: {}): `{}`".format(proc.returncode, " ".join(args)))
except subprocess.TimeoutExpired as e:
proc.kill()
raise e
try: if not execute_command("sudo apt install -y g++ cmake m4", cwd=csmith_dir):
args = ["tar", "xzvf", csmith_filename] raise Exception("Unable to install dependencies")
proc = subprocess.Popen(args, cwd=tests_dir)
proc.communicate()
if proc.returncode != 0:
raise Exception("Failed to extract Csmith (exit code: {}): `{}`".format(proc.returncode, " ".join(args)))
except subprocess.TimeoutExpired as e:
proc.kill()
raise e
csmith_root_dir = os.path.join(tests_dir, CSMITH_DIR) cmake_command = f"cmake -DCMAKE_INSTALL_PREFIX=/usr/local/ ."
try: if not execute_command(cmake_command, cwd=csmith_dir):
proc = subprocess.Popen("cmake . && make -j", shell=True, cwd=csmith_root_dir) raise Exception("Unable to run cmake.")
proc.communicate()
if proc.returncode != 0: if not execute_command("make && sudo make install", cwd=csmith_dir):
raise Exception("Failed to build Csmith (exit code: {})".format(proc.returncode)) raise Exception("Unable to install.")
except subprocess.TimeoutExpired as e: return usr_bin_path, usr_inc_path
proc.kill()
raise e
return bin_path, inc_path
def generate(tests_dir, bin_path, seed, easy): def generate(tests_dir, bin_path, seed, easy):
"""Feeding options to built Csmith to randomly generate test case. """Feeding options to built Csmith to randomly generate test case.
@@ -122,17 +134,25 @@ def generate(tests_dir, bin_path, seed, easy):
""" """
global CSMITH_DIR global CSMITH_DIR
options = [ options = [
"--no-argc", "--no-arrays", "--no-argc",
"--no-jumps", "--no-pointers", "--no-arrays",
"--no-structs", "--no-unions", "--no-jumps",
"--float", "--strict-float", "--no-pointers",
"--seed", str(seed), "--no-structs",
"--no-unions",
"--float",
"--strict-float",
"--seed",
str(seed),
] ]
if easy: if easy:
options += [ options += [
"--max-block-depth", "2", "--max-block-depth",
"--max-block-size", "2", "2",
"--max-struct-fields", "3", "--max-block-size",
"2",
"--max-struct-fields",
"3",
] ]
args = [bin_path] + options args = [bin_path] + options
@@ -144,17 +164,19 @@ def generate(tests_dir, bin_path, seed, easy):
proc.kill() proc.kill()
raise e raise e
def polish(src, inc_path): def polish(src, inc_path):
"""Polishing test case to fit in kecc parser specification. """Polishing test case to fit in kecc parser specification."""
"""
global REPLACE_DICT, CSMITH_DIR global REPLACE_DICT, CSMITH_DIR
try: try:
args = ["gcc", args = [
"-I", inc_path, "gcc",
"-E", "-I",
"-P", inc_path,
"-", "-E",
"-P",
"-",
] ]
proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(src_preprocessed, err) = proc.communicate(src.encode()) (src_preprocessed, err) = proc.communicate(src.encode())
@@ -169,15 +191,19 @@ def polish(src, inc_path):
return src_replaced return src_replaced
def make_reduce_criteria(tests_dir, fuzz_arg, fuzz_errmsg, analyze): def make_reduce_criteria(tests_dir, fuzz_arg, fuzz_errmsg, analyze):
"""Make executable reduce-criteria.sh """Make executable reduce-criteria.sh"""
"""
# Make shell script i.e. dependent to KECC path # Make shell script i.e. dependent to KECC path
arg_dict = { arg_dict = {
"$PROJECT_DIR": str(Path(tests_dir).parent), "$PROJECT_DIR": str(Path(tests_dir).parent),
"$FUZZ_ARG": fuzz_arg, "$FUZZ_ARG": fuzz_arg,
"$KECC_BIN": str(os.path.abspath(os.path.join(tests_dir, "../target/release/kecc"))), "$KECC_BIN": str(
"$FUZZ_BIN": str(os.path.abspath(os.path.join(tests_dir, "../target/release/fuzz"))), os.path.abspath(os.path.join(tests_dir, "../target/release/kecc"))
),
"$FUZZ_BIN": str(
os.path.abspath(os.path.join(tests_dir, "../target/release/fuzz"))
),
"$FUZZ_ERRMSG": "'{}'".format(fuzz_errmsg), "$FUZZ_ERRMSG": "'{}'".format(fuzz_errmsg),
"$CLANG_ANALYZE": str(analyze).lower(), "$CLANG_ANALYZE": str(analyze).lower(),
} }
@@ -191,33 +217,51 @@ def make_reduce_criteria(tests_dir, fuzz_arg, fuzz_errmsg, analyze):
# chmod the script executable # chmod the script executable
try: try:
args = ["chmod", "u+x", "reduce-criteria.sh"] args = ["chmod", "u+x", "reduce-criteria.sh"]
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) proc = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir
)
proc.communicate() proc.communicate()
if proc.returncode != 0: if proc.returncode != 0:
raise Exception("`{}` failed with exit code {}.".format(" ".join(args), proc.returncode)) raise Exception(
"`{}` failed with exit code {}.".format(" ".join(args), proc.returncode)
)
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
proc.kill() proc.kill()
raise e raise e
def make_fuzz_errmsg(tests_dir, fuzz_arg): def make_fuzz_errmsg(tests_dir, fuzz_arg):
"""Make fuzz error message in reduce-criteria.sh. """Make fuzz error message in reduce-criteria.sh."""
"""
fuzz_errmsg = "panicked" fuzz_errmsg = "panicked"
try: try:
args = ["cargo", "run", "--features=build-bin", "--bin", "fuzz", "--", fuzz_arg, "test_polished.c"] args = [
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) "cargo",
"run",
"--features=build-bin",
"--bin",
"fuzz",
"--",
fuzz_arg,
"test_polished.c",
]
proc = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir
)
(out, err) = proc.communicate() (out, err) = proc.communicate()
if proc.returncode != 0: if proc.returncode != 0:
if "assertion failed" in out.decode(): if "assertion failed" in out.decode():
fuzz_errmsg = "assertion failed" fuzz_errmsg = "assertion failed"
else: else:
raise Exception("Reducing failed because `{}` returned 0".format(" ".join(args))) raise Exception(
"Reducing failed because `{}` returned 0".format(" ".join(args))
)
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
proc.kill() proc.kill()
raise e raise e
return fuzz_errmsg return fuzz_errmsg
def creduce(tests_dir, fuzz_arg, analyze): def creduce(tests_dir, fuzz_arg, analyze):
"""Reduce `tests/test_polished.c` to `tests/test_reduced.c` """Reduce `tests/test_polished.c` to `tests/test_reduced.c`
@@ -230,18 +274,32 @@ def creduce(tests_dir, fuzz_arg, analyze):
try: try:
args = ["cp", "test_polished.c", "test_reduced.c"] args = ["cp", "test_polished.c", "test_reduced.c"]
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) proc = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir
)
proc.communicate() proc.communicate()
if proc.returncode != 0: if proc.returncode != 0:
raise Exception("`{}` failed with exit code {}.".format(" ".join(args), proc.returncode)) raise Exception(
"`{}` failed with exit code {}.".format(" ".join(args), proc.returncode)
)
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
proc.kill() proc.kill()
raise e raise e
try: try:
# --tidy: Do not make a backup copy of each file to reduce as file.orig # --tidy: Do not make a backup copy of each file to reduce as file.orig
args = ["creduce", "--tidy", "--timing", "--timeout", "20", "./reduce-criteria.sh", "test_reduced.c"] args = [
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) "creduce",
"--tidy",
"--timing",
"--timeout",
"20",
"./reduce-criteria.sh",
"test_reduced.c",
]
proc = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir
)
pbar = ProgressBar() pbar = ProgressBar()
while True: while True:
line = proc.stdout.readline() line = proc.stdout.readline()
@@ -250,26 +308,33 @@ def creduce(tests_dir, fuzz_arg, analyze):
line = line.decode() line = line.decode()
if "%" in line: if "%" in line:
try: try:
pbar.print_progressbar(abs(float(line[1:line.index("%")])) / 100) pbar.print_progressbar(abs(float(line[1 : line.index("%")])) / 100)
except: except:
pass # This is for potential error pass # This is for potential error
(out, err) = proc.communicate() (out, err) = proc.communicate()
if proc.returncode != 0: if proc.returncode != 0:
print(out.decode()) print(out.decode())
raise Exception("Reducing test_reduced.c by `{}` failed with exit code {}.".format(" ".join(args), proc.returncode)) raise Exception(
"Reducing test_reduced.c by `{}` failed with exit code {}.".format(
" ".join(args), proc.returncode
)
)
print("Reduce finished.") print("Reduce finished.")
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
proc.kill() proc.kill()
raise e raise e
def fuzz(tests_dir, fuzz_arg, num_iter, easy): def fuzz(tests_dir, fuzz_arg, num_iter, easy):
global SKIP_TEST global SKIP_TEST
csmith_bin, csmith_inc = install_csmith(tests_dir) csmith_bin, csmith_inc = install_csmith()
fuzz_bin = os.path.abspath(os.path.join(tests_dir, "../target/release/fuzz")) fuzz_bin = os.path.abspath(os.path.join(tests_dir, "../target/release/fuzz"))
try: try:
if num_iter is None: if num_iter is None:
print("Fuzzing with infinitely many test cases. Please press [ctrl+C] to break.") print(
"Fuzzing with infinitely many test cases. Please press [ctrl+C] to break."
)
else: else:
assert num_iter > 0 assert num_iter > 0
print("Fuzzing with {} test cases.".format(num_iter)) print("Fuzzing with {} test cases.".format(num_iter))
@@ -279,16 +344,21 @@ def fuzz(tests_dir, fuzz_arg, num_iter, easy):
while True: while True:
print("Test case #{} (skipped: {})".format(i, skip)) print("Test case #{} (skipped: {})".format(i, skip))
src = generate(tests_dir, csmith_bin, random.randint(1, 987654321), easy) src = generate(tests_dir, csmith_bin, random.randint(1, 987654321), easy)
with open(os.path.join(tests_dir, "test.c"), 'w') as dst: with open(os.path.join(tests_dir, "test.c"), "w") as dst:
dst.write(src) dst.write(src)
src_polished = polish(src, csmith_inc) src_polished = polish(src, csmith_inc)
with open(os.path.join(tests_dir, "test_polished.c"), 'w') as dst: with open(os.path.join(tests_dir, "test_polished.c"), "w") as dst:
dst.write(src_polished) dst.write(src_polished)
try: try:
args = [fuzz_bin, fuzz_arg, os.path.join(tests_dir, "test_polished.c")] args = [fuzz_bin, fuzz_arg, os.path.join(tests_dir, "test_polished.c")]
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) proc = subprocess.Popen(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=tests_dir,
)
proc.communicate(timeout=60) proc.communicate(timeout=60)
# KECC sets an exit code of 102 when the test skipped. # KECC sets an exit code of 102 when the test skipped.
@@ -296,11 +366,16 @@ def fuzz(tests_dir, fuzz_arg, num_iter, easy):
skip += 1 skip += 1
continue continue
elif proc.returncode != 0: elif proc.returncode != 0:
raise Exception("Test `{}` failed with exit code {}.".format(" ".join(args), proc.returncode)) raise Exception(
"Test `{}` failed with exit code {}.".format(
" ".join(args), proc.returncode
)
)
i += 1 i += 1
if num_iter is not None: if num_iter is not None:
if i > num_iter: break if i > num_iter:
break
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
proc.kill() proc.kill()
skip += 1 skip += 1
@@ -308,21 +383,38 @@ def fuzz(tests_dir, fuzz_arg, num_iter, easy):
proc.terminate() proc.terminate()
print("\n[Ctrl+C] interrupted") print("\n[Ctrl+C] interrupted")
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Fuzzing KECC.') parser = argparse.ArgumentParser(description="Fuzzing KECC.")
parser.add_argument('-n', '--num', type=int, help='The number of tests') 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') parser.add_argument(
parser.add_argument('-i', '--irgen', action='store_true', help='Fuzzing irgen') "-p", "--print", action="store_true", help="Fuzzing C AST printer"
parser.add_argument('-r', '--reduce', action='store_true', help="Reducing input file") )
parser.add_argument('--skip-build', action='store_true', help="Skipping cargo build") parser.add_argument("-i", "--irgen", action="store_true", help="Fuzzing irgen")
parser.add_argument('--easy', action='store_true', help="Generate more easy code by csmith option") parser.add_argument(
parser.add_argument('--seed', type=int, help="Provide seed of fuzz generation", default=-1) "-r", "--reduce", action="store_true", help="Reducing input file"
parser.add_argument('--clang-analyze', action='store_true', help="Use clang static analyzer for reducing. It prevents undefined behaviors coming from reduced program, but perhaps take a long time to do so") )
parser.add_argument(
"--skip-build", action="store_true", help="Skipping cargo build"
)
parser.add_argument(
"--easy", action="store_true", help="Generate more easy code by csmith option"
)
parser.add_argument(
"--seed", type=int, help="Provide seed of fuzz generation", default=-1
)
parser.add_argument(
"--clang-analyze",
action="store_true",
help="Use clang static analyzer for reducing. It prevents undefined behaviors coming from reduced program, but perhaps take a long time to do so",
)
args = parser.parse_args() args = parser.parse_args()
if args.print and args.irgen: if args.print and args.irgen:
raise Exception("Choose an option used for fuzzing: '--print' or '--irgen', NOT both") raise Exception(
"Choose an option used for fuzzing: '--print' or '--irgen', NOT both"
)
if args.print: if args.print:
fuzz_arg = "-p" fuzz_arg = "-p"
elif args.irgen: elif args.irgen:
@@ -331,26 +423,41 @@ if __name__ == "__main__":
raise Exception("Specify fuzzing argument") raise Exception("Specify fuzzing argument")
if args.seed != -1: if args.seed != -1:
print('Set seed as', args.seed) print("Set seed as", args.seed)
random.seed(args.seed) random.seed(args.seed)
else: else:
print('Use default random seed') print("Use default random seed")
tests_dir = os.path.abspath(os.path.dirname(__file__)) tests_dir = os.path.abspath(os.path.dirname(__file__))
if not args.skip_build: if not args.skip_build:
print("Building KECC..") print("Building KECC..")
try: try:
proc = subprocess.Popen(["cargo", "build", "--features=build-bin", "--release", "--bin", "fuzz", "--bin", "kecc"], cwd=tests_dir) proc = subprocess.Popen(
[
"cargo",
"build",
"--features=build-bin",
"--release",
"--bin",
"fuzz",
"--bin",
"kecc",
],
cwd=tests_dir,
)
proc.communicate() proc.communicate()
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
proc.kill() proc.kill()
raise e raise e
else: else:
print("Skip building. Please run `cargo build --features=build-bin --release --bin fuzz --bin kecc` to manually build.") print(
"Skip building. Please run `cargo build --features=build-bin --release --bin fuzz --bin kecc` to manually build."
)
if args.reduce: if args.reduce:
import tqdm import tqdm
creduce(tests_dir, fuzz_arg, args.clang_analyze) creduce(tests_dir, fuzz_arg, args.clang_analyze)
else: else:
fuzz(tests_dir, fuzz_arg, args.num, args.easy) fuzz(tests_dir, fuzz_arg, args.num, args.easy)