compile/compile: Fix panic from CLI + metadata entrypoint overlaps.

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: #6661

Co-authored-by: Daniel Herzig <danielherzig96@gmail.com>
Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
This commit is contained in:
Philip Conrad
2024-04-03 15:05:14 -04:00
committed by Ashutosh Narkar
parent e0060ce6a1
commit 88eaaa93ad
2 changed files with 49 additions and 3 deletions
+27 -1
View File
@@ -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
}
@@ -429,6 +435,27 @@ 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 {
// Build list of entrypoint refs, without duplicates.
newEntrypointRefs := make([]*ast.Term, 0, len(c.entrypointrefs))
entrypointRefSet := make(map[string]struct{}, 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] = struct{}{}
newEntrypointRefs = append(newEntrypointRefs, c.entrypointrefs[i])
}
}
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 {
@@ -506,7 +533,6 @@ func (c *Compiler) initBundle() error {
}
func (c *Compiler) optimize(ctx context.Context) error {
if c.optimizationLevel <= 0 {
var err error
c.compiler, err = compile(c.capabilities, c.bundle, c.debug, c.enablePrintStatements)
+22 -2
View File
@@ -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))
@@ -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{},