diff --git a/go/analysis/passes/printf/printf.go b/go/analysis/passes/printf/printf.go new file mode 100644 index 00000000000..f980dcb5e87 --- /dev/null +++ b/go/analysis/passes/printf/printf.go @@ -0,0 +1,1072 @@ +// +build ignore + +// Copyright 2010 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. + +// This file contains the printf-checker. + +package main + +import ( + "bytes" + "encoding/gob" + "flag" + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf8" +) + +var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") + +func init() { + register("printf", + "check printf-like invocations", + checkFmtPrintfCall, + funcDecl, callExpr) + registerPkgCheck("printf", findPrintfLike) + registerExport("printf", exportPrintfLike) + gob.Register([]printfExport(nil)) +} + +func initPrintFlags() { + if *printfuncs == "" { + return + } + for _, name := range strings.Split(*printfuncs, ",") { + if len(name) == 0 { + flag.Usage() + } + + // Backwards compatibility: skip optional first argument + // index after the colon. + if colon := strings.LastIndex(name, ":"); colon > 0 { + name = name[:colon] + } + + if !strings.Contains(name, ".") { + name = strings.ToLower(name) + } + isPrint[name] = true + } +} + +var localPrintfLike = make(map[string]int) + +type printfExport struct { + Name string + Kind int +} + +// printfImported maps from package name to the printf vet data +// exported by that package. +var printfImported = make(map[string]map[string]int) + +type printfWrapper struct { + name string + fn *ast.FuncDecl + format *ast.Field + args *ast.Field + callers []printfCaller + failed bool // if true, not a printf wrapper +} + +type printfCaller struct { + w *printfWrapper + call *ast.CallExpr +} + +// maybePrintfWrapper decides whether decl (a declared function) may be a wrapper +// around a fmt.Printf or fmt.Print function. If so it returns a printfWrapper +// function describing the declaration. Later processing will analyze the +// graph of potential printf wrappers to pick out the ones that are true wrappers. +// A function may be a Printf or Print wrapper if its last argument is ...interface{}. +// If the next-to-last argument is a string, then this may be a Printf wrapper. +// Otherwise it may be a Print wrapper. +func maybePrintfWrapper(decl ast.Decl) *printfWrapper { + // Look for functions with final argument type ...interface{}. + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Body == nil { + return nil + } + name := fn.Name.Name + if fn.Recv != nil { + // For (*T).Name or T.name, use "T.name". + rcvr := fn.Recv.List[0].Type + if ptr, ok := rcvr.(*ast.StarExpr); ok { + rcvr = ptr.X + } + id, ok := rcvr.(*ast.Ident) + if !ok { + return nil + } + name = id.Name + "." + name + } + params := fn.Type.Params.List + if len(params) == 0 { + return nil + } + args := params[len(params)-1] + if len(args.Names) != 1 { + return nil + } + ddd, ok := args.Type.(*ast.Ellipsis) + if !ok { + return nil + } + iface, ok := ddd.Elt.(*ast.InterfaceType) + if !ok || len(iface.Methods.List) > 0 { + return nil + } + var format *ast.Field + if len(params) >= 2 { + p := params[len(params)-2] + if len(p.Names) == 1 { + if id, ok := p.Type.(*ast.Ident); ok && id.Name == "string" { + format = p + } + } + } + + return &printfWrapper{ + name: name, + fn: fn, + format: format, + args: args, + } +} + +// findPrintfLike scans the entire package to find printf-like functions. +func findPrintfLike(pkg *Package) { + if vcfg.ImportPath == "" { // no type or vetx information; don't bother + return + } + + // Gather potential wrappesr and call graph between them. + byName := make(map[string]*printfWrapper) + var wrappers []*printfWrapper + for _, file := range pkg.files { + if file.file == nil { + continue + } + for _, decl := range file.file.Decls { + w := maybePrintfWrapper(decl) + if w == nil { + continue + } + byName[w.name] = w + wrappers = append(wrappers, w) + } + } + + // Walk the graph to figure out which are really printf wrappers. + for _, w := range wrappers { + // Scan function for calls that could be to other printf-like functions. + ast.Inspect(w.fn.Body, func(n ast.Node) bool { + if w.failed { + return false + } + + // TODO: Relax these checks; issue 26555. + if assign, ok := n.(*ast.AssignStmt); ok { + for _, lhs := range assign.Lhs { + if match(lhs, w.format) || match(lhs, w.args) { + // Modifies the format + // string or args in + // some way, so not a + // simple wrapper. + w.failed = true + return false + } + } + } + if un, ok := n.(*ast.UnaryExpr); ok && un.Op == token.AND { + if match(un.X, w.format) || match(un.X, w.args) { + // Taking the address of the + // format string or args, + // so not a simple wrapper. + w.failed = true + return false + } + } + + call, ok := n.(*ast.CallExpr) + if !ok || len(call.Args) == 0 || !match(call.Args[len(call.Args)-1], w.args) { + return true + } + + pkgpath, name, kind := printfNameAndKind(pkg, call.Fun) + if kind != 0 { + checkPrintfFwd(pkg, w, call, kind) + return true + } + + // If the call is to another function in this package, + // maybe we will find out it is printf-like later. + // Remember this call for later checking. + if pkgpath == "" && byName[name] != nil { + callee := byName[name] + callee.callers = append(callee.callers, printfCaller{w, call}) + } + + return true + }) + } +} + +func match(arg ast.Expr, param *ast.Field) bool { + id, ok := arg.(*ast.Ident) + return ok && id.Obj != nil && id.Obj.Decl == param +} + +const ( + kindPrintf = 1 + kindPrint = 2 +) + +// printfLike reports whether a call to fn should be considered a call to a printf-like function. +// It returns 0 (indicating not a printf-like function), kindPrintf, or kindPrint. +func printfLike(pkg *Package, fn ast.Expr, byName map[string]*printfWrapper) int { + if id, ok := fn.(*ast.Ident); ok && id.Obj != nil { + if w := byName[id.Name]; w != nil && id.Obj.Decl == w.fn { + // Found call to function in same package. + return localPrintfLike[id.Name] + } + } + if sel, ok := fn.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Name == "fmt" && strings.Contains(sel.Sel.Name, "rint") { + if strings.HasSuffix(sel.Sel.Name, "f") { + return kindPrintf + } + return kindPrint + } + } + return 0 +} + +// 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(pkg *Package, w *printfWrapper, call *ast.CallExpr, kind int) { + matched := kind == kindPrint || + kind == kindPrintf && len(call.Args) >= 2 && match(call.Args[len(call.Args)-2], w.format) + if !matched { + return + } + + if !call.Ellipsis.IsValid() { + typ, ok := pkg.types[call.Fun].Type.(*types.Signature) + if !ok { + return + } + if len(call.Args) > typ.Params().Len() { + // If we're passing more arguments than what the + // print/printf function can take, adding an ellipsis + // would break the program. For example: + // + // func foo(arg1 string, arg2 ...interface{} { + // fmt.Printf("%s %v", arg1, arg2) + // } + return + } + if !vcfg.VetxOnly { + desc := "printf" + if kind == kindPrint { + desc = "print" + } + pkg.files[0].Badf(call.Pos(), "missing ... in args forwarded to %s-like function", desc) + } + return + } + name := w.name + if localPrintfLike[name] == 0 { + localPrintfLike[name] = kind + for _, caller := range w.callers { + checkPrintfFwd(pkg, caller.w, caller.call, kind) + } + } +} + +func exportPrintfLike() interface{} { + out := make([]printfExport, 0, len(localPrintfLike)) + for name, kind := range localPrintfLike { + out = append(out, printfExport{ + Name: name, + Kind: kind, + }) + } + sort.Slice(out, func(i, j int) bool { + return out[i].Name < out[j].Name + }) + return out +} + +// isPrint records the print functions. +// If a key ends in 'f' then it is assumed to be a formatted print. +var isPrint = map[string]bool{ + "fmt.Errorf": true, + "fmt.Fprint": true, + "fmt.Fprintf": true, + "fmt.Fprintln": true, + "fmt.Print": true, + "fmt.Printf": true, + "fmt.Println": true, + "fmt.Sprint": true, + "fmt.Sprintf": true, + "fmt.Sprintln": true, + + // testing.B, testing.T not auto-detected + // because the methods are picked up by embedding. + "testing.B.Error": true, + "testing.B.Errorf": true, + "testing.B.Fatal": true, + "testing.B.Fatalf": true, + "testing.B.Log": true, + "testing.B.Logf": true, + "testing.B.Skip": true, + "testing.B.Skipf": true, + "testing.T.Error": true, + "testing.T.Errorf": true, + "testing.T.Fatal": true, + "testing.T.Fatalf": true, + "testing.T.Log": true, + "testing.T.Logf": true, + "testing.T.Skip": true, + "testing.T.Skipf": true, + + // testing.TB is an interface, so can't detect wrapping. + "testing.TB.Error": true, + "testing.TB.Errorf": true, + "testing.TB.Fatal": true, + "testing.TB.Fatalf": true, + "testing.TB.Log": true, + "testing.TB.Logf": true, + "testing.TB.Skip": true, + "testing.TB.Skipf": true, +} + +// formatString returns the format string argument and its index within +// the given printf-like call expression. +// +// The last parameter before variadic arguments is assumed to be +// a format string. +// +// The first string literal or string constant is assumed to be a format string +// if the call's signature cannot be determined. +// +// If it cannot find any format string parameter, it returns ("", -1). +func formatString(f *File, call *ast.CallExpr) (format string, idx int) { + typ := f.pkg.types[call.Fun].Type + if typ != nil { + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions. + return "", -1 + } + idx := sig.Params().Len() - 2 + if idx < 0 { + // Skip checking variadic functions without + // fixed arguments. + return "", -1 + } + s, ok := stringConstantArg(f, call, idx) + if !ok { + // The last argument before variadic args isn't a string. + return "", -1 + } + return s, idx + } + } + + // Cannot determine call's signature. Fall back to scanning for the first + // string constant in the call. + for idx := range call.Args { + if s, ok := stringConstantArg(f, call, idx); ok { + return s, idx + } + if f.pkg.types[call.Args[idx]].Type == types.Typ[types.String] { + // Skip checking a call with a non-constant format + // string argument, since its contents are unavailable + // for validation. + return "", -1 + } + } + return "", -1 +} + +// stringConstantArg returns call's string constant argument at the index idx. +// +// ("", false) is returned if call's argument at the index idx isn't a string +// constant. +func stringConstantArg(f *File, call *ast.CallExpr, idx int) (string, bool) { + if idx >= len(call.Args) { + return "", false + } + arg := call.Args[idx] + lit := f.pkg.types[arg].Value + if lit != nil && lit.Kind() == constant.String { + return constant.StringVal(lit), true + } + return "", false +} + +// checkCall triggers the print-specific checks if the call invokes a print function. +func checkFmtPrintfCall(f *File, node ast.Node) { + if f.pkg.typesPkg == nil { + // This check now requires type information. + return + } + + if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) { + // Remember we saw this. + if f.stringerPtrs == nil { + f.stringerPtrs = make(map[*ast.Object]bool) + } + if l := d.Recv.List; len(l) == 1 { + if n := l[0].Names; len(n) == 1 { + typ := f.pkg.types[l[0].Type] + _, ptrRecv := typ.Type.(*types.Pointer) + f.stringerPtrs[n[0].Obj] = ptrRecv + } + } + return + } + + call, ok := node.(*ast.CallExpr) + if !ok { + return + } + + // Construct name like pkg.Printf or pkg.Type.Printf for lookup. + _, name, kind := printfNameAndKind(f.pkg, call.Fun) + if kind == kindPrintf { + f.checkPrintf(call, name) + } + if kind == kindPrint { + f.checkPrint(call, name) + } +} + +func printfName(pkg *Package, called ast.Expr) (pkgpath, name string) { + switch x := called.(type) { + case *ast.Ident: + if fn, ok := pkg.uses[x].(*types.Func); ok { + if fn.Pkg() == nil || fn.Pkg() == pkg.typesPkg { + pkgpath = "" + } else { + pkgpath = fn.Pkg().Path() + } + return pkgpath, x.Name + } + + case *ast.SelectorExpr: + // Check for "fmt.Printf". + if id, ok := x.X.(*ast.Ident); ok { + if pkgName, ok := pkg.uses[id].(*types.PkgName); ok { + return pkgName.Imported().Path(), x.Sel.Name + } + } + + // Check for t.Logf where t is a *testing.T. + if sel := pkg.selectors[x]; sel != nil { + recv := sel.Recv() + if p, ok := recv.(*types.Pointer); ok { + recv = p.Elem() + } + if named, ok := recv.(*types.Named); ok { + obj := named.Obj() + if obj.Pkg() == nil || obj.Pkg() == pkg.typesPkg { + pkgpath = "" + } else { + pkgpath = obj.Pkg().Path() + } + return pkgpath, obj.Name() + "." + x.Sel.Name + } + } + } + return "", "" +} + +func printfNameAndKind(pkg *Package, called ast.Expr) (pkgpath, name string, kind int) { + pkgpath, name = printfName(pkg, called) + if name == "" { + return pkgpath, name, 0 + } + + if pkgpath == "" { + kind = localPrintfLike[name] + } else if m, ok := printfImported[pkgpath]; ok { + kind = m[name] + } else { + var m map[string]int + if out, ok := readVetx(pkgpath, "printf").([]printfExport); ok { + m = make(map[string]int) + for _, x := range out { + m[x.Name] = x.Kind + } + } + printfImported[pkgpath] = m + kind = m[name] + } + + if kind == 0 { + _, ok := isPrint[pkgpath+"."+name] + if !ok { + // Next look up just "printf", for use with -printfuncs. + short := name[strings.LastIndex(name, ".")+1:] + _, ok = isPrint[strings.ToLower(short)] + } + if ok { + if strings.HasSuffix(name, "f") { + kind = kindPrintf + } else { + kind = kindPrint + } + } + } + return pkgpath, name, kind +} + +// isStringer returns true if the provided declaration is a "String() string" +// method, an implementation of fmt.Stringer. +func isStringer(f *File, d *ast.FuncDecl) bool { + return d.Recv != nil && d.Name.Name == "String" && d.Type.Results != nil && + len(d.Type.Params.List) == 0 && len(d.Type.Results.List) == 1 && + f.pkg.types[d.Type.Results.List[0].Type].Type == types.Typ[types.String] +} + +// isFormatter reports whether t satisfies fmt.Formatter. +// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt. +func (f *File) isFormatter(t types.Type) bool { + return formatterType != nil && types.Implements(t, formatterType) +} + +// formatState holds the parsed representation of a printf directive such as "%3.*[4]d". +// It is constructed by parsePrintfVerb. +type formatState struct { + verb rune // the format verb: 'd' for "%d" + format string // the full format directive from % through verb, "%.3d". + name string // Printf, Sprintf etc. + flags []byte // the list of # + etc. + argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call + firstArg int // Index of first argument after the format in the Printf call. + // Used only during parse. + file *File + call *ast.CallExpr + argNum int // Which argument we're expecting to format now. + hasIndex bool // Whether the argument is indexed. + indexPending bool // Whether we have an indexed argument that has not resolved. + nbytes int // number of bytes of the format string consumed. +} + +// checkPrintf checks a call to a formatted print routine such as Printf. +func (f *File) checkPrintf(call *ast.CallExpr, name string) { + format, idx := formatString(f, call) + if idx < 0 { + if *verbose { + f.Warn(call.Pos(), "can't check non-constant format in call to", name) + } + return + } + + firstArg := idx + 1 // Arguments are immediately after format string. + if !strings.Contains(format, "%") { + if len(call.Args) > firstArg { + f.Badf(call.Pos(), "%s call has arguments but no formatting directives", name) + } + return + } + // Hard part: check formats against args. + argNum := firstArg + maxArgNum := firstArg + anyIndex := false + for i, w := 0, 0; i < len(format); i += w { + w = 1 + if format[i] != '%' { + continue + } + state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum) + if state == nil { + return + } + w = len(state.format) + if !f.okPrintfArg(call, state) { // One error per format is enough. + return + } + if state.hasIndex { + anyIndex = true + } + if len(state.argNums) > 0 { + // Continue with the next sequential argument. + argNum = state.argNums[len(state.argNums)-1] + 1 + } + for _, n := range state.argNums { + if n >= maxArgNum { + maxArgNum = n + 1 + } + } + } + // Dotdotdot is hard. + if call.Ellipsis.IsValid() && maxArgNum >= len(call.Args)-1 { + return + } + // If any formats are indexed, extra arguments are ignored. + if anyIndex { + return + } + // There should be no leftover arguments. + if maxArgNum != len(call.Args) { + expect := maxArgNum - firstArg + numArgs := len(call.Args) - firstArg + f.Badf(call.Pos(), "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg")) + } +} + +// parseFlags accepts any printf flags. +func (s *formatState) parseFlags() { + for s.nbytes < len(s.format) { + switch c := s.format[s.nbytes]; c { + case '#', '0', '+', '-', ' ': + s.flags = append(s.flags, c) + s.nbytes++ + default: + return + } + } +} + +// scanNum advances through a decimal number if present. +func (s *formatState) scanNum() { + for ; s.nbytes < len(s.format); s.nbytes++ { + c := s.format[s.nbytes] + if c < '0' || '9' < c { + return + } + } +} + +// parseIndex scans an index expression. It returns false if there is a syntax error. +func (s *formatState) parseIndex() bool { + if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' { + return true + } + // Argument index present. + s.nbytes++ // skip '[' + start := s.nbytes + s.scanNum() + ok := true + if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' { + ok = false + s.nbytes = strings.Index(s.format, "]") + if s.nbytes < 0 { + s.file.Badf(s.call.Pos(), "%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.file.Badf(s.call.Pos(), "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes]) + return false + } + s.nbytes++ // skip ']' + arg := int(arg32) + arg += s.firstArg - 1 // We want to zero-index the actual arguments. + s.argNum = arg + s.hasIndex = true + s.indexPending = true + return true +} + +// parseNum scans a width or precision (or *). It returns false if there's a bad index expression. +func (s *formatState) parseNum() bool { + if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' { + if s.indexPending { // Absorb it. + s.indexPending = false + } + s.nbytes++ + s.argNums = append(s.argNums, s.argNum) + s.argNum++ + } else { + s.scanNum() + } + return true +} + +// parsePrecision scans for a precision. It returns false if there's a bad index expression. +func (s *formatState) parsePrecision() bool { + // If there's a period, there may be a precision. + if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' { + s.flags = append(s.flags, '.') // Treat precision as a flag. + s.nbytes++ + if !s.parseIndex() { + return false + } + if !s.parseNum() { + return false + } + } + return true +} + +// parsePrintfVerb looks the formatting directive that begins the format string +// and returns a formatState that encodes what the directive wants, without looking +// at the actual arguments present in the call. The result is nil if there is an error. +func (f *File) parsePrintfVerb(call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState { + state := &formatState{ + format: format, + name: name, + flags: make([]byte, 0, 5), + argNum: argNum, + argNums: make([]int, 0, 1), + nbytes: 1, // There's guaranteed to be a percent sign. + firstArg: firstArg, + file: f, + call: call, + } + // There may be flags. + state.parseFlags() + // There may be an index. + if !state.parseIndex() { + return nil + } + // There may be a width. + if !state.parseNum() { + return nil + } + // There may be a precision. + if !state.parsePrecision() { + return nil + } + // Now a verb, possibly prefixed by an index (which we may already have). + if !state.indexPending && !state.parseIndex() { + return nil + } + if state.nbytes == len(state.format) { + f.Badf(call.Pos(), "%s format %s is missing verb at end of string", name, state.format) + return nil + } + verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:]) + state.verb = verb + state.nbytes += w + if verb != '%' { + state.argNums = append(state.argNums, state.argNum) + } + state.format = state.format[:state.nbytes] + return state +} + +// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask. +type printfArgType int + +const ( + argBool printfArgType = 1 << iota + argInt + argRune + argString + argFloat + argComplex + argPointer + anyType printfArgType = ^0 +) + +type printVerb struct { + verb rune // User may provide verb through Formatter; could be a rune. + flags string // known flags are all ASCII + typ printfArgType +} + +// Common flag sets for printf verbs. +const ( + noFlag = "" + numFlag = " -+.0" + sharpNumFlag = " -+.0#" + allFlags = " -+.0#" +) + +// printVerbs identifies which flags are known to printf for each verb. +var printVerbs = []printVerb{ + // '-' is a width modifier, always valid. + // '.' is a precision for float, max width for strings. + // '+' is required sign for numbers, Go format for %v. + // '#' is alternate format for several verbs. + // ' ' is spacer for numbers + {'%', noFlag, 0}, + {'b', numFlag, argInt | argFloat | argComplex}, + {'c', "-", argRune | argInt}, + {'d', numFlag, argInt | argPointer}, + {'e', sharpNumFlag, argFloat | argComplex}, + {'E', sharpNumFlag, argFloat | argComplex}, + {'f', sharpNumFlag, argFloat | argComplex}, + {'F', sharpNumFlag, argFloat | argComplex}, + {'g', sharpNumFlag, argFloat | argComplex}, + {'G', sharpNumFlag, argFloat | argComplex}, + {'o', sharpNumFlag, argInt}, + {'p', "-#", argPointer}, + {'q', " -+.0#", argRune | argInt | argString}, + {'s', " -+.0", argString}, + {'t', "-", argBool}, + {'T', "-", anyType}, + {'U', "-#", argRune | argInt}, + {'v', allFlags, anyType}, + {'x', sharpNumFlag, argRune | argInt | argString | argPointer}, + {'X', sharpNumFlag, argRune | argInt | argString | argPointer}, +} + +// okPrintfArg compares the formatState to the arguments actually present, +// reporting any discrepancies it can discern. If the final argument is ellipsissed, +// there's little it can do for that. +func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) { + var v printVerb + found := false + // Linear scan is fast enough for a small list. + for _, v = range printVerbs { + if v.verb == state.verb { + found = true + break + } + } + + // Does current arg implement fmt.Formatter? + formatter := false + if state.argNum < len(call.Args) { + if tv, ok := f.pkg.types[call.Args[state.argNum]]; ok { + formatter = f.isFormatter(tv.Type) + } + } + + if !formatter { + if !found { + f.Badf(call.Pos(), "%s format %s has unknown verb %c", state.name, state.format, state.verb) + return false + } + for _, flag := range state.flags { + // TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11. + // See issues 23598 and 23605. + if flag == '0' { + continue + } + if !strings.ContainsRune(v.flags, rune(flag)) { + f.Badf(call.Pos(), "%s format %s has unrecognized flag %c", state.name, state.format, flag) + return false + } + } + } + // Verb is good. If len(state.argNums)>trueArgs, we have something like %.*s and all + // but the final arg must be an integer. + trueArgs := 1 + if state.verb == '%' { + trueArgs = 0 + } + nargs := len(state.argNums) + for i := 0; i < nargs-trueArgs; i++ { + argNum := state.argNums[i] + if !f.argCanBeChecked(call, i, state) { + return + } + arg := call.Args[argNum] + if !f.matchArgType(argInt, nil, arg) { + f.Badf(call.Pos(), "%s format %s uses non-int %s as argument of *", state.name, state.format, f.gofmt(arg)) + return false + } + } + if state.verb == '%' || formatter { + return true + } + argNum := state.argNums[len(state.argNums)-1] + if !f.argCanBeChecked(call, len(state.argNums)-1, state) { + return false + } + arg := call.Args[argNum] + if f.isFunctionValue(arg) && state.verb != 'p' && state.verb != 'T' { + f.Badf(call.Pos(), "%s format %s arg %s is a func value, not called", state.name, state.format, f.gofmt(arg)) + return false + } + if !f.matchArgType(v.typ, nil, arg) { + typeString := "" + if typ := f.pkg.types[arg].Type; typ != nil { + typeString = typ.String() + } + f.Badf(call.Pos(), "%s format %s has arg %s of wrong type %s", state.name, state.format, f.gofmt(arg), typeString) + return false + } + if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) { + f.Badf(call.Pos(), "%s format %s with arg %s causes recursive String method call", state.name, state.format, f.gofmt(arg)) + return false + } + return true +} + +// recursiveStringer reports whether the provided argument is r or &r for the +// fmt.Stringer receiver identifier r. +func (f *File) recursiveStringer(e ast.Expr) bool { + if len(f.stringerPtrs) == 0 { + return false + } + ptr := false + var obj *ast.Object + switch e := e.(type) { + case *ast.Ident: + obj = e.Obj + case *ast.UnaryExpr: + if id, ok := e.X.(*ast.Ident); ok && e.Op == token.AND { + obj = id.Obj + ptr = true + } + } + + // It's unlikely to be a recursive stringer if it has a Format method. + if typ := f.pkg.types[e].Type; typ != nil { + if f.isFormatter(typ) { + return false + } + } + + // We compare the underlying Object, which checks that the identifier + // is the one we declared as the receiver for the String method in + // which this printf appears. + ptrRecv, exist := f.stringerPtrs[obj] + if !exist { + return false + } + // We also need to check that using &t when we declared String + // on (t *T) is ok; in such a case, the address is printed. + if ptr && ptrRecv { + return false + } + return true +} + +// isFunctionValue reports whether the expression is a function as opposed to a function call. +// It is almost always a mistake to print a function value. +func (f *File) isFunctionValue(e ast.Expr) bool { + if typ := f.pkg.types[e].Type; typ != nil { + _, ok := typ.(*types.Signature) + return ok + } + return false +} + +// argCanBeChecked reports whether the specified argument is statically present; +// it may be beyond the list of arguments or in a terminal slice... argument, which +// means we can't see it. +func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, state *formatState) bool { + argNum := state.argNums[formatArg] + if argNum <= 0 { + // Shouldn't happen, so catch it with prejudice. + panic("negative arg num") + } + if argNum < len(call.Args)-1 { + return true // Always OK. + } + if call.Ellipsis.IsValid() { + return false // We just can't tell; there could be many more arguments. + } + if argNum < len(call.Args) { + 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. + f.Badf(call.Pos(), "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg")) + return false +} + +// printFormatRE is the regexp we match and report as a possible format string +// in the first argument to unformatted prints like fmt.Print. +// We exclude the space flag, so that printing a string like "x % y" is not reported as a format. +var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE) + +const ( + flagsRE = `[+\-#]*` + indexOptRE = `(\[[0-9]+\])?` + numOptRE = `([0-9]+|` + indexOptRE + `\*)?` + verbRE = `[bcdefgopqstvxEFGTUX]` +) + +// checkPrint checks a call to an unformatted print routine such as Println. +func (f *File) checkPrint(call *ast.CallExpr, name string) { + firstArg := 0 + typ := f.pkg.types[call.Fun].Type + if typ == nil { + // Skip checking functions with unknown type. + return + } + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions. + return + } + params := sig.Params() + firstArg = params.Len() - 1 + + typ := params.At(firstArg).Type() + typ = typ.(*types.Slice).Elem() + it, ok := typ.(*types.Interface) + if !ok || !it.Empty() { + // Skip variadic functions accepting non-interface{} args. + return + } + } + args := call.Args + if len(args) <= firstArg { + // Skip calls without variadic args. + return + } + args = args[firstArg:] + + if firstArg == 0 { + 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") { + f.Badf(call.Pos(), "%s does not take io.Writer but has first arg %s", name, f.gofmt(call.Args[0])) + } + } + } + } + + arg := args[0] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + // Ignore trailing % character in lit.Value. + // The % in "abc 0.0%" couldn't be a formatting directive. + s := strings.TrimSuffix(lit.Value, `%"`) + if strings.Contains(s, "%") { + m := printFormatRE.FindStringSubmatch(s) + if m != nil { + f.Badf(call.Pos(), "%s call has possible formatting directive %s", name, m[0]) + } + } + } + if strings.HasSuffix(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") { + f.Badf(call.Pos(), "%s arg list ends with redundant newline", name) + } + } + } + for _, arg := range args { + if f.isFunctionValue(arg) { + f.Badf(call.Pos(), "%s arg %s is a func value, not called", name, f.gofmt(arg)) + } + if f.recursiveStringer(arg) { + f.Badf(call.Pos(), "%s arg %s causes recursive call to String method", name, f.gofmt(arg)) + } + } +} + +// count(n, what) returns "1 what" or "N whats" +// (assuming the plural of what is whats). +func count(n int, what string) string { + if n == 1 { + return "1 " + what + } + return fmt.Sprintf("%d %ss", n, what) +} diff --git a/go/analysis/passes/printf/testdata/src/a/a.go b/go/analysis/passes/printf/testdata/src/a/a.go new file mode 100644 index 00000000000..88163b59d94 --- /dev/null +++ b/go/analysis/passes/printf/testdata/src/a/a.go @@ -0,0 +1,647 @@ +// Copyright 2010 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. + +// This file contains tests for the printf checker. + +package testdata + +import ( + "fmt" + logpkg "log" // renamed to make it harder to see + "math" + "os" + "testing" + "unsafe" // just for test case printing unsafe.Pointer + // For testing printf-like functions from external package. + // "github.com/foobar/externalprintf" +) + +func UnsafePointerPrintfTest() { + var up unsafe.Pointer + fmt.Printf("%p, %x %X", up, up, up) +} + +// Error methods that do not satisfy the Error interface and should be checked. +type errorTest1 int + +func (errorTest1) Error(...interface{}) string { + return "hi" +} + +type errorTest2 int // Analogous to testing's *T type. +func (errorTest2) Error(...interface{}) { +} + +type errorTest3 int + +func (errorTest3) Error() { // No return value. +} + +type errorTest4 int + +func (errorTest4) Error() int { // Different return type. + return 3 +} + +type errorTest5 int + +func (errorTest5) error() { // niladic; don't complain if no args (was bug) +} + +// This function never executes, but it serves as a simple test for the program. +// Test with make test. +func PrintfTests() { + var b bool + var i int + var r rune + var s string + var x float64 + var p *int + var imap map[int]int + var fslice []float64 + var c complex64 + // Some good format/argtypes + fmt.Printf("") + fmt.Printf("%b %b %b", 3, i, x) + fmt.Printf("%c %c %c %c", 3, i, 'x', r) + fmt.Printf("%d %d %d", 3, i, imap) + fmt.Printf("%e %e %e %e", 3e9, x, fslice, c) + fmt.Printf("%E %E %E %E", 3e9, x, fslice, c) + fmt.Printf("%f %f %f %f", 3e9, x, fslice, c) + fmt.Printf("%F %F %F %F", 3e9, x, fslice, c) + fmt.Printf("%g %g %g %g", 3e9, x, fslice, c) + fmt.Printf("%G %G %G %G", 3e9, x, fslice, c) + fmt.Printf("%b %b %b %b", 3e9, x, fslice, c) + fmt.Printf("%o %o", 3, i) + fmt.Printf("%p", p) + fmt.Printf("%q %q %q %q", 3, i, 'x', r) + fmt.Printf("%s %s %s", "hi", s, []byte{65}) + fmt.Printf("%t %t", true, b) + fmt.Printf("%T %T", 3, i) + fmt.Printf("%U %U", 3, i) + fmt.Printf("%v %v", 3, i) + fmt.Printf("%x %x %x %x", 3, i, "hi", s) + fmt.Printf("%X %X %X %X", 3, i, "hi", s) + fmt.Printf("%.*s %d %g", 3, "hi", 23, 2.3) + fmt.Printf("%s", &stringerv) + fmt.Printf("%v", &stringerv) + fmt.Printf("%T", &stringerv) + fmt.Printf("%s", &embeddedStringerv) + fmt.Printf("%v", &embeddedStringerv) + fmt.Printf("%T", &embeddedStringerv) + fmt.Printf("%v", notstringerv) + fmt.Printf("%T", notstringerv) + fmt.Printf("%q", stringerarrayv) + fmt.Printf("%v", stringerarrayv) + fmt.Printf("%s", stringerarrayv) + fmt.Printf("%v", notstringerarrayv) + fmt.Printf("%T", notstringerarrayv) + fmt.Printf("%d", new(fmt.Formatter)) + fmt.Printf("%*%", 2) // Ridiculous but allowed. + fmt.Printf("%s", interface{}(nil)) // Nothing useful we can say. + + fmt.Printf("%g", 1+2i) + fmt.Printf("%#e %#E %#f %#F %#g %#G", 1.2, 1.2, 1.2, 1.2, 1.2, 1.2) // OK since Go 1.9 + // Some bad format/argTypes + fmt.Printf("%b", "hi") // ERROR "Printf format %b has arg \x22hi\x22 of wrong type string" + fmt.Printf("%t", c) // ERROR "Printf format %t has arg c of wrong type complex64" + fmt.Printf("%t", 1+2i) // ERROR "Printf format %t has arg 1 \+ 2i of wrong type complex128" + fmt.Printf("%c", 2.3) // ERROR "Printf format %c has arg 2.3 of wrong type float64" + fmt.Printf("%d", 2.3) // ERROR "Printf format %d has arg 2.3 of wrong type float64" + fmt.Printf("%e", "hi") // ERROR "Printf format %e has arg \x22hi\x22 of wrong type string" + fmt.Printf("%E", true) // ERROR "Printf format %E has arg true of wrong type bool" + fmt.Printf("%f", "hi") // ERROR "Printf format %f has arg \x22hi\x22 of wrong type string" + fmt.Printf("%F", 'x') // ERROR "Printf format %F has arg 'x' of wrong type rune" + fmt.Printf("%g", "hi") // ERROR "Printf format %g has arg \x22hi\x22 of wrong type string" + fmt.Printf("%g", imap) // ERROR "Printf format %g has arg imap of wrong type map\[int\]int" + fmt.Printf("%G", i) // ERROR "Printf format %G has arg i of wrong type int" + fmt.Printf("%o", x) // ERROR "Printf format %o has arg x of wrong type float64" + fmt.Printf("%p", nil) // ERROR "Printf format %p has arg nil of wrong type untyped nil" + fmt.Printf("%p", 23) // ERROR "Printf format %p has arg 23 of wrong type int" + fmt.Printf("%q", x) // ERROR "Printf format %q has arg x of wrong type float64" + fmt.Printf("%s", b) // ERROR "Printf format %s has arg b of wrong type bool" + fmt.Printf("%s", byte(65)) // ERROR "Printf format %s has arg byte\(65\) of wrong type byte" + fmt.Printf("%t", 23) // ERROR "Printf format %t has arg 23 of wrong type int" + fmt.Printf("%U", x) // ERROR "Printf format %U has arg x of wrong type float64" + fmt.Printf("%x", nil) // ERROR "Printf format %x has arg nil of wrong type untyped nil" + fmt.Printf("%X", 2.3) // ERROR "Printf format %X has arg 2.3 of wrong type float64" + fmt.Printf("%s", stringerv) // ERROR "Printf format %s has arg stringerv of wrong type testdata.ptrStringer" + fmt.Printf("%t", stringerv) // ERROR "Printf format %t has arg stringerv of wrong type testdata.ptrStringer" + fmt.Printf("%s", embeddedStringerv) // ERROR "Printf format %s has arg embeddedStringerv of wrong type testdata.embeddedStringer" + fmt.Printf("%t", embeddedStringerv) // ERROR "Printf format %t has arg embeddedStringerv of wrong type testdata.embeddedStringer" + fmt.Printf("%q", notstringerv) // ERROR "Printf format %q has arg notstringerv of wrong type testdata.notstringer" + fmt.Printf("%t", notstringerv) // ERROR "Printf format %t has arg notstringerv of wrong type testdata.notstringer" + fmt.Printf("%t", stringerarrayv) // ERROR "Printf format %t has arg stringerarrayv of wrong type testdata.stringerarray" + fmt.Printf("%t", notstringerarrayv) // ERROR "Printf format %t has arg notstringerarrayv of wrong type testdata.notstringerarray" + fmt.Printf("%q", notstringerarrayv) // ERROR "Printf format %q has arg notstringerarrayv of wrong type testdata.notstringerarray" + fmt.Printf("%d", BoolFormatter(true)) // ERROR "Printf format %d has arg BoolFormatter\(true\) of wrong type testdata.BoolFormatter" + fmt.Printf("%z", FormatterVal(true)) // correct (the type is responsible for formatting) + fmt.Printf("%d", FormatterVal(true)) // correct (the type is responsible for formatting) + fmt.Printf("%s", nonemptyinterface) // correct (the type is responsible for formatting) + fmt.Printf("%.*s %d %6g", 3, "hi", 23, 'x') // ERROR "Printf format %6g has arg 'x' of wrong type rune" + fmt.Println() // not an error + fmt.Println("%s", "hi") // ERROR "Println call has possible formatting directive %s" + fmt.Println("%v", "hi") // ERROR "Println call has possible formatting directive %v" + fmt.Println("%T", "hi") // ERROR "Println call has possible formatting directive %T" + fmt.Println("0.0%") // correct (trailing % couldn't be a formatting directive) + fmt.Printf("%s", "hi", 3) // ERROR "Printf call needs 1 arg but has 2 args" + _ = fmt.Sprintf("%"+("s"), "hi", 3) // ERROR "Sprintf call needs 1 arg but has 2 args" + fmt.Printf("%s%%%d", "hi", 3) // correct + fmt.Printf("%08s", "woo") // correct + fmt.Printf("% 8s", "woo") // correct + fmt.Printf("%.*d", 3, 3) // correct + fmt.Printf("%.*d x", 3, 3, 3, 3) // ERROR "Printf call needs 2 args but has 4 args" + fmt.Printf("%.*d x", "hi", 3) // ERROR "Printf format %.*d uses non-int \x22hi\x22 as argument of \*" + fmt.Printf("%.*d x", i, 3) // correct + fmt.Printf("%.*d x", s, 3) // ERROR "Printf format %.\*d uses non-int s as argument of \*" + fmt.Printf("%*% x", 0.22) // ERROR "Printf format %\*% uses non-int 0.22 as argument of \*" + fmt.Printf("%q %q", multi()...) // ok + fmt.Printf("%#q", `blah`) // ok + // printf("now is the time", "buddy") // no error "printf call has arguments but no formatting directives" + Printf("now is the time", "buddy") // ERROR "Printf call has arguments but no formatting directives" + Printf("hi") // ok + const format = "%s %s\n" + Printf(format, "hi", "there") + Printf(format, "hi") // ERROR "Printf format %s reads arg #2, but call has 1 arg$" + Printf("%s %d %.3v %q", "str", 4) // ERROR "Printf format %.3v reads arg #3, but call has 2 args" + f := new(ptrStringer) + f.Warn(0, "%s", "hello", 3) // ERROR "Warn call has possible formatting directive %s" + f.Warnf(0, "%s", "hello", 3) // ERROR "Warnf call needs 1 arg but has 2 args" + f.Warnf(0, "%r", "hello") // ERROR "Warnf format %r has unknown verb r" + f.Warnf(0, "%#s", "hello") // ERROR "Warnf format %#s has unrecognized flag #" + f.Warn2(0, "%s", "hello", 3) // ERROR "Warn2 call has possible formatting directive %s" + f.Warnf2(0, "%s", "hello", 3) // ERROR "Warnf2 call needs 1 arg but has 2 args" + f.Warnf2(0, "%r", "hello") // ERROR "Warnf2 format %r has unknown verb r" + f.Warnf2(0, "%#s", "hello") // ERROR "Warnf2 format %#s has unrecognized flag #" + f.Wrap(0, "%s", "hello", 3) // ERROR "Wrap call has possible formatting directive %s" + f.Wrapf(0, "%s", "hello", 3) // ERROR "Wrapf call needs 1 arg but has 2 args" + f.Wrapf(0, "%r", "hello") // ERROR "Wrapf format %r has unknown verb r" + f.Wrapf(0, "%#s", "hello") // ERROR "Wrapf format %#s has unrecognized flag #" + f.Wrap2(0, "%s", "hello", 3) // ERROR "Wrap2 call has possible formatting directive %s" + f.Wrapf2(0, "%s", "hello", 3) // ERROR "Wrapf2 call needs 1 arg but has 2 args" + f.Wrapf2(0, "%r", "hello") // ERROR "Wrapf2 format %r has unknown verb r" + f.Wrapf2(0, "%#s", "hello") // ERROR "Wrapf2 format %#s has unrecognized flag #" + fmt.Printf("%#s", FormatterVal(true)) // correct (the type is responsible for formatting) + Printf("d%", 2) // ERROR "Printf format % is missing verb at end of string" + Printf("%d", percentDV) + Printf("%d", &percentDV) + Printf("%d", notPercentDV) // ERROR "Printf format %d has arg notPercentDV of wrong type testdata.notPercentDStruct" + Printf("%d", ¬PercentDV) // ERROR "Printf format %d has arg ¬PercentDV of wrong type \*testdata.notPercentDStruct" + Printf("%p", ¬PercentDV) // Works regardless: we print it as a pointer. + Printf("%q", &percentDV) // ERROR "Printf format %q has arg &percentDV of wrong type \*testdata.percentDStruct" + Printf("%s", percentSV) + Printf("%s", &percentSV) + // Good argument reorderings. + Printf("%[1]d", 3) + Printf("%[1]*d", 3, 1) + Printf("%[2]*[1]d", 1, 3) + Printf("%[2]*.[1]*[3]d", 2, 3, 4) + fmt.Fprintf(os.Stderr, "%[2]*.[1]*[3]d", 2, 3, 4) // Use Fprintf to make sure we count arguments correctly. + // Bad argument reorderings. + Printf("%[xd", 3) // ERROR "Printf format %\[xd is missing closing \]" + Printf("%[x]d x", 3) // ERROR "Printf format has invalid argument index \[x\]" + Printf("%[3]*s x", "hi", 2) // ERROR "Printf format has invalid argument index \[3\]" + _ = fmt.Sprintf("%[3]d x", 2) // ERROR "Sprintf format has invalid argument index \[3\]" + Printf("%[2]*.[1]*[3]d x", 2, "hi", 4) // ERROR "Printf format %\[2]\*\.\[1\]\*\[3\]d uses non-int \x22hi\x22 as argument of \*" + Printf("%[0]s x", "arg1") // ERROR "Printf format has invalid argument index \[0\]" + Printf("%[0]d x", 1) // ERROR "Printf format has invalid argument index \[0\]" + // Something that satisfies the error interface. + var e error + fmt.Println(e.Error()) // ok + // Something that looks like an error interface but isn't, such as the (*T).Error method + // in the testing package. + var et1 *testing.T + et1.Error() // ok + et1.Error("hi") // ok + et1.Error("%d", 3) // ERROR "Error call has possible formatting directive %d" + var et3 errorTest3 + et3.Error() // ok, not an error method. + var et4 errorTest4 + et4.Error() // ok, not an error method. + var et5 errorTest5 + et5.error() // ok, not an error method. + // Interfaces can be used with any verb. + var iface interface { + ToTheMadness() bool // Method ToTheMadness usually returns false + } + fmt.Printf("%f", iface) // ok: fmt treats interfaces as transparent and iface may well have a float concrete type + // Can't print a function. + Printf("%d", someFunction) // ERROR "Printf format %d arg someFunction is a func value, not called" + Printf("%v", someFunction) // ERROR "Printf format %v arg someFunction is a func value, not called" + Println(someFunction) // ERROR "Println arg someFunction is a func value, not called" + Printf("%p", someFunction) // ok: maybe someone wants to see the pointer + Printf("%T", someFunction) // ok: maybe someone wants to see the type + // Bug: used to recur forever. + Printf("%p %x", recursiveStructV, recursiveStructV.next) + Printf("%p %x", recursiveStruct1V, recursiveStruct1V.next) + Printf("%p %x", recursiveSliceV, recursiveSliceV) + Printf("%p %x", recursiveMapV, recursiveMapV) + // Special handling for Log. + math.Log(3) // OK + var t *testing.T + t.Log("%d", 3) // ERROR "Log call has possible formatting directive %d" + t.Logf("%d", 3) + t.Logf("%d", "hi") // ERROR "Logf format %d has arg \x22hi\x22 of wrong type string" + + Errorf(1, "%d", 3) // OK + Errorf(1, "%d", "hi") // ERROR "Errorf format %d has arg \x22hi\x22 of wrong type string" + + // Multiple string arguments before variadic args + errorf("WARNING", "foobar") // OK + errorf("INFO", "s=%s, n=%d", "foo", 1) // OK + errorf("ERROR", "%d") // no error "errorf format %d reads arg #1, but call has 0 args" + + // Printf from external package + // externalprintf.Printf("%d", 42) // OK + // externalprintf.Printf("foobar") // OK + // level := 123 + // externalprintf.Logf(level, "%d", 42) // OK + // externalprintf.Errorf(level, level, "foo %q bar", "foobar") // OK + // externalprintf.Logf(level, "%d") // no error "Logf format %d reads arg #1, but call has 0 args" + // var formatStr = "%s %s" + // externalprintf.Sprintf(formatStr, "a", "b") // OK + // externalprintf.Logf(level, formatStr, "a", "b") // OK + + // user-defined Println-like functions + ss := &someStruct{} + ss.Log(someFunction, "foo") // OK + ss.Error(someFunction, someFunction) // OK + ss.Println() // OK + ss.Println(1.234, "foo") // OK + ss.Println(1, someFunction) // no error "Println arg someFunction is a func value, not called" + ss.log(someFunction) // OK + ss.log(someFunction, "bar", 1.33) // OK + ss.log(someFunction, someFunction) // no error "log arg someFunction is a func value, not called" + + // indexed arguments + Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4) // OK + Printf("%d %[0]d %d %[2]d x", 1, 2, 3, 4) // ERROR "Printf format has invalid argument index \[0\]" + Printf("%d %[3]d %d %[-2]d x", 1, 2, 3, 4) // ERROR "Printf format has invalid argument index \[-2\]" + Printf("%d %[3]d %d %[2234234234234]d x", 1, 2, 3, 4) // ERROR "Printf format has invalid argument index \[2234234234234\]" + Printf("%d %[3]d %-10d %[2]d x", 1, 2, 3) // ERROR "Printf format %-10d reads arg #4, but call has 3 args" + Printf("%[1][3]d x", 1, 2) // ERROR "Printf format %\[1\]\[ has unknown verb \[" + Printf("%[1]d x", 1, 2) // OK + Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4, 5) // OK + + // wrote Println but meant Fprintln + Printf("%p\n", os.Stdout) // OK + Println(os.Stdout, "hello") // ERROR "Println does not take io.Writer but has first arg os.Stdout" + + Printf(someString(), "hello") // OK + + // Printf wrappers in package log should be detected automatically + logpkg.Fatal("%d", 1) // ERROR "Fatal call has possible formatting directive %d" + logpkg.Fatalf("%d", "x") // ERROR "Fatalf format %d has arg \x22x\x22 of wrong type string" + logpkg.Fatalln("%d", 1) // ERROR "Fatalln call has possible formatting directive %d" + logpkg.Panic("%d", 1) // ERROR "Panic call has possible formatting directive %d" + logpkg.Panicf("%d", "x") // ERROR "Panicf format %d has arg \x22x\x22 of wrong type string" + logpkg.Panicln("%d", 1) // ERROR "Panicln call has possible formatting directive %d" + logpkg.Print("%d", 1) // ERROR "Print call has possible formatting directive %d" + logpkg.Printf("%d", "x") // ERROR "Printf format %d has arg \x22x\x22 of wrong type string" + logpkg.Println("%d", 1) // ERROR "Println call has possible formatting directive %d" + + // Methods too. + var l *logpkg.Logger + l.Fatal("%d", 1) // ERROR "Fatal call has possible formatting directive %d" + l.Fatalf("%d", "x") // ERROR "Fatalf format %d has arg \x22x\x22 of wrong type string" + l.Fatalln("%d", 1) // ERROR "Fatalln call has possible formatting directive %d" + l.Panic("%d", 1) // ERROR "Panic call has possible formatting directive %d" + l.Panicf("%d", "x") // ERROR "Panicf format %d has arg \x22x\x22 of wrong type string" + l.Panicln("%d", 1) // ERROR "Panicln call has possible formatting directive %d" + l.Print("%d", 1) // ERROR "Print call has possible formatting directive %d" + l.Printf("%d", "x") // ERROR "Printf format %d has arg \x22x\x22 of wrong type string" + l.Println("%d", 1) // ERROR "Println call has possible formatting directive %d" + + // Issue 26486 + dbg("", 1) // no error "call has arguments but no formatting directive" +} + +func someString() string { return "X" } + +type someStruct struct{} + +// Log is non-variadic user-define Println-like function. +// Calls to this func must be skipped when checking +// for Println-like arguments. +func (ss *someStruct) Log(f func(), s string) {} + +// Error is variadic user-define Println-like function. +// Calls to this func mustn't be checked for Println-like arguments, +// since variadic arguments type isn't interface{}. +func (ss *someStruct) Error(args ...func()) {} + +// Println is variadic user-defined Println-like function. +// Calls to this func must be checked for Println-like arguments. +func (ss *someStruct) Println(args ...interface{}) {} + +// log is variadic user-defined Println-like function. +// Calls to this func must be checked for Println-like arguments. +func (ss *someStruct) log(f func(), args ...interface{}) {} + +// A function we use as a function value; it has no other purpose. +func someFunction() {} + +// Printf is used by the test so we must declare it. +func Printf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +// Println is used by the test so we must declare it. +func Println(args ...interface{}) { + fmt.Println(args...) +} + +// printf is used by the test so we must declare it. +func printf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +// Errorf is used by the test for a case in which the first parameter +// is not a format string. +func Errorf(i int, format string, args ...interface{}) { + _ = fmt.Errorf(format, args...) +} + +// errorf is used by the test for a case in which the function accepts multiple +// string parameters before variadic arguments +func errorf(level, format string, args ...interface{}) { + _ = fmt.Errorf(format, args...) +} + +// multi is used by the test. +func multi() []interface{} { + panic("don't call - testing only") +} + +type stringer int + +func (stringer) String() string { return "string" } + +type ptrStringer float64 + +var stringerv ptrStringer + +func (*ptrStringer) String() string { + return "string" +} + +func (p *ptrStringer) Warn2(x int, args ...interface{}) string { + return p.Warn(x, args...) +} + +func (p *ptrStringer) Warnf2(x int, format string, args ...interface{}) string { + return p.Warnf(x, format, args...) +} + +func (*ptrStringer) Warn(x int, args ...interface{}) string { + return "warn" +} + +func (*ptrStringer) Warnf(x int, format string, args ...interface{}) string { + return "warnf" +} + +func (p *ptrStringer) Wrap2(x int, args ...interface{}) string { + return p.Wrap(x, args...) +} + +func (p *ptrStringer) Wrapf2(x int, format string, args ...interface{}) string { + return p.Wrapf(x, format, args...) +} + +func (*ptrStringer) Wrap(x int, args ...interface{}) string { + return fmt.Sprint(args...) +} + +func (*ptrStringer) Wrapf(x int, format string, args ...interface{}) string { + return fmt.Sprintf(format, args...) +} + +func (*ptrStringer) BadWrap(x int, args ...interface{}) string { + return fmt.Sprint(args) // ERROR "missing ... in args forwarded to print-like function" +} + +func (*ptrStringer) BadWrapf(x int, format string, args ...interface{}) string { + return fmt.Sprintf(format, args) // ERROR "missing ... in args forwarded to printf-like function" +} + +func (*ptrStringer) WrapfFalsePositive(x int, arg1 string, arg2 ...interface{}) string { + return fmt.Sprintf("%s %v", arg1, arg2) +} + +type embeddedStringer struct { + foo string + ptrStringer + bar int +} + +var embeddedStringerv embeddedStringer + +type notstringer struct { + f float64 +} + +var notstringerv notstringer + +type stringerarray [4]float64 + +func (stringerarray) String() string { + return "string" +} + +var stringerarrayv stringerarray + +type notstringerarray [4]float64 + +var notstringerarrayv notstringerarray + +var nonemptyinterface = interface { + f() +}(nil) + +// A data type we can print with "%d". +type percentDStruct struct { + a int + b []byte + c *float64 +} + +var percentDV percentDStruct + +// A data type we cannot print correctly with "%d". +type notPercentDStruct struct { + a int + b []byte + c bool +} + +var notPercentDV notPercentDStruct + +// A data type we can print with "%s". +type percentSStruct struct { + a string + b []byte + C stringerarray +} + +var percentSV percentSStruct + +type recursiveStringer int + +func (s recursiveStringer) String() string { + _ = fmt.Sprintf("%d", s) + _ = fmt.Sprintf("%#v", s) + _ = fmt.Sprintf("%v", s) // ERROR "Sprintf format %v with arg s causes recursive String method call" + _ = fmt.Sprintf("%v", &s) // ERROR "Sprintf format %v with arg &s causes recursive String method call" + _ = fmt.Sprintf("%T", s) // ok; does not recursively call String + return fmt.Sprintln(s) // ERROR "Sprintln arg s causes recursive call to String method" +} + +type recursivePtrStringer int + +func (p *recursivePtrStringer) String() string { + _ = fmt.Sprintf("%v", *p) + _ = fmt.Sprint(&p) // ok; prints address + return fmt.Sprintln(p) // ERROR "Sprintln arg p causes recursive call to String method" +} + +type BoolFormatter bool + +func (*BoolFormatter) Format(fmt.State, rune) { +} + +// Formatter with value receiver +type FormatterVal bool + +func (FormatterVal) Format(fmt.State, rune) { +} + +type RecursiveSlice []RecursiveSlice + +var recursiveSliceV = &RecursiveSlice{} + +type RecursiveMap map[int]RecursiveMap + +var recursiveMapV = make(RecursiveMap) + +type RecursiveStruct struct { + next *RecursiveStruct +} + +var recursiveStructV = &RecursiveStruct{} + +type RecursiveStruct1 struct { + next *RecursiveStruct2 +} + +type RecursiveStruct2 struct { + next *RecursiveStruct1 +} + +var recursiveStruct1V = &RecursiveStruct1{} + +type unexportedInterface struct { + f interface{} +} + +// Issue 17798: unexported ptrStringer cannot be formatted. +type unexportedStringer struct { + t ptrStringer +} +type unexportedStringerOtherFields struct { + s string + t ptrStringer + S string +} + +// Issue 17798: unexported error cannot be formatted. +type unexportedError struct { + e error +} +type unexportedErrorOtherFields struct { + s string + e error + S string +} + +type errorer struct{} + +func (e errorer) Error() string { return "errorer" } + +type unexportedCustomError struct { + e errorer +} + +type errorInterface interface { + error + ExtraMethod() +} + +type unexportedErrorInterface struct { + e errorInterface +} + +func UnexportedStringerOrError() { + fmt.Printf("%s", unexportedInterface{"foo"}) // ok; prints {foo} + fmt.Printf("%s", unexportedInterface{3}) // ok; we can't see the problem + + us := unexportedStringer{} + fmt.Printf("%s", us) // ERROR "Printf format %s has arg us of wrong type testdata.unexportedStringer" + fmt.Printf("%s", &us) // ERROR "Printf format %s has arg &us of wrong type [*]testdata.unexportedStringer" + + usf := unexportedStringerOtherFields{ + s: "foo", + S: "bar", + } + fmt.Printf("%s", usf) // ERROR "Printf format %s has arg usf of wrong type testdata.unexportedStringerOtherFields" + fmt.Printf("%s", &usf) // ERROR "Printf format %s has arg &usf of wrong type [*]testdata.unexportedStringerOtherFields" + + ue := unexportedError{ + e: &errorer{}, + } + fmt.Printf("%s", ue) // ERROR "Printf format %s has arg ue of wrong type testdata.unexportedError" + fmt.Printf("%s", &ue) // ERROR "Printf format %s has arg &ue of wrong type [*]testdata.unexportedError" + + uef := unexportedErrorOtherFields{ + s: "foo", + e: &errorer{}, + S: "bar", + } + fmt.Printf("%s", uef) // ERROR "Printf format %s has arg uef of wrong type testdata.unexportedErrorOtherFields" + fmt.Printf("%s", &uef) // ERROR "Printf format %s has arg &uef of wrong type [*]testdata.unexportedErrorOtherFields" + + uce := unexportedCustomError{ + e: errorer{}, + } + fmt.Printf("%s", uce) // ERROR "Printf format %s has arg uce of wrong type testdata.unexportedCustomError" + + uei := unexportedErrorInterface{} + fmt.Printf("%s", uei) // ERROR "Printf format %s has arg uei of wrong type testdata.unexportedErrorInterface" + fmt.Println("foo\n", "bar") // not an error + + fmt.Println("foo\n") // ERROR "Println arg list ends with redundant newline" + fmt.Println("foo\\n") // not an error + fmt.Println(`foo\n`) // not an error + + intSlice := []int{3, 4} + fmt.Printf("%s", intSlice) // ERROR "Printf format %s has arg intSlice of wrong type \[\]int" + nonStringerArray := [1]unexportedStringer{{}} + fmt.Printf("%s", nonStringerArray) // ERROR "Printf format %s has arg nonStringerArray of wrong type \[1\]testdata.unexportedStringer" + fmt.Printf("%s", []stringer{3, 4}) // not an error + fmt.Printf("%s", [2]stringer{3, 4}) // not an error +} + +// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11. +// See issues 23598 and 23605. +func DisableErrorForFlag0() { + fmt.Printf("%0t", true) +} + +// Issue 26486. +func dbg(format string, args ...interface{}) { + if format == "" { + format = "%v" + } + fmt.Printf(format, args...) +}