require (
github.com/google/pprof v0.0.0-20211104044539-f987b9c94b31
golang.org/x/arch v0.0.0-20210923205945-b76863e36670
- golang.org/x/mod v0.5.1-0.20210913215816-37dd6891021a
+ golang.org/x/mod v0.6.0-dev.0.20210913215816-37dd6891021a
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
- golang.org/x/tools v0.1.8-0.20211025211149-f916b54a1784
+ golang.org/x/tools v0.1.8-0.20211109164901-e9000123914f
)
require (
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/mod v0.5.1-0.20210913215816-37dd6891021a h1:55PVa91KndtPGH2lus5l2gDZqoO/x+Oa5CV0lVf8Ij8=
-golang.org/x/mod v0.5.1-0.20210913215816-37dd6891021a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20210913215816-37dd6891021a h1:gAiIC0JKDJwXAQFyqEYxROcAzeeh5ZTwWjKORCFuQxs=
+golang.org/x/mod v0.6.0-dev.0.20210913215816-37dd6891021a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/tools v0.1.8-0.20211025211149-f916b54a1784 h1:+xP+QoP2SEPgbn+07I/yJTzP+gavj0XKGS6+JU5tlck=
-golang.org/x/tools v0.1.8-0.20211025211149-f916b54a1784/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/tools v0.1.8-0.20211109164901-e9000123914f h1:wwsTeyXackfHvwdCKtGcDlYwO78AwwW6OwUomSMB0aI=
+golang.org/x/tools v0.1.8-0.20211109164901-e9000123914f/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
package facts
-import "go/types"
+import (
+ "go/types"
+
+ "golang.org/x/tools/internal/typeparams"
+)
// importMap computes the import map for a package by traversing the
// entire exported API each of its imports.
// nop
case *types.Named:
if addObj(T.Obj()) {
+ // TODO(taking): Investigate why the Underlying type is not added here.
for i := 0; i < T.NumMethods(); i++ {
addObj(T.Method(i))
}
+ if tparams := typeparams.ForNamed(T); tparams != nil {
+ for i := 0; i < tparams.Len(); i++ {
+ addType(tparams.At(i))
+ }
+ }
+ if targs := typeparams.NamedTypeArgs(T); targs != nil {
+ for i := 0; i < targs.Len(); i++ {
+ addType(targs.At(i))
+ }
+ }
}
case *types.Pointer:
addType(T.Elem())
case *types.Signature:
addType(T.Params())
addType(T.Results())
+ if tparams := typeparams.ForSignature(T); tparams != nil {
+ for i := 0; i < tparams.Len(); i++ {
+ addType(tparams.At(i))
+ }
+ }
case *types.Struct:
for i := 0; i < T.NumFields(); i++ {
addObj(T.Field(i))
for i := 0; i < T.NumMethods(); i++ {
addObj(T.Method(i))
}
+ for i := 0; i < T.NumEmbeddeds(); i++ {
+ addType(T.EmbeddedType(i)) // walk Embedded for implicits
+ }
+ case *typeparams.Union:
+ for i := 0; i < T.Len(); i++ {
+ addType(T.Term(i).Type())
+ }
+ case *typeparams.TypeParam:
+ if addObj(T.Obj()) {
+ addType(T.Constraint())
+ }
}
}
"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
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)
}
}
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)
}
}
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 {
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()
}
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 {
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)
// 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.
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())
}
}
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
"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
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]".
+ if id, ok := typeparams.GetIndexExprData(v).X.(*ast.Ident); ok {
+ obj = pass.TypesInfo.Uses[id]
+ }
default:
return
}
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
}
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
}
}
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{'#'}) {
}
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 {
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())
}
}
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
+ return "", true
}
+
+ typ := pass.TypesInfo.Types[arg].Type
if typ == nil {
- // external call
- typ = pass.TypesInfo.Types[arg].Type
- if typ == nil {
- return true // probably a type check problem
- }
+ 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)
}
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
}
under := typ.Elem().Underlying()
switch under.(type) {
+ case *typeparams.TypeParam:
+ return true // We don't know whether the logic below applies. Give up.
case *types.Struct: // see below
case *types.Array: // see below
case *types.Slice: // see below
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.
switch typ.Kind() {
case types.UntypedBool,
types.Bool:
- return t&argBool != 0
+ return m.t&argBool != 0
case types.UntypedInt,
types.Int,
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")
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
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
-}
"go/ast"
"go/constant"
"go/token"
+ "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"
if t == nil {
return
}
- size := 8 * pass.TypesSizes.Sizeof(t)
- if amt >= size {
+ terms, err := typeparams.StructuralTerms(t)
+ if err != nil {
+ return // invalid type
+ }
+ sizes := make(map[int64]struct{})
+ for _, term := range terms {
+ size := 8 * pass.TypesSizes.Sizeof(term.Type())
+ 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)
}
}
"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
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()
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) {
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
+ // 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()
+ tterms, err := typeparams.StructuralTerms(T)
+ if err != nil {
+ return // invalid type
}
- if s := T.Name(); target != s {
- target += " (" + s + ")"
+
+ var T0 types.Type // string type in the type set of T
+
+ for _, term := range tterms {
+ u, _ := term.Type().Underlying().(*types.Basic)
+ if u != nil && u.Kind() == types.String {
+ T0 = term.Type()
+ break
+ }
}
- // Check that type V of v has an underlying integral type that is not byte or rune.
- if len(call.Args) != 1 {
+ if T0 == nil {
+ // No target types have an underlying type of string.
return
}
- v := call.Args[0]
- vtyp := pass.TypesInfo.TypeOf(v)
- V, _ := vtyp.Underlying().(*types.Basic)
- if V == nil || V.Info()&types.IsInteger == 0 {
- return
+
+ // Next, find a type V0 in V that has an underlying integral type that is
+ // not byte or rune.
+ V := pass.TypesInfo.TypeOf(arg)
+ vterms, err := typeparams.StructuralTerms(V)
+ if err != nil {
+ return // invalid type
}
- switch V.Kind() {
- case types.Byte, types.Rune, types.UntypedRune:
- return
+
+ var V0 types.Type // integral type in the type set of V
+
+ for _, term := range vterms {
+ u, _ := term.Type().Underlying().(*types.Basic)
+ if u != nil && u.Info()&types.IsInteger != 0 {
+ switch u.Kind() {
+ case types.Byte, types.Rune, types.UntypedRune:
+ continue
+ }
+ V0 = term.Type()
+ break
+ }
}
- // Retrieve source type name.
- source := typeName(vtyp)
- if source == "" {
+ 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 _, term := range vterms {
+ if !types.ConvertibleTo(term.Type(), 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)
})
"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.
// function literals declared in the same function, and
// static calls within the same package are supported.
func goStmtFun(goStmt *ast.GoStmt) ast.Node {
- switch goStmt.Call.Fun.(type) {
- case *ast.Ident:
- id := goStmt.Call.Fun.(*ast.Ident)
- // TODO(cuonglm): improve this once golang/go#48141 resolved.
+ switch fun := goStmt.Call.Fun.(type) {
+ case *ast.IndexExpr, *typeparams.IndexListExpr:
+ ix := typeparams.GetIndexExprData(fun)
+ if ix == nil {
+ break
+ }
+ id, _ := ix.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
}
"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
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.
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)
}
"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
return // a conversion, not a call
}
+ index := typeparams.GetIndexExprData(fun)
+ if index != nil {
+ fun = index.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
"go/types"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/internal/typeparams"
)
// Callee returns the named target of a function call, if any:
// a function, method, builtin, or variable.
+//
+// Functions and methods may potentially have type parameters.
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
+ fun := astutil.Unparen(call.Fun)
+
+ // Look through type instantiation if necessary.
+ isInstance := false
+ switch fun.(type) {
+ case *ast.IndexExpr, *typeparams.IndexListExpr:
+ // When extracting the callee from an *IndexExpr, we need to check that
+ // it is a *types.Func and not a *types.Var.
+ // Example: Don't match a slice m within the expression `m[0]()`.
+ isInstance = true
+ ix := typeparams.GetIndexExprData(fun)
+ fun = ix.X
+ }
+
var obj types.Object
- switch fun := astutil.Unparen(call.Fun).(type) {
+ switch fun := fun.(type) {
case *ast.Ident:
obj = info.Uses[fun] // type, var, builtin, or declared func
case *ast.SelectorExpr:
if _, ok := obj.(*types.TypeName); ok {
return nil // T(x) is a conversion, not a call
}
+ // A Func is required to match instantiations.
+ if _, ok := obj.(*types.Func); isInstance && !ok {
+ return nil // Was not a Func.
+ }
return obj
}
-// StaticCallee returns the target (function or method) of a static
-// function call, if any. It returns nil for calls to builtins.
+// StaticCallee returns the target (function or method) of a static function
+// call, if any. It returns nil for calls to builtins.
+//
+// Note: for calls of instantiated functions and methods, StaticCallee returns
+// the corresponding generic function or method on the generic type.
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
if f, ok := Callee(info, call).(*types.Func); ok && !interfaceMethod(f) {
return f
## explicit; go 1.17
golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519
-# golang.org/x/mod v0.5.1-0.20210913215816-37dd6891021a
+# golang.org/x/mod v0.6.0-dev.0.20210913215816-37dd6891021a
## explicit; go 1.17
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
# golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
## explicit; go 1.17
golang.org/x/term
-# golang.org/x/tools v0.1.8-0.20211025211149-f916b54a1784
+# golang.org/x/tools v0.1.8-0.20211109164901-e9000123914f
## explicit; go 1.17
golang.org/x/tools/cover
golang.org/x/tools/go/analysis