Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/env/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1534,7 +1534,7 @@ func unmarshalYAML(t *testing.T, data []byte) *Config {
t.Helper()
config, err := ConfigFromYAML(data)
if err != nil {
t.Fatalf("ConfigFromYaml(%q) failed: %v", string(data), err)
t.Fatalf("ConfigFromYAML(%q) failed: %v", string(data), err)
}
return config
}
Expand Down
3 changes: 2 additions & 1 deletion policy/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ go_test(
"//test:go_default_library",
"//common/debug:go_default_library",
"//common/types:go_default_library",
"//interpreter:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
"//interpreter:go_default_library",
"//test/proto3pb:go_default_library",
"@in_yaml_go_yaml_v3//:go_default_library",
"@com_github_google_go_cmp//cmp:go_default_library",
Expand Down
33 changes: 28 additions & 5 deletions policy/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type CompiledRule struct {
id *ValueString
variables []*CompiledVariable
matches []*CompiledMatch
semantic SemanticType
}

// SourceID returns the source metadata identifier associated with the compiled rule.
Expand All @@ -56,11 +57,21 @@ func (r *CompiledRule) Matches() []*CompiledMatch {
return r.matches[:]
}

// Semantic returns the evaluation semantic for the compiled rule.
func (r *CompiledRule) Semantic() SemanticType {
return r.semantic
}

// OutputType returns the output type of the first match clause as all match clauses
// are validated for agreement prior to construction fo the CompiledRule.
func (r *CompiledRule) OutputType() *cel.Type {
// It's a compilation error if the output types of the matches don't agree
for _, m := range r.Matches() {
matches := r.Matches()
if len(matches) > 0 {
m := matches[0]
if r.semantic == aggregate {
return cel.ListType(m.OutputType())
}
return m.OutputType()
}
return cel.DynType
Expand All @@ -69,6 +80,9 @@ func (r *CompiledRule) OutputType() *cel.Type {
// HasOptionalOutput returns whether the rule returns a concrete or optional value.
// The rule may return an optional value if all match expressions under the rule are conditional.
func (r *CompiledRule) HasOptionalOutput() bool {
if r.semantic == aggregate {
return false
}
optionalOutput := false
for _, m := range r.Matches() {
if m.NestedRule() != nil && m.NestedRule().HasOptionalOutput() {
Expand Down Expand Up @@ -297,7 +311,7 @@ func CompileRule(env *cel.Env, p *Policy, opts ...CompilerOption) (*CompiledRule
c.env = env
}
}
return c.compileRule(p.Rule(), p, c.env, iss)
return c.compileRule(p.Rule(), p, c.env, iss, false)
}

type compiler struct {
Expand All @@ -310,7 +324,10 @@ type compiler struct {
nestedCount int
}

func (c *compiler) compileRule(r *Rule, p *Policy, ruleEnv *cel.Env, iss *cel.Issues) (*CompiledRule, *cel.Issues) {
func (c *compiler) compileRule(r *Rule, p *Policy, ruleEnv *cel.Env, iss *cel.Issues, hasAggregateAncestor bool) (*CompiledRule, *cel.Issues) {
if hasAggregateAncestor && r.semantic == aggregate {
iss.ReportErrorAtID(r.SourceID(), "nested aggregate rules are not allowed")
}
compiledVars := make([]*CompiledVariable, len(r.Variables()))
for i, v := range r.Variables() {
exprSrc := c.relSource(v.Expression())
Expand Down Expand Up @@ -379,7 +396,8 @@ func (c *compiler) compileRule(r *Rule, p *Policy, ruleEnv *cel.Env, iss *cel.Is
continue
}
if m.HasRule() {
nestedRule, ruleIss := c.compileRule(m.Rule(), p, ruleEnv, iss)
nextHasAggregateAncestor := hasAggregateAncestor || r.semantic == aggregate
nestedRule, ruleIss := c.compileRule(m.Rule(), p, ruleEnv, iss, nextHasAggregateAncestor)
iss = iss.Append(ruleIss)
compiledMatches = append(compiledMatches, &CompiledMatch{
exprID: m.exprID,
Expand All @@ -401,6 +419,7 @@ func (c *compiler) compileRule(r *Rule, p *Policy, ruleEnv *cel.Env, iss *cel.Is
id: r.id,
variables: compiledVars,
matches: compiledMatches,
semantic: r.semantic,
}

// Note: Consider supporting configurable policy validators that take the policy, rule, and issues
Expand Down Expand Up @@ -453,10 +472,14 @@ func (c *compiler) checkUnreachableCode(rule *CompiledRule, iss *cel.Issues) {
m := compiledMatches[i]
triviallyTrue := m.ConditionIsLiteral(types.True)

if m.ConditionIsLiteral(types.False) {
iss.ReportErrorAtID(m.SourceID(), "Condition is always false")
}

// If the match is a single output or a nested rule that always returns a value, it is
// exhaustive. If the condition is trivially true, then all subsequent branches are unreachable.
isExhaustive := triviallyTrue && (m.NestedRule() == nil || !m.NestedRule().HasOptionalOutput())
if isExhaustive && i != matchCount-1 {
if rule.semantic == firstMatch && isExhaustive && i != matchCount-1 {
if m.Output() != nil {
iss.ReportErrorAtID(m.SourceID(), "match creates unreachable outputs")
}
Expand Down
Loading
Loading