Files
releases/v1/ast/compile.go
T
Anders Eknert afb30d3f9d Add gocritic linter, fix a bunch of stuff (#7377)
Brace yourselves! For there are many touched files here. No changes
in semantics however.

Spent a long time trying out the various optional rules gocritic
provides, and settled for a few of them. There are more I really
like, but that would take many hours to address across the codebase.

Perhaps others find gocritic too pedantic? If so, we can merge the
fixes without enabling the rule.

Signed-off-by: Anders Eknert <anders@styra.com>
2025-02-24 16:28:41 +01:00

5980 lines
165 KiB
Go

// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package ast
import (
"errors"
"fmt"
"io"
"maps"
"slices"
"sort"
"strconv"
"strings"
"github.com/open-policy-agent/opa/internal/debug"
"github.com/open-policy-agent/opa/internal/gojsonschema"
"github.com/open-policy-agent/opa/v1/ast/location"
"github.com/open-policy-agent/opa/v1/metrics"
"github.com/open-policy-agent/opa/v1/types"
"github.com/open-policy-agent/opa/v1/util"
)
// CompileErrorLimitDefault is the default number errors a compiler will allow before
// exiting.
const CompileErrorLimitDefault = 10
var errLimitReached = NewError(CompileErr, nil, "error limit reached")
// Compiler contains the state of a compilation process.
type Compiler struct {
// Errors contains errors that occurred during the compilation process.
// If there are one or more errors, the compilation process is considered
// "failed".
Errors Errors
// Modules contains the compiled modules. The compiled modules are the
// output of the compilation process. If the compilation process failed,
// there is no guarantee about the state of the modules.
Modules map[string]*Module
// ModuleTree organizes the modules into a tree where each node is keyed by
// an element in the module's package path. E.g., given modules containing
// the following package directives: "a", "a.b", "a.c", and "a.b", the
// resulting module tree would be:
//
// root
// |
// +--- data (no modules)
// |
// +--- a (1 module)
// |
// +--- b (2 modules)
// |
// +--- c (1 module)
//
ModuleTree *ModuleTreeNode
// RuleTree organizes rules into a tree where each node is keyed by an
// element in the rule's path. The rule path is the concatenation of the
// containing package and the stringified rule name. E.g., given the
// following module:
//
// package ex
// p[1] { true }
// p[2] { true }
// q = true
// a.b.c = 3
//
// root
// |
// +--- data (no rules)
// |
// +--- ex (no rules)
// |
// +--- p (2 rules)
// |
// +--- q (1 rule)
// |
// +--- a
// |
// +--- b
// |
// +--- c (1 rule)
//
// Another example with general refs containing vars at arbitrary locations:
//
// package ex
// a.b[x].d { x := "c" } # R1
// a.b.c[x] { x := "d" } # R2
// a.b[x][y] { x := "c"; y := "d" } # R3
// p := true # R4
//
// root
// |
// +--- data (no rules)
// |
// +--- ex (no rules)
// |
// +--- a
// | |
// | +--- b (R1, R3)
// | |
// | +--- c (R2)
// |
// +--- p (R4)
RuleTree *TreeNode
// Graph contains dependencies between rules. An edge (u,v) is added to the
// graph if rule 'u' refers to the virtual document defined by 'v'.
Graph *Graph
// TypeEnv holds type information for values inferred by the compiler.
TypeEnv *TypeEnv
// RewrittenVars is a mapping of variables that have been rewritten
// with the key being the generated name and value being the original.
RewrittenVars map[Var]Var
// Capabilities required by the modules that were compiled.
Required *Capabilities
localvargen *localVarGenerator
moduleLoader ModuleLoader
ruleIndices *util.HasherMap[Ref, RuleIndex]
stages []stage
maxErrs int
sorted []string // list of sorted module names
pathExists func([]string) (bool, error)
pathConflictCheckRoots []string
after map[string][]CompilerStageDefinition
metrics metrics.Metrics
capabilities *Capabilities // user-supplied capabilities
imports map[string][]*Import // saved imports from stripping
builtins map[string]*Builtin // universe of built-in functions
customBuiltins map[string]*Builtin // user-supplied custom built-in functions (deprecated: use capabilities)
unsafeBuiltinsMap map[string]struct{} // user-supplied set of unsafe built-ins functions to block (deprecated: use capabilities)
deprecatedBuiltinsMap map[string]struct{} // set of deprecated, but not removed, built-in functions
enablePrintStatements bool // indicates if print statements should be elided (default)
comprehensionIndices map[*Term]*ComprehensionIndex // comprehension key index
initialized bool // indicates if init() has been called
debug debug.Debug // emits debug information produced during compilation
schemaSet *SchemaSet // user-supplied schemas for input and data documents
inputType types.Type // global input type retrieved from schema set
annotationSet *AnnotationSet // hierarchical set of annotations
strict bool // enforce strict compilation checks
keepModules bool // whether to keep the unprocessed, parse modules (below)
parsedModules map[string]*Module // parsed, but otherwise unprocessed modules, kept track of when keepModules is true
useTypeCheckAnnotations bool // whether to provide annotated information (schemas) to the type checker
allowUndefinedFuncCalls bool // don't error on calls to unknown functions.
evalMode CompilerEvalMode //
rewriteTestRulesForTracing bool // rewrite test rules to capture dynamic values for tracing.
defaultRegoVersion RegoVersion
}
func (c *Compiler) DefaultRegoVersion() RegoVersion {
return c.defaultRegoVersion
}
// CompilerStage defines the interface for stages in the compiler.
type CompilerStage func(*Compiler) *Error
// CompilerEvalMode allows toggling certain stages that are only
// needed for certain modes, Concretely, only "topdown" mode will
// have the compiler build comprehension and rule indices.
type CompilerEvalMode int
const (
// EvalModeTopdown (default) instructs the compiler to build rule
// and comprehension indices used by topdown evaluation.
EvalModeTopdown CompilerEvalMode = iota
// EvalModeIR makes the compiler skip the stages for comprehension
// and rule indices.
EvalModeIR
)
// CompilerStageDefinition defines a compiler stage
type CompilerStageDefinition struct {
Name string
MetricName string
Stage CompilerStage
}
// RulesOptions defines the options for retrieving rules by Ref from the
// compiler.
type RulesOptions struct {
// IncludeHiddenModules determines if the result contains hidden modules,
// currently only the "system" namespace, i.e. "data.system.*".
IncludeHiddenModules bool
}
// QueryContext contains contextual information for running an ad-hoc query.
//
// Ad-hoc queries can be run in the context of a package and imports may be
// included to provide concise access to data.
type QueryContext struct {
Package *Package
Imports []*Import
}
// NewQueryContext returns a new QueryContext object.
func NewQueryContext() *QueryContext {
return &QueryContext{}
}
// WithPackage sets the pkg on qc.
func (qc *QueryContext) WithPackage(pkg *Package) *QueryContext {
if qc == nil {
qc = NewQueryContext()
}
qc.Package = pkg
return qc
}
// WithImports sets the imports on qc.
func (qc *QueryContext) WithImports(imports []*Import) *QueryContext {
if qc == nil {
qc = NewQueryContext()
}
qc.Imports = imports
return qc
}
// Copy returns a deep copy of qc.
func (qc *QueryContext) Copy() *QueryContext {
if qc == nil {
return nil
}
cpy := *qc
if cpy.Package != nil {
cpy.Package = qc.Package.Copy()
}
cpy.Imports = make([]*Import, len(qc.Imports))
for i := range qc.Imports {
cpy.Imports[i] = qc.Imports[i].Copy()
}
return &cpy
}
// QueryCompiler defines the interface for compiling ad-hoc queries.
type QueryCompiler interface {
// Compile should be called to compile ad-hoc queries. The return value is
// the compiled version of the query.
Compile(q Body) (Body, error)
// TypeEnv returns the type environment built after running type checking
// on the query.
TypeEnv() *TypeEnv
// WithContext sets the QueryContext on the QueryCompiler. Subsequent calls
// to Compile will take the QueryContext into account.
WithContext(qctx *QueryContext) QueryCompiler
// WithEnablePrintStatements enables print statements in queries compiled
// with the QueryCompiler.
WithEnablePrintStatements(yes bool) QueryCompiler
// WithUnsafeBuiltins sets the built-in functions to treat as unsafe and not
// allow inside of queries. By default the query compiler inherits the
// compiler's unsafe built-in functions. This function allows callers to
// override that set. If an empty (non-nil) map is provided, all built-ins
// are allowed.
WithUnsafeBuiltins(unsafe map[string]struct{}) QueryCompiler
// WithStageAfter registers a stage to run during query compilation after
// the named stage.
WithStageAfter(after string, stage QueryCompilerStageDefinition) QueryCompiler
// RewrittenVars maps generated vars in the compiled query to vars from the
// parsed query. For example, given the query "input := 1" the rewritten
// query would be "__local0__ = 1". The mapping would then be {__local0__: input}.
RewrittenVars() map[Var]Var
// ComprehensionIndex returns an index data structure for the given comprehension
// term. If no index is found, returns nil.
ComprehensionIndex(term *Term) *ComprehensionIndex
// WithStrict enables strict mode for the query compiler.
WithStrict(strict bool) QueryCompiler
}
// QueryCompilerStage defines the interface for stages in the query compiler.
type QueryCompilerStage func(QueryCompiler, Body) (Body, error)
// QueryCompilerStageDefinition defines a QueryCompiler stage
type QueryCompilerStageDefinition struct {
Name string
MetricName string
Stage QueryCompilerStage
}
type stage struct {
name string
metricName string
f func()
}
// NewCompiler returns a new empty compiler.
func NewCompiler() *Compiler {
c := &Compiler{
Modules: map[string]*Module{},
RewrittenVars: map[Var]Var{},
Required: &Capabilities{},
ruleIndices: util.NewHasherMap[Ref, RuleIndex](RefEqual),
maxErrs: CompileErrorLimitDefault,
after: map[string][]CompilerStageDefinition{},
unsafeBuiltinsMap: map[string]struct{}{},
deprecatedBuiltinsMap: map[string]struct{}{},
comprehensionIndices: map[*Term]*ComprehensionIndex{},
debug: debug.Discard(),
defaultRegoVersion: DefaultRegoVersion,
}
c.ModuleTree = NewModuleTree(nil)
c.RuleTree = NewRuleTree(c.ModuleTree)
c.stages = []stage{
// Reference resolution should run first as it may be used to lazily
// load additional modules. If any stages run before resolution, they
// need to be re-run after resolution.
{"ResolveRefs", "compile_stage_resolve_refs", c.resolveAllRefs},
// The local variable generator must be initialized after references are
// resolved and the dynamic module loader has run but before subsequent
// stages that need to generate variables.
{"InitLocalVarGen", "compile_stage_init_local_var_gen", c.initLocalVarGen},
{"RewriteRuleHeadRefs", "compile_stage_rewrite_rule_head_refs", c.rewriteRuleHeadRefs},
{"CheckKeywordOverrides", "compile_stage_check_keyword_overrides", c.checkKeywordOverrides},
{"CheckDuplicateImports", "compile_stage_check_imports", c.checkImports},
{"RemoveImports", "compile_stage_remove_imports", c.removeImports},
{"SetModuleTree", "compile_stage_set_module_tree", c.setModuleTree},
{"SetRuleTree", "compile_stage_set_rule_tree", c.setRuleTree}, // depends on RewriteRuleHeadRefs
{"RewriteLocalVars", "compile_stage_rewrite_local_vars", c.rewriteLocalVars},
{"CheckVoidCalls", "compile_stage_check_void_calls", c.checkVoidCalls},
{"RewritePrintCalls", "compile_stage_rewrite_print_calls", c.rewritePrintCalls},
{"RewriteExprTerms", "compile_stage_rewrite_expr_terms", c.rewriteExprTerms},
{"ParseMetadataBlocks", "compile_stage_parse_metadata_blocks", c.parseMetadataBlocks},
{"SetAnnotationSet", "compile_stage_set_annotationset", c.setAnnotationSet},
{"RewriteRegoMetadataCalls", "compile_stage_rewrite_rego_metadata_calls", c.rewriteRegoMetadataCalls},
{"SetGraph", "compile_stage_set_graph", c.setGraph},
{"RewriteComprehensionTerms", "compile_stage_rewrite_comprehension_terms", c.rewriteComprehensionTerms},
{"RewriteRefsInHead", "compile_stage_rewrite_refs_in_head", c.rewriteRefsInHead},
{"RewriteWithValues", "compile_stage_rewrite_with_values", c.rewriteWithModifiers},
{"CheckRuleConflicts", "compile_stage_check_rule_conflicts", c.checkRuleConflicts},
{"CheckUndefinedFuncs", "compile_stage_check_undefined_funcs", c.checkUndefinedFuncs},
{"CheckSafetyRuleHeads", "compile_stage_check_safety_rule_heads", c.checkSafetyRuleHeads},
{"CheckSafetyRuleBodies", "compile_stage_check_safety_rule_bodies", c.checkSafetyRuleBodies},
{"RewriteEquals", "compile_stage_rewrite_equals", c.rewriteEquals},
{"RewriteDynamicTerms", "compile_stage_rewrite_dynamic_terms", c.rewriteDynamicTerms},
{"RewriteTestRulesForTracing", "compile_stage_rewrite_test_rules_for_tracing", c.rewriteTestRuleEqualities}, // must run after RewriteDynamicTerms
{"CheckRecursion", "compile_stage_check_recursion", c.checkRecursion},
{"CheckTypes", "compile_stage_check_types", c.checkTypes}, // must be run after CheckRecursion
{"CheckUnsafeBuiltins", "compile_state_check_unsafe_builtins", c.checkUnsafeBuiltins},
{"CheckDeprecatedBuiltins", "compile_state_check_deprecated_builtins", c.checkDeprecatedBuiltins},
{"BuildRuleIndices", "compile_stage_rebuild_indices", c.buildRuleIndices},
{"BuildComprehensionIndices", "compile_stage_rebuild_comprehension_indices", c.buildComprehensionIndices},
{"BuildRequiredCapabilities", "compile_stage_build_required_capabilities", c.buildRequiredCapabilities},
}
return c
}
// SetErrorLimit sets the number of errors the compiler can encounter before it
// quits. Zero or a negative number indicates no limit.
func (c *Compiler) SetErrorLimit(limit int) *Compiler {
c.maxErrs = limit
return c
}
// WithEnablePrintStatements enables print statements inside of modules compiled
// by the compiler. If print statements are not enabled, calls to print() are
// erased at compile-time.
func (c *Compiler) WithEnablePrintStatements(yes bool) *Compiler {
c.enablePrintStatements = yes
return c
}
// WithPathConflictsCheck enables base-virtual document conflict
// detection. The compiler will check that rules don't overlap with
// paths that exist as determined by the provided callable.
func (c *Compiler) WithPathConflictsCheck(fn func([]string) (bool, error)) *Compiler {
c.pathExists = fn
return c
}
// WithPathConflictsCheckRoots enables checking path conflicts from the specified root instead
// of the top root node. Limiting conflict checks to a known set of roots, such as bundle roots,
// improves performance. Each root has the format of a "/"-delimited string, excluding the "data"
// root document.
func (c *Compiler) WithPathConflictsCheckRoots(rootPaths []string) *Compiler {
c.pathConflictCheckRoots = rootPaths
return c
}
// WithStageAfter registers a stage to run during compilation after
// the named stage.
func (c *Compiler) WithStageAfter(after string, stage CompilerStageDefinition) *Compiler {
c.after[after] = append(c.after[after], stage)
return c
}
// WithMetrics will set a metrics.Metrics and be used for profiling
// the Compiler instance.
func (c *Compiler) WithMetrics(metrics metrics.Metrics) *Compiler {
c.metrics = metrics
return c
}
// WithCapabilities sets capabilities to enable during compilation. Capabilities allow the caller
// to specify the set of built-in functions available to the policy. In the future, capabilities
// may be able to restrict access to other language features. Capabilities allow callers to check
// if policies are compatible with a particular version of OPA. If policies are a compiled for a
// specific version of OPA, there is no guarantee that _this_ version of OPA can evaluate them
// successfully.
func (c *Compiler) WithCapabilities(capabilities *Capabilities) *Compiler {
c.capabilities = capabilities
return c
}
// Capabilities returns the capabilities enabled during compilation.
func (c *Compiler) Capabilities() *Capabilities {
return c.capabilities
}
// WithDebug sets where debug messages are written to. Passing `nil` has no
// effect.
func (c *Compiler) WithDebug(sink io.Writer) *Compiler {
if sink != nil {
c.debug = debug.New(sink)
}
return c
}
// WithBuiltins is deprecated.
// Deprecated: Use WithCapabilities instead.
func (c *Compiler) WithBuiltins(builtins map[string]*Builtin) *Compiler {
c.customBuiltins = maps.Clone(builtins)
return c
}
// WithUnsafeBuiltins is deprecated.
// Deprecated: Use WithCapabilities instead.
func (c *Compiler) WithUnsafeBuiltins(unsafeBuiltins map[string]struct{}) *Compiler {
maps.Copy(c.unsafeBuiltinsMap, unsafeBuiltins)
return c
}
// WithStrict toggles strict mode in the compiler.
func (c *Compiler) WithStrict(strict bool) *Compiler {
c.strict = strict
return c
}
// WithKeepModules enables retaining unprocessed modules in the compiler.
// Note that the modules aren't copied on the way in or out -- so when
// accessing them via ParsedModules(), mutations will occur in the module
// map that was passed into Compile().`
func (c *Compiler) WithKeepModules(y bool) *Compiler {
c.keepModules = y
return c
}
// WithUseTypeCheckAnnotations use schema annotations during type checking
func (c *Compiler) WithUseTypeCheckAnnotations(enabled bool) *Compiler {
c.useTypeCheckAnnotations = enabled
return c
}
func (c *Compiler) WithAllowUndefinedFunctionCalls(allow bool) *Compiler {
c.allowUndefinedFuncCalls = allow
return c
}
// WithEvalMode allows setting the CompilerEvalMode of the compiler
func (c *Compiler) WithEvalMode(e CompilerEvalMode) *Compiler {
c.evalMode = e
return c
}
// WithRewriteTestRules enables rewriting test rules to capture dynamic values in local variables,
// so they can be accessed by tracing.
func (c *Compiler) WithRewriteTestRules(rewrite bool) *Compiler {
c.rewriteTestRulesForTracing = rewrite
return c
}
// ParsedModules returns the parsed, unprocessed modules from the compiler.
// It is `nil` if keeping modules wasn't enabled via `WithKeepModules(true)`.
// The map includes all modules loaded via the ModuleLoader, if one was used.
func (c *Compiler) ParsedModules() map[string]*Module {
return c.parsedModules
}
func (c *Compiler) QueryCompiler() QueryCompiler {
c.init()
c0 := *c
return newQueryCompiler(&c0)
}
// Compile runs the compilation process on the input modules. The compiled
// version of the modules and associated data structures are stored on the
// compiler. If the compilation process fails for any reason, the compiler will
// contain a slice of errors.
func (c *Compiler) Compile(modules map[string]*Module) {
c.init()
c.Modules = make(map[string]*Module, len(modules))
c.sorted = make([]string, 0, len(modules))
if c.keepModules {
c.parsedModules = make(map[string]*Module, len(modules))
} else {
c.parsedModules = nil
}
for k, v := range modules {
c.Modules[k] = v.Copy()
c.sorted = append(c.sorted, k)
if c.parsedModules != nil {
c.parsedModules[k] = v
}
}
sort.Strings(c.sorted)
c.compile()
}
// WithSchemas sets a schemaSet to the compiler
func (c *Compiler) WithSchemas(schemas *SchemaSet) *Compiler {
c.schemaSet = schemas
return c
}
// Failed returns true if a compilation error has been encountered.
func (c *Compiler) Failed() bool {
return len(c.Errors) > 0
}
// ComprehensionIndex returns a data structure specifying how to index comprehension
// results so that callers do not have to recompute the comprehension more than once.
// If no index is found, returns nil.
func (c *Compiler) ComprehensionIndex(term *Term) *ComprehensionIndex {
return c.comprehensionIndices[term]
}
// GetArity returns the number of args a function referred to by ref takes. If
// ref refers to built-in function, the built-in declaration is consulted,
// otherwise, the ref is used to perform a ruleset lookup.
func (c *Compiler) GetArity(ref Ref) int {
if bi := c.builtins[ref.String()]; bi != nil {
return bi.Decl.Arity()
}
rules := c.GetRulesExact(ref)
if len(rules) == 0 {
return -1
}
return len(rules[0].Head.Args)
}
// GetRulesExact returns a slice of rules referred to by the reference.
//
// E.g., given the following module:
//
// package a.b.c
//
// p[k] = v { ... } # rule1
// p[k1] = v1 { ... } # rule2
//
// The following calls yield the rules on the right.
//
// GetRulesExact("data.a.b.c.p") => [rule1, rule2]
// GetRulesExact("data.a.b.c.p.x") => nil
// GetRulesExact("data.a.b.c") => nil
func (c *Compiler) GetRulesExact(ref Ref) (rules []*Rule) {
node := c.RuleTree
for _, x := range ref {
if node = node.Child(x.Value); node == nil {
return nil
}
}
return extractRules(node.Values)
}
// GetRulesForVirtualDocument returns a slice of rules that produce the virtual
// document referred to by the reference.
//
// E.g., given the following module:
//
// package a.b.c
//
// p[k] = v { ... } # rule1
// p[k1] = v1 { ... } # rule2
//
// The following calls yield the rules on the right.
//
// GetRulesForVirtualDocument("data.a.b.c.p") => [rule1, rule2]
// GetRulesForVirtualDocument("data.a.b.c.p.x") => [rule1, rule2]
// GetRulesForVirtualDocument("data.a.b.c") => nil
func (c *Compiler) GetRulesForVirtualDocument(ref Ref) (rules []*Rule) {
node := c.RuleTree
for _, x := range ref {
if node = node.Child(x.Value); node == nil {
return nil
}
if len(node.Values) > 0 {
return extractRules(node.Values)
}
}
return extractRules(node.Values)
}
// GetRulesWithPrefix returns a slice of rules that share the prefix ref.
//
// E.g., given the following module:
//
// package a.b.c
//
// p[x] = y { ... } # rule1
// p[k] = v { ... } # rule2
// q { ... } # rule3
//
// The following calls yield the rules on the right.
//
// GetRulesWithPrefix("data.a.b.c.p") => [rule1, rule2]
// GetRulesWithPrefix("data.a.b.c.p.a") => nil
// GetRulesWithPrefix("data.a.b.c") => [rule1, rule2, rule3]
func (c *Compiler) GetRulesWithPrefix(ref Ref) (rules []*Rule) {
node := c.RuleTree
for _, x := range ref {
if node = node.Child(x.Value); node == nil {
return nil
}
}
var acc func(node *TreeNode)
acc = func(node *TreeNode) {
rules = append(rules, extractRules(node.Values)...)
for _, child := range node.Children {
if child.Hide {
continue
}
acc(child)
}
}
acc(node)
return rules
}
func extractRules(s []any) []*Rule {
rules := make([]*Rule, len(s))
for i := range s {
rules[i] = s[i].(*Rule)
}
return rules
}
// GetRules returns a slice of rules that are referred to by ref.
//
// E.g., given the following module:
//
// package a.b.c
//
// p[x] = y { q[x] = y; ... } # rule1
// q[x] = y { ... } # rule2
//
// The following calls yield the rules on the right.
//
// GetRules("data.a.b.c.p") => [rule1]
// GetRules("data.a.b.c.p.x") => [rule1]
// GetRules("data.a.b.c.q") => [rule2]
// GetRules("data.a.b.c") => [rule1, rule2]
// GetRules("data.a.b.d") => nil
func (c *Compiler) GetRules(ref Ref) (rules []*Rule) {
set := map[*Rule]struct{}{}
for _, rule := range c.GetRulesForVirtualDocument(ref) {
set[rule] = struct{}{}
}
for _, rule := range c.GetRulesWithPrefix(ref) {
set[rule] = struct{}{}
}
for rule := range set {
rules = append(rules, rule)
}
return rules
}
// GetRulesDynamic returns a slice of rules that could be referred to by a ref.
//
// Deprecated: use GetRulesDynamicWithOpts
func (c *Compiler) GetRulesDynamic(ref Ref) []*Rule {
return c.GetRulesDynamicWithOpts(ref, RulesOptions{})
}
// GetRulesDynamicWithOpts returns a slice of rules that could be referred to by
// a ref.
// When parts of the ref are statically known, we use that information to narrow
// down which rules the ref could refer to, but in the most general case this
// will be an over-approximation.
//
// E.g., given the following modules:
//
// package a.b.c
//
// r1 = 1 # rule1
//
// and:
//
// package a.d.c
//
// r2 = 2 # rule2
//
// The following calls yield the rules on the right.
//
// GetRulesDynamicWithOpts("data.a[x].c[y]", opts) => [rule1, rule2]
// GetRulesDynamicWithOpts("data.a[x].c.r2", opts) => [rule2]
// GetRulesDynamicWithOpts("data.a.b[x][y]", opts) => [rule1]
//
// Using the RulesOptions parameter, the inclusion of hidden modules can be
// controlled:
//
// With
//
// package system.main
//
// r3 = 3 # rule3
//
// We'd get this result:
//
// GetRulesDynamicWithOpts("data[x]", RulesOptions{IncludeHiddenModules: true}) => [rule1, rule2, rule3]
//
// Without the options, it would be excluded.
func (c *Compiler) GetRulesDynamicWithOpts(ref Ref, opts RulesOptions) []*Rule {
node := c.RuleTree
set := map[*Rule]struct{}{}
var walk func(node *TreeNode, i int)
walk = func(node *TreeNode, i int) {
switch {
case i >= len(ref):
// We've reached the end of the reference and want to collect everything
// under this "prefix".
node.DepthFirst(func(descendant *TreeNode) bool {
insertRules(set, descendant.Values)
if opts.IncludeHiddenModules {
return false
}
return descendant.Hide
})
case i == 0 || IsConstant(ref[i].Value):
// The head of the ref is always grounded. In case another part of the
// ref is also grounded, we can lookup the exact child. If it's not found
// we can immediately return...
if child := node.Child(ref[i].Value); child != nil {
if len(child.Values) > 0 {
// Add any rules at this position
insertRules(set, child.Values)
}
// There might still be "sub-rules" contributing key-value "overrides" for e.g. partial object rules, continue walking
walk(child, i+1)
} else {
return
}
default:
// This part of the ref is a dynamic term. We can't know what it refers
// to and will just need to try all of the children.
for _, child := range node.Children {
if child.Hide && !opts.IncludeHiddenModules {
continue
}
insertRules(set, child.Values)
walk(child, i+1)
}
}
}
walk(node, 0)
rules := make([]*Rule, 0, len(set))
for rule := range set {
rules = append(rules, rule)
}
return rules
}
// Utility: add all rule values to the set.
func insertRules(set map[*Rule]struct{}, rules []any) {
for _, rule := range rules {
set[rule.(*Rule)] = struct{}{}
}
}
// RuleIndex returns a RuleIndex built for the rule set referred to by path.
// The path must refer to the rule set exactly, i.e., given a rule set at path
// data.a.b.c.p, refs data.a.b.c.p.x and data.a.b.c would not return a
// RuleIndex built for the rule.
func (c *Compiler) RuleIndex(path Ref) RuleIndex {
r, ok := c.ruleIndices.Get(path)
if !ok {
return nil
}
return r
}
// PassesTypeCheck determines whether the given body passes type checking
func (c *Compiler) PassesTypeCheck(body Body) bool {
checker := newTypeChecker().WithSchemaSet(c.schemaSet).WithInputType(c.inputType)
env := c.TypeEnv
_, errs := checker.CheckBody(env, body)
return len(errs) == 0
}
// PassesTypeCheckRules determines whether the given rules passes type checking
func (c *Compiler) PassesTypeCheckRules(rules []*Rule) Errors {
elems := []util.T{}
for _, rule := range rules {
elems = append(elems, rule)
}
// Load the global input schema if one was provided.
if c.schemaSet != nil {
if schema := c.schemaSet.Get(SchemaRootRef); schema != nil {
var allowNet []string
if c.capabilities != nil {
allowNet = c.capabilities.AllowNet
}
tpe, err := loadSchema(schema, allowNet)
if err != nil {
return Errors{NewError(TypeErr, nil, err.Error())} //nolint:govet
}
c.inputType = tpe
}
}
var as *AnnotationSet
if c.useTypeCheckAnnotations {
as = c.annotationSet
}
checker := newTypeChecker().WithSchemaSet(c.schemaSet).WithInputType(c.inputType)
if c.TypeEnv == nil {
if c.capabilities == nil {
c.capabilities = CapabilitiesForThisVersion()
}
c.builtins = make(map[string]*Builtin, len(c.capabilities.Builtins)+len(c.customBuiltins))
for _, bi := range c.capabilities.Builtins {
c.builtins[bi.Name] = bi
}
for name, bi := range c.customBuiltins {
c.builtins[name] = bi
}
c.TypeEnv = checker.Env(c.builtins)
}
_, errs := checker.CheckTypes(c.TypeEnv, elems, as)
return errs
}
// ModuleLoader defines the interface that callers can implement to enable lazy
// loading of modules during compilation.
type ModuleLoader func(resolved map[string]*Module) (parsed map[string]*Module, err error)
// WithModuleLoader sets f as the ModuleLoader on the compiler.
//
// The compiler will invoke the ModuleLoader after resolving all references in
// the current set of input modules. The ModuleLoader can return a new
// collection of parsed modules that are to be included in the compilation
// process. This process will repeat until the ModuleLoader returns an empty
// collection or an error. If an error is returned, compilation will stop
// immediately.
func (c *Compiler) WithModuleLoader(f ModuleLoader) *Compiler {
c.moduleLoader = f
return c
}
// WithDefaultRegoVersion sets the default Rego version to use when a module doesn't specify one;
// such as when it's hand-crafted instead of parsed.
func (c *Compiler) WithDefaultRegoVersion(regoVersion RegoVersion) *Compiler {
c.defaultRegoVersion = regoVersion
return c
}
func (c *Compiler) counterAdd(name string, n uint64) {
if c.metrics == nil {
return
}
c.metrics.Counter(name).Add(n)
}
func (c *Compiler) buildRuleIndices() {
c.RuleTree.DepthFirst(func(node *TreeNode) bool {
if len(node.Values) == 0 {
return false
}
rules := extractRules(node.Values)
hasNonGroundRef := false
for _, r := range rules {
hasNonGroundRef = !r.Head.Ref().IsGround()
}
if hasNonGroundRef {
// Collect children to ensure that all rules within the extent of a rule with a general ref
// are found on the same index. E.g. the following rules should be indexed under data.a.b.c:
//
// package a
// b.c[x].e := 1 { x := input.x }
// b.c.d := 2
// b.c.d2.e[x] := 3 { x := input.x }
for _, child := range node.Children {
child.DepthFirst(func(c *TreeNode) bool {
rules = append(rules, extractRules(c.Values)...)
return false
})
}
}
index := newBaseDocEqIndex(func(ref Ref) bool {
return isVirtual(c.RuleTree, ref.GroundPrefix())
})
if index.Build(rules) {
c.ruleIndices.Put(rules[0].Ref().GroundPrefix(), index)
}
return hasNonGroundRef // currently, we don't allow those branches to go deeper
})
}
func (c *Compiler) buildComprehensionIndices() {
for _, name := range c.sorted {
WalkRules(c.Modules[name], func(r *Rule) bool {
candidates := r.Head.Args.Vars()
candidates.Update(ReservedVars)
n := buildComprehensionIndices(c.debug, c.GetArity, candidates, c.RewrittenVars, r.Body, c.comprehensionIndices)
c.counterAdd(compileStageComprehensionIndexBuild, n)
return false
})
}
}
var (
keywordsTerm = StringTerm("keywords")
pathTerm = StringTerm("path")
annotationsTerm = StringTerm("annotations")
futureKeywordsPrefix = Ref{FutureRootDocument, keywordsTerm}
)
// buildRequiredCapabilities updates the required capabilities on the compiler
// to include any keyword and feature dependencies present in the modules. The
// built-in function dependencies will have already been added by the type
// checker.
func (c *Compiler) buildRequiredCapabilities() {
features := map[string]struct{}{}
// extract required keywords from modules
keywords := map[string]struct{}{}
for _, name := range c.sorted {
for _, imp := range c.imports[name] {
mod := c.Modules[name]
path := imp.Path.Value.(Ref)
switch {
case path.Equal(RegoV1CompatibleRef):
if !c.moduleIsRegoV1(mod) {
features[FeatureRegoV1Import] = struct{}{}
}
case path.HasPrefix(futureKeywordsPrefix):
if len(path) == 2 {
if c.moduleIsRegoV1(mod) {
for kw := range futureKeywords {
keywords[kw] = struct{}{}
}
} else {
for kw := range allFutureKeywords {
keywords[kw] = struct{}{}
}
}
} else {
kw := string(path[2].Value.(String))
if c.moduleIsRegoV1(mod) {
for allowedKw := range futureKeywords {
if kw == allowedKw {
keywords[kw] = struct{}{}
break
}
}
} else {
for allowedKw := range allFutureKeywords {
if kw == allowedKw {
keywords[kw] = struct{}{}
break
}
}
}
}
}
}
}
c.Required.FutureKeywords = util.KeysSorted(keywords)
// extract required features from modules
for _, name := range c.sorted {
mod := c.Modules[name]
if c.moduleIsRegoV1(mod) {
features[FeatureRegoV1] = struct{}{}
} else {
for _, rule := range mod.Rules {
refLen := len(rule.Head.Reference)
if refLen >= 3 {
if refLen > len(rule.Head.Reference.ConstantPrefix()) {
features[FeatureRefHeads] = struct{}{}
} else {
features[FeatureRefHeadStringPrefixes] = struct{}{}
}
}
}
}
}
c.Required.Features = util.KeysSorted(features)
for i, bi := range c.Required.Builtins {
c.Required.Builtins[i] = bi.Minimal()
}
}
// checkRecursion ensures that there are no recursive definitions, i.e., there are
// no cycles in the Graph.
func (c *Compiler) checkRecursion() {
eq := func(a, b util.T) bool {
return a.(*Rule) == b.(*Rule)
}
c.RuleTree.DepthFirst(func(node *TreeNode) bool {
for _, rule := range node.Values {
for node := rule.(*Rule); node != nil; node = node.Else {
c.checkSelfPath(node.Loc(), eq, node, node)
}
}
return false
})
}
func (c *Compiler) checkSelfPath(loc *Location, eq func(a, b util.T) bool, a, b util.T) {
tr := NewGraphTraversal(c.Graph)
if p := util.DFSPath(tr, eq, a, b); len(p) > 0 {
n := make([]string, 0, len(p))
for _, x := range p {
n = append(n, astNodeToString(x))
}
c.err(NewError(RecursionErr, loc, "rule %v is recursive: %v", astNodeToString(a), strings.Join(n, " -> ")))
}
}
func astNodeToString(x interface{}) string {
return x.(*Rule).Ref().String()
}
// checkRuleConflicts ensures that rules definitions are not in conflict.
func (c *Compiler) checkRuleConflicts() {
rw := rewriteVarsInRef(c.RewrittenVars)
c.RuleTree.DepthFirst(func(node *TreeNode) bool {
if len(node.Values) == 0 {
return false // go deeper
}
kinds := make(map[RuleKind]struct{}, len(node.Values))
completeRules := 0
partialRules := 0
arities := make(map[int]struct{}, len(node.Values))
name := ""
var conflicts []Ref
defaultRules := make([]*Rule, 0)
for _, rule := range node.Values {
r := rule.(*Rule)
ref := r.Ref()
name = rw(ref.CopyNonGround()).String() // varRewriter operates in-place
kinds[r.Head.RuleKind()] = struct{}{}
arities[len(r.Head.Args)] = struct{}{}
if r.Default {
defaultRules = append(defaultRules, r)
}
// Single-value rules may not have any other rules in their extent.
// Rules with vars in their ref are allowed to have rules inside their extent.
// Only the ground portion (terms before the first var term) of a rule's ref is considered when determining
// whether it's inside the extent of another (c.RuleTree is organized this way already).
// These pairs are invalid:
//
// data.p.q.r { true } # data.p.q is { "r": true }
// data.p.q.r.s { true }
//
// data.p.q.r { true }
// data.p.q.r[s].t { s = input.key }
//
// But this is allowed:
//
// data.p.q.r { true }
// data.p.q[r].s.t { r = input.key }
//
// data.p[r] := x { r = input.key; x = input.bar }
// data.p.q[r] := x { r = input.key; x = input.bar }
//
// data.p.q[r] { r := input.r }
// data.p.q.r.s { true }
//
// data.p.q[r] = 1 { r := "r" }
// data.p.q.s = 2
//
// data.p[q][r] { q := input.q; r := input.r }
// data.p.q.r { true }
//
// data.p.q[r] { r := input.r }
// data.p[q].r { q := input.q }
//
// data.p.q[r][s] { r := input.r; s := input.s }
// data.p[q].r.s { q := input.q }
if ref.IsGround() && len(node.Children) > 0 {
conflicts = node.flattenChildren()
}
if r.Head.RuleKind() == SingleValue && r.Head.Ref().IsGround() {
completeRules++
} else {
partialRules++
}
}
switch {
case conflicts != nil:
c.err(NewError(TypeErr, node.Values[0].(*Rule).Loc(), "rule %v conflicts with %v", name, conflicts))
case len(kinds) > 1 || len(arities) > 1 || (completeRules >= 1 && partialRules >= 1):
c.err(NewError(TypeErr, node.Values[0].(*Rule).Loc(), "conflicting rules %v found", name))
case len(defaultRules) > 1:
defaultRuleLocations := strings.Builder{}
defaultRuleLocations.WriteString(defaultRules[0].Loc().String())
for i := 1; i < len(defaultRules); i++ {
defaultRuleLocations.WriteString(", ")
defaultRuleLocations.WriteString(defaultRules[i].Loc().String())
}
c.err(NewError(
TypeErr,
defaultRules[0].Module.Package.Loc(),
"multiple default rules %s found at %s",
name, defaultRuleLocations.String()),
)
}
return false
})
if c.pathExists != nil {
for _, err := range CheckPathConflicts(c, c.pathExists) {
c.err(err)
}
}
// NOTE(sr): depthfirst might better use sorted for stable errs?
c.ModuleTree.DepthFirst(func(node *ModuleTreeNode) bool {
for _, mod := range node.Modules {
for _, rule := range mod.Rules {
ref := rule.Head.Ref().GroundPrefix()
// Rules with a dynamic portion in their ref are exempted, as a conflict within the dynamic portion
// can only be detected at eval-time.
if len(ref) < len(rule.Head.Ref()) {
continue
}
childNode, tail := node.find(ref)
if childNode != nil && len(tail) == 0 {
for _, childMod := range childNode.Modules {
// Avoid recursively checking a module for equality unless we know it's a possible self-match.
if childMod.Equal(mod) {
continue // don't self-conflict
}
msg := fmt.Sprintf("%v conflicts with rule %v defined at %v", childMod.Package, rule.Head.Ref(), rule.Loc())
c.err(NewError(TypeErr, mod.Package.Loc(), msg)) //nolint:govet
}
}
}
}
return false
})
}
func (c *Compiler) checkUndefinedFuncs() {
for _, name := range c.sorted {
m := c.Modules[name]
for _, err := range checkUndefinedFuncs(c.TypeEnv, m, c.GetArity, c.RewrittenVars) {
c.err(err)
}
}
}
func checkUndefinedFuncs(env *TypeEnv, x interface{}, arity func(Ref) int, rwVars map[Var]Var) Errors {
var errs Errors
WalkExprs(x, func(expr *Expr) bool {
if !expr.IsCall() {
return false
}
ref := expr.Operator()
if arity := arity(ref); arity >= 0 {
operands := len(expr.Operands())
if expr.Generated { // an output var was added
if !expr.IsEquality() && operands != arity+1 {
ref = rewriteVarsInRef(rwVars)(ref)
errs = append(errs, arityMismatchError(env, ref, expr, arity, operands-1))
return true
}
} else { // either output var or not
if operands != arity && operands != arity+1 {
ref = rewriteVarsInRef(rwVars)(ref)
errs = append(errs, arityMismatchError(env, ref, expr, arity, operands))
return true
}
}
return false
}
ref = rewriteVarsInRef(rwVars)(ref)
errs = append(errs, NewError(TypeErr, expr.Loc(), "undefined function %v", ref))
return true
})
return errs
}
func arityMismatchError(env *TypeEnv, f Ref, expr *Expr, exp, act int) *Error {
if want, ok := env.Get(f).(*types.Function); ok { // generate richer error for built-in functions
have := make([]types.Type, len(expr.Operands()))
for i, op := range expr.Operands() {
have[i] = env.Get(op)
}
return newArgError(expr.Loc(), f, "arity mismatch", have, want.NamedFuncArgs())
}
if act != 1 {
return NewError(TypeErr, expr.Loc(), "function %v has arity %d, got %d arguments", f, exp, act)
}
return NewError(TypeErr, expr.Loc(), "function %v has arity %d, got %d argument", f, exp, act)
}
// checkSafetyRuleBodies ensures that variables appearing in negated expressions or non-target
// positions of built-in expressions will be bound when evaluating the rule from left
// to right, re-ordering as necessary.
func (c *Compiler) checkSafetyRuleBodies() {
for _, name := range c.sorted {
m := c.Modules[name]
WalkRules(m, func(r *Rule) bool {
safe := ReservedVars.Copy()
safe.Update(r.Head.Args.Vars())
r.Body = c.checkBodySafety(safe, r.Body)
return false
})
}
}
func (c *Compiler) checkBodySafety(safe VarSet, b Body) Body {
reordered, unsafe := reorderBodyForSafety(c.builtins, c.GetArity, safe, b)
if errs := safetyErrorSlice(unsafe, c.RewrittenVars); len(errs) > 0 {
for _, err := range errs {
c.err(err)
}
return b
}
return reordered
}
// SafetyCheckVisitorParams defines the AST visitor parameters to use for collecting
// variables during the safety check. This has to be exported because it's relied on
// by the copy propagation implementation in topdown.
var SafetyCheckVisitorParams = VarVisitorParams{
SkipRefCallHead: true,
SkipClosures: true,
}
// checkSafetyRuleHeads ensures that variables appearing in the head of a
// rule also appear in the body.
func (c *Compiler) checkSafetyRuleHeads() {
for _, name := range c.sorted {
m := c.Modules[name]
WalkRules(m, func(r *Rule) bool {
safe := r.Body.Vars(SafetyCheckVisitorParams)
safe.Update(r.Head.Args.Vars())
unsafe := r.Head.Vars().Diff(safe)
for v := range unsafe {
if w, ok := c.RewrittenVars[v]; ok {
v = w
}
if !v.IsGenerated() {
c.err(NewError(UnsafeVarErr, r.Loc(), "var %v is unsafe", v))
}
}
return false
})
}
}
func compileSchema(goSchema interface{}, allowNet []string) (*gojsonschema.Schema, error) {
gojsonschema.SetAllowNet(allowNet)
var refLoader gojsonschema.JSONLoader
sl := gojsonschema.NewSchemaLoader()
if goSchema != nil {
refLoader = gojsonschema.NewGoLoader(goSchema)
} else {
return nil, errors.New("no schema as input to compile")
}
schemasCompiled, err := sl.Compile(refLoader)
if err != nil {
return nil, fmt.Errorf("unable to compile the schema: %w", err)
}
return schemasCompiled, nil
}
func mergeSchemas(schemas ...*gojsonschema.SubSchema) (*gojsonschema.SubSchema, error) {
if len(schemas) == 0 {
return nil, nil
}
var result = schemas[0]
for i := range schemas {
if len(schemas[i].PropertiesChildren) > 0 {
if !schemas[i].Types.Contains("object") {
if err := schemas[i].Types.Add("object"); err != nil {
return nil, errors.New("unable to set the type in schemas")
}
}
} else if len(schemas[i].ItemsChildren) > 0 {
if !schemas[i].Types.Contains("array") {
if err := schemas[i].Types.Add("array"); err != nil {
return nil, errors.New("unable to set the type in schemas")
}
}
}
}
for i := 1; i < len(schemas); i++ {
if result.Types.String() != schemas[i].Types.String() {
return nil, fmt.Errorf("unable to merge these schemas: type mismatch: %v and %v", result.Types.String(), schemas[i].Types.String())
} else if result.Types.Contains("object") && len(result.PropertiesChildren) > 0 && schemas[i].Types.Contains("object") && len(schemas[i].PropertiesChildren) > 0 {
result.PropertiesChildren = append(result.PropertiesChildren, schemas[i].PropertiesChildren...)
} else if result.Types.Contains("array") && len(result.ItemsChildren) > 0 && schemas[i].Types.Contains("array") && len(schemas[i].ItemsChildren) > 0 {
for j := range len(schemas[i].ItemsChildren) {
if len(result.ItemsChildren)-1 < j && !(len(schemas[i].ItemsChildren)-1 < j) {
result.ItemsChildren = append(result.ItemsChildren, schemas[i].ItemsChildren[j])
}
if result.ItemsChildren[j].Types.String() != schemas[i].ItemsChildren[j].Types.String() {
return nil, errors.New("unable to merge these schemas")
}
}
}
}
return result, nil
}
type schemaParser struct {
definitionCache map[string]*cachedDef
}
type cachedDef struct {
properties []*types.StaticProperty
}
func newSchemaParser() *schemaParser {
return &schemaParser{
definitionCache: map[string]*cachedDef{},
}
}
func (parser *schemaParser) parseSchema(schema interface{}) (types.Type, error) {
return parser.parseSchemaWithPropertyKey(schema, "")
}
func (parser *schemaParser) parseSchemaWithPropertyKey(schema interface{}, propertyKey string) (types.Type, error) {
subSchema, ok := schema.(*gojsonschema.SubSchema)
if !ok {
return nil, fmt.Errorf("unexpected schema type %v", subSchema)
}
// Handle referenced schemas, returns directly when a $ref is found
if subSchema.RefSchema != nil {
if existing, ok := parser.definitionCache[subSchema.Ref.String()]; ok {
return types.NewObject(existing.properties, nil), nil
}
return parser.parseSchemaWithPropertyKey(subSchema.RefSchema, subSchema.Ref.String())
}
// Handle anyOf
if subSchema.AnyOf != nil {
var orType types.Type
// If there is a core schema, find its type first
if subSchema.Types.IsTyped() {
copySchema := *subSchema
copySchemaRef := &copySchema
copySchemaRef.AnyOf = nil
coreType, err := parser.parseSchema(copySchemaRef)
if err != nil {
return nil, fmt.Errorf("unexpected schema type %v: %w", subSchema, err)
}
// Only add Object type with static props to orType
if objType, ok := coreType.(*types.Object); ok {
if objType.StaticProperties() != nil && objType.DynamicProperties() == nil {
orType = types.Or(orType, coreType)
}
}
}
// Iterate through every property of AnyOf and add it to orType
for _, pSchema := range subSchema.AnyOf {
newtype, err := parser.parseSchema(pSchema)
if err != nil {
return nil, fmt.Errorf("unexpected schema type %v: %w", pSchema, err)
}
orType = types.Or(newtype, orType)
}
return orType, nil
}
if subSchema.AllOf != nil {
subSchemaArray := subSchema.AllOf
allOfResult, err := mergeSchemas(subSchemaArray...)
if err != nil {
return nil, err
}
if subSchema.Types.IsTyped() {
if (subSchema.Types.Contains("object") && allOfResult.Types.Contains("object")) || (subSchema.Types.Contains("array") && allOfResult.Types.Contains("array")) {
objectOrArrayResult, err := mergeSchemas(allOfResult, subSchema)
if err != nil {
return nil, err
}
return parser.parseSchema(objectOrArrayResult)
} else if subSchema.Types.String() != allOfResult.Types.String() {
return nil, errors.New("unable to merge these schemas")
}
}
return parser.parseSchema(allOfResult)
}
if subSchema.Types.IsTyped() {
if subSchema.Types.Contains("boolean") {
return types.B, nil
} else if subSchema.Types.Contains("string") {
return types.S, nil
} else if subSchema.Types.Contains("integer") || subSchema.Types.Contains("number") {
return types.N, nil
} else if subSchema.Types.Contains("object") {
if len(subSchema.PropertiesChildren) > 0 {
def := &cachedDef{
properties: make([]*types.StaticProperty, 0, len(subSchema.PropertiesChildren)),
}
for _, pSchema := range subSchema.PropertiesChildren {
def.properties = append(def.properties, types.NewStaticProperty(pSchema.Property, nil))
}
if propertyKey != "" {
parser.definitionCache[propertyKey] = def
}
for _, pSchema := range subSchema.PropertiesChildren {
newtype, err := parser.parseSchema(pSchema)
if err != nil {
return nil, fmt.Errorf("unexpected schema type %v: %w", pSchema, err)
}
for i, prop := range def.properties {
if prop.Key == pSchema.Property {
def.properties[i].Value = newtype
break
}
}
}
return types.NewObject(def.properties, nil), nil
}
return types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)), nil
} else if subSchema.Types.Contains("array") {
if len(subSchema.ItemsChildren) > 0 {
if subSchema.ItemsChildrenIsSingleSchema {
iSchema := subSchema.ItemsChildren[0]
newtype, err := parser.parseSchema(iSchema)
if err != nil {
return nil, fmt.Errorf("unexpected schema type %v", iSchema)
}
return types.NewArray(nil, newtype), nil
}
newTypes := make([]types.Type, 0, len(subSchema.ItemsChildren))
for i := 0; i != len(subSchema.ItemsChildren); i++ {
iSchema := subSchema.ItemsChildren[i]
newtype, err := parser.parseSchema(iSchema)
if err != nil {
return nil, fmt.Errorf("unexpected schema type %v", iSchema)
}
newTypes = append(newTypes, newtype)
}
return types.NewArray(newTypes, nil), nil
}
return types.NewArray(nil, types.A), nil
}
}
// Assume types if not specified in schema
if len(subSchema.PropertiesChildren) > 0 {
if err := subSchema.Types.Add("object"); err == nil {
return parser.parseSchema(subSchema)
}
} else if len(subSchema.ItemsChildren) > 0 {
if err := subSchema.Types.Add("array"); err == nil {
return parser.parseSchema(subSchema)
}
}
return types.A, nil
}
func (c *Compiler) setAnnotationSet() {
// Sorting modules by name for stable error reporting
sorted := make([]*Module, 0, len(c.Modules))
for _, mName := range c.sorted {
sorted = append(sorted, c.Modules[mName])
}
as, errs := BuildAnnotationSet(sorted)
for _, err := range errs {
c.err(err)
}
c.annotationSet = as
}
// checkTypes runs the type checker on all rules. The type checker builds a
// TypeEnv that is stored on the compiler.
func (c *Compiler) checkTypes() {
// Recursion is caught in earlier step, so this cannot fail.
sorted, _ := c.Graph.Sort()
checker := newTypeChecker().
WithAllowNet(c.capabilities.AllowNet).
WithSchemaSet(c.schemaSet).
WithInputType(c.inputType).
WithBuiltins(c.builtins).
WithRequiredCapabilities(c.Required).
WithVarRewriter(rewriteVarsInRef(c.RewrittenVars)).
WithAllowUndefinedFunctionCalls(c.allowUndefinedFuncCalls)
var as *AnnotationSet
if c.useTypeCheckAnnotations {
as = c.annotationSet
}
env, errs := checker.CheckTypes(c.TypeEnv, sorted, as)
for _, err := range errs {
c.err(err)
}
c.TypeEnv = env
}
func (c *Compiler) checkUnsafeBuiltins() {
if len(c.unsafeBuiltinsMap) == 0 {
return
}
for _, name := range c.sorted {
errs := checkUnsafeBuiltins(c.unsafeBuiltinsMap, c.Modules[name])
for _, err := range errs {
c.err(err)
}
}
}
func (c *Compiler) checkDeprecatedBuiltins() {
checkNeeded := false
for _, b := range c.Required.Builtins {
if _, found := c.deprecatedBuiltinsMap[b.Name]; found {
checkNeeded = true
break
}
}
if !checkNeeded {
return
}
for _, name := range c.sorted {
mod := c.Modules[name]
if c.strict || mod.regoV1Compatible() {
errs := checkDeprecatedBuiltins(c.deprecatedBuiltinsMap, mod)
for _, err := range errs {
c.err(err)
}
}
}
}
func (c *Compiler) runStage(metricName string, f func()) {
if c.metrics != nil {
c.metrics.Timer(metricName).Start()
defer c.metrics.Timer(metricName).Stop()
}
f()
}
func (c *Compiler) runStageAfter(metricName string, s CompilerStage) *Error {
if c.metrics != nil {
c.metrics.Timer(metricName).Start()
defer c.metrics.Timer(metricName).Stop()
}
return s(c)
}
func (c *Compiler) compile() {
defer func() {
if r := recover(); r != nil && r != errLimitReached {
panic(r)
}
}()
for _, s := range c.stages {
if c.evalMode == EvalModeIR {
switch s.name {
case "BuildRuleIndices", "BuildComprehensionIndices":
continue // skip these stages
}
}
if c.allowUndefinedFuncCalls && (s.name == "CheckUndefinedFuncs" || s.name == "CheckSafetyRuleBodies") {
continue
}
c.runStage(s.metricName, s.f)
if c.Failed() {
return
}
for _, a := range c.after[s.name] {
if err := c.runStageAfter(a.MetricName, a.Stage); err != nil {
c.err(err)
return
}
}
}
}
func (c *Compiler) init() {
if c.initialized {
return
}
if c.capabilities == nil {
c.capabilities = CapabilitiesForThisVersion()
}
c.builtins = make(map[string]*Builtin, len(c.capabilities.Builtins)+len(c.customBuiltins))
for _, bi := range c.capabilities.Builtins {
c.builtins[bi.Name] = bi
if bi.IsDeprecated() {
c.deprecatedBuiltinsMap[bi.Name] = struct{}{}
}
}
for name, bi := range c.customBuiltins {
c.builtins[name] = bi
}
// Load the global input schema if one was provided.
if c.schemaSet != nil {
if schema := c.schemaSet.Get(SchemaRootRef); schema != nil {
tpe, err := loadSchema(schema, c.capabilities.AllowNet)
if err != nil {
c.err(NewError(TypeErr, nil, err.Error())) //nolint:govet
} else {
c.inputType = tpe
}
}
}
c.TypeEnv = newTypeChecker().
WithSchemaSet(c.schemaSet).
WithInputType(c.inputType).
Env(c.builtins)
c.initialized = true
}
func (c *Compiler) err(err *Error) {
if c.maxErrs > 0 && len(c.Errors) >= c.maxErrs {
c.Errors = append(c.Errors, errLimitReached)
panic(errLimitReached)
}
c.Errors = append(c.Errors, err)
}
func (c *Compiler) getExports() *util.HasherMap[Ref, []Ref] {
rules := util.NewHasherMap[Ref, []Ref](RefEqual)
for _, name := range c.sorted {
mod := c.Modules[name]
for _, rule := range mod.Rules {
hashMapAdd(rules, mod.Package.Path, rule.Head.Ref().GroundPrefix())
}
}
return rules
}
func refSliceEqual(a, b []Ref) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].Equal(b[i]) {
return false
}
}
return true
}
func hashMapAdd(rules *util.HasherMap[Ref, []Ref], pkg, rule Ref) {
prev, ok := rules.Get(pkg)
if !ok {
rules.Put(pkg, []Ref{rule})
return
}
for _, p := range prev {
if p.Equal(rule) {
return
}
}
rules.Put(pkg, append(prev, rule))
}
func (c *Compiler) GetAnnotationSet() *AnnotationSet {
return c.annotationSet
}
func (c *Compiler) checkImports() {
modules := make([]*Module, 0, len(c.Modules))
supportsRegoV1Import := c.capabilities.ContainsFeature(FeatureRegoV1Import) ||
c.capabilities.ContainsFeature(FeatureRegoV1)
for _, name := range c.sorted {
mod := c.Modules[name]
for _, imp := range mod.Imports {
if !supportsRegoV1Import && RegoV1CompatibleRef.Equal(imp.Path.Value) {
c.err(NewError(CompileErr, imp.Loc(), "rego.v1 import is not supported"))
}
}
if c.strict || c.moduleIsRegoV1Compatible(mod) {
modules = append(modules, mod)
}
}
errs := checkDuplicateImports(modules)
for _, err := range errs {
c.err(err)
}
}
func (c *Compiler) checkKeywordOverrides() {
for _, name := range c.sorted {
mod := c.Modules[name]
if c.strict || c.moduleIsRegoV1Compatible(mod) {
errs := checkRootDocumentOverrides(mod)
for _, err := range errs {
c.err(err)
}
}
}
}
func (c *Compiler) moduleIsRegoV1(mod *Module) bool {
if mod.regoVersion == RegoUndefined {
switch c.defaultRegoVersion {
case RegoUndefined:
c.err(NewError(CompileErr, mod.Package.Loc(), "cannot determine rego version for module"))
return false
case RegoV1:
return true
}
return false
}
return mod.regoVersion == RegoV1
}
func (c *Compiler) moduleIsRegoV1Compatible(mod *Module) bool {
if mod.regoVersion == RegoUndefined {
switch c.defaultRegoVersion {
case RegoUndefined:
c.err(NewError(CompileErr, mod.Package.Loc(), "cannot determine rego version for module"))
return false
case RegoV1, RegoV0CompatV1:
return true
}
return false
}
return mod.regoV1Compatible()
}
// resolveAllRefs resolves references in expressions to their fully qualified values.
//
// For instance, given the following module:
//
// package a.b
// import data.foo.bar
// p[x] { bar[_] = x }
//
// The reference "bar[_]" would be resolved to "data.foo.bar[_]".
//
// Ref rules are resolved, too:
//
// package a.b
// q { c.d.e == 1 }
// c.d[e] := 1 if e := "e"
//
// The reference "c.d.e" would be resolved to "data.a.b.c.d.e".
func (c *Compiler) resolveAllRefs() {
rules := c.getExports()
for _, name := range c.sorted {
mod := c.Modules[name]
var ruleExports []Ref
if x, ok := rules.Get(mod.Package.Path); ok {
ruleExports = x
}
globals := getGlobals(mod.Package, ruleExports, mod.Imports)
WalkRules(mod, func(rule *Rule) bool {
err := resolveRefsInRule(globals, rule)
if err != nil {
c.err(NewError(CompileErr, rule.Location, err.Error())) //nolint:govet
}
return false
})
if c.strict { // check for unused imports
for _, imp := range mod.Imports {
path := imp.Path.Value.(Ref)
if FutureRootDocument.Equal(path[0]) || RegoRootDocument.Equal(path[0]) {
continue // ignore future and rego imports
}
for v, u := range globals {
if v.Equal(imp.Name()) && !u.used {
c.err(NewError(CompileErr, imp.Location, "%s unused", imp.String()))
}
}
}
}
}
if c.moduleLoader != nil {
parsed, err := c.moduleLoader(c.Modules)
if err != nil {
c.err(NewError(CompileErr, nil, err.Error())) //nolint:govet
return
}
if len(parsed) == 0 {
return
}
for id, module := range parsed {
c.Modules[id] = module.Copy()
c.sorted = append(c.sorted, id)
if c.parsedModules != nil {
c.parsedModules[id] = module
}
}
sort.Strings(c.sorted)
c.resolveAllRefs()
}
}
func (c *Compiler) removeImports() {
c.imports = make(map[string][]*Import, len(c.Modules))
for name := range c.Modules {
c.imports[name] = c.Modules[name].Imports
c.Modules[name].Imports = nil
}
}
func (c *Compiler) initLocalVarGen() {
c.localvargen = newLocalVarGeneratorForModuleSet(c.sorted, c.Modules)
}
func (c *Compiler) rewriteComprehensionTerms() {
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
mod := c.Modules[name]
_, _ = rewriteComprehensionTerms(f, mod) // ignore error
}
}
func (c *Compiler) rewriteExprTerms() {
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(rule *Rule) bool {
rewriteExprTermsInHead(c.localvargen, rule)
rule.Body = rewriteExprTermsInBody(c.localvargen, rule.Body)
return false
})
}
}
func (c *Compiler) rewriteRuleHeadRefs() {
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
WalkRules(c.Modules[name], func(rule *Rule) bool {
ref := rule.Head.Ref()
// NOTE(sr): We're backfilling Refs here -- all parser code paths would have them, but
// it's possible to construct Module{} instances from Golang code, so we need
// to accommodate for that, too.
if len(rule.Head.Reference) == 0 {
rule.Head.Reference = ref
}
cannotSpeakStringPrefixRefs := true
cannotSpeakGeneralRefs := true
for _, f := range c.capabilities.Features {
switch f {
case FeatureRefHeadStringPrefixes:
cannotSpeakStringPrefixRefs = false
case FeatureRefHeads:
cannotSpeakGeneralRefs = false
case FeatureRegoV1:
cannotSpeakStringPrefixRefs = false
cannotSpeakGeneralRefs = false
}
}
if cannotSpeakStringPrefixRefs && cannotSpeakGeneralRefs && rule.Head.Name == "" {
c.err(NewError(CompileErr, rule.Loc(), "rule heads with refs are not supported: %v", rule.Head.Reference))
return true
}
for i := 1; i < len(ref); i++ {
if cannotSpeakGeneralRefs && (rule.Head.RuleKind() == MultiValue || i != len(ref)-1) { // last
if _, ok := ref[i].Value.(String); !ok {
c.err(NewError(TypeErr, rule.Loc(), "rule heads with general refs (containing variables) are not supported: %v", rule.Head.Reference))
continue
}
}
// Rewrite so that any non-scalar elements in the rule's ref are vars:
// p.q.r[y.z] { ... } => p.q.r[__local0__] { __local0__ = y.z }
// p.q[a.b][c.d] { ... } => p.q[__local0__] { __local0__ = a.b; __local1__ = c.d }
// because that's what the RuleTree knows how to deal with.
if _, ok := ref[i].Value.(Var); !ok && !IsScalar(ref[i].Value) {
expr := f.Generate(ref[i])
if i == len(ref)-1 && rule.Head.Key.Equal(ref[i]) {
rule.Head.Key = expr.Operand(0)
}
rule.Head.Reference[i] = expr.Operand(0)
rule.Body.Append(expr)
}
}
return true
})
}
}
func (c *Compiler) checkVoidCalls() {
for _, name := range c.sorted {
mod := c.Modules[name]
for _, err := range checkVoidCalls(c.TypeEnv, mod) {
c.err(err)
}
}
}
func (c *Compiler) rewritePrintCalls() {
var modified bool
if !c.enablePrintStatements {
for _, name := range c.sorted {
if erasePrintCalls(c.Modules[name]) {
modified = true
}
}
} else {
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(r *Rule) bool {
safe := r.Head.Args.Vars()
safe.Update(ReservedVars)
vis := func(b Body) bool {
modrec, errs := rewritePrintCalls(c.localvargen, c.GetArity, safe, b)
if modrec {
modified = true
}
for _, err := range errs {
c.err(err)
}
return false
}
WalkBodies(r.Head, vis)
WalkBodies(r.Body, vis)
return false
})
}
}
if modified {
c.Required.addBuiltinSorted(Print)
}
}
// checkVoidCalls returns errors for any expressions that treat void function
// calls as values. The only void functions in Rego are specific built-ins like
// print().
func checkVoidCalls(env *TypeEnv, x interface{}) Errors {
var errs Errors
WalkTerms(x, func(x *Term) bool {
if call, ok := x.Value.(Call); ok {
if tpe, ok := env.Get(call[0]).(*types.Function); ok && tpe.Result() == nil {
errs = append(errs, NewError(TypeErr, x.Loc(), "%v used as value", call))
}
}
return false
})
return errs
}
// rewritePrintCalls will rewrite the body so that print operands are captured
// in local variables and their evaluation occurs within a comprehension.
// Wrapping the terms inside of a comprehension ensures that undefined values do
// not short-circuit evaluation.
//
// For example, given the following print statement:
//
// print("the value of x is:", input.x)
//
// The expression would be rewritten to:
//
// print({__local0__ | __local0__ = "the value of x is:"}, {__local1__ | __local1__ = input.x})
func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals VarSet, body Body) (bool, Errors) {
var errs Errors
var modified bool
// Visit comprehension bodies recursively to ensure print statements inside
// those bodies only close over variables that are safe.
for i := range body {
if ContainsClosures(body[i]) {
safe := outputVarsForBody(body[:i], getArity, globals)
safe.Update(globals)
WalkClosures(body[i], func(x interface{}) bool {
var modrec bool
var errsrec Errors
switch x := x.(type) {
case *SetComprehension:
modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body)
case *ArrayComprehension:
modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body)
case *ObjectComprehension:
modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body)
case *Every:
safe.Update(x.KeyValueVars())
modrec, errsrec = rewritePrintCalls(gen, getArity, safe, x.Body)
}
if modrec {
modified = true
}
errs = append(errs, errsrec...)
return true
})
if len(errs) > 0 {
return false, errs
}
}
}
for i := range body {
if !isPrintCall(body[i]) {
continue
}
modified = true
var errs Errors
safe := outputVarsForBody(body[:i], getArity, globals)
safe.Update(globals)
args := body[i].Operands()
for j := range args {
vis := NewVarVisitor().WithParams(SafetyCheckVisitorParams)
vis.Walk(args[j])
unsafe := vis.Vars().Diff(safe)
for _, v := range unsafe.Sorted() {
errs = append(errs, NewError(CompileErr, args[j].Loc(), "var %v is undeclared", v))
}
}
if len(errs) > 0 {
return false, errs
}
arr := NewArray()
for j := range args {
x := NewTerm(gen.Generate()).SetLocation(args[j].Loc())
capture := Equality.Expr(x, args[j]).SetLocation(args[j].Loc())
arr = arr.Append(SetComprehensionTerm(x, NewBody(capture)).SetLocation(args[j].Loc()))
}
body.Set(NewExpr([]*Term{
NewTerm(InternalPrint.Ref()).SetLocation(body[i].Loc()),
NewTerm(arr).SetLocation(body[i].Loc()),
}).SetLocation(body[i].Loc()), i)
}
return modified, nil
}
func erasePrintCalls(node interface{}) bool {
var modified bool
NewGenericVisitor(func(x interface{}) bool {
var modrec bool
switch x := x.(type) {
case *Rule:
modrec, x.Body = erasePrintCallsInBody(x.Body)
case *ArrayComprehension:
modrec, x.Body = erasePrintCallsInBody(x.Body)
case *SetComprehension:
modrec, x.Body = erasePrintCallsInBody(x.Body)
case *ObjectComprehension:
modrec, x.Body = erasePrintCallsInBody(x.Body)
case *Every:
modrec, x.Body = erasePrintCallsInBody(x.Body)
}
if modrec {
modified = true
}
return false
}).Walk(node)
return modified
}
func erasePrintCallsInBody(x Body) (bool, Body) {
if !containsPrintCall(x) {
return false, x
}
var cpy Body
for i := range x {
// Recursively visit any comprehensions contained in this expression.
erasePrintCalls(x[i])
if !isPrintCall(x[i]) {
cpy.Append(x[i])
}
}
if len(cpy) == 0 {
term := BooleanTerm(true).SetLocation(x.Loc())
expr := NewExpr(term).SetLocation(x.Loc())
cpy.Append(expr)
}
return true, cpy
}
func containsPrintCall(x interface{}) bool {
var found bool
WalkExprs(x, func(expr *Expr) bool {
if !found {
if isPrintCall(expr) {
found = true
}
}
return found
})
return found
}
var printRef = Print.Ref()
func isPrintCall(x *Expr) bool {
return x.IsCall() && x.Operator().Equal(printRef)
}
// rewriteRefsInHead will rewrite rules so that the head does not contain any
// terms that require evaluation (e.g., refs or comprehensions). If the key or
// value contains one or more of these terms, the key or value will be moved
// into the body and assigned to a new variable. The new variable will replace
// the key or value in the head.
//
// For instance, given the following rule:
//
// p[{"foo": data.foo[i]}] { i < 100 }
//
// The rule would be re-written as:
//
// p[__local0__] { i < 100; __local0__ = {"foo": data.foo[i]} }
func (c *Compiler) rewriteRefsInHead() {
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(rule *Rule) bool {
if requiresEval(rule.Head.Key) {
expr := f.Generate(rule.Head.Key)
rule.Head.Key = expr.Operand(0)
rule.Body.Append(expr)
}
if requiresEval(rule.Head.Value) {
expr := f.Generate(rule.Head.Value)
rule.Head.Value = expr.Operand(0)
rule.Body.Append(expr)
}
for i := 0; i < len(rule.Head.Args); i++ {
if requiresEval(rule.Head.Args[i]) {
expr := f.Generate(rule.Head.Args[i])
rule.Head.Args[i] = expr.Operand(0)
rule.Body.Append(expr)
}
}
return false
})
}
}
func (c *Compiler) rewriteEquals() {
modified := false
for _, name := range c.sorted {
mod := c.Modules[name]
modified = rewriteEquals(mod) || modified
}
if modified {
c.Required.addBuiltinSorted(Equal)
}
}
func (c *Compiler) rewriteDynamicTerms() {
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(rule *Rule) bool {
rule.Body = rewriteDynamics(f, rule.Body)
return false
})
}
}
// rewriteTestRuleEqualities rewrites equality expressions in test rule bodies to create local vars for statements that would otherwise
// not have their values captured through tracing, such as refs and comprehensions not unified/assigned to a local var.
// For example, given the following module:
//
// package test
//
// p.q contains v if {
// some v in numbers.range(1, 3)
// }
//
// p.r := "foo"
//
// test_rule {
// p == {
// "q": {4, 5, 6}
// }
// }
//
// `p` in `test_rule` resolves to `data.test.p`, which won't be an entry in the virtual-cache and must therefore be calculated after-the-fact.
// If `p` isn't captured in a local var, there is no trivial way to retrieve its value for test reporting.
func (c *Compiler) rewriteTestRuleEqualities() {
if !c.rewriteTestRulesForTracing {
return
}
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(rule *Rule) bool {
if strings.HasPrefix(string(rule.Head.Name), "test_") {
rule.Body = rewriteTestEqualities(f, rule.Body)
}
return false
})
}
}
func (c *Compiler) parseMetadataBlocks() {
// Only parse annotations if rego.metadata built-ins are called
regoMetadataCalled := false
for _, name := range c.sorted {
mod := c.Modules[name]
WalkExprs(mod, func(expr *Expr) bool {
if isRegoMetadataChainCall(expr) || isRegoMetadataRuleCall(expr) {
regoMetadataCalled = true
}
return regoMetadataCalled
})
if regoMetadataCalled {
break
}
}
if regoMetadataCalled {
// NOTE: Possible optimization: only parse annotations for modules on the path of rego.metadata-calling module
for _, name := range c.sorted {
mod := c.Modules[name]
if len(mod.Annotations) == 0 {
var errs Errors
mod.Annotations, errs = parseAnnotations(mod.Comments)
errs = append(errs, attachAnnotationsNodes(mod)...)
for _, err := range errs {
c.err(err)
}
attachRuleAnnotations(mod)
}
}
}
}
func (c *Compiler) rewriteRegoMetadataCalls() {
eqFactory := newEqualityFactory(c.localvargen)
_, chainFuncAllowed := c.builtins[RegoMetadataChain.Name]
_, ruleFuncAllowed := c.builtins[RegoMetadataRule.Name]
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(rule *Rule) bool {
var firstChainCall *Expr
var firstRuleCall *Expr
WalkExprs(rule, func(expr *Expr) bool {
if chainFuncAllowed && firstChainCall == nil && isRegoMetadataChainCall(expr) {
firstChainCall = expr
} else if ruleFuncAllowed && firstRuleCall == nil && isRegoMetadataRuleCall(expr) {
firstRuleCall = expr
}
return firstChainCall != nil && firstRuleCall != nil
})
chainCalled := firstChainCall != nil
ruleCalled := firstRuleCall != nil
if chainCalled || ruleCalled {
body := make(Body, 0, len(rule.Body)+2)
var metadataChainVar Var
if chainCalled {
// Create and inject metadata chain for rule
chain, err := createMetadataChain(c.annotationSet.Chain(rule))
if err != nil {
c.err(err)
return false
}
chain.Location = firstChainCall.Location
eq := eqFactory.Generate(chain)
metadataChainVar = eq.Operands()[0].Value.(Var)
body.Append(eq)
}
var metadataRuleVar Var
if ruleCalled {
// Create and inject metadata for rule
var metadataRuleTerm *Term
a := getPrimaryRuleAnnotations(c.annotationSet, rule)
if a != nil {
annotObj, err := a.toObject()
if err != nil {
c.err(err)
return false
}
metadataRuleTerm = NewTerm(*annotObj)
} else {
// If rule has no annotations, assign an empty object
metadataRuleTerm = ObjectTerm()
}
metadataRuleTerm.Location = firstRuleCall.Location
eq := eqFactory.Generate(metadataRuleTerm)
metadataRuleVar = eq.Operands()[0].Value.(Var)
body.Append(eq)
}
for _, expr := range rule.Body {
body.Append(expr)
}
rule.Body = body
vis := func(b Body) bool {
for _, err := range rewriteRegoMetadataCalls(&metadataChainVar, &metadataRuleVar, b, &c.RewrittenVars) {
c.err(err)
}
return false
}
WalkBodies(rule.Head, vis)
WalkBodies(rule.Body, vis)
}
return false
})
}
}
func getPrimaryRuleAnnotations(as *AnnotationSet, rule *Rule) *Annotations {
annots := as.GetRuleScope(rule)
if len(annots) == 0 {
return nil
}
// Sort by annotation location; chain must start with annotations declared closest to rule, then going outward
slices.SortStableFunc(annots, func(a, b *Annotations) int {
return -a.Location.Compare(b.Location)
})
return annots[0]
}
func rewriteRegoMetadataCalls(metadataChainVar *Var, metadataRuleVar *Var, body Body, rewrittenVars *map[Var]Var) Errors {
var errs Errors
WalkClosures(body, func(x interface{}) bool {
switch x := x.(type) {
case *ArrayComprehension:
errs = rewriteRegoMetadataCalls(metadataChainVar, metadataRuleVar, x.Body, rewrittenVars)
case *SetComprehension:
errs = rewriteRegoMetadataCalls(metadataChainVar, metadataRuleVar, x.Body, rewrittenVars)
case *ObjectComprehension:
errs = rewriteRegoMetadataCalls(metadataChainVar, metadataRuleVar, x.Body, rewrittenVars)
case *Every:
errs = rewriteRegoMetadataCalls(metadataChainVar, metadataRuleVar, x.Body, rewrittenVars)
}
return true
})
for i := range body {
expr := body[i]
var metadataVar Var
if metadataChainVar != nil && isRegoMetadataChainCall(expr) {
metadataVar = *metadataChainVar
} else if metadataRuleVar != nil && isRegoMetadataRuleCall(expr) {
metadataVar = *metadataRuleVar
} else {
continue
}
// NOTE(johanfylling): An alternative strategy would be to walk the body and replace all operands[0]
// usages with *metadataChainVar
operands := expr.Operands()
var newExpr *Expr
if len(operands) > 0 { // There is an output var to rewrite
rewrittenVar := operands[0]
newExpr = Equality.Expr(rewrittenVar, NewTerm(metadataVar))
} else { // No output var, just rewrite expr to metadataVar
newExpr = NewExpr(NewTerm(metadataVar))
}
newExpr.Generated = true
newExpr.Location = expr.Location
body.Set(newExpr, i)
}
return errs
}
var regoMetadataChainRef = RegoMetadataChain.Ref()
var regoMetadataRuleRef = RegoMetadataRule.Ref()
func isRegoMetadataChainCall(x *Expr) bool {
return x.IsCall() && x.Operator().Equal(regoMetadataChainRef)
}
func isRegoMetadataRuleCall(x *Expr) bool {
return x.IsCall() && x.Operator().Equal(regoMetadataRuleRef)
}
func createMetadataChain(chain []*AnnotationsRef) (*Term, *Error) {
metaArray := NewArray()
for _, link := range chain {
p := link.Path.toArray().
Slice(1, -1) // Dropping leading 'data' element of path
obj := NewObject(
Item(pathTerm, NewTerm(p)),
)
if link.Annotations != nil {
annotObj, err := link.Annotations.toObject()
if err != nil {
return nil, err
}
obj.Insert(annotationsTerm, NewTerm(*annotObj))
}
metaArray = metaArray.Append(NewTerm(obj))
}
return NewTerm(metaArray), nil
}
func (c *Compiler) rewriteLocalVars() {
var assignment bool
for _, name := range c.sorted {
mod := c.Modules[name]
gen := c.localvargen
WalkRules(mod, func(rule *Rule) bool {
argsStack := newLocalDeclaredVars()
args := NewVarVisitor()
if c.strict {
args.Walk(rule.Head.Args)
}
unusedArgs := args.Vars()
c.rewriteLocalArgVars(gen, argsStack, rule)
// Rewrite local vars in each else-branch of the rule.
// Note: this is done instead of a walk so that we can capture any unused function arguments
// across else-branches.
for rule := rule; rule != nil; rule = rule.Else {
stack, errs := c.rewriteLocalVarsInRule(rule, unusedArgs, argsStack, gen)
if stack.assignment {
assignment = true
}
for arg := range unusedArgs {
if stack.Count(arg) > 1 {
delete(unusedArgs, arg)
}
}
for _, err := range errs {
c.err(err)
}
}
if c.strict {
// Report an error for each unused function argument
for arg := range unusedArgs {
if !arg.IsWildcard() {
c.err(NewError(CompileErr, rule.Head.Location, "unused argument %v. (hint: use _ (wildcard variable) instead)", arg))
}
}
}
return true
})
}
if assignment {
c.Required.addBuiltinSorted(Assign)
}
}
func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsStack *localDeclaredVars, gen *localVarGenerator) (*localDeclaredVars, Errors) {
// Rewrite assignments contained in head of rule. Assignments can
// occur in rule head if they're inside a comprehension. Note,
// assigned vars in comprehensions in the head will be rewritten
// first to preserve scoping rules. For example:
//
// p = [x | x := 1] { x := 2 } becomes p = [__local0__ | __local0__ = 1] { __local1__ = 2 }
//
// This behaviour is consistent scoping inside the body. For example:
//
// p = xs { x := 2; xs = [x | x := 1] } becomes p = xs { __local0__ = 2; xs = [__local1__ | __local1__ = 1] }
nestedXform := &rewriteNestedHeadVarLocalTransform{
gen: gen,
RewrittenVars: c.RewrittenVars,
strict: c.strict,
}
NewGenericVisitor(nestedXform.Visit).Walk(rule.Head)
for _, err := range nestedXform.errs {
c.err(err)
}
// Rewrite assignments in body.
used := NewVarSet()
for _, t := range rule.Head.Ref()[1:] {
used.Update(t.Vars())
}
if rule.Head.Key != nil {
used.Update(rule.Head.Key.Vars())
}
if rule.Head.Value != nil {
valueVars := rule.Head.Value.Vars()
used.Update(valueVars)
for arg := range unusedArgs {
if valueVars.Contains(arg) {
delete(unusedArgs, arg)
}
}
}
stack := argsStack.Copy()
body, declared, errs := rewriteLocalVars(gen, stack, used, rule.Body, c.strict)
// For rewritten vars use the collection of all variables that
// were in the stack at some point in time.
maps.Copy(c.RewrittenVars, stack.rewritten)
rule.Body = body
// Rewrite vars in head that refer to locally declared vars in the body.
localXform := rewriteHeadVarLocalTransform{declared: declared}
for i := range rule.Head.Args {
rule.Head.Args[i], _ = transformTerm(localXform, rule.Head.Args[i])
}
for i := 1; i < len(rule.Head.Ref()); i++ {
rule.Head.Reference[i], _ = transformTerm(localXform, rule.Head.Ref()[i])
}
if rule.Head.Key != nil {
rule.Head.Key, _ = transformTerm(localXform, rule.Head.Key)
}
if rule.Head.Value != nil {
rule.Head.Value, _ = transformTerm(localXform, rule.Head.Value)
}
return stack, errs
}
type rewriteNestedHeadVarLocalTransform struct {
gen *localVarGenerator
errs Errors
RewrittenVars map[Var]Var
strict bool
}
func (xform *rewriteNestedHeadVarLocalTransform) Visit(x interface{}) bool {
if term, ok := x.(*Term); ok {
stop := false
stack := newLocalDeclaredVars()
switch x := term.Value.(type) {
case *object:
cpy, _ := x.Map(func(k, v *Term) (*Term, *Term, error) {
kcpy := k.Copy()
NewGenericVisitor(xform.Visit).Walk(kcpy)
vcpy := v.Copy()
NewGenericVisitor(xform.Visit).Walk(vcpy)
return kcpy, vcpy, nil
})
term.Value = cpy
stop = true
case *set:
cpy, _ := x.Map(func(v *Term) (*Term, error) {
vcpy := v.Copy()
NewGenericVisitor(xform.Visit).Walk(vcpy)
return vcpy, nil
})
term.Value = cpy
stop = true
case *ArrayComprehension:
xform.errs = rewriteDeclaredVarsInArrayComprehension(xform.gen, stack, x, xform.errs, xform.strict)
stop = true
case *SetComprehension:
xform.errs = rewriteDeclaredVarsInSetComprehension(xform.gen, stack, x, xform.errs, xform.strict)
stop = true
case *ObjectComprehension:
xform.errs = rewriteDeclaredVarsInObjectComprehension(xform.gen, stack, x, xform.errs, xform.strict)
stop = true
}
maps.Copy(xform.RewrittenVars, stack.rewritten)
return stop
}
return false
}
type rewriteHeadVarLocalTransform struct {
declared map[Var]Var
}
func (xform rewriteHeadVarLocalTransform) Transform(x interface{}) (interface{}, error) {
if v, ok := x.(Var); ok {
if gv, ok := xform.declared[v]; ok {
return gv, nil
}
}
return x, nil
}
func (c *Compiler) rewriteLocalArgVars(gen *localVarGenerator, stack *localDeclaredVars, rule *Rule) {
vis := &ruleArgLocalRewriter{
stack: stack,
gen: gen,
}
for i := range rule.Head.Args {
Walk(vis, rule.Head.Args[i])
}
for i := range vis.errs {
c.err(vis.errs[i])
}
}
type ruleArgLocalRewriter struct {
stack *localDeclaredVars
gen *localVarGenerator
errs []*Error
}
func (vis *ruleArgLocalRewriter) Visit(x interface{}) Visitor {
t, ok := x.(*Term)
if !ok {
return vis
}
switch v := t.Value.(type) {
case Var:
gv, ok := vis.stack.Declared(v)
if ok {
vis.stack.Seen(v)
} else {
gv = vis.gen.Generate()
vis.stack.Insert(v, gv, argVar)
}
t.Value = gv
return nil
case *object:
if cpy, err := v.Map(func(k, v *Term) (*Term, *Term, error) {
vcpy := v.Copy()
Walk(vis, vcpy)
return k, vcpy, nil
}); err != nil {
vis.errs = append(vis.errs, NewError(CompileErr, t.Location, err.Error())) //nolint:govet
} else {
t.Value = cpy
}
return nil
case Null, Boolean, Number, String, *ArrayComprehension, *SetComprehension, *ObjectComprehension, Set:
// Scalars are no-ops. Comprehensions are handled above. Sets must not
// contain variables.
return nil
case Call:
vis.errs = append(vis.errs, NewError(CompileErr, t.Location, "rule arguments cannot contain calls"))
return nil
default:
// Recurse on refs and arrays. Any embedded
// variables can be rewritten.
return vis
}
}
func (c *Compiler) rewriteWithModifiers() {
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
mod := c.Modules[name]
t := NewGenericTransformer(func(x interface{}) (interface{}, error) {
body, ok := x.(Body)
if !ok {
return x, nil
}
body, err := rewriteWithModifiersInBody(c, c.unsafeBuiltinsMap, f, body)
if err != nil {
c.err(err)
}
return body, nil
})
_, _ = Transform(t, mod) // ignore error
}
}
func (c *Compiler) setModuleTree() {
c.ModuleTree = NewModuleTree(c.Modules)
}
func (c *Compiler) setRuleTree() {
c.RuleTree = NewRuleTree(c.ModuleTree)
}
func (c *Compiler) setGraph() {
list := func(r Ref) []*Rule {
return c.GetRulesDynamicWithOpts(r, RulesOptions{IncludeHiddenModules: true})
}
c.Graph = NewGraph(c.Modules, list)
}
type queryCompiler struct {
compiler *Compiler
qctx *QueryContext
typeEnv *TypeEnv
rewritten map[Var]Var
after map[string][]QueryCompilerStageDefinition
unsafeBuiltins map[string]struct{}
comprehensionIndices map[*Term]*ComprehensionIndex
enablePrintStatements bool
}
func newQueryCompiler(compiler *Compiler) QueryCompiler {
qc := &queryCompiler{
compiler: compiler,
qctx: nil,
after: map[string][]QueryCompilerStageDefinition{},
comprehensionIndices: map[*Term]*ComprehensionIndex{},
}
return qc
}
func (qc *queryCompiler) WithStrict(strict bool) QueryCompiler {
qc.compiler.WithStrict(strict)
return qc
}
func (qc *queryCompiler) WithEnablePrintStatements(yes bool) QueryCompiler {
qc.enablePrintStatements = yes
return qc
}
func (qc *queryCompiler) WithContext(qctx *QueryContext) QueryCompiler {
qc.qctx = qctx
return qc
}
func (qc *queryCompiler) WithStageAfter(after string, stage QueryCompilerStageDefinition) QueryCompiler {
qc.after[after] = append(qc.after[after], stage)
return qc
}
func (qc *queryCompiler) WithUnsafeBuiltins(unsafe map[string]struct{}) QueryCompiler {
qc.unsafeBuiltins = unsafe
return qc
}
func (qc *queryCompiler) RewrittenVars() map[Var]Var {
return qc.rewritten
}
func (qc *queryCompiler) ComprehensionIndex(term *Term) *ComprehensionIndex {
if result, ok := qc.comprehensionIndices[term]; ok {
return result
} else if result, ok := qc.compiler.comprehensionIndices[term]; ok {
return result
}
return nil
}
func (qc *queryCompiler) runStage(metricName string, qctx *QueryContext, query Body, s func(*QueryContext, Body) (Body, error)) (Body, error) {
if qc.compiler.metrics != nil {
qc.compiler.metrics.Timer(metricName).Start()
defer qc.compiler.metrics.Timer(metricName).Stop()
}
return s(qctx, query)
}
func (qc *queryCompiler) runStageAfter(metricName string, query Body, s QueryCompilerStage) (Body, error) {
if qc.compiler.metrics != nil {
qc.compiler.metrics.Timer(metricName).Start()
defer qc.compiler.metrics.Timer(metricName).Stop()
}
return s(qc, query)
}
type queryStage = struct {
name string
metricName string
f func(*QueryContext, Body) (Body, error)
}
func (qc *queryCompiler) Compile(query Body) (Body, error) {
if len(query) == 0 {
return nil, Errors{NewError(CompileErr, nil, "empty query cannot be compiled")}
}
query = query.Copy()
stages := []queryStage{
{"CheckKeywordOverrides", "query_compile_stage_check_keyword_overrides", qc.checkKeywordOverrides},
{"ResolveRefs", "query_compile_stage_resolve_refs", qc.resolveRefs},
{"RewriteLocalVars", "query_compile_stage_rewrite_local_vars", qc.rewriteLocalVars},
{"CheckVoidCalls", "query_compile_stage_check_void_calls", qc.checkVoidCalls},
{"RewritePrintCalls", "query_compile_stage_rewrite_print_calls", qc.rewritePrintCalls},
{"RewriteExprTerms", "query_compile_stage_rewrite_expr_terms", qc.rewriteExprTerms},
{"RewriteComprehensionTerms", "query_compile_stage_rewrite_comprehension_terms", qc.rewriteComprehensionTerms},
{"RewriteWithValues", "query_compile_stage_rewrite_with_values", qc.rewriteWithModifiers},
{"CheckUndefinedFuncs", "query_compile_stage_check_undefined_funcs", qc.checkUndefinedFuncs},
{"CheckSafety", "query_compile_stage_check_safety", qc.checkSafety},
{"RewriteDynamicTerms", "query_compile_stage_rewrite_dynamic_terms", qc.rewriteDynamicTerms},
{"CheckTypes", "query_compile_stage_check_types", qc.checkTypes},
{"CheckUnsafeBuiltins", "query_compile_stage_check_unsafe_builtins", qc.checkUnsafeBuiltins},
{"CheckDeprecatedBuiltins", "query_compile_stage_check_deprecated_builtins", qc.checkDeprecatedBuiltins},
}
if qc.compiler.evalMode == EvalModeTopdown {
stages = append(stages, queryStage{"BuildComprehensionIndex", "query_compile_stage_build_comprehension_index", qc.buildComprehensionIndices})
}
qctx := qc.qctx.Copy()
for _, s := range stages {
var err error
query, err = qc.runStage(s.metricName, qctx, query, s.f)
if err != nil {
return nil, qc.applyErrorLimit(err)
}
for _, s := range qc.after[s.name] {
query, err = qc.runStageAfter(s.MetricName, query, s.Stage)
if err != nil {
return nil, qc.applyErrorLimit(err)
}
}
}
return query, nil
}
func (qc *queryCompiler) TypeEnv() *TypeEnv {
return qc.typeEnv
}
func (qc *queryCompiler) applyErrorLimit(err error) error {
var errs Errors
if errors.As(err, &errs) {
if qc.compiler.maxErrs > 0 && len(errs) > qc.compiler.maxErrs {
err = append(errs[:qc.compiler.maxErrs], errLimitReached)
}
}
return err
}
func (qc *queryCompiler) checkKeywordOverrides(_ *QueryContext, body Body) (Body, error) {
if qc.compiler.strict {
if errs := checkRootDocumentOverrides(body); len(errs) > 0 {
return nil, errs
}
}
return body, nil
}
func (qc *queryCompiler) resolveRefs(qctx *QueryContext, body Body) (Body, error) {
var globals map[Var]*usedRef
if qctx != nil {
pkg := qctx.Package
// Query compiler ought to generate a package if one was not provided and one or more imports were provided.
// The generated package name could even be an empty string to avoid conflicts (it doesn't have to be valid syntactically)
if pkg == nil && len(qctx.Imports) > 0 {
pkg = &Package{Path: RefTerm(VarTerm("")).Value.(Ref)}
}
if pkg != nil {
var ruleExports []Ref
rules := qc.compiler.getExports()
if exist, ok := rules.Get(pkg.Path); ok {
ruleExports = exist
}
globals = getGlobals(qctx.Package, ruleExports, qctx.Imports)
qctx.Imports = nil
}
}
ignore := &declaredVarStack{declaredVars(body)}
return resolveRefsInBody(globals, ignore, body), nil
}
func (qc *queryCompiler) rewriteComprehensionTerms(_ *QueryContext, body Body) (Body, error) {
gen := newLocalVarGenerator("q", body)
f := newEqualityFactory(gen)
node, err := rewriteComprehensionTerms(f, body)
if err != nil {
return nil, err
}
return node.(Body), nil
}
func (qc *queryCompiler) rewriteDynamicTerms(_ *QueryContext, body Body) (Body, error) {
gen := newLocalVarGenerator("q", body)
f := newEqualityFactory(gen)
return rewriteDynamics(f, body), nil
}
func (qc *queryCompiler) rewriteExprTerms(_ *QueryContext, body Body) (Body, error) {
gen := newLocalVarGenerator("q", body)
return rewriteExprTermsInBody(gen, body), nil
}
func (qc *queryCompiler) rewriteLocalVars(_ *QueryContext, body Body) (Body, error) {
gen := newLocalVarGenerator("q", body)
stack := newLocalDeclaredVars()
body, _, err := rewriteLocalVars(gen, stack, nil, body, qc.compiler.strict)
if len(err) != 0 {
return nil, err
}
// The vars returned during the rewrite will include all seen vars,
// even if they're not declared with an assignment operation. We don't
// want to include these inside the rewritten set though.
qc.rewritten = maps.Clone(stack.rewritten)
return body, nil
}
func (qc *queryCompiler) rewritePrintCalls(_ *QueryContext, body Body) (Body, error) {
if !qc.enablePrintStatements {
_, cpy := erasePrintCallsInBody(body)
return cpy, nil
}
gen := newLocalVarGenerator("q", body)
if _, errs := rewritePrintCalls(gen, qc.compiler.GetArity, ReservedVars, body); len(errs) > 0 {
return nil, errs
}
return body, nil
}
func (qc *queryCompiler) checkVoidCalls(_ *QueryContext, body Body) (Body, error) {
if errs := checkVoidCalls(qc.compiler.TypeEnv, body); len(errs) > 0 {
return nil, errs
}
return body, nil
}
func (qc *queryCompiler) checkUndefinedFuncs(_ *QueryContext, body Body) (Body, error) {
if errs := checkUndefinedFuncs(qc.compiler.TypeEnv, body, qc.compiler.GetArity, qc.rewritten); len(errs) > 0 {
return nil, errs
}
return body, nil
}
func (qc *queryCompiler) checkSafety(_ *QueryContext, body Body) (Body, error) {
safe := ReservedVars.Copy()
reordered, unsafe := reorderBodyForSafety(qc.compiler.builtins, qc.compiler.GetArity, safe, body)
if errs := safetyErrorSlice(unsafe, qc.RewrittenVars()); len(errs) > 0 {
return nil, errs
}
return reordered, nil
}
func (qc *queryCompiler) checkTypes(_ *QueryContext, body Body) (Body, error) {
var errs Errors
checker := newTypeChecker().
WithSchemaSet(qc.compiler.schemaSet).
WithInputType(qc.compiler.inputType).
WithVarRewriter(rewriteVarsInRef(qc.rewritten, qc.compiler.RewrittenVars))
qc.typeEnv, errs = checker.CheckBody(qc.compiler.TypeEnv, body)
if len(errs) > 0 {
return nil, errs
}
return body, nil
}
func (qc *queryCompiler) checkUnsafeBuiltins(_ *QueryContext, body Body) (Body, error) {
errs := checkUnsafeBuiltins(qc.unsafeBuiltinsMap(), body)
if len(errs) > 0 {
return nil, errs
}
return body, nil
}
func (qc *queryCompiler) unsafeBuiltinsMap() map[string]struct{} {
if qc.unsafeBuiltins != nil {
return qc.unsafeBuiltins
}
return qc.compiler.unsafeBuiltinsMap
}
func (qc *queryCompiler) checkDeprecatedBuiltins(_ *QueryContext, body Body) (Body, error) {
if qc.compiler.strict {
errs := checkDeprecatedBuiltins(qc.compiler.deprecatedBuiltinsMap, body)
if len(errs) > 0 {
return nil, errs
}
}
return body, nil
}
func (qc *queryCompiler) rewriteWithModifiers(_ *QueryContext, body Body) (Body, error) {
f := newEqualityFactory(newLocalVarGenerator("q", body))
body, err := rewriteWithModifiersInBody(qc.compiler, qc.unsafeBuiltinsMap(), f, body)
if err != nil {
return nil, Errors{err}
}
return body, nil
}
func (qc *queryCompiler) buildComprehensionIndices(_ *QueryContext, body Body) (Body, error) {
// NOTE(tsandall): The query compiler does not have a metrics object so we
// cannot record index metrics currently.
_ = buildComprehensionIndices(qc.compiler.debug, qc.compiler.GetArity, ReservedVars, qc.RewrittenVars(), body, qc.comprehensionIndices)
return body, nil
}
// ComprehensionIndex specifies how the comprehension term can be indexed. The keys
// tell the evaluator what variables to use for indexing. In the future, the index
// could be expanded with more information that would allow the evaluator to index
// a larger fragment of comprehensions (e.g., by closing over variables in the outer
// query.)
type ComprehensionIndex struct {
Term *Term
Keys []*Term
}
func (ci *ComprehensionIndex) String() string {
if ci == nil {
return ""
}
return fmt.Sprintf("<keys: %v>", NewArray(ci.Keys...))
}
func buildComprehensionIndices(dbg debug.Debug, arity func(Ref) int, candidates VarSet, rwVars map[Var]Var, node interface{}, result map[*Term]*ComprehensionIndex) uint64 {
var n uint64
cpy := candidates.Copy()
WalkBodies(node, func(b Body) bool {
for _, expr := range b {
index := getComprehensionIndex(dbg, arity, cpy, rwVars, expr)
if index != nil {
result[index.Term] = index
n++
}
// Any variables appearing in the expressions leading up to the comprehension
// are fair-game to be used as index keys.
cpy.Update(expr.Vars(VarVisitorParams{SkipClosures: true, SkipRefCallHead: true}))
}
return false
})
return n
}
func getComprehensionIndex(dbg debug.Debug, arity func(Ref) int, candidates VarSet, rwVars map[Var]Var, expr *Expr) *ComprehensionIndex {
// Ignore everything except <var> = <comprehension> expressions. Extract
// the comprehension term from the expression.
if !expr.IsEquality() || expr.Negated || len(expr.With) > 0 {
// No debug message, these are assumed to be known hinderances
// to comprehension indexing.
return nil
}
var term *Term
lhs, rhs := expr.Operand(0), expr.Operand(1)
if _, ok := lhs.Value.(Var); ok && IsComprehension(rhs.Value) {
term = rhs
} else if _, ok := rhs.Value.(Var); ok && IsComprehension(lhs.Value) {
term = lhs
}
if term == nil {
// no debug for this, it's the ordinary "nothing to do here" case
return nil
}
// Ignore comprehensions that contain expressions that close over variables
// in the outer body if those variables are not also output variables in the
// comprehension body. In other words, ignore comprehensions that we cannot
// safely evaluate without bindings from the outer body. For example:
//
// x = [1]
// [true | data.y[z] = x] # safe to evaluate w/o outer body
// [true | data.y[z] = x[0]] # NOT safe to evaluate because 'x' would be unsafe.
//
// By identifying output variables in the body we also know what to index on by
// intersecting with candidate variables from the outer query.
//
// For example:
//
// x = data.foo[_]
// _ = [y | data.bar[y] = x] # index on 'x'
//
// This query goes from O(data.foo*data.bar) to O(data.foo+data.bar).
var body Body
switch x := term.Value.(type) {
case *ArrayComprehension:
body = x.Body
case *SetComprehension:
body = x.Body
case *ObjectComprehension:
body = x.Body
}
outputs := outputVarsForBody(body, arity, ReservedVars)
unsafe := body.Vars(SafetyCheckVisitorParams).Diff(outputs).Diff(ReservedVars)
if len(unsafe) > 0 {
dbg.Printf("%s: comprehension index: unsafe vars: %v", expr.Location, unsafe)
return nil
}
// Similarly, ignore comprehensions that contain references with output variables
// that intersect with the candidates. Indexing these comprehensions could worsen
// performance.
regressionVis := newComprehensionIndexRegressionCheckVisitor(candidates)
regressionVis.Walk(body)
if regressionVis.worse {
dbg.Printf("%s: comprehension index: output vars intersect candidates", expr.Location)
return nil
}
// Check if any nested comprehensions close over candidates. If any intersection is found
// the comprehension cannot be cached because it would require closing over the candidates
// which the evaluator does not support today.
nestedVis := newComprehensionIndexNestedCandidateVisitor(candidates)
nestedVis.Walk(body)
if nestedVis.found {
dbg.Printf("%s: comprehension index: nested comprehensions close over candidates", expr.Location)
return nil
}
// Make a sorted set of variable names that will serve as the index key set.
// Sort to ensure deterministic indexing. In future this could be relaxed
// if we can decide that one ordering is better than another. If the set is
// empty, there is no indexing to do.
indexVars := candidates.Intersect(outputs)
if len(indexVars) == 0 {
dbg.Printf("%s: comprehension index: no index vars", expr.Location)
return nil
}
result := make([]*Term, 0, len(indexVars))
for v := range indexVars {
result = append(result, NewTerm(v))
}
slices.SortFunc(result, TermValueCompare)
debugRes := make([]*Term, len(result))
for i, r := range result {
if o, ok := rwVars[r.Value.(Var)]; ok {
debugRes[i] = NewTerm(o)
} else {
debugRes[i] = r
}
}
dbg.Printf("%s: comprehension index: built with keys: %v", expr.Location, debugRes)
return &ComprehensionIndex{Term: term, Keys: result}
}
type comprehensionIndexRegressionCheckVisitor struct {
candidates VarSet
seen VarSet
worse bool
}
// TODO(tsandall): Improve this so that users can either supply this list explicitly
// or the information is maintained on the built-in function declaration. What we really
// need to know is whether the built-in function allows callers to push down output
// values or not. It's unlikely that anything outside of OPA does this today so this
// solution is fine for now.
var comprehensionIndexBlacklist = map[string]int{
WalkBuiltin.Name: len(WalkBuiltin.Decl.FuncArgs().Args),
}
func newComprehensionIndexRegressionCheckVisitor(candidates VarSet) *comprehensionIndexRegressionCheckVisitor {
return &comprehensionIndexRegressionCheckVisitor{
candidates: candidates,
seen: NewVarSet(),
}
}
func (vis *comprehensionIndexRegressionCheckVisitor) Walk(x interface{}) {
NewGenericVisitor(vis.visit).Walk(x)
}
func (vis *comprehensionIndexRegressionCheckVisitor) visit(x interface{}) bool {
if !vis.worse {
switch x := x.(type) {
case *Expr:
operands := x.Operands()
if pos := comprehensionIndexBlacklist[x.Operator().String()]; pos > 0 && pos < len(operands) {
vis.assertEmptyIntersection(operands[pos].Vars())
}
case Ref:
vis.assertEmptyIntersection(x.OutputVars())
case Var:
vis.seen.Add(x)
// Always skip comprehensions. We do not have to visit their bodies here.
case *ArrayComprehension, *SetComprehension, *ObjectComprehension:
return true
}
}
return vis.worse
}
func (vis *comprehensionIndexRegressionCheckVisitor) assertEmptyIntersection(vs VarSet) {
for v := range vs {
if vis.candidates.Contains(v) && !vis.seen.Contains(v) {
vis.worse = true
return
}
}
}
type comprehensionIndexNestedCandidateVisitor struct {
candidates VarSet
found bool
}
func newComprehensionIndexNestedCandidateVisitor(candidates VarSet) *comprehensionIndexNestedCandidateVisitor {
return &comprehensionIndexNestedCandidateVisitor{
candidates: candidates,
}
}
func (vis *comprehensionIndexNestedCandidateVisitor) Walk(x interface{}) {
NewGenericVisitor(vis.visit).Walk(x)
}
func (vis *comprehensionIndexNestedCandidateVisitor) visit(x interface{}) bool {
if vis.found {
return true
}
if v, ok := x.(Value); ok && IsComprehension(v) {
varVis := NewVarVisitor().WithParams(VarVisitorParams{SkipRefHead: true})
varVis.Walk(v)
vis.found = len(varVis.Vars().Intersect(vis.candidates)) > 0
return true
}
return false
}
// ModuleTreeNode represents a node in the module tree. The module
// tree is keyed by the package path.
type ModuleTreeNode struct {
Key Value
Modules []*Module
Children map[Value]*ModuleTreeNode
Hide bool
}
func (n *ModuleTreeNode) String() string {
var rules []string
for _, m := range n.Modules {
for _, r := range m.Rules {
rules = append(rules, r.Head.String())
}
}
return fmt.Sprintf("<ModuleTreeNode key:%v children:%v rules:%v hide:%v>", n.Key, n.Children, rules, n.Hide)
}
// NewModuleTree returns a new ModuleTreeNode that represents the root
// of the module tree populated with the given modules.
func NewModuleTree(mods map[string]*Module) *ModuleTreeNode {
root := &ModuleTreeNode{
Children: map[Value]*ModuleTreeNode{},
}
for _, name := range util.KeysSorted(mods) {
m := mods[name]
node := root
for i, x := range m.Package.Path {
c, ok := node.Children[x.Value]
if !ok {
var hide bool
if i == 1 && x.Value.Compare(SystemDocumentKey) == 0 {
hide = true
}
c = &ModuleTreeNode{
Key: x.Value,
Children: map[Value]*ModuleTreeNode{},
Hide: hide,
}
node.Children[x.Value] = c
}
node = c
}
node.Modules = append(node.Modules, m)
}
return root
}
// Size returns the number of modules in the tree.
func (n *ModuleTreeNode) Size() int {
s := len(n.Modules)
for _, c := range n.Children {
s += c.Size()
}
return s
}
// Child returns n's child with key k.
func (n *ModuleTreeNode) child(k Value) *ModuleTreeNode {
switch k.(type) {
case String, Var:
return n.Children[k]
}
return nil
}
// Find dereferences ref along the tree. ref[0] is converted to a String
// for convenience.
func (n *ModuleTreeNode) find(ref Ref) (*ModuleTreeNode, Ref) {
if v, ok := ref[0].Value.(Var); ok {
ref = Ref{StringTerm(string(v))}.Concat(ref[1:])
}
node := n
for i, r := range ref {
next := node.child(r.Value)
if next == nil {
tail := make(Ref, len(ref)-i)
tail[0] = VarTerm(string(ref[i].Value.(String)))
copy(tail[1:], ref[i+1:])
return node, tail
}
node = next
}
return node, nil
}
// DepthFirst performs a depth-first traversal of the module tree rooted at n.
// If f returns true, traversal will not continue to the children of n.
func (n *ModuleTreeNode) DepthFirst(f func(*ModuleTreeNode) bool) {
if f(n) {
return
}
for _, node := range n.Children {
node.DepthFirst(f)
}
}
// TreeNode represents a node in the rule tree. The rule tree is keyed by
// rule path.
type TreeNode struct {
Key Value
Values []any
Children map[Value]*TreeNode
Sorted []Value
Hide bool
}
func (n *TreeNode) String() string {
return fmt.Sprintf("<TreeNode key:%v values:%v sorted:%v hide:%v>", n.Key, n.Values, n.Sorted, n.Hide)
}
// NewRuleTree returns a new TreeNode that represents the root
// of the rule tree populated with the given rules.
func NewRuleTree(mtree *ModuleTreeNode) *TreeNode {
root := TreeNode{
Key: mtree.Key,
}
mtree.DepthFirst(func(m *ModuleTreeNode) bool {
for _, mod := range m.Modules {
if len(mod.Rules) == 0 {
root.add(mod.Package.Path, nil)
}
for _, rule := range mod.Rules {
root.add(rule.Ref().GroundPrefix(), rule)
}
}
return false
})
// ensure that data.system's TreeNode is hidden
node, tail := root.find(DefaultRootRef.Append(NewTerm(SystemDocumentKey)))
if len(tail) == 0 { // found
node.Hide = true
}
root.DepthFirst(func(x *TreeNode) bool {
x.sort()
return false
})
return &root
}
func (n *TreeNode) add(path Ref, rule *Rule) {
node, tail := n.find(path)
if len(tail) > 0 {
sub := treeNodeFromRef(tail, rule)
if node.Children == nil {
node.Children = make(map[Value]*TreeNode, 1)
}
node.Children[sub.Key] = sub
node.Sorted = append(node.Sorted, sub.Key)
} else if rule != nil {
node.Values = append(node.Values, rule)
}
}
// Size returns the number of rules in the tree.
func (n *TreeNode) Size() int {
s := len(n.Values)
for _, c := range n.Children {
s += c.Size()
}
return s
}
// Child returns n's child with key k.
func (n *TreeNode) Child(k Value) *TreeNode {
switch k.(type) {
case Ref, Call:
return nil
default:
return n.Children[k]
}
}
// Find dereferences ref along the tree
func (n *TreeNode) Find(ref Ref) *TreeNode {
node := n
for _, r := range ref {
node = node.Child(r.Value)
if node == nil {
return nil
}
}
return node
}
// Iteratively dereferences ref along the node's subtree.
// - If matching fails immediately, the tail will contain the full ref.
// - Partial matching will result in a tail of non-zero length.
// - A complete match will result in a 0 length tail.
func (n *TreeNode) find(ref Ref) (*TreeNode, Ref) {
node := n
for i := range ref {
next := node.Child(ref[i].Value)
if next == nil {
tail := make(Ref, len(ref)-i)
copy(tail, ref[i:])
return node, tail
}
node = next
}
return node, nil
}
// DepthFirst performs a depth-first traversal of the rule tree rooted at n. If
// f returns true, traversal will not continue to the children of n.
func (n *TreeNode) DepthFirst(f func(*TreeNode) bool) {
if f(n) {
return
}
for _, node := range n.Children {
node.DepthFirst(f)
}
}
func (n *TreeNode) sort() {
slices.SortFunc(n.Sorted, Value.Compare)
}
func treeNodeFromRef(ref Ref, rule *Rule) *TreeNode {
depth := len(ref) - 1
key := ref[depth].Value
node := &TreeNode{
Key: key,
Children: nil,
}
if rule != nil {
node.Values = []any{rule}
}
for i := len(ref) - 2; i >= 0; i-- {
key := ref[i].Value
node = &TreeNode{
Key: key,
Children: map[Value]*TreeNode{ref[i+1].Value: node},
Sorted: []Value{ref[i+1].Value},
}
}
return node
}
// flattenChildren flattens all children's rule refs into a sorted array.
func (n *TreeNode) flattenChildren() []Ref {
ret := newRefSet()
for _, sub := range n.Children { // we only want the children, so don't use n.DepthFirst() right away
sub.DepthFirst(func(x *TreeNode) bool {
for _, r := range x.Values {
rule := r.(*Rule)
ret.AddPrefix(rule.Ref())
}
return false
})
}
slices.SortFunc(ret.s, RefCompare)
return ret.s
}
// Graph represents the graph of dependencies between rules.
type Graph struct {
adj map[util.T]map[util.T]struct{}
radj map[util.T]map[util.T]struct{}
nodes map[util.T]struct{}
sorted []util.T
}
// NewGraph returns a new Graph based on modules. The list function must return
// the rules referred to directly by the ref.
func NewGraph(modules map[string]*Module, list func(Ref) []*Rule) *Graph {
graph := &Graph{
adj: map[util.T]map[util.T]struct{}{},
radj: map[util.T]map[util.T]struct{}{},
nodes: map[util.T]struct{}{},
sorted: nil,
}
// Create visitor to walk a rule AST and add edges to the rule graph for
// each dependency.
vis := func(a *Rule) *GenericVisitor {
stop := false
return NewGenericVisitor(func(x interface{}) bool {
switch x := x.(type) {
case Ref:
for _, b := range list(x) {
for node := b; node != nil; node = node.Else {
graph.addDependency(a, node)
}
}
case *Rule:
if stop {
// Do not recurse into else clauses (which will be handled
// by the outer visitor.)
return true
}
stop = true
}
return false
})
}
// Walk over all rules, add them to graph, and build adjacency lists.
for _, module := range modules {
WalkRules(module, func(a *Rule) bool {
graph.addNode(a)
vis(a).Walk(a)
return false
})
}
return graph
}
// Dependencies returns the set of rules that x depends on.
func (g *Graph) Dependencies(x util.T) map[util.T]struct{} {
return g.adj[x]
}
// Dependents returns the set of rules that depend on x.
func (g *Graph) Dependents(x util.T) map[util.T]struct{} {
return g.radj[x]
}
// Sort returns a slice of rules sorted by dependencies. If a cycle is found,
// ok is set to false.
func (g *Graph) Sort() (sorted []util.T, ok bool) {
if g.sorted != nil {
return g.sorted, true
}
sorter := &graphSort{
sorted: make([]util.T, 0, len(g.nodes)),
deps: g.Dependencies,
marked: map[util.T]struct{}{},
temp: map[util.T]struct{}{},
}
for node := range g.nodes {
if !sorter.Visit(node) {
return nil, false
}
}
g.sorted = sorter.sorted
return g.sorted, true
}
func (g *Graph) addDependency(u util.T, v util.T) {
if _, ok := g.nodes[u]; !ok {
g.addNode(u)
}
if _, ok := g.nodes[v]; !ok {
g.addNode(v)
}
edges, ok := g.adj[u]
if !ok {
edges = map[util.T]struct{}{}
g.adj[u] = edges
}
edges[v] = struct{}{}
edges, ok = g.radj[v]
if !ok {
edges = map[util.T]struct{}{}
g.radj[v] = edges
}
edges[u] = struct{}{}
}
func (g *Graph) addNode(n util.T) {
g.nodes[n] = struct{}{}
}
type graphSort struct {
sorted []util.T
deps func(util.T) map[util.T]struct{}
marked map[util.T]struct{}
temp map[util.T]struct{}
}
func (sort *graphSort) Marked(node util.T) bool {
_, marked := sort.marked[node]
return marked
}
func (sort *graphSort) Visit(node util.T) (ok bool) {
if _, ok := sort.temp[node]; ok {
return false
}
if sort.Marked(node) {
return true
}
sort.temp[node] = struct{}{}
for other := range sort.deps(node) {
if !sort.Visit(other) {
return false
}
}
sort.marked[node] = struct{}{}
delete(sort.temp, node)
sort.sorted = append(sort.sorted, node)
return true
}
// GraphTraversal is a Traversal that understands the dependency graph
type GraphTraversal struct {
graph *Graph
visited map[util.T]struct{}
}
// NewGraphTraversal returns a Traversal for the dependency graph
func NewGraphTraversal(graph *Graph) *GraphTraversal {
return &GraphTraversal{
graph: graph,
visited: map[util.T]struct{}{},
}
}
// Edges lists all dependency connections for a given node
func (g *GraphTraversal) Edges(x util.T) []util.T {
r := []util.T{}
for v := range g.graph.Dependencies(x) {
r = append(r, v)
}
return r
}
// Visited returns whether a node has been visited, setting a node to visited if not
func (g *GraphTraversal) Visited(u util.T) bool {
_, ok := g.visited[u]
g.visited[u] = struct{}{}
return ok
}
type unsafePair struct {
Expr *Expr
Vars VarSet
}
type unsafeVarLoc struct {
Var Var
Loc *Location
}
type unsafeVars map[*Expr]VarSet
func (vs unsafeVars) Add(e *Expr, v Var) {
if u, ok := vs[e]; ok {
u[v] = struct{}{}
} else {
vs[e] = VarSet{v: struct{}{}}
}
}
func (vs unsafeVars) Set(e *Expr, s VarSet) {
vs[e] = s
}
func (vs unsafeVars) Update(o unsafeVars) {
for k, v := range o {
if _, ok := vs[k]; !ok {
vs[k] = VarSet{}
}
vs[k].Update(v)
}
}
func (vs unsafeVars) Vars() (result []unsafeVarLoc) {
locs := map[Var]*Location{}
// If var appears in multiple sets then pick first by location.
for expr, vars := range vs {
for v := range vars {
if locs[v].Compare(expr.Location) > 0 {
locs[v] = expr.Location
}
}
}
for v, loc := range locs {
result = append(result, unsafeVarLoc{
Var: v,
Loc: loc,
})
}
slices.SortFunc(result, func(a, b unsafeVarLoc) int {
return a.Loc.Compare(b.Loc)
})
return result
}
func (vs unsafeVars) Slice() (result []unsafePair) {
for expr, vs := range vs {
result = append(result, unsafePair{
Expr: expr,
Vars: vs,
})
}
return
}
// reorderBodyForSafety returns a copy of the body ordered such that
// left to right evaluation of the body will not encounter unbound variables
// in input positions or negated expressions.
//
// Expressions are added to the re-ordered body as soon as they are considered
// safe. If multiple expressions become safe in the same pass, they are added
// in their original order. This results in minimal re-ordering of the body.
//
// If the body cannot be reordered to ensure safety, the second return value
// contains a mapping of expressions to unsafe variables in those expressions.
func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, globals VarSet, body Body) (Body, unsafeVars) {
bodyVars := body.Vars(SafetyCheckVisitorParams)
reordered := make(Body, 0, len(body))
safe := VarSet{}
unsafe := unsafeVars{}
for _, e := range body {
for v := range e.Vars(SafetyCheckVisitorParams) {
if globals.Contains(v) {
safe.Add(v)
} else {
unsafe.Add(e, v)
}
}
}
for {
n := len(reordered)
for _, e := range body {
if reordered.Contains(e) {
continue
}
ovs := outputVarsForExpr(e, arity, safe)
// check closures: is this expression closing over variables that
// haven't been made safe by what's already included in `reordered`?
vs := unsafeVarsInClosures(e)
cv := vs.Intersect(bodyVars).Diff(globals)
uv := cv.Diff(outputVarsForBody(reordered, arity, safe))
if len(uv) > 0 {
if uv.Equal(ovs) { // special case "closure-self"
continue
}
unsafe.Set(e, uv)
}
for v := range unsafe[e] {
if ovs.Contains(v) || safe.Contains(v) {
delete(unsafe[e], v)
}
}
if len(unsafe[e]) == 0 {
delete(unsafe, e)
reordered.Append(e)
safe.Update(ovs) // this expression's outputs are safe
}
}
if len(reordered) == n { // fixed point, could not add any expr of body
break
}
}
// Recursively visit closures and perform the safety checks on them.
// Update the globals at each expression to include the variables that could
// be closed over.
g := globals.Copy()
for i, e := range reordered {
if i > 0 {
g.Update(reordered[i-1].Vars(SafetyCheckVisitorParams))
}
xform := &bodySafetyTransformer{
builtins: builtins,
arity: arity,
current: e,
globals: g,
unsafe: unsafe,
}
NewGenericVisitor(xform.Visit).Walk(e)
}
return reordered, unsafe
}
type bodySafetyTransformer struct {
builtins map[string]*Builtin
arity func(Ref) int
current *Expr
globals VarSet
unsafe unsafeVars
}
func (xform *bodySafetyTransformer) Visit(x interface{}) bool {
switch term := x.(type) {
case *Term:
switch x := term.Value.(type) {
case *object:
cpy, _ := x.Map(func(k, v *Term) (*Term, *Term, error) {
kcpy := k.Copy()
NewGenericVisitor(xform.Visit).Walk(kcpy)
vcpy := v.Copy()
NewGenericVisitor(xform.Visit).Walk(vcpy)
return kcpy, vcpy, nil
})
term.Value = cpy
return true
case *set:
cpy, _ := x.Map(func(v *Term) (*Term, error) {
vcpy := v.Copy()
NewGenericVisitor(xform.Visit).Walk(vcpy)
return vcpy, nil
})
term.Value = cpy
return true
case *ArrayComprehension:
xform.reorderArrayComprehensionSafety(x)
return true
case *ObjectComprehension:
xform.reorderObjectComprehensionSafety(x)
return true
case *SetComprehension:
xform.reorderSetComprehensionSafety(x)
return true
}
case *Expr:
if ev, ok := term.Terms.(*Every); ok {
xform.globals.Update(ev.KeyValueVars())
ev.Body = xform.reorderComprehensionSafety(NewVarSet(), ev.Body)
return true
}
}
return false
}
func (xform *bodySafetyTransformer) reorderComprehensionSafety(tv VarSet, body Body) Body {
bv := body.Vars(SafetyCheckVisitorParams)
bv.Update(xform.globals)
uv := tv.Diff(bv)
for v := range uv {
xform.unsafe.Add(xform.current, v)
}
r, u := reorderBodyForSafety(xform.builtins, xform.arity, xform.globals, body)
if len(u) == 0 {
return r
}
xform.unsafe.Update(u)
return body
}
func (xform *bodySafetyTransformer) reorderArrayComprehensionSafety(ac *ArrayComprehension) {
ac.Body = xform.reorderComprehensionSafety(ac.Term.Vars(), ac.Body)
}
func (xform *bodySafetyTransformer) reorderObjectComprehensionSafety(oc *ObjectComprehension) {
tv := oc.Key.Vars()
tv.Update(oc.Value.Vars())
oc.Body = xform.reorderComprehensionSafety(tv, oc.Body)
}
func (xform *bodySafetyTransformer) reorderSetComprehensionSafety(sc *SetComprehension) {
sc.Body = xform.reorderComprehensionSafety(sc.Term.Vars(), sc.Body)
}
// unsafeVarsInClosures collects vars that are contained in closures within
// this expression.
func unsafeVarsInClosures(e *Expr) VarSet {
vs := VarSet{}
WalkClosures(e, func(x interface{}) bool {
vis := &VarVisitor{vars: vs}
if ev, ok := x.(*Every); ok {
vis.Walk(ev.Body)
return true
}
vis.Walk(x)
return true
})
return vs
}
// OutputVarsFromBody returns all variables which are the "output" for
// the given body. For safety checks this means that they would be
// made safe by the body.
func OutputVarsFromBody(c *Compiler, body Body, safe VarSet) VarSet {
return outputVarsForBody(body, c.GetArity, safe)
}
func outputVarsForBody(body Body, arity func(Ref) int, safe VarSet) VarSet {
o := safe.Copy()
for _, e := range body {
o.Update(outputVarsForExpr(e, arity, o))
}
return o.Diff(safe)
}
// OutputVarsFromExpr returns all variables which are the "output" for
// the given expression. For safety checks this means that they would be
// made safe by the expr.
func OutputVarsFromExpr(c *Compiler, expr *Expr, safe VarSet) VarSet {
return outputVarsForExpr(expr, c.GetArity, safe)
}
func outputVarsForExpr(expr *Expr, arity func(Ref) int, safe VarSet) VarSet {
// Negated expressions must be safe.
if expr.Negated {
return VarSet{}
}
// With modifier inputs must be safe.
for _, with := range expr.With {
vis := NewVarVisitor().WithParams(SafetyCheckVisitorParams)
vis.Walk(with)
vars := vis.Vars()
unsafe := vars.Diff(safe)
if len(unsafe) > 0 {
return VarSet{}
}
}
switch terms := expr.Terms.(type) {
case *Term:
return outputVarsForTerms(expr, safe)
case []*Term:
if expr.IsEquality() {
return outputVarsForExprEq(expr, safe)
}
operator, ok := terms[0].Value.(Ref)
if !ok {
return VarSet{}
}
ar := arity(operator)
if ar < 0 {
return VarSet{}
}
return outputVarsForExprCall(expr, ar, safe, terms)
case *Every:
return outputVarsForTerms(terms.Domain, safe)
default:
panic("illegal expression")
}
}
func outputVarsForExprEq(expr *Expr, safe VarSet) VarSet {
if !validEqAssignArgCount(expr) {
return safe
}
output := outputVarsForTerms(expr, safe)
output.Update(safe)
output.Update(Unify(output, expr.Operand(0), expr.Operand(1)))
return output.Diff(safe)
}
func outputVarsForExprCall(expr *Expr, arity int, safe VarSet, terms []*Term) VarSet {
output := outputVarsForTerms(expr, safe)
numInputTerms := arity + 1
if numInputTerms >= len(terms) {
return output
}
params := VarVisitorParams{
SkipClosures: true,
SkipSets: true,
SkipObjectKeys: true,
SkipRefHead: true,
}
vis := NewVarVisitor().WithParams(params)
vis.Walk(Args(terms[:numInputTerms]))
unsafe := vis.Vars().Diff(output).Diff(safe)
if len(unsafe) > 0 {
return VarSet{}
}
vis = NewVarVisitor().WithParams(params)
vis.Walk(Args(terms[numInputTerms:]))
output.Update(vis.vars)
return output
}
func outputVarsForTerms(expr interface{}, safe VarSet) VarSet {
output := VarSet{}
WalkTerms(expr, func(x *Term) bool {
switch r := x.Value.(type) {
case *SetComprehension, *ArrayComprehension, *ObjectComprehension:
return true
case Ref:
if !isRefSafe(r, safe) {
return true
}
output.Update(r.OutputVars())
return false
}
return false
})
return output
}
type equalityFactory struct {
gen *localVarGenerator
}
func newEqualityFactory(gen *localVarGenerator) *equalityFactory {
return &equalityFactory{gen}
}
func (f *equalityFactory) Generate(other *Term) *Expr {
term := NewTerm(f.gen.Generate()).SetLocation(other.Location)
expr := Equality.Expr(term, other)
expr.Generated = true
expr.Location = other.Location
return expr
}
// TODO: Move to internal package?
const LocalVarPrefix = "__local"
type localVarGenerator struct {
exclude VarSet
suffix string
next int
}
func newLocalVarGeneratorForModuleSet(sorted []string, modules map[string]*Module) *localVarGenerator {
exclude := NewVarSet()
vis := &VarVisitor{vars: exclude}
for _, key := range sorted {
vis.Walk(modules[key])
}
return &localVarGenerator{exclude: exclude, next: 0}
}
func newLocalVarGenerator(suffix string, node interface{}) *localVarGenerator {
exclude := NewVarSet()
vis := &VarVisitor{vars: exclude}
vis.Walk(node)
return &localVarGenerator{exclude: exclude, suffix: suffix, next: 0}
}
func (l *localVarGenerator) Generate() Var {
for {
result := Var(LocalVarPrefix + l.suffix + strconv.Itoa(l.next) + "__")
l.next++
if !l.exclude.Contains(result) {
return result
}
}
}
func getGlobals(pkg *Package, rules []Ref, imports []*Import) map[Var]*usedRef {
globals := make(map[Var]*usedRef, len(rules)) // NB: might grow bigger with imports
// Populate globals with exports within the package.
for _, ref := range rules {
v := ref[0].Value.(Var)
globals[v] = &usedRef{ref: pkg.Path.Append(StringTerm(string(v)))}
}
// Populate globals with imports.
for _, imp := range imports {
path := imp.Path.Value.(Ref)
if FutureRootDocument.Equal(path[0]) || RegoRootDocument.Equal(path[0]) {
continue // ignore future and rego imports
}
globals[imp.Name()] = &usedRef{ref: path}
}
return globals
}
func requiresEval(x *Term) bool {
if x == nil {
return false
}
return ContainsRefs(x) || ContainsComprehensions(x)
}
func resolveRef(globals map[Var]*usedRef, ignore *declaredVarStack, ref Ref) Ref {
r := Ref{}
for i, x := range ref {
switch v := x.Value.(type) {
case Var:
if g, ok := globals[v]; ok && !ignore.Contains(v) {
cpy := g.ref.Copy()
for i := range cpy {
cpy[i].SetLocation(x.Location)
}
if i == 0 {
r = cpy
} else {
r = append(r, NewTerm(cpy).SetLocation(x.Location))
}
g.used = true
} else {
r = append(r, x)
}
case Ref, *Array, Object, Set, *ArrayComprehension, *SetComprehension, *ObjectComprehension, Call:
r = append(r, resolveRefsInTerm(globals, ignore, x))
default:
r = append(r, x)
}
}
return r
}
type usedRef struct {
ref Ref
used bool
}
func resolveRefsInRule(globals map[Var]*usedRef, rule *Rule) error {
ignore := &declaredVarStack{}
vars := NewVarSet()
var vis *GenericVisitor
var err error
// Walk args to collect vars and transform body so that callers can shadow
// root documents.
vis = NewGenericVisitor(func(x interface{}) bool {
if err != nil {
return true
}
switch x := x.(type) {
case Var:
vars.Add(x)
// Object keys cannot be pattern matched so only walk values.
case *object:
x.Foreach(func(_, v *Term) {
vis.Walk(v)
})
// Skip terms that could contain vars that cannot be pattern matched.
case Set, *ArrayComprehension, *SetComprehension, *ObjectComprehension, Call:
return true
case *Term:
if _, ok := x.Value.(Ref); ok {
if RootDocumentRefs.Contains(x) {
// We could support args named input, data, etc. however
// this would require rewriting terms in the head and body.
// Preventing root document shadowing is simpler, and
// arguably, will prevent confusing names from being used.
// NOTE: this check is also performed as part of strict-mode in
// checkRootDocumentOverrides.
err = fmt.Errorf("args must not shadow %v (use a different variable name)", x)
return true
}
}
}
return false
})
vis.Walk(rule.Head.Args)
if err != nil {
return err
}
ignore.Push(vars)
ignore.Push(declaredVars(rule.Body))
ref := rule.Head.Ref()
for i := 1; i < len(ref); i++ {
ref[i] = resolveRefsInTerm(globals, ignore, ref[i])
}
if rule.Head.Key != nil {
rule.Head.Key = resolveRefsInTerm(globals, ignore, rule.Head.Key)
}
if rule.Head.Value != nil {
rule.Head.Value = resolveRefsInTerm(globals, ignore, rule.Head.Value)
}
rule.Body = resolveRefsInBody(globals, ignore, rule.Body)
return nil
}
func resolveRefsInBody(globals map[Var]*usedRef, ignore *declaredVarStack, body Body) Body {
r := make([]*Expr, 0, len(body))
for _, expr := range body {
r = append(r, resolveRefsInExpr(globals, ignore, expr))
}
return r
}
func resolveRefsInExpr(globals map[Var]*usedRef, ignore *declaredVarStack, expr *Expr) *Expr {
cpy := *expr
switch ts := expr.Terms.(type) {
case *Term:
cpy.Terms = resolveRefsInTerm(globals, ignore, ts)
case []*Term:
buf := make([]*Term, len(ts))
for i := range ts {
buf[i] = resolveRefsInTerm(globals, ignore, ts[i])
}
cpy.Terms = buf
case *SomeDecl:
if val, ok := ts.Symbols[0].Value.(Call); ok {
cpy.Terms = &SomeDecl{Symbols: []*Term{CallTerm(resolveRefsInTermSlice(globals, ignore, val)...)}}
}
case *Every:
locals := NewVarSet()
if ts.Key != nil {
locals.Update(ts.Key.Vars())
}
locals.Update(ts.Value.Vars())
ignore.Push(locals)
cpy.Terms = &Every{
Key: ts.Key.Copy(), // TODO(sr): do more?
Value: ts.Value.Copy(), // TODO(sr): do more?
Domain: resolveRefsInTerm(globals, ignore, ts.Domain),
Body: resolveRefsInBody(globals, ignore, ts.Body),
}
ignore.Pop()
}
for _, w := range cpy.With {
w.Target = resolveRefsInTerm(globals, ignore, w.Target)
w.Value = resolveRefsInTerm(globals, ignore, w.Value)
}
return &cpy
}
func resolveRefsInTerm(globals map[Var]*usedRef, ignore *declaredVarStack, term *Term) *Term {
switch v := term.Value.(type) {
case Var:
if g, ok := globals[v]; ok && !ignore.Contains(v) {
cpy := g.ref.Copy()
for i := range cpy {
cpy[i].SetLocation(term.Location)
}
g.used = true
return NewTerm(cpy).SetLocation(term.Location)
}
return term
case Ref:
fqn := resolveRef(globals, ignore, v)
cpy := *term
cpy.Value = fqn
return &cpy
case *object:
cpy := *term
cpy.Value, _ = v.Map(func(k, v *Term) (*Term, *Term, error) {
k = resolveRefsInTerm(globals, ignore, k)
v = resolveRefsInTerm(globals, ignore, v)
return k, v, nil
})
return &cpy
case *Array:
cpy := *term
cpy.Value = NewArray(resolveRefsInTermArray(globals, ignore, v)...)
return &cpy
case Call:
cpy := *term
cpy.Value = Call(resolveRefsInTermSlice(globals, ignore, v))
return &cpy
case Set:
s, _ := v.Map(func(e *Term) (*Term, error) {
return resolveRefsInTerm(globals, ignore, e), nil
})
cpy := *term
cpy.Value = s
return &cpy
case *ArrayComprehension:
ac := &ArrayComprehension{}
ignore.Push(declaredVars(v.Body))
ac.Term = resolveRefsInTerm(globals, ignore, v.Term)
ac.Body = resolveRefsInBody(globals, ignore, v.Body)
cpy := *term
cpy.Value = ac
ignore.Pop()
return &cpy
case *ObjectComprehension:
oc := &ObjectComprehension{}
ignore.Push(declaredVars(v.Body))
oc.Key = resolveRefsInTerm(globals, ignore, v.Key)
oc.Value = resolveRefsInTerm(globals, ignore, v.Value)
oc.Body = resolveRefsInBody(globals, ignore, v.Body)
cpy := *term
cpy.Value = oc
ignore.Pop()
return &cpy
case *SetComprehension:
sc := &SetComprehension{}
ignore.Push(declaredVars(v.Body))
sc.Term = resolveRefsInTerm(globals, ignore, v.Term)
sc.Body = resolveRefsInBody(globals, ignore, v.Body)
cpy := *term
cpy.Value = sc
ignore.Pop()
return &cpy
default:
return term
}
}
func resolveRefsInTermArray(globals map[Var]*usedRef, ignore *declaredVarStack, terms *Array) []*Term {
cpy := make([]*Term, terms.Len())
for i := range terms.Len() {
cpy[i] = resolveRefsInTerm(globals, ignore, terms.Elem(i))
}
return cpy
}
func resolveRefsInTermSlice(globals map[Var]*usedRef, ignore *declaredVarStack, terms []*Term) []*Term {
cpy := make([]*Term, len(terms))
for i := range terms {
cpy[i] = resolveRefsInTerm(globals, ignore, terms[i])
}
return cpy
}
type declaredVarStack []VarSet
func (s declaredVarStack) Contains(v Var) bool {
for i := len(s) - 1; i >= 0; i-- {
if _, ok := s[i][v]; ok {
return ok
}
}
return false
}
func (s declaredVarStack) Add(v Var) {
s[len(s)-1].Add(v)
}
func (s *declaredVarStack) Push(vs VarSet) {
*s = append(*s, vs)
}
func (s *declaredVarStack) Pop() {
curr := *s
*s = curr[:len(curr)-1]
}
func declaredVars(x interface{}) VarSet {
vars := NewVarSet()
vis := NewGenericVisitor(func(x interface{}) bool {
switch x := x.(type) {
case *Expr:
if x.IsAssignment() && validEqAssignArgCount(x) {
WalkVars(x.Operand(0), func(v Var) bool {
vars.Add(v)
return false
})
} else if decl, ok := x.Terms.(*SomeDecl); ok {
for i := range decl.Symbols {
switch val := decl.Symbols[i].Value.(type) {
case Var:
vars.Add(val)
case Call:
args := val[1:]
if len(args) == 3 { // some x, y in xs
WalkVars(args[1], func(v Var) bool {
vars.Add(v)
return false
})
}
// some x in xs
WalkVars(args[0], func(v Var) bool {
vars.Add(v)
return false
})
}
}
}
case *ArrayComprehension, *SetComprehension, *ObjectComprehension:
return true
}
return false
})
vis.Walk(x)
return vars
}
// rewriteComprehensionTerms will rewrite comprehensions so that the term part
// is bound to a variable in the body. This allows any type of term to be used
// in the term part (even if the term requires evaluation.)
//
// For instance, given the following comprehension:
//
// [x[0] | x = y[_]; y = [1,2,3]]
//
// The comprehension would be rewritten as:
//
// [__local0__ | x = y[_]; y = [1,2,3]; __local0__ = x[0]]
func rewriteComprehensionTerms(f *equalityFactory, node interface{}) (interface{}, error) {
return TransformComprehensions(node, func(x interface{}) (Value, error) {
switch x := x.(type) {
case *ArrayComprehension:
if requiresEval(x.Term) {
expr := f.Generate(x.Term)
x.Term = expr.Operand(0)
x.Body.Append(expr)
}
return x, nil
case *SetComprehension:
if requiresEval(x.Term) {
expr := f.Generate(x.Term)
x.Term = expr.Operand(0)
x.Body.Append(expr)
}
return x, nil
case *ObjectComprehension:
if requiresEval(x.Key) {
expr := f.Generate(x.Key)
x.Key = expr.Operand(0)
x.Body.Append(expr)
}
if requiresEval(x.Value) {
expr := f.Generate(x.Value)
x.Value = expr.Operand(0)
x.Body.Append(expr)
}
return x, nil
}
panic("illegal type")
})
}
// rewriteEquals will rewrite exprs under x as unification calls instead of ==
// calls. For example:
//
// data.foo == data.bar is rewritten as data.foo = data.bar
//
// This stage should only run the safety check (since == is a built-in with no
// outputs, so the inputs must not be marked as safe.)
//
// This stage is not executed by the query compiler by default because when
// callers specify == instead of = they expect to receive a true/false/undefined
// result back whereas with = the result is only ever true/undefined. For
// partial evaluation cases we do want to rewrite == to = to simplify the
// result.
func rewriteEquals(x interface{}) (modified bool) {
doubleEq := Equal.Ref()
unifyOp := Equality.Ref()
t := NewGenericTransformer(func(x interface{}) (interface{}, error) {
if x, ok := x.(*Expr); ok && x.IsCall() {
operator := x.Operator()
if operator.Equal(doubleEq) && len(x.Operands()) == 2 {
modified = true
x.SetOperator(NewTerm(unifyOp))
}
}
return x, nil
})
_, _ = Transform(t, x) // ignore error
return modified
}
func rewriteTestEqualities(f *equalityFactory, body Body) Body {
result := make(Body, 0, len(body))
for _, expr := range body {
// We can't rewrite negated expressions; if the extracted term is undefined, evaluation would fail before
// reaching the negation check.
if !expr.Negated && !expr.Generated {
switch {
case expr.IsEquality():
terms := expr.Terms.([]*Term)
result, terms[1] = rewriteDynamicsShallow(expr, f, terms[1], result)
result, terms[2] = rewriteDynamicsShallow(expr, f, terms[2], result)
case expr.IsEvery():
// We rewrite equalities inside of every-bodies as a fail here will be the cause of the test-rule fail.
// Failures inside other expressions with closures, such as comprehensions, won't cause the test-rule to fail, so we skip those.
every := expr.Terms.(*Every)
every.Body = rewriteTestEqualities(f, every.Body)
}
}
result = appendExpr(result, expr)
}
return result
}
func rewriteDynamicsShallow(original *Expr, f *equalityFactory, term *Term, result Body) (Body, *Term) {
switch term.Value.(type) {
case Ref, *ArrayComprehension, *SetComprehension, *ObjectComprehension:
generated := f.Generate(term)
generated.With = original.With
result.Append(generated)
connectGeneratedExprs(original, generated)
return result, result[len(result)-1].Operand(0)
}
return result, term
}
// rewriteDynamics will rewrite the body so that dynamic terms (i.e., refs and
// comprehensions) are bound to vars earlier in the query. This translation
// results in eager evaluation.
//
// For instance, given the following query:
//
// foo(data.bar) = 1
//
// The rewritten version will be:
//
// __local0__ = data.bar; foo(__local0__) = 1
func rewriteDynamics(f *equalityFactory, body Body) Body {
result := make(Body, 0, len(body))
for _, expr := range body {
switch {
case expr.IsEquality():
result = rewriteDynamicsEqExpr(f, expr, result)
case expr.IsCall():
result = rewriteDynamicsCallExpr(f, expr, result)
case expr.IsEvery():
result = rewriteDynamicsEveryExpr(f, expr, result)
default:
result = rewriteDynamicsTermExpr(f, expr, result)
}
}
return result
}
func appendExpr(body Body, expr *Expr) Body {
body.Append(expr)
return body
}
func rewriteDynamicsEqExpr(f *equalityFactory, expr *Expr, result Body) Body {
if !validEqAssignArgCount(expr) {
return appendExpr(result, expr)
}
terms := expr.Terms.([]*Term)
result, terms[1] = rewriteDynamicsInTerm(expr, f, terms[1], result)
result, terms[2] = rewriteDynamicsInTerm(expr, f, terms[2], result)
return appendExpr(result, expr)
}
func rewriteDynamicsCallExpr(f *equalityFactory, expr *Expr, result Body) Body {
terms := expr.Terms.([]*Term)
for i := 1; i < len(terms); i++ {
result, terms[i] = rewriteDynamicsOne(expr, f, terms[i], result)
}
return appendExpr(result, expr)
}
func rewriteDynamicsEveryExpr(f *equalityFactory, expr *Expr, result Body) Body {
ev := expr.Terms.(*Every)
result, ev.Domain = rewriteDynamicsOne(expr, f, ev.Domain, result)
ev.Body = rewriteDynamics(f, ev.Body)
return appendExpr(result, expr)
}
func rewriteDynamicsTermExpr(f *equalityFactory, expr *Expr, result Body) Body {
term := expr.Terms.(*Term)
result, expr.Terms = rewriteDynamicsInTerm(expr, f, term, result)
return appendExpr(result, expr)
}
func rewriteDynamicsInTerm(original *Expr, f *equalityFactory, term *Term, result Body) (Body, *Term) {
switch v := term.Value.(type) {
case Ref:
for i := 1; i < len(v); i++ {
result, v[i] = rewriteDynamicsOne(original, f, v[i], result)
}
case *ArrayComprehension:
v.Body = rewriteDynamics(f, v.Body)
case *SetComprehension:
v.Body = rewriteDynamics(f, v.Body)
case *ObjectComprehension:
v.Body = rewriteDynamics(f, v.Body)
default:
result, term = rewriteDynamicsOne(original, f, term, result)
}
return result, term
}
func rewriteDynamicsOne(original *Expr, f *equalityFactory, term *Term, result Body) (Body, *Term) {
switch v := term.Value.(type) {
case Ref:
for i := 1; i < len(v); i++ {
result, v[i] = rewriteDynamicsOne(original, f, v[i], result)
}
generated := f.Generate(term)
generated.With = original.With
result.Append(generated)
connectGeneratedExprs(original, generated)
return result, result[len(result)-1].Operand(0)
case *Array:
for i := range v.Len() {
var t *Term
result, t = rewriteDynamicsOne(original, f, v.Elem(i), result)
v.set(i, t)
}
return result, term
case *object:
cpy := NewObject()
v.Foreach(func(key, value *Term) {
result, key = rewriteDynamicsOne(original, f, key, result)
result, value = rewriteDynamicsOne(original, f, value, result)
cpy.Insert(key, value)
})
return result, NewTerm(cpy).SetLocation(term.Location)
case Set:
cpy := NewSet()
for _, term := range v.Slice() {
var rw *Term
result, rw = rewriteDynamicsOne(original, f, term, result)
cpy.Add(rw)
}
return result, NewTerm(cpy).SetLocation(term.Location)
case *ArrayComprehension:
var extra *Expr
v.Body, extra = rewriteDynamicsComprehensionBody(original, f, v.Body, term)
result.Append(extra)
connectGeneratedExprs(original, extra)
return result, result[len(result)-1].Operand(0)
case *SetComprehension:
var extra *Expr
v.Body, extra = rewriteDynamicsComprehensionBody(original, f, v.Body, term)
result.Append(extra)
connectGeneratedExprs(original, extra)
return result, result[len(result)-1].Operand(0)
case *ObjectComprehension:
var extra *Expr
v.Body, extra = rewriteDynamicsComprehensionBody(original, f, v.Body, term)
result.Append(extra)
connectGeneratedExprs(original, extra)
return result, result[len(result)-1].Operand(0)
}
return result, term
}
func rewriteDynamicsComprehensionBody(original *Expr, f *equalityFactory, body Body, term *Term) (Body, *Expr) {
body = rewriteDynamics(f, body)
generated := f.Generate(term)
generated.With = original.With
return body, generated
}
func rewriteExprTermsInHead(gen *localVarGenerator, rule *Rule) {
for i := range rule.Head.Args {
support, output := expandExprTerm(gen, rule.Head.Args[i])
for j := range support {
rule.Body.Append(support[j])
}
rule.Head.Args[i] = output
}
if rule.Head.Key != nil {
support, output := expandExprTerm(gen, rule.Head.Key)
for i := range support {
rule.Body.Append(support[i])
}
rule.Head.Key = output
}
if rule.Head.Value != nil {
support, output := expandExprTerm(gen, rule.Head.Value)
for i := range support {
rule.Body.Append(support[i])
}
rule.Head.Value = output
}
}
func rewriteExprTermsInBody(gen *localVarGenerator, body Body) Body {
cpy := make(Body, 0, len(body))
for i := range body {
for _, expr := range expandExpr(gen, body[i]) {
cpy.Append(expr)
}
}
return cpy
}
func expandExpr(gen *localVarGenerator, expr *Expr) (result []*Expr) {
for i := range expr.With {
extras, value := expandExprTerm(gen, expr.With[i].Value)
expr.With[i].Value = value
result = append(result, extras...)
}
switch terms := expr.Terms.(type) {
case *Term:
extras, term := expandExprTerm(gen, terms)
if len(expr.With) > 0 {
for i := range extras {
extras[i].With = expr.With
}
}
result = append(result, extras...)
expr.Terms = term
result = append(result, expr)
case []*Term:
for i := 1; i < len(terms); i++ {
var extras []*Expr
extras, terms[i] = expandExprTerm(gen, terms[i])
connectGeneratedExprs(expr, extras...)
if len(expr.With) > 0 {
for i := range extras {
extras[i].With = expr.With
}
}
result = append(result, extras...)
}
result = append(result, expr)
case *Every:
var extras []*Expr
term := NewTerm(gen.Generate()).SetLocation(terms.Domain.Location)
eq := Equality.Expr(term, terms.Domain).SetLocation(terms.Domain.Location)
eq.Generated = true
eq.With = expr.With
extras = expandExpr(gen, eq)
terms.Domain = term
terms.Body = rewriteExprTermsInBody(gen, terms.Body)
result = append(result, extras...)
result = append(result, expr)
}
return
}
func connectGeneratedExprs(parent *Expr, children ...*Expr) {
for _, child := range children {
child.generatedFrom = parent
parent.generates = append(parent.generates, child)
}
}
func expandExprTerm(gen *localVarGenerator, term *Term) (support []*Expr, output *Term) {
output = term
switch v := term.Value.(type) {
case Call:
for i := 1; i < len(v); i++ {
var extras []*Expr
extras, v[i] = expandExprTerm(gen, v[i])
support = append(support, extras...)
}
output = NewTerm(gen.Generate()).SetLocation(term.Location)
expr := v.MakeExpr(output).SetLocation(term.Location)
expr.Generated = true
support = append(support, expr)
case Ref:
support = expandExprRef(gen, v)
case *Array:
support = expandExprTermArray(gen, v)
case *object:
cpy, _ := v.Map(func(k, v *Term) (*Term, *Term, error) {
extras1, expandedKey := expandExprTerm(gen, k)
extras2, expandedValue := expandExprTerm(gen, v)
support = append(support, extras1...)
support = append(support, extras2...)
return expandedKey, expandedValue, nil
})
output = NewTerm(cpy).SetLocation(term.Location)
case Set:
cpy, _ := v.Map(func(x *Term) (*Term, error) {
extras, expanded := expandExprTerm(gen, x)
support = append(support, extras...)
return expanded, nil
})
output = NewTerm(cpy).SetLocation(term.Location)
case *ArrayComprehension:
support, term := expandExprTerm(gen, v.Term)
for i := range support {
v.Body.Append(support[i])
}
v.Term = term
v.Body = rewriteExprTermsInBody(gen, v.Body)
case *SetComprehension:
support, term := expandExprTerm(gen, v.Term)
for i := range support {
v.Body.Append(support[i])
}
v.Term = term
v.Body = rewriteExprTermsInBody(gen, v.Body)
case *ObjectComprehension:
support, key := expandExprTerm(gen, v.Key)
for i := range support {
v.Body.Append(support[i])
}
v.Key = key
support, value := expandExprTerm(gen, v.Value)
for i := range support {
v.Body.Append(support[i])
}
v.Value = value
v.Body = rewriteExprTermsInBody(gen, v.Body)
}
return
}
func expandExprRef(gen *localVarGenerator, v []*Term) (support []*Expr) {
// Start by calling a normal expandExprTerm on all terms.
support = expandExprTermSlice(gen, v)
// Rewrite references in order to support indirect references. We rewrite
// e.g.
//
// [1, 2, 3][i]
//
// to
//
// __local_var = [1, 2, 3]
// __local_var[i]
//
// to support these. This only impacts the reference subject, i.e. the
// first item in the slice.
var subject = v[0]
switch subject.Value.(type) {
case *Array, Object, Set, *ArrayComprehension, *SetComprehension, *ObjectComprehension, Call:
f := newEqualityFactory(gen)
assignToLocal := f.Generate(subject)
support = append(support, assignToLocal)
v[0] = assignToLocal.Operand(0)
}
return
}
func expandExprTermArray(gen *localVarGenerator, arr *Array) (support []*Expr) {
for i := range arr.Len() {
extras, v := expandExprTerm(gen, arr.Elem(i))
arr.set(i, v)
support = append(support, extras...)
}
return
}
func expandExprTermSlice(gen *localVarGenerator, v []*Term) (support []*Expr) {
for i := 0; i < len(v); i++ {
var extras []*Expr
extras, v[i] = expandExprTerm(gen, v[i])
support = append(support, extras...)
}
return
}
type localDeclaredVars struct {
vars []*declaredVarSet
// rewritten contains a mapping of *all* user-defined variables
// that have been rewritten whereas vars contains the state
// from the current query (not any nested queries, and all vars
// seen).
rewritten map[Var]Var
// indicates if an assignment (:= operator) has been seen *ever*
assignment bool
}
type varOccurrence int
const (
newVar varOccurrence = iota
argVar
seenVar
assignedVar
declaredVar
)
type declaredVarSet struct {
vs map[Var]Var
reverse map[Var]Var
occurrence map[Var]varOccurrence
count map[Var]int
}
func newDeclaredVarSet() *declaredVarSet {
return &declaredVarSet{
vs: map[Var]Var{},
reverse: map[Var]Var{},
occurrence: map[Var]varOccurrence{},
count: map[Var]int{},
}
}
func newLocalDeclaredVars() *localDeclaredVars {
return &localDeclaredVars{
vars: []*declaredVarSet{newDeclaredVarSet()},
rewritten: map[Var]Var{},
}
}
func (s *localDeclaredVars) Copy() *localDeclaredVars {
stack := &localDeclaredVars{
vars: []*declaredVarSet{},
rewritten: map[Var]Var{},
}
for i := range s.vars {
stack.vars = append(stack.vars, newDeclaredVarSet())
maps.Copy(stack.vars[0].vs, s.vars[i].vs)
maps.Copy(stack.vars[0].reverse, s.vars[i].reverse)
maps.Copy(stack.vars[0].occurrence, s.vars[i].occurrence)
maps.Copy(stack.vars[0].count, s.vars[i].count)
}
maps.Copy(stack.rewritten, s.rewritten)
return stack
}
func (s *localDeclaredVars) Push() {
s.vars = append(s.vars, newDeclaredVarSet())
}
func (s *localDeclaredVars) Pop() *declaredVarSet {
sl := s.vars
curr := sl[len(sl)-1]
s.vars = sl[:len(sl)-1]
return curr
}
func (s localDeclaredVars) Peek() *declaredVarSet {
return s.vars[len(s.vars)-1]
}
func (s localDeclaredVars) Insert(x, y Var, occurrence varOccurrence) {
elem := s.vars[len(s.vars)-1]
elem.vs[x] = y
elem.reverse[y] = x
elem.occurrence[x] = occurrence
elem.count[x] = 1
// If the variable has been rewritten (where x != y, with y being
// the generated value), store it in the map of rewritten vars.
// Assume that the generated values are unique for the compilation.
if !x.Equal(y) {
s.rewritten[y] = x
}
}
func (s localDeclaredVars) Declared(x Var) (y Var, ok bool) {
for i := len(s.vars) - 1; i >= 0; i-- {
if y, ok = s.vars[i].vs[x]; ok {
return
}
}
return
}
// Occurrence returns a flag that indicates whether x has occurred in the
// current scope.
func (s localDeclaredVars) Occurrence(x Var) varOccurrence {
return s.vars[len(s.vars)-1].occurrence[x]
}
// GlobalOccurrence returns a flag that indicates whether x has occurred in the
// global scope.
func (s localDeclaredVars) GlobalOccurrence(x Var) (varOccurrence, bool) {
for i := len(s.vars) - 1; i >= 0; i-- {
if occ, ok := s.vars[i].occurrence[x]; ok {
return occ, true
}
}
return newVar, false
}
// Seen marks x as seen by incrementing its counter
func (s localDeclaredVars) Seen(x Var) {
for i := len(s.vars) - 1; i >= 0; i-- {
dvs := s.vars[i]
if c, ok := dvs.count[x]; ok {
dvs.count[x] = c + 1
return
}
}
s.vars[len(s.vars)-1].count[x] = 1
}
// Count returns how many times x has been seen
func (s localDeclaredVars) Count(x Var) int {
for i := len(s.vars) - 1; i >= 0; i-- {
if c, ok := s.vars[i].count[x]; ok {
return c
}
}
return 0
}
// rewriteLocalVars rewrites bodies to remove assignment/declaration
// expressions. For example:
//
// a := 1; p[a]
//
// Is rewritten to:
//
// __local0__ = 1; p[__local0__]
//
// During rewriting, assignees are validated to prevent use before declaration.
func rewriteLocalVars(g *localVarGenerator, stack *localDeclaredVars, used VarSet, body Body, strict bool) (Body, map[Var]Var, Errors) {
var errs Errors
body, errs = rewriteDeclaredVarsInBody(g, stack, used, body, errs, strict)
return body, stack.Peek().vs, errs
}
func rewriteDeclaredVarsInBody(g *localVarGenerator, stack *localDeclaredVars, used VarSet, body Body, errs Errors, strict bool) (Body, Errors) {
var cpy Body
for i := range body {
var expr *Expr
switch {
case body[i].IsAssignment():
stack.assignment = true
expr, errs = rewriteDeclaredAssignment(g, stack, body[i], errs, strict)
case body[i].IsSome():
expr, errs = rewriteSomeDeclStatement(g, stack, body[i], errs, strict)
case body[i].IsEvery():
expr, errs = rewriteEveryStatement(g, stack, body[i], errs, strict)
default:
expr, errs = rewriteDeclaredVarsInExpr(g, stack, body[i], errs, strict)
}
if expr != nil {
cpy.Append(expr)
}
}
// If the body only contained a var statement it will be empty at this
// point. Append true to the body to ensure that it's non-empty (zero length
// bodies are not supported.)
if len(cpy) == 0 {
cpy.Append(NewExpr(BooleanTerm(true)))
}
errs = checkUnusedAssignedVars(body, stack, used, errs, strict)
return cpy, checkUnusedDeclaredVars(body, stack, used, cpy, errs)
}
func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, errs Errors, strict bool) Errors {
if !strict || len(errs) > 0 {
return errs
}
dvs := stack.Peek()
unused := NewVarSet()
for v, occ := range dvs.occurrence {
// A var that was assigned in this scope must have been seen (used) more than once (the time of assignment) in
// the same, or nested, scope to be counted as used.
if !v.IsWildcard() && stack.Count(v) <= 1 && occ == assignedVar {
unused.Add(dvs.vs[v])
}
}
rewrittenUsed := NewVarSet()
for v := range used {
if gv, ok := stack.Declared(v); ok {
rewrittenUsed.Add(gv)
} else {
rewrittenUsed.Add(v)
}
}
unused = unused.Diff(rewrittenUsed)
for _, gv := range unused.Sorted() {
found := false
for i := range body {
if body[i].Vars(VarVisitorParams{}).Contains(gv) {
errs = append(errs, NewError(CompileErr, body[i].Loc(), "assigned var %v unused", dvs.reverse[gv]))
found = true
break
}
}
if !found {
errs = append(errs, NewError(CompileErr, body[0].Loc(), "assigned var %v unused", dvs.reverse[gv]))
}
}
return errs
}
func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, cpy Body, errs Errors) Errors {
// NOTE(tsandall): Do not generate more errors if there are existing
// declaration errors.
if len(errs) > 0 {
return errs
}
dvs := stack.Peek()
declared := NewVarSet()
for v, occ := range dvs.occurrence {
if occ == declaredVar {
declared.Add(dvs.vs[v])
}
}
bodyvars := cpy.Vars(VarVisitorParams{})
for v := range used {
if gv, ok := stack.Declared(v); ok {
bodyvars.Add(gv)
} else {
bodyvars.Add(v)
}
}
unused := declared.Diff(bodyvars).Diff(used)
for _, gv := range unused.Sorted() {
rv := dvs.reverse[gv]
if !rv.IsGenerated() {
// Scan through body exprs, looking for a match between the
// bad var's original name, and each expr's declared vars.
foundUnusedVarByName := false
for i := range body {
varsDeclaredInExpr := declaredVars(body[i])
if varsDeclaredInExpr.Contains(dvs.reverse[gv]) {
// TODO(philipc): Clean up the offset logic here when the parser
// reports more accurate locations.
errs = append(errs, NewError(CompileErr, body[i].Loc(), "declared var %v unused", dvs.reverse[gv]))
foundUnusedVarByName = true
break
}
}
// Default error location returned.
if !foundUnusedVarByName {
errs = append(errs, NewError(CompileErr, body[0].Loc(), "declared var %v unused", dvs.reverse[gv]))
}
}
}
return errs
}
func rewriteEveryStatement(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) {
e := expr.Copy()
every := e.Terms.(*Every)
errs = rewriteDeclaredVarsInTermRecursive(g, stack, every.Domain, errs, strict)
stack.Push()
defer stack.Pop()
// if the key exists, rewrite
if every.Key != nil {
if v := every.Key.Value.(Var); !v.IsWildcard() {
gv, err := rewriteDeclaredVar(g, stack, v, declaredVar)
if err != nil {
return nil, append(errs, NewError(CompileErr, every.Loc(), err.Error())) //nolint:govet
}
every.Key.Value = gv
}
} else { // if the key doesn't exist, add dummy local
every.Key = NewTerm(g.Generate())
}
// value is always present
if v := every.Value.Value.(Var); !v.IsWildcard() {
gv, err := rewriteDeclaredVar(g, stack, v, declaredVar)
if err != nil {
return nil, append(errs, NewError(CompileErr, every.Loc(), err.Error())) //nolint:govet
}
every.Value.Value = gv
}
used := NewVarSet()
every.Body, errs = rewriteDeclaredVarsInBody(g, stack, used, every.Body, errs, strict)
return rewriteDeclaredVarsInExpr(g, stack, e, errs, strict)
}
func rewriteSomeDeclStatement(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) {
e := expr.Copy()
decl := e.Terms.(*SomeDecl)
for i := range decl.Symbols {
switch v := decl.Symbols[i].Value.(type) {
case Var:
if _, err := rewriteDeclaredVar(g, stack, v, declaredVar); err != nil {
return nil, append(errs, NewError(CompileErr, decl.Loc(), err.Error())) //nolint:govet
}
case Call:
var key, val, container *Term
switch len(v) {
case 4: // member3
key = v[1]
val = v[2]
container = v[3]
case 3: // member
key = NewTerm(g.Generate())
val = v[1]
container = v[2]
}
var rhs *Term
switch c := container.Value.(type) {
case Ref:
rhs = RefTerm(append(c, key)...)
default:
rhs = RefTerm(container, key)
}
e.Terms = []*Term{
RefTerm(VarTerm(Equality.Name)), val, rhs,
}
for _, v0 := range outputVarsForExprEq(e, container.Vars()).Sorted() {
if _, err := rewriteDeclaredVar(g, stack, v0, declaredVar); err != nil {
return nil, append(errs, NewError(CompileErr, decl.Loc(), err.Error())) //nolint:govet
}
}
return rewriteDeclaredVarsInExpr(g, stack, e, errs, strict)
}
}
return nil, errs
}
func rewriteDeclaredVarsInExpr(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) {
vis := NewGenericVisitor(func(x interface{}) bool {
var stop bool
switch x := x.(type) {
case *Term:
stop, errs = rewriteDeclaredVarsInTerm(g, stack, x, errs, strict)
case *With:
stop, errs = true, rewriteDeclaredVarsInWithRecursive(g, stack, x, errs, strict)
}
return stop
})
vis.Walk(expr)
return expr, errs
}
func rewriteDeclaredAssignment(g *localVarGenerator, stack *localDeclaredVars, expr *Expr, errs Errors, strict bool) (*Expr, Errors) {
if expr.Negated {
errs = append(errs, NewError(CompileErr, expr.Location, "cannot assign vars inside negated expression"))
return expr, errs
}
numErrsBefore := len(errs)
if !validEqAssignArgCount(expr) {
return expr, errs
}
// Rewrite terms on right hand side capture seen vars and recursively
// process comprehensions before left hand side is processed. Also
// rewrite with modifier.
errs = rewriteDeclaredVarsInTermRecursive(g, stack, expr.Operand(1), errs, strict)
for _, w := range expr.With {
errs = rewriteDeclaredVarsInTermRecursive(g, stack, w.Value, errs, strict)
}
// Rewrite vars on left hand side with unique names. Catch redeclaration
// and invalid term types here.
var vis func(t *Term) bool
vis = func(t *Term) bool {
switch v := t.Value.(type) {
case Var:
if gv, err := rewriteDeclaredVar(g, stack, v, assignedVar); err != nil {
errs = append(errs, NewError(CompileErr, t.Location, err.Error())) //nolint:govet
} else {
t.Value = gv
}
return true
case *Array:
return false
case *object:
v.Foreach(func(_, v *Term) {
WalkTerms(v, vis)
})
return true
case Ref:
if RootDocumentRefs.Contains(t) {
if gv, err := rewriteDeclaredVar(g, stack, v[0].Value.(Var), assignedVar); err != nil {
errs = append(errs, NewError(CompileErr, t.Location, err.Error())) //nolint:govet
} else {
t.Value = gv
}
return true
}
}
errs = append(errs, NewError(CompileErr, t.Location, "cannot assign to %v", ValueName(t.Value)))
return true
}
WalkTerms(expr.Operand(0), vis)
if len(errs) == numErrsBefore {
loc := expr.Operator()[0].Location
expr.SetOperator(RefTerm(VarTerm(Equality.Name).SetLocation(loc)).SetLocation(loc))
}
return expr, errs
}
func rewriteDeclaredVarsInTerm(g *localVarGenerator, stack *localDeclaredVars, term *Term, errs Errors, strict bool) (bool, Errors) {
switch v := term.Value.(type) {
case Var:
if gv, ok := stack.Declared(v); ok {
term.Value = gv
stack.Seen(v)
} else if stack.Occurrence(v) == newVar {
stack.Insert(v, v, seenVar)
}
case Ref:
if RootDocumentRefs.Contains(term) {
x := v[0].Value.(Var)
if occ, ok := stack.GlobalOccurrence(x); ok && occ != seenVar {
gv, _ := stack.Declared(x)
term.Value = gv
}
return true, errs
}
return false, errs
case Call:
ref := v[0]
WalkVars(ref, func(v Var) bool {
if gv, ok := stack.Declared(v); ok && !gv.Equal(v) {
// We will rewrite the ref of a function call, which is never ok since we don't have first-class functions.
errs = append(errs, NewError(CompileErr, term.Location, "called function %s shadowed", ref))
return true
}
return false
})
return false, errs
case *object:
cpy, _ := v.Map(func(k, v *Term) (*Term, *Term, error) {
kcpy := k.Copy()
errs = rewriteDeclaredVarsInTermRecursive(g, stack, kcpy, errs, strict)
errs = rewriteDeclaredVarsInTermRecursive(g, stack, v, errs, strict)
return kcpy, v, nil
})
term.Value = cpy
case Set:
cpy, _ := v.Map(func(elem *Term) (*Term, error) {
elemcpy := elem.Copy()
errs = rewriteDeclaredVarsInTermRecursive(g, stack, elemcpy, errs, strict)
return elemcpy, nil
})
term.Value = cpy
case *ArrayComprehension:
errs = rewriteDeclaredVarsInArrayComprehension(g, stack, v, errs, strict)
case *SetComprehension:
errs = rewriteDeclaredVarsInSetComprehension(g, stack, v, errs, strict)
case *ObjectComprehension:
errs = rewriteDeclaredVarsInObjectComprehension(g, stack, v, errs, strict)
default:
return false, errs
}
return true, errs
}
func rewriteDeclaredVarsInTermRecursive(g *localVarGenerator, stack *localDeclaredVars, term *Term, errs Errors, strict bool) Errors {
WalkTerms(term, func(t *Term) bool {
var stop bool
stop, errs = rewriteDeclaredVarsInTerm(g, stack, t, errs, strict)
return stop
})
return errs
}
func rewriteDeclaredVarsInWithRecursive(g *localVarGenerator, stack *localDeclaredVars, w *With, errs Errors, strict bool) Errors {
// NOTE(sr): `with input as` and `with input.a.b.c as` are deliberately skipped here: `input` could
// have been shadowed by a local variable/argument but should NOT be replaced in the `with` target.
//
// We cannot drop `input` from the stack since it's conceivable to do `with input[input] as` where
// the second input is meant to be the local var. It's a terrible idea, but when you're shadowing
// `input` those might be your thing.
errs = rewriteDeclaredVarsInTermRecursive(g, stack, w.Target, errs, strict)
if sdwInput, ok := stack.Declared(InputRootDocument.Value.(Var)); ok { // Was "input" shadowed...
switch value := w.Target.Value.(type) {
case Var:
if sdwInput.Equal(value) { // ...and replaced? If so, fix it
w.Target.Value = InputRootRef
}
case Ref:
if sdwInput.Equal(value[0].Value.(Var)) {
w.Target.Value.(Ref)[0].Value = InputRootDocument.Value
}
}
}
// No special handling of the `with` value
return rewriteDeclaredVarsInTermRecursive(g, stack, w.Value, errs, strict)
}
func rewriteDeclaredVarsInArrayComprehension(g *localVarGenerator, stack *localDeclaredVars, v *ArrayComprehension, errs Errors, strict bool) Errors {
used := NewVarSet()
used.Update(v.Term.Vars())
stack.Push()
v.Body, errs = rewriteDeclaredVarsInBody(g, stack, used, v.Body, errs, strict)
errs = rewriteDeclaredVarsInTermRecursive(g, stack, v.Term, errs, strict)
stack.Pop()
return errs
}
func rewriteDeclaredVarsInSetComprehension(g *localVarGenerator, stack *localDeclaredVars, v *SetComprehension, errs Errors, strict bool) Errors {
used := NewVarSet()
used.Update(v.Term.Vars())
stack.Push()
v.Body, errs = rewriteDeclaredVarsInBody(g, stack, used, v.Body, errs, strict)
errs = rewriteDeclaredVarsInTermRecursive(g, stack, v.Term, errs, strict)
stack.Pop()
return errs
}
func rewriteDeclaredVarsInObjectComprehension(g *localVarGenerator, stack *localDeclaredVars, v *ObjectComprehension, errs Errors, strict bool) Errors {
used := NewVarSet()
used.Update(v.Key.Vars())
used.Update(v.Value.Vars())
stack.Push()
v.Body, errs = rewriteDeclaredVarsInBody(g, stack, used, v.Body, errs, strict)
errs = rewriteDeclaredVarsInTermRecursive(g, stack, v.Key, errs, strict)
errs = rewriteDeclaredVarsInTermRecursive(g, stack, v.Value, errs, strict)
stack.Pop()
return errs
}
func rewriteDeclaredVar(g *localVarGenerator, stack *localDeclaredVars, v Var, occ varOccurrence) (gv Var, err error) {
switch stack.Occurrence(v) {
case seenVar:
return gv, fmt.Errorf("var %v referenced above", v)
case assignedVar:
return gv, fmt.Errorf("var %v assigned above", v)
case declaredVar:
return gv, fmt.Errorf("var %v declared above", v)
case argVar:
return gv, fmt.Errorf("arg %v redeclared", v)
}
gv = g.Generate()
stack.Insert(v, gv, occ)
return
}
// rewriteWithModifiersInBody will rewrite the body so that with modifiers do
// not contain terms that require evaluation as values. If this function
// encounters an invalid with modifier target then it will raise an error.
func rewriteWithModifiersInBody(c *Compiler, unsafeBuiltinsMap map[string]struct{}, f *equalityFactory, body Body) (Body, *Error) {
var result Body
for i := range body {
exprs, err := rewriteWithModifier(c, unsafeBuiltinsMap, f, body[i])
if err != nil {
return nil, err
}
if len(exprs) > 0 {
for _, expr := range exprs {
result.Append(expr)
}
} else {
result.Append(body[i])
}
}
return result, nil
}
func rewriteWithModifier(c *Compiler, unsafeBuiltinsMap map[string]struct{}, f *equalityFactory, expr *Expr) ([]*Expr, *Error) {
var result []*Expr
for i := range expr.With {
eval, err := validateWith(c, unsafeBuiltinsMap, expr, i)
if err != nil {
return nil, err
}
if eval {
eq := f.Generate(expr.With[i].Value)
result = append(result, eq)
expr.With[i].Value = eq.Operand(0)
}
}
return append(result, expr), nil
}
func validateWith(c *Compiler, unsafeBuiltinsMap map[string]struct{}, expr *Expr, i int) (bool, *Error) {
target, value := expr.With[i].Target, expr.With[i].Value
// Ensure that values that are built-ins are rewritten to Ref (not Var)
if v, ok := value.Value.(Var); ok {
if _, ok := c.builtins[v.String()]; ok {
value.Value = Ref([]*Term{NewTerm(v)})
}
}
isBuiltinRefOrVar, err := isBuiltinRefOrVar(c.builtins, unsafeBuiltinsMap, target)
if err != nil {
return false, err
}
isAllowedUnknownFuncCall := false
if c.allowUndefinedFuncCalls {
switch target.Value.(type) {
case Ref, Var:
isAllowedUnknownFuncCall = true
}
}
switch {
case isDataRef(target):
ref := target.Value.(Ref)
targetNode := c.RuleTree
for i := range len(ref) - 1 {
child := targetNode.Child(ref[i].Value)
if child == nil {
break
} else if len(child.Values) > 0 {
return false, NewError(CompileErr, target.Loc(), "with keyword cannot partially replace virtual document(s)")
}
targetNode = child
}
if targetNode != nil {
// NOTE(sr): at this point in the compiler stages, we don't have a fully-populated
// TypeEnv yet -- so we have to make do with this check to see if the replacement
// target is a function. It's probably wrong for arity-0 functions, but those are
// and edge case anyways.
if child := targetNode.Child(ref[len(ref)-1].Value); child != nil {
for _, v := range child.Values {
if len(v.(*Rule).Head.Args) > 0 {
if ok, err := validateWithFunctionValue(c.builtins, unsafeBuiltinsMap, c.RuleTree, value); err != nil || ok {
return false, err // err may be nil
}
}
}
}
}
// If the with-value is a ref to a function, but not a call, we can't rewrite it
if r, ok := value.Value.(Ref); ok {
// TODO: check that target ref doesn't exist?
if valueNode := c.RuleTree.Find(r); valueNode != nil {
for _, v := range valueNode.Values {
if len(v.(*Rule).Head.Args) > 0 {
return false, nil
}
}
}
}
case isInputRef(target): // ok, valid
case isBuiltinRefOrVar:
// NOTE(sr): first we ensure that parsed Var builtins (`count`, `concat`, etc)
// are rewritten to their proper Ref convention
if v, ok := target.Value.(Var); ok {
target.Value = Ref([]*Term{NewTerm(v)})
}
targetRef := target.Value.(Ref)
bi := c.builtins[targetRef.String()] // safe because isBuiltinRefOrVar checked this
if err := validateWithBuiltinTarget(bi, targetRef, target.Loc()); err != nil {
return false, err
}
if ok, err := validateWithFunctionValue(c.builtins, unsafeBuiltinsMap, c.RuleTree, value); err != nil || ok {
return false, err // err may be nil
}
case isAllowedUnknownFuncCall:
// The target isn't a ref to the input doc, data doc, or a known built-in, but it might be a ref to an unknown built-in.
return false, nil
default:
return false, NewError(TypeErr, target.Location, "with keyword target must reference existing %v, %v, or a function", InputRootDocument, DefaultRootDocument)
}
return requiresEval(value), nil
}
func validateWithBuiltinTarget(bi *Builtin, target Ref, loc *location.Location) *Error {
switch bi.Name {
case Equality.Name,
RegoMetadataChain.Name,
RegoMetadataRule.Name:
return NewError(CompileErr, loc, "with keyword replacing built-in function: replacement of %q invalid", bi.Name)
}
switch {
case target.HasPrefix(Ref([]*Term{VarTerm("internal")})):
return NewError(CompileErr, loc, "with keyword replacing built-in function: replacement of internal function %q invalid", target)
case bi.Relation:
return NewError(CompileErr, loc, "with keyword replacing built-in function: target must not be a relation")
case bi.Decl.Result() == nil:
return NewError(CompileErr, loc, "with keyword replacing built-in function: target must not be a void function")
}
return nil
}
func validateWithFunctionValue(bs map[string]*Builtin, unsafeMap map[string]struct{}, ruleTree *TreeNode, value *Term) (bool, *Error) {
if v, ok := value.Value.(Ref); ok {
if ruleTree.Find(v) != nil { // ref exists in rule tree
return true, nil
}
}
return isBuiltinRefOrVar(bs, unsafeMap, value)
}
func isInputRef(term *Term) bool {
if ref, ok := term.Value.(Ref); ok {
if ref.HasPrefix(InputRootRef) {
return true
}
}
return false
}
func isDataRef(term *Term) bool {
if ref, ok := term.Value.(Ref); ok {
if ref.HasPrefix(DefaultRootRef) {
return true
}
}
return false
}
func isBuiltinRefOrVar(bs map[string]*Builtin, unsafeBuiltinsMap map[string]struct{}, term *Term) (bool, *Error) {
switch v := term.Value.(type) {
case Ref, Var:
if _, ok := unsafeBuiltinsMap[v.String()]; ok {
return false, NewError(CompileErr, term.Location, "with keyword replacing built-in function: target must not be unsafe: %q", v)
}
_, ok := bs[v.String()]
return ok, nil
}
return false, nil
}
func isVirtual(node *TreeNode, ref Ref) bool {
for i := range ref {
child := node.Child(ref[i].Value)
if child == nil {
return false
} else if len(child.Values) > 0 {
return true
}
node = child
}
return true
}
func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var) (result Errors) {
if len(unsafe) == 0 {
return
}
for _, pair := range unsafe.Vars() {
v := pair.Var
if w, ok := rewritten[v]; ok {
v = w
}
if !v.IsGenerated() {
if _, ok := allFutureKeywords[string(v)]; ok {
result = append(result, NewError(UnsafeVarErr, pair.Loc,
"var %[1]v is unsafe (hint: `import future.keywords.%[1]v` to import a future keyword)", v))
continue
}
result = append(result, NewError(UnsafeVarErr, pair.Loc, "var %v is unsafe", v))
}
}
if len(result) > 0 {
return
}
// If the expression contains unsafe generated variables, report which
// expressions are unsafe instead of the variables that are unsafe (since
// the latter are not meaningful to the user.)
pairs := unsafe.Slice()
slices.SortFunc(pairs, func(a, b unsafePair) int {
return a.Expr.Location.Compare(b.Expr.Location)
})
// Report at most one error per generated variable.
seen := NewVarSet()
for _, expr := range pairs {
before := len(seen)
for v := range expr.Vars {
if v.IsGenerated() {
seen.Add(v)
}
}
if len(seen) > before {
result = append(result, NewError(UnsafeVarErr, expr.Expr.Location, "expression is unsafe"))
}
}
return
}
func checkUnsafeBuiltins(unsafeBuiltinsMap map[string]struct{}, node interface{}) Errors {
errs := make(Errors, 0)
WalkExprs(node, func(x *Expr) bool {
if x.IsCall() {
operator := x.Operator().String()
if _, ok := unsafeBuiltinsMap[operator]; ok {
errs = append(errs, NewError(TypeErr, x.Loc(), "unsafe built-in function calls in expression: %v", operator))
}
}
return false
})
return errs
}
func rewriteVarsInRef(vars ...map[Var]Var) varRewriter {
return func(node Ref) Ref {
i, _ := TransformVars(node, func(v Var) (Value, error) {
for _, m := range vars {
if u, ok := m[v]; ok {
return u, nil
}
}
return v, nil
})
return i.(Ref)
}
}
// NOTE(sr): This is duplicated with compile/compile.go; but moving it into another location
// would cause a circular dependency -- the refSet definition needs ast.Ref. If we make it
// public in the ast package, the compile package could take it from there, but it would also
// increase our public interface. Let's reconsider if we need it in a third place.
type refSet struct {
s []Ref
}
func newRefSet(x ...Ref) *refSet {
result := &refSet{}
for i := range x {
result.AddPrefix(x[i])
}
return result
}
// ContainsPrefix returns true if r is prefixed by any of the existing refs in the set.
func (rs *refSet) ContainsPrefix(r Ref) bool {
return slices.ContainsFunc(rs.s, r.HasPrefix)
}
// AddPrefix inserts r into the set if r is not prefixed by any existing
// refs in the set. If any existing refs are prefixed by r, those existing
// refs are removed.
func (rs *refSet) AddPrefix(r Ref) {
if rs.ContainsPrefix(r) {
return
}
cpy := []Ref{r}
for i := range rs.s {
if !rs.s[i].HasPrefix(r) {
cpy = append(cpy, rs.s[i])
}
}
rs.s = cpy
}
// Sorted returns a sorted slice of terms for refs in the set.
func (rs *refSet) Sorted() []*Term {
terms := make([]*Term, len(rs.s))
for i := range rs.s {
terms[i] = NewTerm(rs.s[i])
}
slices.SortFunc(terms, TermValueCompare)
return terms
}