Skip to content

Commit 5dccfcc

Browse files
tautschnigkiro-agentkeyboardDrummer-bot
authored
Eliminate redundant exception checks in try/except bodies (#1091)
Only insert isError(maybe_except) checks after statements that actually modify maybe_except (procedure calls that may throw). Previously, every statement in a try body got an isError check, creating massive ite nesting in verification conditions. For a chosen benchmark this reduces 62 down to 8 isError checks (87% reduction). By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Co-authored-by: Kiro <kiro-agent@users.noreply.github.com> Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
1 parent e9a8eb8 commit 5dccfcc

6 files changed

Lines changed: 115 additions & 13 deletions

File tree

Strata/Languages/Python/PythonToLaurel.lean

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,13 +1810,31 @@ partial def translateStmt (ctx : TranslationContext) (s : Python.stmt SourceRang
18101810
handlerCtx := hCtx
18111811
handlerStmts := handlerStmts ++ hStmts
18121812

1813-
-- Insert exception checks after each statement in try body
1813+
-- Insert exception checks only after statements that could modify
1814+
-- maybe_except (procedure calls that may throw). Consecutive checks
1815+
-- without intervening modifications are redundant and create unnecessary
1816+
-- ite branches in the verification conditions.
1817+
let targetIsMaybeExcept (target : AstNode Variable) : Bool :=
1818+
match target.val with | .Local n => n == "maybe_except" | _ => false
1819+
let rec modifiesMaybeExceptVal (e : StmtExpr) : Bool :=
1820+
match e with
1821+
| .Assign targets _ => targets.any targetIsMaybeExcept
1822+
| .Block stmts _ => stmts.any fun s => modifiesMaybeExceptVal s.val
1823+
| .IfThenElse _ t e => modifiesMaybeExceptVal t.val ||
1824+
(e.map (modifiesMaybeExceptVal ·.val)).getD false
1825+
| .While _ _ _ body => modifiesMaybeExceptVal body.val
1826+
| _ => false
1827+
let modifiesMaybeExcept (stmt : StmtExprMd) : Bool :=
1828+
modifiesMaybeExceptVal stmt.val
1829+
let isException := mkStmtExprMd (StmtExpr.StaticCall "isError"
1830+
[mkStmtExprMd (StmtExpr.Var (.Local "maybe_except"))])
1831+
let exitToHandler := mkStmtExprMd (StmtExpr.IfThenElse isException
1832+
(mkStmtExprMd (StmtExpr.Exit catchersLabel)) none)
18141833
let bodyStmtsWithChecks := bodyStmts.flatMap fun stmt =>
1815-
let isException := mkStmtExprMd (StmtExpr.StaticCall "isError"
1816-
[mkStmtExprMd (StmtExpr.Var (.Local "maybe_except"))])
1817-
let exitToHandler := mkStmtExprMd (StmtExpr.IfThenElse isException
1818-
(mkStmtExprMd (StmtExpr.Exit catchersLabel)) none)
1819-
[stmt, exitToHandler]
1834+
if modifiesMaybeExcept stmt then
1835+
[stmt, exitToHandler]
1836+
else
1837+
[stmt]
18201838

18211839
-- Normal completion: exit the try block, skipping handlers
18221840
let exitTry := mkStmtExprMd (StmtExpr.Exit tryLabel)
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
test_missing_models.py(12, 4): ❓ unknown (pass on 2 paths, unknown on 1 path) - (Origin_test_helper_procedure_Requires)req_name_is_foo
2-
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
3-
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
4-
test_missing_models.py(15, 4): ❓ unknown (pass on 2 paths, unknown on 1 path) - (Origin_test_helper_procedure_Requires)req_name_is_foo
5-
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
6-
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
71
test_missing_models.py(8, 4): ❓ unknown - init_calls_Any_get_0
82
test_missing_models.py(8, 4): ❓ unknown - init_calls_Any_get_1
93
test_missing_models.py(9, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo
104
test_missing_models.py(9, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
115
test_missing_models.py(9, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
12-
DETAIL: 6 passed, 0 failed, 5 inconclusive
6+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo
7+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
8+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
9+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo
10+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
11+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
12+
DETAIL: 8 passed, 0 failed, 3 inconclusive
1313
RESULT: Inconclusive
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
test_try_except_modeled.py(8, 4): ✅ pass - assert(337)
2+
test_try_except_modeled.py(10, 8): ✅ pass - set_result_calls_Any_get_0
3+
test_try_except_modeled.py(13, 4): ✅ pass - dict access should succeed
4+
test_try_except_modeled.py(6, 30): ✅ pass - (test_try_dict_access ensures) Return type constraint
5+
test_try_except_modeled.py(17, 4): ✅ pass - assert(541)
6+
test_try_except_modeled.py(18, 4): ✅ pass - assert(557)
7+
test_try_except_modeled.py(19, 4): ✅ pass - assert(572)
8+
test_try_except_modeled.py(21, 17): ✅ pass - Check PAdd exception
9+
test_try_except_modeled.py(24, 4): ✅ pass - addition should succeed
10+
test_try_except_modeled.py(16, 29): ✅ pass - (test_try_arithmetic ensures) Return type constraint
11+
test_try_except_modeled.py(32, 4): ✅ pass - assert(950)
12+
test_try_except_modeled.py(35, 12): ✅ pass - set_result_calls_Any_get_0
13+
test_try_except_modeled.py(38, 4): ✅ pass - nested dict access should succeed
14+
test_try_except_modeled.py(30, 37): ✅ pass - (test_try_nested_dict_access ensures) Return type constraint
15+
DETAIL: 14 passed, 0 failed, 0 inconclusive
16+
RESULT: Analysis success
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
test_try_except_nested.py(5, 26): ✅ pass - (might_fail ensures) Return type constraint
2+
test_try_except_nested.py(9, 4): ✅ pass - assert(256)
3+
test_try_except_nested.py(12, 12): ✅ pass - (might_fail requires) Type constraint of x
4+
test_try_except_nested.py(15, 4): ❓ unknown - should succeed
5+
test_try_except_nested.py(8, 28): ✅ pass - (test_nested_except ensures) Return type constraint
6+
DETAIL: 4 passed, 0 failed, 1 inconclusive
7+
RESULT: Inconclusive
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Test: modeled procedure calls in try body that set maybe_except.
2+
# Dict access (Any_get) and arithmetic (PAdd) are modeled operations
3+
# that can raise exceptions. The isError check must be inserted after
4+
# these calls so the except handler is entered on failure.
5+
6+
def test_try_dict_access() -> bool:
7+
d: dict = {"key": "value"}
8+
result: str = ""
9+
try:
10+
result = d["key"]
11+
except:
12+
result = "error"
13+
assert result == "value", "dict access should succeed"
14+
return True
15+
16+
def test_try_arithmetic() -> bool:
17+
x: int = 10
18+
y: int = 3
19+
result: int = 0
20+
try:
21+
result = x + y
22+
except:
23+
result = -1
24+
assert result == 13, "addition should succeed"
25+
return True
26+
27+
# Modeled call inside nested control flow in try body.
28+
# The isError check must still be inserted even though the
29+
# dict access is inside an if branch.
30+
def test_try_nested_dict_access() -> bool:
31+
d: dict = {"key": "value"}
32+
result: str = ""
33+
try:
34+
if True:
35+
result = d["key"]
36+
except:
37+
result = "error"
38+
assert result == "value", "nested dict access should succeed"
39+
return True
40+
41+
test_try_dict_access()
42+
test_try_arithmetic()
43+
test_try_nested_dict_access()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Test: exception-raising call inside nested control flow in try body.
2+
# The except handler must still be entered when the call inside the if
3+
# branch raises an exception.
4+
5+
def might_fail(x: int) -> int:
6+
return x
7+
8+
def test_nested_except() -> bool:
9+
result: int = 0
10+
try:
11+
if True:
12+
result = might_fail(42)
13+
except:
14+
result = -1
15+
assert result == 42, "should succeed"
16+
return True
17+
18+
test_nested_except()

0 commit comments

Comments
 (0)