github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/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 var str Value 344 if f.Index() == MaxIndex { 345 f &= fTypeMask 346 } else if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() { 347 str = f.ToValue(ctx) 348 } 349 350 for _, s := range n.Structs { 351 if !s.useForAccept() { 352 continue 353 } 354 if verifyArc(ctx, s, f, str) { 355 // Beware: don't add to below expression: this relies on the 356 // side effects of markUp. 357 ok := markUp(ctx, s.closeInfo, 0) 358 found = found || ok 359 } 360 } 361 362 // Reject if any of the roots is not accepted. 363 for x := ctx.todo; x != nil; x = x.next { 364 if !x.accepted { 365 return false, true 366 } 367 } 368 369 return found, ctx.todo != nil 370 } 371 372 func markCounts(ctx *OpContext, info CloseInfo) { 373 if info.IsClosed { 374 markRequired(ctx, info.closeInfo) 375 return 376 } 377 for s := info.closeInfo; s != nil; s = s.parent { 378 if s.isClosed() { 379 markRequired(ctx, s) 380 return 381 } 382 } 383 } 384 385 func markRequired(ctx *OpContext, info *closeInfo) { 386 count := 0 387 for ; ; info = info.parent { 388 var s closeInfo 389 if info != nil { 390 s = *info 391 } 392 393 x := getScratch(ctx, info) 394 395 x.requiredCount += count 396 397 if x.required { 398 return 399 } 400 401 if s.span&EmbeddingSpan == 0 { 402 x.next = ctx.todo 403 ctx.todo = x 404 } 405 406 x.required = true 407 408 if info == nil { 409 return 410 } 411 412 count = 0 413 if s.mode != closeEmbed { 414 count = 1 415 } 416 } 417 } 418 419 func markUp(ctx *OpContext, info *closeInfo, count int) bool { 420 for ; ; info = info.parent { 421 var s closeInfo 422 if info != nil { 423 s = *info 424 } 425 426 x := getScratch(ctx, info) 427 428 x.acceptedCount += count 429 430 if x.acceptedCount < x.requiredCount { 431 return false 432 } 433 434 x.accepted = true 435 436 if info == nil { 437 return true 438 } 439 440 count = 0 441 if x.required && s.mode != closeEmbed { 442 count = 1 443 } 444 } 445 } 446 447 // getScratch: explain generation. 448 func getScratch(ctx *OpContext, s *closeInfo) *closeStats { 449 m := ctx.closed 450 if m == nil { 451 m = map[*closeInfo]*closeStats{} 452 ctx.closed = m 453 } 454 455 x := m[s] 456 if x == nil { 457 x = &closeStats{} 458 m[s] = x 459 } 460 461 if x.generation != ctx.generation { 462 *x = closeStats{generation: ctx.generation} 463 } 464 465 return x 466 } 467 468 func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label Value) bool { 469 isRegular := f.IsString() 470 471 o := s.StructLit 472 env := s.Env 473 474 if isRegular && (len(o.Additional) > 0 || o.IsOpen) { 475 return true 476 } 477 478 for _, g := range o.Fields { 479 if f == g.Label { 480 return true 481 } 482 } 483 484 if !isRegular { 485 return false 486 } 487 488 // Do not record errors during this validation. 489 errs := ctx.errs 490 defer func() { ctx.errs = errs }() 491 492 if len(o.Dynamic) > 0 && f.IsString() && label != nil { 493 for _, b := range o.Dynamic { 494 v := env.evalCached(ctx, b.Key) 495 s, ok := Unwrap(v).(*String) 496 if !ok { 497 continue 498 } 499 if label.(*String).Str == s.Str { 500 return true 501 } 502 } 503 } 504 505 for _, b := range o.Bulk { 506 if matchBulk(ctx, env, b, f, label) { 507 return true 508 } 509 } 510 511 // TODO(perf): delay adding this position: create a special error type that 512 // computes all necessary positions on demand. 513 if ctx != nil { 514 ctx.AddPosition(s.StructLit) 515 } 516 517 return false 518 }