From b6d904ed09ad874de45ac302cf8db16cfcd21cfe Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Mon, 21 Mar 2022 00:24:34 +0300 Subject: [PATCH] ruleguard: implement Var.Comparable predicate (#382) --- analyzer/testdata/src/filtertest/f1.go | 63 ++++++++++++++++++ analyzer/testdata/src/filtertest/rules.go | 4 ++ go.mod | 2 +- go.sum | 2 + ruleguard/filters.go | 15 +++++ ruleguard/ir/filter_op.gen.go | 78 ++++++++++++----------- ruleguard/ir/gen_filter_op.go | 1 + ruleguard/ir_loader.go | 2 + ruleguard/irconv/irconv.go | 2 + ruleguard/irconv/irconv_test.go | 1 + 10 files changed, 133 insertions(+), 37 deletions(-) diff --git a/analyzer/testdata/src/filtertest/f1.go b/analyzer/testdata/src/filtertest/f1.go index 924e530c..7634d015 100644 --- a/analyzer/testdata/src/filtertest/f1.go +++ b/analyzer/testdata/src/filtertest/f1.go @@ -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 diff --git a/analyzer/testdata/src/filtertest/rules.go b/analyzer/testdata/src/filtertest/rules.go index e0210a79..6ab6e54a 100644 --- a/analyzer/testdata/src/filtertest/rules.go +++ b/analyzer/testdata/src/filtertest/rules.go @@ -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()`, diff --git a/go.mod b/go.mod index 1c6360a6..6c1619fb 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 04961702..e77e0746 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/ruleguard/filters.go b/ruleguard/filters.go index 6c049d70..d1d0708e 100644 --- a/ruleguard/filters.go +++ b/ruleguard/filters.go @@ -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() diff --git a/ruleguard/ir/filter_op.gen.go b/ruleguard/ir/filter_op.gen.go index 639724b0..99eaa38e 100644 --- a/ruleguard/ir/filter_op.gen.go +++ b/ruleguard/ir/filter_op.gen.go @@ -36,147 +36,151 @@ const ( // $Value type: string FilterVarAddressableOp FilterOp = 10 + // m[$Value].Comparable + // $Value type: string + FilterVarComparableOp FilterOp = 11 + // m[$Value].Pure // $Value type: string - FilterVarPureOp FilterOp = 11 + FilterVarPureOp FilterOp = 12 // m[$Value].Const // $Value type: string - FilterVarConstOp FilterOp = 12 + FilterVarConstOp FilterOp = 13 // m[$Value].ConstSlice // $Value type: string - FilterVarConstSliceOp FilterOp = 13 + FilterVarConstSliceOp FilterOp = 14 // m[$Value].Text // $Value type: string - FilterVarTextOp FilterOp = 14 + FilterVarTextOp FilterOp = 15 // m[$Value].Line // $Value type: string - FilterVarLineOp FilterOp = 15 + FilterVarLineOp FilterOp = 16 // m[$Value].Value.Int() // $Value type: string - FilterVarValueIntOp FilterOp = 16 + FilterVarValueIntOp FilterOp = 17 // m[$Value].Type.Size // $Value type: string - FilterVarTypeSizeOp FilterOp = 17 + FilterVarTypeSizeOp FilterOp = 18 // m[$Value].Type.HasPointers() // $Value type: string - FilterVarTypeHasPointersOp FilterOp = 18 + FilterVarTypeHasPointersOp FilterOp = 19 // m[$Value].Filter($Args[0]) // $Value type: string - FilterVarFilterOp FilterOp = 19 + FilterVarFilterOp FilterOp = 20 // m[$Value].Node.Is($Args[0]) // $Value type: string - FilterVarNodeIsOp FilterOp = 20 + FilterVarNodeIsOp FilterOp = 21 // m[$Value].Object.Is($Args[0]) // $Value type: string - FilterVarObjectIsOp FilterOp = 21 + FilterVarObjectIsOp FilterOp = 22 // m[$Value].Object.IsGlobal() // $Value type: string - FilterVarObjectIsGlobalOp FilterOp = 22 + FilterVarObjectIsGlobalOp FilterOp = 23 // m[$Value].Type.Is($Args[0]) // $Value type: string - FilterVarTypeIsOp FilterOp = 23 + FilterVarTypeIsOp FilterOp = 24 // m[$Value].Type.IdenticalTo($Args[0]) // $Value type: string - FilterVarTypeIdenticalToOp FilterOp = 24 + FilterVarTypeIdenticalToOp FilterOp = 25 // m[$Value].Type.Underlying().Is($Args[0]) // $Value type: string - FilterVarTypeUnderlyingIsOp FilterOp = 25 + FilterVarTypeUnderlyingIsOp FilterOp = 26 // m[$Value].Type.OfKind($Args[0]) // $Value type: string - FilterVarTypeOfKindOp FilterOp = 26 + FilterVarTypeOfKindOp FilterOp = 27 // m[$Value].Type.Underlying().OfKind($Args[0]) // $Value type: string - FilterVarTypeUnderlyingOfKindOp FilterOp = 27 + FilterVarTypeUnderlyingOfKindOp FilterOp = 28 // m[$Value].Type.ConvertibleTo($Args[0]) // $Value type: string - FilterVarTypeConvertibleToOp FilterOp = 28 + FilterVarTypeConvertibleToOp FilterOp = 29 // m[$Value].Type.AssignableTo($Args[0]) // $Value type: string - FilterVarTypeAssignableToOp FilterOp = 29 + FilterVarTypeAssignableToOp FilterOp = 30 // m[$Value].Type.Implements($Args[0]) // $Value type: string - FilterVarTypeImplementsOp FilterOp = 30 + FilterVarTypeImplementsOp FilterOp = 31 // m[$Value].Type.HasMethod($Args[0]) // $Value type: string - FilterVarTypeHasMethodOp FilterOp = 31 + FilterVarTypeHasMethodOp FilterOp = 32 // m[$Value].Text.Matches($Args[0]) // $Value type: string - FilterVarTextMatchesOp FilterOp = 32 + FilterVarTextMatchesOp FilterOp = 33 // m[$Value].Contains($Args[0]) // $Value type: string - FilterVarContainsOp FilterOp = 33 + FilterVarContainsOp FilterOp = 34 // m.Deadcode() - FilterDeadcodeOp FilterOp = 34 + FilterDeadcodeOp FilterOp = 35 // m.GoVersion().Eq($Value) // $Value type: string - FilterGoVersionEqOp FilterOp = 35 + FilterGoVersionEqOp FilterOp = 36 // m.GoVersion().LessThan($Value) // $Value type: string - FilterGoVersionLessThanOp FilterOp = 36 + FilterGoVersionLessThanOp FilterOp = 37 // m.GoVersion().GreaterThan($Value) // $Value type: string - FilterGoVersionGreaterThanOp FilterOp = 37 + FilterGoVersionGreaterThanOp FilterOp = 38 // m.GoVersion().LessEqThan($Value) // $Value type: string - FilterGoVersionLessEqThanOp FilterOp = 38 + FilterGoVersionLessEqThanOp FilterOp = 39 // m.GoVersion().GreaterEqThan($Value) // $Value type: string - FilterGoVersionGreaterEqThanOp FilterOp = 39 + FilterGoVersionGreaterEqThanOp FilterOp = 40 // m.File.Imports($Value) // $Value type: string - FilterFileImportsOp FilterOp = 40 + FilterFileImportsOp FilterOp = 41 // m.File.PkgPath.Matches($Value) // $Value type: string - FilterFilePkgPathMatchesOp FilterOp = 41 + FilterFilePkgPathMatchesOp FilterOp = 42 // m.File.Name.Matches($Value) // $Value type: string - FilterFileNameMatchesOp FilterOp = 42 + FilterFileNameMatchesOp FilterOp = 43 // $Value holds a function name // $Value type: string - FilterFilterFuncRefOp FilterOp = 43 + FilterFilterFuncRefOp FilterOp = 44 // $Value holds a string constant // $Value type: string - FilterStringOp FilterOp = 44 + FilterStringOp FilterOp = 45 // $Value holds an int64 constant // $Value type: int64 - FilterIntOp FilterOp = 45 + FilterIntOp FilterOp = 46 // m[`$$`].Node.Parent().Is($Args[0]) - FilterRootNodeParentIsOp FilterOp = 46 + FilterRootNodeParentIsOp FilterOp = 47 ) var filterOpNames = map[FilterOp]string{ @@ -191,6 +195,7 @@ var filterOpNames = map[FilterOp]string{ FilterGtEqOp: `GtEq`, FilterLtEqOp: `LtEq`, FilterVarAddressableOp: `VarAddressable`, + FilterVarComparableOp: `VarComparable`, FilterVarPureOp: `VarPure`, FilterVarConstOp: `VarConst`, FilterVarConstSliceOp: `VarConstSlice`, @@ -238,6 +243,7 @@ var filterOpFlags = map[FilterOp]uint64{ FilterGtEqOp: flagIsBinaryExpr, FilterLtEqOp: flagIsBinaryExpr, FilterVarAddressableOp: flagHasVar, + FilterVarComparableOp: flagHasVar, FilterVarPureOp: flagHasVar, FilterVarConstOp: flagHasVar, FilterVarConstSliceOp: flagHasVar, diff --git a/ruleguard/ir/gen_filter_op.go b/ruleguard/ir/gen_filter_op.go index 3e9806d8..c7ccf005 100644 --- a/ruleguard/ir/gen_filter_op.go +++ b/ruleguard/ir/gen_filter_op.go @@ -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}, diff --git a/ruleguard/ir_loader.go b/ruleguard/ir_loader.go index 7f5d6d64..18fbb2d5 100644 --- a/ruleguard/ir_loader.go +++ b/ruleguard/ir_loader.go @@ -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)) diff --git a/ruleguard/irconv/irconv.go b/ruleguard/irconv/irconv.go index aca84a24..9b525c5a 100644 --- a/ruleguard/irconv/irconv.go +++ b/ruleguard/irconv/irconv.go @@ -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} } diff --git a/ruleguard/irconv/irconv_test.go b/ruleguard/irconv/irconv_test.go index c31ccc73..087a9e8d 100644 --- a/ruleguard/irconv/irconv_test.go +++ b/ruleguard/irconv/irconv_test.go @@ -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"])`},