| Index: mojom/mojom_tool/utils/wellfounded_graphs.go
|
| diff --git a/mojom/mojom_tool/utils/wellfounded_graphs.go b/mojom/mojom_tool/utils/wellfounded_graphs.go
|
| index 6603c0fa0d192aa9d055cb80a48ea0f44951bebb..1266a96c4d5ec85bb1c9f3ab5a41a453fe31320a 100644
|
| --- a/mojom/mojom_tool/utils/wellfounded_graphs.go
|
| +++ b/mojom/mojom_tool/utils/wellfounded_graphs.go
|
| @@ -28,12 +28,15 @@ import (
|
| ///////////////////////////////////////////////////
|
| // Node type
|
| //
|
| -// A |Node| is a node in a directed graph.
|
| +// A |Node| is a node in a directed graph. A user of this library is responsible
|
| +// for implementing this interface. This library will use the Go equality operator
|
| +// to compare instances of |Node| and it is the responsibility of the user
|
| +// to ensure that x == y iff x and y represent the same logical node.
|
| ///////////////////////////////////////////////////
|
|
|
| type Node interface {
|
| // Returns the list of children of this node.
|
| - OutEdges() []Node
|
| + OutEdges() []OutEdge
|
|
|
| // Returns whether or not this node is a square node.
|
| IsSquare() bool
|
| @@ -53,8 +56,18 @@ type Node interface {
|
| // this library.
|
| KnownWellFounded() bool
|
|
|
| - // Returns the name of this node, appropriate for debugging.
|
| - DebugName() string
|
| + // Returns a human-readable name for this node. Not used by the algorithm
|
| + // but used for generating debugging and error messages.
|
| + Name() string
|
| +}
|
| +
|
| +type OutEdge struct {
|
| + // The Label on an edge is opaque data that may be used by the client however it wishes.
|
| + // The algorithm never inspects this data.
|
| + Label interface{}
|
| +
|
| + // The target node of the out edge.
|
| + Target Node
|
| }
|
|
|
| ///////////////////////////////////////////////////
|
| @@ -134,13 +147,14 @@ func checkWellFounded(root Node, debugDataRequest *debugData) *CycleDescription
|
| ///////////////////////////////////////////////////
|
| type cycleFinder struct {
|
|
|
| - // Maps each node seen by the computation to its holder.
|
| + // Maps each node seen by the computation to its holder. Note that this assumes
|
| + // that the implementation of Node uses values that are comparable and
|
| + // obey identity semantics.
|
| nodeToHolder map[Node]*nodeHolder
|
|
|
| foundationSet, pendingSet NodeSet
|
|
|
| visitationIndex int
|
| - currentPath nodeStack
|
|
|
| debugDataRequest *debugData
|
| }
|
| @@ -155,7 +169,6 @@ func makeCycleFinder() cycleFinder {
|
| finder.nodeToHolder = make(map[Node]*nodeHolder)
|
| finder.foundationSet = MakeNodeSet()
|
| finder.pendingSet = MakeNodeSet()
|
| - finder.currentPath = nodeStack{}
|
| return finder
|
| }
|
|
|
| @@ -206,7 +219,7 @@ func (finder *cycleFinder) checkWellFounded(nodeHolder *nodeHolder) *CycleDescri
|
| // build a canonical cycle.
|
| freshCycleFinder := makeCycleFinder()
|
| holder := freshCycleFinder.holderForNode(minIllfoundedNode)
|
| - return freshCycleFinder.findKnownIllFoundedCycle(holder)
|
| + return freshCycleFinder.findKnownIllFoundedCycle(holder, nil)
|
| }
|
|
|
| // checkWellFoundedPhase1 is a recursive helper function that does a depth-first traversal
|
| @@ -246,8 +259,8 @@ func (finder *cycleFinder) checkWellFoundedPhase1(nodeHolder *nodeHolder) {
|
|
|
| // Next we examine each of the children and recurse into the unvisited ones.
|
| sawUnverifiedChild := false
|
| - for _, child := range nodeHolder.node.OutEdges() {
|
| - childHolder := finder.holderForNode(child)
|
| + for _, edge := range nodeHolder.node.OutEdges() {
|
| + childHolder := finder.holderForNode(edge.Target)
|
| if childHolder.state == vsUnvisited {
|
| // Recursively visit this child.
|
| finder.checkWellFoundedPhase1(childHolder)
|
| @@ -323,7 +336,8 @@ func (finder *cycleFinder) checkWellFoundedPhase2() {
|
| if finder.pendingSet.Contains(p) {
|
| knownWellFounded := true
|
| if !p.node.IsSquare() {
|
| - for _, child := range p.node.OutEdges() {
|
| + for _, edge := range p.node.OutEdges() {
|
| + child := edge.Target
|
| if child != p.node && !child.KnownWellFounded() {
|
| knownWellFounded = false
|
| break
|
| @@ -340,6 +354,14 @@ func (finder *cycleFinder) checkWellFoundedPhase2() {
|
| }
|
| }
|
|
|
| +// A pathElement is a node and one of its out-edges.
|
| +type pathElement struct {
|
| + node Node
|
| + edge OutEdge
|
| +}
|
| +
|
| +type nodePath []pathElement
|
| +
|
| // findKnownIllFoundedCycle finds and returns a |CycleDescription| starting from a node that is known
|
| // to be ill-founded. This proceeds by following edges from an ill-founded node to
|
| // an ill-founded child node until a cycle is formed. We return a *canonical* cycle,
|
| @@ -348,18 +370,28 @@ func (finder *cycleFinder) checkWellFoundedPhase2() {
|
| // We are not concerned with optimizing the performance of phase 3 because in the intended application
|
| // phase 3 can occur at most once in the lifetime of a program: Once an ill-founded node is detected the
|
| // program exits with a cycle description allowing the user to fix the ill-foundedness.
|
| -func (finder *cycleFinder) findKnownIllFoundedCycle(nodeHolder *nodeHolder) *CycleDescription {
|
| +//
|
| +// This method is recursive. To initiate the recursion |nodeHolder| should be set to the starting node and
|
| +// |path| should be nil. In subsequent recursive calls |nodeHolder| is the current node of the walk and
|
| +// |path| is the path from the starting node to the current node.
|
| +func (finder *cycleFinder) findKnownIllFoundedCycle(nodeHolder *nodeHolder, path nodePath) *CycleDescription {
|
| + // If this is the start of the recursion initialize the path.
|
| + if path == nil {
|
| + path = make(nodePath, 0)
|
| + }
|
| +
|
| // Mark the current node as started
|
| nodeHolder.state = vsVisitStarted
|
| - finder.currentPath.Push(nodeHolder)
|
| - for _, child := range nodeHolder.node.OutEdges() {
|
| - childHolder := finder.holderForNode(child)
|
| + for _, edge := range nodeHolder.node.OutEdges() {
|
| + childHolder := finder.holderForNode(edge.Target)
|
| if childHolder.state == vsVisitStarted {
|
| // If the child has been started but not finished then we have found a cycle
|
| // from the child to the current node back to the child.
|
| - return newCycleDescription(finder.currentPath.elements, childHolder, nodeHolder)
|
| + path = append(path, pathElement{nodeHolder.node, edge})
|
| + return newCycleDescription(path, childHolder.node, nodeHolder.node)
|
| } else if !childHolder.node.KnownWellFounded() {
|
| - return finder.findKnownIllFoundedCycle(childHolder)
|
| + path = append(path, pathElement{nodeHolder.node, edge})
|
| + return finder.findKnownIllFoundedCycle(childHolder, path)
|
| }
|
| }
|
| panic("Program logic error: Could not find a known ill-founded cycle.")
|
| @@ -413,49 +445,6 @@ func newNodeHolder(node Node, visitationOrder int) *nodeHolder {
|
| return nodeHolder
|
| }
|
|
|
| -//////////////////////////////////////////////
|
| -/// nodeStack type
|
| -//////////////////////////////////////////////
|
| -
|
| -// A nodeStack is a stack of *nodeHolders
|
| -type nodeStack struct {
|
| - elements []*nodeHolder
|
| -}
|
| -
|
| -func (stack *nodeStack) Push(n *nodeHolder) {
|
| - stack.elements = append(stack.elements, n)
|
| -}
|
| -
|
| -func (stack *nodeStack) Size() int {
|
| - return len(stack.elements)
|
| -}
|
| -
|
| -func (stack *nodeStack) Pop() (n *nodeHolder) {
|
| - lastIndex := stack.Size() - 1
|
| - n = stack.elements[lastIndex]
|
| - stack.elements = stack.elements[:lastIndex]
|
| - return
|
| -}
|
| -
|
| -func (stack *nodeStack) Peek() (n *nodeHolder) {
|
| - return stack.elements[stack.Size()-1]
|
| -}
|
| -
|
| -func (stack *nodeStack) String() string {
|
| - var buffer bytes.Buffer
|
| - fmt.Fprintf(&buffer, "[")
|
| - first := true
|
| - for _, e := range stack.elements {
|
| - if !first {
|
| - fmt.Fprintf(&buffer, ", ")
|
| - }
|
| - fmt.Fprintf(&buffer, "%s", e.node.DebugName())
|
| - first = false
|
| - }
|
| - fmt.Fprintln(&buffer, "]")
|
| - return buffer.String()
|
| -}
|
| -
|
| ///////////////////////////////////////////////////
|
| /// NodeSet type
|
| ///////////////////////////////////////////////////
|
| @@ -542,12 +531,12 @@ func (set *NodeSet) Size() int {
|
| func compareNodeSets(expected, actual *NodeSet) error {
|
| for n, _ := range expected.elements {
|
| if !actual.Contains(n) {
|
| - return fmt.Errorf("%s is in expected but not actual", n.node.DebugName())
|
| + return fmt.Errorf("%s is in expected but not actual", n.node.Name())
|
| }
|
| }
|
| for n, _ := range actual.elements {
|
| if !expected.Contains(n) {
|
| - return fmt.Errorf("%s is in actual but not expected", n.node.DebugName())
|
| + return fmt.Errorf("%s is in actual but not expected", n.node.Name())
|
| }
|
| }
|
| return nil
|
| @@ -562,7 +551,7 @@ func (set *NodeSet) String() string {
|
| if !first {
|
| fmt.Fprintf(&buffer, ", ")
|
| }
|
| - fmt.Fprintf(&buffer, "%s", e.node.DebugName())
|
| + fmt.Fprintf(&buffer, "%s", e.node.Name())
|
| first = false
|
| }
|
| fmt.Fprintln(&buffer, "}")
|
| @@ -576,39 +565,57 @@ func (set *NodeSet) String() string {
|
| ///////////////////////////////////////////////////
|
|
|
| type CycleDescription struct {
|
| - first, last Node
|
| - path []Node
|
| + First, Last Node
|
| + Path []OutEdge
|
| }
|
|
|
| func (c *CycleDescription) String() string {
|
| var buffer bytes.Buffer
|
| - fmt.Fprintf(&buffer, "first:%s", c.first.DebugName())
|
| - fmt.Fprintf(&buffer, ", last:%s", c.last.DebugName())
|
| + fmt.Fprintf(&buffer, "first:%s", c.First.Name())
|
| + fmt.Fprintf(&buffer, ", last:%s", c.Last.Name())
|
| fmt.Fprintf(&buffer, ", path:{")
|
| - first := true
|
| - for _, n := range c.path {
|
| - if !first {
|
| + fmt.Fprintf(&buffer, "%s", c.First.Name())
|
| + for _, edge := range c.Path {
|
| + if edge.Target != c.First {
|
| fmt.Fprintf(&buffer, ", ")
|
| + fmt.Fprintf(&buffer, "%s", edge.Target.Name())
|
| }
|
| - fmt.Fprintf(&buffer, "%s", n.DebugName())
|
| - first = false
|
| }
|
| fmt.Fprintln(&buffer, "}")
|
| return buffer.String()
|
| }
|
|
|
| -func newCycleDescription(path []*nodeHolder, first, last *nodeHolder) *CycleDescription {
|
| +// newCycleDescription builds a CycleDescription based on the given data.
|
| +// |path| should be a nodePath that ends with a cycle. That is it should look like
|
| +// {x1, ->x2}, {x2, ->x3}, {x3, ->x4}, {x4, ->x5}, {x5, ->x2}
|
| +//
|
| +// |cycleStart| should be the first node of |path| included in the cycle. In
|
| +// the above example it would be x2.
|
| +//
|
| +// |end| should be the last node of the path. In the above example it would be x5.
|
| +//
|
| +// The return value using our example would be:
|
| +// {First: x2, Last: x5, Path:[{x2, x2->x3}, {x3, x3->x4}, {x4, x4->x5}, {x5, x5->x2}]}
|
| +func newCycleDescription(path nodePath, cycleStart, end Node) *CycleDescription {
|
| + lastNode := path[len(path)-1].node
|
| + if lastNode != end {
|
| + panic(fmt.Sprintf("%s != %s", lastNode.Name(), end.Name()))
|
| + }
|
| + lastTarget := path[len(path)-1].edge.Target
|
| + if lastTarget != cycleStart {
|
| + panic(fmt.Sprintf("(%T)%s != (%T)%s", lastTarget, lastTarget.Name(), cycleStart, cycleStart.Name()))
|
| + }
|
| description := CycleDescription{}
|
| - description.first = first.node
|
| - description.last = last.node
|
| - description.path = make([]Node, 0, len(path))
|
| - for _, n := range path {
|
| - if len(description.path) > 0 || n.node == first.node {
|
| - description.path = append(description.path, n.node)
|
| + description.First = cycleStart
|
| + description.Last = end
|
| + description.Path = make([]OutEdge, 0, len(path))
|
| + for _, element := range path {
|
| + if len(description.Path) > 0 || element.node == cycleStart {
|
| + description.Path = append(description.Path, element.edge)
|
| }
|
| }
|
| - if description.path[len(description.path)-1] != last.node {
|
| - panic(fmt.Sprintf("%s != %s", description.path[len(description.path)-1], last.node))
|
| + if description.Path[len(description.Path)-1].Target != cycleStart {
|
| + panic(fmt.Sprintf("%s != %s", description.Path[len(description.Path)-1].Target.Name(), cycleStart.Name()))
|
| }
|
| return &description
|
| }
|
|
|