// ParseSubGraph parses the provided DOT file into a subgraph with a dedicated // entry and exit node. The entry and exit nodes are identified using the node // "label" attribute, e.g. // // digraph if { // A->B [label="true"] // A->C [label="false"] // B->C // A [label="entry"] // B // C [label="exit"] // } func ParseSubGraph(path string) (*SubGraph, error) { graph, err := dot.ParseFile(path) if err != nil { return nil, err } return NewSubGraph(graph) }
func main() { g, err := dot.ParseFile("dotu.dot") if err != nil { panic(err) } s := g.String() fmt.Println(s) }
// locateIntervals locates all intervals in the provided graph. func locateIntervals(dotPath string) ([]*graphs.Interval, error) { // Parse graph. g, err := dot.ParseFile(dotPath) if err != nil { return nil, errutil.Err(err) } // Locate intervals return graphs.GetIntervals(g), nil }
// locate parses the provided graphs and tries to locate isomorphisms of the // subgraph in the graph. func locate(graphPath, subPath string) error { // Parse graph. graph, err := dot.ParseFile(graphPath) if err != nil { return errutil.Err(err) } // Search for subgraph in GOPATH if not found. if ok, _ := osutil.Exists(subPath); !ok { dir, err := goutil.SrcDir("decomp.org/decomp/graphs/testdata/primitives") if err != nil { return errutil.Err(err) } subPath = filepath.Join(dir, subPath) } sub, err := graphs.ParseSubGraph(subPath) if err != nil { return errutil.Err(err) } // Locate isomorphisms. found := false if len(flagStart) > 0 { // Locate an isomorphism of sub in graph which starts at the node // specified by the "-start" flag. m, ok := iso.Isomorphism(graph, flagStart, sub) if ok { found = true printMapping(graph, sub, m) } } else { // Locate all isomorphisms of sub in graph. var names []string for name := range graph.Nodes.Lookup { names = append(names, name) } sort.Strings(names) for _, name := range names { m, ok := iso.Isomorphism(graph, name, sub) if !ok { continue } found = true printMapping(graph, sub, m) } } if !found { fmt.Println("not found.") } return nil }
// restructure attempts to recover the control flow primitives of a given // control flow graph. It does so by repeatedly locating and merging structured // subgraphs (graph representations of control flow primitives) into single // nodes until the entire graph is reduced into a single node or no structured // subgraphs may be located. The list of primitives is ordered in the same // sequence as they were located. func restructure(dotPath string, subs []*graphs.SubGraph) (prims []*primitive.Primitive, err error) { // Parse the unstructured CFG. var graph *dot.Graph switch dotPath { case "-": // Read from stdin. buf, err := ioutil.ReadAll(os.Stdin) if err != nil { return nil, errutil.Err(err) } graph, err = dot.Read(buf) if err != nil { return nil, errutil.Err(err) } default: // Read from FILE. graph, err = dot.ParseFile(dotPath) if err != nil { return nil, errutil.Err(err) } } if len(graph.Nodes.Nodes) == 0 { return nil, errutil.Newf("unable to restructure empty graph %q", dotPath) } // Locate control flow primitives. for step := 1; len(graph.Nodes.Nodes) > 1; step++ { prim, err := findPrim(graph, subs, step) if err != nil { return nil, errutil.Err(err) } prims = append(prims, prim) } return prims, nil }
// parseCFG parses the control flow graph of the function. // // For a source file "foo.ll" containing the functions "bar" and "baz" the // following DOT files will be created: // // foo_graphs/bar.dot // foo_graphs/baz.dot func parseCFG(basePath, funcName string) (graph *dot.Graph, err error) { dotDir := basePath + "_graphs" dotName := funcName + ".dot" dotPath := fmt.Sprintf("%s/%s", dotDir, dotName) return dot.ParseFile(dotPath) }
func TestEquationIsValid(t *testing.T) { golden := []struct { subPath string graphPath string eq *equation want bool }{ // i=0 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", eq: &equation{ m: map[string]string{ "cond": "71", "body": "74", "exit": "75", }, }, want: true, }, // i=1 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", eq: &equation{ m: map[string]string{ "cond": "17", "body": "24", "exit": "32", }, }, want: true, }, // i=2 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", eq: &equation{ m: map[string]string{ "cond": "89", "body": "92", "exit": "93", }, }, want: false, }, // i=3 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", eq: &equation{ m: map[string]string{ "cond": "94", "body": "97", "exit": "98", }, }, want: false, }, // i=4 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "282", "body_true": "292", "body_false": "287", "exit": "299", }, }, want: true, }, // i=5 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "282", "body_true": "287", "body_false": "292", "exit": "299", }, }, want: true, }, // i=6 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/next.dot", eq: &equation{ m: map[string]string{ "cond": "438", "body_true": "446", "body_false": "443", "exit": "447", }, }, want: true, }, // i=7 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/next.dot", eq: &equation{ m: map[string]string{ "cond": "438", "body_true": "443", "body_false": "446", "exit": "447", }, }, want: true, }, // i=8 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/next.dot", eq: &equation{ m: map[string]string{ "cond": "487", "body_true": "492", "body_false": "495", "exit": "496", }, }, want: true, }, // i=9 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/next.dot", eq: &equation{ m: map[string]string{ "cond": "487", "body_true": "495", "body_false": "492", "exit": "496", }, }, want: true, }, // i=10 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/next.dot", eq: &equation{ m: map[string]string{ "cond": "124", "body_true": "134", "body_false": "126", "exit": "145", }, }, want: false, }, // i=11 { subPath: "../testdata/primitives/list.dot", graphPath: "../testdata/c4_graphs/main.dot", eq: &equation{ m: map[string]string{ "entry": "740", "exit": "760", }, }, want: true, }, // i=12 { subPath: "../testdata/primitives/list.dot", graphPath: "../testdata/c4_graphs/main.dot", eq: &equation{ m: map[string]string{ "entry": "761", "exit": "762", }, }, want: false, }, // i=13 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "191", "body": "194", "exit": "196", }, }, want: true, }, // i=14 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "370", "body": "378", "exit": "374", }, }, want: false, }, // i=15 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "526", "body": "530", "exit": "539", }, }, want: false, }, // i=16 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "611", "body_true": "615", "body_false": "615", "exit": "631", }, }, want: false, }, // i=17 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/expr.dot", eq: &equation{ m: map[string]string{ "cond": "611", "body_true": "615", "exit": "631", }, }, want: false, }, // i=18 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/main.dot", eq: &equation{ m: map[string]string{ "cond": "20", "body": "25", "exit": "34", }, }, want: false, }, // i=19 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", eq: &equation{ m: map[string]string{ "cond": "39", // 48 "body": "44", "exit": "52", // 45 }, }, want: false, }, // i=20 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", eq: &equation{ m: map[string]string{ "cond": "39", "body": "44", "exit": "45", }, }, want: false, }, } for i, g := range golden { sub, err := graphs.ParseSubGraph(g.subPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } graph, err := dot.ParseFile(g.graphPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } got := g.eq.isValid(graph, sub) if got != g.want { t.Errorf("i=%d: ok mismatch; expected %v, got %v", i, g.want, got) continue } } }
func TestSolveBrute(t *testing.T) { golden := []struct { subPath string graphPath string entry string wants []map[string]string err string }{ // i=0 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/primitives/if_else.dot", entry: "cond", wants: []map[string]string{ { "cond": "cond", "body_true": "body_true", "body_false": "body_false", "exit": "exit", }, { "cond": "cond", "body_true": "body_false", "body_false": "body_true", "exit": "exit", }, }, err: "", }, // i=1 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "85", wants: []map[string]string{nil}, err: "unable to locate node pair mapping", }, // i=2 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "71", wants: []map[string]string{nil}, err: "unable to locate node pair mapping", }, // i=3 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "89", wants: []map[string]string{ { "cond": "89", "body": "92", "exit": "93", }, }, }, // i=4 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "71", wants: []map[string]string{ { "cond": "71", "body": "74", "exit": "75", }, }, }, } loop: for i, g := range golden { graph, err := dot.ParseFile(g.graphPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } sub, err := graphs.ParseSubGraph(g.subPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } eq, err := candidates(graph, g.entry, sub) if err != nil { t.Errorf("i=%d: %v", i, err) continue } m, err := eq.solveBrute(graph, sub) if !sameError(err, g.err) { t.Errorf("i=%d: error mismatch; expected %v, got %v", i, g.err, err) continue } else if err != nil { // Expected error, check next test case. continue } for _, want := range g.wants { if reflect.DeepEqual(m, want) { continue loop } } t.Errorf("i=%d: node pair map mismatch; expected one of %v, got %v", i, g.wants, m) } }
func TestSearch(t *testing.T) { golden := []struct { subPath string graphPath string m map[string]string ok bool }{ // i=0 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", m: map[string]string{ "cond": "17", "body": "24", "exit": "32", }, ok: true, }, // i=1 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/stmt.dot", m: nil, ok: false, }, // i=2 { subPath: "../testdata/primitives/list.dot", graphPath: "../testdata/c4_graphs/stmt.dot", m: map[string]string{ "entry": "101", "exit": "105", }, ok: true, }, // i=3 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", m: map[string]string{ "cond": "89", "body": "92", "exit": "93", }, ok: true, }, // i=4 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/expr.dot", m: map[string]string{ "cond": "120", "body": "122", "exit": "127", }, ok: true, }, // i=5 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/expr.dot", m: map[string]string{ "cond": "282", "body_true": "292", "body_false": "287", "exit": "299", }, ok: true, }, // i=6 { subPath: "../testdata/primitives/list.dot", graphPath: "../testdata/c4_graphs/expr.dot", m: map[string]string{ "entry": "109", "exit": "119", }, ok: true, }, // i=7 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/expr.dot", m: map[string]string{ "cond": "191", "body": "194", "exit": "196", }, ok: true, }, // i=8 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/next.dot", m: map[string]string{ "cond": "195", "body": "200", "exit": "205", }, ok: true, }, // i=9 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/next.dot", m: map[string]string{ "cond": "28", "body_true": "44", "body_false": "39", "exit": "46", }, ok: true, }, // i=10 { subPath: "../testdata/primitives/list.dot", graphPath: "../testdata/c4_graphs/next.dot", m: map[string]string{ "entry": "320", "exit": "322", }, ok: true, }, // i=11 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/next.dot", m: nil, ok: false, }, // i=12 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/main.dot", m: map[string]string{ "cond": "135", "body": "138", "exit": "139", }, ok: true, }, // i=13 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/main.dot", m: map[string]string{ "cond": "442", "body_true": "451", "body_false": "448", "exit": "453", }, ok: true, }, // i=14 { subPath: "../testdata/primitives/list.dot", graphPath: "../testdata/c4_graphs/main.dot", m: map[string]string{ "entry": "740", "exit": "760", }, ok: true, }, // i=15 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/main.dot", m: map[string]string{ "cond": "190", "body": "193", "exit": "195", }, ok: true, }, } for i, g := range golden { sub, err := graphs.ParseSubGraph(g.subPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } graph, err := dot.ParseFile(g.graphPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } m, ok := Search(graph, sub) if ok != g.ok { t.Errorf("i=%d: ok mismatch; expected %v, got %v", i, g.ok, ok) continue } if !reflect.DeepEqual(m, g.m) { t.Errorf("i=%d: node pair mapping mismatch; expected %v, got %v", i, g.m, m) } } }
func TestCandidates(t *testing.T) { golden := []struct { subPath string graphPath string entry string want map[string]map[string]bool err string }{ // i=0 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/primitives/if_else.dot", entry: "cond", want: map[string]map[string]bool{ "cond": { "cond": true, }, "body_true": { "body_true": true, "body_false": true, }, "body_false": { "body_true": true, "body_false": true, }, "exit": { "exit": true, }, }, err: "", }, // i=1 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "85", want: map[string]map[string]bool{ "cond": { "85": true, }, "body_true": { "88": true, }, "body_false": { "88": true, }, "exit": { "89": true, }, }, err: "", }, // i=2 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "71", want: map[string]map[string]bool{ "cond": { "71": true, }, "body": { "74": true, }, "exit": { "74": true, }, }, err: "", }, // i=3 { subPath: "../testdata/primitives/pre_loop.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "89", want: map[string]map[string]bool{ "cond": { "89": true, }, "body": { "92": true, "93": true, }, "exit": { "92": true, "93": true, }, }, err: "", }, // i=4 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "71", want: map[string]map[string]bool{ "cond": { "71": true, }, "body": { "74": true, }, "exit": { "75": true, }, }, err: "", }, // i=5 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "foo", want: nil, err: `unable to locate entry node "foo" in graph`, }, // i=6 { subPath: "../testdata/primitives/if.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "97", want: nil, err: `invalid entry node candidate "97"; expected 2 successors, got 1`, }, // i=7 { subPath: "../testdata/primitives/if_else.dot", graphPath: "../testdata/c4_graphs/stmt.dot", entry: "68", want: nil, err: "incomplete candidate mapping; expected 4 map entites, got 1", }, } for i, g := range golden { sub, err := graphs.ParseSubGraph(g.subPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } graph, err := dot.ParseFile(g.graphPath) if err != nil { t.Errorf("i=%d: %v", i, err) continue } eq, err := candidates(graph, g.entry, sub) if !sameError(err, g.err) { t.Errorf("i=%d: error mismatch; expected %v, got %v", i, g.err, err) continue } else if err != nil { // Expected error, check next test case. continue } if !reflect.DeepEqual(eq.c, g.want) { t.Errorf("i=%d: candidate map mismatch; expected %v, got %v", i, g.want, eq.c) } } }
// locateAndMerge parses the provided graphs and tries to merge isomorphisms of // the subgraph in the graph into single nodes. func locateAndMerge(graphPath, subPath string) error { // Parse graph. graph, err := dot.ParseFile(graphPath) if err != nil { return errutil.Err(err) } // Search for subgraph in GOPATH if not found. if ok, _ := osutil.Exists(subPath); !ok { dir, err := goutil.SrcDir("decomp.org/decomp/graphs/testdata/primitives") if err != nil { return errutil.Err(err) } subPath = filepath.Join(dir, subPath) } sub, err := graphs.ParseSubGraph(subPath) if err != nil { return errutil.Err(err) } // Merge isomorphisms. found := false if len(flagStart) > 0 { // Merge an isomorphism of sub in graph which starts at the node // specified by the "-start" flag. m, ok := iso.Isomorphism(graph, flagStart, sub) if ok { found = true printMapping(graph, sub, m) _, err := merge.Merge(graph, m, sub) if err != nil { return errutil.Err(err) } } } else { // Merge all isomorphisms of sub in graph. for { m, ok := iso.Search(graph, sub) if !ok { break } found = true printMapping(graph, sub, m) _, err := merge.Merge(graph, m, sub) if err != nil { return errutil.Err(err) } } } // Store DOT and PNG representation of graph. if found { err = dump(graph) if err != nil { return errutil.Err(err) } } else { fmt.Println("not found.") } return nil }