func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) {
for i, x := range as.Rhs {
if path := lockPathRhs(pass, x); path != nil {
- pass.Reportf(x.Pos(), "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
+ pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
}
}
}
valueSpec := spec.(*ast.ValueSpec)
for i, x := range valueSpec.Values {
if path := lockPathRhs(pass, x); path != nil {
- pass.Reportf(x.Pos(), "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
+ pass.ReportRangef(x, "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
}
}
}
x = node.Value
}
if path := lockPathRhs(pass, x); path != nil {
- pass.Reportf(x.Pos(), "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path)
+ pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path)
}
}
}
func checkCopyLocksReturnStmt(pass *analysis.Pass, rs *ast.ReturnStmt) {
for _, x := range rs.Results {
if path := lockPathRhs(pass, x); path != nil {
- pass.Reportf(x.Pos(), "return copies lock value: %v", path)
+ pass.ReportRangef(x, "return copies lock value: %v", path)
}
}
}
}
for _, x := range ce.Args {
if path := lockPathRhs(pass, x); path != nil {
- pass.Reportf(x.Pos(), "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path)
+ pass.ReportRangef(x, "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path)
}
}
}
if recv != nil && len(recv.List) > 0 {
expr := recv.List[0].Type
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil {
- pass.Reportf(expr.Pos(), "%s passes lock by value: %v", name, path)
+ pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
}
}
for _, field := range typ.Params.List {
expr := field.Type
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil {
- pass.Reportf(expr.Pos(), "%s passes lock by value: %v", name, path)
+ pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
}
}
}
"go/constant"
"go/token"
"go/types"
+ "reflect"
"regexp"
"sort"
"strconv"
}
var Analyzer = &analysis.Analyzer{
- Name: "printf",
- Doc: doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
- FactTypes: []analysis.Fact{new(isWrapper)},
+ Name: "printf",
+ Doc: doc,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: run,
+ ResultType: reflect.TypeOf((*Result)(nil)),
+ FactTypes: []analysis.Fact{new(isWrapper)},
}
const doc = `check consistency of Printf format strings and arguments
of arguments with no format string.
`
+// Kind is a kind of fmt function behavior.
+type Kind int
+
+const (
+ KindNone Kind = iota // not a fmt wrapper function
+ KindPrint // function behaves like fmt.Print
+ KindPrintf // function behaves like fmt.Printf
+ KindErrorf // function behaves like fmt.Errorf
+)
+
+func (kind Kind) String() string {
+ switch kind {
+ case KindPrint:
+ return "print"
+ case KindPrintf:
+ return "printf"
+ case KindErrorf:
+ return "errorf"
+ }
+ return ""
+}
+
+// Result is the printf analyzer's result type. Clients may query the result
+// to learn whether a function behaves like fmt.Print or fmt.Printf.
+type Result struct {
+ funcs map[*types.Func]Kind
+}
+
+// Kind reports whether fn behaves like fmt.Print or fmt.Printf.
+func (r *Result) Kind(fn *types.Func) Kind {
+ _, ok := isPrint[fn.FullName()]
+ if !ok {
+ // Next look up just "printf", for use with -printf.funcs.
+ _, ok = isPrint[strings.ToLower(fn.Name())]
+ }
+ if ok {
+ if strings.HasSuffix(fn.Name(), "f") {
+ return KindPrintf
+ } else {
+ return KindPrint
+ }
+ }
+
+ return r.funcs[fn]
+}
+
// isWrapper is a fact indicating that a function is a print or printf wrapper.
-type isWrapper struct{ Kind funcKind }
+type isWrapper struct{ Kind Kind }
func (f *isWrapper) AFact() {}
func (f *isWrapper) String() string {
switch f.Kind {
- case kindPrintf:
+ case KindPrintf:
return "printfWrapper"
- case kindPrint:
+ case KindPrint:
return "printWrapper"
- case kindErrorf:
+ case KindErrorf:
return "errorfWrapper"
default:
return "unknownWrapper"
}
func run(pass *analysis.Pass) (interface{}, error) {
- findPrintfLike(pass)
+ res := &Result{
+ funcs: make(map[*types.Func]Kind),
+ }
+ findPrintfLike(pass, res)
checkCall(pass)
- return nil, nil
+ return res, nil
}
type printfWrapper struct {
}
// findPrintfLike scans the entire package to find printf-like functions.
-func findPrintfLike(pass *analysis.Pass) (interface{}, error) {
+func findPrintfLike(pass *analysis.Pass, res *Result) (interface{}, error) {
// Gather potential wrappers and call graph between them.
byObj := make(map[*types.Func]*printfWrapper)
var wrappers []*printfWrapper
fn, kind := printfNameAndKind(pass, call)
if kind != 0 {
- checkPrintfFwd(pass, w, call, kind)
+ checkPrintfFwd(pass, w, call, kind, res)
return true
}
return ok && info.ObjectOf(id) == param
}
-type funcKind int
-
-const (
- kindUnknown funcKind = iota
- kindPrintf = iota
- kindPrint
- kindErrorf
-)
-
// checkPrintfFwd checks that a printf-forwarding wrapper is forwarding correctly.
// It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...).
-func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind funcKind) {
- matched := kind == kindPrint ||
- kind != kindUnknown && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format)
+func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind Kind, res *Result) {
+ matched := kind == KindPrint ||
+ kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format)
if !matched {
return
}
return
}
desc := "printf"
- if kind == kindPrint {
+ if kind == KindPrint {
desc = "print"
}
- pass.Reportf(call.Pos(), "missing ... in args forwarded to %s-like function", desc)
+ pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc)
return
}
fn := w.obj
if !pass.ImportObjectFact(fn, &fact) {
fact.Kind = kind
pass.ExportObjectFact(fn, &fact)
+ res.funcs[fn] = kind
for _, caller := range w.callers {
- checkPrintfFwd(pass, caller.w, caller.call, kind)
+ checkPrintfFwd(pass, caller.w, caller.call, kind, res)
}
}
}
call := n.(*ast.CallExpr)
fn, kind := printfNameAndKind(pass, call)
switch kind {
- case kindPrintf, kindErrorf:
+ case KindPrintf, KindErrorf:
checkPrintf(pass, kind, call, fn)
- case kindPrint:
+ case KindPrint:
checkPrint(pass, call, fn)
}
})
}
-func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind funcKind) {
+func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind Kind) {
fn, _ = typeutil.Callee(pass.TypesInfo, call).(*types.Func)
if fn == nil {
return nil, 0
}
if ok {
if fn.Name() == "Errorf" {
- kind = kindErrorf
+ kind = KindErrorf
} else if strings.HasSuffix(fn.Name(), "f") {
- kind = kindPrintf
+ kind = KindPrintf
} else {
- kind = kindPrint
+ kind = KindPrint
}
return fn, kind
}
return fn, fact.Kind
}
- return fn, kindUnknown
+ return fn, KindNone
}
// isFormatter reports whether t satisfies fmt.Formatter.
}
// checkPrintf checks a call to a formatted print routine such as Printf.
-func checkPrintf(pass *analysis.Pass, kind funcKind, call *ast.CallExpr, fn *types.Func) {
+func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, fn *types.Func) {
format, idx := formatString(pass, call)
if idx < 0 {
if false {
anyIndex = true
}
if state.verb == 'w' {
- if kind != kindErrorf {
+ if kind != KindErrorf {
pass.Reportf(call.Pos(), "%s call has error-wrapping directive %%w", state.name)
return
}
if maxArgNum != len(call.Args) {
expect := maxArgNum - firstArg
numArgs := len(call.Args) - firstArg
- pass.Reportf(call.Pos(), "%s call needs %v but has %v", fn.Name(), count(expect, "arg"), count(numArgs, "arg"))
+ pass.ReportRangef(call, "%s call needs %v but has %v", fn.Name(), count(expect, "arg"), count(numArgs, "arg"))
}
}
ok = false
s.nbytes = strings.Index(s.format, "]")
if s.nbytes < 0 {
- s.pass.Reportf(s.call.Pos(), "%s format %s is missing closing ]", s.name, s.format)
+ s.pass.ReportRangef(s.call, "%s format %s is missing closing ]", s.name, s.format)
return false
}
}
arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
if err != nil || !ok || arg32 <= 0 || arg32 > int64(len(s.call.Args)-s.firstArg) {
- s.pass.Reportf(s.call.Pos(), "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes])
+ s.pass.ReportRangef(s.call, "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes])
return false
}
s.nbytes++ // skip ']'
return nil
}
if state.nbytes == len(state.format) {
- pass.Reportf(call.Pos(), "%s format %s is missing verb at end of string", name, state.format)
+ pass.ReportRangef(call.Fun, "%s format %s is missing verb at end of string", name, state.format)
return nil
}
verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
// '#' is alternate format for several verbs.
// ' ' is spacer for numbers
{'%', noFlag, 0},
- {'b', numFlag, argInt | argFloat | argComplex | argPointer},
+ {'b', sharpNumFlag, argInt | argFloat | argComplex | argPointer},
{'c', "-", argRune | argInt},
{'d', numFlag, argInt | argPointer},
{'e', sharpNumFlag, argFloat | argComplex},
if !formatter {
if !found {
- pass.Reportf(call.Pos(), "%s format %s has unknown verb %c", state.name, state.format, state.verb)
+ pass.ReportRangef(call, "%s format %s has unknown verb %c", state.name, state.format, state.verb)
return false
}
for _, flag := range state.flags {
continue
}
if !strings.ContainsRune(v.flags, rune(flag)) {
- pass.Reportf(call.Pos(), "%s format %s has unrecognized flag %c", state.name, state.format, flag)
+ pass.ReportRangef(call, "%s format %s has unrecognized flag %c", state.name, state.format, flag)
return false
}
}
}
arg := call.Args[argNum]
if !matchArgType(pass, argInt, nil, arg) {
- pass.Reportf(call.Pos(), "%s format %s uses non-int %s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg))
+ pass.ReportRangef(call, "%s format %s uses non-int %s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg))
return false
}
}
}
arg := call.Args[argNum]
if isFunctionValue(pass, arg) && state.verb != 'p' && state.verb != 'T' {
- pass.Reportf(call.Pos(), "%s format %s arg %s is a func value, not called", state.name, state.format, analysisutil.Format(pass.Fset, arg))
+ 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 typ := pass.TypesInfo.Types[arg].Type; typ != nil {
typeString = typ.String()
}
- pass.Reportf(call.Pos(), "%s format %s has arg %s of wrong type %s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString)
+ pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString)
return false
}
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && recursiveStringer(pass, arg) {
- pass.Reportf(call.Pos(), "%s format %s with arg %s causes recursive String method call", state.name, state.format, analysisutil.Format(pass.Fset, arg))
+ pass.ReportRangef(call, "%s format %s with arg %s causes recursive String method call", state.name, state.format, analysisutil.Format(pass.Fset, arg))
return false
}
return true
// There are bad indexes in the format or there are fewer arguments than the format needs.
// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed.
- pass.Reportf(call.Pos(), "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg"))
+ pass.ReportRangef(call, "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg"))
return false
}
if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
if x, ok := sel.X.(*ast.Ident); ok {
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
- pass.Reportf(call.Pos(), "%s does not take io.Writer but has first arg %s", fn.Name(), analysisutil.Format(pass.Fset, call.Args[0]))
+ pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", fn.Name(), analysisutil.Format(pass.Fset, call.Args[0]))
}
}
}
if strings.Contains(s, "%") {
m := printFormatRE.FindStringSubmatch(s)
if m != nil {
- pass.Reportf(call.Pos(), "%s call has possible formatting directive %s", fn.Name(), m[0])
+ pass.ReportRangef(call, "%s call has possible formatting directive %s", fn.Name(), m[0])
}
}
}
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
str, _ := strconv.Unquote(lit.Value)
if strings.HasSuffix(str, "\n") {
- pass.Reportf(call.Pos(), "%s arg list ends with redundant newline", fn.Name())
+ pass.ReportRangef(call, "%s arg list ends with redundant newline", fn.Name())
}
}
}
for _, arg := range args {
if isFunctionValue(pass, arg) {
- pass.Reportf(call.Pos(), "%s arg %s is a func value, not called", fn.Name(), analysisutil.Format(pass.Fset, arg))
+ pass.ReportRangef(call, "%s arg %s is a func value, not called", fn.Name(), analysisutil.Format(pass.Fset, arg))
}
if recursiveStringer(pass, arg) {
- pass.Reportf(call.Pos(), "%s arg %s causes recursive call to String method", fn.Name(), analysisutil.Format(pass.Fset, arg))
+ pass.ReportRangef(call, "%s arg %s causes recursive call to String method", fn.Name(), analysisutil.Format(pass.Fset, arg))
}
}
}