Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ast/compile: reorder body for safety differently #4801

Conversation

srenatus
Copy link
Contributor

Our previous approach, ordering for closures first, and taking the growing
set of output variables of the reordered body into account in a second step,
did not work out for some examples:

object.get(input.subject.roles[_], comp, [""], output)
comp = [ 1 | true ]
every y in [2] {
	y in output
}

Here, the closure of every would have not checkout out because we've never
registered the output variable output -- since the first call to object.get
is unsafe without comp, too.

Now, the two stages have been merged. It's got surprisingly little fallout,
one test case had to be adjusted, which I believe to be a minor case, too.

Fixes the second part of #4766.

Our previous approach, ordering for closures first, and taking the growing
set of output variables of the reordered body into account in a second step,
did not work out for some examples:

    object.get(input.subject.roles[_], comp, [""], output)
    comp = [ 1 | true ]
    every y in [2] {
    	y in output
    }

Here, the closure of `every` would have not checkout out because we've never
registered the output variable `output` -- since the first call to object.get
is unsafe without `comp`, too.

Now, the two stages have been merged. It's got surprisingly little fallout,
one test case had to be adjusted, which I believe to be a minor case, too.

Fixes the second part of open-policy-agent#4766.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
@srenatus srenatus force-pushed the sr/ast/reordering-for-closures-and-safety-in-one branch from c7225ca to 7823c8d Compare June 21, 2022 12:05
@srenatus srenatus marked this pull request as ready for review June 21, 2022 12:05
Copy link
Contributor Author

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some annotations and comments for reviewing this 👇


reordered := Body{}
bodyVars := body.Vars(SafetyCheckVisitorParams)
reordered := make(Body, 0, len(body))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

micro-optimizations of little consequence:

  1. the bodyVars never change, so we can collect them outside of the loop (compared to reorderBodyForClosures)
  2. we know how large reordered could become, and usually would, if there are no unsafe variables; so allocate upfront.

@@ -3160,21 +3157,35 @@ func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, glo
continue
}

safe.Update(outputVarsForExpr(e, arity, safe))
ovs := outputVarsForExpr(e, arity, safe)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the ones we've been missing in reorderBodyForClosures: because there, we kept feeding globals as third parameter, instead of the accumulated safe vars, we'd never see output variables for function calls whose inputs only become safe after reordering.

safe.Update(outputVarsForExpr(e, arity, safe))
ovs := outputVarsForExpr(e, arity, safe)

// check closures: is this expression closing over variables that
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically the inlining of reorderBodyForClosures.

uv := cv.Diff(outputVarsForBody(reordered, arity, safe))

if len(uv) > 0 {
if uv.Equal(ovs) { // special case "closure-self"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is ugly, but it's what makes x = [ x | x = 1 ] fail without affecting the other test cases 🙈

`package compr
opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true}

tests := []struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"modernized" the test case structure here, adding the one we care about

object.get(input.subject.roles[_], comp, [""], output)
comp = [ 1 | true ]
every y in [2] {
y in output
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the case that broke PE for #4766.

@@ -797,7 +832,7 @@ func TestCompilerCheckSafetyBodyErrors(t *testing.T) {
{"array-compr-mixed", `p { _ = [x | y = [a | a = z[i]]] }`, `{a, x, z, i}`},
{"array-compr-builtin", `p { [true | eq != 2] }`, `{eq,}`},
{"closure-self", `p { x = [x | x = 1] }`, `{x,}`},
{"closure-transitive", `p { x = y; x = [y | y = 1] }`, `{y,}`},
{"closure-transitive", `p { x = y; x = [y | y = 1] }`, `{x,y}`},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we accept that? I hope so 😅

Copy link
Member

@tsandall tsandall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Before the closure reordering would not see the result of the normal expression reordering. Now, closures are handled in the same pass regular expressions so queries like x = y; y = [1 | true]; [1 | x] no longer get rejected w/ an unsafe error (x in this example).

@srenatus srenatus merged commit 8a3bf90 into open-policy-agent:main Jun 29, 2022
@srenatus srenatus deleted the sr/ast/reordering-for-closures-and-safety-in-one branch June 29, 2022 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants