Skip to content

Commit

Permalink
ruleguard: implement Var.Comparable predicate (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
quasilyte authored Mar 20, 2022
1 parent c887ed9 commit b6d904e
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 37 deletions.
63 changes: 63 additions & 0 deletions analyzer/testdata/src/filtertest/f1.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,69 @@ func detectValue() {
valueTest(0, 5, "variadic value 5") // want `false`
}

func detectComparable() {
typeTest("", "comparable") // want `true`
typeTest(0, "comparable") // want `true`

type good1 struct {
x, y int
}
type good2 struct {
nested good1
x [2]byte
s string
}
type good3 struct {
x *int
}
type good4 struct {
*good3
good2
}

typeTest(good1{}, "comparable") // want `true`
typeTest(good2{}, "comparable") // want `true`
typeTest(good3{}, "comparable") // want `true`
typeTest(good4{}, "comparable") // want `true`
typeTest(&good1{}, "comparable") // want `true`
typeTest(&good2{}, "comparable") // want `true`
typeTest(&good3{}, "comparable") // want `true`
typeTest(&good4{}, "comparable") // want `true`

var (
g1 good1
g2 good2
g3 good3
g4 good4
)
_ = g1 == good1{}
_ = g2 == good2{}
_ = g3 == good3{}
_ = g4 == good4{}
_ = g1 != good1{}
_ = g2 != good2{}
_ = g3 != good3{}
_ = g4 != good4{}

type bad1 struct {
_ [1]func()
}
type bad2 struct {
slice []int
}
type bad3 struct {
bad2
}

typeTest(bad1{}, "comparable")
typeTest(bad2{}, "comparable")
typeTest(bad3{}, "comparable")

typeTest(&bad1{}, "comparable") // want `true`
typeTest(&bad2{}, "comparable") // want `true`
typeTest(&bad3{}, "comparable") // want `true`
}

func detectType() {
{
var s fmt.Stringer
Expand Down
4 changes: 4 additions & 0 deletions analyzer/testdata/src/filtertest/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ func testRules(m dsl.Matcher) {
Where(m["x"].Type.IdenticalTo(m["y"])).
Report(`true`)

m.Match(`typeTest($x, "comparable")`).
Where(m["x"].Comparable).
Report(`true`)

m.Match(`$x = time.Now().String()`,
`var $x = time.Now().String()`,
`var $x $_ = time.Now().String()`,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.17
require (
github.com/go-toolsmith/astcopy v1.0.0
github.com/google/go-cmp v0.5.6
github.com/quasilyte/go-ruleguard/dsl v0.3.17
github.com/quasilyte/go-ruleguard/dsl v0.3.18
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71
github.com/quasilyte/gogrep v0.0.0-20220120141003-628d8b3623b5
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1
github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/dsl v0.3.17 h1:L5xf3nifnRIdYe9vyMuY2sDnZHIgQol/fDq74FQz7ZY=
github.com/quasilyte/go-ruleguard/dsl v0.3.17/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/dsl v0.3.18 h1:gzHcFxmTwhn+ZKZd6nGw7JyjoDcYuwcA+TY5MNn9oMk=
github.com/quasilyte/go-ruleguard/dsl v0.3.18/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc=
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71 h1:CNooiryw5aisadVfzneSZPswRWvnVW8hF1bS/vo8ReI=
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
Expand Down
15 changes: 15 additions & 0 deletions ruleguard/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ func makeAddressableFilter(src, varname string) filterFunc {
}
}

func makeComparableFilter(src, varname string) filterFunc {
return func(params *filterParams) matchFilterResult {
if list, ok := params.subNode(varname).(gogrep.ExprSlice); ok {
return exprListFilterApply(src, list, func(x ast.Expr) bool {
return types.Comparable(params.typeofNode(x))
})
}

if types.Comparable(params.typeofNode(params.subNode(varname))) {
return filterSuccess
}
return filterFailure(src)
}
}

func makeVarContainsFilter(src, varname string, pat *gogrep.Pattern) filterFunc {
return func(params *filterParams) matchFilterResult {
params.gogrepSubState.CapturePreset = params.match.CaptureList()
Expand Down
78 changes: 42 additions & 36 deletions ruleguard/ir/filter_op.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ruleguard/ir/gen_filter_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func main() {
{name: "LtEq", comment: "$Args[0] <= $Args[1]", flags: flagIsBinaryExpr},

{name: "VarAddressable", comment: "m[$Value].Addressable", valueType: "string", flags: flagHasVar},
{name: "VarComparable", comment: "m[$Value].Comparable", valueType: "string", flags: flagHasVar},
{name: "VarPure", comment: "m[$Value].Pure", valueType: "string", flags: flagHasVar},
{name: "VarConst", comment: "m[$Value].Const", valueType: "string", flags: flagHasVar},
{name: "VarConstSlice", comment: "m[$Value].ConstSlice", valueType: "string", flags: flagHasVar},
Expand Down
2 changes: 2 additions & 0 deletions ruleguard/ir_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,8 @@ func (l *irLoader) newFilter(filter ir.FilterExpr, info *filterInfo) (matchFilte
result.fn = makeConstSliceFilter(result.src, filter.Value.(string))
case ir.FilterVarAddressableOp:
result.fn = makeAddressableFilter(result.src, filter.Value.(string))
case ir.FilterVarComparableOp:
result.fn = makeComparableFilter(result.src, filter.Value.(string))

case ir.FilterFileImportsOp:
result.fn = makeFileImportsFilter(result.src, filter.Value.(string))
Expand Down
2 changes: 2 additions & 0 deletions ruleguard/irconv/irconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,8 @@ func (conv *converter) convertFilterExprImpl(e ast.Expr) ir.FilterExpr {
return ir.FilterExpr{Op: ir.FilterVarConstSliceOp, Value: op.varName}
case "Addressable":
return ir.FilterExpr{Op: ir.FilterVarAddressableOp, Value: op.varName}
case "Comparable":
return ir.FilterExpr{Op: ir.FilterVarComparableOp, Value: op.varName}
case "Type.Size":
return ir.FilterExpr{Op: ir.FilterVarTypeSizeOp, Value: op.varName}
}
Expand Down
1 change: 1 addition & 0 deletions ruleguard/irconv/irconv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestConvFilterExpr(t *testing.T) {
{`m["x"].Pure`, `(VarPure ["x"])`},
{`m["x"].Const`, `(VarConst ["x"])`},
{`m["x"].Addressable`, `(VarAddressable ["x"])`},
{`m["x"].Comparable`, `(VarComparable ["x"])`},

// Parens should not break the conversion.
{`(m["x"].Pure)`, `(VarPure ["x"])`},
Expand Down

0 comments on commit b6d904e

Please sign in to comment.