// findCandidates recursively locates potential node pairs (g and s) for an // isomorphism of sub in graph and adds them to c. func (eq *equation) findCandidates(g, s *dot.Node, sub *graphs.SubGraph) { // Exit early for impossible node pairs. if !isPotential(g, s, sub) { return } // Prevent infinite cycles. if _, ok := eq.c[s.Name]; ok { if eq.c[s.Name][g.Name] { return } } // Add node pair candidate. if _, ok := eq.c[s.Name]; !ok { eq.c[s.Name] = make(map[string]bool) } else if s.Name == sub.Entry() { // Locate candidates for the entry node and its immediate successors // exactly once. return } eq.c[s.Name][g.Name] = true // Recursively locate candidate successor pairs. for _, ssucc := range s.Succs { for _, gsucc := range g.Succs { eq.findCandidates(gsucc, ssucc, sub) } } }
// candidates locates node pair candidates for an isomorphism of sub in graph // which starts at the entry node. func candidates(graph *dot.Graph, entry string, sub *graphs.SubGraph) (*equation, error) { // Sanity checks. g, ok := graph.Nodes.Lookup[entry] if !ok { return nil, errutil.Newf("unable to locate entry node %q in graph", entry) } s, ok := sub.Nodes.Lookup[sub.Entry()] if !ok { panic(fmt.Sprintf("unable to locate entry node %q in sub", sub.Entry())) } if !isPotential(g, s, sub) { return nil, errutil.Newf("invalid entry node candidate %q; expected %d successors, got %d", g.Name, len(s.Succs), len(g.Succs)) } // Locate candidate node pairs. eq := &equation{ c: make(map[string]map[string]bool), m: make(map[string]string), } eq.findCandidates(g, s, sub) if len(eq.c) != len(sub.Nodes.Nodes) { return nil, errutil.Newf("incomplete candidate mapping; expected %d map entites, got %d", len(sub.Nodes.Nodes), len(eq.c)) } return eq, nil }
// isPotential returns true if the graph node g is a potential candidate for the // sub node s, and false otherwise. func isPotential(g, s *dot.Node, sub *graphs.SubGraph) bool { // Verify predecessors. if s.Name != sub.Entry() && len(g.Preds) != len(s.Preds) { return false } // Verify successors. if s.Name != sub.Exit() && len(g.Succs) != len(s.Succs) { return false } return true }
// printMapping prints the mapping from sub node name to graph node name for an // isomorphism of sub in graph. func printMapping(graph *dot.Graph, sub *graphs.SubGraph, m map[string]string) { entry := m[sub.Entry()] var snames []string for sname := range m { snames = append(snames, sname) } sort.Strings(snames) fmt.Printf("Isomorphism of %q found at node %q:\n", sub.Name, entry) for _, sname := range snames { fmt.Printf(" %q=%q\n", sname, m[sname]) } }
// Merge merges the nodes of the isomorphism of sub in graph into a single node. // If successful it returns the name of the new node. func Merge(graph *dot.Graph, m map[string]string, sub *graphs.SubGraph) (name string, err error) { var nodes []*dot.Node for _, gname := range m { node, ok := graph.Nodes.Lookup[gname] if !ok { return "", errutil.Newf("unable to locate mapping for node %q", gname) } nodes = append(nodes, node) } name = uniqName(graph, sub.Name) entry, ok := graph.Nodes.Lookup[m[sub.Entry()]] if !ok { return "", errutil.Newf("unable to locate mapping for entry node %q", sub.Entry()) } exit, ok := graph.Nodes.Lookup[m[sub.Exit()]] if !ok { return "", errutil.Newf("unable to locate mapping for exit node %q", sub.Exit()) } err = graph.Replace(nodes, name, entry, exit) if err != nil { return "", errutil.Err(err) } return name, nil }
// isValid returns true if m is a valid mapping, from sub node name to graph // node name, for an isomorphism of sub in graph considering all nodes and edges // except predecessors of entry and successors of exit. func (eq *equation) isValid(graph *dot.Graph, sub *graphs.SubGraph) bool { if len(eq.m) != len(sub.Nodes.Nodes) { return false } // Check for duplicate values. if hasDup(eq.m) { return false } // Verify that the entry node dominates the exit node. entry, ok := graph.Nodes.Lookup[eq.m[sub.Entry()]] if !ok { return false } exit, ok := graph.Nodes.Lookup[eq.m[sub.Exit()]] if !ok { return false } // TODO: Figure out how to handle find the if-statement in the following graph: // digraph bar { // E -> F // E -> J // F -> G // F -> E // G -> I // I -> E // E [label="entry"] // F // G // I // J [label="exit"] // } // // ref: https://github.com/decomp/decompilation/issues/172 if !entry.Dominates(exit) { return false } // Sort keys to make the algorithm deterministic. var snames []string for sname := range eq.m { snames = append(snames, sname) } sort.Strings(snames) for _, sname := range snames { gname := eq.m[sname] s, ok := sub.Nodes.Lookup[sname] if !ok { panic(fmt.Sprintf("unable to locate node %q in sub", sname)) } g, ok := graph.Nodes.Lookup[gname] if !ok { panic(fmt.Sprintf("unable to locate node %q in graph", gname)) } // Verify predecessors. if s.Name != sub.Entry() { if len(s.Preds) != len(g.Preds) { return false } for _, spred := range s.Preds { found := false for _, gpred := range g.Preds { if gpred.Name == eq.m[spred.Name] { found = true break } } if !found { return false } } } // Verify successors. if s.Name != sub.Exit() { if len(s.Succs) != len(g.Succs) { return false } for _, ssucc := range s.Succs { found := false for _, gsucc := range g.Succs { if gsucc.Name == eq.m[ssucc.Name] { found = true break } } if !found { return false } } } } // Isomorphism found! return true }