From c9877b37e85be607e3781a579234b546a787ee20 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:37:11 +0300 Subject: [PATCH 1/4] added @filter for dynamic rules --- src/linter/root.go | 7 +++++++ src/rules/parser.go | 24 ++++++++++++++++++++++++ src/rules/rules.go | 6 ++++-- src/tests/rules/rules_test.go | 26 ++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/linter/root.go b/src/linter/root.go index d27157e43..0a3be4793 100644 --- a/src/linter/root.go +++ b/src/linter/root.go @@ -1823,6 +1823,13 @@ func (d *rootWalker) checkFilterSet(m *phpgrep.MatchData, sc *meta.Scope, filter if filter.Pure && !solver.SideEffectFree(d.scope(), d.ctx.st, nil, nn) { return false } + if filter.Regexp != nil { + if vr, ok := nn.(*ir.SimpleVar); ok { + if filter.Regexp.MatchString(vr.Name) { + return true + } + } + } } return true diff --git a/src/rules/parser.go b/src/rules/parser.go index 986d746ee..88b75e1ef 100644 --- a/src/rules/parser.go +++ b/src/rules/parser.go @@ -291,6 +291,30 @@ func (p *parser) parseRuleInfo(st ir.Node, labelStmt ir.Node, proto *Rule) (Rule filter := filterSet[name] filter.Pure = true filterSet[name] = filter + case "filter": + if len(part.Params) != 2 { + return rule, p.errorf(st, "@filter expects exactly 2 param, got %d", len(part.Params)) + } + name := part.Params[0] + if !strings.HasPrefix(name, "$") { + return rule, p.errorf(st, "@pure param must be a phpgrep variable") + } + name = strings.TrimPrefix(name, "$") + found := p.checkForVariableInPattern(name, patternStmt, verifiedVars) + if !found { + return rule, p.errorf(st, "@filter contains a reference to a variable %s that is not present in the pattern", name) + } + if filterSet == nil { + filterSet = map[string]Filter{} + } + regexString := part.Params[1] + filter := filterSet[name] + regex, err := regexp.Compile(regexString) + if err != nil { + return rule, p.errorf(st, "@filter %s: can't compile regexp %s", regexString, err) + } + filter.Regexp = regex + filterSet[name] = filter default: return rule, p.errorf(st, "unknown attribute @%s on line %d", part.Name(), part.Line()) diff --git a/src/rules/rules.go b/src/rules/rules.go index 29adaa39c..5480585bc 100644 --- a/src/rules/rules.go +++ b/src/rules/rules.go @@ -2,6 +2,7 @@ package rules import ( "io" + "regexp" "github.com/VKCOM/noverify/src/ir" "github.com/VKCOM/noverify/src/phpdoc" @@ -117,6 +118,7 @@ func (r *Rule) String() string { // Filter describes constraints that should be applied to a given phpgrep variable. type Filter struct { - Type *phpdoc.Type - Pure bool + Type *phpdoc.Type + Pure bool + Regexp *regexp.Regexp } diff --git a/src/tests/rules/rules_test.go b/src/tests/rules/rules_test.go index 95af8463a..1e32fcc61 100644 --- a/src/tests/rules/rules_test.go +++ b/src/tests/rules/rules_test.go @@ -416,3 +416,29 @@ function bad(string $x) { } test.RunRulesTest() } + +func TestRulesFilter(t *testing.T) { + rfile := ` Date: Mon, 20 Jun 2022 17:42:59 +0300 Subject: [PATCH 2/4] foxed typo --- src/rules/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/parser.go b/src/rules/parser.go index 88b75e1ef..3eb6fd3e0 100644 --- a/src/rules/parser.go +++ b/src/rules/parser.go @@ -297,7 +297,7 @@ func (p *parser) parseRuleInfo(st ir.Node, labelStmt ir.Node, proto *Rule) (Rule } name := part.Params[0] if !strings.HasPrefix(name, "$") { - return rule, p.errorf(st, "@pure param must be a phpgrep variable") + return rule, p.errorf(st, "@filter param must be a phpgrep variable") } name = strings.TrimPrefix(name, "$") found := p.checkForVariableInPattern(name, patternStmt, verifiedVars) From 16db5f217a5375b8e82aa0bd17ee8dd8c31d6a25 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Mon, 20 Jun 2022 17:47:28 +0300 Subject: [PATCH 3/4] updated docs --- docs/dynamic_rules.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/dynamic_rules.md b/docs/dynamic_rules.md index 2ea2bd81b..e4b41dfa6 100644 --- a/docs/dynamic_rules.md +++ b/docs/dynamic_rules.md @@ -422,6 +422,26 @@ function ternarySimplify() { This rule will now don't apply to files with the `common/` folder in the path. +##### `@filter` + +The `@filter` restriction allows you to restrict the rule by name of matched variable. + +Thus, the rule will be applied only if there is a matched variable that not matches passed regexp. + +For example: + +```php +function forbiddenIdUsage() { + /** + * @maybe Not use $id + * @filter $id ^id$ + */ + $id; +} +``` + +This rule will match usage if `$id` variable. + #### Underline location (`@location`) For every warning that NoVerify finds, it underlines the location. However, for dynamic rules, the right place is not always underlined. From 04d05a71f02e06fe7eaa76a45144cf128710dddb Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Mon, 20 Jun 2022 17:48:14 +0300 Subject: [PATCH 4/4] fixed typo --- docs/dynamic_rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dynamic_rules.md b/docs/dynamic_rules.md index e4b41dfa6..5bfa56b69 100644 --- a/docs/dynamic_rules.md +++ b/docs/dynamic_rules.md @@ -426,7 +426,7 @@ This rule will now don't apply to files with the `common/` folder in the path. The `@filter` restriction allows you to restrict the rule by name of matched variable. -Thus, the rule will be applied only if there is a matched variable that not matches passed regexp. +Thus, the rule will be applied only if there is a matched variable that matches passed regexp. For example: