]> Cypherpunks.ru repositories - gostls13.git/commitdiff
internal/dag: add a Graph type and make node order deterministic
authorAustin Clements <austin@google.com>
Mon, 18 Jul 2022 18:56:40 +0000 (14:56 -0400)
committerAustin Clements <austin@google.com>
Thu, 4 Aug 2022 15:31:42 +0000 (15:31 +0000)
The go/types package doesn't care about node ordering because it's
just querying paths in the graph, but we're about to use this for the
runtime lock graph, and there we want determinism.

For #53789.

Change-Id: Ic41329bf2eb9a3a202f97c21c761ea588ca551c8
Reviewed-on: https://go-review.googlesource.com/c/go/+/418593
Reviewed-by: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Austin Clements <austin@google.com>

src/go/build/deps_test.go
src/internal/dag/parse.go
src/internal/dag/parse_test.go [new file with mode: 0644]

index c7e22463f90046d99899917700dd3e904ce4f7c9..e5f343a185705ea6fc9e50f7c05facd9611991f7 100644 (file)
@@ -597,11 +597,10 @@ func TestDependencies(t *testing.T) {
                if sawImport[pkg] == nil {
                        sawImport[pkg] = map[string]bool{}
                }
-               ok := policy[pkg]
                var bad []string
                for _, imp := range imports {
                        sawImport[pkg][imp] = true
-                       if !ok[imp] {
+                       if !policy.HasEdge(pkg, imp) {
                                bad = append(bad, imp)
                        }
                }
@@ -670,7 +669,7 @@ func findImports(pkg string) ([]string, error) {
 }
 
 // depsPolicy returns a map m such that m[p][d] == true when p can import d.
-func depsPolicy(t *testing.T) map[string]map[string]bool {
+func depsPolicy(t *testing.T) *dag.Graph {
        g, err := dag.Parse(depsRules)
        if err != nil {
                t.Fatal(err)
index 640b535454e943d97ac552dd629499a085759b0d..1991772e39e7dc927c409cf1af06cd9a1533fcc9 100644 (file)
@@ -43,13 +43,52 @@ package dag
 
 import (
        "fmt"
+       "sort"
        "strings"
 )
 
-// Parse returns a map m such that m[p][d] == true when there is a
-// path from p to d.
-func Parse(dag string) (map[string]map[string]bool, error) {
-       allowed := map[string]map[string]bool{"NONE": {}}
+type Graph struct {
+       Nodes   []string
+       byLabel map[string]int
+       edges   map[string]map[string]bool
+}
+
+func newGraph() *Graph {
+       return &Graph{byLabel: map[string]int{}, edges: map[string]map[string]bool{}}
+}
+
+func (g *Graph) addNode(label string) bool {
+       if _, ok := g.byLabel[label]; ok {
+               return false
+       }
+       g.byLabel[label] = len(g.Nodes)
+       g.Nodes = append(g.Nodes, label)
+       g.edges[label] = map[string]bool{}
+       return true
+}
+
+func (g *Graph) AddEdge(from, to string) {
+       g.edges[from][to] = true
+}
+
+func (g *Graph) HasEdge(from, to string) bool {
+       return g.edges[from] != nil && g.edges[from][to]
+}
+
+func (g *Graph) Edges(from string) []string {
+       edges := make([]string, 0, 16)
+       for k := range g.edges[from] {
+               edges = append(edges, k)
+       }
+       sort.Slice(edges, func(i, j int) bool { return g.byLabel[edges[i]] < g.byLabel[edges[j]] })
+       return edges
+}
+
+// Parse parses the DAG language and returns the transitive closure of
+// the described graph. In the returned graph, there is an edge from "b"
+// to "a" if b < a (or a > b) in the partial order.
+func Parse(dag string) (*Graph, error) {
+       g := newGraph()
        disallowed := []rule{}
 
        rules, err := parseRules(dag)
@@ -68,40 +107,47 @@ func Parse(dag string) (map[string]map[string]bool, error) {
                        continue
                }
                for _, def := range r.def {
-                       if allowed[def] != nil {
+                       if def == "NONE" {
+                               errorf("NONE cannot be a predecessor")
+                               continue
+                       }
+                       if !g.addNode(def) {
                                errorf("multiple definitions for %s", def)
                        }
-                       allowed[def] = make(map[string]bool)
                        for _, less := range r.less {
-                               if allowed[less] == nil {
+                               if less == "NONE" {
+                                       continue
+                               }
+                               if _, ok := g.byLabel[less]; !ok {
                                        errorf("use of %s before its definition", less)
+                               } else {
+                                       g.AddEdge(def, less)
                                }
-                               allowed[def][less] = true
                        }
                }
        }
 
        // Check for missing definition.
-       for _, tos := range allowed {
+       for _, tos := range g.edges {
                for to := range tos {
-                       if allowed[to] == nil {
+                       if g.edges[to] == nil {
                                errorf("missing definition for %s", to)
                        }
                }
        }
 
        // Complete transitive closure.
-       for k := range allowed {
-               for i := range allowed {
-                       for j := range allowed {
-                               if i != k && k != j && allowed[i][k] && allowed[k][j] {
+       for _, k := range g.Nodes {
+               for _, i := range g.Nodes {
+                       for _, j := range g.Nodes {
+                               if i != k && k != j && g.HasEdge(i, k) && g.HasEdge(k, j) {
                                        if i == j {
                                                // Can only happen along with a "use of X before deps" error above,
                                                // but this error is more specific - it makes clear that reordering the
                                                // rules will not be enough to fix the problem.
                                                errorf("graph cycle: %s < %s < %s", j, k, i)
                                        }
-                                       allowed[i][j] = true
+                                       g.AddEdge(i, j)
                                }
                        }
                }
@@ -111,7 +157,7 @@ func Parse(dag string) (map[string]map[string]bool, error) {
        for _, bad := range disallowed {
                for _, less := range bad.less {
                        for _, def := range bad.def {
-                               if allowed[def][less] {
+                               if g.HasEdge(def, less) {
                                        errorf("graph edge assertion failed: %s !< %s", less, def)
                                }
                        }
@@ -122,7 +168,7 @@ func Parse(dag string) (map[string]map[string]bool, error) {
                return nil, fmt.Errorf("%s", strings.Join(errors, "\n"))
        }
 
-       return allowed, nil
+       return g, nil
 }
 
 // A rule is a line in the DAG language where "less < def" or "less !< def".
diff --git a/src/internal/dag/parse_test.go b/src/internal/dag/parse_test.go
new file mode 100644 (file)
index 0000000..b2520c3
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package dag
+
+import (
+       "reflect"
+       "strings"
+       "testing"
+)
+
+const diamond = `
+NONE < a < b, c < d;
+`
+
+func mustParse(t *testing.T, dag string) *Graph {
+       t.Helper()
+       g, err := Parse(dag)
+       if err != nil {
+               t.Fatal(err)
+       }
+       return g
+}
+
+func wantEdges(t *testing.T, g *Graph, edges string) {
+       t.Helper()
+
+       wantEdges := strings.Fields(edges)
+       wantEdgeMap := make(map[string]bool)
+       for _, e := range wantEdges {
+               wantEdgeMap[e] = true
+       }
+
+       for _, n1 := range g.Nodes {
+               for _, n2 := range g.Nodes {
+                       got := g.HasEdge(n1, n2)
+                       want := wantEdgeMap[n1+"->"+n2]
+                       if got && want {
+                               t.Logf("%s->%s", n1, n2)
+                       } else if got && !want {
+                               t.Errorf("%s->%s present but not expected", n1, n2)
+                       } else if want && !got {
+                               t.Errorf("%s->%s missing but expected", n1, n2)
+                       }
+               }
+       }
+}
+
+func TestParse(t *testing.T) {
+       // Basic smoke test for graph parsing.
+       g := mustParse(t, diamond)
+
+       wantNodes := strings.Fields("a b c d")
+       if !reflect.DeepEqual(wantNodes, g.Nodes) {
+               t.Fatalf("want nodes %v, got %v", wantNodes, g.Nodes)
+       }
+
+       // Parse returns the transitive closure, so it adds d->a.
+       wantEdges(t, g, "b->a c->a d->a d->b d->c")
+}