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

Creating a list of all possible function paths #1

Closed Gharibw closed 6 years ago

Gharibw commented 6 years ago

Pyan has been super helpful to me! However, I would like to use it in a different way to output, in addition to the current graphs, a list of all possible functions' paths start->end. Right now, the Graph edges include the source and the target (e.g., A -> B and another edge B -> C). What I want to do is to print (A -> B -> C). Is that currently supported? If not, where to start? Any guidance is appreciated.

#Assume we have the following functions:
def A(): 
    C()
    B()
def B():
    C()

I want the output to be something like: A -> C -> D A -> B -> C -> D

Technologicat commented 6 years ago

Unfortunately, that's not supported. But you could extract that information by walking the graph in a variety of ways:

  1. Read in Pyan's output file in a completely separate program, or

  2. Use Pyan's analyzer as a library, and grab the information directly from its data structures once it's done running.

For option 2, specifically, look at uses_edges in analyzer.py; it's a dictionary, where the key is a Node object (node.py), and the corresponding value is a list of Node objects. The key is the "from", the items in the value list are the "to"s.

So given a CallGraphVisitor instance analyzer, it should be possible to do something like:

def make_chains(edges):
    def make_chains_rec(from_node):
        to_nodes = edges.get(from_node, [])
        if len(to_nodes):
            chains = []
            for target in to_nodes:
                tails = make_chains_rec(target)
                for tail in tails:
                    chains.append([from_node] + tail)
            return chains
        else:
            return [from_node]

    all_chains = {}
    for from_node in edges.keys():
        all_chains[from_node] = analyzerec(from_node)

    return all_chains

make_chains(analyzer.uses_edges)

...modulo any bugs. This is a fairly simple recursive problem, but I didn't actually test the solution ;)

There's also a defines_edges, which has the same format, if you want to do this analysis for "defines" relationships. (Maybe useful to catch nested defs, otherwise maybe not so interesting.)

As for how to instantiate and run the CallGraphVisitor, look at main.py.

Hope this helps!

Technologicat commented 6 years ago

One more thing - once you have the output of make_chains, you may be interested in the flavor, name and namespace attributes of the Node objects.

(And there's at least one typo in the above code snippet - analyzerec should be make_chains_rec. Renamed it for clarity, but forgot to change one of the calls.)

Technologicat commented 6 years ago

No new comments since end of May, closing issue. Feel free to re-open if needed.