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