Skip to content

Commit f53f655

Browse files
committed
core/toposort: Stop using RawString
Use of RawString in debug output is unsafe because the Feature could be an IntLabel for a list which exceeds the string index. So for debugging, switch to using SelectorString. In general, the graph can contain nodes which have an IntLabel Feature, and we must be able to sort those correctly. So adjust the comparison function in order to detect the type of the Feature and make sensible choices for comparison. Fixes #3698. Signed-off-by: Matthew Sackman <[email protected]> Change-Id: I9eb692d6c722ea9c098d7ca380fd8a71c8cb040c Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1207715 TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent 1ac3fe0 commit f53f655

File tree

4 files changed

+65
-30
lines changed

4 files changed

+65
-30
lines changed

internal/core/toposort/graph.go

+24-9
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ func (n *Node) IsSorted() bool {
4949
return n.position >= 0
5050
}
5151

52-
func (n *Node) Name(index adt.StringIndexer) string {
53-
// TODO: two different fields like "#foo" and #foo, can have the same raw
54-
// string
55-
return n.Feature.RawString(index)
52+
// SafeName returns a string useful for debugging, regardless of the
53+
// type of the feature. So for IntLabels, you'll get back `1`, `10`
54+
// etc; for identifiers, you may get back a string with quotes in it,
55+
// eg `"runs-on"`. So this is not useful for comparisons, but it is
56+
// useful (and safe) for debugging.
57+
func (n *Node) SafeName(index adt.StringIndexer) string {
58+
return n.Feature.SelectorString(index)
5659
}
5760

5861
type Nodes []*Node
@@ -136,7 +139,19 @@ func (builder *GraphBuilder) Build() *Graph {
136139
type indexComparison struct{ adt.StringIndexer }
137140

138141
func (index *indexComparison) compareNodeByName(a, b *Node) int {
139-
return cmp.Compare(a.Name(index), b.Name(index))
142+
aFeature, bFeature := a.Feature, b.Feature
143+
aIsInt, bIsInt := aFeature.Typ() == adt.IntLabel, bFeature.Typ() == adt.IntLabel
144+
145+
switch {
146+
case aIsInt && bIsInt:
147+
return cmp.Compare(aFeature.Index(), bFeature.Index())
148+
case aIsInt:
149+
return -1
150+
case bIsInt:
151+
return 1
152+
default:
153+
return cmp.Compare(aFeature.RawString(index), bFeature.RawString(index))
154+
}
140155
}
141156

142157
func (index *indexComparison) compareCyclesByNames(a, b *Cycle) int {
@@ -202,7 +217,7 @@ func chooseCycle(indexCmp *indexComparison, unusedCycles []*Cycle) *Cycle {
202217

203218
debug("cycle %v; edgeCount %v; enabledSince %v; entryNode %v\n",
204219
cycle, brokenEdgeCount, enabledSince,
205-
entryNode.Name(indexCmp))
220+
entryNode.SafeName(indexCmp))
206221

207222
cycleIsBetter := chosenCycleIdx == -1
208223
// this is written out long-form for ease of readability
@@ -219,7 +234,7 @@ func chooseCycle(indexCmp *indexComparison, unusedCycles []*Cycle) *Cycle {
219234
case enabledSince > chosenCycleEnabledSince:
220235
// noop - only continue if ==
221236

222-
case entryNode.Name(indexCmp) < chosenCycleEntryNode.Name(indexCmp):
237+
case indexCmp.compareNodeByName(entryNode, chosenCycleEntryNode) < 0:
223238
cycleIsBetter = true
224239
case entryNode == chosenCycleEntryNode:
225240
cycleIsBetter =
@@ -239,7 +254,7 @@ func chooseCycle(indexCmp *indexComparison, unusedCycles []*Cycle) *Cycle {
239254
}
240255

241256
debug("Chose cycle: %v; entering at node: %s\n",
242-
unusedCycles[chosenCycleIdx], chosenCycleEntryNode.Name(indexCmp))
257+
unusedCycles[chosenCycleIdx], chosenCycleEntryNode.SafeName(indexCmp))
243258
cycle := unusedCycles[chosenCycleIdx]
244259
unusedCycles[chosenCycleIdx] = nil
245260
cycle.RotateToStartAt(chosenCycleEntryNode)
@@ -361,7 +376,7 @@ func appendNodes(indexCmp *indexComparison, nodesSorted, nodesReady, nodesEnable
361376
}
362377
}
363378
debug("After %v, found new ready: %s\n",
364-
nodesSorted, next.Name(indexCmp))
379+
nodesSorted, next.SafeName(indexCmp))
365380
nodesEnabled = append(nodesEnabled, next)
366381
nodesReadyNeedsSorting = true
367382
}

internal/core/toposort/sort_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ import (
1818
"fmt"
1919
"testing"
2020

21+
"cuelang.org/go/cue"
22+
"cuelang.org/go/cue/cuecontext"
2123
"cuelang.org/go/cue/format"
24+
"cuelang.org/go/internal/core/adt"
2225
"cuelang.org/go/internal/core/eval"
2326
"cuelang.org/go/internal/core/export"
27+
"cuelang.org/go/internal/core/toposort"
2428
"cuelang.org/go/internal/cuetdtest"
2529
"cuelang.org/go/internal/cuetxtar"
30+
"cuelang.org/go/internal/value"
2631
)
2732

2833
func TestTopologicalSort(t *testing.T) {
@@ -73,3 +78,18 @@ func TestTopologicalSort(t *testing.T) {
7378
})
7479
}
7580
}
81+
82+
func TestIssue3698(t *testing.T) {
83+
str := `import "list"
84+
list.Repeat([{x: 5}], 1000)
85+
`
86+
ctx := cuecontext.New()
87+
val := ctx.CompileString(str, cue.Filename("issue3698"))
88+
if err := val.Err(); err != nil {
89+
t.Fatal(err)
90+
}
91+
run, v := value.ToInternal(val)
92+
// If toposort does not correctly handle feature names,
93+
// specifically IntLabels, then this will panic:
94+
toposort.VertexFeatures(adt.NewContext(run, v), v)
95+
}

internal/core/toposort/vertex.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ func (vf *vertexFeatures) compareStructMeta(a, b *structMeta) int {
393393
}
394394

395395
func VertexFeatures(ctx *adt.OpContext, v *adt.Vertex) []adt.Feature {
396-
debug("\n*** V (%s %v %p) ***\n", v.Label.RawString(ctx), v.Label, v)
396+
debug("\n*** V (%s %v %p) ***\n", v.Label.SelectorString(ctx), v.Label, v)
397397

398398
builder := NewGraphBuilder(!ctx.Config.SortFields)
399399
dynFieldsMap := dynamicFieldsFeatures(v)

tools/flow/testdata/slice.txtar

+20-20
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@ root: [
2424
-- out/run/errors --
2525
-- out/run/t0 --
2626
graph TD
27-
t0("root[2] [Waiting]")
28-
t0-->t2
29-
t0-->t1
27+
t0("root[0] [Ready]")
3028
t1("root[1] [Waiting]")
31-
t1-->t2
32-
t2("root[0] [Ready]")
29+
t1-->t0
30+
t2("root[2] [Waiting]")
31+
t2-->t0
32+
t2-->t1
3333

3434
-- out/run/t1 --
3535
graph TD
36-
t0("root[2] [Waiting]")
37-
t0-->t2
38-
t0-->t1
36+
t0("root[0] [Terminated]")
3937
t1("root[1] [Ready]")
40-
t1-->t2
41-
t2("root[0] [Terminated]")
38+
t1-->t0
39+
t2("root[2] [Waiting]")
40+
t2-->t0
41+
t2-->t1
4242

4343
-- out/run/t1/value --
4444
{
@@ -58,12 +58,12 @@ Conjuncts: 30
5858
Disjuncts: 17
5959
-- out/run/t2 --
6060
graph TD
61-
t0("root[2] [Ready]")
62-
t0-->t2
63-
t0-->t1
61+
t0("root[0] [Terminated]")
6462
t1("root[1] [Terminated]")
65-
t1-->t2
66-
t2("root[0] [Terminated]")
63+
t1-->t0
64+
t2("root[2] [Ready]")
65+
t2-->t0
66+
t2-->t1
6767

6868
-- out/run/t2/value --
6969
{
@@ -88,12 +88,12 @@ Conjuncts: 33
8888
Disjuncts: 17
8989
-- out/run/t3 --
9090
graph TD
91-
t0("root[2] [Terminated]")
92-
t0-->t2
93-
t0-->t1
91+
t0("root[0] [Terminated]")
9492
t1("root[1] [Terminated]")
95-
t1-->t2
96-
t2("root[0] [Terminated]")
93+
t1-->t0
94+
t2("root[2] [Terminated]")
95+
t2-->t0
96+
t2-->t1
9797

9898
-- out/run/t3/value --
9999
{

0 commit comments

Comments
 (0)