コード例 #1
0
ファイル: script.py プロジェクト: yinglang/gcc-python-plugin
 def test_long_path(self):
     LENGTH = 1000
     g = Graph()
     first, last = add_long_path(g, LENGTH)
     self.assertEqual(len(g.edges), LENGTH)
     self.assertEqual(len(g.nodes), LENGTH + 1)
     dot = g.to_dot('example')
コード例 #2
0
ファイル: script.py プロジェクト: B-Rich/gcc-python-plugin
 def test_no_path(self):
     g = Graph()
     a = g.add_node(Node())
     b = g.add_node(Node())
     # no edges between them
     path = g.get_shortest_path(a, b)
     self.assertEqual(path, []) # FIXME: shouldn't this be None?
コード例 #3
0
ファイル: script.py プロジェクト: yinglang/gcc-python-plugin
 def test_cycle(self):
     LENGTH = 5
     g = Graph()
     first = add_cycle(g, LENGTH)
     self.assertEqual(len(g.edges), LENGTH)
     self.assertEqual(len(g.nodes), LENGTH)
     dot = g.to_dot('example')
コード例 #4
0
ファイル: script.py プロジェクト: yinglang/gcc-python-plugin
 def test_no_path(self):
     g = Graph()
     a = g.add_node(Node())
     b = g.add_node(Node())
     # no edges between them
     path = g.get_shortest_path(a, b)
     self.assertEqual(path, [])  # FIXME: shouldn't this be None?
コード例 #5
0
ファイル: script.py プロジェクト: B-Rich/gcc-python-plugin
 def test_cycle(self):
     LENGTH = 5
     g = Graph()
     first = add_cycle(g, LENGTH)
     self.assertEqual(len(g.edges), LENGTH)
     self.assertEqual(len(g.nodes), LENGTH)
     dot = g.to_dot('example')
コード例 #6
0
ファイル: script.py プロジェクト: B-Rich/gcc-python-plugin
 def test_long_path(self):
     LENGTH = 1000
     g = Graph()
     first, last = add_long_path(g, LENGTH)
     self.assertEqual(len(g.edges), LENGTH)
     self.assertEqual(len(g.nodes), LENGTH + 1)
     dot = g.to_dot('example')
コード例 #7
0
ファイル: script.py プロジェクト: yinglang/gcc-python-plugin
 def test_long_path(self):
     LENGTH = 100
     g = Graph()
     first, last = add_long_path(g, LENGTH)
     path = g.get_shortest_path(first, last)
     self.assertEqual(len(path), LENGTH)
     self.assertEqual(path[0].srcnode, first)
     self.assertEqual(path[-1].dstnode, last)
コード例 #8
0
ファイル: script.py プロジェクト: B-Rich/gcc-python-plugin
 def test_long_path(self):
     LENGTH = 100
     g = Graph()
     first, last = add_long_path(g, LENGTH)
     path = g.get_shortest_path(first, last)
     self.assertEqual(len(path), LENGTH)
     self.assertEqual(path[0].srcnode, first)
     self.assertEqual(path[-1].dstnode, last)
コード例 #9
0
ファイル: script.py プロジェクト: B-Rich/gcc-python-plugin
def make_trivial_graph():
    """
    Construct a trivial graph:
       a ─> b
    """
    g = Graph()
    a = g.add_node(NamedNode('a'))
    b = g.add_node(NamedNode('b'))
    ab = g.add_edge(a, b)
    return g, a, b, ab
コード例 #10
0
ファイル: script.py プロジェクト: yinglang/gcc-python-plugin
def make_trivial_graph():
    """
    Construct a trivial graph:
       a ─> b
    """
    g = Graph()
    a = g.add_node(NamedNode('a'))
    b = g.add_node(NamedNode('b'))
    ab = g.add_edge(a, b)
    return g, a, b, ab
コード例 #11
0
ファイル: script.py プロジェクト: yinglang/gcc-python-plugin
 def test_cycles(self):
     LENGTH = 5
     g = Graph()
     a = add_cycle(g, LENGTH)
     b = add_cycle(g, LENGTH)
     c = add_cycle(g, LENGTH)
     ab = g.add_edge(a, b)
     bc = g.add_edge(b, c)
     path = g.get_shortest_path(a, c)
     self.assertEqual(len(path), 2)
     p0, p1 = path
     self.assertEqual(p0, ab)
     self.assertEqual(p1, bc)
コード例 #12
0
ファイル: script.py プロジェクト: B-Rich/gcc-python-plugin
 def test_cycles(self):
     LENGTH = 5
     g = Graph()
     a = add_cycle(g, LENGTH)
     b = add_cycle(g, LENGTH)
     c = add_cycle(g, LENGTH)
     ab = g.add_edge(a, b)
     bc = g.add_edge(b, c)
     path = g.get_shortest_path(a, c)
     self.assertEqual(len(path), 2)
     p0, p1 = path
     self.assertEqual(p0, ab)
     self.assertEqual(p1, bc)
コード例 #13
0
ファイル: stmtgraph.py プロジェクト: bukzor/gcc-python-plugin
    def __init__(self, fun, split_phi_nodes, omit_complex_edges=False):
        """
        fun : the underlying gcc.Function

        split_phi_nodes:

           if true, split phi nodes so that there is one copy of each phi
           node per edge as a SplitPhiNode instance, allowing client code
           to walk the StmtGraph without having to track which edge we came
           from

           if false, create a StmtNode per phi node at the top of the BB

        """
        Graph.__init__(self)
        self.fun = fun
        self.entry = None
        self.exit = None
        # Mappings from gcc.BasicBlock to StmtNode so that we can wire up
        # the edges for the gcc.Edge:
        self.entry_of_bb = {}
        self.exit_of_bb = {}
        self.node_for_stmt = {}

        basic_blocks = fun.cfg.basic_blocks

        # 1st pass: create nodes and edges within BBs:
        for bb in basic_blocks:
            self.__lastnode = None

            def add_stmt(stmt):
                nextnode = self.add_node(StmtNode(fun, bb, stmt))
                self.node_for_stmt[stmt] = nextnode
                if self.__lastnode:
                    self.add_edge(self.__lastnode, nextnode, None)
                else:
                    self.entry_of_bb[bb] = nextnode
                self.__lastnode = nextnode

            if bb.phi_nodes and not split_phi_nodes:
                # If we're not splitting the phi nodes, add them to the top
                # of each BB:
                for stmt in bb.phi_nodes:
                    add_stmt(stmt)
                self.exit_of_bb[bb] = self.__lastnode
            if bb.gimple:
                for stmt in bb.gimple:
                    add_stmt(stmt)
                self.exit_of_bb[bb] = self.__lastnode

            if self.__lastnode is None:
                # We have a BB with neither statements nor phis
                # Create a single node for this BB:
                if bb == fun.cfg.entry:
                    cls = EntryNode
                elif bb == fun.cfg.exit:
                    cls = ExitNode
                else:
                    # gcc appears to create empty BBs for functions
                    # returning void that contain multiple "return;"
                    # statements:
                    cls = StmtNode
                node = self.add_node(cls(fun, bb, None))
                self.entry_of_bb[bb] = node
                self.exit_of_bb[bb] = node
                if bb == fun.cfg.entry:
                    self.entry = node
                elif bb == fun.cfg.exit:
                    self.exit = node

            assert self.entry_of_bb[bb] is not None
            assert self.exit_of_bb[bb] is not None

        # 2nd pass: wire up the cross-BB edges:
        for bb in basic_blocks:
            for edge in bb.succs:

                # If requested, omit "complex" edges e.g. due to
                # exception-handling:
                if omit_complex_edges:
                    if edge.complex:
                        continue

                last_node = self.exit_of_bb[bb]
                if split_phi_nodes:
                    # add SplitPhiNode instances at the end of each edge
                    # as a copy of each phi node, specialized for this edge
                    if edge.dest.phi_nodes:
                        for stmt in edge.dest.phi_nodes:
                            split_phi = self.add_node(SplitPhiNode(fun, stmt, edge))
                            self.add_edge(last_node,
                                          split_phi,
                                          edge)
                            last_node = split_phi

                # After optimization, the CFG sometimes contains edges that
                # point to blocks that are no longer within fun.cfg.basic_blocks
                # Skip them:
                if edge.dest not in basic_blocks:
                    continue

                self.add_edge(last_node,
                              self.entry_of_bb[edge.dest],
                              edge)

        # 3rd pass: set up caselabelexprs for edges within switch statements
        # There doesn't seem to be any direct association between edges in a
        # CFG and the switch labels; store this information so that it's
        # trivial to go from an edge to the set of case labels that might be
        # being followed:
        for stmt in self.node_for_stmt:
            if isinstance(stmt, gcc.GimpleSwitch):
                labels = stmt.labels
                node = self.node_for_stmt[stmt]
                for edge in node.succs:
                    caselabelexprs = set()
                    for label in labels:
                        dststmtnode_of_labeldecl = self.get_node_for_labeldecl(label.target)
                        if dststmtnode_of_labeldecl == edge.dstnode:
                            caselabelexprs.add(label)
                    edge.caselabelexprs = frozenset(caselabelexprs)
コード例 #14
0
    def __init__(self, fun, split_phi_nodes, omit_complex_edges=False):
        """
        fun : the underlying gcc.Function

        split_phi_nodes:

           if true, split phi nodes so that there is one copy of each phi
           node per edge as a SplitPhiNode instance, allowing client code
           to walk the StmtGraph without having to track which edge we came
           from

           if false, create a StmtNode per phi node at the top of the BB

        """
        Graph.__init__(self)
        self.fun = fun
        self.entry = None
        self.exit = None
        # Mappings from gcc.BasicBlock to StmtNode so that we can wire up
        # the edges for the gcc.Edge:
        self.entry_of_bb = {}
        self.exit_of_bb = {}
        self.node_for_stmt = {}

        basic_blocks = fun.cfg.basic_blocks

        # 1st pass: create nodes and edges within BBs:
        for bb in basic_blocks:
            self.__lastnode = None

            def add_stmt(stmt):
                nextnode = self.add_node(StmtNode(fun, bb, stmt))
                self.node_for_stmt[stmt] = nextnode
                if self.__lastnode:
                    self.add_edge(self.__lastnode, nextnode, None)
                else:
                    self.entry_of_bb[bb] = nextnode
                self.__lastnode = nextnode

            if bb.phi_nodes and not split_phi_nodes:
                # If we're not splitting the phi nodes, add them to the top
                # of each BB:
                for stmt in bb.phi_nodes:
                    add_stmt(stmt)
                self.exit_of_bb[bb] = self.__lastnode
            if bb.gimple:
                for stmt in bb.gimple:
                    add_stmt(stmt)
                self.exit_of_bb[bb] = self.__lastnode

            if self.__lastnode is None:
                # We have a BB with neither statements nor phis
                # Create a single node for this BB:
                if bb == fun.cfg.entry:
                    cls = EntryNode
                elif bb == fun.cfg.exit:
                    cls = ExitNode
                else:
                    # gcc appears to create empty BBs for functions
                    # returning void that contain multiple "return;"
                    # statements:
                    cls = StmtNode
                node = self.add_node(cls(fun, bb, None))
                self.entry_of_bb[bb] = node
                self.exit_of_bb[bb] = node
                if bb == fun.cfg.entry:
                    self.entry = node
                elif bb == fun.cfg.exit:
                    self.exit = node

            assert self.entry_of_bb[bb] is not None
            assert self.exit_of_bb[bb] is not None

        # 2nd pass: wire up the cross-BB edges:
        for bb in basic_blocks:
            for edge in bb.succs:

                # If requested, omit "complex" edges e.g. due to
                # exception-handling:
                if omit_complex_edges:
                    if edge.complex:
                        continue

                last_node = self.exit_of_bb[bb]
                if split_phi_nodes:
                    # add SplitPhiNode instances at the end of each edge
                    # as a copy of each phi node, specialized for this edge
                    if edge.dest.phi_nodes:
                        for stmt in edge.dest.phi_nodes:
                            split_phi = self.add_node(
                                SplitPhiNode(fun, stmt, edge))
                            self.add_edge(last_node, split_phi, edge)
                            last_node = split_phi

                # After optimization, the CFG sometimes contains edges that
                # point to blocks that are no longer within fun.cfg.basic_blocks
                # Skip them:
                if edge.dest not in basic_blocks:
                    continue

                self.add_edge(last_node, self.entry_of_bb[edge.dest], edge)

        # 3rd pass: set up caselabelexprs for edges within switch statements
        # There doesn't seem to be any direct association between edges in a
        # CFG and the switch labels; store this information so that it's
        # trivial to go from an edge to the set of case labels that might be
        # being followed:
        for stmt in self.node_for_stmt:
            if isinstance(stmt, gcc.GimpleSwitch):
                labels = stmt.labels
                node = self.node_for_stmt[stmt]
                for edge in node.succs:
                    caselabelexprs = set()
                    for label in labels:
                        dststmtnode_of_labeldecl = self.get_node_for_labeldecl(
                            label.target)
                        if dststmtnode_of_labeldecl == edge.dstnode:
                            caselabelexprs.add(label)
                    edge.caselabelexprs = frozenset(caselabelexprs)
コード例 #15
0
 def add_node(self, supernode):
     Graph.add_node(self, supernode)
     # Keep track of mapping from stmtnode -> supernode
     self.supernode_for_stmtnode[supernode.innernode] = supernode
     return supernode
コード例 #16
0
    def __init__(self, split_phi_nodes, add_fake_entry_node):
        Graph.__init__(self)
        self.supernode_for_stmtnode = {}
        # 1st pass: locate interprocedural instances of gcc.GimpleCall
        # i.e. where both caller and callee are within the supergraph
        # (perhaps the same function)
        ipcalls = set()
        from gcc import get_callgraph_nodes
        for node in get_callgraph_nodes():
            fun = node.decl.function
            if fun:
                for edge in node.callees:
                    if edge.callee.decl.function:
                        ipcalls.add(edge.call_stmt)

        # 2nd pass: construct a StmtGraph for each function in the callgraph
        # and add nodes and edges to "self" wrapping the nodes and edges
        # within each StmtGraph:
        self.stmtg_for_fun = {}
        for node in get_callgraph_nodes():
            fun = node.decl.function
            if fun:
                stmtg = StmtGraph(fun, split_phi_nodes)
                self.stmtg_for_fun[fun] = stmtg
                # Clone the stmtg nodes and edges into the Supergraph:
                stmtg.supernode_for_stmtnode = {}
                for node in stmtg.nodes:
                    if node.stmt in ipcalls:
                        # These nodes will have two supernodes, a CallNode
                        # and a ReturnNode:
                        callnode = self.add_node(CallNode(node, stmtg))
                        returnnode = self.add_node(ReturnNode(node, stmtg))
                        callnode.returnnode = returnnode
                        returnnode.callnode = callnode
                        stmtg.supernode_for_stmtnode[node] = (callnode,
                                                              returnnode)
                        self.add_edge(callnode, returnnode,
                                      CallToReturnSiteEdge, None)
                    else:
                        stmtg.supernode_for_stmtnode[node] = \
                            self.add_node(SupergraphNode(node, stmtg))
                for edge in stmtg.edges:
                    if edge.srcnode.stmt in ipcalls:
                        # Begin the superedge from the ReturnNode:
                        srcsupernode = stmtg.supernode_for_stmtnode[
                            edge.srcnode][1]
                    else:
                        srcsupernode = stmtg.supernode_for_stmtnode[
                            edge.srcnode]
                    if edge.dstnode.stmt in ipcalls:
                        # End the superedge at the CallNode:
                        dstsupernode = stmtg.supernode_for_stmtnode[
                            edge.dstnode][0]
                    else:
                        dstsupernode = stmtg.supernode_for_stmtnode[
                            edge.dstnode]
                    superedge = self.add_edge(srcsupernode, dstsupernode,
                                              SupergraphEdge, edge)

        # 3rd pass: add the interprocedural edges (call and return):
        for node in get_callgraph_nodes():
            fun = node.decl.function
            if fun:
                for edge in node.callees:
                    if edge.callee.decl.function:
                        calling_stmtg = self.stmtg_for_fun[fun]
                        called_stmtg = self.stmtg_for_fun[
                            edge.callee.decl.function]

                        calling_stmtnode = calling_stmtg.node_for_stmt[
                            edge.call_stmt]
                        assert calling_stmtnode

                        entry_stmtnode = called_stmtg.entry
                        assert entry_stmtnode

                        exit_stmtnode = called_stmtg.exit
                        assert exit_stmtnode

                        superedge_call = self.add_edge(
                            calling_stmtg.
                            supernode_for_stmtnode[calling_stmtnode][0],
                            called_stmtg.
                            supernode_for_stmtnode[entry_stmtnode],
                            CallToStart, None)
                        superedge_return = self.add_edge(
                            called_stmtg.supernode_for_stmtnode[exit_stmtnode],
                            calling_stmtg.
                            supernode_for_stmtnode[calling_stmtnode][1],
                            ExitToReturnSite, None)
                        superedge_return.calling_stmtnode = calling_stmtnode

        # 4th pass: create fake entry node:
        if not add_fake_entry_node:
            self.fake_entry_node = None
            return

        self.fake_entry_node = self.add_node(FakeEntryNode(None, None))
        """
	/* At file scope, the presence of a `static' or `register' storage
	   class specifier, or the absence of all storage class specifiers
	   makes this declaration a definition (perhaps tentative).  Also,
	   the absence of `static' makes it public.  */
	if (current_scope == file_scope)
	  {
	    TREE_PUBLIC (decl) = storage_class != csc_static;
	    TREE_STATIC (decl) = !extern_ref;
	  }
          """
        # For now, assume all non-static functions are possible entrypoints:
        for fun in self.stmtg_for_fun:
            # Only for non-static functions:
            if fun.decl.is_public:
                stmtg = self.stmtg_for_fun[fun]
                self.add_edge(self.fake_entry_node,
                              stmtg.supernode_for_stmtnode[stmtg.entry],
                              FakeEntryEdge, None)
コード例 #17
0
 def add_node(self, supernode):
     Graph.add_node(self, supernode)
     # Keep track of mapping from stmtnode -> supernode
     self.supernode_for_stmtnode[supernode.innernode] = supernode
     return supernode
コード例 #18
0
    def __init__(self, split_phi_nodes, add_fake_entry_node):
        Graph.__init__(self)
        self.supernode_for_stmtnode = {}
        # 1st pass: locate interprocedural instances of gcc.GimpleCall
        # i.e. where both caller and callee are within the supergraph
        # (perhaps the same function)
        ipcalls = set()
        from gcc import get_callgraph_nodes
        for node in get_callgraph_nodes():
            fun = node.decl.function
            if fun:
                for edge in node.callees:
                    if edge.callee.decl.function:
                        ipcalls.add(edge.call_stmt)

        # 2nd pass: construct a StmtGraph for each function in the callgraph
        # and add nodes and edges to "self" wrapping the nodes and edges
        # within each StmtGraph:
        self.stmtg_for_fun = {}
        for node in get_callgraph_nodes():
            fun = node.decl.function
            if fun:
                stmtg = StmtGraph(fun, split_phi_nodes)
                self.stmtg_for_fun[fun] = stmtg
                # Clone the stmtg nodes and edges into the Supergraph:
                stmtg.supernode_for_stmtnode = {}
                for node in stmtg.nodes:
                    if node.stmt in ipcalls:
                        # These nodes will have two supernodes, a CallNode
                        # and a ReturnNode:
                        callnode = self.add_node(CallNode(node, stmtg))
                        returnnode = self.add_node(ReturnNode(node, stmtg))
                        callnode.returnnode = returnnode
                        returnnode.callnode = callnode
                        stmtg.supernode_for_stmtnode[node] = (callnode, returnnode)
                        self.add_edge(
                            callnode, returnnode,
                            CallToReturnSiteEdge, None)
                    else:
                        stmtg.supernode_for_stmtnode[node] = \
                            self.add_node(SupergraphNode(node, stmtg))
                for edge in stmtg.edges:
                    if edge.srcnode.stmt in ipcalls:
                        # Begin the superedge from the ReturnNode:
                        srcsupernode = stmtg.supernode_for_stmtnode[edge.srcnode][1]
                    else:
                        srcsupernode = stmtg.supernode_for_stmtnode[edge.srcnode]
                    if edge.dstnode.stmt in ipcalls:
                        # End the superedge at the CallNode:
                        dstsupernode = stmtg.supernode_for_stmtnode[edge.dstnode][0]
                    else:
                        dstsupernode = stmtg.supernode_for_stmtnode[edge.dstnode]
                    superedge = self.add_edge(srcsupernode, dstsupernode,
                                              SupergraphEdge, edge)

        # 3rd pass: add the interprocedural edges (call and return):
        for node in get_callgraph_nodes():
            fun = node.decl.function
            if fun:
                for edge in node.callees:
                    if edge.callee.decl.function:
                        calling_stmtg = self.stmtg_for_fun[fun]
                        called_stmtg = self.stmtg_for_fun[edge.callee.decl.function]

                        calling_stmtnode = calling_stmtg.node_for_stmt[edge.call_stmt]
                        assert calling_stmtnode

                        entry_stmtnode = called_stmtg.entry
                        assert entry_stmtnode

                        exit_stmtnode = called_stmtg.exit
                        assert exit_stmtnode

                        superedge_call = self.add_edge(
                            calling_stmtg.supernode_for_stmtnode[calling_stmtnode][0],
                            called_stmtg.supernode_for_stmtnode[entry_stmtnode],
                            CallToStart,
                            None)
                        superedge_return = self.add_edge(
                            called_stmtg.supernode_for_stmtnode[exit_stmtnode],
                            calling_stmtg.supernode_for_stmtnode[calling_stmtnode][1],
                            ExitToReturnSite,
                            None)
                        superedge_return.calling_stmtnode = calling_stmtnode

        # 4th pass: create fake entry node:
        if not add_fake_entry_node:
            self.fake_entry_node = None
            return

        self.fake_entry_node = self.add_node(FakeEntryNode(None, None))
        """
	/* At file scope, the presence of a `static' or `register' storage
	   class specifier, or the absence of all storage class specifiers
	   makes this declaration a definition (perhaps tentative).  Also,
	   the absence of `static' makes it public.  */
	if (current_scope == file_scope)
	  {
	    TREE_PUBLIC (decl) = storage_class != csc_static;
	    TREE_STATIC (decl) = !extern_ref;
	  }
          """
        # For now, assume all non-static functions are possible entrypoints:
        for fun in self.stmtg_for_fun:
            # Only for non-static functions:
            if fun.decl.is_public:
                stmtg = self.stmtg_for_fun[fun]
                self.add_edge(self.fake_entry_node,
                              stmtg.supernode_for_stmtnode[stmtg.entry],
                              FakeEntryEdge,
                              None)
コード例 #19
0
    def __init__(self, sg, maxlength):
        Graph.__init__(self)
        self.sg = sg
        self.maxlength = maxlength

        # Dict mapping from (callstring, supernode) to IvpNode
        self.ivpnodes = {}

        self._entrynodes = set()

        # 1st pass: walk from the entrypoints, calling functions,
        # building nodes, and calling edges.
        # We will fill in the return edges later:
        # set of (ivpnode, inneredge) pairs deferred for later processsing:
        _pending_return_edges = set()

        def _add_node_for_key(key):
            callstring, supernode = key
            newnode = IvpNode(callstring, supernode)
            self.add_node(newnode)
            self.ivpnodes[key] = newnode
            return newnode

        # The "worklist" is a set of IvpNodes that we need to add
        # edges for.  Doing so may lead to more IvpNodes being
        # created.
        worklist = set()
        for supernode in sg.get_entry_nodes():
            key = (Callstring(tuple()), supernode)
            node = _add_node_for_key(key)
            worklist.add(node)
            self._entrynodes.add(node)

        while worklist:
            ivpnode = worklist.pop()
            if 0:
                print('ivpnode: %s' % ivpnode)
                print('ivpnode: %r' % ivpnode)

            for inneredge in ivpnode.innernode.succs:
                if 0:
                    print('  inneredge: %s' % inneredge)
                    print('  inneredge: %r' % inneredge)
                callstring = ivpnode.callstring

                def get_callstring():
                    if isinstance(inneredge, CallToStart):
                        # interprocedural call: push onto stack:
                        callnode = inneredge.srcnode
                        assert len(callstring.callnodes) <= maxlength
                        if len(callstring.callnodes) == maxlength:
                            # Truncate, losing the bottom of the stack:
                            oldstack = list(callstring.callnodes[1:])
                        else:
                            oldstack = list(callstring.callnodes)
                        return Callstring(tuple(oldstack + [callnode]))

                    elif isinstance(inneredge, ExitToReturnSite):
                        # interprocedural return: pop from stack
                        if not callstring.callnodes:
                            return None

                        # Ensure that we're returning to the correct place
                        # according to the top of the stack:
                        callnode = callstring.callnodes[-1]
                        if inneredge.dstnode == callnode.returnnode:
                            # add to the pending list
                            _pending_return_edges.add( (ivpnode, inneredge) )

                        return None
                    else:
                        # same stack depth:
                        return ivpnode.callstring

                newcallstring = get_callstring()
                if newcallstring:
                    key = (newcallstring, inneredge.dstnode)
                    if key not in self.ivpnodes:
                        dstnode = _add_node_for_key(key)
                        worklist.add( dstnode )
                    else:
                        dstnode = self.ivpnodes[key]
                    self.add_edge(ivpnode, dstnode, inneredge)

            # FIXME: in case we don't terminate, this is useful for debugging why:
            #if len(self.nodes) > 100:
            #    return

        # 2nd pass: now gather all valid callstrings:
        self.all_callstrings = set()
        for node in self.nodes:
            self.all_callstrings.add(node.callstring)

        if 0:
            print('self.all_callstrings: %s' % self.all_callstrings)

        # 3rd pass: go back and add the return edges (using the set of valid
        # callstrings to expand possible-truncated stacks):
        for srcivpnode, inneredge in _pending_return_edges:
            callstring = srcivpnode.callstring

            # We have a return edge, valid in the sense
            # that the dstnode is the call at the top of the stack
            #
            # What state should the stack end up in?
            def iter_valid_pops(callstring):
                # We could be at the top of an untruncated stack, in which
                # case we simply lose the top element:
                candidate = Callstring(callstring.callnodes[:-1])
                if candidate in self.all_callstrings:
                    yield candidate

                # Alternatively, the stack could be truncated, in which
                # case we need to generate all possible new elements for the
                # prefix part of the truncated stack
                if len(callstring.callnodes) == maxlength:
                    suffix = callstring.callnodes[0:-1]
                    for candidate in self.all_callstrings:
                        if candidate.callnodes[1:] == suffix:
                            yield candidate

            valid_pops = set(iter_valid_pops(callstring))
            for newcallstring in valid_pops:
                key = (newcallstring, inneredge.dstnode)
                dstivpnode = self.ivpnodes[key]
                self.add_edge(srcivpnode, dstivpnode, inneredge)