Skip to content

Commit f016b02

Browse files
committed
Fix linter hanging in certain situations.
We had some accidental aliasing due to shallow copy instead of deep copy. Fixes #541.
1 parent cd59751 commit f016b02

File tree

6 files changed

+142
-8
lines changed

6 files changed

+142
-8
lines changed

linter/internal/types/build_graph.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ func calcTP(node ast.Node, varAt map[ast.Node]*common.Variable, g *typeGraph) ty
192192
case *ast.Error:
193193
return concreteTP(voidTypeDesc())
194194
case *ast.Index:
195+
// TODO(sbarzowski) It appears we create a separate index node each time.
196+
// Perhaps we could de-duplicate (cache) index placeholders to save processing
197+
// later?
195198
switch index := node.Index.(type) {
196199
case *ast.LiteralString:
197200
return tpIndex(knownObjectIndex(g.getExprPlaceholder(node.Target), index.Value))

linter/internal/types/check.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ func Check(mainNode ast.Node, roots map[string]ast.Node, vars map[string]map[ast
140140
g.prepareTypes(mainNode, et)
141141

142142
// TODO(sbarzowski) Useful for debugging – expose it in CLI?
143+
// spew.Dump(g.timeStats)
144+
// spew.Dump(g.counters)
143145
// t := et[node.node]
144146
// fmt.Fprintf(os.Stderr, "%v\n", types.Describe(&t))
145147

linter/internal/types/desc.go

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ type arrayDesc struct {
2020
elementContains [][]placeholderID
2121
}
2222

23+
func (a *arrayDesc) copy() *arrayDesc {
24+
elementContains := make([][]placeholderID, len(a.elementContains))
25+
for i, placeholders := range a.elementContains {
26+
elementContains[i] = append([]placeholderID{}, placeholders...)
27+
}
28+
return &arrayDesc{
29+
furtherContain: append([]placeholderID{}, a.furtherContain...),
30+
elementContains: elementContains,
31+
}
32+
}
33+
2334
func (a *arrayDesc) widen(other *arrayDesc) {
2435
if other == nil {
2536
return
@@ -49,6 +60,18 @@ type objectDesc struct {
4960
allFieldsKnown bool
5061
}
5162

63+
func (o *objectDesc) copy() *objectDesc {
64+
fieldContains := make(map[string][]placeholderID)
65+
for k, v := range o.fieldContains {
66+
fieldContains[k] = append([]placeholderID(nil), v...)
67+
}
68+
return &objectDesc{
69+
unknownContain: append([]placeholderID(nil), o.unknownContain...),
70+
fieldContains: fieldContains,
71+
allFieldsKnown: o.allFieldsKnown,
72+
}
73+
}
74+
5275
func (o *objectDesc) widen(other *objectDesc) {
5376
if other == nil {
5477
return
@@ -77,6 +100,7 @@ func (o *objectDesc) normalize() {
77100
type functionDesc struct {
78101
resultContains []placeholderID
79102

103+
// Read-only
80104
// TODO(sbarzowski) instead of keeping "real" parameters here,
81105
// maybe keep only what we care about in the linter desc
82106
// (names and required-or-not).
@@ -85,6 +109,15 @@ type functionDesc struct {
85109
minArity, maxArity int
86110
}
87111

112+
func (f *functionDesc) copy() *functionDesc {
113+
return &functionDesc{
114+
resultContains: append([]placeholderID{}, f.resultContains...),
115+
params: f.params, // this is read-only, so we can just copy the pointer
116+
minArity: f.minArity,
117+
maxArity: f.maxArity,
118+
}
119+
}
120+
88121
func sameParameters(a, b []ast.Parameter) bool {
89122
if a == nil || b == nil {
90123
return false
@@ -255,7 +288,44 @@ func Describe(t *TypeDesc) string {
255288
return strings.Join(parts, " or ")
256289
}
257290

291+
// An intuitive notion of the size of type description. For performance tuning.
292+
func (t *TypeDesc) size() int64 {
293+
res := 0
294+
if t.Bool {
295+
res += 1
296+
}
297+
if t.Number {
298+
res += 1
299+
}
300+
if t.String {
301+
res += 1
302+
}
303+
if t.Null {
304+
res += 1
305+
}
306+
if t.Function() {
307+
res += len(t.FunctionDesc.resultContains)
308+
res += len(t.FunctionDesc.params)
309+
}
310+
if t.Array() {
311+
res += len(t.ArrayDesc.furtherContain)
312+
for _, elem := range t.ArrayDesc.elementContains {
313+
res += len(elem)
314+
}
315+
}
316+
if t.Object() {
317+
res += len(t.ObjectDesc.unknownContain)
318+
for _, elem := range t.ObjectDesc.fieldContains {
319+
res += len(elem)
320+
}
321+
}
322+
return int64(res)
323+
}
324+
258325
func (t *TypeDesc) widen(b *TypeDesc) {
326+
if t == b {
327+
panic("BUG: widening the type with itself")
328+
}
259329
t.Bool = t.Bool || b.Bool
260330
t.Number = t.Number || b.Number
261331
t.String = t.String || b.String
@@ -264,22 +334,24 @@ func (t *TypeDesc) widen(b *TypeDesc) {
264334
if t.FunctionDesc != nil {
265335
t.FunctionDesc.widen(b.FunctionDesc)
266336
} else if t.FunctionDesc == nil && b.FunctionDesc != nil {
267-
copy := *b.FunctionDesc
268-
t.FunctionDesc = &copy
337+
// copy := *b.FunctionDesc
338+
// t.FunctionDesc = &copy
339+
t.FunctionDesc = b.FunctionDesc.copy()
269340
}
270341

271342
if t.ObjectDesc != nil {
272343
t.ObjectDesc.widen(b.ObjectDesc)
273344
} else if t.ObjectDesc == nil && b.ObjectDesc != nil {
274-
copy := *b.ObjectDesc
275-
t.ObjectDesc = &copy
345+
// TODO(sbarzowski) Maybe we want copy on write?
346+
// So that it actually aliases underneath (with a bool marker)
347+
// which is resolved when widened.
348+
t.ObjectDesc = b.ObjectDesc.copy()
276349
}
277350

278351
if t.ArrayDesc != nil {
279352
t.ArrayDesc.widen(b.ArrayDesc)
280353
} else if t.ArrayDesc == nil && b.ArrayDesc != nil {
281-
copy := *b.ArrayDesc
282-
t.ArrayDesc = &copy
354+
t.ArrayDesc = b.ArrayDesc.copy()
283355
}
284356
}
285357

linter/internal/types/graph.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package types
22

33
import (
4+
"fmt"
5+
"io"
6+
"time"
7+
48
"github.com/google/go-jsonnet/ast"
59
)
610

@@ -20,6 +24,32 @@ type typeGraph struct {
2024

2125
// TODO(sbarzowski) what was this for?
2226
importFunc ImportFunc
27+
28+
// For performance tuning
29+
timeStats timeStats
30+
31+
counters counters
32+
}
33+
34+
// Subject to change at any point. For fine-tuning only.
35+
type timeStats struct {
36+
simplifyRef time.Duration
37+
separateElemTypes time.Duration
38+
topoOrder time.Duration
39+
findTypes time.Duration
40+
}
41+
42+
type counters struct {
43+
sccCount int
44+
containWidenCount int
45+
builtinWidenConcreteCount int
46+
builtinWidenContainedCount int
47+
preNormalizationSumSize int64
48+
postNormalizationSumSize int64
49+
}
50+
51+
func (g *typeGraph) debugStats(w io.Writer) {
52+
fmt.Fprintf(w, "placeholders no: %d\n", len(g._placeholders))
2353
}
2454

2555
func (g *typeGraph) placeholder(id placeholderID) *typePlaceholder {
@@ -150,10 +180,21 @@ func newTypeGraph(importFunc ImportFunc) *typeGraph {
150180
// prepareTypes produces a final type for each expression in the graph.
151181
// No further operations on the graph are valid after this is called.
152182
func (g *typeGraph) prepareTypes(node ast.Node, typeOf exprTypes) {
183+
tStart := time.Now()
153184
g.simplifyReferences()
185+
tSimplify := time.Now()
154186
g.separateElementTypes()
187+
tSeparate := time.Now()
155188
g.makeTopoOrder()
189+
tTopo := time.Now()
156190
g.findTypes()
191+
tTypes := time.Now()
192+
g.timeStats = timeStats{
193+
simplifyRef: tSimplify.Sub(tStart),
194+
separateElemTypes: tSeparate.Sub(tSimplify),
195+
topoOrder: tTopo.Sub(tSeparate),
196+
findTypes: tTypes.Sub(tTopo),
197+
}
157198
for e, p := range g.exprPlaceholder {
158199
typeOf[e] = g.upperBound[p]
159200
}

linter/internal/types/process_graph.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package types
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
)
46

57
type stronglyConnectedComponentID int
68

@@ -288,6 +290,7 @@ func (g *typeGraph) findTypes() {
288290
sccID++
289291
}
290292
}
293+
g.counters.sccCount = len(stronglyConnectedComponents)
291294

292295
for i := len(stronglyConnectedComponents) - 1; i >= 0; i-- {
293296
scc := stronglyConnectedComponents[i]
@@ -301,11 +304,18 @@ func (g *typeGraph) resolveTypesInSCC(scc []placeholderID) {
301304
common := voidTypeDesc()
302305

303306
for _, p := range scc {
307+
// Add types from contained SCCs. These are fully calculated
308+
// because we are going in topo order
304309
for _, contained := range g.placeholder(p).contains {
310+
// TODO(sbarzowski) Is it possible that we are adding multiple times from the same placeholder?
311+
// Each `p` is normalized, but different placeholders in scc may still contain the same thing.
305312
if g.sccOf[contained] != sccID {
306313
common.widen(&g.upperBound[contained])
314+
g.counters.containWidenCount += 1
307315
}
308316
}
317+
318+
// Handle builtins (e.g)
309319
builtinOp := g.placeholder(p).builtinOp
310320
if builtinOp != nil {
311321
concreteArgs := []*TypeDesc{}
@@ -318,9 +328,11 @@ func (g *typeGraph) resolveTypesInSCC(scc []placeholderID) {
318328
}
319329
res := builtinOp.f(concreteArgs, builtinOp.args)
320330
common.widen(&res.concrete)
331+
g.counters.builtinWidenConcreteCount += 1
321332
for _, contained := range res.contained {
322333
if g.sccOf[contained] != sccID {
323334
common.widen(&g.upperBound[contained])
335+
g.counters.builtinWidenContainedCount += 1
324336
}
325337
}
326338
}
@@ -333,8 +345,12 @@ func (g *typeGraph) resolveTypesInSCC(scc []placeholderID) {
333345
}
334346
}
335347

348+
g.counters.preNormalizationSumSize += common.size()
349+
336350
common.normalize()
337351

352+
g.counters.postNormalizationSumSize += common.size()
353+
338354
for _, p := range scc {
339355
g.upperBound[p] = common
340356
}

linter/internal/types/stdlib.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ func prepareStdlib(g *typeGraph) {
99
arrayOfNumber := anyArrayType
1010
stringOrArray := anyType
1111
stringOrNumber := anyType
12-
jsonType := anyType // It actually cannot functions anywhere
12+
jsonType := anyType // It actually cannot have functions anywhere
1313

1414
required := func(name string) ast.Parameter {
1515
return ast.Parameter{Name: ast.Identifier(name)}

0 commit comments

Comments
 (0)