13 views
 owned this note
# pylingual: PLDI Tut3 --- ## Tutorial preparation 1. Connect to tutorial VM via ```shell= ssh <userid>@pldi25.pylingual.io ``` 2. Clone the repo on the VM and create a virtual environment ```shell== mkdir $HOME/repos cd $HOME/repos # checkout local version of pylingual source git clone /usr/share/pylingual cd pylingual python -m venv venv --system-site-packages source venv/bin/activate pip install -e . ``` 3. To confirm the correct set-up please run the following command. ```shell= git status On branch pldi Your branch is up to date with 'origin/pldi'. ``` 4. (optional) You are recommended to a tmux session for the tutorial session. ```shell= tmux new-session -t tut3 ``` ## Control Flow Reconstruction To reconstruct control flow, PyLingual matches [templates] that model control flow structures found in Python. We attempt to match these templates from the bottom up and once a template is matched we add the appropriate indentation levels. **Please note that control flow structures may differ across versions.** ### Creating a template Each template is implemented as a class inheriting from the ControlFlowTemplate class, and each template needs to be registered with the `register_template` decorator to be used. `register_template(priority, run, version)` where priority is the priority of your template over other templates, run the run where your template can begin being matched, and version being the python version your template is used for. Ex. `@register_template(0,0(3,10))` ### Template setup Setup your template by adding nodes via the T() function. Create the nodes and add their edges through the N() class. Add the name of the node you wish to connect to as a string. ```python= template = T(node=N("natural edge", "conditional edge","exception edge") node2=N("node") ) ``` ### Matching nodes To match nodes each class requires a try_match function. However you can use the `make_try_match` function to create the function for you by providing the nodes of your template into the function. Note down EdgeKind.Falls and EdgeKing.Exceptions. For cases where a node can exist, but does not need to exist. ### To indented source Once a node its match to_indented_source is called and turns the contents of it's nodes into source code. using the `@to_indented_source` decorator you can use a docstring to create your indented source. ## With statement (3.10) ### Test File 1 Run the control flow reconstructor on the `with` test file: ```shell= python dev_scripts/cflow.py test_files/with.py -v3.10 ``` ```python= === original file === def read_file(filename): with open(filename, 'r') as file: content = file.read() return content === reconstructed file === def read_file(filename): # meta: irreducible cflow pass === equivalence report === <module>: Success: Equal <module>.read_file: Failure: Different control flow ``` The control flow reconstructor was not successful. Check the CFGs to see where it got stuck: ```shell= # generate cfgs with -g flag python dev_scripts/cflow.py test_files/with.py -v3.10 -g # you can view the cfgs in your browser using this script, # at pldi25.pylingual.io:<port> # where port is 5000 + your user id # (port 5000 for user00, port 5001 for user01, ...) python dev_scripts/dotviewer.py ``` ```graphviz digraph "<module>.read_file_10_1.dot" { graph [bb="0,0,940.5,959.75", splines=true ]; node [label="\N"]; 8074026080507 [fontname="Courier New", height=0.5, label="Node 6: <38: RERAISE 1>\l", labeljust=l, pos="771.5,109.48", shape=box, width=1.9444]; 8074026080417 [fontname="Courier New", height=0.5, label="MetaTemplate[end]\l", labeljust=l, pos="617.5,18", shape=box, width=2.1806]; 8074026080507 -> 8074026080417 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="677.49,71.236", pos="e,647.86,36.036 741.07,91.406 716.92,77.06 682.98,56.898 656.78,41.333"]; 8074026080441 [fontname="Courier New", height=0.5, label="MetaTemplate[start]\l", labeljust=l, pos="494.5,941.75", shape=box, width=2.4028]; 8074026080537 [fontname="Courier New", height=1.3611, label="Node 1: BlockTemplate[\l| [2] <0: LOAD_GLOBAL 0 (open)>\l| <2: LOAD_FAST 0 (filename)>\l| <4: LOAD_CONST 1 (\"r\")>\l| <6: CALL_\ FUNCTION 2 (2 positional arguments)>\l| <8: SETUP_WITH 13 (to 34)>]\l", labeljust=l, pos="494.5,818.45", shape=box, width=5.9583]; 8074026080441 -> 8074026080537 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="477.5,903.18", pos="e,494.5,867.64 494.5,923.56 494.5,911.35 494.5,894.52 494.5,877.91"]; 8074026080549 [fontname="Courier New", height=1.5694, label="Node 2: BlockTemplate[\l| <10: STORE_FAST 1 (file)>\l| [3] <12: LOAD_FAST 1 (file)>\l| <14: LOAD_METHOD 1 (read)>\l| <16: CALL_\ METHOD 0 (0 positional arguments)>\l| <18: STORE_FAST 2 (content)>\l| <20: POP_BLOCK>]\l", labeljust=l, pos="494.5,633.26", shape=box, width=5.8472]; 8074026080537 -> 8074026080549 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="465.5,737.14", pos="e,494.5,689.85 494.5,769.31 494.5,748.03 494.5,722.78 494.5,699.86", type=natural]; 8074026080552 [fontname="Courier New", height=1.5694, label="Node 4: BlockTemplate[\l| <22: LOAD_CONST 0 (None)>\l| <24: DUP_TOP>\l| <26: DUP_TOP>\l| <28: CALL_FUNCTION 3 (3 positional \ arguments)>\l| <30: POP_TOP>\l| <32: JUMP_ABSOLUTE 52 (to 50)>]\l", labeljust=l, pos="218.5,437.45", shape=box, width=6.0694]; 8074026080549 -> 8074026080552 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="327.52,542.87", pos="e,298.15,493.96 414.67,576.62 380.77,552.57 341.14,524.46 306.4,499.81", type=natural]; 8074026080561 [fontname="Courier New", height=0.73611, label="Node 3: BlockTemplate[\l| <34: WITH_EXCEPT_START>\l| <36: POP_JUMP_IF_TRUE 21 (to 40)>]\l", labeljust=l, pos="771.5,437.45", shape=box, width=4.6944]; 8074026080549 -> 8074026080561 [color=red, fontname="Courier New", kind=exception, label=exception, labeljust=l, lp="616.57,527.96", pos="e,733.8,464.1 574.62,576.62 623.65,541.96 684.63,498.86 725.62,469.88", type=exception]; 8074026080564 [fontname="Courier New", height=0.73611, label="Node 7: BlockTemplate[\l| [4] <50: LOAD_FAST 2 (content)>\l| <52: RETURN_VALUE>]\l", labeljust=l, pos="464.5,109.48", shape=box, width=4.3611]; 8074026080552 -> 8074026080564 [color=black, fontname="Courier New", kind=jump, label=jump, labeljust=l, lp="336.13,265.46", pos="e,444.49,136.16 261.07,380.7 312.08,312.69 395.96,200.85 438.31,144.4", type=jump]; 8074026080561 -> 8074026080507 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="742.5,277.19", pos="e,771.5,127.79 771.5,410.89 771.5,350.04 771.5,200.95 771.5,138.13", type=natural]; 8074026080543 [fontname="Courier New", height=1.3611, label="Node 5: BlockTemplate[\l| <40: POP_TOP>\l| <42: POP_TOP>\l| <44: POP_TOP>\l| <46: POP_EXCEPT>\l| <48: POP_TOP>]\l", labeljust=l, pos="548.5,252.25", shape=box, width=2.6389]; 8074026080561 -> 8074026080543 [color=green, fontname="Courier New", kind=true_jump, label=true_jump, labeljust=l, lp="636.11,363.65", pos="e,607.62,301.35 739.55,410.92 707.3,384.14 656.54,341.98 615.44,307.84", type=true_jump]; 8074026080543 -> 8074026080564 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="470.7,176.81", pos="e,480.24,136.23 519.59,203.12 508.36,184.02 495.73,162.57 485.41,145.02", type=natural]; 8074026080564 -> 8074026080417 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="530.97,67.071", pos="e,587.09,36.185 509.17,82.774 531.08,69.669 557.25,54.026 578.37,41.397"]; } ``` ### Reading the Control Flow Graph Before making a template you must first understand what each node in the control flow graph corresponds to. #### Node 1 `setup_with` The bytecode in this node appears to be the setup for the with, it contains the open function call and its parameters. It also has a natural edge to **node 2**. #### Node 2 `with_body` The bytecode in this node appears to correspond to our with's body as the bytecode for content = file.read() is in it. This has an exception edge to **node 3** and a natural edge to **node 4**. #### Node 3 `with_except_start` This node has bytecode setting up exception handling. With a conditional edge to **node 5** and an exception edge to **node 6**. #### Node 4 `cleanup` This node is the natural edge for the with body. The bytecode in this node does not correspond to any source code while preparing the stack. This has one natural edge to **node 7**. #### Node 5 `pop_top` This node is cleaning up after an exception is raised. This has one natural edge to **node 7**. #### Node 6 `reraise` This is a node that raises an exception that terminates the program. #### Node 7 `tail` This is beginning of code beyond the with statement. ### Nodes to code ```python= # registers the template for version 3.10, # note your template will not be matched without registering it @register_template(0, 0, (3, 10)) class With3_10(ControlFlowTemplate): # node_name = N("natural edge", "conditional edge, exception edge") template = T( setup_with=N("with_body"), with_body=N("cleanup", None, "with_except_start"), with_except_start=N("reraise", "pop_top"), cleanup=N("tail"), pop_top=N("tail"), reraise=N.tail(), tail=N.tail(), ) # add all your nodes into make_try_match to try matching try_match = make_try_match( # EdgeKind.Fall means tail doesn't have to exist to match {EdgeKind.Fall: "tail"}, "setup_with", "with_body", "cleanup", "with_except_start", \ "pop_top", "reraise" ) # use contents of nodes to create indented source @to_indented_source def to_indented_source(): """ {setup_with} {with_body} """ ``` Rerun the control flow reconstructor to see if the change solves the issue: ```shell= python dev_scripts/cflow.py test_files/with.py -v3.10 ``` ### CFlow output ```python== """original file""" def read_file(filename): with open(filename, 'r') as file: content = file.read() return content """reconstructed file""" def read_file(filename): with open(filename, 'r') as file: content = file.read() return content """equivalence report""" <module>: Success: Equal <module>.read_file: Success: Equal ``` <!-- ### Test File 2 Now try it on the next test file: ``` python dev_scripts/cflow.py test_files/with2.py -v3.10 ``` ``` === original file === def compare_file(filename1, filename2): with open(filename1, 'r') as file1, open(filename2, 'r') as file2: content1 = file1.read() content2 = file2.read() if content1 == content2: print('files are equal') else: print('files are not equal') === reconstructed file === def compare_file(filename1, filename2): # meta: irreducible cflow pass === equivalence report === <module>: Success: Equal <module>.compare_file: Failure: Different control flow ``` Replace the with-template with a more versitile one: ```python class WithCleanup3_10(ControlFlowTemplate): template = T( start=~N("reraise", "poptop").with_cond(starting_instructions("WITH_EXCEPT_START")), reraise=+N().with_cond(exact_instructions("RERAISE")).with_in_deg(1), poptop=~N("tail.", None).with_cond(starting_instructions("POP_TOP")).with_in_deg(1), tail=N.tail(), ) try_match = make_try_match({EdgeKind.Fall: "tail"}, "start", "reraise", "poptop") @to_indented_source def to_indented_source(): """ {poptop} """ @register_template(0, 0, (3, 10)) class With3_10(ControlFlowTemplate): template = T( setup_with=~N("with_body", None), with_body=N("normal_cleanup.", None, "exc_cleanup").with_in_deg(1), exc_cleanup=N.tail().of_subtemplate(WithCleanup3_10).with_in_deg(1), normal_cleanup=~N.tail(), ) try_match = make_try_match({EdgeKind.Fall: "normal_cleanup"}, "setup_with", "with_body", "exc_cleanup") @to_indented_source def to_indented_source(): """ {setup_with} {with_body} {exc_cleanup} """ ``` ``` === original file === def compare_file(filename1, filename2): with open(filename1, 'r') as file1, open(filename2, 'r') as file2: content1 = file1.read() content2 = file2.read() if content1 == content2: print('files are equal') else: print('files are not equal') === reconstructed file === def compare_file(filename1, filename2): with open(filename1, 'r') as file1, open(filename2, 'r') as file2: content1 = file1.read() content2 = file2.read() if content1 == content2: print('files are equal') else: print('files are not equal') === equivalence report === <module>: Success: Equal <module>.compare_file: Success: Equal ``` --> ## Try-Except 3.10 ### Test File 1 Run the control flow reconstructor with the `try-except` test file: ```shell= python dev_scripts/cflow.py test_files/try.py -v3.10 -g ``` ```python= """original file""" def division(a, b): try: result = a / b except: result = None return result """reconstructed file""" def division(a, b): # meta: irreducible cflow pass """equivalence report""" <module>: Success: Equal <module>.division: Failure: Different control flow ``` The CFG looks like this: ```graphviz digraph "<module>.division_10_1.dot" { graph [bb="0,0,678,712.26", splines=true ]; node [label="\N"]; 8740437942198 [fontname="Courier New", height=0.5, label="Node 1: [2] <0: SETUP_FINALLY 7 (to 14)>\l", labeljust=l, pos="330.75,622.26", shape=box, width=3.8889]; 8740437942117 [fontname="Courier New", height=1.4861, label="Node 2: BlockTemplate[\l| [3] <2: LOAD_FAST 0 (a)>\l| <4: LOAD_FAST 1 (b)>\l| <6: BINARY_TRUE_DIVIDE>\l| <8: STORE_FAST 2 (result)>\l| <\ 10: POP_BLOCK>]\l", labeljust=l, pos="330.75,489.42", shape=box, width=3.7743]; 8740437942198 -> 8740437942117 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="301.88,581.79", pos="e,330.75,543.28 330.75,603.79 330.75,590.86 330.75,572.75 330.75,554.74", type=natural]; 8740437942192 [fontname="Courier New", height=0.5, label="Node 3: <12: JUMP_ABSOLUTE 28 (to 26)>\l", labeljust=l, pos="131.75,283.54", shape=box, width=3.6597]; 8740437942060 [fontname="Courier New", height=0.79861, label="Node 5: BlockTemplate[\l| [6] <26: LOAD_FAST 2 (result)>\l| <28: RETURN_VALUE>]\l", labeljust=l, pos="330.75,114.25", shape=box, width=4.2326]; 8740437942192 -> 8740437942060 [color=black, fontname="Courier New", kind=jump, label=jump, labeljust=l, lp="208.22,212.7", pos="e,296.48,143.4 153.43,265.1 185.59,237.74 246.6,185.84 287.97,150.65", type=jump]; 8740439082412 [fontname="Courier New", height=0.5, label="MetaTemplate[start]\l", labeljust=l, pos="330.75,694.26", shape=box, width=2.3993]; 8740439082412 -> 8740437942198 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="314.25,666.89", pos="e,330.75,640.67 330.75,676.09 330.75,668.81 330.75,660.24 330.75,652.09"]; 8740439082409 [fontname="Courier New", height=0.5, label="MetaTemplate[end]\l", labeljust=l, pos="330.75,18", shape=box, width=2.1701]; 8740437942117 -> 8740437942192 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="185.35,377.12", pos="e,149.56,301.97 278.73,435.6 239.61,395.13 187.71,341.44 157.2,309.87", type=natural]; 8740437942138 [fontname="Courier New", height=1.7153, label="Node 4: BlockTemplate[\l| [4] <14: POP_TOP>\l| <16: POP_TOP>\l| <18: POP_TOP>\l| [5] <20: LOAD_CONST 0 (None)>\l| <22: STORE_\ FAST 2 (result)>\l| <24: POP_EXCEPT>]\l", labeljust=l, pos="529.75,283.54", shape=box, width=4.1181]; 8740437942117 -> 8740437942138 [color=red, fontname="Courier New", kind=exception, label=exception, labeljust=l, lp="389.08,398.92", pos="e,469.93,345.43 382.77,435.6 406.99,410.55 436.1,380.43 462,353.63", type=exception]; 8740437942138 -> 8740437942060 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="381.92,190.6", pos="e,364.99,143.38 456.78,221.46 428.82,197.68 397.85,171.33 373.6,150.7", type=natural]; 8740437942060 -> 8740439082409 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="314.25,69.244", pos="e,330.75,36.434 330.75,85.111 330.75,73.242 330.75,59.539 330.75,47.622"]; } ``` ### Reading the Control Flow Graph #### Node 1 `try_header` This node has bytecode that sets up the try block and it has one natural edge to **node 2**. #### Node 2 `try_body` This node has bytecode that translates to the source code in the try body of our test case. This node has a natural edge to **node 3** and an exception edge to **node 4**. #### Node 3 `jump` This node is a jump with a natural edge to **node 5**. #### Node 4 `except_body` This node has bytecode that translates to the source code in the except block of our test case. This has one natural edge to **node 5** #### Node 5 `tail` This is the return statement after the try except block. Add the template to `pylingual/control_flow_reconstructor/templates/Exception.py`: ### Nodes to code ```python= # register template for 3.10 @register_template(0, 0, (3, 10)) class TryExcept3_10(ControlFlowTemplate): # node_name=N("natural edge", "conditional edge", "exception edge") template = T( try_header=N("try_body"), try_body=N("jump", None, "except_body"), jump=N("tail"), except_body=N("tail"), tail=N.tail(), ) ## EdgeKind.Fall means that tail does not have to exist to match this template try_match = make_try_match({EdgeKind.Fall: "tail"}, "try_header", "try_body", "jump", "except_body") @to_indented_source def to_indented_source(): """ {try_header} try: {try_body} except: {except_body} """ ``` ### CFlow Result ```python= """ original file """ def division(a, b): try: result = a / b except: result = None return result """ reconstructed file """ def division(a, b): try: result = a / b except: result = None return result ``` <!-- ### Test File 2 ``` python dev_scripts/cflow.py test_files/try2.py -v3.10 -g ``` ``` === original file === def division(a, b): try: result = a / b except Exception: result = None return result === reconstructed file === def division(a, b): try: result = a / b except: except Exception: result = None return result === equivalence report === Result.CompileError ``` `except:` and `except SomeException:` produce different CFGs: ```graphviz digraph "<module>.division_10_1.dot" { graph [bb="0,0,783.25,857.16", splines=true ]; node [label="\N"]; 8746727694301 [fontname="Courier New", height=0.5, label="[2] <0: SETUP_FINALLY 7 (to 14)>\l", labeljust=l, pos="362.75,767.16", shape=box, width=3.8889]; 8746727306314 [fontname="Courier New", height=1.4861, label="BlockTemplate[\l| [3] <2: LOAD_FAST 0 (a)>\l| <4: LOAD_FAST 1 (b)>\l| <6: BINARY_TRUE_DIVIDE>\l| <8: STORE_FAST 2 (result)>\l| <\ 10: POP_BLOCK>]\l", labeljust=l, pos="362.75,634.32", shape=box, width=3.7743]; 8746727694301 -> 8746727306314 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="333.88,726.69", pos="e,362.75,688.18 362.75,748.69 362.75,735.76 362.75,717.65 362.75,699.64", type=natural]; 8746727306446 [fontname="Courier New", height=0.5, label="<12: JUMP_ABSOLUTE 34 (to 32)>\l", labeljust=l, pos="131.75,465.03", shape=box, width=3.6597]; 8746727306317 [fontname="Courier New", height=0.79861, label="BlockTemplate[\l| [6] <32: LOAD_FAST 2 (result)>\l| <34: RETURN_VALUE>]\l", labeljust=l, pos="299.75,114.25", shape=box, width=4.2326]; 8746727306446 -> 8746727306317 [color=black, fontname="Courier New", kind=jump, label=jump, labeljust=l, lp="167.31,295.41", pos="e,271.74,143.11 135.63,446.77 145.1,404.57 171.84,297.8 215.8,218.09 228.94,194.26 247.69,170.44 264.13,151.64", type=jump]; 8746727306383 [fontname="Courier New", height=0.5, label="<36: RERAISE 0>\l", labeljust=l, pos="603.75,114.25", shape=box, width=1.941]; 8746727694328 [fontname="Courier New", height=0.5, label="MetaTemplate[end]\l", labeljust=l, pos="451.75,18", shape=box, width=2.1701]; 8746727306383 -> 8746727694328 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="511.12,74.292", pos="e,480.85,36.426 574.8,95.92 550.9,80.782 516.75,59.159 490.49,42.53"]; 8746727694316 [fontname="Courier New", height=0.5, label="MetaTemplate[start]\l", labeljust=l, pos="362.75,839.16", shape=box, width=2.3993]; 8746727694316 -> 8746727694301 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="346.25,811.79", pos="e,362.75,785.57 362.75,820.99 362.75,813.71 362.75,805.14 362.75,796.99"]; 8746727306314 -> 8746727306446 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="194.36,540.33", pos="e,156.84,483.42 289.34,580.52 247.98,550.21 198.27,513.78 165.9,490.05", type=natural]; 8746727306347 [fontname="Courier New", height=1.0278, label="BlockTemplate[\l| [4] <14: DUP_TOP>\l| <16: LOAD_GLOBAL 0 (Exception)>\l| <18: JUMP_IF_NOT_EXC_MATCH 19 (to 36)>]\l", labeljust=l, pos="593.75,465.03", shape=box, width=5.2639]; 8746727306314 -> 8746727306347 [color=red, fontname="Courier New", kind=exception, label=exception, labeljust=l, lp="452.39,549.67", pos="e,542.84,502.34 436.16,580.52 467.64,557.45 503.97,530.83 533.8,508.96", type=exception]; 8746727306347 -> 8746727306383 [color=green, fontname="Courier New", kind=false_jump, label=false_jump, labeljust=l, lp="557.77,288.52", pos="e,603.23,132.62 594.81,427.82 596.81,357.75 601.09,207.55 602.91,143.82", type=false_jump]; 8746727306311 [fontname="Courier New", height=1.7153, label="BlockTemplate[\l| <20: POP_TOP>\l| <22: POP_TOP>\l| <24: POP_TOP>\l| [5] <26: LOAD_CONST 0 (None)>\l| <28: STORE_\ FAST 2 (result)>\l| <30: POP_EXCEPT>]\l", labeljust=l, pos="367.75,283.54", shape=box, width=4.1181]; 8746727306347 -> 8746727306311 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="467.01,394.69", pos="e,444.94,345.53 547.2,427.65 520.27,406.02 485.53,378.12 453.8,352.64", type=natural]; 8746727306311 -> 8746727306317 [color=black, fontname="Courier New", kind=natural, label=natural, labeljust=l, lp="298.13,190.35", pos="e,311.36,143.15 342.82,221.46 333.73,198.85 323.72,173.92 315.63,153.8", type=natural]; 8746727306317 -> 8746727694328 [color=blue, fontname="Courier New", kind=meta, label=meta, labeljust=l, lp="367.86,68.924", pos="e,422.64,36.434 345.77,85.111 367.24,71.512 392.52,55.504 413.01,42.534"]; } ``` Replace the template with this one that checks for both cases: ```python class Except3_10(ControlFlowTemplate): @classmethod def try_match(cls, cfg, node) -> ControlFlowTemplate | None: if x := ExceptExc3_10.try_match(cfg, node): return x if x := BareExcept3_10.try_match(cfg, node): return x class ExceptExc3_10(Except3_10): template = T( except_header=N("except_body", "no_match"), except_body=N("tail"), no_match=N.tail(), tail=N.tail(), ) try_match = make_try_match({EdgeKind.Fall: "tail"}, "except_header", "except_body", "no_match") @to_indented_source def to_indented_source(): """ {except_header} {except_body} """ class BareExcept3_10(Except3_10): template = T( except_body=N("tail"), tail=N.tail(), ) try_match = make_try_match({EdgeKind.Fall: "tail"}, "except_body") @to_indented_source def to_indented_source(): """ except: {except_body} """ @register_template(0, 0, (3, 10)) class Try3_10(ControlFlowTemplate): template = T( try_header=N("try_body"), try_body=N("jump", None, "except_body"), jump=N("tail"), except_body=N("tail").of_subtemplate(Except3_10), tail=N.tail(), ) try_match = make_try_match({EdgeKind.Fall: "tail"}, "try_header", "try_body", "jump", "except_body") @to_indented_source def to_indented_source(): """ {try_header} try: {try_body} {except_body} """ ``` ``` python dev_scripts/cflow.py test_files/try.py -v3.10 python dev_scripts/cflow.py test_files/try2.py -v3.10 ``` ``` === original file === def division(a, b): try: result = a / b except: result = None return result === reconstructed file === def division(a, b): try: result = a / b except: result = None return result === equivalence report === <module>: Success: Equal <module>.division: Success: Equal === original file === def division(a, b): try: result = a / b except Exception: result = None return result === reconstructed file === def division(a, b): try: result = a / b except Exception: result = None return result === equivalence report === <module>: Success: Equal <module>.division: Success: Equal ``` Both cases work now. ### Test File 3 ``` python dev_scripts/cflow.py test_files/try3.py -v3.10 -g ``` ``` === original file === def division1(a, b): try: result = a / b except ZeroDivisionError: result = a except Exception: result = None return result def division2(a, b): try: result = a / b except TypeError: result = 0 except ZeroDivisionError: result = a except Exception: result = 1234 except: result = None return result === reconstructed file === def division1(a, b): # meta: irreducible cflow pass def division2(a, b): # meta: irreducible cflow pass === equivalence report === <module>: Success: Equal <module>.division1: Failure: Different control flow <module>.division2: Failure: Different control flow ``` There can be an arbitrary number of `except` statements on a `try` statement. Make the following changes to handle this: ```diff class Except3_10(ControlFlowTemplate): @classmethod def try_match(cls, cfg, node) -> ControlFlowTemplate | None: + if [x.opname for x in node.get_instructions()] == ["RERAISE"]: + return node if x := ExceptExc3_10.try_match(cfg, node): return x if x := BareExcept3_10.try_match(cfg, node): return x class ExceptExc3_10(Except3_10): template = T( except_header=N("except_body", "no_match"), except_body=N("tail"), - no_match=N.tail(), + no_match=N("tail.").of_subtemplate(Except3_10), tail=N.tail(), ) try_match = make_try_match({EdgeKind.Fall: "tail"}, "except_header", "except_body", "no_match") @to_indented_source def to_indented_source(): """ {except_header} {except_body} + {no_match} """ ``` --> More details [CFG document 1](/s/XbHVeE0WF) and [CFG document 2](/s/Ucm_4um7L). Have fun! ###### tags: `pldi`,`pylingual`,`standalone` --- [templates]:https://todo