1// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package adt
16
17// This file implements the closedness algorithm.
18
19// Outline of algorithm
20//
21// To compute closedness each Vertex is associated with a tree which has
22// leaf nodes with sets of allowed labels, and interior nodes that describe
23// how these sets may be combines: Or, for embedding, or And for definitions.
24//
25// Each conjunct of a Vertex is associated with such a leaf node. Each
26// conjunct that evaluates to a struct is added to the list of Structs, which
27// in the end forms this tree. If a conjunct is embedded, or references another
28// struct or definition, it adds interior node to reflect this.
29//
30// To test whether a feature is allowed, it must satisfy the resulting
31// expression tree.
32//
33// In order to avoid having to copy the tree for each node, the tree is linked
34// from leaf node to root, rather than the other way around. This allows
35// parent nodes to be shared as the tree grows and ensures that the growth
36// of the tree is bounded by the number of conjuncts. As a consequence, this
37// requires a two-pass algorithm:
38//
39//    - walk up to mark which nodes are required and count the number of
40//      child nodes that need to be satisfied.
41//    - verify fields in leaf structs and mark parent leafs as satisfied
42//      when appropriate.
43//
44// A label is allowed if all required root nodes are marked as accepted after
45// these two passes.
46//
47
48// A note on embeddings: it is important to keep track which conjuncts originate
49// from an embedding, as an embedded value may eventually turn into a closed
50// struct. Consider
51//
52//    a: {
53//       b
54//       d: e: int
55//    }
56//    b: d: {
57//       #A & #B
58//    }
59//
60// At the point of evaluating `a`, the struct is not yet closed. However,
61// descending into `d` will trigger the inclusion of definitions which in turn
62// causes the struct to be closed. At this point, it is important to know that
63// `b` originated from an embedding, as otherwise `e` may not be allowed.
64
65// TODO(perf):
66// - less nodes
67// - disable StructInfo nodes that can no longer pass a feature
68// - sort StructInfos active ones first.
69
70// TODO(errors): return a dedicated ConflictError that can track original
71// positions on demand.
72
73func (v *Vertex) IsInOneOf(t SpanType) bool {
74	for _, s := range v.Structs {
75		if s.CloseInfo.IsInOneOf(t) {
76			return true
77		}
78	}
79	return false
80}
81
82// IsRecursivelyClosed returns true if this value is either a definition or unified
83// with a definition.
84func (v *Vertex) IsRecursivelyClosed() bool {
85	return v.Closed || v.IsInOneOf(DefinitionSpan)
86}
87
88type closeNodeType uint8
89
90const (
91	// a closeRef node is created when there is a non-definition reference.
92	// These nodes are not necessary for computing results, but may be
93	// relevant down the line to group closures through embedded values and
94	// to track position information for failures.
95	closeRef closeNodeType = iota
96
97	// closeDef indicates this node was introduced as a result of referencing
98	// a definition.
99	closeDef
100
101	// closeEmbed indicates this node was added as a result of an embedding.
102	closeEmbed
103
104	_ = closeRef // silence the linter
105)
106
107// TODO: merge with closeInfo: this is a leftover of the refactoring.
108type CloseInfo struct {
109	*closeInfo
110
111	IsClosed   bool
112	FieldTypes OptionalType
113}
114
115func (c CloseInfo) Location() Node {
116	if c.closeInfo == nil {
117		return nil
118	}
119	return c.closeInfo.location
120}
121
122func (c CloseInfo) SpanMask() SpanType {
123	if c.closeInfo == nil {
124		return 0
125	}
126	return c.span
127}
128
129func (c CloseInfo) RootSpanType() SpanType {
130	if c.closeInfo == nil {
131		return 0
132	}
133	return c.root
134}
135
136func (c CloseInfo) IsInOneOf(t SpanType) bool {
137	if c.closeInfo == nil {
138		return false
139	}
140	return c.span&t != 0
141}
142
143// TODO(perf): remove: error positions should always be computed on demand
144// in dedicated error types.
145func (c *CloseInfo) AddPositions(ctx *OpContext) {
146	for s := c.closeInfo; s != nil; s = s.parent {
147		if loc := s.location; loc != nil {
148			ctx.AddPosition(loc)
149		}
150	}
151}
152
153// TODO(perf): use on StructInfo. Then if parent and expression are the same
154// it is possible to use cached value.
155func (c CloseInfo) SpawnEmbed(x Expr) CloseInfo {
156	var span SpanType
157	if c.closeInfo != nil {
158		span = c.span
159	}
160
161	c.closeInfo = &closeInfo{
162		parent:   c.closeInfo,
163		location: x,
164		mode:     closeEmbed,
165		root:     EmbeddingSpan,
166		span:     span | EmbeddingSpan,
167	}
168	return c
169}
170
171// SpawnGroup is used for structs that contain embeddings that may end up
172// closing the struct. This is to force that `b` is not allowed in
173//
174//      a: {#foo} & {b: int}
175//
176func (c CloseInfo) SpawnGroup(x Expr) CloseInfo {
177	var span SpanType
178	if c.closeInfo != nil {
179		span = c.span
180	}
181	c.closeInfo = &closeInfo{
182		parent:   c.closeInfo,
183		location: x,
184		span:     span,
185	}
186	return c
187}
188
189// SpawnSpan is used to track that a value is introduced by a comprehension
190// or constraint. Definition and embedding spans are introduced with SpawnRef
191// and SpawnEmbed, respectively.
192func (c CloseInfo) SpawnSpan(x Node, t SpanType) CloseInfo {
193	var span SpanType
194	if c.closeInfo != nil {
195		span = c.span
196	}
197	c.closeInfo = &closeInfo{
198		parent:   c.closeInfo,
199		location: x,
200		root:     t,
201		span:     span | t,
202	}
203	return c
204}
205
206func (c CloseInfo) SpawnRef(arc *Vertex, isDef bool, x Expr) CloseInfo {
207	var span SpanType
208	if c.closeInfo != nil {
209		span = c.span
210	}
211	c.closeInfo = &closeInfo{
212		parent:   c.closeInfo,
213		location: x,
214		span:     span,
215	}
216	if isDef {
217		c.mode = closeDef
218		c.closeInfo.root = DefinitionSpan
219		c.closeInfo.span |= DefinitionSpan
220	}
221	return c
222}
223
224// isDef reports whether an expressions is a reference that references a
225// definition anywhere in its selection path.
226//
227// TODO(performance): this should be merged with resolve(). But for now keeping
228// this code isolated makes it easier to see what it is for.
229func IsDef(x Expr) bool {
230	switch r := x.(type) {
231	case *FieldReference:
232		return r.Label.IsDef()
233
234	case *SelectorExpr:
235		if r.Sel.IsDef() {
236			return true
237		}
238		return IsDef(r.X)
239
240	case *IndexExpr:
241		return IsDef(r.X)
242	}
243	return false
244}
245
246// A SpanType is used to indicate whether a CUE value is within the scope of
247// a certain CUE language construct, the span type.
248type SpanType uint8
249
250const (
251	// EmbeddingSpan means that this value was embedded at some point and should
252	// not be included as a possible root node in the todo field of OpContext.
253	EmbeddingSpan SpanType = 1 << iota
254	ConstraintSpan
255	ComprehensionSpan
256	DefinitionSpan
257)
258
259type closeInfo struct {
260	// location records the expression that led to this node's introduction.
261	location Node
262
263	// The parent node in the tree.
264	parent *closeInfo
265
266	// TODO(performance): if references are chained, we could have a separate
267	// parent pointer to skip the chain.
268
269	// mode indicates whether this node was added as part of an embedding,
270	// definition or non-definition reference.
271	mode closeNodeType
272
273	// noCheck means this struct is irrelevant for closedness checking. This can
274	// happen when:
275	//  - it is a sibling of a new definition.
276	noCheck bool // don't process for inclusion info
277
278	root SpanType
279	span SpanType
280}
281
282// closeStats holds the administrative fields for a closeInfo value. Each
283// closeInfo is associated with a single closeStats value per unification
284// operator. This association is done through an OpContext. This allows the
285// same value to be used in multiple concurrent unification operations.
286// NOTE: there are other parts of the algorithm that are not thread-safe yet.
287type closeStats struct {
288	// the other fields of this closeStats value are only valid if generation
289	// is equal to the generation in OpContext. This allows for lazy
290	// initialization of closeStats.
291	generation int
292
293	// These counts keep track of how many required child nodes need to be
294	// completed before this node is accepted.
295	requiredCount int
296	acceptedCount int
297
298	// accepted is set if this node is accepted.
299	accepted bool
300
301	required bool
302	next     *closeStats
303}
304
305func (c *closeInfo) isClosed() bool {
306	return c.mode == closeDef
307}
308
309func isClosed(v *Vertex) bool {
310	for _, s := range v.Structs {
311		if s.IsClosed {
312			return true
313		}
314		for c := s.closeInfo; c != nil; c = c.parent {
315			if c.isClosed() {
316				return true
317			}
318		}
319	}
320	return false
321}
322
323// Accept determines whether f is allowed in n. It uses the OpContext for
324// caching administrative fields.
325func Accept(ctx *OpContext, n *Vertex, f Feature) (found, required bool) {
326	ctx.generation++
327	ctx.todo = nil
328
329	var optionalTypes OptionalType
330
331	// TODO(perf): more aggressively determine whether a struct is open or
332	// closed: open structs do not have to be checked, yet they can particularly
333	// be the ones with performance isssues, for instanced as a result of
334	// embedded for comprehensions.
335	for _, s := range n.Structs {
336		if !s.useForAccept() {
337			continue
338		}
339		markCounts(ctx, s.CloseInfo)
340		optionalTypes |= s.types
341	}
342
343	if f.Index() == MaxIndex {
344		f = 0
345	}
346
347	var str Value
348	if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() && f > 0 {
349		str = f.ToValue(ctx)
350	}
351
352	for _, s := range n.Structs {
353		if !s.useForAccept() {
354			continue
355		}
356		if verifyArc(ctx, s, f, str) {
357			// Beware: don't add to below expression: this relies on the
358			// side effects of markUp.
359			ok := markUp(ctx, s.closeInfo, 0)
360			found = found || ok
361		}
362	}
363
364	// Reject if any of the roots is not accepted.
365	for x := ctx.todo; x != nil; x = x.next {
366		if !x.accepted {
367			return false, true
368		}
369	}
370
371	return found, ctx.todo != nil
372}
373
374func markCounts(ctx *OpContext, info CloseInfo) {
375	if info.IsClosed {
376		markRequired(ctx, info.closeInfo)
377		return
378	}
379	for s := info.closeInfo; s != nil; s = s.parent {
380		if s.isClosed() {
381			markRequired(ctx, s)
382			return
383		}
384	}
385}
386
387func markRequired(ctx *OpContext, info *closeInfo) {
388	count := 0
389	for ; ; info = info.parent {
390		var s closeInfo
391		if info != nil {
392			s = *info
393		}
394
395		x := getScratch(ctx, info)
396
397		x.requiredCount += count
398
399		if x.required {
400			return
401		}
402
403		if s.span&EmbeddingSpan == 0 {
404			x.next = ctx.todo
405			ctx.todo = x
406		}
407
408		x.required = true
409
410		if info == nil {
411			return
412		}
413
414		count = 0
415		if s.mode != closeEmbed {
416			count = 1
417		}
418	}
419}
420
421func markUp(ctx *OpContext, info *closeInfo, count int) bool {
422	for ; ; info = info.parent {
423		var s closeInfo
424		if info != nil {
425			s = *info
426		}
427
428		x := getScratch(ctx, info)
429
430		x.acceptedCount += count
431
432		if x.acceptedCount < x.requiredCount {
433			return false
434		}
435
436		x.accepted = true
437
438		if info == nil {
439			return true
440		}
441
442		count = 0
443		if x.required && s.mode != closeEmbed {
444			count = 1
445		}
446	}
447}
448
449// getScratch: explain generation.
450func getScratch(ctx *OpContext, s *closeInfo) *closeStats {
451	m := ctx.closed
452	if m == nil {
453		m = map[*closeInfo]*closeStats{}
454		ctx.closed = m
455	}
456
457	x := m[s]
458	if x == nil {
459		x = &closeStats{}
460		m[s] = x
461	}
462
463	if x.generation != ctx.generation {
464		*x = closeStats{generation: ctx.generation}
465	}
466
467	return x
468}
469
470func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label Value) bool {
471	isRegular := f.IsRegular()
472
473	o := s.StructLit
474	env := s.Env
475
476	if isRegular && (len(o.Additional) > 0 || o.IsOpen) {
477		return true
478	}
479
480	for _, g := range o.Fields {
481		if f == g.Label {
482			return true
483		}
484	}
485
486	if !isRegular {
487		return false
488	}
489
490	// Do not record errors during this validation.
491	errs := ctx.errs
492	defer func() { ctx.errs = errs }()
493
494	if len(o.Dynamic) > 0 && f.IsString() && label != nil {
495		for _, b := range o.Dynamic {
496			v := env.evalCached(ctx, b.Key)
497			s, ok := v.(*String)
498			if !ok {
499				continue
500			}
501			if label.(*String).Str == s.Str {
502				return true
503			}
504		}
505	}
506
507	for _, b := range o.Bulk {
508		if matchBulk(ctx, env, b, f, label) {
509			return true
510		}
511	}
512
513	// TODO(perf): delay adding this position: create a special error type that
514	// computes all necessary positions on demand.
515	if ctx != nil {
516		ctx.AddPosition(s.StructLit)
517	}
518
519	return false
520}
521