From 829be3369e349896b3b4a3cf298394f3ed31e9ec Mon Sep 17 00:00:00 2001 From: Xinlong Hu Date: Mon, 30 Jun 2025 13:52:19 -0500 Subject: [PATCH 01/21] Refactoring + 3.10 TryExceptRaiseNamed Test Case --- .../templates/Exception.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 4f64926..2cc0f0f 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -38,20 +38,6 @@ class Except3_11(ControlFlowTemplate): return x -class Except3_9(ControlFlowTemplate): - @classmethod - @override - def try_match(cls, cfg, node) -> ControlFlowTemplate | None: - if [x.opname for x in node.get_instructions()] == ["RERAISE"]: - return node - if x := ExceptExc3_9.try_match(cfg, node): - return x - if x := BareExcept3_9.try_match(cfg, node): - return x - if isinstance(node, Except3_9): - return node - - @register_template(0, 0, *versions_from(3, 11)) class Try3_11(ControlFlowTemplate): template = T( @@ -305,6 +291,20 @@ class TryFinally3_11(ControlFlowTemplate): return list(chain(header, self.line("try:"), body, self.line("finally:"), in_finally, after)) +class Except3_9(ControlFlowTemplate): + @classmethod + @override + def try_match(cls, cfg, node) -> ControlFlowTemplate | None: + if [x.opname for x in node.get_instructions()] == ["RERAISE"]: + return node + if x := ExceptExc3_9.try_match(cfg, node): + return x + if x := BareExcept3_9.try_match(cfg, node): + return x + if isinstance(node, Except3_9): + return node + + @register_template(0, 1, (3, 9), (3, 10)) class Try3_9(ControlFlowTemplate): template = T( @@ -385,7 +385,7 @@ class ExcBody3_9(ControlFlowTemplate): class NamedExc3_9(ExcBody3_9): template = T( header=~N("body", None).with_cond(with_instructions("POP_TOP", "STORE_FAST")), - body=N("normal_cleanup", None, "exception_cleanup"), + body=N("normal_cleanup.", None, "exception_cleanup"), normal_cleanup=~N("tail.").with_cond(with_instructions("STORE_FAST", "DELETE_FAST")), exception_cleanup=~N.tail().with_cond(with_instructions("STORE_FAST", "DELETE_FAST")), tail=N.tail(), From 598e02cc48e64af463a89d991ba82e4237be7afc Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Tue, 1 Jul 2025 10:29:18 -0500 Subject: [PATCH 02/21] TryExceptElseBare Test Case --- .../control_flow_reconstruction/templates/Exception.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 2cc0f0f..0780a73 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -340,12 +340,12 @@ class Try3_9(ControlFlowTemplate): @register_template(0, 0, (3, 9), (3, 10)) class TryElse3_9(ControlFlowTemplate): template = T( - try_header=N("try_body"), + try_header=~N("try_body"), try_body=N("try_footer.", None, "except_body"), - try_footer=N("else_body").with_in_deg(1), - except_body=N("tail.").with_in_deg(1).of_subtemplate(Except3_9), + try_footer=~N("else_body").with_in_deg(1), + except_body=~N("tail.").with_in_deg(1).of_subtemplate(Except3_9), else_body=~N("tail.").with_in_deg(1), - tail=N.tail(), + tail=~N.tail(), ) try_match = revert_on_fail( From 621997a384cc82b84d26e96584d1b9703c351ecf Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:07:39 -0500 Subject: [PATCH 03/21] 3.11/3.12/3.13 TryExceptRaise test case --- .../control_flow_reconstruction/templates/Exception.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 0780a73..7fb1b64 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -108,7 +108,7 @@ class TryElse3_11(ControlFlowTemplate): class BareExcept3_11(Except3_11): template = T( - except_body=N("except_footer", None, "reraise"), + except_body=N("except_footer.", None, "reraise").with_cond(without_top_level_instructions("RERAISE")), except_footer=~N("tail.").with_in_deg(1).with_cond(starting_instructions("POP_EXCEPT")), reraise=reraise, tail=N.tail(), @@ -219,14 +219,14 @@ class TryFinally3_11(ControlFlowTemplate): try_header=N("try_body"), try_body=N("finally_body", None, "fail_body"), finally_body=~N("tail.").with_in_deg(1).with_cond(no_back_edges), - fail_body=N(E.exc("reraise")), + fail_body=N(E.exc("reraise")).with_cond(ending_instructions("POP_TOP", "RERAISE")), reraise=reraise, tail=N.tail(), ) template2 = T( try_except=N("finally_body", None, "fail_body").of_type(Try3_11, TryElse3_11), finally_body=~N("tail.").with_in_deg(1).with_cond(no_back_edges), - fail_body=N(E.exc("reraise")), + fail_body=N(E.exc("reraise")).with_cond(ending_instructions("POP_TOP", "RERAISE")), reraise=reraise, tail=N.tail(), ) From 69e52a9c838225e252c71f17c9d11aaccb91a18d Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:40:45 -0500 Subject: [PATCH 04/21] 3.11/3.12/3.13 Finally test cases Test cases F, K, L, O, R, T, U, V --- .../control_flow_reconstruction/templates/Exception.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 7fb1b64..1e6660a 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -279,6 +279,10 @@ class TryFinally3_11(ControlFlowTemplate): def to_indented_source(self, source: SourceContext) -> list[SourceLine]: header = source[self.try_header] body = source[self.try_body, 1] + if isinstance(self.try_header, (Try3_11, TryElse3_11)) and self.members['try_body'] is None: + s = header + else: + s = chain(header, self.line('try:'), body) if isinstance(self.finally_body, BlockTemplate): i = self.cutoff + 1 @@ -288,7 +292,7 @@ class TryFinally3_11(ControlFlowTemplate): in_finally = source[self.finally_body, 1] after = [] - return list(chain(header, self.line("try:"), body, self.line("finally:"), in_finally, after)) + return list(chain(s, self.line("finally:"), in_finally, after)) class Except3_9(ControlFlowTemplate): From fe68c6a27bc85f3f1fc7ed91af3c5e84f11b1614 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:42:11 -0500 Subject: [PATCH 05/21] Alphabetized try-exception & added fall through test cases --- test/TryExcept.py | 627 ++++++++++++++++++++++++++++------------------ 1 file changed, 383 insertions(+), 244 deletions(-) diff --git a/test/TryExcept.py b/test/TryExcept.py index ac8efff..173895c 100644 --- a/test/TryExcept.py +++ b/test/TryExcept.py @@ -1,218 +1,11 @@ -def TryExcept(): +def a_TryExcept(): try: print(1) except: print(2) print(3) -def TryExceptMulti(): - try: - print(1) - except a: - print(2) - except b: - print(3) - except c: - print(4) - print(5) - -def TryExceptMultiFallback(): - try: - print(1) - except a: - print(2) - except b: - print(3) - except: - print(4) - print(5) - -def TryExceptMultiNamed(): - try: - print(1) - except A as a: - print(2) - except B as b: - print(3) - except C as c: - print(4) - print(5) - -def TryExceptMultiNamedFallback(): - try: - print(1) - except A as a: - print(2) - except B as b: - print(3) - except: - print(4) - print(5) - -def TryExceptMultiNamedAndUnnamed(): - try: - print(1) - except A as a: - print(2) - except b: - print(3) - except: - print(4) - print(5) - -def TryExceptElseBare(): - try: - print(1) - except: - print(2) - else: - print(3) - print(4) - -def TryExceptElseMulti(): - try: - print(1) - except a: - print(2) - except b: - print(3) - else: - print(4) - print(5) - -def TryExceptElseMultiFallback(): - try: - print(1) - except a: - print(2) - except b: - print(3) - except: - print(4) - else: - print(5) - print(6) - -def TryExceptElseNamed(): - try: - print(1) - except A as a: - print(2) - except B as b: - print(3) - else: - print(4) - print(5) - -def TryExceptElseMultiNamedAndUnnamed(): - try: - print(1) - except A as a: - print(2) - except b: - print(3) - except: - print(4) - else: - print(5) - print(6) - -def TryFinallyBare(): - try: - print(1) - finally: - print(2) - print(3) - -def TryExceptFinallyBare(): - try: - print(1) - except: - print(2) - finally: - print(3) - print(4) -##### NOT YET IMPLEMENTED ##### -##### crashes cflow.py ##### -##def TryExceptFinallyBareSpecific(): -## try: -## print(1) -## except a: -## print(2) -## finally: -## print(3) -## print(4) -## -##def TryExceptMultiFinally(): -## try: -## print(1) -## except a: -## print(2) -## except b: -## print(3) -## finally: -## print(4) -## print(5) -## -## Broken -##def TryExceptMultiFallbackFinally(): -## try: -## print(1) -## except a: -## print(2) -## except: -## print(3) -## finally: -## print(4) -## print(5) - -def TryExceptMultiNamedFinally(): - try: - print(1) - except A as a: - print(2) - except B as b: - print(3) - finally: - print(4) - print(5) - -def TryExceptMultiNamedFallbackFinally(): - try: - print(1) - except A as a: - print(2) - except: - print(3) - finally: - print(4) - print(5) - -def TryExceptMultiNamedAndUnnamedFinally(): - try: - print(1) - except A as a: - print(2) - except b: - print(3) - except: - print(4) - finally: - print(5) - print(6) - -def TryExceptElseFinallyBare(): - try: - print(1) - except: - print(2) - else: - print(3) - finally: - print(4) - print(5) - -def TryExceptBareNested(): +def b_TryExceptBareNested(): try: print(1) except: @@ -222,47 +15,41 @@ def TryExceptBareNested(): except: print(4) -def TryExceptReturn(): - try: - print(1) - return 2 - except: - print(2) - -# Not currently working, see https://github.com/syssec-utd/pylingual/issues/24#issuecomment-3005215427 -def TryExceptRaise(): +def b1_TryExceptBareNestedFallthrough(): try: print(1) except: - print(2) - raise Exc - -def TryExceptRaiseNamed(): - try: - print(1) - except A as a: - print(2) - raise Exc - -### Expected to fail on 3.10- has the same issue as TryExceptFinally -def TryExceptBareNestedNamed(): - try: - print(1) - except A as a: print(2) try: print(3) except: print(4) + print(5) -def TryExceptReturnNamed(): +def b2_TryExceptBareNestedEarlyFallthrough(): try: print(1) - return 2 - except A as a: + except: print(2) + try: + print(3) + except: + print(4) + print(5) -def TryExceptBareNestedMulti(): +def b3_TryExceptBareNestedDoubleFallthrough(): + try: + print(1) + except: + print(2) + try: + print(3) + except: + print(4) + print(5) + print(6) + +def c_TryExceptBareMultiNested(): try: print(1) except a: @@ -278,7 +65,349 @@ def TryExceptBareNestedMulti(): except: print(7) -def TryExceptReturnMulti(): +def c1_TryExceptBareMultiNestedFallthrough(): + try: + print(1) + except a: + print(2) + try: + print(3) + except: + print(4) + print(5) + except b: + print(6) + try: + print(7) + except: + print(8) + +def c2_TryExceptBareMultiNestedFallthrough2(): + try: + print(1) + except a: + print(2) + try: + print(3) + except: + print(4) + print(5) + except b: + print(6) + try: + print(7) + except: + print(8) + print(9) + +def c3_TryExceptBareMultiNestedEarlyFallthrough(): + try: + print(1) + except a: + print(2) + try: + print(3) + except: + print(4) + except b: + print(5) + try: + print(6) + except: + print(7) + print(8) + +def c4_TryExceptBareMultiNestedAllFallthrough(): + try: + print(1) + except a: + print(2) + try: + print(3) + except: + print(4) + print(5) + except b: + print(6) + try: + print(7) + except: + print(8) + print(9) + print(10) + +def d_TryExceptBareNestedNamed(): + try: + print(1) + except A as a: + print(2) + try: + print(3) + except: + print(4) + +def d1_TryExceptBareNestedNamedFallthrough(): + try: + print(1) + except A as a: + print(2) + try: + print(3) + except: + print(4) + print(5) + +def d2_TryExceptBareNestedNamedEarlyFallthrough(): + try: + print(1) + except A as a: + print(2) + try: + print(3) + except: + print(4) + print(5) + +def d3_TryExceptBareNestedNamedDoubleFallthrough(): + try: + print(1) + except A as a: + print(2) + try: + print(3) + except: + print(4) + print(5) + print(6) + +def e_TryExceptElseBare(): + try: + print(1) + except: + print(2) + else: + print(3) + print(4) + +def f_TryExceptElseFinallyBare(): + try: + print(1) + except: + print(2) + else: + print(3) + finally: + print(4) + print(5) + +def g_TryExceptElseMulti(): + try: + print(1) + except a: + print(2) + except b: + print(3) + else: + print(4) + print(5) + +def h_TryExceptElseMultiFallback(): + try: + print(1) + except a: + print(2) + except b: + print(3) + except: + print(4) + else: + print(5) + print(6) + +def i_TryExceptElseMultiNamedAndUnnamed(): + try: + print(1) + except A as a: + print(2) + except b: + print(3) + except: + print(4) + else: + print(5) + print(6) + +def j_TryExceptElseNamed(): + try: + print(1) + except A as a: + print(2) + except B as b: + print(3) + else: + print(4) + print(5) + +def k_TryExceptFinallyBare(): + try: + print(1) + except: + print(2) + finally: + print(3) + print(4) + +def l_TryExceptFinallyBareSpecific(): + try: + print(1) + except a: + print(2) + finally: + print(3) + print(4) + +def m_TryExceptMulti(): + try: + print(1) + except a: + print(2) + except b: + print(3) + except c: + print(4) + print(5) + +def n_TryExceptMultiFallback(): + try: + print(1) + except a: + print(2) + except b: + print(3) + except: + print(4) + print(5) + +def o_TryExceptMultiFallbackFinally(): + try: + print(1) + except a: + print(2) + except: + print(3) + finally: + print(4) + print(5) + +def p_TryExceptMultiNamed(): + try: + print(1) + except A as a: + print(2) + except B as b: + print(3) + except C as c: + print(4) + print(5) + +def q_TryExceptMultiNamedAndUnnamed(): + try: + print(1) + except A as a: + print(2) + except b: + print(3) + except: + print(4) + print(5) + +def r_TryExceptMultiNamedAndUnnamedFinally(): + try: + print(1) + except A as a: + print(2) + except b: + print(3) + except: + print(4) + finally: + print(5) + print(6) + +def s_TryExceptMultiNamedFallback(): + try: + print(1) + except A as a: + print(2) + except: + print(3) + print(4) + +def t_TryExceptMultiNamedFallbackFinally(): + try: + print(1) + except A as a: + print(2) + except: + print(3) + finally: + print(4) + print(5) + +def u_TryExceptMultiNamedFinally(): + try: + print(1) + except A as a: + print(2) + except B as b: + print(3) + finally: + print(4) + print(5) + +def v_TryExceptMultiFinally(): + try: + print(1) + except a: + print(2) + except b: + print(3) + finally: + print(4) + print(5) + +def w_TryExceptRaise(): + try: + print(1) + except: + print(2) + raise Exc + +def x_TryExceptRaiseMulti(): + try: + print(1) + except a: + print(2) + raise Exc + except b: + print(3) + raise Exc + +def y_TryExceptRaiseNamed(): + try: + print(1) + except A as a: + print(2) + raise Exc + +def z_TryExceptReturn(): + try: + print(1) + return 2 + except: + print(2) + +def aa_TryExceptReturnMulti(): try: print(1) return 2 @@ -287,12 +416,22 @@ def TryExceptReturnMulti(): except b: print(3) -def TryExceptRaiseMulti(): +def ab_TryExceptReturnNamed(): try: print(1) - except a: + return 2 + except A as a: print(2) - raise Exc - except b: - print(3) - raise Exc + +def ac_TryFinallyBareFallthrough(): + try: + print(1) + finally: + print(2) + print(3) + +def ad_TryFinallyBare(): + try: + print(1) + finally: + print(2) \ No newline at end of file From 426994aead1ad36f6453752164c172c9d2cc3d87 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:49:01 -0500 Subject: [PATCH 06/21] Constraining TryElse3_6 Matching unintended code blocks --- pylingual/control_flow_reconstruction/templates/Exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 1e6660a..f8c7b78 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -627,7 +627,7 @@ class ExceptExc3_6(Except3_6): @register_template(0, 0, (3, 6), (3, 7), (3, 8)) class TryElse3_6(ControlFlowTemplate): template = T( - try_header=N("try_body"), + try_header=N("try_body").with_cond(exact_instructions("SETUP_EXCEPT"), exact_instructions("SETUP_FINALLY")), try_body=N("try_footer.", None, "except_body"), try_footer=N("else_body").with_in_deg(1), except_body=N("tail").with_in_deg(1).of_subtemplate(Except3_6), From e4a46f29ecf86fdba30170f7a485f7288e4b909c Mon Sep 17 00:00:00 2001 From: Xinlong Hu Date: Wed, 2 Jul 2025 14:29:04 -0500 Subject: [PATCH 07/21] Test case D --- .../control_flow_reconstruction/templates/Exception.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index f8c7b78..f09dd7d 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -542,10 +542,10 @@ class Except3_6(ControlFlowTemplate): @register_template(0, 0, (3, 6), (3, 7), (3, 8)) class Try3_6(ControlFlowTemplate): template = T( - try_header=N("try_body").with_cond(without_top_level_instructions("SETUP_WITH")), + try_header=~N("try_body").with_cond(without_top_level_instructions("SETUP_WITH")), try_body=N("try_footer", None, "except_body"), - try_footer=N("tail."), - except_body=N("tail.").with_in_deg(1).of_subtemplate(Except3_6), + try_footer=~N("tail."), + except_body=~N("tail.").with_in_deg(1).of_subtemplate(Except3_6), tail=N.tail(), ) @@ -662,8 +662,8 @@ class TryElse3_6(ControlFlowTemplate): class BareExcept3_6(Except3_6): template = T( - except_body=N("tail."), - tail=N.tail(), + except_body=~N("tail."), + tail=~N.tail(), ) try_match = make_try_match( From 0624fe55ffa6efce2bd4b729bd345e1e74d5a1bd Mon Sep 17 00:00:00 2001 From: Xinlong Hu Date: Wed, 2 Jul 2025 15:01:26 -0500 Subject: [PATCH 08/21] Test case Y --- pylingual/control_flow_reconstruction/templates/Exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index f09dd7d..1daa99d 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -583,7 +583,7 @@ class ExcBody3_6(ControlFlowTemplate): class NamedExc3_6(ExcBody3_6): template = T( header=N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST")), - body=N("normal_cleanup", None, "exception_cleanup"), + body=N("normal_cleanup.", None, "exception_cleanup"), normal_cleanup=N("exception_cleanup."), exception_cleanup=N.tail().with_cond(with_instructions("LOAD_CONST", "STORE_FAST")), ) From ad2cb03cb735a8990c1d804d102fe303183045dc Mon Sep 17 00:00:00 2001 From: Xinlong Hu Date: Wed, 2 Jul 2025 15:37:43 -0500 Subject: [PATCH 09/21] Test case G/J --- pylingual/control_flow_reconstruction/templates/Exception.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 1daa99d..d4832df 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -585,7 +585,8 @@ class NamedExc3_6(ExcBody3_6): header=N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST")), body=N("normal_cleanup.", None, "exception_cleanup"), normal_cleanup=N("exception_cleanup."), - exception_cleanup=N.tail().with_cond(with_instructions("LOAD_CONST", "STORE_FAST")), + exception_cleanup=N("tail.").with_cond(with_instructions("LOAD_CONST", "STORE_FAST")), + tail=N.tail() ) try_match = make_try_match({EdgeKind.Fall: "tail"}, "exception_cleanup", "header", "body", "normal_cleanup") @@ -600,7 +601,7 @@ class ExceptExc3_6(Except3_6): ending_instructions("COMPARE_OP", "POP_JUMP_FORWARD_IF_FALSE") ), except_body=N("tail.", None).of_subtemplate(ExcBody3_6).with_in_deg(1), - no_match=N("tail?", None).of_subtemplate(Except3_6), + no_match=N.tail().of_subtemplate(Except3_6), tail=N.tail(), ) From 6b939f5a4418a250f153531c324cc30c32b6b323 Mon Sep 17 00:00:00 2001 From: Xinlong Hu Date: Wed, 2 Jul 2025 18:10:35 -0500 Subject: [PATCH 10/21] Setup for TryFinally implementation --- .../templates/Exception.py | 82 ++++++++++++++++++- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index d4832df..70434fe 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -628,10 +628,10 @@ class ExceptExc3_6(Except3_6): @register_template(0, 0, (3, 6), (3, 7), (3, 8)) class TryElse3_6(ControlFlowTemplate): template = T( - try_header=N("try_body").with_cond(exact_instructions("SETUP_EXCEPT"), exact_instructions("SETUP_FINALLY")), + try_header=~N("try_body").with_cond(exact_instructions("SETUP_EXCEPT"), exact_instructions("SETUP_FINALLY")), try_body=N("try_footer.", None, "except_body"), - try_footer=N("else_body").with_in_deg(1), - except_body=N("tail").with_in_deg(1).of_subtemplate(Except3_6), + try_footer=~N("else_body").with_in_deg(1), + except_body=~N("tail").with_in_deg(1).of_subtemplate(Except3_6), else_body=~N("tail").with_in_deg(1), tail=N.tail(), ) @@ -679,4 +679,78 @@ class BareExcept3_6(Except3_6): """ except: {except_body} - """ \ No newline at end of file + """ + +@register_template(2, 50, (3, 6), (3, 7), (3, 8)) +class TryFinally3_6(ControlFlowTemplate): + template = T( + try_header=N("try_body"), + try_body=N("finally_body", None, "fail_body"), + finally_body=~N("fail_body").with_in_deg(1).with_cond(no_back_edges), + fail_body=N("tail.").with_cond(with_instructions("POP_TOP", "END_FINALLY")), + tail=N.tail(), + ) + template2 = T( + try_except=N("finally_tail", None, "fail_body").of_type(TryElse3_6, Try3_6), + finally_tail=N("finally_body", None, "fail_body"), + finally_body=~N("fail_body").with_in_deg(1).with_cond(no_back_edges), + fail_body=N("tail.").with_cond(with_instructions("POP_TOP", "END_FINALLY")), + tail=N.tail(), + ) + + @staticmethod + def find_finally_cutoff(mapping): + f = mapping["finally_body"] + g = mapping["fail_body"] + if any(x.starts_line is not None for x in g.get_instructions()): + return None + if not isinstance(f, BlockTemplate): + f = BlockTemplate([f]) + if not isinstance(g, BlockTemplate): + g = BlockTemplate([g]) + if isinstance(g.members[-1], InstTemplate) and g.members[-1].inst.opname == "END_FINALLY": + g.members.pop() + x = None + for x, y in zip(f.members, g.members): + if all(type(a) in [IfThen, IfElse] for a in (x, y)): + continue + if type(x) is not type(y): + return None + return x and f.members.index(x) + + cutoff: int + + @classmethod + @override + def try_match(cls, cfg, node) -> ControlFlowTemplate | None: + mapping = cls.template.try_match(cfg, node) + if mapping is None: + mapping = cls.template2.try_match(cfg, node) + if mapping is None: + return None + mapping["try_header"] = mapping.pop("try_except") + + cutoff = cls.find_finally_cutoff(mapping) + if cutoff is None: + if cfg.run == 2: + cutoff = 9999 + else: + return None + + template = condense_mapping(cls, cfg, mapping, "try_header", "try_body", "finally_body", "fail_body") + template.cutoff = cutoff + return template + + def to_indented_source(self, source: SourceContext) -> list[SourceLine]: + header = source[self.try_header] + body = source[self.try_body, 1] + + if isinstance(self.fail_body, BlockTemplate): + i = self.cutoff + 1 + in_finally = source[BlockTemplate(self.fail_body.members[:i]), 1] if i > 0 else [] + after = source[BlockTemplate(self.fail_body.members[i:])] if i < len(self.fail_body.members) else [] + else: + in_finally = source[self.fail_body, 1] + after = [] + + return list(chain(header, self.line("try:"), body, self.line("finally:"), in_finally, after)) \ No newline at end of file From dcad140a5f737a41d79dd73743ac6c56e90c047c Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:28:01 -0500 Subject: [PATCH 11/21] TryFinally test cases completed --- .../templates/Exception.py | 41 ++++--------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 70434fe..9ebf04a 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -582,10 +582,10 @@ class ExcBody3_6(ControlFlowTemplate): class NamedExc3_6(ExcBody3_6): template = T( - header=N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST")), + header=~N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST")), body=N("normal_cleanup.", None, "exception_cleanup"), - normal_cleanup=N("exception_cleanup."), - exception_cleanup=N("tail.").with_cond(with_instructions("LOAD_CONST", "STORE_FAST")), + normal_cleanup=~N("exception_cleanup."), + exception_cleanup=~N("tail.").with_cond(with_instructions("LOAD_CONST", "STORE_FAST")), tail=N.tail() ) @@ -596,12 +596,12 @@ class NamedExc3_6(ExcBody3_6): class ExceptExc3_6(Except3_6): template = T( - except_header=N("except_body", "no_match").with_cond( + except_header=~N("except_body", "no_match").with_cond( ending_instructions("COMPARE_OP", "POP_JUMP_IF_FALSE"), ending_instructions("COMPARE_OP", "POP_JUMP_FORWARD_IF_FALSE") ), - except_body=N("tail.", None).of_subtemplate(ExcBody3_6).with_in_deg(1), - no_match=N.tail().of_subtemplate(Except3_6), + except_body=~N("tail.", None).of_subtemplate(ExcBody3_6).with_in_deg(1), + no_match=~N.tail().of_subtemplate(Except3_6), tail=N.tail(), ) @@ -698,26 +698,6 @@ class TryFinally3_6(ControlFlowTemplate): tail=N.tail(), ) - @staticmethod - def find_finally_cutoff(mapping): - f = mapping["finally_body"] - g = mapping["fail_body"] - if any(x.starts_line is not None for x in g.get_instructions()): - return None - if not isinstance(f, BlockTemplate): - f = BlockTemplate([f]) - if not isinstance(g, BlockTemplate): - g = BlockTemplate([g]) - if isinstance(g.members[-1], InstTemplate) and g.members[-1].inst.opname == "END_FINALLY": - g.members.pop() - x = None - for x, y in zip(f.members, g.members): - if all(type(a) in [IfThen, IfElse] for a in (x, y)): - continue - if type(x) is not type(y): - return None - return x and f.members.index(x) - cutoff: int @classmethod @@ -730,12 +710,9 @@ class TryFinally3_6(ControlFlowTemplate): return None mapping["try_header"] = mapping.pop("try_except") - cutoff = cls.find_finally_cutoff(mapping) + cutoff = next((i for i, x in enumerate(mapping["fail_body"].get_instructions())), None) if cutoff is None: - if cfg.run == 2: - cutoff = 9999 - else: - return None + return None template = condense_mapping(cls, cfg, mapping, "try_header", "try_body", "finally_body", "fail_body") template.cutoff = cutoff @@ -753,4 +730,4 @@ class TryFinally3_6(ControlFlowTemplate): in_finally = source[self.fail_body, 1] after = [] - return list(chain(header, self.line("try:"), body, self.line("finally:"), in_finally, after)) \ No newline at end of file + return list(chain(header, self.line("try:"), body, self.line("finally:"), in_finally, after)) From 55e71d2185e181c4acd3f86c6c499448578d6ff8 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:29:19 -0500 Subject: [PATCH 12/21] 3.6/3.7/3.8 assertions not being matched --- .../control_flow_reconstruction/templates/Conditional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Conditional.py b/pylingual/control_flow_reconstruction/templates/Conditional.py index 7d0b53d..7bdb50f 100644 --- a/pylingual/control_flow_reconstruction/templates/Conditional.py +++ b/pylingual/control_flow_reconstruction/templates/Conditional.py @@ -1,5 +1,5 @@ from ..cft import ControlFlowTemplate, EdgeKind, register_template -from ..utils import T, N, defer_source_to, run_is, starting_instructions, to_indented_source, make_try_match, without_top_level_instructions +from ..utils import T, N, defer_source_to, run_is, exact_instructions, starting_instructions, to_indented_source, make_try_match, without_top_level_instructions @register_template(1, 40) @@ -46,7 +46,7 @@ class IfThen(ControlFlowTemplate): class Assertion(ControlFlowTemplate): template = T( assertion=~N("fail", "tail"), - fail=+N().with_cond(starting_instructions("LOAD_ASSERTION_ERROR")), + fail=+N().with_cond(starting_instructions("LOAD_ASSERTION_ERROR"), exact_instructions("LOAD_GLOBAL", "RAISE_VARARGS")), tail=N.tail(), ) From 3ee41410aefb44e32516c39b85513b37c66d214f Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:55:31 -0500 Subject: [PATCH 13/21] Additional try except test case and fix --- .../templates/Exception.py | 4 ++-- test/TryExcept.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 9ebf04a..715c1d0 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -631,8 +631,8 @@ class TryElse3_6(ControlFlowTemplate): try_header=~N("try_body").with_cond(exact_instructions("SETUP_EXCEPT"), exact_instructions("SETUP_FINALLY")), try_body=N("try_footer.", None, "except_body"), try_footer=~N("else_body").with_in_deg(1), - except_body=~N("tail").with_in_deg(1).of_subtemplate(Except3_6), - else_body=~N("tail").with_in_deg(1), + except_body=~N("tail.").with_in_deg(1).of_subtemplate(Except3_6), + else_body=~N("tail.").with_in_deg(1), tail=N.tail(), ) diff --git a/test/TryExcept.py b/test/TryExcept.py index 173895c..94cd00e 100644 --- a/test/TryExcept.py +++ b/test/TryExcept.py @@ -407,6 +407,14 @@ def z_TryExceptReturn(): except: print(2) +def z1_TryExceptReturn(): + try: + print(1) + return 2 + except: + print(2) + return 3 + def aa_TryExceptReturnMulti(): try: print(1) @@ -423,6 +431,14 @@ def ab_TryExceptReturnNamed(): except A as a: print(2) +def ab1_TryExceptReturnNamed(): + try: + print(1) + return 2 + except A as a: + print(2) + return 3 + def ac_TryFinallyBareFallthrough(): try: print(1) From b6a1007a49af6f0053edba839c496ee65efd78cd Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:02:33 -0500 Subject: [PATCH 14/21] 3.6/3.7/3.8/3.9/3.10 Try except outside definition fix --- .../control_flow_reconstruction/templates/Exception.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 715c1d0..206986d 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -388,10 +388,10 @@ class ExcBody3_9(ControlFlowTemplate): class NamedExc3_9(ExcBody3_9): template = T( - header=~N("body", None).with_cond(with_instructions("POP_TOP", "STORE_FAST")), + header=~N("body", None).with_cond(with_instructions("POP_TOP", "STORE_FAST"), with_instructions("POP_TOP", "STORE_NAME")), body=N("normal_cleanup.", None, "exception_cleanup"), - normal_cleanup=~N("tail.").with_cond(with_instructions("STORE_FAST", "DELETE_FAST")), - exception_cleanup=~N.tail().with_cond(with_instructions("STORE_FAST", "DELETE_FAST")), + normal_cleanup=~N("tail.").with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_FAST")), + exception_cleanup=~N.tail().with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_FAST")), tail=N.tail(), ) @@ -582,10 +582,10 @@ class ExcBody3_6(ControlFlowTemplate): class NamedExc3_6(ExcBody3_6): template = T( - header=~N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST")), + header=~N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST"), with_instructions("POP_TOP", "STORE_NAME")), body=N("normal_cleanup.", None, "exception_cleanup"), normal_cleanup=~N("exception_cleanup."), - exception_cleanup=~N("tail.").with_cond(with_instructions("LOAD_CONST", "STORE_FAST")), + exception_cleanup=~N("tail.").with_cond(with_instructions("LOAD_CONST", "STORE_FAST"), with_instructions("LOAD_CONST", "STORE_NAME")), tail=N.tail() ) From 93ee5cd830879e544cb18529846ee906c7d11358 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:27:59 -0500 Subject: [PATCH 15/21] Additional failing test case --- test/TryExcept.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/TryExcept.py b/test/TryExcept.py index 94cd00e..04b0f42 100644 --- a/test/TryExcept.py +++ b/test/TryExcept.py @@ -424,6 +424,16 @@ def aa_TryExceptReturnMulti(): except b: print(3) +def aa1_TryExceptReturnMulti(): + try: + print(1) + return 2 + except a: + print(2) + return 3 + except b: + print(3) + def ab_TryExceptReturnNamed(): try: print(1) From 5765e07b10aaa724786d9be0bf33c2ba088e868e Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:06:56 -0500 Subject: [PATCH 16/21] Fixed assertion matching --- .../control_flow_reconstruction/templates/Conditional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Conditional.py b/pylingual/control_flow_reconstruction/templates/Conditional.py index 7bdb50f..05f2239 100644 --- a/pylingual/control_flow_reconstruction/templates/Conditional.py +++ b/pylingual/control_flow_reconstruction/templates/Conditional.py @@ -1,5 +1,5 @@ from ..cft import ControlFlowTemplate, EdgeKind, register_template -from ..utils import T, N, defer_source_to, run_is, exact_instructions, starting_instructions, to_indented_source, make_try_match, without_top_level_instructions +from ..utils import T, N, defer_source_to, run_is, with_instructions, starting_instructions, to_indented_source, make_try_match, without_top_level_instructions @register_template(1, 40) @@ -46,7 +46,7 @@ class IfThen(ControlFlowTemplate): class Assertion(ControlFlowTemplate): template = T( assertion=~N("fail", "tail"), - fail=+N().with_cond(starting_instructions("LOAD_ASSERTION_ERROR"), exact_instructions("LOAD_GLOBAL", "RAISE_VARARGS")), + fail=+N().with_cond(starting_instructions("LOAD_ASSERTION_ERROR"), with_instructions("LOAD_GLOBAL", "RAISE_VARARGS")), tail=N.tail(), ) From d5a859e8b9f9de1b5f78fe26458b4d603ec227dc Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:43:09 -0500 Subject: [PATCH 17/21] Outside def fix --- pylingual/control_flow_reconstruction/templates/Exception.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index 206986d..a785231 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -390,8 +390,8 @@ class NamedExc3_9(ExcBody3_9): template = T( header=~N("body", None).with_cond(with_instructions("POP_TOP", "STORE_FAST"), with_instructions("POP_TOP", "STORE_NAME")), body=N("normal_cleanup.", None, "exception_cleanup"), - normal_cleanup=~N("tail.").with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_FAST")), - exception_cleanup=~N.tail().with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_FAST")), + normal_cleanup=~N("tail.").with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_NAME")), + exception_cleanup=~N.tail().with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_NAME")), tail=N.tail(), ) From c7ac2dc18ce14586e83ee82c8edf4ec8508138f4 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:59:46 -0500 Subject: [PATCH 18/21] Overlap with raise Exc fix + helper method --- .../templates/Conditional.py | 4 ++-- pylingual/control_flow_reconstruction/utils.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Conditional.py b/pylingual/control_flow_reconstruction/templates/Conditional.py index 05f2239..a920de0 100644 --- a/pylingual/control_flow_reconstruction/templates/Conditional.py +++ b/pylingual/control_flow_reconstruction/templates/Conditional.py @@ -1,5 +1,5 @@ from ..cft import ControlFlowTemplate, EdgeKind, register_template -from ..utils import T, N, defer_source_to, run_is, with_instructions, starting_instructions, to_indented_source, make_try_match, without_top_level_instructions +from ..utils import T, N, defer_source_to, run_is, with_instructions, has_instval, starting_instructions, to_indented_source, make_try_match, without_top_level_instructions @register_template(1, 40) @@ -46,7 +46,7 @@ class IfThen(ControlFlowTemplate): class Assertion(ControlFlowTemplate): template = T( assertion=~N("fail", "tail"), - fail=+N().with_cond(starting_instructions("LOAD_ASSERTION_ERROR"), with_instructions("LOAD_GLOBAL", "RAISE_VARARGS")), + fail=+N().with_cond(starting_instructions("LOAD_ASSERTION_ERROR"), has_instval("LOAD_GLOBAL", argval = "AssertionError")), tail=N.tail(), ) diff --git a/pylingual/control_flow_reconstruction/utils.py b/pylingual/control_flow_reconstruction/utils.py index 1eab7ab..8724bfd 100644 --- a/pylingual/control_flow_reconstruction/utils.py +++ b/pylingual/control_flow_reconstruction/utils.py @@ -118,6 +118,18 @@ def has_incoming_edge_of_categories(*categories: str): return False return check + +def has_instval(opname: str, argval : Any): + def check_instructions(cfg: CFG, node: ControlFlowTemplate | None) -> bool: + ops = {x.opname : x.argval for x in node.get_instructions()} + for op in ops: + if ops[op] == argval: + return True + return False + + return check_instructions + + def run_is(n: int): def check_run(cfg: CFG, node: ControlFlowTemplate | None) -> bool: return cfg.run == n From 5e6c46c2973676a54d4e45d87d2d6b09ccf4f549 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:49:23 -0500 Subject: [PATCH 19/21] END_FINALLY not matching some cases --- .../control_flow_reconstruction/templates/Exception.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pylingual/control_flow_reconstruction/templates/Exception.py b/pylingual/control_flow_reconstruction/templates/Exception.py index a785231..266c736 100644 --- a/pylingual/control_flow_reconstruction/templates/Exception.py +++ b/pylingual/control_flow_reconstruction/templates/Exception.py @@ -530,7 +530,7 @@ class Except3_6(ControlFlowTemplate): @classmethod @override def try_match(cls, cfg, node) -> ControlFlowTemplate | None: - if [x.opname for x in node.get_instructions()] == ["END_FINALLY"]: + if [x.opname for x in node.get_instructions()[:1]] == ["END_FINALLY"]: return node if x := ExceptExc3_6.try_match(cfg, node): return x @@ -543,7 +543,7 @@ class Except3_6(ControlFlowTemplate): class Try3_6(ControlFlowTemplate): template = T( try_header=~N("try_body").with_cond(without_top_level_instructions("SETUP_WITH")), - try_body=N("try_footer", None, "except_body"), + try_body=N("try_footer.", None, "except_body"), try_footer=~N("tail."), except_body=~N("tail.").with_in_deg(1).of_subtemplate(Except3_6), tail=N.tail(), @@ -582,10 +582,10 @@ class ExcBody3_6(ControlFlowTemplate): class NamedExc3_6(ExcBody3_6): template = T( - header=~N("body", None).with_cond(starting_instructions("POP_TOP", "STORE_FAST"), with_instructions("POP_TOP", "STORE_NAME")), + header=~N("body", None).with_cond(with_instructions("POP_TOP", "STORE_FAST"), with_instructions("POP_TOP", "STORE_NAME")), body=N("normal_cleanup.", None, "exception_cleanup"), normal_cleanup=~N("exception_cleanup."), - exception_cleanup=~N("tail.").with_cond(with_instructions("LOAD_CONST", "STORE_FAST"), with_instructions("LOAD_CONST", "STORE_NAME")), + exception_cleanup=~N("tail.").with_cond(with_instructions("STORE_FAST", "DELETE_FAST"), with_instructions("STORE_NAME", "DELETE_NAME")), tail=N.tail() ) @@ -663,7 +663,7 @@ class TryElse3_6(ControlFlowTemplate): class BareExcept3_6(Except3_6): template = T( - except_body=~N("tail."), + except_body=~N("tail.").with_cond(starting_instructions("POP_TOP", "POP_TOP", "POP_TOP")), tail=~N.tail(), ) From e887a24bf187ebe9aeccfb7285b0cc3e97e842d7 Mon Sep 17 00:00:00 2001 From: Xinlong Hu <118075581+XinlongCS@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:02:57 -0500 Subject: [PATCH 20/21] Assertion fix --- pylingual/control_flow_reconstruction/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pylingual/control_flow_reconstruction/utils.py b/pylingual/control_flow_reconstruction/utils.py index 8724bfd..968b5b0 100644 --- a/pylingual/control_flow_reconstruction/utils.py +++ b/pylingual/control_flow_reconstruction/utils.py @@ -121,9 +121,8 @@ def has_incoming_edge_of_categories(*categories: str): def has_instval(opname: str, argval : Any): def check_instructions(cfg: CFG, node: ControlFlowTemplate | None) -> bool: - ops = {x.opname : x.argval for x in node.get_instructions()} - for op in ops: - if ops[op] == argval: + for x in node.get_instructions(): + if x.opname == opname and x.argval == argval: return True return False From f4a5d1dc1e11f7dc6ec1fe8c895a0e22d410a23b Mon Sep 17 00:00:00 2001 From: caandt Date: Thu, 10 Jul 2025 11:56:14 -0500 Subject: [PATCH 21/21] fix decorator in <=3.7 cflow --- dev_scripts/cflow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev_scripts/cflow.py b/dev_scripts/cflow.py index 5f11e16..fadfe3b 100644 --- a/dev_scripts/cflow.py +++ b/dev_scripts/cflow.py @@ -52,6 +52,8 @@ def edit_pyc_lines(pyc: PYCFile, src_lines: list[str]): line_insts[0].starts_line = lno for inst in line_insts[1:]: inst.starts_line = None + if lno is not None and lno + 1 not in lno_bytecodes and pyc.version <= (3, 7) and src_lines[lno - 1].strip().startswith('@'): + bc.instructions[line_insts[0].offset // 2 + 1].starts_line = lno + 1 def run(file: Path, out_dir: Path, version: PythonVersion, print=False):