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