diff --git a/analyzer/testdata/src/filtertest/f1.go b/analyzer/testdata/src/filtertest/f1.go index e727e04e..9c90e728 100644 --- a/analyzer/testdata/src/filtertest/f1.go +++ b/analyzer/testdata/src/filtertest/f1.go @@ -2,6 +2,7 @@ package filtertest import ( "errors" + "fmt" "os" "time" ) @@ -49,6 +50,24 @@ func assignableTo() { } func detectType() { + { + var s fmt.Stringer + + typeTest(s, "is interface") + typeTest(interface{}(nil), "is interface") // want `YES` + typeTest(implementsAll{}, "is interface") + typeTest(&implementsAll{}, "is interface") + typeTest(4, "is interface") + typeTest("", "is interface") + + typeTest(s, "underlying is interface") // want `YES` + typeTest(interface{}(nil), "underlying is interface") // want `YES` + typeTest(implementsAll{}, "underlying is interface") + typeTest(&implementsAll{}, "underlying is interface") + typeTest(4, "underlying is interface") + typeTest("", "underlying is interface") + } + { type withNamedTime struct { x int diff --git a/analyzer/testdata/src/filtertest/rules.go b/analyzer/testdata/src/filtertest/rules.go index f06473d6..0a7dbb36 100644 --- a/analyzer/testdata/src/filtertest/rules.go +++ b/analyzer/testdata/src/filtertest/rules.go @@ -137,4 +137,12 @@ func testRules(m dsl.Matcher) { m.Match(`typeTest($x, "assignable to interface{}")`). Where(m["x"].Type.AssignableTo(`interface{}`)). Report(`YES`) + + m.Match(`typeTest($x, "is interface")`). + Where(m["x"].Type.Is(`interface{ $*_ }`)). + Report(`YES`) + + m.Match(`typeTest($x, "underlying is interface")`). + Where(m["x"].Type.Underlying().Is(`interface{ $*_ }`)). + Report(`YES`) } diff --git a/ruleguard/typematch/patternop_string.go b/ruleguard/typematch/patternop_string.go index 1d739819..a909e3e8 100644 --- a/ruleguard/typematch/patternop_string.go +++ b/ruleguard/typematch/patternop_string.go @@ -19,12 +19,13 @@ func _() { _ = x[opFunc-8] _ = x[opStructNoSeq-9] _ = x[opStruct-10] - _ = x[opNamed-11] + _ = x[opAnyInterface-11] + _ = x[opNamed-12] } -const _patternOp_name = "opBuiltinTypeopPointeropVaropVarSeqopSliceopArrayopMapopChanopFuncopStructNoSeqopStructopNamed" +const _patternOp_name = "opBuiltinTypeopPointeropVaropVarSeqopSliceopArrayopMapopChanopFuncopStructNoSeqopStructopAnyInterfaceopNamed" -var _patternOp_index = [...]uint8{0, 13, 22, 27, 35, 42, 49, 54, 60, 66, 79, 87, 94} +var _patternOp_index = [...]uint8{0, 13, 22, 27, 35, 42, 49, 54, 60, 66, 79, 87, 101, 108} func (i patternOp) String() string { if i < 0 || i >= patternOp(len(_patternOp_index)-1) { diff --git a/ruleguard/typematch/typematch.go b/ruleguard/typematch/typematch.go index 19391ecd..4749106e 100644 --- a/ruleguard/typematch/typematch.go +++ b/ruleguard/typematch/typematch.go @@ -27,6 +27,7 @@ const ( opFunc opStructNoSeq opStruct + opAnyInterface opNamed ) @@ -313,6 +314,16 @@ func parseExpr(ctx *Context, e ast.Expr) *pattern { if len(e.Methods.List) == 0 { return &pattern{op: opBuiltinType, value: efaceType} } + if len(e.Methods.List) == 1 { + p := parseExpr(ctx, e.Methods.List[0].Type) + if p == nil { + return nil + } + if p.op != opVarSeq { + return nil + } + return &pattern{op: opAnyInterface} + } } return nil @@ -525,6 +536,10 @@ func (p *Pattern) matchIdentical(sub *pattern, typ types.Type) bool { } return true + case opAnyInterface: + _, ok := typ.(*types.Interface) + return ok + default: return false } diff --git a/ruleguard/typematch/typematch_test.go b/ruleguard/typematch/typematch_test.go index d2fea5ed..2aa54402 100644 --- a/ruleguard/typematch/typematch_test.go +++ b/ruleguard/typematch/typematch_test.go @@ -14,6 +14,11 @@ var ( typeUint8 = types.Typ[types.Uint8] typeEstruct = types.NewStruct(nil, nil) + stringerIface = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "String", + types.NewSignature(nil, types.NewTuple(), types.NewTuple(types.NewVar(token.NoPos, nil, "result", typeString)), false)), + }, nil) + intVar = types.NewVar(token.NoPos, nil, "_", typeInt) int32Var = types.NewVar(token.NoPos, nil, "_", typeInt32) estructVar = types.NewVar(token.NoPos, nil, "_", typeEstruct) @@ -55,7 +60,9 @@ func TestIdentical(t *testing.T) { {`[][]int`, types.NewSlice(types.NewSlice(typeInt))}, {`[10]int`, types.NewArray(typeInt, 10)}, {`map[int]int`, types.NewMap(typeInt, typeInt)}, + {`interface{}`, types.NewInterfaceType(nil, nil)}, + {`interface{ $*_ }`, stringerIface}, {`$t`, typeInt}, {`*$t`, types.NewPointer(typeInt)}, @@ -159,7 +166,10 @@ func TestIdenticalNegative(t *testing.T) { {`map[int]int`, types.NewMap(typeString, typeString)}, {`map[int]int`, types.NewMap(typeString, typeInt)}, {`map[int]int`, types.NewMap(typeInt, typeString)}, + {`interface{}`, typeInt}, + {`interface{ $*_ }`, typeString}, + {`interface{ $*_ }`, types.NewArray(typeString, 10)}, {`*$t`, typeInt}, {`map[$t]$t`, types.NewMap(typeString, typeInt)},