cuelang.org/go@v0.13.0/internal/core/dep/dep.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 dep analyzes dependencies between values. 16 package dep 17 18 import ( 19 "cuelang.org/go/cue/errors" 20 "cuelang.org/go/internal" 21 "cuelang.org/go/internal/core/adt" 22 ) 23 24 // Dependencies 25 // 26 // A dependency is a reference relation from one Vertex to another. A Vertex 27 // has multiple Conjuncts, each of which is associated with an expression. 28 // Each expression, in turn, may have multiple references, each representing 29 // a single dependency. 30 // 31 // A reference that occurs in a node will point to another node. A reference 32 // `x.y` may point to a node `x.y` as well as `x`. By default, only the most 33 // precise node is reported, which is `x.y` if it exists, or `x` otherwise. 34 // In the latter case, a path is associated with the reference to indicate 35 // the specific non-existing path that is needed for that dependency. (TODO) 36 // 37 // A single reference may point to multiple nodes. For instance, 38 // (a & b).z may point to both `a.z` and `b.z`. This has to be taken into 39 // account if dep is used for substitutions. 40 // 41 // 42 // field: Conjunct 43 // | 44 // Expr Conjunct Expression 45 // |- Reference A reference to led to a target 46 // |- \- Target Node Pointed to by Reference 47 // |- \- UsedPath The sole path used within Node 48 49 // TODO: verify that these concepts are correctly reflected in the API: 50 // Source: 51 // The CUE value for which dependencies are analyzed. 52 // This may differ per dependency for dynamic and transitive analysis. 53 // Target: 54 // The field to which the found reference resolves. 55 // Reference: 56 // The reference that resolved to the dependency. 57 // Replacing this reference in the conjuncts of the source vertex with a 58 // link to the target vertex yields the same result if there only a single 59 // dependency matching this reference. 60 // Conjunct: 61 // The conjunct in which the Reference was found. 62 // Used Path: 63 // The target vertex may be a parent of the actual, more precise, 64 // dependency, if the latter does not yet exist. The target path is the path 65 // from the target vertex to the actual dependency. 66 // Trace: 67 // A sequence of dependencies leading to the result in case of transitive 68 // dependencies. 69 70 // TODO: for a public API, a better approach seems to be to have a single 71 // Visit method, with a configuration to set a bunch of orthogonal options. 72 // Here are some examples of the options: 73 // - Dynamic: evaluate and descend into computed fields. 74 // - Recurse: evaluate dependencies of subfields as well. 75 // - Inner: report dependencies within the root being visited. 76 // - RootLess: report dependencies that do not have a path to the root. 77 // - Transitive: get all dependencies, not just the direct ones. 78 // - Substitute: do not get precise dependencies, but rather keep them 79 // such that each expression needs to be replaced with at most 80 // one dependency. Could be a method on Dependency. 81 // - ContinueOnError: continue visiting even if there are errors. 82 // [add more as they come up] 83 // 84 85 type Config struct { 86 // Dynamic enables evaluting dependencies Vertex Arcs, recursively 87 Dynamic bool 88 89 // Descend enables recursively descending into fields. This option is 90 // implied by Dynamic. 91 Descend bool 92 93 // Cycles allows a Node to reported more than once. This includes the node 94 // passed to Visit, which is otherwise never reported. This option can be 95 // used to disable cycle checking. TODO: this is not yet implemented. 96 AllowCycles bool 97 98 // Rootless enables reporting nodes that do not have a path from the root. 99 // This includes variables of comprehensions and fields of composite literal 100 // values that are part of expressions, such as {out: v}.out. 101 Rootless bool 102 103 // TODO: 104 // ContinueOnError indicates whether to continue finding dependencies 105 // even when there are errors. 106 // ContinueOnError bool 107 108 // pkg indicates the main package for which the analyzer is configured, 109 // which is used for reporting purposes. 110 Pkg *adt.ImportReference 111 } 112 113 // A Dependency is a reference and the node that reference resolves to. 114 type Dependency struct { 115 // Node is the referenced node. 116 Node *adt.Vertex 117 118 // Reference is the expression that referenced the node. 119 Reference adt.Resolver 120 121 pkg *adt.ImportReference 122 123 top bool 124 125 visitor *visitor 126 } 127 128 // Recurse visits the dependencies of d.Node, using the same visit function as 129 // the original. 130 func (d *Dependency) Recurse() { 131 savedAll := d.visitor.all 132 savedTop := d.visitor.top 133 savedMarked := d.visitor.marked 134 d.visitor.all = d.visitor.recurse 135 d.visitor.top = true 136 d.visitor.marked = nil 137 138 d.visitor.visitReusingVisitor(d.Node, false) 139 140 d.visitor.all = savedAll 141 d.visitor.top = savedTop 142 d.visitor.marked = savedMarked 143 } 144 145 // Import returns the import reference or nil if the reference was within 146 // the same package as the visited Vertex. 147 func (d *Dependency) Import() *adt.ImportReference { 148 return d.pkg 149 } 150 151 // IsRoot reports whether the dependency is referenced by the root of the 152 // original Vertex passed to any of the Visit* functions, and not one of its 153 // descendent arcs. This always returns true for [Visit]. 154 func (d *Dependency) IsRoot() bool { 155 return d.top 156 } 157 158 func importRef(r adt.Expr) *adt.ImportReference { 159 switch x := r.(type) { 160 case *adt.ImportReference: 161 return x 162 case *adt.SelectorExpr: 163 return importRef(x.X) 164 case *adt.IndexExpr: 165 return importRef(x.X) 166 } 167 return nil 168 } 169 170 // VisitFunc is used for reporting dependencies. 171 type VisitFunc func(Dependency) error 172 173 var empty *adt.Vertex 174 175 func init() { 176 // TODO: Consider setting a non-nil BaseValue. 177 empty = &adt.Vertex{} 178 empty.ForceDone() 179 } 180 181 var zeroConfig = &Config{} 182 183 // Visit calls f for the dependencies of n as determined by the given 184 // configuration. 185 func Visit(cfg *Config, c *adt.OpContext, n *adt.Vertex, f VisitFunc) error { 186 if cfg == nil { 187 cfg = zeroConfig 188 } 189 if c == nil { 190 panic("nil context") 191 } 192 v := visitor{ 193 ctxt: c, 194 fn: f, 195 pkg: cfg.Pkg, 196 recurse: cfg.Descend, 197 all: cfg.Descend, 198 top: true, 199 cfgDynamic: cfg.Dynamic, 200 } 201 return v.visitReusingVisitor(n, true) 202 } 203 204 // visitReusingVisitor is factored out of Visit so that we may reuse visitor. 205 func (v *visitor) visitReusingVisitor(n *adt.Vertex, top bool) error { 206 if v.cfgDynamic { 207 if v.marked == nil { 208 v.marked = marked{} 209 } 210 v.marked.markExpr(n) 211 212 v.dynamic(n, top) 213 } else { 214 v.visit(n, top) 215 } 216 return v.err 217 } 218 219 func (v *visitor) visit(n *adt.Vertex, top bool) (err error) { 220 savedNode := v.node 221 savedTop := v.top 222 223 v.node = n 224 v.top = top 225 226 defer func() { 227 v.node = savedNode 228 v.top = savedTop 229 230 switch x := recover(); x { 231 case nil: 232 case aborted: 233 err = v.err 234 default: 235 panic(x) 236 } 237 }() 238 239 n.VisitLeafConjuncts(func(x adt.Conjunct) bool { 240 v.markExpr(x.Env, x.Elem()) 241 return true 242 }) 243 244 return nil 245 } 246 247 var aborted = errors.New("aborted") 248 249 type visitor struct { 250 ctxt *adt.OpContext 251 fn VisitFunc 252 node *adt.Vertex 253 err error 254 pkg *adt.ImportReference 255 256 // recurse indicates whether, during static analysis, to process references 257 // that will be unified into different fields. 258 recurse bool 259 // all indicates wether to process references that would be unified into 260 // different fields. This similar to recurse, but sometimes gets temporarily 261 // overridden to deal with special cases. 262 all bool 263 top bool 264 topRef adt.Resolver 265 pathStack []refEntry 266 numRefs int // count of reported dependencies 267 268 // cfgDynamic is kept from the original config. 269 cfgDynamic bool 270 271 marked marked 272 } 273 274 type refEntry struct { 275 env *adt.Environment 276 ref adt.Resolver 277 } 278 279 // TODO: factor out the below logic as either a low-level dependency analyzer or 280 // some walk functionality. 281 282 // markExpr visits all nodes in an expression to mark dependencies. 283 func (c *visitor) markExpr(env *adt.Environment, expr adt.Elem) { 284 if expr, ok := expr.(adt.Resolver); ok { 285 c.markResolver(env, expr) 286 return 287 } 288 289 saved := c.topRef 290 c.topRef = nil 291 defer func() { c.topRef = saved }() 292 293 switch x := expr.(type) { 294 case nil: 295 case *adt.BinaryExpr: 296 c.markExpr(env, x.X) 297 c.markExpr(env, x.Y) 298 299 case *adt.UnaryExpr: 300 c.markExpr(env, x.X) 301 302 case *adt.Interpolation: 303 for i := 1; i < len(x.Parts); i += 2 { 304 c.markExpr(env, x.Parts[i]) 305 } 306 307 case *adt.BoundExpr: 308 c.markExpr(env, x.Expr) 309 310 case *adt.CallExpr: 311 c.markExpr(env, x.Fun) 312 saved := c.all 313 c.all = true 314 for _, a := range x.Args { 315 c.markExpr(env, a) 316 } 317 c.all = saved 318 319 case *adt.DisjunctionExpr: 320 for _, d := range x.Values { 321 c.markExpr(env, d.Val) 322 } 323 324 case *adt.SliceExpr: 325 c.markExpr(env, x.X) 326 c.markExpr(env, x.Lo) 327 c.markExpr(env, x.Hi) 328 c.markExpr(env, x.Stride) 329 330 case *adt.ListLit: 331 env := &adt.Environment{Up: env, Vertex: empty} 332 for _, e := range x.Elems { 333 switch x := e.(type) { 334 case *adt.Comprehension: 335 c.markComprehension(env, x) 336 337 case adt.Expr: 338 c.markSubExpr(env, x) 339 340 case *adt.Ellipsis: 341 if x.Value != nil { 342 c.markSubExpr(env, x.Value) 343 } 344 } 345 } 346 347 case *adt.StructLit: 348 env := &adt.Environment{Up: env, Vertex: empty} 349 for _, e := range x.Decls { 350 c.markDecl(env, e) 351 } 352 353 case *adt.Comprehension: 354 c.markComprehension(env, x) 355 } 356 } 357 358 // markResolve resolves dependencies. 359 func (c *visitor) markResolver(env *adt.Environment, r adt.Resolver) { 360 // Note: it is okay to pass an empty CloseInfo{} here as we assume that 361 // all nodes are finalized already and we need neither closedness nor cycle 362 // checks. 363 ref, _ := c.ctxt.Resolve(adt.MakeConjunct(env, r, adt.CloseInfo{}), r) 364 365 // TODO: consider the case where an inlined composite literal does not 366 // resolve, but has references. For instance, {a: k, ref}.b would result 367 // in a failure during evaluation if b is not defined within ref. However, 368 // ref might still specialize to allow b. 369 370 if ref != nil { 371 c.reportDependency(env, r, ref) 372 return 373 } 374 375 // It is possible that a reference cannot be resolved because it is 376 // incomplete. In this case, we should check whether subexpressions of the 377 // reference can be resolved to mark those dependencies. For instance, 378 // prefix paths of selectors and the value or index of an index expression 379 // may independently resolve to a valid dependency. 380 381 switch x := r.(type) { 382 case *adt.NodeLink: 383 panic("unreachable") 384 385 case *adt.IndexExpr: 386 c.markExpr(env, x.X) 387 c.markExpr(env, x.Index) 388 389 case *adt.SelectorExpr: 390 c.markExpr(env, x.X) 391 } 392 } 393 394 // reportDependency reports a dependency from r to v. 395 // v must be the value that is obtained after resolving r. 396 func (c *visitor) reportDependency(env *adt.Environment, ref adt.Resolver, v *adt.Vertex) { 397 if v == c.node || v == empty { 398 return 399 } 400 401 reference := ref 402 if c.topRef == nil && len(c.pathStack) == 0 { 403 saved := c.topRef 404 c.topRef = ref 405 defer func() { c.topRef = saved }() 406 } 407 408 // TODO: in "All" mode we still report the latest reference used, instead 409 // of the reference at the start of the traversal, as the self-contained 410 // algorithm (its only user) depends on it. 411 // However, if the stack is non-nil, the reference will not correctly 412 // reflect the substituted value, so we use the top reference instead. 413 if !c.recurse && len(c.pathStack) == 0 && c.topRef != nil { 414 reference = c.topRef 415 } 416 417 inspect := false 418 419 if c.ctxt.Version == internal.DevVersion { 420 inspect = v.IsDetached() || !v.MayAttach() 421 } else { 422 inspect = !v.Rooted() 423 } 424 425 if inspect { 426 // TODO: there is currently no way to inspect where a non-rooted node 427 // originated from. As of EvalV3, we allow non-rooted nodes to be 428 // structure shared. This makes them effectively rooted, with the 429 // difference that there is an indirection in BaseValue for the 430 // structure sharing. Nonetheless, this information is lost in the 431 // internal API when traversing. 432 433 // As an alternative we now do not skip processing the node if we 434 // an inlined, non-rooted node is associated with another node than 435 // the one we are currently processing. 436 437 // If a node is internal, we need to further investigate any references. 438 // If there are any, reference, even if it is otherwise not reported, 439 // we report this reference. 440 before := c.numRefs 441 c.markInternalResolvers(env, ref, v) 442 // TODO: this logic could probably be simplified if we let clients 443 // explicitly mark whether to visit rootless nodes. Visiting these 444 // may be necessary when substituting values. 445 switch _, ok := ref.(*adt.FieldReference); { 446 case !ok && c.isLocal(env, ref): 447 // Do not report rootless nodes for selectors. 448 return 449 case c.numRefs > before: 450 // For FieldReferences that resolve to something we do not need 451 // to report anything intermediate. 452 return 453 } 454 } 455 if hasLetParent(v) { 456 return 457 } 458 459 // Expand path. 460 altRef := reference 461 for i := len(c.pathStack) - 1; i >= 0; i-- { 462 x := c.pathStack[i] 463 var w *adt.Vertex 464 // TODO: instead of setting the reference, the proper thing to do is 465 // to record a path that still needs to be selected into the recorded 466 // dependency. See the Target Path definition at the top of the file. 467 if f := c.feature(x.env, x.ref); f != 0 { 468 w = v.Lookup(f) 469 } 470 if w == nil { 471 break 472 } 473 altRef = x.ref 474 if i == 0 && c.topRef != nil { 475 altRef = c.topRef 476 } 477 v = w 478 } 479 if inspect && len(c.pathStack) == 0 && c.topRef != nil { 480 altRef = c.topRef 481 } 482 483 // All resolvers are expressions. 484 if p := importRef(ref.(adt.Expr)); p != nil { 485 savedPkg := c.pkg 486 c.pkg = p 487 defer func() { c.pkg = savedPkg }() 488 } 489 490 c.numRefs++ 491 492 if c.ctxt.Version == internal.DevVersion { 493 v.Finalize(c.ctxt) 494 } 495 496 d := Dependency{ 497 Node: v, 498 Reference: altRef, 499 pkg: c.pkg, 500 top: c.top, 501 visitor: c, 502 } 503 if err := c.fn(d); err != nil { 504 c.err = err 505 panic(aborted) 506 } 507 } 508 509 // isLocal reports whether a non-rooted struct is an internal node or not. 510 // If it is not, we need to further investigate any references. 511 func (c *visitor) isLocal(env *adt.Environment, r adt.Resolver) bool { 512 for { 513 switch x := r.(type) { 514 case *adt.FieldReference: 515 for i := 0; i < int(x.UpCount); i++ { 516 env = env.Up 517 } 518 return env.Vertex == empty 519 case *adt.SelectorExpr: 520 r, _ = x.X.(adt.Resolver) 521 case *adt.IndexExpr: 522 r, _ = x.X.(adt.Resolver) 523 default: 524 return env.Vertex == empty 525 } 526 } 527 } 528 529 // TODO(perf): make this available as a property of vertices to avoid doing 530 // work repeatedly. 531 func hasLetParent(v *adt.Vertex) bool { 532 for ; v != nil; v = v.Parent { 533 if v.Label.IsLet() { 534 return true 535 } 536 } 537 return false 538 } 539 540 // markConjuncts transitively marks all reference of the current node. 541 func (c *visitor) markConjuncts(v *adt.Vertex) { 542 v.VisitLeafConjuncts(func(x adt.Conjunct) bool { 543 // Use Elem instead of Expr to preserve the Comprehension to, in turn, 544 // ensure an Environment is inserted for the Value clause. 545 c.markExpr(x.Env, x.Elem()) 546 return true 547 }) 548 } 549 550 // markInternalResolvers marks dependencies for rootless nodes. As these 551 // nodes may not be visited during normal traversal, we need to be more 552 // proactive. For selectors and indices this means we need to evaluate their 553 // objects to see exactly what the selector or index refers to. 554 func (c *visitor) markInternalResolvers(env *adt.Environment, r adt.Resolver, v *adt.Vertex) { 555 saved := c.all // recursive traversal already done by this function. 556 557 // As lets have no path and we otherwise will not process them, we set 558 // processing all to true. 559 if c.marked != nil && hasLetParent(v) { 560 v.VisitLeafConjuncts(func(x adt.Conjunct) bool { 561 c.marked.markExpr(x.Expr()) 562 return true 563 }) 564 } 565 566 c.markConjuncts(v) 567 568 // evaluateInner will already process all values recursively, so disable 569 // while processing in this case. 570 c.all = false 571 572 switch r := r.(type) { 573 case *adt.SelectorExpr: 574 c.evaluateInner(env, r.X, r) 575 case *adt.IndexExpr: 576 c.evaluateInner(env, r.X, r) 577 } 578 579 c.all = saved 580 } 581 582 // evaluateInner evaluates the LHS of the given selector or index expression, 583 // and marks all its conjuncts. The reference is pushed on a stack to mark 584 // the field or index that needs to be selected for any dependencies that are 585 // subsequently encountered. This is handled by reportDependency. 586 func (c *visitor) evaluateInner(env *adt.Environment, x adt.Expr, r adt.Resolver) { 587 value, _ := c.ctxt.Evaluate(env, x) 588 v, _ := value.(*adt.Vertex) 589 if v == nil { 590 return 591 } 592 // TODO(perf): one level of evaluation would suffice. 593 v.Finalize(c.ctxt) 594 595 saved := len(c.pathStack) 596 c.pathStack = append(c.pathStack, refEntry{env, r}) 597 c.markConjuncts(v) 598 c.pathStack = c.pathStack[:saved] 599 } 600 601 func (c *visitor) feature(env *adt.Environment, r adt.Resolver) adt.Feature { 602 switch r := r.(type) { 603 case *adt.SelectorExpr: 604 return r.Sel 605 case *adt.IndexExpr: 606 v, _ := c.ctxt.Evaluate(env, r.Index) 607 v = adt.Unwrap(v) 608 return adt.LabelFromValue(c.ctxt, r.Index, v) 609 default: 610 return adt.InvalidLabel 611 } 612 } 613 614 func (c *visitor) markSubExpr(env *adt.Environment, x adt.Expr) { 615 if c.all { 616 saved := c.top 617 c.top = false 618 c.markExpr(env, x) 619 c.top = saved 620 } 621 } 622 623 func (c *visitor) markDecl(env *adt.Environment, d adt.Decl) { 624 switch x := d.(type) { 625 case *adt.Field: 626 c.markSubExpr(env, x.Value) 627 628 case *adt.BulkOptionalField: 629 c.markExpr(env, x.Filter) 630 // when dynamic, only continue if there is evidence of 631 // the field in the parallel actual evaluation. 632 c.markSubExpr(env, x.Value) 633 634 case *adt.DynamicField: 635 c.markExpr(env, x.Key) 636 // when dynamic, only continue if there is evidence of 637 // a matching field in the parallel actual evaluation. 638 c.markSubExpr(env, x.Value) 639 640 case *adt.Comprehension: 641 c.markComprehension(env, x) 642 643 case adt.Expr: 644 c.markExpr(env, x) 645 646 case *adt.Ellipsis: 647 if x.Value != nil { 648 c.markSubExpr(env, x.Value) 649 } 650 } 651 } 652 653 func (c *visitor) markComprehension(env *adt.Environment, y *adt.Comprehension) { 654 env = c.markClauses(env, y.Clauses) 655 656 // Use "live" environments if we have them. This is important if 657 // dependencies are computed on a partially evaluated value where a pushed 658 // down comprehension is defined outside the root of the dependency 659 // analysis. For instance, when analyzing dependencies at path a.b in: 660 // 661 // a: { 662 // for value in { test: 1 } { 663 // b: bar: value 664 // } 665 // } 666 // 667 if envs := y.Envs(); len(envs) > 0 { 668 // We use the Environment to get access to the parent chain. It 669 // suffices to take any Environment (in this case the first), as all 670 // will have the same parent chain. 671 env = envs[0] 672 } 673 for i := y.Nest(); i > 0; i-- { 674 env = &adt.Environment{Up: env, Vertex: empty} 675 } 676 // TODO: consider using adt.EnvExpr and remove the above loop. 677 c.markExpr(env, adt.ToExpr(y.Value)) 678 } 679 680 func (c *visitor) markClauses(env *adt.Environment, a []adt.Yielder) *adt.Environment { 681 for _, y := range a { 682 switch x := y.(type) { 683 case *adt.ForClause: 684 c.markExpr(env, x.Src) 685 env = &adt.Environment{Up: env, Vertex: empty} 686 // In dynamic mode, iterate over all actual value and 687 // evaluate. 688 689 case *adt.LetClause: 690 c.markExpr(env, x.Expr) 691 env = &adt.Environment{Up: env, Vertex: empty} 692 693 case *adt.IfClause: 694 c.markExpr(env, x.Condition) 695 // In dynamic mode, only continue if condition is true. 696 697 case *adt.ValueClause: 698 env = &adt.Environment{Up: env, Vertex: empty} 699 } 700 } 701 return env 702 }