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