planner: forget variables from comprehensions (#3327)

Before, variables introduced in a comprehension, like the `x` in

    { x | xs[x] }

would have been recorded in `p.vars`, and have an effect outside of
the scope of this comprehension, leading to wrong plans.

Now, we'll push a new frame for learning about variables we've not
known before, and reset to that state after planning the
comprehensions, thereby forgetting them.

Fixes #3325.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
Stephan Renatus
2021-03-30 13:29:02 +02:00
committed by GitHub
parent a4f3e6a592
commit afe33a5d7c
3 changed files with 65 additions and 0 deletions
+5
View File
@@ -1360,6 +1360,10 @@ func (p *Planner) planObjectComprehension(oc *ast.ObjectComprehension, iter plan
func (p *Planner) planComprehension(body ast.Body, closureIter planiter, target ir.Local, iter planiter) error {
// Variables that have been introduced in this comprehension have
// no effect on other parts of the policy, so they'll be dropped
// below.
p.vars.Push(map[ast.Var]ir.Local{})
prev := p.curr
p.curr = &ir.Block{}
ploc := p.loc
@@ -1373,6 +1377,7 @@ func (p *Planner) planComprehension(body ast.Body, closureIter planiter, target
block := p.curr
p.curr = prev
p.loc = ploc
p.vars.Pop()
p.appendStmt(&ir.BlockStmt{
Blocks: []*ir.Block{
+44
View File
@@ -0,0 +1,44 @@
package planner
import (
"testing"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/internal/ir"
)
func TestVarStackPushPop(t *testing.T) {
p := New()
vs := newVarstack()
vs.Push(map[ast.Var]ir.Local{})
loc0, loc1 := p.newLocal(), p.newLocal()
vs.Put(ast.Var("x"), loc0)
vs.Push(map[ast.Var]ir.Local{})
loc, ok := vs.Get(ast.Var("x"))
if exp, act := true, ok; exp != act {
t.Errorf("Get(x): expected %v, got %v", exp, act)
}
if exp, act := loc0, loc; exp != act {
t.Errorf("Get(x) expected %v, got %v", exp, act)
}
vs.Put(ast.Var("y"), loc1)
pop := vs.Pop()
if exp, act := 1, len(pop); exp != act {
t.Errorf("Pop(): expected len %v, got %v", exp, act)
}
// x still there
loc, ok = vs.Get(ast.Var("x"))
if exp, act := true, ok; exp != act {
t.Errorf("Get(x): expected %v, got %v", exp, act)
}
if exp, act := loc0, loc; exp != act {
t.Errorf("Get(x) expected %v, got %v", exp, act)
}
// y not there
_, ok = vs.Get(ast.Var("y"))
if exp, act := false, ok; exp != act {
t.Errorf("Get(y): expected %v, got %v", exp, act)
}
}
@@ -0,0 +1,16 @@
cases:
- data:
modules:
- |
package test
xs := {"a", "b", "c"}
p = x {
y := { x | xs[x] }
z := { x | xs[x] }
count(y) == count(z)
x := count(y)
}
note: comprehensions/var bindings have no effect outside
query: data.test.p = x
want_result:
- x: 3