github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/src/cmd/compile/fmt_test.go (about) 1 // Copyright 2016 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 5 // This file implements TestFormats; a test that verifies 6 // format strings in the compiler (this directory and all 7 // subdirectories, recursively). 8 // 9 // TestFormats finds potential (Printf, etc.) format strings. 10 // If they are used in a call, the format verbs are verified 11 // based on the matching argument type against a precomputed 12 // table of valid formats. The knownFormats table can be used 13 // to automatically rewrite format strings with the -u flag. 14 // 15 // A new knownFormats table based on the found formats is printed 16 // when the test is run in verbose mode (-v flag). The table 17 // needs to be updated whenever a new (type, format) combination 18 // is found and the format verb is not 'v' or 'T' (as in "%v" or 19 // "%T"). 20 // 21 // Run as: go test -run Formats [-u][-v] 22 // 23 // Known bugs: 24 // - indexed format strings ("%[2]s", etc.) are not supported 25 // (the test will fail) 26 // - format strings that are not simple string literals cannot 27 // be updated automatically 28 // (the test will fail with respective warnings) 29 // - format strings in _test packages outside the current 30 // package are not processed 31 // (the test will report those files) 32 // 33 package main_test 34 35 import ( 36 "bytes" 37 "flag" 38 "fmt" 39 "go/ast" 40 "go/build" 41 "go/constant" 42 "go/format" 43 "go/importer" 44 "go/parser" 45 "go/token" 46 "go/types" 47 "internal/testenv" 48 "io/ioutil" 49 "log" 50 "os" 51 "path/filepath" 52 "sort" 53 "strconv" 54 "strings" 55 "testing" 56 "unicode/utf8" 57 ) 58 59 var update = flag.Bool("u", false, "update format strings") 60 61 // The following variables collect information across all processed files. 62 var ( 63 fset = token.NewFileSet() 64 formatStrings = make(map[*ast.BasicLit]bool) // set of all potential format strings found 65 foundFormats = make(map[string]bool) // set of all formats found 66 callSites = make(map[*ast.CallExpr]*callSite) // map of all calls 67 ) 68 69 // A File is a corresponding (filename, ast) pair. 70 type File struct { 71 name string 72 ast *ast.File 73 } 74 75 func TestFormats(t *testing.T) { 76 testenv.MustHaveGoBuild(t) // more restrictive than necessary, but that's ok 77 78 // process all directories 79 filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 80 if info.IsDir() { 81 if info.Name() == "testdata" { 82 return filepath.SkipDir 83 } 84 85 importPath := filepath.Join("cmd/compile", path) 86 if blacklistedPackages[filepath.ToSlash(importPath)] { 87 return filepath.SkipDir 88 } 89 90 pkg, err := build.Import(importPath, path, 0) 91 if err != nil { 92 if _, ok := err.(*build.NoGoError); ok { 93 return nil // nothing to do here 94 } 95 t.Fatal(err) 96 } 97 collectPkgFormats(t, pkg) 98 } 99 return nil 100 }) 101 102 // test and rewrite formats 103 updatedFiles := make(map[string]File) // files that were rewritten 104 for _, p := range callSites { 105 // test current format literal and determine updated one 106 out := formatReplace(p.str, func(index int, in string) string { 107 if in == "*" { 108 return in // cannot rewrite '*' (as in "%*d") 109 } 110 // in != '*' 111 typ := p.types[index] 112 format := typ + " " + in // e.g., "*Node %n" 113 114 // check if format is known 115 out, known := knownFormats[format] 116 117 // record format if not yet found 118 _, found := foundFormats[format] 119 if !found { 120 foundFormats[format] = true 121 } 122 123 // report an error if the format is unknown and this is the first 124 // time we see it; ignore "%v" and "%T" which are always valid 125 if !known && !found && in != "%v" && in != "%T" { 126 t.Errorf("%s: unknown format %q for %s argument", posString(p.arg), in, typ) 127 } 128 129 if out == "" { 130 out = in 131 } 132 return out 133 }) 134 135 // replace existing format literal if it changed 136 if out != p.str { 137 // we cannot replace the argument if it's not a string literal for now 138 // (e.g., it may be "foo" + "bar") 139 lit, ok := p.arg.(*ast.BasicLit) 140 if !ok { 141 delete(callSites, p.call) // treat as if we hadn't found this site 142 continue 143 } 144 145 if testing.Verbose() { 146 fmt.Printf("%s:\n\t- %q\n\t+ %q\n", posString(p.arg), p.str, out) 147 } 148 149 // find argument index of format argument 150 index := -1 151 for i, arg := range p.call.Args { 152 if p.arg == arg { 153 index = i 154 break 155 } 156 } 157 if index < 0 { 158 // we may have processed the same call site twice, 159 // but that shouldn't happen 160 panic("internal error: matching argument not found") 161 } 162 163 // replace literal 164 new := *lit // make a copy 165 new.Value = strconv.Quote(out) // this may introduce "-quotes where there were `-quotes 166 p.call.Args[index] = &new 167 updatedFiles[p.file.name] = p.file 168 } 169 } 170 171 // write dirty files back 172 var filesUpdated bool 173 if len(updatedFiles) > 0 && *update { 174 for _, file := range updatedFiles { 175 var buf bytes.Buffer 176 if err := format.Node(&buf, fset, file.ast); err != nil { 177 t.Errorf("WARNING: formatting %s failed: %v", file.name, err) 178 continue 179 } 180 if err := ioutil.WriteFile(file.name, buf.Bytes(), 0x666); err != nil { 181 t.Errorf("WARNING: writing %s failed: %v", file.name, err) 182 continue 183 } 184 fmt.Printf("updated %s\n", file.name) 185 filesUpdated = true 186 } 187 } 188 189 // report all function names containing a format string 190 if len(callSites) > 0 && testing.Verbose() { 191 set := make(map[string]bool) 192 for _, p := range callSites { 193 set[nodeString(p.call.Fun)] = true 194 } 195 var list []string 196 for s := range set { 197 list = append(list, s) 198 } 199 fmt.Println("\nFunctions") 200 printList(list) 201 } 202 203 // report all formats found 204 if len(foundFormats) > 0 && testing.Verbose() { 205 var list []string 206 for s := range foundFormats { 207 list = append(list, fmt.Sprintf("%q: \"\",", s)) 208 } 209 fmt.Println("\nvar knownFormats = map[string]string{") 210 printList(list) 211 fmt.Println("}") 212 } 213 214 // check that knownFormats is up to date 215 if !testing.Verbose() && !*update { 216 var mismatch bool 217 for s := range foundFormats { 218 if _, ok := knownFormats[s]; !ok { 219 mismatch = true 220 break 221 } 222 } 223 if !mismatch { 224 for s := range knownFormats { 225 if _, ok := foundFormats[s]; !ok { 226 mismatch = true 227 break 228 } 229 } 230 } 231 if mismatch { 232 t.Errorf("knownFormats is out of date; please run with -v to regenerate") 233 } 234 } 235 236 // all format strings of calls must be in the formatStrings set (self-verification) 237 for _, p := range callSites { 238 if lit, ok := p.arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { 239 if formatStrings[lit] { 240 // ok 241 delete(formatStrings, lit) 242 } else { 243 // this should never happen 244 panic(fmt.Sprintf("internal error: format string not found (%s)", posString(lit))) 245 } 246 } 247 } 248 249 // if we have any strings left, we may need to update them manually 250 if len(formatStrings) > 0 && filesUpdated { 251 var list []string 252 for lit := range formatStrings { 253 list = append(list, fmt.Sprintf("%s: %s", posString(lit), nodeString(lit))) 254 } 255 fmt.Println("\nWARNING: Potentially missed format strings") 256 printList(list) 257 t.Fail() 258 } 259 260 fmt.Println() 261 } 262 263 // A callSite describes a function call that appears to contain 264 // a format string. 265 type callSite struct { 266 file File 267 call *ast.CallExpr // call containing the format string 268 arg ast.Expr // format argument (string literal or constant) 269 str string // unquoted format string 270 types []string // argument types 271 } 272 273 func collectPkgFormats(t *testing.T, pkg *build.Package) { 274 // collect all files 275 var filenames []string 276 filenames = append(filenames, pkg.GoFiles...) 277 filenames = append(filenames, pkg.CgoFiles...) 278 filenames = append(filenames, pkg.TestGoFiles...) 279 280 // TODO(gri) verify _test files outside package 281 for _, name := range pkg.XTestGoFiles { 282 // don't process this test itself 283 if name != "fmt_test.go" && testing.Verbose() { 284 fmt.Printf("WARNING: %s not processed\n", filepath.Join(pkg.Dir, name)) 285 } 286 } 287 288 // make filenames relative to . 289 for i, name := range filenames { 290 filenames[i] = filepath.Join(pkg.Dir, name) 291 } 292 293 // parse all files 294 files := make([]*ast.File, len(filenames)) 295 for i, filename := range filenames { 296 f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) 297 if err != nil { 298 t.Fatal(err) 299 } 300 files[i] = f 301 } 302 303 // typecheck package 304 conf := types.Config{Importer: importer.Default()} 305 etypes := make(map[ast.Expr]types.TypeAndValue) 306 if _, err := conf.Check(pkg.ImportPath, fset, files, &types.Info{Types: etypes}); err != nil { 307 t.Fatal(err) 308 } 309 310 // collect all potential format strings (for extra verification later) 311 for _, file := range files { 312 ast.Inspect(file, func(n ast.Node) bool { 313 if s, ok := stringLit(n); ok && isFormat(s) { 314 formatStrings[n.(*ast.BasicLit)] = true 315 } 316 return true 317 }) 318 } 319 320 // collect all formats/arguments of calls with format strings 321 for index, file := range files { 322 ast.Inspect(file, func(n ast.Node) bool { 323 if call, ok := n.(*ast.CallExpr); ok { 324 // ignore blacklisted functions 325 if blacklistedFunctions[nodeString(call.Fun)] { 326 return true 327 } 328 // look for an arguments that might be a format string 329 for i, arg := range call.Args { 330 if s, ok := stringVal(etypes[arg]); ok && isFormat(s) { 331 // make sure we have enough arguments 332 n := numFormatArgs(s) 333 if i+1+n > len(call.Args) { 334 t.Errorf("%s: not enough format args (blacklist %s?)", posString(call), nodeString(call.Fun)) 335 break // ignore this call 336 } 337 // assume last n arguments are to be formatted; 338 // determine their types 339 argTypes := make([]string, n) 340 for i, arg := range call.Args[len(call.Args)-n:] { 341 if tv, ok := etypes[arg]; ok { 342 argTypes[i] = typeString(tv.Type) 343 } 344 } 345 // collect call site 346 if callSites[call] != nil { 347 panic("internal error: file processed twice?") 348 } 349 callSites[call] = &callSite{ 350 file: File{filenames[index], file}, 351 call: call, 352 arg: arg, 353 str: s, 354 types: argTypes, 355 } 356 break // at most one format per argument list 357 } 358 } 359 } 360 return true 361 }) 362 } 363 } 364 365 // printList prints list in sorted order. 366 func printList(list []string) { 367 sort.Strings(list) 368 for _, s := range list { 369 fmt.Println("\t", s) 370 } 371 } 372 373 // posString returns a string representation of n's position 374 // in the form filename:line:col: . 375 func posString(n ast.Node) string { 376 if n == nil { 377 return "" 378 } 379 return fset.Position(n.Pos()).String() 380 } 381 382 // nodeString returns a string representation of n. 383 func nodeString(n ast.Node) string { 384 var buf bytes.Buffer 385 if err := format.Node(&buf, fset, n); err != nil { 386 log.Fatal(err) // should always succeed 387 } 388 return buf.String() 389 } 390 391 // typeString returns a string representation of n. 392 func typeString(typ types.Type) string { 393 return filepath.ToSlash(typ.String()) 394 } 395 396 // stringLit returns the unquoted string value and true if 397 // n represents a string literal; otherwise it returns "" 398 // and false. 399 func stringLit(n ast.Node) (string, bool) { 400 if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING { 401 s, err := strconv.Unquote(lit.Value) 402 if err != nil { 403 log.Fatal(err) // should not happen with correct ASTs 404 } 405 return s, true 406 } 407 return "", false 408 } 409 410 // stringVal returns the (unquoted) string value and true if 411 // tv is a string constant; otherwise it returns "" and false. 412 func stringVal(tv types.TypeAndValue) (string, bool) { 413 if tv.IsValue() && tv.Value != nil && tv.Value.Kind() == constant.String { 414 return constant.StringVal(tv.Value), true 415 } 416 return "", false 417 } 418 419 // formatIter iterates through the string s in increasing 420 // index order and calls f for each format specifier '%..v'. 421 // The arguments for f describe the specifier's index range. 422 // If a format specifier contains a "*", f is called with 423 // the index range for "*" alone, before being called for 424 // the entire specifier. The result of f is the index of 425 // the rune at which iteration continues. 426 func formatIter(s string, f func(i, j int) int) { 427 i := 0 // index after current rune 428 var r rune // current rune 429 430 next := func() { 431 r1, w := utf8.DecodeRuneInString(s[i:]) 432 if w == 0 { 433 r1 = -1 // signal end-of-string 434 } 435 r = r1 436 i += w 437 } 438 439 flags := func() { 440 for r == ' ' || r == '#' || r == '+' || r == '-' || r == '0' { 441 next() 442 } 443 } 444 445 index := func() { 446 if r == '[' { 447 log.Fatalf("cannot handle indexed arguments: %s", s) 448 } 449 } 450 451 digits := func() { 452 index() 453 if r == '*' { 454 i = f(i-1, i) 455 next() 456 return 457 } 458 for '0' <= r && r <= '9' { 459 next() 460 } 461 } 462 463 for next(); r >= 0; next() { 464 if r == '%' { 465 i0 := i 466 next() 467 flags() 468 digits() 469 if r == '.' { 470 next() 471 digits() 472 } 473 index() 474 // accept any letter (a-z, A-Z) as format verb; 475 // ignore anything else 476 if 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' { 477 i = f(i0-1, i) 478 } 479 } 480 } 481 } 482 483 // isFormat reports whether s contains format specifiers. 484 func isFormat(s string) (yes bool) { 485 formatIter(s, func(i, j int) int { 486 yes = true 487 return len(s) // stop iteration 488 }) 489 return 490 } 491 492 // oneFormat reports whether s is exactly one format specifier. 493 func oneFormat(s string) (yes bool) { 494 formatIter(s, func(i, j int) int { 495 yes = i == 0 && j == len(s) 496 return j 497 }) 498 return 499 } 500 501 // numFormatArgs returns the number of format specifiers in s. 502 func numFormatArgs(s string) int { 503 count := 0 504 formatIter(s, func(i, j int) int { 505 count++ 506 return j 507 }) 508 return count 509 } 510 511 // formatReplace replaces the i'th format specifier s in the incoming 512 // string in with the result of f(i, s) and returns the new string. 513 func formatReplace(in string, f func(i int, s string) string) string { 514 var buf []byte 515 i0 := 0 516 index := 0 517 formatIter(in, func(i, j int) int { 518 if sub := in[i:j]; sub != "*" { // ignore calls for "*" width/length specifiers 519 buf = append(buf, in[i0:i]...) 520 buf = append(buf, f(index, sub)...) 521 i0 = j 522 } 523 index++ 524 return j 525 }) 526 return string(append(buf, in[i0:]...)) 527 } 528 529 // blacklistedPackages is the set of packages which can 530 // be ignored. 531 var blacklistedPackages = map[string]bool{} 532 533 // blacklistedFunctions is the set of functions which may have 534 // format-like arguments but which don't do any formatting and 535 // thus may be ignored. 536 var blacklistedFunctions = map[string]bool{} 537 538 func init() { 539 // verify that knownFormats entries are correctly formatted 540 for key, val := range knownFormats { 541 // key must be "typename format", and format starts with a '%' 542 // (formats containing '*' alone are not collected in this table) 543 i := strings.Index(key, "%") 544 if i < 0 || !oneFormat(key[i:]) { 545 log.Fatalf("incorrect knownFormats key: %q", key) 546 } 547 // val must be "format" or "" 548 if val != "" && !oneFormat(val) { 549 log.Fatalf("incorrect knownFormats value: %q (key = %q)", val, key) 550 } 551 } 552 } 553 554 // knownFormats entries are of the form "typename format" -> "newformat". 555 // An absent entry means that the format is not recognized as valid. 556 // An empty new format means that the format should remain unchanged. 557 // To print out a new table, run: go test -run Formats -v. 558 var knownFormats = map[string]string{ 559 "*bytes.Buffer %s": "", 560 "*cmd/compile/internal/gc.Field %p": "", 561 "*cmd/compile/internal/gc.Field %v": "", 562 "*cmd/compile/internal/gc.Mpflt %v": "", 563 "*cmd/compile/internal/gc.Mpint %v": "", 564 "*cmd/compile/internal/gc.Node %#v": "", 565 "*cmd/compile/internal/gc.Node %+S": "", 566 "*cmd/compile/internal/gc.Node %+v": "", 567 "*cmd/compile/internal/gc.Node %0j": "", 568 "*cmd/compile/internal/gc.Node %L": "", 569 "*cmd/compile/internal/gc.Node %S": "", 570 "*cmd/compile/internal/gc.Node %j": "", 571 "*cmd/compile/internal/gc.Node %p": "", 572 "*cmd/compile/internal/gc.Node %v": "", 573 "*cmd/compile/internal/gc.Sym %+v": "", 574 "*cmd/compile/internal/gc.Sym %-v": "", 575 "*cmd/compile/internal/gc.Sym %0S": "", 576 "*cmd/compile/internal/gc.Sym %S": "", 577 "*cmd/compile/internal/gc.Sym %p": "", 578 "*cmd/compile/internal/gc.Sym %v": "", 579 "*cmd/compile/internal/gc.Type %#v": "", 580 "*cmd/compile/internal/gc.Type %+v": "", 581 "*cmd/compile/internal/gc.Type %-S": "", 582 "*cmd/compile/internal/gc.Type %0S": "", 583 "*cmd/compile/internal/gc.Type %L": "", 584 "*cmd/compile/internal/gc.Type %S": "", 585 "*cmd/compile/internal/gc.Type %p": "", 586 "*cmd/compile/internal/gc.Type %v": "", 587 "*cmd/compile/internal/ssa.Block %s": "", 588 "*cmd/compile/internal/ssa.Block %v": "", 589 "*cmd/compile/internal/ssa.Func %s": "", 590 "*cmd/compile/internal/ssa.SparseTreeNode %v": "", 591 "*cmd/compile/internal/ssa.Value %s": "", 592 "*cmd/compile/internal/ssa.Value %v": "", 593 "*cmd/compile/internal/ssa.sparseTreeMapEntry %v": "", 594 "*cmd/internal/obj.Addr %v": "", 595 "*cmd/internal/obj.Prog %p": "", 596 "*cmd/internal/obj.Prog %s": "", 597 "*cmd/internal/obj.Prog %v": "", 598 "*math/big.Int %#x": "", 599 "[16]byte %x": "", 600 "[]*cmd/compile/internal/gc.Node %v": "", 601 "[]*cmd/compile/internal/gc.Sig %#v": "", 602 "[]*cmd/compile/internal/ssa.Value %v": "", 603 "[]byte %s": "", 604 "[]byte %x": "", 605 "[]cmd/compile/internal/ssa.Edge %v": "", 606 "[]cmd/compile/internal/ssa.ID %v": "", 607 "[]string %v": "", 608 "bool %v": "", 609 "byte %02x": "", 610 "byte %08b": "", 611 "byte %c": "", 612 "cmd/compile/internal/arm.shift %d": "", 613 "cmd/compile/internal/gc.Class %d": "", 614 "cmd/compile/internal/gc.Ctype %d": "", 615 "cmd/compile/internal/gc.Ctype %v": "", 616 "cmd/compile/internal/gc.EType %d": "", 617 "cmd/compile/internal/gc.EType %s": "", 618 "cmd/compile/internal/gc.EType %v": "", 619 "cmd/compile/internal/gc.Level %d": "", 620 "cmd/compile/internal/gc.Level %v": "", 621 "cmd/compile/internal/gc.Node %#v": "", 622 "cmd/compile/internal/gc.Nodes %#v": "", 623 "cmd/compile/internal/gc.Nodes %+v": "", 624 "cmd/compile/internal/gc.Nodes %.v": "", 625 "cmd/compile/internal/gc.Nodes %v": "", 626 "cmd/compile/internal/gc.Op %#v": "", 627 "cmd/compile/internal/gc.Op %v": "", 628 "cmd/compile/internal/gc.Val %#v": "", 629 "cmd/compile/internal/gc.Val %T": "", 630 "cmd/compile/internal/gc.Val %v": "", 631 "cmd/compile/internal/gc.initKind %d": "", 632 "cmd/compile/internal/ssa.BranchPrediction %d": "", 633 "cmd/compile/internal/ssa.Edge %v": "", 634 "cmd/compile/internal/ssa.GCNode %v": "", 635 "cmd/compile/internal/ssa.ID %d": "", 636 "cmd/compile/internal/ssa.LocalSlot %v": "", 637 "cmd/compile/internal/ssa.Location %v": "", 638 "cmd/compile/internal/ssa.Op %s": "", 639 "cmd/compile/internal/ssa.Op %v": "", 640 "cmd/compile/internal/ssa.SizeAndAlign %s": "", 641 "cmd/compile/internal/ssa.Type %s": "", 642 "cmd/compile/internal/ssa.Type %v": "", 643 "cmd/compile/internal/ssa.ValAndOff %s": "", 644 "cmd/compile/internal/ssa.markKind %d": "", 645 "cmd/compile/internal/ssa.rbrank %d": "", 646 "cmd/compile/internal/ssa.regMask %d": "", 647 "cmd/compile/internal/ssa.register %d": "", 648 "cmd/compile/internal/syntax.Expr %#v": "", 649 "cmd/compile/internal/syntax.Expr %s": "", 650 "cmd/compile/internal/syntax.Node %T": "", 651 "cmd/compile/internal/syntax.Operator %d": "", 652 "cmd/compile/internal/syntax.Operator %s": "", 653 "cmd/compile/internal/syntax.token %d": "", 654 "cmd/compile/internal/syntax.token %q": "", 655 "cmd/compile/internal/syntax.token %s": "", 656 "cmd/internal/obj.As %v": "", 657 "error %v": "", 658 "float64 %.2f": "", 659 "float64 %.3f": "", 660 "float64 %.6g": "", 661 "float64 %g": "", 662 "fmt.Stringer %T": "", 663 "int %-12d": "", 664 "int %-6d": "", 665 "int %-8o": "", 666 "int %5d": "", 667 "int %6d": "", 668 "int %c": "", 669 "int %d": "", 670 "int %v": "", 671 "int %x": "", 672 "int16 %d": "", 673 "int16 %x": "", 674 "int32 %d": "", 675 "int32 %v": "", 676 "int32 %x": "", 677 "int64 %+d": "", 678 "int64 %-10d": "", 679 "int64 %X": "", 680 "int64 %d": "", 681 "int64 %v": "", 682 "int64 %x": "", 683 "int8 %d": "", 684 "int8 %x": "", 685 "interface{} %#v": "", 686 "interface{} %T": "", 687 "interface{} %q": "", 688 "interface{} %s": "", 689 "interface{} %v": "", 690 "map[*cmd/compile/internal/gc.Node]*cmd/compile/internal/ssa.Value %v": "", 691 "reflect.Type %s": "", 692 "rune %#U": "", 693 "rune %c": "", 694 "string %-16s": "", 695 "string %.*s": "", 696 "string %q": "", 697 "string %s": "", 698 "string %v": "", 699 "time.Duration %d": "", 700 "time.Duration %v": "", 701 "uint %04x": "", 702 "uint %d": "", 703 "uint16 %d": "", 704 "uint16 %v": "", 705 "uint16 %x": "", 706 "uint32 %08x": "", 707 "uint32 %d": "", 708 "uint32 %x": "", 709 "uint64 %016x": "", 710 "uint64 %08x": "", 711 "uint64 %d": "", 712 "uint64 %x": "", 713 "uint8 %d": "", 714 "uint8 %x": "", 715 "uintptr %d": "", 716 }