Skip to content
Open
Show file tree
Hide file tree
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down Expand Up @@ -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`
13 changes: 11 additions & 2 deletions reduce.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import argparse
import sys

from src import flags, prep
from src import flags, prep, code


VERSION = "0.2.1"
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sys

from argparse import ArgumentTypeError
from pathlib import Path
from .types import Command
Expand All @@ -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:
Expand Down
78 changes: 78 additions & 0 deletions src/code.py
Original file line number Diff line number Diff line change
@@ -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="",
)
22 changes: 10 additions & 12 deletions src/prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,20 @@ 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

dash_o_idx = cc_flags.index("-o")
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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down