mirror of
https://github.com/golang/go
synced 2024-11-22 00:14:42 -07:00
govet: a new static checker for Go programs.
At the moment, and for the forseeable future, it only checks arguments to print calls. R=rsc, gri, niemeyer, iant2, rog, lstoakes, jacek.masiulaniec, cw CC=golang-dev https://golang.org/cl/3522041
This commit is contained in:
parent
b7c73110b1
commit
8e609cddef
11
src/cmd/govet/Makefile
Normal file
11
src/cmd/govet/Makefile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
include ../../Make.inc
|
||||||
|
|
||||||
|
TARG=govet
|
||||||
|
GOFILES=\
|
||||||
|
govet.go\
|
||||||
|
|
||||||
|
include ../../Make.cmd
|
37
src/cmd/govet/doc.go
Normal file
37
src/cmd/govet/doc.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Govet does simple checking of Go source code.
|
||||||
|
|
||||||
|
It checks for simple errors in calls to functions named
|
||||||
|
Print Printf Println
|
||||||
|
Fprint Fprintf Fprintln
|
||||||
|
Sprint Sprintf Sprintln
|
||||||
|
Error Errorf
|
||||||
|
Fatal Fatalf
|
||||||
|
If the function name ends with an 'f', the function is assumed to take
|
||||||
|
a format descriptor string in the manner of fmt.Printf. If not, govet
|
||||||
|
complains about arguments that look like format descriptor strings.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
govet [flag] [file.go ...]
|
||||||
|
|
||||||
|
The flags are:
|
||||||
|
-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
|
||||||
|
|
||||||
|
*/
|
||||||
|
package documentation
|
281
src/cmd/govet/govet.go
Normal file
281
src/cmd/govet/govet.go
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Govet is a simple checker for static errors in Go source code.
|
||||||
|
// See doc.go for more information.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var verbose = flag.Bool("v", false, "verbose")
|
||||||
|
var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")
|
||||||
|
var exitCode = 0
|
||||||
|
|
||||||
|
// Usage is a replacement usage function for the flags package.
|
||||||
|
func Usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is a wrapper for the state of a file used in the parser.
|
||||||
|
// The parse tree walkers are all methods of this type.
|
||||||
|
type File struct {
|
||||||
|
file *token.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = Usage
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *printfuncs != "" {
|
||||||
|
for _, name := range strings.Split(*printfuncs, ",", -1) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
skip := 0
|
||||||
|
if colon := strings.LastIndex(name, ":"); colon > 0 {
|
||||||
|
var err os.Error
|
||||||
|
skip, err = strconv.Atoi(name[colon+1:])
|
||||||
|
if err != nil {
|
||||||
|
die(`illegal format for "Func:N" argument %q; %s`, name, err)
|
||||||
|
}
|
||||||
|
name = name[:colon]
|
||||||
|
}
|
||||||
|
if name[len(name)-1] == 'f' {
|
||||||
|
printfList[name] = skip
|
||||||
|
} else {
|
||||||
|
printList[name] = skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.NArg() == 0 {
|
||||||
|
doFile("stdin", os.Stdin)
|
||||||
|
} else {
|
||||||
|
for _, arg := range flag.Args() {
|
||||||
|
doFile(arg, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doFile analyzes one file. If the reader is nil, the source code is read from the
|
||||||
|
// named file.
|
||||||
|
func doFile(name string, reader io.Reader) {
|
||||||
|
// TODO: process directories?
|
||||||
|
fs := token.NewFileSet()
|
||||||
|
parsedFile, err := parser.ParseFile(fs, name, reader, 0)
|
||||||
|
if err != nil {
|
||||||
|
die("%s: %s", name, err)
|
||||||
|
}
|
||||||
|
file := &File{fs.File(parsedFile.Pos())}
|
||||||
|
file.checkFile(name, parsedFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// die formats the error to standard error, adding program identification
|
||||||
|
// and a newline, and exits the program.
|
||||||
|
func die(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is fmt.Println guarded by -v.
|
||||||
|
func Println(args ...interface{}) {
|
||||||
|
if !*verbose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is fmt.Printf guarded by -v.
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
if !*verbose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf(format+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad reports an error and sets the exit code..
|
||||||
|
func (f *File) Bad(pos token.Pos, args ...interface{}) {
|
||||||
|
f.Warn(pos, args...)
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badf reports a formatted error and sets the exit code.
|
||||||
|
func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
|
||||||
|
f.Warnf(pos, format, args...)
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn reports an error but does not set the exit code.
|
||||||
|
func (f *File) Warn(pos token.Pos, args ...interface{}) {
|
||||||
|
loc := f.file.Position(pos).String() + ": "
|
||||||
|
fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf reports a formatted error but does not set the exit code.
|
||||||
|
func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
|
||||||
|
loc := f.file.Position(pos).String() + ": "
|
||||||
|
fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkFile checks all the top-level declarations in a file.
|
||||||
|
func (f *File) checkFile(name string, file *ast.File) {
|
||||||
|
Println("Checking", name)
|
||||||
|
ast.Walk(f, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit implements the ast.Visitor interface.
|
||||||
|
func (f *File) Visit(node interface{}) ast.Visitor {
|
||||||
|
// TODO: could return nil for nodes that cannot contain a CallExpr -
|
||||||
|
// will shortcut traversal. Worthwhile?
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
f.checkCallExpr(n)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// checkCallExpr checks a call expression.
|
||||||
|
func (f *File) checkCallExpr(call *ast.CallExpr) {
|
||||||
|
switch x := call.Fun.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
f.checkCall(call, x.Name)
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
f.checkCall(call, x.Sel.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printfList records the formatted-print functions. The value is the location
|
||||||
|
// of the format parameter.
|
||||||
|
var printfList = map[string]int{
|
||||||
|
"Errorf": 0,
|
||||||
|
"Fatalf": 0,
|
||||||
|
"Fprintf": 1,
|
||||||
|
"Printf": 0,
|
||||||
|
"Sprintf": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// printList records the unformatted-print functions. The value is the location
|
||||||
|
// of the first parameter to be printed.
|
||||||
|
var printList = map[string]int{
|
||||||
|
"Error": 0,
|
||||||
|
"Fatal": 0,
|
||||||
|
"Fprint": 1, "Fprintln": 1,
|
||||||
|
"Print": 0, "Println": 0,
|
||||||
|
"Sprint": 0, "Sprintln": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkCall triggers the print-specific checks if the call invokes a print function.
|
||||||
|
func (f *File) checkCall(call *ast.CallExpr, name string) {
|
||||||
|
if skip, ok := printfList[name]; ok {
|
||||||
|
f.checkPrintf(call, name, skip)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if skip, ok := printList[name]; ok {
|
||||||
|
f.checkPrint(call, name, skip)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPrintf checks a call to a formatted print routine such as Printf.
|
||||||
|
// The skip argument records how many arguments to ignore; that is,
|
||||||
|
// call.Args[skip] is (well, should be) the format argument.
|
||||||
|
func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
|
||||||
|
if len(call.Args) <= skip {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Common case: literal is first argument.
|
||||||
|
arg := call.Args[skip]
|
||||||
|
lit, ok := arg.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
// Too hard to check.
|
||||||
|
if *verbose {
|
||||||
|
f.Warn(call.Pos(), "can't check args for call to", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lit.Kind == token.STRING {
|
||||||
|
if bytes.IndexByte(lit.Value, '%') < 0 {
|
||||||
|
if len(call.Args) > skip+1 {
|
||||||
|
f.Badf(call.Pos(), "no formatting directive in %s call", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Hard part: check formats against args.
|
||||||
|
// Trivial but useful test: count.
|
||||||
|
numPercent := 0
|
||||||
|
for i := 0; i < len(lit.Value); i++ {
|
||||||
|
if lit.Value[i] == '%' {
|
||||||
|
if i+1 < len(lit.Value) && lit.Value[i+1] == '%' {
|
||||||
|
// %% doesn't count.
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
numPercent++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect := len(call.Args) - (skip + 1)
|
||||||
|
if numPercent != expect {
|
||||||
|
f.Badf(call.Pos(), "wrong number of formatting directives in %s call: %d percent(s) for %d args", name, numPercent, expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var terminalNewline = []byte(`\n"`) // \n at end of interpreted string
|
||||||
|
|
||||||
|
// checkPrint checks a call to an unformatted print routine such as Println.
|
||||||
|
// The skip argument records how many arguments to ignore; that is,
|
||||||
|
// call.Args[skip] is the first argument to be printed.
|
||||||
|
func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
|
||||||
|
isLn := strings.HasSuffix(name, "ln")
|
||||||
|
args := call.Args
|
||||||
|
if len(args) <= skip {
|
||||||
|
if *verbose && !isLn {
|
||||||
|
f.Badf(call.Pos(), "no args in %s call", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
arg := args[skip]
|
||||||
|
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||||
|
if bytes.IndexByte(lit.Value, '%') >= 0 {
|
||||||
|
f.Badf(call.Pos(), "possible formatting directive in %s call", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isLn {
|
||||||
|
// The last item, if a string, should not have a newline.
|
||||||
|
arg = args[len(call.Args)-1]
|
||||||
|
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||||
|
if bytes.HasSuffix(lit.Value, terminalNewline) {
|
||||||
|
f.Badf(call.Pos(), "%s call ends with newline", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function never executes, but it serves as a simple test for the program.
|
||||||
|
// Test with govet --funcs="Bad:1,Badf:1,Warn:1,Warnf:1" govet.go
|
||||||
|
func BadFunctionUsedInTests() {
|
||||||
|
fmt.Println() // niladic call
|
||||||
|
fmt.Println("%s", "hi") // % in call to Println
|
||||||
|
fmt.Printf("%s", "hi", 3) // wrong # percents
|
||||||
|
fmt.Printf("%s%%%d", "hi", 3) // right # percents
|
||||||
|
Printf("now is the time", "buddy") // no %s
|
||||||
|
f := new(File)
|
||||||
|
f.Warn(0, "%s", "hello", 3) // % in call to added function
|
||||||
|
f.Warnf(0, "%s", "hello", 3) // wrong # %s in call to added function
|
||||||
|
}
|
@ -137,6 +137,7 @@ DIRS=\
|
|||||||
../cmd/godoc\
|
../cmd/godoc\
|
||||||
../cmd/gofmt\
|
../cmd/gofmt\
|
||||||
../cmd/goinstall\
|
../cmd/goinstall\
|
||||||
|
../cmd/govet\
|
||||||
../cmd/goyacc\
|
../cmd/goyacc\
|
||||||
../cmd/hgpatch\
|
../cmd/hgpatch\
|
||||||
|
|
||||||
@ -163,6 +164,7 @@ NOTEST=\
|
|||||||
../cmd/godoc\
|
../cmd/godoc\
|
||||||
../cmd/gofmt\
|
../cmd/gofmt\
|
||||||
../cmd/goinstall\
|
../cmd/goinstall\
|
||||||
|
../cmd/govet\
|
||||||
../cmd/goyacc\
|
../cmd/goyacc\
|
||||||
../cmd/hgpatch\
|
../cmd/hgpatch\
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user