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)
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)
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)
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)
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)