Technologicat / pyan

Static call graph generator. The official Python 3 version. Development repo.
GNU General Public License v2.0
324 stars 57 forks source link

Can not build graph from most of the projects I have tried #40

Open avijit1258 opened 3 years ago

avijit1258 commented 3 years ago

Hello,

Thanks for maintaining pyan. I have been trying to use pyan to generate caller-callee relationship in trivial graph format (tgf) for my research work. For example, to get static call graph of this (https://github.com/tiangolo/fastapi) repo I have tried the following commands

/home/avijit/Github/updated_pyan/pyan/pyan3 *.py --uses --no-defines --colored --grouped --annotated -f fastapi.txt --uses --no-defines --tgf By executing above command, I am getting empty file with a '#' which seems like glob failed to load any .py file.

/home/avijit/Github/updated_pyan/pyan/pyan3 fastapi/*.py --uses --no-defines --colored --grouped --annotated -f fastapi.txt --uses --no-defines --tgf

When I tried to point to the source files in fastapi folder, I am getting the following error.

Screen Shot 2020-09-13 at 6 06 03 PM

I want to execute pyan for different popular python repositories on Github with nested folder structures which pyan fails most of the time. Please let me know if there is any workaround to make pyan working to generate tgf file for python 3 projects. I would be glad to contribute if there is any need.

Thanks Avijit

Technologicat commented 3 years ago

Hi,

For a nested folder structure, Pyan itself has no recurse-into-subdirectories option, but shells do. If you're using bash, shopt -s globstar (see e.g. here), and then, as the filename pattern, use **/*.py instead of just *.py. If needed, use ls **/*.py to check what it would find.

Pyan should work for Python 3, up to and including 3.7 - but there have been some AST changes in Python 3.6 which it hasn't been extensively tested with. AnnAssign is one of those.

(Note in Python 3.8 there are AST changes that require changes to Pyan before it'll work. Various literals now produce ast.Constant instead of ast.Num, ast.Str, and some others; and there's the new ast.NamedExpr for the walrus operator :=.)

As for the error, thanks for reporting! I didn't immediately see a trivial mistake in the code, so this requires some debugging.

Thanks for the link to a repo which makes the analysis fail - this should make it much easier to track down.

Contributions are welcome! I don't have much time for developing Pyan right now, but I'll be glad to answer any questions.

If you would like to try fixing the error yourself, just let me know. The important things to know are:

avijit1258 commented 3 years ago

Thank you so much for your detailed reply with resources.I am trying to debug analyzer.py. However, looks like there is some issue with entry point due to recent updates. I can not run pyan3 following the instruction in readme. I have tried pip install pyan3. When I try to use pyan3 from command line it says pyan module not found. Is there any way to run pyan3 from the source code now.

johnyf commented 3 years ago

pip install . or pip install -e . from within the repository (after cloning) would work (using #43).

Daniel-Mock commented 3 years ago

@Technologicat I am interested in adding support for pytest fixture calls. Right now, when I run pyan3 on a basic structure involving pytest tests & fixtures, it shows them as modules & function types, but does not show how they are being called: image

Here's how the example fixtures are being called: image

I am working my way through Analyzer.py to see where to add support for this. Any suggestions are appreciated. Thanks!

Technologicat commented 3 years ago

Hi @Daniel-Mock ! Sorry for the late reply, I've been working on another project.

Fixtures break ZoP's explicit is better than implicit guideline: they are imported implicitly by pytest.

So, if we want to support them, what we need is some logic to statically detect which fixtures are in scope (to know which names refer to fixtures), and where they are defined (so we can add the uses-edges). This is probably a rather large and complex change, and it's beyond the scope of what I need pyan to do. But if you want to have a go at adding it:

Detecting the use of a fixture

    def attr_ast_to_dotted_name(tree):
        # Input is like:
        #     (a.b).thing
        #     ((a.b).c).thing
        #     ...
        if type(tree) is not ast.Attribute:
            raise TypeError
        acc = []
        def recurse(tree):
            acc.append(tree.attr)
            if type(tree.value) is ast.Attribute:
                recurse(tree.value)
            elif type(tree.value) is ast.Name:
                acc.append(tree.value.id)
            else:
                raise NotImplementedError
        recurse(tree)
        return ".".join(reversed(acc))

(That snippet comes from an old development version of mcpyrate.) If you want to handle the fully general case (not only ast.Attribute and ast.Name), you'll need a Python AST unparser, such as this one. (You can strip away the AST marker stuff, that's a mcpyrate thing.) There are also suggestions for unparser libraries in the AST docs. From a cursory look, parso looks promising and should be up to date, but I haven't used it myself. Alternatively, you could also skip the unparsing, and analyze the AST directly - whichever is easier.

Gathering the definitions of fixtures