Skip to content

Commit fb560c1

Browse files
authored
Fix false positive in use-some-for-output-vars (#1731)
Which was reported for nested ref head vars Fixes #1489 Signed-off-by: Anders Eknert <anders@eknert.com>
1 parent 13de8a9 commit fb560c1

4 files changed

Lines changed: 50 additions & 52 deletions

File tree

bundle/regal/ast/search.rego

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -272,14 +272,27 @@ found.expressions[rule_index] contains value if {
272272
value.terms
273273
}
274274

275+
# METADATA
276+
# description: |
277+
# answers whether a variable of the given name (value) is declared in the
278+
# local scope of the provided rule at the provided location
279+
is_in_local_scope(rule, location, value) if {
280+
some var
281+
found.vars[_rule_index(rule)][_][var].value == value
282+
var.location != location
283+
284+
_before_location(rule.head, var, util.to_location_object(location))
285+
}
286+
275287
# METADATA
276288
# description: |
277289
# finds all vars declared in `rule` *before* the `location` provided
278290
# note: this isn't 100% accurate, as it doesn't take into account `=`
279291
# assignments / unification, but it's likely good enough since other rules
280292
# recommend against those
281293
find_vars_in_local_scope(rule, location) := [var |
282-
var := found.vars[_rule_index(rule)][_][_]
294+
some var
295+
found.vars[_rule_index(rule)][_][var]
283296

284297
not startswith(var.value, "$")
285298
_before_location(rule.head, var, util.to_location_object(location))
@@ -309,32 +322,25 @@ _before_location(_, var, loc) if {
309322

310323
# METADATA
311324
# description: find *only* names in the local scope, and not e.g. rule names
312-
find_names_in_local_scope(rule, location) := names if {
313-
fn_arg_names := {arg.value |
314-
some arg in rule.head.args
315-
arg.type == "var"
316-
}
317-
var_names := {var.value | some var in find_vars_in_local_scope(rule, util.to_location_object(location))}
318-
319-
names := fn_arg_names | var_names
325+
find_names_in_local_scope(rule, location) := {var.value |
326+
some var in find_vars_in_local_scope(rule, util.to_location_object(location))
320327
}
321328

322329
# METADATA
323330
# description: |
324331
# similar to `find_vars_in_local_scope`, but returns all variable names in scope
325332
# of the given location *and* the rule names present in the scope (i.e. module)
326-
find_names_in_scope(rule, location) := names if {
327-
locals := find_names_in_local_scope(rule, util.to_location_object(location))
328-
329-
# parens below added by opa-fmt :)
330-
names := (rule_names | imported_identifiers) | locals
331-
}
333+
find_names_in_scope(rule, location) := (rule_names | imported_identifiers) | find_names_in_local_scope(
334+
rule,
335+
util.to_location_object(location),
336+
)
332337

333338
# METADATA
334339
# description: |
335340
# find all variables declared via `some` declarations (and *not* `some .. in`)
336341
# in the scope of the given location
337342
find_some_decl_names_in_scope(rule, location) := {some_var.value |
343+
loc := util.to_location_object(location)
338344
some some_var in found.vars[_rule_index(rule)].some
339-
_before_location(rule.head, some_var, util.to_location_object(location))
345+
_before_location(rule.head, some_var, loc)
340346
}

bundle/regal/rules/idiomatic/use-some-for-output-vars/use_some_for_output_vars.rego

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,39 @@ import data.regal.result
77
import data.regal.util
88

99
report contains violation if {
10-
some rule_index, i
11-
elem := ast.found.refs[rule_index][_].value[i]
10+
some rule_index, term
11+
ast.found.vars[rule_index].ref[term]
1212

13-
# first item can't be a loop or key ref
14-
i != 0
15-
elem.type == "var"
16-
not ast.is_wildcard(elem)
13+
not startswith(term.value, "$")
14+
not term.value in ast.imported_identifiers
15+
not term.value in ast.rule_names
1716

1817
rule := input.rules[to_number(rule_index)]
1918

20-
not elem.value in ast.find_names_in_scope(rule, elem.location)
19+
not ast.is_in_local_scope(rule, term.location, term.value)
2120

22-
path := _location_path(rule, elem.location)
21+
walk(rule, [path, term.location])
2322

24-
not _var_in_comprehension_body(elem, rule, path)
23+
not _var_in_ref_head_declared(path, rule_index, term.value)
24+
not _var_in_comprehension_body(path, term.value, rule)
2525

26-
violation := result.fail(rego.metadata.chain(), result.location(elem))
26+
violation := result.fail(rego.metadata.chain(), result.location(term))
2727
}
2828

29-
_location_path(rule, location) := path if walk(rule, [path, location])
30-
31-
_var_in_comprehension_body(var, rule, path) if {
32-
some v in _comprehension_body_vars(rule, path)
33-
v.type == var.type
34-
v.value == var.value
35-
}
36-
37-
_comprehension_body_vars(rule, path) := [vars |
29+
_var_in_comprehension_body(path, value, rule) if {
3830
some parent_path in array.reverse(util.all_paths(path))
3931

4032
node := object.get(rule, parent_path, {})
4133

4234
node.type in {"arraycomprehension", "objectcomprehension", "setcomprehension"}
4335

44-
vars := ast.find_vars(node.value.body)
45-
][0]
36+
some v in ast.find_vars(node.value.body)
37+
v.type == "var"
38+
v.value == value
39+
}
40+
41+
_var_in_ref_head_declared(path, rule_index, value) if {
42+
path[0] == "head"
43+
path[1] == "ref"
44+
ast.found.vars[rule_index]["some"][_].value == value
45+
}

bundle/regal/rules/idiomatic/use-some-for-output-vars/use_some_for_output_vars_test.rego

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,10 @@ test_success_not_an_output_var if {
160160

161161
r == set()
162162
}
163+
164+
# verify fix for https://github.com/open-policy-agent/regal/issues/1489
165+
test_success_nested_ref_head_var_declared if {
166+
r := rule.report with input as ast.policy(`rule[input[i]] contains i if some i`)
167+
168+
r == set()
169+
}

pkg/linter/linter_bench_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ import (
1111
"github.com/open-policy-agent/regal/pkg/report"
1212
)
1313

14-
// 752941230 ns/op 2513359004 B/op 52228468 allocs/op
15-
// 731859438 ns/op 2481404780 B/op 51310839 allocs/op main
16-
// 720459812 ns/op 2474991988 B/op 51155691 allocs/op
17-
// 704425916 ns/op 2476541140 B/op 51151877 allocs/op
18-
19-
// BenchmarkRegalLintingItself-16 2 717249188 ns/op 2477907556 B/op 51198136 allocs/op
2014
func BenchmarkRegalLintingItself(b *testing.B) {
2115
conf := testutil.Must(config.FromPath(filepath.Join("..", "..", ".regal", "config.yaml")))(b)
2216

@@ -34,8 +28,6 @@ func BenchmarkRegalLintingItself(b *testing.B) {
3428
testutil.AssertNumViolations(b, 0, rep)
3529
}
3630

37-
// 955670792 ns/op 3004937416 B/op 57408554 allocs/op
38-
// ...
3931
func BenchmarkRegalLintingItselfPrepareOnce(b *testing.B) {
4032
conf := testutil.Must(config.FromPath(filepath.Join("..", "..", ".regal", "config.yaml")))(b)
4133

@@ -54,8 +46,6 @@ func BenchmarkRegalLintingItselfPrepareOnce(b *testing.B) {
5446
testutil.AssertNumViolations(b, 0, rep)
5547
}
5648

57-
// 62198250 ns/op 42255370 B/op 985472 allocs/op
58-
// ...
5949
func BenchmarkOnlyPrepare(b *testing.B) {
6050
conf := testutil.Must(config.FromPath(filepath.Join("..", "..", ".regal", "config.yaml")))(b)
6151
linter := NewLinter().WithInputPaths([]string{"../../bundle"}).WithUserConfig(conf)
@@ -65,9 +55,6 @@ func BenchmarkOnlyPrepare(b *testing.B) {
6555
}
6656
}
6757

68-
// 6 168875708 ns/op 455586606 B/op 8537889 allocs/op
69-
// 8 139592615 ns/op 326694376 B/op 5996314 allocs/op
70-
// ...
7158
func BenchmarkRegalNoEnabledRules(b *testing.B) {
7259
linter := NewLinter().
7360
WithInputPaths([]string{"../../bundle"}).
@@ -83,8 +70,6 @@ func BenchmarkRegalNoEnabledRules(b *testing.B) {
8370
testutil.AssertNumViolations(b, 0, rep)
8471
}
8572

86-
// 97546746 ns/op 401323023 B/op 7427903 allocs/op
87-
// ...
8873
func BenchmarkRegalNoEnabledRulesPrepareOnce(b *testing.B) {
8974
linter := NewLinter().
9075
WithInputPaths([]string{"../../bundle"}).

0 commit comments

Comments
 (0)