github.com/solo-io/cue@v0.4.7/internal/core/adt/closed.go (about)

     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  
    15  package 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  
    73  func (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.
    84  func (v *Vertex) IsRecursivelyClosed() bool {
    85  	return v.Closed || v.IsInOneOf(DefinitionSpan)
    86  }
    87  
    88  type closeNodeType uint8
    89  
    90  const (
    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.
   108  type CloseInfo struct {
   109  	*closeInfo
   110  
   111  	IsClosed   bool
   112  	FieldTypes OptionalType
   113  }
   114  
   115  func (c CloseInfo) Location() Node {
   116  	if c.closeInfo == nil {
   117  		return nil
   118  	}
   119  	return c.closeInfo.location
   120  }
   121  
   122  func (c CloseInfo) SpanMask() SpanType {
   123  	if c.closeInfo == nil {
   124  		return 0
   125  	}
   126  	return c.span
   127  }
   128  
   129  func (c CloseInfo) RootSpanType() SpanType {
   130  	if c.closeInfo == nil {
   131  		return 0
   132  	}
   133  	return c.root
   134  }
   135  
   136  func (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.
   145  func (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.
   155  func (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  //
   176  func (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.
   192  func (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  
   206  func (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.
   229  func 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.
   248  type SpanType uint8
   249  
   250  const (
   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  
   259  type 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.
   287  type 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  
   305  func (c *closeInfo) isClosed() bool {
   306  	return c.mode == closeDef
   307  }
   308  
   309  func 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.
   325  func 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  
   374  func 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  
   387  func 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  
   421  func 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.
   450  func 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  
   470  func 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  }