Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: --solver-command lets user replace z3 invocations #272

Merged
merged 1 commit into from
Apr 11, 2024
Merged
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
99 changes: 53 additions & 46 deletions src/halmos/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,25 +1017,52 @@ def copy_model(model: Model) -> Dict:
def solve(
query: str, args: Namespace, dump_filename: Optional[str] = None
) -> Tuple[CheckSatResult, Model]:
if dump_filename:
if args.dump_smt_queries or args.solver_command:
if not dump_filename:
dump_filename = f"/tmp/{uuid.uuid4().hex}.smt2"

with open(dump_filename, "w") as f:
print(f"Writing SMT query to {dump_filename}")
f.write("(set-logic QF_AUFBV)\n")
f.write(query)
f.write("(get-model)\n")

solver = mk_solver(args, ctx=Context(), assertion=True)
solver.from_string(query)
result = solver.check()
model = copy_model(solver.model()) if result == sat else None
return result, model
if args.solver_command:
if args.verbose >= 1:
print(f" Checking with external solver process")
print(f" {args.solver_command} {dump_filename} >{dump_filename}.out")

cmd = args.solver_command.split() + [dump_filename]
res_str = subprocess.run(cmd, capture_output=True, text=True).stdout.strip()
res_str_head = res_str.split("\n", 1)[0]

with open(f"{dump_filename}.out", "w") as f:
f.write(res_str)

if args.verbose >= 1:
print(f" {res_str_head}")

if res_str_head == "unsat":
return unsat, None
elif res_str_head == "sat":
return sat, f"{dump_filename}.out"
else:
return unknown, None

else:
solver = mk_solver(args, ctx=Context(), assertion=True)
solver.from_string(query)
result = solver.check()
model = copy_model(solver.model()) if result == sat else None
return result, model


def gen_model_from_sexpr(fn_args: GenModelArgs) -> ModelWithContext:
args, idx, sexpr = fn_args.args, fn_args.idx, fn_args.sexpr

dump_dirname = fn_args.dump_dirname if args.dump_smt_queries else None
dump_filename = f"{dump_dirname}/{idx+1}.smt2" if dump_dirname else None
if dump_dirname:
dump_dirname = fn_args.dump_dirname
dump_filename = f"{dump_dirname}/{idx+1}.smt2"
if args.dump_smt_queries or args.solver_command:
if not os.path.isdir(dump_dirname):
os.makedirs(dump_dirname)
print(f"Generating SMT queries in {dump_dirname}")
Expand All @@ -1049,39 +1076,8 @@ def gen_model_from_sexpr(fn_args: GenModelArgs) -> ModelWithContext:
if args.verbose >= 1:
print(f" Checking again with refinement")

res, model = solve(
refine(sexpr),
args,
dump_filename.replace(".smt2", ".refined.smt2") if dump_filename else None,
)

if args.solver_subprocess and is_unknown(res, model):
fname = f"/tmp/{uuid.uuid4().hex}.smt2"

if args.verbose >= 1:
print(f" Checking again in an external process")
print(f" {args.solver_subprocess_command} {fname} >{fname}.out")

query = refine(sexpr)
with open(fname, "w") as f:
f.write("(set-logic QF_AUFBV)\n")
f.write(query)

cmd = args.solver_subprocess_command.split() + [fname]
res_str = subprocess.run(cmd, capture_output=True, text=True).stdout.strip()
res_str_head = res_str.split("\n", 1)[0]

with open(f"{fname}.out", "w") as f:
f.write(res_str)

if args.verbose >= 1:
print(f" {res_str_head}")

if res_str_head == "unsat":
res = unsat
elif res_str_head == "sat":
res = sat
model = f"{fname}.out"
refined_filename = dump_filename.replace(".smt2", ".refined.smt2")
res, model = solve(refine(sexpr), args, refined_filename)

return package_result(model, idx, res, args)

Expand Down Expand Up @@ -1140,10 +1136,21 @@ def package_result(
def is_model_valid(model: AnyModel) -> bool:
# TODO: evaluate the path condition against the given model after excluding evm_* symbols,
# since the evm_* symbols may still appear in valid models.
for decl in model:
if str(decl).startswith("evm_"):
return False
return True

# model is a filename, containing solver output
if isinstance(model, str):
with open(model, "r") as f:
for line in f:
if "evm_" in line:
return False
return True

# z3 model object
else:
for decl in model:
if str(decl).startswith("evm_"):
return False
return True


def to_str_model(model: Model, print_full_model: bool) -> StrModel:
Expand Down
15 changes: 2 additions & 13 deletions src/halmos/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,20 +214,9 @@ def mk_arg_parser() -> argparse.ArgumentParser:
help="set memory limit (in megabytes) for the solver; 0 means no limit (default: %(default)s)",
)
group_solver.add_argument(
"--solver-fresh",
action="store_true",
help="run an extra solver with a fresh state for unknown",
)
group_solver.add_argument(
"--solver-subprocess",
action="store_true",
help="run an extra solver in subprocess for unknown",
)
group_solver.add_argument(
"--solver-subprocess-command",
"--solver-command",
metavar="COMMAND",
default="z3 -model",
help="use the given command for the subprocess solver (requires --solver-subprocess) (default: '%(default)s')",
help="use the given command when invoking the solver, e.g. `z3 -model`",
)
group_solver.add_argument(
"--solver-parallel",
Expand Down
Loading