diff --git a/README.md b/README.md index ae46e90..8d33ca7 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ Currently, there are two scripts: 2) flags > Minimize the compiler flags required to reproduce behavior. Use after prep +3) code +> Minimize a target `.i` file's source code using C-Vise. Use after prep + ## Dependencies * Python 3.11 * [icecream](https://pypi.org/project/icecream/) @@ -46,3 +49,11 @@ Currently, there are two scripts: > **Note** > Change your cwd to wherever `flags.txt` is located or try the `-p` option. + +### code +> Minimize a `.i` file after using `prep` and, preferably, `flags` + +> **Note** +> This script simply wraps C-Vise and will yield better results based on how good your interestingness test is + +`$ python reduce.py code` diff --git a/reduce.py b/reduce.py index 7979ab5..93376b3 100755 --- a/reduce.py +++ b/reduce.py @@ -9,7 +9,7 @@ import argparse import sys -from src import flags, prep +from src import flags, prep, code VERSION = "0.2.1" @@ -47,9 +47,18 @@ def parse_cli_args() -> argparse.Namespace: formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) prep.setup_argparser(prep_parser) - prep_parser.set_defaults(func=prep.main) + code_parser = subparsers.add_parser( + "code", + prog="prepreduce", + description=f"A script to reduce code using cvise", + epilog="Will use:\n\t$ cvise test.sh target.i\n", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + code.setup_argparser(code_parser) + code_parser.set_defaults(func=code.main) + if len(sys.argv) == 1: parser.print_help(sys.stderr) sys.exit(1) diff --git a/src/cli.py b/src/cli.py index 45d77f7..738bee3 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,3 +1,5 @@ +import sys + from argparse import ArgumentTypeError from pathlib import Path from .types import Command @@ -18,7 +20,7 @@ class Colors: def error(msg: str) -> None: print(f"{Colors.FAIL}[ERROR]{Colors.ENDC} {msg}") - exit(1) + sys.exit(1) def warn(msg: str, ret_only: bool = False) -> str: diff --git a/src/code.py b/src/code.py new file mode 100644 index 0000000..8bcba97 --- /dev/null +++ b/src/code.py @@ -0,0 +1,78 @@ +import argparse +import subprocess +from pathlib import Path + +from icecream import ic + +from .cli import error, info + + +def find_dot_i_files() -> list[str]: + info("Looking for .i files to reduce") + results = Path.cwd().glob(pattern=r"*.i") + + return [x.name for x in results] + + +def use_cvise(target: str) -> None: + i_files = find_dot_i_files() + if len(i_files) < 1: + error( + "Couldn't find any .i files in ./\n\tDid you run:\n\t$ python3 reduce.py prep" + ) + if len(i_files) > 1: + error( + "Couldn't automatically determine the .i file to target for reduction.\n" + "There may be multiple .i files in this directory.\n" + "Choose a specific one with the -t flag" + ) + + chosen = i_files[0] if not target else target + ic(chosen) + + ic(i_files) + + if not Path("./test.sh").exists(): + error("Couldn't find test.sh in ./\n\tDid you run:\n\t$ python3 reduce.py prep") + + cmd = ["cvise", "test.sh", chosen] + + info(f"Reducing {chosen} with C-Vise") + + result = subprocess.run(cmd, stdout=subprocess.PIPE) + if result.returncode == 1: + error( + "C-Vise error...\n\tDid you specifiy a target file with -t which does not exist?" + ) + ic(result) + + if "cannot run" in result.stdout.decode(): + print(result.stdout.decode()) + error("Please write an interestingness test in ./test.sh") + + +def main(cli_args: argparse.Namespace) -> None: + ic.enable() if cli_args.debug else ic.disable() + ic(cli_args) + + use_cvise(cli_args.target) + + +def setup_argparser(parser: argparse.ArgumentParser) -> None: + """ + Parse out arguments from command line interface. + """ + parser.add_argument( + "-d", + "--debug", + help="Enable Debug logs for $ reduce code", + action="store_true", + default=False, + ) + + parser.add_argument( + "-t", + "--target", + help="Which (if multiple) .i file to target for reduction", + default="", + ) diff --git a/src/prep.py b/src/prep.py index c1bbe5c..19a8511 100755 --- a/src/prep.py +++ b/src/prep.py @@ -128,12 +128,9 @@ def clean_compiler_invocation(cc_invocation: Command) -> Command: return cleaned -def cc_invocation_to_flags(cc_invocation: Command) -> str: +def cc_invocation_to_flags(cc_invocation: Command, target: Path) -> str: """ Accepts a full cc_invocation command and parses out just the flags - - The order of the pop()'s are important as the relative index of items - changes as items are removed """ cc_flags = cc_invocation[1:].copy() # ditch the `clang` or `gcc` invocation @@ -141,9 +138,10 @@ def cc_invocation_to_flags(cc_invocation: Command) -> str: cc_flags.pop(dash_o_idx + 1) # remove target.o cc_flags.pop(dash_o_idx) # remove -o - dash_c_idx = cc_flags.index("-c") - cc_flags.pop(dash_c_idx + 1) # remove target.c - cc_flags.pop(dash_c_idx) # remove -c + cc_flags.remove("-c") + + adjusted_target = target.with_suffix(".i").name + cc_flags.remove(str(adjusted_target)) return "\n".join([str(x) for x in cc_flags]) # cast is necessary for Paths @@ -173,7 +171,7 @@ def write_test_script( if script_path.exists(): double_check_removal_with_user(file=script_path, force_rm=force_rm) - cc_flags = cc_invocation_to_flags(cc_invocation) + cc_flags = cc_invocation_to_flags(cc_invocation, target) flags_output_file = output_dir / Path("flags.txt") write_flags_txt(cc_flags, flags_output_file) @@ -194,14 +192,14 @@ def write_test_script( info(f"Added execute permissions to {script_path}") todo( "Now, modify the last line of test.sh with an interestingness test \n" - "that properly captures the behavior you're after. After that, " - "use test.sh with \nreduction tools like cvise.\n" - "Example Usage: $ cvise test.sh string.i" + "that properly captures the behavior you're after. \nAfter that, " + "run: \n$ python3 reduce.py code\n" + "Or, optionally, run cvise manually with: \n" + f"$ cvise test.sh {target.with_suffix('.i').name}" ) def double_check_removal_with_user(*, file: Path, force_rm: bool) -> None: - # TODO: add optional rename while not force_rm: warning_msg = warn( f"target {file} already exists. Remove it? (y/n): ", ret_only=True