Skip to content

Commit 43ae32d

Browse files
authored
make_checkpoint.py: detect OCaml exceptions during loading (#175)
The send_and_wait() function sends user code followed by a sentinel Printf as the next toplevel phrase. When the user code raises an exception (e.g. a failed proof or missing file), the OCaml toplevel catches it, prints the error, and continues to the next phrase, so the sentinel always appears regardless of success or failure. This means make_checkpoint.py silently creates checkpoints of broken or partial states. Fix: wrap each expression in try/with that emits a unique error sentinel (HOL_MCP_LOAD_ERROR) on exception. wait_for_line() detects this sentinel and reports the exception with a clear error message before the script can proceed to checkpointing. Also reject expressions containing ';;' (multiple toplevel phrases) upfront, since try/with can only wrap a single expression. The error message guides the user to place composite inputs in a file and load with needs or loadt instead. This does not affect the MCP server's interactive use, where the LLM reads the full output and sees exception messages directly. It only affects make_checkpoint.py's batch usage, where intermediate output is discarded and only sentinel arrival is checked.
1 parent b1e9a98 commit 43ae32d

1 file changed

Lines changed: 47 additions & 7 deletions

File tree

mcp/make_checkpoint.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#!/usr/bin/env python3
22
"""Create a DMTCP checkpoint of HOL Light.
33
4+
Each extra positional argument must be a single OCaml expression
5+
(e.g. 'needs "foo.ml"'). Expressions are wrapped in try/with to
6+
detect exceptions; multi-phrase inputs containing ';;' are not
7+
supported — place them in a file and load with needs or loadt.
8+
49
Examples:
510
python3 mcp/make_checkpoint.py # base HOL Light
611
python3 mcp/make_checkpoint.py --name s2n \\
@@ -17,6 +22,7 @@
1722

1823
HOL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
1924
SENTINEL = "HOL_MCP_CKPT_READY"
25+
ERROR_SENTINEL = "HOL_MCP_LOAD_ERROR"
2026

2127

2228
def fatal(msg):
@@ -27,8 +33,11 @@ def fatal(msg):
2733
def parse_args():
2834
p = argparse.ArgumentParser(
2935
description="Create a DMTCP checkpoint of HOL Light.",
30-
epilog="Extra positional arguments are OCaml expressions evaluated after HOL Light loads "
31-
"(e.g., 'needs \"arm/proofs/base.ml\"').",
36+
epilog="Extra positional arguments are single OCaml expressions evaluated "
37+
"after HOL Light loads (e.g., 'needs \"arm/proofs/base.ml\"'). "
38+
"Each expression is wrapped in try/with to detect exceptions. "
39+
"Multi-phrase inputs containing ';;' are not supported; place "
40+
"them in a file and load with needs or loadt.",
3241
)
3342
p.add_argument("--name", default="base",
3443
help="Checkpoint name (creates hol-<name>.ckpt/). Default: base")
@@ -60,21 +69,52 @@ def build_env():
6069
return env
6170

6271

63-
def wait_for_line(proc, marker, error_msg):
64-
"""Read stdout until a line contains marker. Dies on EOF."""
72+
def wait_for_line(proc, marker, error_msg, error_marker=None):
73+
"""Read stdout until a line contains marker. Dies on EOF.
74+
75+
If error_marker is set, treat any line containing it as a fatal
76+
error from the OCaml toplevel."""
6577
while True:
6678
line = proc.stdout.readline()
6779
if not line:
6880
fatal(error_msg)
81+
if error_marker and error_marker in line:
82+
# Extract the exception description after the sentinel
83+
detail = line.strip()
84+
idx = detail.find(error_marker)
85+
if idx >= 0:
86+
detail = detail[idx + len(error_marker):].lstrip(":").strip()
87+
fatal(f"OCaml exception: {detail}")
6988
if marker in line:
7089
return
7190

7291

7392
def send_and_wait(proc, code, error_msg):
74-
"""Send OCaml code and wait for sentinel."""
75-
proc.stdin.write(f'{code};;\nPrintf.printf "{SENTINEL}\\n%!";;\n')
93+
"""Send OCaml code wrapped in try/with and wait for sentinel.
94+
95+
The OCaml toplevel recovers from exceptions and continues to the
96+
next phrase, so a bare sentinel would appear even after a failure.
97+
Wrapping in try/with catches the exception and emits an error
98+
sentinel that wait_for_line detects.
99+
100+
Only single toplevel expressions are supported. Multi-phrase
101+
inputs containing ';;' will be rejected with a helpful message."""
102+
clean = code.rstrip().rstrip(";")
103+
if ";;" in clean:
104+
fatal(
105+
f"Expression contains ';;' (multiple toplevel phrases):\n"
106+
f" {code}\n"
107+
f"Only single expressions can be error-checked. Place composite\n"
108+
f"inputs in a file and use: needs \"file.ml\""
109+
)
110+
proc.stdin.write(
111+
f'(try ({clean}) with e -> '
112+
f'Printf.printf "{ERROR_SENTINEL}:%s\\n%!" '
113+
f'(Printexc.to_string e));;\n'
114+
f'Printf.printf "{SENTINEL}\\n%!";;\n'
115+
)
76116
proc.stdin.flush()
77-
wait_for_line(proc, SENTINEL, error_msg)
117+
wait_for_line(proc, SENTINEL, error_msg, error_marker=ERROR_SENTINEL)
78118

79119

80120
def main():

0 commit comments

Comments
 (0)