PageRenderTime 40ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/cmd/govet/govet.go

https://bitbucket.org/taruti/go.plan9
Go | 325 lines | 244 code | 32 blank | 49 comment | 59 complexity | 95f7c20d88affb74c269b31ad66a21a1 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. // Copyright 2010 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Govet is a simple checker for static errors in Go source code.
  5. // See doc.go for more information.
  6. package main
  7. import (
  8. "bytes"
  9. "flag"
  10. "fmt"
  11. "io"
  12. "go/ast"
  13. "go/parser"
  14. "go/token"
  15. "os"
  16. "path"
  17. "strconv"
  18. "strings"
  19. )
  20. var verbose = flag.Bool("v", false, "verbose")
  21. var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")
  22. var exitCode = 0
  23. // setExit sets the value for os.Exit when it is called, later. It
  24. // remembers the highest value.
  25. func setExit(err int) {
  26. if err > exitCode {
  27. exitCode = err
  28. }
  29. }
  30. // Usage is a replacement usage function for the flags package.
  31. func Usage() {
  32. fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
  33. flag.PrintDefaults()
  34. os.Exit(2)
  35. }
  36. // File is a wrapper for the state of a file used in the parser.
  37. // The parse tree walkers are all methods of this type.
  38. type File struct {
  39. file *token.File
  40. }
  41. func main() {
  42. flag.Usage = Usage
  43. flag.Parse()
  44. if *printfuncs != "" {
  45. for _, name := range strings.Split(*printfuncs, ",", -1) {
  46. if len(name) == 0 {
  47. flag.Usage()
  48. }
  49. skip := 0
  50. if colon := strings.LastIndex(name, ":"); colon > 0 {
  51. var err os.Error
  52. skip, err = strconv.Atoi(name[colon+1:])
  53. if err != nil {
  54. error(`illegal format for "Func:N" argument %q; %s`, name, err)
  55. }
  56. name = name[:colon]
  57. }
  58. if name[len(name)-1] == 'f' {
  59. printfList[name] = skip
  60. } else {
  61. printList[name] = skip
  62. }
  63. }
  64. }
  65. if flag.NArg() == 0 {
  66. doFile("stdin", os.Stdin)
  67. } else {
  68. for _, name := range flag.Args() {
  69. // Is it a directory?
  70. if fi, err := os.Stat(name); err == nil && fi.IsDirectory() {
  71. walkDir(name)
  72. } else {
  73. doFile(name, nil)
  74. }
  75. }
  76. }
  77. os.Exit(exitCode)
  78. }
  79. // doFile analyzes one file. If the reader is nil, the source code is read from the
  80. // named file.
  81. func doFile(name string, reader io.Reader) {
  82. fs := token.NewFileSet()
  83. parsedFile, err := parser.ParseFile(fs, name, reader, 0)
  84. if err != nil {
  85. error("%s: %s", name, err)
  86. return
  87. }
  88. file := &File{fs.File(parsedFile.Pos())}
  89. file.checkFile(name, parsedFile)
  90. }
  91. // Visitor for path.Walk - trivial. Just calls doFile on each file.
  92. // TODO: if govet becomes richer, might want to process
  93. // a directory (package) at a time.
  94. type V struct{}
  95. func (v V) VisitDir(path string, f *os.FileInfo) bool {
  96. return true
  97. }
  98. func (v V) VisitFile(path string, f *os.FileInfo) {
  99. if strings.HasSuffix(path, ".go") {
  100. doFile(path, nil)
  101. }
  102. }
  103. // walkDir recursively walks the tree looking for .go files.
  104. func walkDir(root string) {
  105. errors := make(chan os.Error)
  106. done := make(chan bool)
  107. go func() {
  108. for e := range errors {
  109. error("walk error: %s", e)
  110. }
  111. done <- true
  112. }()
  113. path.Walk(root, V{}, errors)
  114. close(errors)
  115. <-done
  116. }
  117. // error formats the error to standard error, adding program
  118. // identification and a newline
  119. func error(format string, args ...interface{}) {
  120. fmt.Fprintf(os.Stderr, "govet: "+format+"\n", args...)
  121. setExit(2)
  122. }
  123. // Println is fmt.Println guarded by -v.
  124. func Println(args ...interface{}) {
  125. if !*verbose {
  126. return
  127. }
  128. fmt.Println(args...)
  129. }
  130. // Printf is fmt.Printf guarded by -v.
  131. func Printf(format string, args ...interface{}) {
  132. if !*verbose {
  133. return
  134. }
  135. fmt.Printf(format+"\n", args...)
  136. }
  137. // Bad reports an error and sets the exit code..
  138. func (f *File) Bad(pos token.Pos, args ...interface{}) {
  139. f.Warn(pos, args...)
  140. setExit(1)
  141. }
  142. // Badf reports a formatted error and sets the exit code.
  143. func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
  144. f.Warnf(pos, format, args...)
  145. setExit(1)
  146. }
  147. // Warn reports an error but does not set the exit code.
  148. func (f *File) Warn(pos token.Pos, args ...interface{}) {
  149. loc := f.file.Position(pos).String() + ": "
  150. fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...))
  151. }
  152. // Warnf reports a formatted error but does not set the exit code.
  153. func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
  154. loc := f.file.Position(pos).String() + ": "
  155. fmt.Fprintf(os.Stderr, loc+format+"\n", args...)
  156. }
  157. // checkFile checks all the top-level declarations in a file.
  158. func (f *File) checkFile(name string, file *ast.File) {
  159. Println("Checking file", name)
  160. ast.Walk(f, file)
  161. }
  162. // Visit implements the ast.Visitor interface.
  163. func (f *File) Visit(node ast.Node) ast.Visitor {
  164. // TODO: could return nil for nodes that cannot contain a CallExpr -
  165. // will shortcut traversal. Worthwhile?
  166. switch n := node.(type) {
  167. case *ast.CallExpr:
  168. f.checkCallExpr(n)
  169. }
  170. return f
  171. }
  172. // checkCallExpr checks a call expression.
  173. func (f *File) checkCallExpr(call *ast.CallExpr) {
  174. switch x := call.Fun.(type) {
  175. case *ast.Ident:
  176. f.checkCall(call, x.Name)
  177. case *ast.SelectorExpr:
  178. f.checkCall(call, x.Sel.Name)
  179. }
  180. }
  181. // printfList records the formatted-print functions. The value is the location
  182. // of the format parameter.
  183. var printfList = map[string]int{
  184. "Errorf": 0,
  185. "Fatalf": 0,
  186. "Fprintf": 1,
  187. "Printf": 0,
  188. "Sprintf": 0,
  189. }
  190. // printList records the unformatted-print functions. The value is the location
  191. // of the first parameter to be printed.
  192. var printList = map[string]int{
  193. "Error": 0,
  194. "Fatal": 0,
  195. "Fprint": 1, "Fprintln": 1,
  196. "Print": 0, "Println": 0,
  197. "Sprint": 0, "Sprintln": 0,
  198. }
  199. // checkCall triggers the print-specific checks if the call invokes a print function.
  200. func (f *File) checkCall(call *ast.CallExpr, name string) {
  201. if skip, ok := printfList[name]; ok {
  202. f.checkPrintf(call, name, skip)
  203. return
  204. }
  205. if skip, ok := printList[name]; ok {
  206. f.checkPrint(call, name, skip)
  207. return
  208. }
  209. }
  210. // checkPrintf checks a call to a formatted print routine such as Printf.
  211. // The skip argument records how many arguments to ignore; that is,
  212. // call.Args[skip] is (well, should be) the format argument.
  213. func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) {
  214. if len(call.Args) <= skip {
  215. return
  216. }
  217. // Common case: literal is first argument.
  218. arg := call.Args[skip]
  219. lit, ok := arg.(*ast.BasicLit)
  220. if !ok {
  221. // Too hard to check.
  222. if *verbose {
  223. f.Warn(call.Pos(), "can't check args for call to", name)
  224. }
  225. return
  226. }
  227. if lit.Kind == token.STRING {
  228. if bytes.IndexByte(lit.Value, '%') < 0 {
  229. if len(call.Args) > skip+1 {
  230. f.Badf(call.Pos(), "no formatting directive in %s call", name)
  231. }
  232. return
  233. }
  234. }
  235. // Hard part: check formats against args.
  236. // Trivial but useful test: count.
  237. numPercent := 0
  238. for i := 0; i < len(lit.Value); i++ {
  239. if lit.Value[i] == '%' {
  240. if i+1 < len(lit.Value) && lit.Value[i+1] == '%' {
  241. // %% doesn't count.
  242. i++
  243. } else {
  244. numPercent++
  245. }
  246. }
  247. }
  248. expect := len(call.Args) - (skip + 1)
  249. if numPercent != expect {
  250. f.Badf(call.Pos(), "wrong number of formatting directives in %s call: %d percent(s) for %d args", name, numPercent, expect)
  251. }
  252. }
  253. var terminalNewline = []byte(`\n"`) // \n at end of interpreted string
  254. // checkPrint checks a call to an unformatted print routine such as Println.
  255. // The skip argument records how many arguments to ignore; that is,
  256. // call.Args[skip] is the first argument to be printed.
  257. func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
  258. isLn := strings.HasSuffix(name, "ln")
  259. args := call.Args
  260. if len(args) <= skip {
  261. if *verbose && !isLn {
  262. f.Badf(call.Pos(), "no args in %s call", name)
  263. }
  264. return
  265. }
  266. arg := args[skip]
  267. if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
  268. if bytes.IndexByte(lit.Value, '%') >= 0 {
  269. f.Badf(call.Pos(), "possible formatting directive in %s call", name)
  270. }
  271. }
  272. if isLn {
  273. // The last item, if a string, should not have a newline.
  274. arg = args[len(call.Args)-1]
  275. if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
  276. if bytes.HasSuffix(lit.Value, terminalNewline) {
  277. f.Badf(call.Pos(), "%s call ends with newline", name)
  278. }
  279. }
  280. }
  281. }
  282. // This function never executes, but it serves as a simple test for the program.
  283. // Test with govet -printfuncs="Bad:1,Badf:1,Warn:1,Warnf:1" govet.go
  284. func BadFunctionUsedInTests() {
  285. fmt.Println() // niladic call
  286. fmt.Println("%s", "hi") // % in call to Println
  287. fmt.Printf("%s", "hi", 3) // wrong # percents
  288. fmt.Printf("%s%%%d", "hi", 3) // right # percents
  289. Printf("now is the time", "buddy") // no %s
  290. f := new(File)
  291. f.Warn(0, "%s", "hello", 3) // % in call to added function
  292. f.Warnf(0, "%s", "hello", 3) // wrong # %s in call to added function
  293. }