SMAT-Lab / Scalpel

Scalpel: The Python Static Analysis Framework
Apache License 2.0
295 stars 46 forks source link

CFG with try-catch blocks is missing some code #98

Closed samuelchassot closed 1 year ago

samuelchassot commented 1 year ago

Hi, First of all, thanks for sharing this framework, this offers great opportunities for Python static analysis :)

I am building a tool that uses the CFG part of Scalpel, and I think I found a bug. When extracting the CFG of a function containing a try-catch-except-finally block, the CFG does not take the code in the finally or after the try-except blocks into account.

Here is an example showing the behaviour I am describing:

import textwrap
import scalpel.cfg as scfg

src = textwrap.dedent(
        """
        def f():
            try:
                a = open("test.txt", "r")
                v = a.read()
                a.close()
            except IOError:
                v = "ioError"
            except:
                v = "otherError"
            finally:
                a = 123

        return v + str(a)
        """
    )

f_name = "f"

cfg_top_level: scfg.CFG = cast(
    type(scfg.CFGBuilder),
    scfg.CFGBuilder().build_from_src(name="top_level", src=src),
)

cfg_function = cast(
    type(scfg.CFGBuilder),
    CFGHelper.get_fct_block(cfg_top_level, f_name),
)

cfg_helper_without_ret = CFGHelper(cfg_function_without_ret)
cfg_function.build_visual("png")

Running this code outputs this CFG as png:

image

As we can see, a block is missing after the 3 blocks of the try-except part. There is not other blocks in the CFG object, it does not seem to be a rendering bug.

I am available for any missing information or to try out stuff :)

Thanks a lot!

Jarvx commented 1 year ago

This is indeed an important issue as cfg is a core component of the framework. Thanks for raising this test case, it will be given top priority.

samuelchassot commented 1 year ago

Great! You're fast :) I am looking into the code, I'll open a PR if I can fix the bug!

samuelchassot commented 1 year ago

This example works as intended:

"""
 src = textwrap.dedent("""
        def f():
            x = 1
            try:
                a = open("a.txt", "r")
                x = 2
                a.close()
            except IOError:
                x = 3
            except:
                x = 4
            finally:
                y = 1

            y += 6
            return x + y    
        """)

(it is to keep track, I'm working on a fix)