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

gogrep: save and restore wildcard positions during backtracking #211

Merged
merged 1 commit into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions analyzer/testdata/src/regression/issue192.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package regression

import "fmt"

func testIssue192() {
fmt.Print(fmt.Sprintf("abc")) // want `\Qfmt.Printf("abc", )`
fmt.Print(fmt.Sprintf("%d", 10)) // want `\Qfmt.Printf("%d", 10)`
fmt.Print(fmt.Sprintf("%d%d", 1, 2)) // want `\Qfmt.Printf("%d%d", 1, 2)`

fmt.Println(fmt.Sprintf("%d", 10)) // want `\Qfmt.Printf("%d"+"\n", , 10)`
fmt.Println(fmt.Sprintf("%d%d", 10, 20)) // want `\Qfmt.Printf("%d%d"+"\n", 10, 20)`
fmt.Println(fmt.Sprintf("%d%d%s", 10, 20, "abc")) // want `\Qfmt.Printf("%d%d%s"+"\n", 10, 20, "abc")`
}
8 changes: 8 additions & 0 deletions analyzer/testdata/src/regression/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ func issue115(m dsl.Matcher) {
Where(!(m["x"].Const && m["x"].Type.Is("int"))).
Report("$x is not a constexpr int")
}

func issue192(m dsl.Matcher) {
m.Match(`fmt.Print(fmt.Sprintf($format, $*args))`).
Suggest(`fmt.Printf($format, $args)`)

m.Match(`fmt.Println(fmt.Sprintf($format, $*args, $last))`).
Suggest(`fmt.Printf($format+"\n", $args, $last)`)
}
7 changes: 7 additions & 0 deletions internal/mvdan.cc/gogrep/gogrep.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import (
type StmtList = stmtList
type ExprList = exprList

func IsEmptyList(n ast.Node) bool {
if list, ok := n.(nodeList); ok {
return list.len() == 0
}
return false
}

// Parse creates a gogrep pattern out of a given string expression.
func Parse(fset *token.FileSet, expr string, strict bool) (*Pattern, error) {
m := matcher{
Expand Down
74 changes: 52 additions & 22 deletions internal/mvdan.cc/gogrep/gogrep_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,46 @@ func TestCapture(t *testing.T) {
`f(1, 2)`,
vars{"left": "1", "mid": "", "right": "2"},
},
// TODO: #192
// {
// `f($left, $*mid, $right)`,
// `f(1, 2, 3)`,
// vars{"left": "1", "mid": "2", "right": "3"},
// },
// {
// `f($left, $*mid, $right)`,
// `f(1, 2, 3, 4)`,
// vars{"left": "1", "mid": "2, 3", "right": "4"},
// },
{
`f($left, $*mid, $right)`,
`f(1, 2, 3)`,
vars{"left": "1", "mid": "2", "right": "3"},
},
{
`f($left, $*mid, $right)`,
`f(1, 2, 3, 4)`,
vars{"left": "1", "mid": "2, 3", "right": "4"},
},
{
`f($a, $b, $*mid, $c, $d)`,
`f(1, 2, 3, 4)`,
vars{"a": "1", "b": "2", "c": "3", "d": "4", "mid": ""},
},
{
`f($a, $b, $*mid, $c, $d)`,
`f(1, 2, 3, 4, 5)`,
vars{"a": "1", "b": "2", "c": "4", "d": "5", "mid": "3"},
},
{
`f($*left, $x, 0, $*right)`,
`f(1, 2, 7, 0, 1, 0)`,
vars{"left": "1, 2", "x": "7", "right": "1, 0"},
},
{
`f($*left, $x, $*right)`,
`f(1, 2, 7, 0, 1, 0)`,
vars{"left": "", "x": "1", "right": "2, 7, 0, 1, 0"},
},
{
`f($*left, $*right)`,
`f()`,
vars{"right": ""},
},
{
`f($*left, $*right)`,
`f(1, 2)`,
vars{"right": "1, 2"},
},

{
`f($*butlast, "last")`,
Expand Down Expand Up @@ -129,17 +158,16 @@ func TestCapture(t *testing.T) {
`f(1)`,
vars{"butlast": "", "x": "1"},
},
// TODO: #192
// {
// `f($*butlast, $x)`,
// `f(1, 2, 3)`,
// vars{"butlast": "1, 2", "x": "3"},
// },
// {
// `f($first, $*butlast, $x)`,
// `f(1, 2, 3)`,
// vars{"first": "1", "butlast": "2", "x": "2"},
// },
{
`f($*butlast, $x)`,
`f(1, 2, 3)`,
vars{"butlast": "1, 2", "x": "3"},
},
{
`f($first, $*butlast, $x)`,
`f(1, 2, 3)`,
vars{"first": "1", "butlast": "2", "x": "3"},
},
}

emptyFset := token.NewFileSet()
Expand Down Expand Up @@ -303,6 +331,8 @@ func TestMatch(t *testing.T) {

// calls
{`someFunc($x)`, 1, `someFunc(a > b)`},
{`fmt.Printf($format, $*args)`, 1, `g(fmt.Printf("x"))`},
{`fmt.Printf($format, $*args)`, 1, `g(fmt.Printf("%d", 1))`},

// selector
{`$x.Field`, 1, `a.Field`},
Expand Down
10 changes: 7 additions & 3 deletions internal/mvdan.cc/gogrep/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,32 +387,36 @@ func (m *matcher) nodes(ns1, ns2 nodeList, partial bool) (ast.Node, int) {
type restart struct {
matches []CapturedNode
next1, next2 int
wildStart int
wildName string
}
// We need to stack these because otherwise some edge cases
// would not match properly. Since we have various kinds of
// wildcards (nodes containing them, $_, and $*_), in some cases
// we may have to go back and do multiple restarts to get to the
// right starting position.
var stack []restart
wildName := ""
wildStart := 0
push := func(n1, n2 int) {
if n2 > ns2len {
return // would be discarded anyway
}
stack = append(stack, restart{m.capture, n1, n2})
stack = append(stack, restart{m.capture, n1, n2, wildStart, wildName})
next1, next2 = n1, n2
}
pop := func() {
i1, i2 = next1, next2
m.capture = stack[len(stack)-1].matches
wildName = stack[len(stack)-1].wildName
wildStart = stack[len(stack)-1].wildStart
stack = stack[:len(stack)-1]
next1, next2 = 0, 0
if len(stack) > 0 {
next1 = stack[len(stack)-1].next1
next2 = stack[len(stack)-1].next2
}
}
wildName := ""
wildStart := 0

// wouldMatch returns whether the current wildcard - if any -
// matches the nodes we are currently trying it on.
Expand Down
4 changes: 4 additions & 0 deletions ruleguard/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func newRulesRunner(ctx *RunContext, state *engineState, rules *goRuleSet) *rule
}

func (rr *rulesRunner) nodeText(n ast.Node) []byte {
if gogrep.IsEmptyList(n) {
return nil
}

from := rr.ctx.Fset.Position(n.Pos()).Offset
to := rr.ctx.Fset.Position(n.End()).Offset
src := rr.fileBytes()
Expand Down