Skip to content

Commit

Permalink
compile/compile: Fix panic from CLI + metadata entrypoint overlaps.
Browse files Browse the repository at this point in the history
This commit fixes a panic that could occur when `opa build` was provided
an entrypoint from both a CLI flag, and via entrypoint metadata
annotation.

The fix is simple: deduplicate the slice of entrypoint refs that the
compiler uses, before compiling WASM or Plan targets.

Fixes: open-policy-agent#6661

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
  • Loading branch information
philipaconrad committed Apr 3, 2024
1 parent 9a13941 commit 60111d7
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 2 deletions.
30 changes: 30 additions & 0 deletions compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,12 @@ func (c *Compiler) Build(ctx context.Context) error {
return err
}

// Dedup entrypoint refs, if both CLI and entrypoint metadata annotations
// were used.
if err := c.dedupEntrypointRefs(); err != nil {
return err
}

if err := c.optimize(ctx); err != nil {
return err
}
Expand Down Expand Up @@ -429,6 +435,30 @@ func (c *Compiler) checkNumEntrypoints() error {
return nil
}

// Note(philipc): When an entrypoint is provided on the CLI and from an
// entrypoint annotation, it can lead to duplicates in the slice of
// entrypoint refs. This can cause panics down the line due to c.entrypoints
// being a different length than c.entrypointrefs. As a result, we have to
// trim out the duplicates.
func (c *Compiler) dedupEntrypointRefs() error {
// Discover the distinct entrypoint refs.
entrypointRefSet := make(map[string]int, len(c.entrypointrefs))
for i, r := range c.entrypointrefs {
refString := r.String()
// Store only the first index in the list that matches.
if _, ok := entrypointRefSet[refString]; !ok {
entrypointRefSet[refString] = i
}
}
// Build list of entrypoint refs, without duplicates.
newEntrypointRefs := make([]*ast.Term, 0, len(entrypointRefSet))
for _, idx := range entrypointRefSet {
newEntrypointRefs = append(newEntrypointRefs, c.entrypointrefs[idx])
}
c.entrypointrefs = newEntrypointRefs
return nil
}

// Bundle returns the compiled bundle. This function can be called to retrieve the
// output of the compiler (as an alternative to having the bundle written to a stream.)
func (c *Compiler) Bundle() *bundle.Bundle {
Expand Down
24 changes: 22 additions & 2 deletions compile/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,9 +978,7 @@ update {

func modulesToString(modules []bundle.ModuleFile) string {
var buf bytes.Buffer
//result := make([]string, len(modules))
for i, m := range modules {
//result[i] = m.Parsed.String()
buf.WriteString(strconv.Itoa(i))
buf.WriteString(":\n")
buf.WriteString(string(m.Raw))
Expand Down Expand Up @@ -1623,6 +1621,28 @@ q[3]
"test/p": {},
},
},
{
note: "overlapping manual entrypoints + annotation entrypoints",
entrypoints: []string{"test/p"},
modules: map[string]string{
"test.rego": `
package test
# METADATA
# entrypoint: true
p {
q[input.x]
}
q[1]
q[2]
q[3]
`,
},
wantEntrypoints: map[string]struct{}{
"test/p": {},
},
},
{
note: "ref head rule annotation",
entrypoints: []string{},
Expand Down

0 comments on commit 60111d7

Please sign in to comment.