1
2
3
4
5 package printf
6
7 import (
8 "bytes"
9 _ "embed"
10 "fmt"
11 "go/ast"
12 "go/constant"
13 "go/token"
14 "go/types"
15 "reflect"
16 "regexp"
17 "sort"
18 "strconv"
19 "strings"
20 "unicode/utf8"
21
22 "golang.org/x/tools/go/analysis"
23 "golang.org/x/tools/go/analysis/passes/inspect"
24 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
25 "golang.org/x/tools/go/ast/inspector"
26 "golang.org/x/tools/go/types/typeutil"
27 "golang.org/x/tools/internal/typeparams"
28 "golang.org/x/tools/internal/versions"
29 )
30
31 func init() {
32 Analyzer.Flags.Var(isPrint, "funcs", "comma-separated list of print function names to check")
33 }
34
35
36 var doc string
37
38 var Analyzer = &analysis.Analyzer{
39 Name: "printf",
40 Doc: analysisutil.MustExtractDoc(doc, "printf"),
41 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
42 Requires: []*analysis.Analyzer{inspect.Analyzer},
43 Run: run,
44 ResultType: reflect.TypeOf((*Result)(nil)),
45 FactTypes: []analysis.Fact{new(isWrapper)},
46 }
47
48
49 type Kind int
50
51 const (
52 KindNone Kind = iota
53 KindPrint
54 KindPrintf
55 KindErrorf
56 )
57
58 func (kind Kind) String() string {
59 switch kind {
60 case KindPrint:
61 return "print"
62 case KindPrintf:
63 return "printf"
64 case KindErrorf:
65 return "errorf"
66 }
67 return ""
68 }
69
70
71
72 type Result struct {
73 funcs map[*types.Func]Kind
74 }
75
76
77 func (r *Result) Kind(fn *types.Func) Kind {
78 _, ok := isPrint[fn.FullName()]
79 if !ok {
80
81 _, ok = isPrint[strings.ToLower(fn.Name())]
82 }
83 if ok {
84 if strings.HasSuffix(fn.Name(), "f") {
85 return KindPrintf
86 } else {
87 return KindPrint
88 }
89 }
90
91 return r.funcs[fn]
92 }
93
94
95 type isWrapper struct{ Kind Kind }
96
97 func (f *isWrapper) AFact() {}
98
99 func (f *isWrapper) String() string {
100 switch f.Kind {
101 case KindPrintf:
102 return "printfWrapper"
103 case KindPrint:
104 return "printWrapper"
105 case KindErrorf:
106 return "errorfWrapper"
107 default:
108 return "unknownWrapper"
109 }
110 }
111
112 func run(pass *analysis.Pass) (any, error) {
113 res := &Result{
114 funcs: make(map[*types.Func]Kind),
115 }
116 findPrintfLike(pass, res)
117 checkCalls(pass)
118 return res, nil
119 }
120
121 type printfWrapper struct {
122 obj *types.Func
123 fdecl *ast.FuncDecl
124 format *types.Var
125 args *types.Var
126 callers []printfCaller
127 failed bool
128 }
129
130 type printfCaller struct {
131 w *printfWrapper
132 call *ast.CallExpr
133 }
134
135
136
137
138
139
140
141
142 func maybePrintfWrapper(info *types.Info, decl ast.Decl) *printfWrapper {
143
144 fdecl, ok := decl.(*ast.FuncDecl)
145 if !ok || fdecl.Body == nil {
146 return nil
147 }
148 fn, ok := info.Defs[fdecl.Name].(*types.Func)
149
150 if !ok {
151 return nil
152 }
153
154 sig := fn.Type().(*types.Signature)
155 if !sig.Variadic() {
156 return nil
157 }
158
159 params := sig.Params()
160 nparams := params.Len()
161
162
163 args := params.At(nparams - 1)
164 iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
165 if !ok || !iface.Empty() {
166 return nil
167 }
168
169
170 var format *types.Var
171 if nparams >= 2 {
172 if p := params.At(nparams - 2); p.Type() == types.Typ[types.String] {
173 format = p
174 }
175 }
176
177 return &printfWrapper{
178 obj: fn,
179 fdecl: fdecl,
180 format: format,
181 args: args,
182 }
183 }
184
185
186 func findPrintfLike(pass *analysis.Pass, res *Result) (any, error) {
187
188 byObj := make(map[*types.Func]*printfWrapper)
189 var wrappers []*printfWrapper
190 for _, file := range pass.Files {
191 for _, decl := range file.Decls {
192 w := maybePrintfWrapper(pass.TypesInfo, decl)
193 if w == nil {
194 continue
195 }
196 byObj[w.obj] = w
197 wrappers = append(wrappers, w)
198 }
199 }
200
201
202 for _, w := range wrappers {
203
204 ast.Inspect(w.fdecl.Body, func(n ast.Node) bool {
205 if w.failed {
206 return false
207 }
208
209
210 if assign, ok := n.(*ast.AssignStmt); ok {
211 for _, lhs := range assign.Lhs {
212 if match(pass.TypesInfo, lhs, w.format) ||
213 match(pass.TypesInfo, lhs, w.args) {
214
215
216
217
218 w.failed = true
219 return false
220 }
221 }
222 }
223 if un, ok := n.(*ast.UnaryExpr); ok && un.Op == token.AND {
224 if match(pass.TypesInfo, un.X, w.format) ||
225 match(pass.TypesInfo, un.X, w.args) {
226
227
228
229 w.failed = true
230 return false
231 }
232 }
233
234 call, ok := n.(*ast.CallExpr)
235 if !ok || len(call.Args) == 0 || !match(pass.TypesInfo, call.Args[len(call.Args)-1], w.args) {
236 return true
237 }
238
239 fn, kind := printfNameAndKind(pass, call)
240 if kind != 0 {
241 checkPrintfFwd(pass, w, call, kind, res)
242 return true
243 }
244
245
246
247
248 if fn != nil && fn.Pkg() == pass.Pkg && byObj[fn] != nil {
249 callee := byObj[fn]
250 callee.callers = append(callee.callers, printfCaller{w, call})
251 }
252
253 return true
254 })
255 }
256 return nil, nil
257 }
258
259 func match(info *types.Info, arg ast.Expr, param *types.Var) bool {
260 id, ok := arg.(*ast.Ident)
261 return ok && info.ObjectOf(id) == param
262 }
263
264
265
266 func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind Kind, res *Result) {
267 matched := kind == KindPrint ||
268 kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format)
269 if !matched {
270 return
271 }
272
273 if !call.Ellipsis.IsValid() {
274 typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature)
275 if !ok {
276 return
277 }
278 if len(call.Args) > typ.Params().Len() {
279
280
281
282
283
284
285
286 return
287 }
288 desc := "printf"
289 if kind == KindPrint {
290 desc = "print"
291 }
292 pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc)
293 return
294 }
295 fn := w.obj
296 var fact isWrapper
297 if !pass.ImportObjectFact(fn, &fact) {
298 fact.Kind = kind
299 pass.ExportObjectFact(fn, &fact)
300 res.funcs[fn] = kind
301 for _, caller := range w.callers {
302 checkPrintfFwd(pass, caller.w, caller.call, kind, res)
303 }
304 }
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320 var isPrint = stringSet{
321 "fmt.Appendf": true,
322 "fmt.Append": true,
323 "fmt.Appendln": true,
324 "fmt.Errorf": true,
325 "fmt.Fprint": true,
326 "fmt.Fprintf": true,
327 "fmt.Fprintln": true,
328 "fmt.Print": true,
329 "fmt.Printf": true,
330 "fmt.Println": true,
331 "fmt.Sprint": true,
332 "fmt.Sprintf": true,
333 "fmt.Sprintln": true,
334
335 "runtime/trace.Logf": true,
336
337 "log.Print": true,
338 "log.Printf": true,
339 "log.Println": true,
340 "log.Fatal": true,
341 "log.Fatalf": true,
342 "log.Fatalln": true,
343 "log.Panic": true,
344 "log.Panicf": true,
345 "log.Panicln": true,
346 "(*log.Logger).Fatal": true,
347 "(*log.Logger).Fatalf": true,
348 "(*log.Logger).Fatalln": true,
349 "(*log.Logger).Panic": true,
350 "(*log.Logger).Panicf": true,
351 "(*log.Logger).Panicln": true,
352 "(*log.Logger).Print": true,
353 "(*log.Logger).Printf": true,
354 "(*log.Logger).Println": true,
355
356 "(*testing.common).Error": true,
357 "(*testing.common).Errorf": true,
358 "(*testing.common).Fatal": true,
359 "(*testing.common).Fatalf": true,
360 "(*testing.common).Log": true,
361 "(*testing.common).Logf": true,
362 "(*testing.common).Skip": true,
363 "(*testing.common).Skipf": true,
364
365
366 "(testing.TB).Error": true,
367 "(testing.TB).Errorf": true,
368 "(testing.TB).Fatal": true,
369 "(testing.TB).Fatalf": true,
370 "(testing.TB).Log": true,
371 "(testing.TB).Logf": true,
372 "(testing.TB).Skip": true,
373 "(testing.TB).Skipf": true,
374 }
375
376
377
378
379 func formatStringIndex(pass *analysis.Pass, call *ast.CallExpr) int {
380 typ := pass.TypesInfo.Types[call.Fun].Type
381 if typ == nil {
382 return -1
383 }
384 sig, ok := typ.(*types.Signature)
385 if !ok {
386 return -1
387 }
388 if !sig.Variadic() {
389
390 return -1
391 }
392 idx := sig.Params().Len() - 2
393 if idx < 0 {
394
395
396 return -1
397 }
398 return idx
399 }
400
401
402
403
404
405 func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
406 lit := pass.TypesInfo.Types[expr].Value
407 if lit != nil && lit.Kind() == constant.String {
408 return constant.StringVal(lit), true
409 }
410 return "", false
411 }
412
413
414
415 func checkCalls(pass *analysis.Pass) {
416 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
417 nodeFilter := []ast.Node{
418 (*ast.File)(nil),
419 (*ast.CallExpr)(nil),
420 }
421
422 var fileVersion string
423 inspect.Preorder(nodeFilter, func(n ast.Node) {
424 switch n := n.(type) {
425 case *ast.File:
426 fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
427
428 case *ast.CallExpr:
429 fn, kind := printfNameAndKind(pass, n)
430 switch kind {
431 case KindPrintf, KindErrorf:
432 checkPrintf(pass, fileVersion, kind, n, fn)
433 case KindPrint:
434 checkPrint(pass, n, fn)
435 }
436 }
437 })
438 }
439
440 func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind Kind) {
441 fn, _ = typeutil.Callee(pass.TypesInfo, call).(*types.Func)
442 if fn == nil {
443 return nil, 0
444 }
445
446
447 fn = fn.Origin()
448
449 _, ok := isPrint[fn.FullName()]
450 if !ok {
451
452 _, ok = isPrint[strings.ToLower(fn.Name())]
453 }
454 if ok {
455 if fn.FullName() == "fmt.Errorf" {
456 kind = KindErrorf
457 } else if strings.HasSuffix(fn.Name(), "f") {
458 kind = KindPrintf
459 } else {
460 kind = KindPrint
461 }
462 return fn, kind
463 }
464
465 var fact isWrapper
466 if pass.ImportObjectFact(fn, &fact) {
467 return fn, fact.Kind
468 }
469
470 return fn, KindNone
471 }
472
473
474
475 func isFormatter(typ types.Type) bool {
476
477 if _, ok := typ.Underlying().(*types.Interface); ok {
478
479
480
481 if !typeparams.IsTypeParam(typ) {
482 return true
483 }
484 }
485 obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format")
486 fn, ok := obj.(*types.Func)
487 if !ok {
488 return false
489 }
490 sig := fn.Type().(*types.Signature)
491 return sig.Params().Len() == 2 &&
492 sig.Results().Len() == 0 &&
493 analysisutil.IsNamedType(sig.Params().At(0).Type(), "fmt", "State") &&
494 types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
495 }
496
497
498
499 type formatState struct {
500 verb rune
501 format string
502 name string
503 flags []byte
504 argNums []int
505 firstArg int
506
507 pass *analysis.Pass
508 call *ast.CallExpr
509 argNum int
510 hasIndex bool
511 indexPending bool
512 nbytes int
513 }
514
515
516 func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, fn *types.Func) {
517 idx := formatStringIndex(pass, call)
518 if idx < 0 || idx >= len(call.Args) {
519 return
520 }
521 formatArg := call.Args[idx]
522 format, ok := stringConstantExpr(pass, formatArg)
523 if !ok {
524
525
526
527
528
529
530
531
532
533
534
535
536 if !suppressNonconstants &&
537 idx == len(call.Args)-1 &&
538 fileVersion != "" &&
539 versions.AtLeast(fileVersion, "go1.24") {
540
541 pass.Report(analysis.Diagnostic{
542 Pos: formatArg.Pos(),
543 End: formatArg.End(),
544 Message: fmt.Sprintf("non-constant format string in call to %s",
545 fn.FullName()),
546 SuggestedFixes: []analysis.SuggestedFix{{
547 Message: `Insert "%s" format string`,
548 TextEdits: []analysis.TextEdit{{
549 Pos: formatArg.Pos(),
550 End: formatArg.Pos(),
551 NewText: []byte(`"%s", `),
552 }},
553 }},
554 })
555 }
556 return
557 }
558
559 firstArg := idx + 1
560 if !strings.Contains(format, "%") {
561 if len(call.Args) > firstArg {
562 pass.Reportf(call.Lparen, "%s call has arguments but no formatting directives", fn.FullName())
563 }
564 return
565 }
566
567 argNum := firstArg
568 maxArgNum := firstArg
569 anyIndex := false
570 for i, w := 0, 0; i < len(format); i += w {
571 w = 1
572 if format[i] != '%' {
573 continue
574 }
575 state := parsePrintfVerb(pass, call, fn.FullName(), format[i:], firstArg, argNum)
576 if state == nil {
577 return
578 }
579 w = len(state.format)
580 if !okPrintfArg(pass, call, state) {
581 return
582 }
583 if state.hasIndex {
584 anyIndex = true
585 }
586 if state.verb == 'w' {
587 switch kind {
588 case KindNone, KindPrint, KindPrintf:
589 pass.Reportf(call.Pos(), "%s does not support error-wrapping directive %%w", state.name)
590 return
591 }
592 }
593 if len(state.argNums) > 0 {
594
595 argNum = state.argNums[len(state.argNums)-1] + 1
596 }
597 for _, n := range state.argNums {
598 if n >= maxArgNum {
599 maxArgNum = n + 1
600 }
601 }
602 }
603
604 if call.Ellipsis.IsValid() && maxArgNum >= len(call.Args)-1 {
605 return
606 }
607
608 if anyIndex {
609 return
610 }
611
612 if maxArgNum != len(call.Args) {
613 expect := maxArgNum - firstArg
614 numArgs := len(call.Args) - firstArg
615 pass.ReportRangef(call, "%s call needs %v but has %v", fn.FullName(), count(expect, "arg"), count(numArgs, "arg"))
616 }
617 }
618
619
620 func (s *formatState) parseFlags() {
621 for s.nbytes < len(s.format) {
622 switch c := s.format[s.nbytes]; c {
623 case '#', '0', '+', '-', ' ':
624 s.flags = append(s.flags, c)
625 s.nbytes++
626 default:
627 return
628 }
629 }
630 }
631
632
633 func (s *formatState) scanNum() {
634 for ; s.nbytes < len(s.format); s.nbytes++ {
635 c := s.format[s.nbytes]
636 if c < '0' || '9' < c {
637 return
638 }
639 }
640 }
641
642
643 func (s *formatState) parseIndex() bool {
644 if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' {
645 return true
646 }
647
648 s.nbytes++
649 start := s.nbytes
650 s.scanNum()
651 ok := true
652 if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
653 ok = false
654 s.nbytes = strings.Index(s.format[start:], "]")
655 if s.nbytes < 0 {
656 s.pass.ReportRangef(s.call, "%s format %s is missing closing ]", s.name, s.format)
657 return false
658 }
659 s.nbytes = s.nbytes + start
660 }
661 arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
662 if err != nil || !ok || arg32 <= 0 || arg32 > int64(len(s.call.Args)-s.firstArg) {
663 s.pass.ReportRangef(s.call, "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes])
664 return false
665 }
666 s.nbytes++
667 arg := int(arg32)
668 arg += s.firstArg - 1
669 s.argNum = arg
670 s.hasIndex = true
671 s.indexPending = true
672 return true
673 }
674
675
676 func (s *formatState) parseNum() bool {
677 if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' {
678 if s.indexPending {
679 s.indexPending = false
680 }
681 s.nbytes++
682 s.argNums = append(s.argNums, s.argNum)
683 s.argNum++
684 } else {
685 s.scanNum()
686 }
687 return true
688 }
689
690
691 func (s *formatState) parsePrecision() bool {
692
693 if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' {
694 s.flags = append(s.flags, '.')
695 s.nbytes++
696 if !s.parseIndex() {
697 return false
698 }
699 if !s.parseNum() {
700 return false
701 }
702 }
703 return true
704 }
705
706
707
708
709 func parsePrintfVerb(pass *analysis.Pass, call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState {
710 state := &formatState{
711 format: format,
712 name: name,
713 flags: make([]byte, 0, 5),
714 argNum: argNum,
715 argNums: make([]int, 0, 1),
716 nbytes: 1,
717 firstArg: firstArg,
718 pass: pass,
719 call: call,
720 }
721
722 state.parseFlags()
723
724 if !state.parseIndex() {
725 return nil
726 }
727
728 if !state.parseNum() {
729 return nil
730 }
731
732 if !state.parsePrecision() {
733 return nil
734 }
735
736 if !state.indexPending && !state.parseIndex() {
737 return nil
738 }
739 if state.nbytes == len(state.format) {
740 pass.ReportRangef(call.Fun, "%s format %s is missing verb at end of string", name, state.format)
741 return nil
742 }
743 verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
744 state.verb = verb
745 state.nbytes += w
746 if verb != '%' {
747 state.argNums = append(state.argNums, state.argNum)
748 }
749 state.format = state.format[:state.nbytes]
750 return state
751 }
752
753
754 type printfArgType int
755
756 const (
757 argBool printfArgType = 1 << iota
758 argInt
759 argRune
760 argString
761 argFloat
762 argComplex
763 argPointer
764 argError
765 anyType printfArgType = ^0
766 )
767
768 type printVerb struct {
769 verb rune
770 flags string
771 typ printfArgType
772 }
773
774
775 const (
776 noFlag = ""
777 numFlag = " -+.0"
778 sharpNumFlag = " -+.0#"
779 allFlags = " -+.0#"
780 )
781
782
783 var printVerbs = []printVerb{
784
785
786
787
788
789 {'%', noFlag, 0},
790 {'b', sharpNumFlag, argInt | argFloat | argComplex | argPointer},
791 {'c', "-", argRune | argInt},
792 {'d', numFlag, argInt | argPointer},
793 {'e', sharpNumFlag, argFloat | argComplex},
794 {'E', sharpNumFlag, argFloat | argComplex},
795 {'f', sharpNumFlag, argFloat | argComplex},
796 {'F', sharpNumFlag, argFloat | argComplex},
797 {'g', sharpNumFlag, argFloat | argComplex},
798 {'G', sharpNumFlag, argFloat | argComplex},
799 {'o', sharpNumFlag, argInt | argPointer},
800 {'O', sharpNumFlag, argInt | argPointer},
801 {'p', "-#", argPointer},
802 {'q', " -+.0#", argRune | argInt | argString},
803 {'s', " -+.0", argString},
804 {'t', "-", argBool},
805 {'T', "-", anyType},
806 {'U', "-#", argRune | argInt},
807 {'v', allFlags, anyType},
808 {'w', allFlags, argError},
809 {'x', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
810 {'X', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
811 }
812
813
814
815
816 func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (ok bool) {
817 var v printVerb
818 found := false
819
820 for _, v = range printVerbs {
821 if v.verb == state.verb {
822 found = true
823 break
824 }
825 }
826
827
828
829 formatter := false
830 if v.typ != argError && state.argNum < len(call.Args) {
831 if tv, ok := pass.TypesInfo.Types[call.Args[state.argNum]]; ok {
832 formatter = isFormatter(tv.Type)
833 }
834 }
835
836 if !formatter {
837 if !found {
838 pass.ReportRangef(call, "%s format %s has unknown verb %c", state.name, state.format, state.verb)
839 return false
840 }
841 for _, flag := range state.flags {
842
843
844 if flag == '0' {
845 continue
846 }
847 if !strings.ContainsRune(v.flags, rune(flag)) {
848 pass.ReportRangef(call, "%s format %s has unrecognized flag %c", state.name, state.format, flag)
849 return false
850 }
851 }
852 }
853
854
855 trueArgs := 1
856 if state.verb == '%' {
857 trueArgs = 0
858 }
859 nargs := len(state.argNums)
860 for i := 0; i < nargs-trueArgs; i++ {
861 argNum := state.argNums[i]
862 if !argCanBeChecked(pass, call, i, state) {
863 return
864 }
865 arg := call.Args[argNum]
866 if reason, ok := matchArgType(pass, argInt, arg); !ok {
867 details := ""
868 if reason != "" {
869 details = " (" + reason + ")"
870 }
871 pass.ReportRangef(call, "%s format %s uses non-int %s%s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg), details)
872 return false
873 }
874 }
875
876 if state.verb == '%' || formatter {
877 return true
878 }
879 argNum := state.argNums[len(state.argNums)-1]
880 if !argCanBeChecked(pass, call, len(state.argNums)-1, state) {
881 return false
882 }
883 arg := call.Args[argNum]
884 if isFunctionValue(pass, arg) && state.verb != 'p' && state.verb != 'T' {
885 pass.ReportRangef(call, "%s format %s arg %s is a func value, not called", state.name, state.format, analysisutil.Format(pass.Fset, arg))
886 return false
887 }
888 if reason, ok := matchArgType(pass, v.typ, arg); !ok {
889 typeString := ""
890 if typ := pass.TypesInfo.Types[arg].Type; typ != nil {
891 typeString = typ.String()
892 }
893 details := ""
894 if reason != "" {
895 details = " (" + reason + ")"
896 }
897 pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s%s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString, details)
898 return false
899 }
900 if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) {
901 if methodName, ok := recursiveStringer(pass, arg); ok {
902 pass.ReportRangef(call, "%s format %s with arg %s causes recursive %s method call", state.name, state.format, analysisutil.Format(pass.Fset, arg), methodName)
903 return false
904 }
905 }
906 return true
907 }
908
909
910
911
912
913
914
915 func recursiveStringer(pass *analysis.Pass, e ast.Expr) (string, bool) {
916 typ := pass.TypesInfo.Types[e].Type
917
918
919 if isFormatter(typ) {
920 return "", false
921 }
922
923
924 strObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "String")
925 strMethod, strOk := strObj.(*types.Func)
926 errObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "Error")
927 errMethod, errOk := errObj.(*types.Func)
928 if !strOk && !errOk {
929 return "", false
930 }
931
932
933 inScope := func(e ast.Expr, f *types.Func) bool {
934 return f.Scope() != nil && f.Scope().Contains(e.Pos())
935 }
936
937
938 var method *types.Func
939 if strOk && strMethod.Pkg() == pass.Pkg && inScope(e, strMethod) {
940 method = strMethod
941 } else if errOk && errMethod.Pkg() == pass.Pkg && inScope(e, errMethod) {
942 method = errMethod
943 } else {
944 return "", false
945 }
946
947 sig := method.Type().(*types.Signature)
948 if !isStringer(sig) {
949 return "", false
950 }
951
952
953 if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
954 e = u.X
955 }
956 if id, ok := e.(*ast.Ident); ok {
957 if pass.TypesInfo.Uses[id] == sig.Recv() {
958 return method.FullName(), true
959 }
960 }
961 return "", false
962 }
963
964
965 func isStringer(sig *types.Signature) bool {
966 return sig.Params().Len() == 0 &&
967 sig.Results().Len() == 1 &&
968 sig.Results().At(0).Type() == types.Typ[types.String]
969 }
970
971
972
973 func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
974 if typ := pass.TypesInfo.Types[e].Type; typ != nil {
975
976
977 _, ok := typ.(*types.Signature)
978 return ok
979 }
980 return false
981 }
982
983
984
985
986 func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, formatArg int, state *formatState) bool {
987 argNum := state.argNums[formatArg]
988 if argNum <= 0 {
989
990 panic("negative arg num")
991 }
992 if argNum < len(call.Args)-1 {
993 return true
994 }
995 if call.Ellipsis.IsValid() {
996 return false
997 }
998 if argNum < len(call.Args) {
999 return true
1000 }
1001
1002
1003 arg := argNum - state.firstArg + 1
1004 pass.ReportRangef(call, "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg"))
1005 return false
1006 }
1007
1008
1009
1010
1011 var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE)
1012
1013 const (
1014 flagsRE = `[+\-#]*`
1015 indexOptRE = `(\[[0-9]+\])?`
1016 numOptRE = `([0-9]+|` + indexOptRE + `\*)?`
1017 verbRE = `[bcdefgopqstvxEFGTUX]`
1018 )
1019
1020
1021 func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
1022 firstArg := 0
1023 typ := pass.TypesInfo.Types[call.Fun].Type
1024 if typ == nil {
1025
1026 return
1027 }
1028 if sig, ok := typ.Underlying().(*types.Signature); ok {
1029 if !sig.Variadic() {
1030
1031 return
1032 }
1033 params := sig.Params()
1034 firstArg = params.Len() - 1
1035
1036 typ := params.At(firstArg).Type()
1037 typ = typ.(*types.Slice).Elem()
1038 it, ok := types.Unalias(typ).(*types.Interface)
1039 if !ok || !it.Empty() {
1040
1041 return
1042 }
1043 }
1044 args := call.Args
1045 if len(args) <= firstArg {
1046
1047 return
1048 }
1049 args = args[firstArg:]
1050
1051 if firstArg == 0 {
1052 if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
1053 if x, ok := sel.X.(*ast.Ident); ok {
1054 if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
1055 pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", fn.FullName(), analysisutil.Format(pass.Fset, call.Args[0]))
1056 }
1057 }
1058 }
1059 }
1060
1061 arg := args[0]
1062 if s, ok := stringConstantExpr(pass, arg); ok {
1063
1064
1065 s = strings.TrimSuffix(s, "%")
1066 if strings.Contains(s, "%") {
1067 m := printFormatRE.FindStringSubmatch(s)
1068 if m != nil {
1069 pass.ReportRangef(call, "%s call has possible Printf formatting directive %s", fn.FullName(), m[0])
1070 }
1071 }
1072 }
1073 if strings.HasSuffix(fn.Name(), "ln") {
1074
1075 arg = args[len(args)-1]
1076 if s, ok := stringConstantExpr(pass, arg); ok {
1077 if strings.HasSuffix(s, "\n") {
1078 pass.ReportRangef(call, "%s arg list ends with redundant newline", fn.FullName())
1079 }
1080 }
1081 }
1082 for _, arg := range args {
1083 if isFunctionValue(pass, arg) {
1084 pass.ReportRangef(call, "%s arg %s is a func value, not called", fn.FullName(), analysisutil.Format(pass.Fset, arg))
1085 }
1086 if methodName, ok := recursiveStringer(pass, arg); ok {
1087 pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", fn.FullName(), analysisutil.Format(pass.Fset, arg), methodName)
1088 }
1089 }
1090 }
1091
1092
1093
1094 func count(n int, what string) string {
1095 if n == 1 {
1096 return "1 " + what
1097 }
1098 return fmt.Sprintf("%d %ss", n, what)
1099 }
1100
1101
1102
1103 type stringSet map[string]bool
1104
1105 func (ss stringSet) String() string {
1106 var list []string
1107 for name := range ss {
1108 list = append(list, name)
1109 }
1110 sort.Strings(list)
1111 return strings.Join(list, ",")
1112 }
1113
1114 func (ss stringSet) Set(flag string) error {
1115 for _, name := range strings.Split(flag, ",") {
1116 if len(name) == 0 {
1117 return fmt.Errorf("empty string")
1118 }
1119 if !strings.Contains(name, ".") {
1120 name = strings.ToLower(name)
1121 }
1122 ss[name] = true
1123 }
1124 return nil
1125 }
1126
1127
1128
1129
1130
1131
1132
1133
1134 var suppressNonconstants bool
1135
View as plain text