]> Cypherpunks repositories - gostls13.git/commitdiff
icmd/vet: improved checking for variadic Println-like functions
authorAliaksandr Valialkin <valyala@gmail.com>
Tue, 5 Apr 2016 15:42:07 +0000 (18:42 +0300)
committerRob Pike <r@golang.org>
Wed, 6 Apr 2016 18:11:36 +0000 (18:11 +0000)
- Automatically determine the first argument to check.
- Skip checking matching non-variadic functions.
- Skip checking matching functions accepting non-interface{}
  variadic arguments.
- Removed fragile 'magic' code for special cases such as math.Log
  and error interface.

Fixes #15067
Fixes #15099

Change-Id: Ib313557f18b12b36daa493f4b02c598b9503b55b
Reviewed-on: https://go-review.googlesource.com/21513
Run-TryBot: Rob Pike <r@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
src/cmd/vet/doc.go
src/cmd/vet/print.go
src/cmd/vet/testdata/print.go
src/cmd/vet/types.go

index d295fb434590648d8e697e3b95f53f6f10780c3b..2b5e8fcb597dc9bbefd9eb426b4b07190dcf8e25 100644 (file)
@@ -188,13 +188,8 @@ These flags configure the behavior of vet:
        -v
                Verbose mode
        -printfuncs
-               A comma-separated list of print-like functions to supplement the
-               standard list.  Each entry is in the form Name:N where N is the
-               zero-based argument position of the first argument involved in the
-               print: either the format or the first print argument for non-formatted
-               prints.  For example, if you have Warn and Warnf functions that
-               take an io.Writer as their first argument, like Fprintf,
-                       -printfuncs=Warn:1,Warnf:1
+               A comma-separated list of print-like function names
+               to supplement the standard list.
                For more information, see the discussion of the -printf flag.
        -shadowstrict
                Whether to be strict about shadowing; can be noisy.
index 4e3252f2fbccda95dd1b0b2070539d704c271bdf..07499e6ae6bc6ab6f30ffd528e95ec8ca1dcc582 100644 (file)
@@ -35,20 +35,18 @@ func initPrintFlags() {
                if len(name) == 0 {
                        flag.Usage()
                }
-               skip := 0
+
+               // Backwards compatibility: skip optional first argument
+               // index after the colon.
                if colon := strings.LastIndex(name, ":"); colon > 0 {
-                       var err error
-                       skip, err = strconv.Atoi(name[colon+1:])
-                       if err != nil {
-                               errorf(`illegal format for "Func:N" argument %q; %s`, name, err)
-                       }
                        name = name[:colon]
                }
+
                name = strings.ToLower(name)
                if name[len(name)-1] == 'f' {
                        isFormattedPrint[name] = true
                } else {
-                       printList[name] = skip
+                       isPrint[name] = true
                }
        }
 }
@@ -65,17 +63,20 @@ var isFormattedPrint = map[string]bool{
        "sprintf": true,
 }
 
-// printList records the unformatted-print functions. The value is the location
-// of the first parameter to be printed. Names are lower-cased so the lookup is
-// case insensitive.
-var printList = map[string]int{
-       "error":  0,
-       "fatal":  0,
-       "fprint": 1, "fprintln": 1,
-       "log":   0,
-       "panic": 0, "panicln": 0,
-       "print": 0, "println": 0,
-       "sprint": 0, "sprintln": 0,
+// isPrint records the unformatted-print functions. Names are lower-cased
+// so the lookup is case insensitive.
+var isPrint = map[string]bool{
+       "error":    true,
+       "fatal":    true,
+       "fprint":   true,
+       "fprintln": true,
+       "log":      true,
+       "panic":    true,
+       "panicln":  true,
+       "print":    true,
+       "println":  true,
+       "sprint":   true,
+       "sprintln": true,
 }
 
 // formatString returns the format string argument and its index within
@@ -171,8 +172,8 @@ func checkFmtPrintfCall(f *File, node ast.Node) {
                f.checkPrintf(call, Name)
                return
        }
-       if skip, ok := printList[name]; ok {
-               f.checkPrint(call, Name, skip)
+       if _, ok := isPrint[name]; ok {
+               f.checkPrint(call, Name)
                return
        }
 }
@@ -583,25 +584,36 @@ func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, isStar bool, s
 }
 
 // checkPrint checks a call to an unformatted print routine such as Println.
-// call.Args[firstArg] is the first argument to be printed.
-func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) {
-       isLn := strings.HasSuffix(name, "ln")
-       isF := strings.HasPrefix(name, "F")
-       args := call.Args
-       if name == "Log" && len(args) > 0 {
-               // Special case: Don't complain about math.Log or cmplx.Log.
-               // Not strictly necessary because the only complaint likely is for Log("%d")
-               // but it feels wrong to check that math.Log is a good print function.
-               if sel, ok := args[0].(*ast.SelectorExpr); ok {
-                       if x, ok := sel.X.(*ast.Ident); ok {
-                               if x.Name == "math" || x.Name == "cmplx" {
-                                       return
-                               }
+func (f *File) checkPrint(call *ast.CallExpr, name string) {
+       firstArg := 0
+       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
+                       }
+                       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:]
+
        // check for Println(os.Stderr, ...)
-       if firstArg == 0 && !isF && len(args) > 0 {
+       if firstArg == 0 {
                if sel, ok := args[0].(*ast.SelectorExpr); ok {
                        if x, ok := sel.X.(*ast.Ident); ok {
                                if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
@@ -610,31 +622,15 @@ func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) {
                        }
                }
        }
-       if len(args) <= firstArg {
-               // If we have a call to a method called Error that satisfies the Error interface,
-               // then it's ok. Otherwise it's something like (*T).Error from the testing package
-               // and we need to check it.
-               if name == "Error" && f.isErrorMethodCall(call) {
-                       return
-               }
-               // If it's an Error call now, it's probably for printing errors.
-               if !isLn {
-                       // Check the signature to be sure: there are niladic functions called "error".
-                       if firstArg != 0 || f.numArgsInSignature(call) != firstArg {
-                               f.Badf(call.Pos(), "no args in %s call", name)
-                       }
-               }
-               return
-       }
-       arg := args[firstArg]
+       arg := args[0]
        if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
                if strings.Contains(lit.Value, "%") {
                        f.Badf(call.Pos(), "possible formatting directive in %s call", name)
                }
        }
-       if isLn {
+       if strings.HasSuffix(name, "ln") {
                // The last item, if a string, should not have a newline.
-               arg = args[len(call.Args)-1]
+               arg = args[len(args)-1]
                if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
                        if strings.HasSuffix(lit.Value, `\n"`) {
                                f.Badf(call.Pos(), "%s call ends with newline", name)
index 5c7ff35c90f2f526e9f14f267e4316fc1696e1de..261ee788c7b0d025e5ea6d8c312a555f4bc63c62 100644 (file)
@@ -185,11 +185,11 @@ func PrintfTests() {
        // Something that looks like an error interface but isn't, such as the (*T).Error method
        // in the testing package.
        var et1 errorTest1
-       fmt.Println(et1.Error())        // ERROR "no args in Error call"
+       fmt.Println(et1.Error())        // ok
        fmt.Println(et1.Error("hi"))    // ok
        fmt.Println(et1.Error("%d", 3)) // ERROR "possible formatting directive in Error call"
        var et2 errorTest2
-       et2.Error()        // ERROR "no args in Error call"
+       et2.Error()        // ok
        et2.Error("hi")    // ok, not an error method.
        et2.Error("%d", 3) // ERROR "possible formatting directive in Error call"
        var et3 errorTest3
@@ -231,11 +231,41 @@ func PrintfTests() {
        externalprintf.Logf(level, "%d", 42)                        // OK
        externalprintf.Errorf(level, level, "foo %q bar", "foobar") // OK
        externalprintf.Logf(level, "%d")                            // ERROR "format reads arg 1, have only 0 args"
+
+       // 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)          // ERROR "arg someFunction in Println call is a function value, not a function call"
+       ss.log(someFunction)                 // OK
+       ss.log(someFunction, "bar", 1.33)    // OK
+       ss.log(someFunction, someFunction)   // ERROR "arg someFunction in log call is a function value, not a function call"
 }
 
+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() {
-}
+func someFunction() {}
 
 // Printf is used by the test so we must declare it.
 func Printf(format string, args ...interface{}) {
index 692bae61927e282fe478cbc1998273ca7a697709..4358955d93ec83d8be0c990e778526251ccc8ddd 100644 (file)
@@ -292,72 +292,6 @@ func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Ex
        return true
 }
 
-// numArgsInSignature tells how many formal arguments the function type
-// being called has.
-func (f *File) numArgsInSignature(call *ast.CallExpr) int {
-       // Check the type of the function or method declaration
-       typ := f.pkg.types[call.Fun].Type
-       if typ == nil {
-               return 0
-       }
-       // The type must be a signature, but be sure for safety.
-       sig, ok := typ.(*types.Signature)
-       if !ok {
-               return 0
-       }
-       return sig.Params().Len()
-}
-
-// isErrorMethodCall reports whether the call is of a method with signature
-//     func Error() string
-// where "string" is the universe's string type. We know the method is called "Error".
-func (f *File) isErrorMethodCall(call *ast.CallExpr) bool {
-       typ := f.pkg.types[call].Type
-       if typ != nil {
-               // We know it's called "Error", so just check the function signature
-               // (stringerType has exactly one method, String).
-               if stringerType != nil && stringerType.NumMethods() == 1 {
-                       return types.Identical(f.pkg.types[call.Fun].Type, stringerType.Method(0).Type())
-               }
-       }
-       // Without types, we can still check by hand.
-       // Is it a selector expression? Otherwise it's a function call, not a method call.
-       sel, ok := call.Fun.(*ast.SelectorExpr)
-       if !ok {
-               return false
-       }
-       // The package is type-checked, so if there are no arguments, we're done.
-       if len(call.Args) > 0 {
-               return false
-       }
-       // Check the type of the method declaration
-       typ = f.pkg.types[sel].Type
-       if typ == nil {
-               return false
-       }
-       // The type must be a signature, but be sure for safety.
-       sig, ok := typ.(*types.Signature)
-       if !ok {
-               return false
-       }
-       // There must be a receiver for it to be a method call. Otherwise it is
-       // a function, not something that satisfies the error interface.
-       if sig.Recv() == nil {
-               return false
-       }
-       // There must be no arguments. Already verified by type checking, but be thorough.
-       if sig.Params().Len() > 0 {
-               return false
-       }
-       // Finally the real questions.
-       // There must be one result.
-       if sig.Results().Len() != 1 {
-               return false
-       }
-       // It must have return type "string" from the universe.
-       return sig.Results().At(0).Type() == types.Typ[types.String]
-}
-
 // hasMethod reports whether the type contains a method with the given name.
 // It is part of the workaround for Formatters and should be deleted when
 // that workaround is no longer necessary.