Skip to content
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
2 changes: 1 addition & 1 deletion coq_tools/custom_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ def argstring_to_iterable(arg):


def append_coq_arg(env, arg, passing=""):
for key in ("coqc_args", "coqtop_args"):
for key in ("coqc_args", "coqtop_args", "coqchk_args"):
env[passing + key] = tuple(
list(env.get(passing + key, [])) + list(argstring_to_iterable(arg))
)
Expand Down
118 changes: 115 additions & 3 deletions coq_tools/diagnose_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from .file_util import clean_v_file
from .util import re_escape, get_peak_rss_bytes, apply_memory_limit, cleanup_cgroup
from .custom_arguments import LOG_ALWAYS
from .coq_version import group_coq_args, get_coqc_help, get_coq_accepts_Q
from .coq_version import (
group_coq_args,
get_coqc_help,
get_coq_accepts_Q,
)
from . import util

__all__ = [
Expand Down Expand Up @@ -42,6 +46,26 @@ def clean_output(output):
return adjust_error_message_for_selected_errors(util.normalize_newlines(output))


def process_coqchk_output(output, v_file_name, line_count):
"""
Process coqchk output and insert error preamble before "Fatal Error:" lines.

When coqchk outputs a line starting with "Fatal Error:", we emit a fake Coq
error location pointing to the end of the .v file before that line.
"""
lines = output.split("\n")
result_lines = []
for line in lines:
if line.startswith("Fatal Error:"):
# Insert fake Coq error location before the Fatal Error line
result_lines.append(
f'File "{v_file_name}", line {line_count}, characters 0-0:'
)
result_lines.append("Error:")
result_lines.append(line)
return "\n".join(result_lines)


@memoize
def get_coq_accepts_fine_grained_debug(coqc, debug_kind):
temp_file = tempfile.NamedTemporaryFile(suffix=".v", dir=".", delete=True)
Expand Down Expand Up @@ -548,8 +572,11 @@ def reset_coq_output_cache(
pass_on_stdin=False,
verbose_base=1,
ocamlpath=None,
coqchk_prog=None,
coqchk_prog_args=(),
**kwargs,
):
key_extra = (coqchk_prog, tuple(coqchk_prog_args)) if coqchk_prog else ()
key, file_name, cmds, input_val, cleaner, env = prepare_cmds_for_coq_output(
coqc_prog,
coqc_prog_args,
Expand All @@ -562,6 +589,7 @@ def reset_coq_output_cache(
ocamlpath=ocamlpath,
**kwargs,
)
key = key + key_extra
cleaner()

if key in COQ_OUTPUT.keys():
Expand All @@ -581,10 +609,18 @@ def get_coq_output(
lambda output: "is not a compiled interface for this version of OCaml" in output
),
ocamlpath=None,
coqchk_prog=None,
coqchk_prog_args=(),
**kwargs,
):
"""Returns the coqc output of running through the given
contents. Pass timeout_val = None for no timeout."""
contents. Pass timeout_val = None for no timeout.

If coqchk_prog is provided, after coqc succeeds, coqchk will be run on the
resulting .vo file. The output is combined, but if coqc fails, we end early.
When coqchk outputs "Fatal Error:", a fake Coq error location pointing to
the end of the .v file is inserted before that line.
"""
if (
timeout_val is not None
and timeout_val < 0
Expand All @@ -601,9 +637,12 @@ def get_coq_output(
verbose_base=verbose_base,
retry_with_debug_when=retry_with_debug_when,
ocamlpath=ocamlpath,
coqchk_prog=coqchk_prog,
coqchk_prog_args=coqchk_prog_args,
**kwargs,
)

key_extra = (coqchk_prog, tuple(coqchk_prog_args)) if coqchk_prog else ()
key, file_name, cmds, input_val, cleaner, env = prepare_cmds_for_coq_output(
coqc_prog,
coqc_prog_args,
Expand All @@ -617,6 +656,9 @@ def get_coq_output(
**kwargs,
)

# Extend key to include coqchk info
key = key + key_extra

if key in COQ_OUTPUT.keys():
cleaner()
return COQ_OUTPUT[key][1]
Expand Down Expand Up @@ -659,10 +701,78 @@ def get_coq_output(
)
if get_timeout(coqc_prog) is None and timeout_val is not None:
set_timeout(coqc_prog, 3 * max((1, int(math.ceil(finish - start)))), **kwargs)

combined_stdout = util.s(stdout)
combined_returncode = returncode
combined_runtime = runtime
combined_peak_rss_kb = peak_rss_kb

# If coqchk is enabled and coqc succeeded, run coqchk
if coqchk_prog is not None and returncode == 0:
# Build coqchk command: coqchk -o -silent <args> <vo_file>
vo_file = os.path.splitext(file_name)[0] + ".vo"
assert isinstance(coqchk_prog, tuple), coqchk_prog
coqchk_cmds = [*coqchk_prog, *coqchk_prog_args, vo_file]
chk_start = time.time()
((chk_stdout, chk_stderr), chk_returncode, chk_peak_rss_bytes) = (
memory_robust_timeout_Popen_communicate(
kwargs["log"],
coqchk_cmds,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
timeout=(
timeout_val if timeout_val is not None and timeout_val > 0 else None
),
cwd=cwd,
env=env,
**extra_kwargs,
)
)
chk_finish = time.time()
chk_runtime = chk_finish - chk_start
chk_peak_rss_kb = chk_peak_rss_bytes / 1024

kwargs["log"](
"\ncoqchk retcode: %d\nstdout:\n%s\n\nstderr:\n%s\nruntime:\n%f\npeak_rss:\n%f kb\n\n"
% (
chk_returncode,
util.s(chk_stdout),
util.s(chk_stderr),
chk_runtime,
chk_peak_rss_kb,
),
level=verbose_base + 1,
)

# Count lines in the .v file for error location
line_count = contents.count("\n") + 1

# Process coqchk output to insert error preamble before "Fatal Error:" lines
processed_chk_output = process_coqchk_output(
util.s(chk_stdout), file_name, line_count
)

# Combine outputs
combined_stdout = combined_stdout + "\n" + processed_chk_output
combined_returncode = (
chk_returncode if chk_returncode != 0 else combined_returncode
)
combined_runtime = runtime + chk_runtime
combined_peak_rss_kb = max(peak_rss_kb, chk_peak_rss_kb)

# Now clean up after coqchk has run (or if coqchk wasn't needed)
cleaner()

COQ_OUTPUT[key] = (
file_name,
(clean_output(util.s(stdout)), tuple(cmds), returncode, runtime, peak_rss_kb),
(
clean_output(combined_stdout),
tuple(cmds),
combined_returncode,
combined_runtime,
combined_peak_rss_kb,
),
)
kwargs["log"](
"Storing result: COQ_OUTPUT[%s]:\n%s" % (repr(key), repr(COQ_OUTPUT[key])),
Expand All @@ -684,6 +794,8 @@ def get_coq_output(
verbose_base=verbose_base,
retry_with_debug_when=(lambda output: False),
ocamlpath=ocamlpath,
coqchk_prog=coqchk_prog,
coqchk_prog_args=coqchk_prog_args,
**kwargs,
)
return COQ_OUTPUT[key][1]
105 changes: 86 additions & 19 deletions coq_tools/find_bug.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,30 @@
action="append",
help="The path to the coqchk program for the passing coqc (only used with --chk).",
)
parser.add_argument(
"--coqchk-args",
metavar="ARG",
dest="coqchk_args",
type=str,
action="append",
help='Arguments to pass to coqchk (both passing and nonpassing); e.g., " -silent" (leading and trailing spaces are stripped, only used with --chk)',
)
parser.add_argument(
"--nonpassing-coqchk-args",
metavar="ARG",
dest="nonpassing_coqchk_args",
type=str,
action="append",
help='Arguments to pass to nonpassing coqchk; e.g., " -silent" (leading and trailing spaces are stripped, only used with --chk)',
)
parser.add_argument(
"--passing-coqchk-args",
metavar="ARG",
dest="passing_coqchk_args",
type=str,
action="append",
help='Arguments to pass to passing coqchk; e.g., " -silent" (leading and trailing spaces are stripped, only used with --chk)',
)
parser.add_argument(
"--passing-coqc",
metavar="COQC",
Expand Down Expand Up @@ -1072,6 +1096,8 @@ def get_error_reg_string(output_file_name, **kwargs):
verbose_base=1,
cwd=kwargs["base_dir"],
ocamlpath=kwargs["nonpassing_ocamlpath"],
coqchk_prog=kwargs["coqchk"],
coqchk_prog_args=kwargs["coqchk_args"],
**kwargs,
)
result = ""
Expand Down Expand Up @@ -1313,6 +1339,8 @@ def get_padded_contents():
is_coqtop=kwargs["coqc_is_coqtop"],
verbose_base=2,
ocamlpath=kwargs["nonpassing_ocamlpath"],
coqchk_prog=kwargs["coqchk"],
coqchk_prog_args=kwargs["coqchk_args"],
**kwargs,
)
if not should_succeed and diagnose_error.has_error(
Expand Down Expand Up @@ -1346,6 +1374,8 @@ def get_padded_contents():
is_coqtop=kwargs["passing_coqc_is_coqtop"],
verbose_base=2,
ocamlpath=kwargs["passing_ocamlpath"],
coqchk_prog=kwargs["passing_coqchk"],
coqchk_prog_args=kwargs["passing_coqchk_args"],
**kwargs,
)
if not (
Expand Down Expand Up @@ -3413,6 +3443,8 @@ def minimize_file(
verbose_base=2,
cwd=env["base_dir"],
ocamlpath=env["nonpassing_ocamlpath"],
coqchk_prog=env["coqchk"],
coqchk_prog_args=env["coqchk_args"],
**env,
)
line_num = diagnose_error.get_error_line_number(output, env["error_reg_string"])
Expand Down Expand Up @@ -3809,6 +3841,8 @@ def get_test_output(
is_coqtop=kwargs["coqc_is_coqtop"],
verbose_base=2,
ocamlpath=kwargs["nonpassing_ocamlpath"],
coqchk_prog=kwargs["coqchk"],
coqchk_prog_args=kwargs["coqchk_args"],
**kwargs,
)
)
Expand Down Expand Up @@ -4215,11 +4249,31 @@ def prepend_coqbin(prog):
"coq_makefile": prepend_coqbin(args.coq_makefile or "coq_makefile"),
"coqdep": prepend_coqbin(args.coqdep),
"chk": args.chk,
"coqchk": prepend_coqbin(args.coqchk or "coqchk"),
"coqchk": (prepend_coqbin(args.coqchk or "coqchk") if args.chk else None),
"passing_coqchk": (
prepend_coqbin(args.passing_coqchk)
if args.passing_coqchk
else prepend_coqbin(args.coqchk or "coqchk")
(
prepend_coqbin(args.passing_coqchk)
if args.passing_coqchk
else prepend_coqbin(args.coqchk or "coqchk")
)
if args.chk
else None
),
"coqchk_args": tuple(
i.strip()
for i in (
list(process_maybe_list(args.nonpassing_coqchk_args, log=args.log))
+ list(process_maybe_list(args.coqchk_args, log=args.log))
+ list(process_maybe_list(args.coq_args, log=args.log))
)
),
"passing_coqchk_args": tuple(
i.strip()
for i in (
list(process_maybe_list(args.passing_coqchk_args, log=args.log))
+ list(process_maybe_list(args.coqchk_args, log=args.log))
+ list(process_maybe_list(args.coq_args, log=args.log))
)
),
"passing_coqc_args": tuple(
i.strip()
Expand Down Expand Up @@ -4414,17 +4468,6 @@ def make_make_coqc(coqc_prog, **kwargs):
env["passing_coqc"] = env["coqtop"]
env["passing_make_coqc"] = make_make_coqc(env["passing_coqc"], **env)

if env["chk"]:
# Wrap coqc with coqchk-as-coqc.sh to also run coqchk after coqc
coqchk_wrapper = str(util.resource_path("coqchk-as-coqc.sh"))
env["coqc"] = (coqchk_wrapper, *env["coqc"], *env["coqchk"])
if env["passing_coqc"] is not None:
env["passing_coqc"] = (
coqchk_wrapper,
*env["passing_coqc"],
*env["passing_coqchk"],
)

coqc_help = get_coqc_help(get_preferred_passing("coqc", **env), **env)
coqc_version = get_coqc_version(env["coqc"], **env)

Expand Down Expand Up @@ -4559,6 +4602,17 @@ def make_make_coqc(coqc_prog, **kwargs):
env["passing_coqtop"] if env["passing_coqtop"] else env["coqtop"],
"passing_",
),
) + (
(
("coqchk_args", env["coqchk"], ""),
(
"passing_coqchk_args",
env["passing_coqchk"] if env["passing_coqchk"] else env["coqchk"],
"passing_",
),
)
if env["chk"]
else ()
):
env[args_name] = tuple(list(env[args_name]) + list(extra_args))
for dirname, libname in env.get(passing_prefix + "libnames", []):
Expand Down Expand Up @@ -4587,14 +4641,28 @@ def make_make_coqc(coqc_prog, **kwargs):
if arg[0] == "-I":
env.get(passing_prefix + "ocaml_dirnames", []).append(arg[1])

for args_name, coqtop_prog, coqc_prog, passing_prefix in (
("coqtop_args", env["coqtop"], env["coqc"], ""),
for coqprog, args_name, coqtop_prog, coqc_prog, passing_prefix in (
("coqtop", "coqtop_args", env["coqtop"], env["coqc"], ""),
(
"coqtop",
"passing_coqtop_args",
env["passing_coqtop"] if env["passing_coqtop"] else env["coqtop"],
env["passing_coqc"] if env["passing_coqc"] else env["coqc"],
"passing_",
),
) + (
(
("coqchk", "coqchk_args", env["coqchk"], env["coqc"], ""),
(
"passing_coqchk",
"passing_coqchk_args",
env["passing_coqchk"] if env["passing_coqchk"] else env["coqchk"],
env["passing_coqc"] if env["passing_coqc"] else env["coqc"],
"passing_",
),
)
if env["chk"]
else ()
):
coqc_prog_help = get_coqc_help(coqc_prog, **env)
coqtop_prog_help = get_coqc_help(coqtop_prog, **env)
Expand All @@ -4607,8 +4675,7 @@ def make_make_coqc(coqc_prog, **kwargs):
)
if len(coqc_but_not_coqtop_args) > 0:
env["log"](
"Warning: skipping arguments to coqtop that are only valid for coqc: %s"
% repr(coqc_but_not_coqtop_args),
f"Warning: skipping arguments to {coqprog} that are only valid for coqc: {coqc_but_not_coqtop_args!r}",
level=2,
)
env[args_name] = tuple(
Expand Down
Loading