Update deps; sync vendor

This commit is contained in:
Frank Denis 2022-02-21 09:00:21 +01:00
parent 9373cc7162
commit c08852feb1
76 changed files with 4193 additions and 1014 deletions

View file

@ -51,6 +51,11 @@ type asmArch struct {
bigEndian bool
stack string
lr bool
// retRegs is a list of registers for return value in register ABI (ABIInternal).
// For now, as we only check whether we write to any result, here we only need to
// include the first integer register and first floating-point register. Accessing
// any of them counts as writing to result.
retRegs []string
// calculated during initialization
sizes types.Sizes
intSize int
@ -79,8 +84,8 @@ type asmVar struct {
var (
asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true}
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false}
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}}
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}}
asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
@ -137,7 +142,7 @@ var (
asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
abiSuff = re(`^(.+)<ABI.+>$`)
abiSuff = re(`^(.+)<(ABI.+)>$`)
)
func run(pass *analysis.Pass) (interface{}, error) {
@ -185,6 +190,7 @@ Files:
var (
fn *asmFunc
fnName string
abi string
localSize, argSize int
wroteSP bool
noframe bool
@ -195,18 +201,22 @@ Files:
flushRet := func() {
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
v := fn.vars["ret"]
resultStr := fmt.Sprintf("%d-byte ret+%d(FP)", v.size, v.off)
if abi == "ABIInternal" {
resultStr = "result register"
}
for _, line := range retLine {
pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off)
pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr)
}
}
retLine = nil
}
trimABI := func(fnName string) string {
trimABI := func(fnName string) (string, string) {
m := abiSuff.FindStringSubmatch(fnName)
if m != nil {
return m[1]
return m[1], m[2]
}
return fnName
return fnName, ""
}
for lineno, line := range lines {
lineno++
@ -273,11 +283,12 @@ Files:
// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
fn = nil
fnName = ""
abi = ""
continue
}
}
// Trim off optional ABI selector.
fnName := trimABI(fnName)
fnName, abi = trimABI(fnName)
flag := m[3]
fn = knownFunc[fnName][arch]
if fn != nil {
@ -305,6 +316,7 @@ Files:
flushRet()
fn = nil
fnName = ""
abi = ""
continue
}
@ -335,6 +347,15 @@ Files:
haveRetArg = true
}
if abi == "ABIInternal" && !haveRetArg {
for _, reg := range archDef.retRegs {
if strings.Contains(line, reg) {
haveRetArg = true
break
}
}
}
for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
if m[3] != archDef.stack || wroteSP || noframe {
continue

View file

@ -14,6 +14,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `check for unkeyed composite literals
@ -67,41 +68,61 @@ func run(pass *analysis.Pass) (interface{}, error) {
// skip whitelisted types
return
}
under := typ.Underlying()
for {
ptr, ok := under.(*types.Pointer)
if !ok {
break
var structuralTypes []types.Type
switch typ := typ.(type) {
case *typeparams.TypeParam:
terms, err := typeparams.StructuralTerms(typ)
if err != nil {
return // invalid type
}
under = ptr.Elem().Underlying()
}
if _, ok := under.(*types.Struct); !ok {
// skip non-struct composite literals
return
}
if isLocalType(pass, typ) {
// allow unkeyed locally defined composite literal
return
}
// check if the CompositeLit contains an unkeyed field
allKeyValue := true
for _, e := range cl.Elts {
if _, ok := e.(*ast.KeyValueExpr); !ok {
allKeyValue = false
break
for _, term := range terms {
structuralTypes = append(structuralTypes, term.Type())
}
default:
structuralTypes = append(structuralTypes, typ)
}
if allKeyValue {
// all the composite literal fields are keyed
for _, typ := range structuralTypes {
under := deref(typ.Underlying())
if _, ok := under.(*types.Struct); !ok {
// skip non-struct composite literals
continue
}
if isLocalType(pass, typ) {
// allow unkeyed locally defined composite literal
continue
}
// check if the CompositeLit contains an unkeyed field
allKeyValue := true
for _, e := range cl.Elts {
if _, ok := e.(*ast.KeyValueExpr); !ok {
allKeyValue = false
break
}
}
if allKeyValue {
// all the composite literal fields are keyed
continue
}
pass.ReportRangef(cl, "%s composite literal uses unkeyed fields", typeName)
return
}
pass.ReportRangef(cl, "%s composite literal uses unkeyed fields", typeName)
})
return nil, nil
}
func deref(typ types.Type) types.Type {
for {
ptr, ok := typ.(*types.Pointer)
if !ok {
break
}
typ = ptr.Elem().Underlying()
}
return typ
}
func isLocalType(pass *analysis.Pass, typ types.Type) bool {
switch x := typ.(type) {
case *types.Struct:
@ -112,6 +133,8 @@ func isLocalType(pass *analysis.Pass, typ types.Type) bool {
case *types.Named:
// names in package foo are local to foo_test too
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
case *typeparams.TypeParam:
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
}
return false
}

View file

@ -17,6 +17,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `check for locks erroneously passed by value
@ -145,7 +146,7 @@ func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) {
func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, typ *ast.FuncType) {
if recv != nil && len(recv.List) > 0 {
expr := recv.List[0].Type
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil {
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
}
}
@ -153,7 +154,7 @@ func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, t
if typ.Params != nil {
for _, field := range typ.Params.List {
expr := field.Type
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil {
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
}
}
@ -199,12 +200,12 @@ func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
if typ == nil {
return
}
if path := lockPath(pass.Pkg, typ); path != nil {
if path := lockPath(pass.Pkg, typ, nil); path != nil {
pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisutil.Format(pass.Fset, e), path)
}
}
type typePath []types.Type
type typePath []string
// String pretty-prints a typePath.
func (path typePath) String() string {
@ -215,7 +216,7 @@ func (path typePath) String() string {
fmt.Fprint(&buf, " contains ")
}
// The human-readable path is in reverse order, outermost to innermost.
fmt.Fprint(&buf, path[n-i-1].String())
fmt.Fprint(&buf, path[n-i-1])
}
return buf.String()
}
@ -234,16 +235,57 @@ func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
return nil
}
}
return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type)
return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type, nil)
}
// lockPath returns a typePath describing the location of a lock value
// contained in typ. If there is no contained lock, it returns nil.
func lockPath(tpkg *types.Package, typ types.Type) typePath {
//
// The seenTParams map is used to short-circuit infinite recursion via type
// parameters.
func lockPath(tpkg *types.Package, typ types.Type, seenTParams map[*typeparams.TypeParam]bool) typePath {
if typ == nil {
return nil
}
if tpar, ok := typ.(*typeparams.TypeParam); ok {
if seenTParams == nil {
// Lazily allocate seenTParams, since the common case will not involve
// any type parameters.
seenTParams = make(map[*typeparams.TypeParam]bool)
}
if seenTParams[tpar] {
return nil
}
seenTParams[tpar] = true
terms, err := typeparams.StructuralTerms(tpar)
if err != nil {
return nil // invalid type
}
for _, term := range terms {
subpath := lockPath(tpkg, term.Type(), seenTParams)
if len(subpath) > 0 {
if term.Tilde() {
// Prepend a tilde to our lock path entry to clarify the resulting
// diagnostic message. Consider the following example:
//
// func _[Mutex interface{ ~sync.Mutex; M() }](m Mutex) {}
//
// Here the naive error message will be something like "passes lock
// by value: Mutex contains sync.Mutex". This is misleading because
// the local type parameter doesn't actually contain sync.Mutex,
// which lacks the M method.
//
// With tilde, it is clearer that the containment is via an
// approximation element.
subpath[len(subpath)-1] = "~" + subpath[len(subpath)-1]
}
return append(subpath, typ.String())
}
}
return nil
}
for {
atyp, ok := typ.Underlying().(*types.Array)
if !ok {
@ -252,6 +294,17 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
typ = atyp.Elem()
}
ttyp, ok := typ.Underlying().(*types.Tuple)
if ok {
for i := 0; i < ttyp.Len(); i++ {
subpath := lockPath(tpkg, ttyp.At(i).Type(), seenTParams)
if subpath != nil {
return append(subpath, typ.String())
}
}
return nil
}
// We're only interested in the case in which the underlying
// type is a struct. (Interfaces and pointers are safe to copy.)
styp, ok := typ.Underlying().(*types.Struct)
@ -263,7 +316,7 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
// is a sync.Locker, but a value is not. This differentiates
// embedded interfaces from embedded values.
if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
return []types.Type{typ}
return []string{typ.String()}
}
// In go1.10, sync.noCopy did not implement Locker.
@ -272,15 +325,15 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
if named, ok := typ.(*types.Named); ok &&
named.Obj().Name() == "noCopy" &&
named.Obj().Pkg().Path() == "sync" {
return []types.Type{typ}
return []string{typ.String()}
}
nfields := styp.NumFields()
for i := 0; i < nfields; i++ {
ftyp := styp.Field(i).Type()
subpath := lockPath(tpkg, ftyp)
subpath := lockPath(tpkg, ftyp, seenTParams)
if subpath != nil {
return append(subpath, typ)
return append(subpath, typ.String())
}
}

View file

@ -187,7 +187,11 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
return false // panic never returns
}
// Is this a static call?
// Is this a static call? Also includes static functions
// parameterized by a type. Such functions may or may not
// return depending on the parameter type, but in some
// cases the answer is definite. We let ctrlflow figure
// that out.
fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
if fn == nil {
return true // callee not statically known; be conservative

View file

@ -51,6 +51,12 @@ func assertableTo(v, t types.Type) *types.Func {
if V == nil || T == nil {
return nil
}
// Mitigations for interface comparisons and generics.
// TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion.
if isParameterized(V) || isParameterized(T) {
return nil
}
if f, wrongType := types.MissingMethod(V, T, false); wrongType {
return f
}

View file

@ -0,0 +1,112 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ifaceassert
import (
"go/types"
"golang.org/x/tools/internal/typeparams"
)
// isParameterized reports whether typ contains any of the type parameters of tparams.
//
// NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy.
func isParameterized(typ types.Type) bool {
w := tpWalker{
seen: make(map[types.Type]bool),
}
return w.isParameterized(typ)
}
type tpWalker struct {
seen map[types.Type]bool
}
func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
// detect cycles
if x, ok := w.seen[typ]; ok {
return x
}
w.seen[typ] = false
defer func() {
w.seen[typ] = res
}()
switch t := typ.(type) {
case nil, *types.Basic: // TODO(gri) should nil be handled here?
break
case *types.Array:
return w.isParameterized(t.Elem())
case *types.Slice:
return w.isParameterized(t.Elem())
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
if w.isParameterized(t.Field(i).Type()) {
return true
}
}
case *types.Pointer:
return w.isParameterized(t.Elem())
case *types.Tuple:
n := t.Len()
for i := 0; i < n; i++ {
if w.isParameterized(t.At(i).Type()) {
return true
}
}
case *types.Signature:
// t.tparams may not be nil if we are looking at a signature
// of a generic function type (or an interface method) that is
// part of the type we're testing. We don't care about these type
// parameters.
// Similarly, the receiver of a method may declare (rather then
// use) type parameters, we don't care about those either.
// Thus, we only need to look at the input and result parameters.
return w.isParameterized(t.Params()) || w.isParameterized(t.Results())
case *types.Interface:
for i, n := 0, t.NumMethods(); i < n; i++ {
if w.isParameterized(t.Method(i).Type()) {
return true
}
}
terms, err := typeparams.InterfaceTermSet(t)
if err != nil {
panic(err)
}
for _, term := range terms {
if w.isParameterized(term.Type()) {
return true
}
}
case *types.Map:
return w.isParameterized(t.Key()) || w.isParameterized(t.Elem())
case *types.Chan:
return w.isParameterized(t.Elem())
case *types.Named:
list := typeparams.NamedTypeArgs(t)
for i, n := 0, list.Len(); i < n; i++ {
if w.isParameterized(list.At(i)) {
return true
}
}
case *typeparams.TypeParam:
return true
default:
panic(t) // unreachable
}
return false
}

View file

@ -14,6 +14,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `check for useless comparisons between functions and nil
@ -59,6 +60,12 @@ func run(pass *analysis.Pass) (interface{}, error) {
obj = pass.TypesInfo.Uses[v]
case *ast.SelectorExpr:
obj = pass.TypesInfo.Uses[v.Sel]
case *ast.IndexExpr, *typeparams.IndexListExpr:
// Check generic functions such as "f[T1,T2]".
x, _, _, _ := typeparams.UnpackIndexExpr(v)
if id, ok := x.(*ast.Ident); ok {
obj = pass.TypesInfo.Uses[id]
}
default:
return
}

View file

@ -135,6 +135,11 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) {
if nilnessOf(stack, instr.X) == isnil {
reportf("nilpanic", instr.Pos(), "panic with nil value")
}
case *ssa.SliceToArrayPointer:
nn := nilnessOf(stack, instr.X)
if nn == isnil && slice2ArrayPtrLen(instr) > 0 {
reportf("conversionpanic", instr.Pos(), "nil slice being cast to an array of len > 0 will always panic")
}
}
}
@ -259,6 +264,26 @@ func nilnessOf(stack []fact, v ssa.Value) nilness {
if underlying := nilnessOf(stack, v.X); underlying != unknown {
return underlying
}
case *ssa.SliceToArrayPointer:
nn := nilnessOf(stack, v.X)
if slice2ArrayPtrLen(v) > 0 {
if nn == isnil {
// We know that *(*[1]byte)(nil) is going to panic because of the
// conversion. So return unknown to the caller, prevent useless
// nil deference reporting due to * operator.
return unknown
}
// Otherwise, the conversion will yield a non-nil pointer to array.
// Note that the instruction can still panic if array length greater
// than slice length. If the value is used by another instruction,
// that instruction can assume the panic did not happen when that
// instruction is reached.
return isnonnil
}
// In case array length is zero, the conversion result depends on nilness of the slice.
if nn != unknown {
return nn
}
}
// Is value intrinsically nil or non-nil?
@ -292,6 +317,10 @@ func nilnessOf(stack []fact, v ssa.Value) nilness {
return unknown
}
func slice2ArrayPtrLen(v *ssa.SliceToArrayPointer) int64 {
return v.Type().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
}
// If b ends with an equality comparison, eq returns the operation and
// its true (equal) and false (not equal) successors.
func eq(b *ssa.BasicBlock) (op *ssa.BinOp, tsucc, fsucc *ssa.BasicBlock) {

View file

@ -25,6 +25,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)
func init() {
@ -452,8 +453,15 @@ func stringConstantArg(pass *analysis.Pass, call *ast.CallExpr, idx int) (string
if idx >= len(call.Args) {
return "", false
}
arg := call.Args[idx]
lit := pass.TypesInfo.Types[arg].Value
return stringConstantExpr(pass, call.Args[idx])
}
// stringConstantExpr returns expression's string constant value.
//
// ("", false) is returned if expression isn't a string
// constant.
func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
lit := pass.TypesInfo.Types[expr].Value
if lit != nil && lit.Kind() == constant.String {
return constant.StringVal(lit), true
}
@ -490,7 +498,7 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func,
_, ok = isPrint[strings.ToLower(fn.Name())]
}
if ok {
if fn.Name() == "Errorf" {
if fn.FullName() == "fmt.Errorf" {
kind = KindErrorf
} else if strings.HasSuffix(fn.Name(), "f") {
kind = KindPrintf
@ -513,7 +521,12 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func,
func isFormatter(typ types.Type) bool {
// If the type is an interface, the value it holds might satisfy fmt.Formatter.
if _, ok := typ.Underlying().(*types.Interface); ok {
return true
// Don't assume type parameters could be formatters. With the greater
// expressiveness of constraint interface syntax we expect more type safety
// when using type parameters.
if !typeparams.IsTypeParam(typ) {
return true
}
}
obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format")
fn, ok := obj.(*types.Func)
@ -590,12 +603,9 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, fn *types.F
}
if state.verb == 'w' {
switch kind {
case KindNone, KindPrint:
case KindNone, KindPrint, KindPrintf:
pass.Reportf(call.Pos(), "%s does not support error-wrapping directive %%w", state.name)
return
case KindPrintf:
pass.Reportf(call.Pos(), "%s call has error-wrapping directive %%w, which is only supported for functions backed by fmt.Errorf", state.name)
return
}
if anyW {
pass.Reportf(call.Pos(), "%s call has more than one error-wrapping directive %%w", state.name)
@ -837,8 +847,9 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
}
// Could current arg implement fmt.Formatter?
// Skip check for the %w verb, which requires an error.
formatter := false
if state.argNum < len(call.Args) {
if v.typ != argError && state.argNum < len(call.Args) {
if tv, ok := pass.TypesInfo.Types[call.Args[state.argNum]]; ok {
formatter = isFormatter(tv.Type)
}
@ -874,8 +885,12 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
return
}
arg := call.Args[argNum]
if !matchArgType(pass, argInt, nil, arg) {
pass.ReportRangef(call, "%s format %s uses non-int %s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg))
if reason, ok := matchArgType(pass, argInt, arg); !ok {
details := ""
if reason != "" {
details = " (" + reason + ")"
}
pass.ReportRangef(call, "%s format %s uses non-int %s%s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg), details)
return false
}
}
@ -892,12 +907,16 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
pass.ReportRangef(call, "%s format %s arg %s is a func value, not called", state.name, state.format, analysisutil.Format(pass.Fset, arg))
return false
}
if !matchArgType(pass, v.typ, nil, arg) {
if reason, ok := matchArgType(pass, v.typ, arg); !ok {
typeString := ""
if typ := pass.TypesInfo.Types[arg].Type; typ != nil {
typeString = typ.String()
}
pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString)
details := ""
if reason != "" {
details = " (" + reason + ")"
}
pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s%s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString, details)
return false
}
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) {
@ -1055,10 +1074,10 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
}
arg := args[0]
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
// Ignore trailing % character in lit.Value.
if s, ok := stringConstantExpr(pass, arg); ok {
// Ignore trailing % character
// The % in "abc 0.0%" couldn't be a formatting directive.
s := strings.TrimSuffix(lit.Value, `%"`)
s = strings.TrimSuffix(s, "%")
if strings.Contains(s, "%") {
m := printFormatRE.FindStringSubmatch(s)
if m != nil {
@ -1069,9 +1088,8 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
if strings.HasSuffix(fn.Name(), "ln") {
// The last item, if a string, should not have a newline.
arg = args[len(args)-1]
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
str, _ := strconv.Unquote(lit.Value)
if strings.HasSuffix(str, "\n") {
if s, ok := stringConstantExpr(pass, arg); ok {
if strings.HasSuffix(s, "\n") {
pass.ReportRangef(call, "%s arg list ends with redundant newline", fn.FullName())
}
}

View file

@ -5,45 +5,60 @@
package printf
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/internal/typeparams"
)
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
// matchArgType reports an error if printf verb t is not appropriate
// for operand arg.
// matchArgType reports an error if printf verb t is not appropriate for
// operand arg.
//
// typ is used only for recursive calls; external callers must supply nil.
//
// (Recursion arises from the compound types {map,chan,slice} which
// may be printed with %d etc. if that is appropriate for their element
// types.)
func matchArgType(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr) bool {
return matchArgTypeInternal(pass, t, typ, arg, make(map[types.Type]bool))
}
// matchArgTypeInternal is the internal version of matchArgType. It carries a map
// remembering what types are in progress so we don't recur when faced with recursive
// types or mutually recursive types.
func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool {
// If arg is a type parameter, the verb t must be appropriate for every type in
// the type parameter type set.
func matchArgType(pass *analysis.Pass, t printfArgType, arg ast.Expr) (reason string, ok bool) {
// %v, %T accept any argument type.
if t == anyType {
return true
}
if typ == nil {
// external call
typ = pass.TypesInfo.Types[arg].Type
if typ == nil {
return true // probably a type check problem
}
return "", true
}
typ := pass.TypesInfo.Types[arg].Type
if typ == nil {
return "", true // probably a type check problem
}
m := &argMatcher{t: t, seen: make(map[types.Type]bool)}
ok = m.match(typ, true)
return m.reason, ok
}
// argMatcher recursively matches types against the printfArgType t.
//
// To short-circuit recursion, it keeps track of types that have already been
// matched (or are in the process of being matched) via the seen map. Recursion
// arises from the compound types {map,chan,slice} which may be printed with %d
// etc. if that is appropriate for their element types, as well as from type
// parameters, which are expanded to the constituents of their type set.
//
// The reason field may be set to report the cause of the mismatch.
type argMatcher struct {
t printfArgType
seen map[types.Type]bool
reason string
}
// match checks if typ matches m's printf arg type. If topLevel is true, typ is
// the actual type of the printf arg, for which special rules apply. As a
// special case, top level type parameters pass topLevel=true when checking for
// matches among the constituents of their type set, as type arguments will
// replace the type parameter at compile time.
func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
// %w accepts only errors.
if t == argError {
if m.t == argError {
return types.ConvertibleTo(typ, errorType)
}
@ -51,65 +66,122 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
if isFormatter(typ) {
return true
}
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
if t&argString != 0 && isConvertibleToString(pass, typ) {
if m.t&argString != 0 && isConvertibleToString(typ) {
return true
}
if typ, _ := typ.(*typeparams.TypeParam); typ != nil {
// Avoid infinite recursion through type parameters.
if m.seen[typ] {
return true
}
m.seen[typ] = true
terms, err := typeparams.StructuralTerms(typ)
if err != nil {
return true // invalid type (possibly an empty type set)
}
if len(terms) == 0 {
// No restrictions on the underlying of typ. Type parameters implementing
// error, fmt.Formatter, or fmt.Stringer were handled above, and %v and
// %T was handled in matchType. We're about to check restrictions the
// underlying; if the underlying type is unrestricted there must be an
// element of the type set that violates one of the arg type checks
// below, so we can safely return false here.
if m.t == anyType { // anyType must have already been handled.
panic("unexpected printfArgType")
}
return false
}
// Only report a reason if typ is the argument type, otherwise it won't
// make sense. Note that it is not sufficient to check if topLevel == here,
// as type parameters can have a type set consisting of other type
// parameters.
reportReason := len(m.seen) == 1
for _, term := range terms {
if !m.match(term.Type(), topLevel) {
if reportReason {
if term.Tilde() {
m.reason = fmt.Sprintf("contains ~%s", term.Type())
} else {
m.reason = fmt.Sprintf("contains %s", term.Type())
}
}
return false
}
}
return true
}
typ = typ.Underlying()
if inProgress[typ] {
// We're already looking at this type. The call that started it will take care of it.
if m.seen[typ] {
// We've already considered typ, or are in the process of considering it.
// In case we've already considered typ, it must have been valid (else we
// would have stopped matching). In case we're in the process of
// considering it, we must avoid infinite recursion.
//
// There are some pathological cases where returning true here is
// incorrect, for example `type R struct { F []R }`, but these are
// acceptable false negatives.
return true
}
inProgress[typ] = true
m.seen[typ] = true
switch typ := typ.(type) {
case *types.Signature:
return t == argPointer
return m.t == argPointer
case *types.Map:
return t == argPointer ||
// Recur: map[int]int matches %d.
(matchArgTypeInternal(pass, t, typ.Key(), arg, inProgress) && matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress))
if m.t == argPointer {
return true
}
// Recur: map[int]int matches %d.
return m.match(typ.Key(), false) && m.match(typ.Elem(), false)
case *types.Chan:
return t&argPointer != 0
return m.t&argPointer != 0
case *types.Array:
// Same as slice.
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 {
return true // %s matches []byte
}
// Recur: []int matches %d.
return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)
return m.match(typ.Elem(), false)
case *types.Slice:
// Same as array.
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 {
return true // %s matches []byte
}
if t == argPointer {
if m.t == argPointer {
return true // %p prints a slice's 0th element
}
// Recur: []int matches %d. But watch out for
// type T []T
// If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
return matchArgTypeInternal(pass, t, typ.Elem(), arg, inProgress)
return m.match(typ.Elem(), false)
case *types.Pointer:
// Ugly, but dealing with an edge case: a known pointer to an invalid type,
// probably something from a failed import.
if typ.Elem().String() == "invalid type" {
if false {
pass.Reportf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", analysisutil.Format(pass.Fset, arg))
}
if typ.Elem() == types.Typ[types.Invalid] {
return true // special case
}
// If it's actually a pointer with %p, it prints as one.
if t == argPointer {
if m.t == argPointer {
return true
}
if typeparams.IsTypeParam(typ.Elem()) {
return true // We don't know whether the logic below applies. Give up.
}
under := typ.Elem().Underlying()
switch under.(type) {
case *types.Struct: // see below
@ -118,19 +190,31 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
case *types.Map: // see below
default:
// Check whether the rest can print pointers.
return t&argPointer != 0
return m.t&argPointer != 0
}
// If it's a top-level pointer to a struct, array, slice, or
// If it's a top-level pointer to a struct, array, slice, type param, or
// map, that's equivalent in our analysis to whether we can
// print the type being pointed to. Pointers in nested levels
// are not supported to minimize fmt running into loops.
if len(inProgress) > 1 {
if !topLevel {
return false
}
return matchArgTypeInternal(pass, t, under, arg, inProgress)
return m.match(under, false)
case *types.Struct:
return matchStructArgType(pass, t, typ, arg, inProgress)
// report whether all the elements of the struct match the expected type. For
// instance, with "%d" all the elements must be printable with the "%d" format.
for i := 0; i < typ.NumFields(); i++ {
typf := typ.Field(i)
if !m.match(typf.Type(), false) {
return false
}
if m.t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) {
// Issue #17798: unexported Stringer or error cannot be properly formatted.
return false
}
}
return true
case *types.Interface:
// There's little we can do.
@ -142,7 +226,7 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
switch typ.Kind() {
case types.UntypedBool,
types.Bool:
return t&argBool != 0
return m.t&argBool != 0
case types.UntypedInt,
types.Int,
@ -156,35 +240,32 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
types.Uint32,
types.Uint64,
types.Uintptr:
return t&argInt != 0
return m.t&argInt != 0
case types.UntypedFloat,
types.Float32,
types.Float64:
return t&argFloat != 0
return m.t&argFloat != 0
case types.UntypedComplex,
types.Complex64,
types.Complex128:
return t&argComplex != 0
return m.t&argComplex != 0
case types.UntypedString,
types.String:
return t&argString != 0
return m.t&argString != 0
case types.UnsafePointer:
return t&(argPointer|argInt) != 0
return m.t&(argPointer|argInt) != 0
case types.UntypedRune:
return t&(argInt|argRune) != 0
return m.t&(argInt|argRune) != 0
case types.UntypedNil:
return false
case types.Invalid:
if false {
pass.Reportf(arg.Pos(), "printf argument %v has invalid or unknown type", analysisutil.Format(pass.Fset, arg))
}
return true // Probably a type check problem.
}
panic("unreachable")
@ -193,7 +274,7 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type,
return false
}
func isConvertibleToString(pass *analysis.Pass, typ types.Type) bool {
func isConvertibleToString(typ types.Type) bool {
if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
// We explicitly don't want untyped nil, which is
// convertible to both of the interfaces below, as it
@ -228,19 +309,3 @@ func hasBasicType(pass *analysis.Pass, x ast.Expr, kind types.BasicKind) bool {
b, ok := t.(*types.Basic)
return ok && b.Kind() == kind
}
// matchStructArgType reports whether all the elements of the struct match the expected
// type. For instance, with "%d" all the elements must be printable with the "%d" format.
func matchStructArgType(pass *analysis.Pass, t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool {
for i := 0; i < typ.NumFields(); i++ {
typf := typ.Field(i)
if !matchArgTypeInternal(pass, t, typf.Type(), arg, inProgress) {
return false
}
if t&argString != 0 && !typf.Exported() && isConvertibleToString(pass, typf.Type()) {
// Issue #17798: unexported Stringer or error cannot be properly formatted.
return false
}
}
return true
}

View file

@ -14,11 +14,14 @@ import (
"go/ast"
"go/constant"
"go/token"
"go/types"
"math"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = "check for shifts that equal or exceed the width of the integer"
@ -93,9 +96,36 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
if t == nil {
return
}
size := 8 * pass.TypesSizes.Sizeof(t)
if amt >= size {
var structuralTypes []types.Type
switch t := t.(type) {
case *typeparams.TypeParam:
terms, err := typeparams.StructuralTerms(t)
if err != nil {
return // invalid type
}
for _, term := range terms {
structuralTypes = append(structuralTypes, term.Type())
}
default:
structuralTypes = append(structuralTypes, t)
}
sizes := make(map[int64]struct{})
for _, t := range structuralTypes {
size := 8 * pass.TypesSizes.Sizeof(t)
sizes[size] = struct{}{}
}
minSize := int64(math.MaxInt64)
for size := range sizes {
if size < minSize {
minSize = size
}
}
if amt >= minSize {
ident := analysisutil.Format(pass.Fset, x)
pass.ReportRangef(node, "%s (%d bits) too small for shift of %d", ident, size, amt)
qualifier := ""
if len(sizes) > 1 {
qualifier = "may be "
}
pass.ReportRangef(node, "%s (%s%d bits) too small for shift of %d", ident, qualifier, minSize, amt)
}
}

View file

@ -45,7 +45,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
return
}
if fn.FullName() != "sort.Slice" {
fnName := fn.FullName()
if fnName != "sort.Slice" && fnName != "sort.SliceStable" && fnName != "sort.SliceIsSorted" {
return
}
@ -115,7 +116,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: fmt.Sprintf("sort.Slice's argument must be a slice; is called with %s", typ.String()),
Message: fmt.Sprintf("%s's argument must be a slice; is called with %s", fnName, typ.String()),
SuggestedFixes: fixes,
})
})

View file

@ -61,7 +61,7 @@ var Analyzer = &analysis.Analyzer{
// we let it go. But if it does have a fmt.ScanState, then the
// rest has to match.
var canonicalMethods = map[string]struct{ args, results []string }{
"As": {[]string{"interface{}"}, []string{"bool"}}, // errors.As
"As": {[]string{"any"}, []string{"bool"}}, // errors.As
// "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
"Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
"GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
@ -194,7 +194,9 @@ func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, pref
func matchParamType(expect string, actual types.Type) bool {
expect = strings.TrimPrefix(expect, "=")
// Overkill but easy.
return typeString(actual) == expect
t := typeString(actual)
return t == expect ||
(t == "any" || t == "interface{}") && (expect == "any" || expect == "interface{}")
}
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)

View file

@ -10,10 +10,12 @@ import (
"fmt"
"go/ast"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `check for string(int) conversions
@ -36,6 +38,35 @@ var Analyzer = &analysis.Analyzer{
Run: run,
}
// describe returns a string describing the type typ contained within the type
// set of inType. If non-empty, inName is used as the name of inType (this is
// necessary so that we can use alias type names that may not be reachable from
// inType itself).
func describe(typ, inType types.Type, inName string) string {
name := inName
if typ != inType {
name = typeName(typ)
}
if name == "" {
return ""
}
var parentheticals []string
if underName := typeName(typ.Underlying()); underName != "" && underName != name {
parentheticals = append(parentheticals, underName)
}
if typ != inType && inName != "" && inName != name {
parentheticals = append(parentheticals, "in "+inName)
}
if len(parentheticals) > 0 {
name += " (" + strings.Join(parentheticals, ", ") + ")"
}
return name
}
func typeName(typ types.Type) string {
if v, _ := typ.(interface{ Name() string }); v != nil {
return v.Name()
@ -54,6 +85,11 @@ func run(pass *analysis.Pass) (interface{}, error) {
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
if len(call.Args) != 1 {
return
}
arg := call.Args[0]
// Retrieve target type name.
var tname *types.TypeName
switch fun := call.Fun.(type) {
@ -65,62 +101,119 @@ func run(pass *analysis.Pass) (interface{}, error) {
if tname == nil {
return
}
target := tname.Name()
// Check that target type T in T(v) has an underlying type of string.
T, _ := tname.Type().Underlying().(*types.Basic)
if T == nil || T.Kind() != types.String {
return
}
if s := T.Name(); target != s {
target += " (" + s + ")"
// In the conversion T(v) of a value v of type V to a target type T, we
// look for types T0 in the type set of T and V0 in the type set of V, such
// that V0->T0 is a problematic conversion. If T and V are not type
// parameters, this amounts to just checking if V->T is a problematic
// conversion.
// First, find a type T0 in T that has an underlying type of string.
T := tname.Type()
ttypes, err := structuralTypes(T)
if err != nil {
return // invalid type
}
// Check that type V of v has an underlying integral type that is not byte or rune.
if len(call.Args) != 1 {
return
var T0 types.Type // string type in the type set of T
for _, tt := range ttypes {
u, _ := tt.Underlying().(*types.Basic)
if u != nil && u.Kind() == types.String {
T0 = tt
break
}
}
v := call.Args[0]
vtyp := pass.TypesInfo.TypeOf(v)
V, _ := vtyp.Underlying().(*types.Basic)
if V == nil || V.Info()&types.IsInteger == 0 {
return
}
switch V.Kind() {
case types.Byte, types.Rune, types.UntypedRune:
if T0 == nil {
// No target types have an underlying type of string.
return
}
// Retrieve source type name.
source := typeName(vtyp)
if source == "" {
// Next, find a type V0 in V that has an underlying integral type that is
// not byte or rune.
V := pass.TypesInfo.TypeOf(arg)
vtypes, err := structuralTypes(V)
if err != nil {
return // invalid type
}
var V0 types.Type // integral type in the type set of V
for _, vt := range vtypes {
u, _ := vt.Underlying().(*types.Basic)
if u != nil && u.Info()&types.IsInteger != 0 {
switch u.Kind() {
case types.Byte, types.Rune, types.UntypedRune:
continue
}
V0 = vt
break
}
}
if V0 == nil {
// No source types are non-byte or rune integer types.
return
}
if s := V.Name(); source != s {
source += " (" + s + ")"
convertibleToRune := true // if true, we can suggest a fix
for _, t := range vtypes {
if !types.ConvertibleTo(t, types.Typ[types.Rune]) {
convertibleToRune = false
break
}
}
target := describe(T0, T, tname.Name())
source := describe(V0, V, typeName(V))
if target == "" || source == "" {
return // something went wrong
}
diag := analysis.Diagnostic{
Pos: n.Pos(),
Message: fmt.Sprintf("conversion from %s to %s yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)", source, target),
SuggestedFixes: []analysis.SuggestedFix{
}
if convertibleToRune {
diag.SuggestedFixes = []analysis.SuggestedFix{
{
Message: "Did you mean to convert a rune to a string?",
TextEdits: []analysis.TextEdit{
{
Pos: v.Pos(),
End: v.Pos(),
Pos: arg.Pos(),
End: arg.Pos(),
NewText: []byte("rune("),
},
{
Pos: v.End(),
End: v.End(),
Pos: arg.End(),
End: arg.End(),
NewText: []byte(")"),
},
},
},
},
}
}
pass.Report(diag)
})
return nil, nil
}
func structuralTypes(t types.Type) ([]types.Type, error) {
var structuralTypes []types.Type
switch t := t.(type) {
case *typeparams.TypeParam:
terms, err := typeparams.StructuralTerms(t)
if err != nil {
return nil, err
}
for _, term := range terms {
structuralTypes = append(structuralTypes, term.Type())
}
default:
structuralTypes = append(structuralTypes, t)
}
return structuralTypes, nil
}

View file

@ -11,6 +11,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `report calls to (*testing.T).Fatal from goroutines started by a test.
@ -119,11 +120,44 @@ func typeIsTestingDotTOrB(expr ast.Expr) (string, bool) {
return varTypeName, ok
}
// goStmtFunc returns the ast.Node of a call expression
// that was invoked as a go statement. Currently, only
// function literals declared in the same function, and
// static calls within the same package are supported.
func goStmtFun(goStmt *ast.GoStmt) ast.Node {
switch fun := goStmt.Call.Fun.(type) {
case *ast.IndexExpr, *typeparams.IndexListExpr:
x, _, _, _ := typeparams.UnpackIndexExpr(fun)
id, _ := x.(*ast.Ident)
if id == nil {
break
}
if id.Obj == nil {
break
}
if funDecl, ok := id.Obj.Decl.(ast.Node); ok {
return funDecl
}
case *ast.Ident:
// TODO(cuonglm): improve this once golang/go#48141 resolved.
if fun.Obj == nil {
break
}
if funDecl, ok := fun.Obj.Decl.(ast.Node); ok {
return funDecl
}
case *ast.FuncLit:
return goStmt.Call.Fun
}
return goStmt.Call
}
// checkGoStmt traverses the goroutine and checks for the
// use of the forbidden *testing.(B, T) methods.
func checkGoStmt(pass *analysis.Pass, goStmt *ast.GoStmt) {
fn := goStmtFun(goStmt)
// Otherwise examine the goroutine to check for the forbidden methods.
ast.Inspect(goStmt, func(n ast.Node) bool {
ast.Inspect(fn, func(n ast.Node) bool {
selExpr, ok := n.(*ast.SelectorExpr)
if !ok {
return true
@ -147,7 +181,11 @@ func checkGoStmt(pass *analysis.Pass, goStmt *ast.GoStmt) {
return true
}
if typeName, ok := typeIsTestingDotTOrB(field.Type); ok {
pass.ReportRangef(selExpr, "call to (*%s).%s from a non-test goroutine", typeName, selExpr.Sel)
var fnRange analysis.Range = goStmt
if _, ok := fn.(*ast.FuncLit); ok {
fnRange = selExpr
}
pass.ReportRangef(fnRange, "call to (*%s).%s from a non-test goroutine", typeName, selExpr.Sel)
}
return true
})

View file

@ -8,12 +8,15 @@ package tests
import (
"go/ast"
"go/token"
"go/types"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `check for common mistaken usages of tests and examples
@ -42,10 +45,10 @@ func run(pass *analysis.Pass) (interface{}, error) {
// Ignore non-functions or functions with receivers.
continue
}
switch {
case strings.HasPrefix(fn.Name.Name, "Example"):
checkExample(pass, fn)
checkExampleName(pass, fn)
checkExampleOutput(pass, fn, f.Comments)
case strings.HasPrefix(fn.Name.Name, "Test"):
checkTest(pass, fn, "Test")
case strings.HasPrefix(fn.Name.Name, "Benchmark"):
@ -108,7 +111,59 @@ func lookup(pkg *types.Package, name string) []types.Object {
return ret
}
func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
// This pattern is taken from /go/src/go/doc/example.go
var outputRe = regexp.MustCompile(`(?i)^[[:space:]]*(unordered )?output:`)
type commentMetadata struct {
isOutput bool
pos token.Pos
}
func checkExampleOutput(pass *analysis.Pass, fn *ast.FuncDecl, fileComments []*ast.CommentGroup) {
commentsInExample := []commentMetadata{}
numOutputs := 0
// Find the comment blocks that are in the example. These comments are
// guaranteed to be in order of appearance.
for _, cg := range fileComments {
if cg.Pos() < fn.Pos() {
continue
} else if cg.End() > fn.End() {
break
}
isOutput := outputRe.MatchString(cg.Text())
if isOutput {
numOutputs++
}
commentsInExample = append(commentsInExample, commentMetadata{
isOutput: isOutput,
pos: cg.Pos(),
})
}
// Change message based on whether there are multiple output comment blocks.
msg := "output comment block must be the last comment block"
if numOutputs > 1 {
msg = "there can only be one output comment block per example"
}
for i, cg := range commentsInExample {
// Check for output comments that are not the last comment in the example.
isLast := (i == len(commentsInExample)-1)
if cg.isOutput && !isLast {
pass.Report(
analysis.Diagnostic{
Pos: cg.pos,
Message: msg,
},
)
}
}
}
func checkExampleName(pass *analysis.Pass, fn *ast.FuncDecl) {
fnName := fn.Name.Name
if params := fn.Type.Params; len(params.List) != 0 {
pass.Reportf(fn.Pos(), "%s should be niladic", fnName)
@ -116,6 +171,9 @@ func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
if results := fn.Type.Results; results != nil && len(results.List) != 0 {
pass.Reportf(fn.Pos(), "%s should return nothing", fnName)
}
if tparams := typeparams.ForFuncType(fn.Type); tparams != nil && len(tparams.List) > 0 {
pass.Reportf(fn.Pos(), "%s should not have type params", fnName)
}
if fnName == "Example" {
// Nothing more to do.
@ -182,6 +240,12 @@ func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
return
}
if tparams := typeparams.ForFuncType(fn.Type); tparams != nil && len(tparams.List) > 0 {
// Note: cmd/go/internal/load also errors about TestXXX and BenchmarkXXX functions with type parameters.
// We have currently decided to also warn before compilation/package loading. This can help users in IDEs.
pass.Reportf(fn.Pos(), "%s has type parameters: it will not be run by go test as a %sXXX function", fn.Name.Name, prefix)
}
if !isTestSuffix(fn.Name.Name[len(prefix):]) {
pass.Reportf(fn.Pos(), "%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix)
}

View file

@ -14,6 +14,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `report passing non-pointer or non-interface values to unmarshal
@ -85,7 +86,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
t := pass.TypesInfo.Types[call.Args[argidx]].Type
switch t.Underlying().(type) {
case *types.Pointer, *types.Interface:
case *types.Pointer, *types.Interface, *typeparams.TypeParam:
return
}

View file

@ -17,6 +17,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
// TODO(adonovan): make this analysis modular: export a mustUseResult
@ -70,6 +71,11 @@ func run(pass *analysis.Pass) (interface{}, error) {
return // a conversion, not a call
}
x, _, _, _ := typeparams.UnpackIndexExpr(fun)
if x != nil {
fun = x // If this is generic function or method call, skip the instantiation arguments
}
selector, ok := fun.(*ast.SelectorExpr)
if !ok {
return // neither a method call nor a qualified ident