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/analysis/passes/internal/analysisutil"
22 "golang.org/x/tools/go/ast/inspector"
23 "golang.org/x/tools/go/types/typeutil"
24 "golang.org/x/tools/internal/analysisinternal"
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/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.FullName())
433 case KindPrint:
434 checkPrint(pass, n, fn.FullName())
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 analysisinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") &&
494 types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
495 }
496
497
498 func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, name string) {
499 idx := formatStringIndex(pass, call)
500 if idx < 0 || idx >= len(call.Args) {
501 return
502 }
503 formatArg := call.Args[idx]
504 format, ok := stringConstantExpr(pass, formatArg)
505 if !ok {
506
507
508
509
510
511
512
513
514
515
516
517
518 if !suppressNonconstants &&
519 idx == len(call.Args)-1 &&
520 fileVersion != "" &&
521 versions.AtLeast(fileVersion, "go1.24") {
522
523 pass.Report(analysis.Diagnostic{
524 Pos: formatArg.Pos(),
525 End: formatArg.End(),
526 Message: fmt.Sprintf("non-constant format string in call to %s",
527 name),
528 SuggestedFixes: []analysis.SuggestedFix{{
529 Message: `Insert "%s" format string`,
530 TextEdits: []analysis.TextEdit{{
531 Pos: formatArg.Pos(),
532 End: formatArg.Pos(),
533 NewText: []byte(`"%s", `),
534 }},
535 }},
536 })
537 }
538 return
539 }
540
541 firstArg := idx + 1
542 if !strings.Contains(format, "%") {
543 if len(call.Args) > firstArg {
544 pass.ReportRangef(call.Args[firstArg], "%s call has arguments but no formatting directives", name)
545 }
546 return
547 }
548
549
550
551
552 operations, err := fmtstr.Parse(format, idx)
553 if err != nil {
554
555
556 pass.ReportRangef(formatArg, "%s %s", name, err)
557 return
558 }
559
560
561 maxArgIndex := firstArg - 1
562 anyIndex := false
563
564 for _, op := range operations {
565 if op.Prec.Index != -1 ||
566 op.Width.Index != -1 ||
567 op.Verb.Index != -1 {
568 anyIndex = true
569 }
570 rng := opRange(formatArg, op)
571 if !okPrintfArg(pass, call, rng, &maxArgIndex, firstArg, name, op) {
572
573 return
574 }
575 if op.Verb.Verb == 'w' {
576 switch kind {
577 case KindNone, KindPrint, KindPrintf:
578 pass.ReportRangef(rng, "%s does not support error-wrapping directive %%w", name)
579 return
580 }
581 }
582 }
583
584 if call.Ellipsis.IsValid() && maxArgIndex >= len(call.Args)-2 {
585 return
586 }
587
588 if anyIndex {
589 return
590 }
591
592 if maxArgIndex+1 < len(call.Args) {
593 expect := maxArgIndex + 1 - firstArg
594 numArgs := len(call.Args) - firstArg
595 pass.ReportRangef(call, "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg"))
596 }
597 }
598
599
600
601 func opRange(formatArg ast.Expr, op *fmtstr.Operation) analysis.Range {
602 if lit, ok := formatArg.(*ast.BasicLit); ok {
603 start, end, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
604 if err == nil {
605 return analysisinternal.Range(start, end)
606 }
607 }
608 return formatArg
609 }
610
611
612 type printfArgType int
613
614 const (
615 argBool printfArgType = 1 << iota
616 argInt
617 argRune
618 argString
619 argFloat
620 argComplex
621 argPointer
622 argError
623 anyType printfArgType = ^0
624 )
625
626 type printVerb struct {
627 verb rune
628 flags string
629 typ printfArgType
630 }
631
632
633 const (
634 noFlag = ""
635 numFlag = " -+.0"
636 sharpNumFlag = " -+.0#"
637 allFlags = " -+.0#"
638 )
639
640
641 var printVerbs = []printVerb{
642
643
644
645
646
647 {'%', noFlag, 0},
648 {'b', sharpNumFlag, argInt | argFloat | argComplex | argPointer},
649 {'c', "-", argRune | argInt},
650 {'d', numFlag, argInt | argPointer},
651 {'e', sharpNumFlag, argFloat | argComplex},
652 {'E', sharpNumFlag, argFloat | argComplex},
653 {'f', sharpNumFlag, argFloat | argComplex},
654 {'F', sharpNumFlag, argFloat | argComplex},
655 {'g', sharpNumFlag, argFloat | argComplex},
656 {'G', sharpNumFlag, argFloat | argComplex},
657 {'o', sharpNumFlag, argInt | argPointer},
658 {'O', sharpNumFlag, argInt | argPointer},
659 {'p', "-#", argPointer},
660 {'q', " -+.0#", argRune | argInt | argString},
661 {'s', " -+.0", argString},
662 {'t', "-", argBool},
663 {'T', "-", anyType},
664 {'U', "-#", argRune | argInt},
665 {'v', allFlags, anyType},
666 {'w', allFlags, argError},
667 {'x', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
668 {'X', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
669 }
670
671
672
673
674 func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
675 verb := operation.Verb.Verb
676 var v printVerb
677 found := false
678
679 for _, v = range printVerbs {
680 if v.verb == verb {
681 found = true
682 break
683 }
684 }
685
686
687
688 formatter := false
689 if v.typ != argError && operation.Verb.ArgIndex < len(call.Args) {
690 if tv, ok := pass.TypesInfo.Types[call.Args[operation.Verb.ArgIndex]]; ok {
691 formatter = isFormatter(tv.Type)
692 }
693 }
694
695 if !formatter {
696 if !found {
697 pass.ReportRangef(rng, "%s format %s has unknown verb %c", name, operation.Text, verb)
698 return false
699 }
700 for _, flag := range operation.Flags {
701
702
703 if flag == '0' {
704 continue
705 }
706 if !strings.ContainsRune(v.flags, rune(flag)) {
707 pass.ReportRangef(rng, "%s format %s has unrecognized flag %c", name, operation.Text, flag)
708 return false
709 }
710 }
711 }
712
713 var argIndexes []int
714
715 if operation.Width.Dynamic != -1 {
716 argIndexes = append(argIndexes, operation.Width.Dynamic)
717 }
718 if operation.Prec.Dynamic != -1 {
719 argIndexes = append(argIndexes, operation.Prec.Dynamic)
720 }
721
722
723 for _, argIndex := range argIndexes {
724 if !argCanBeChecked(pass, call, rng, argIndex, firstArg, operation, name) {
725 return
726 }
727 arg := call.Args[argIndex]
728 if reason, ok := matchArgType(pass, argInt, arg); !ok {
729 details := ""
730 if reason != "" {
731 details = " (" + reason + ")"
732 }
733 pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, analysisinternal.Format(pass.Fset, arg), details)
734 return false
735 }
736 }
737
738
739 if operation.Verb.ArgIndex != -1 && verb != '%' {
740 argIndexes = append(argIndexes, operation.Verb.ArgIndex)
741 }
742 for _, index := range argIndexes {
743 *maxArgIndex = max(*maxArgIndex, index)
744 }
745
746
747
748
749 if verb == '%' || formatter {
750 return true
751 }
752
753
754 verbArgIndex := operation.Verb.ArgIndex
755 if !argCanBeChecked(pass, call, rng, verbArgIndex, firstArg, operation, name) {
756 return false
757 }
758 arg := call.Args[verbArgIndex]
759 if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
760 pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, analysisinternal.Format(pass.Fset, arg))
761 return false
762 }
763 if reason, ok := matchArgType(pass, v.typ, arg); !ok {
764 typeString := ""
765 if typ := pass.TypesInfo.Types[arg].Type; typ != nil {
766 typeString = typ.String()
767 }
768 details := ""
769 if reason != "" {
770 details = " (" + reason + ")"
771 }
772 pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, analysisinternal.Format(pass.Fset, arg), typeString, details)
773 return false
774 }
775
776
777 if v.typ&argString != 0 && v.verb != 'T' && (!strings.Contains(operation.Flags, "#") || strings.ContainsRune("qxX", v.verb)) {
778 if methodName, ok := recursiveStringer(pass, arg); ok {
779 pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, analysisinternal.Format(pass.Fset, arg), methodName)
780 return false
781 }
782 }
783 return true
784 }
785
786
787
788
789
790
791
792 func recursiveStringer(pass *analysis.Pass, e ast.Expr) (string, bool) {
793 typ := pass.TypesInfo.Types[e].Type
794
795
796 if isFormatter(typ) {
797 return "", false
798 }
799
800
801 strObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "String")
802 strMethod, strOk := strObj.(*types.Func)
803 errObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "Error")
804 errMethod, errOk := errObj.(*types.Func)
805 if !strOk && !errOk {
806 return "", false
807 }
808
809
810 inScope := func(e ast.Expr, f *types.Func) bool {
811 return f.Scope() != nil && f.Scope().Contains(e.Pos())
812 }
813
814
815 var method *types.Func
816 if strOk && strMethod.Pkg() == pass.Pkg && inScope(e, strMethod) {
817 method = strMethod
818 } else if errOk && errMethod.Pkg() == pass.Pkg && inScope(e, errMethod) {
819 method = errMethod
820 } else {
821 return "", false
822 }
823
824 sig := method.Type().(*types.Signature)
825 if !isStringer(sig) {
826 return "", false
827 }
828
829
830 if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
831 e = u.X
832 }
833 if id, ok := e.(*ast.Ident); ok {
834 if pass.TypesInfo.Uses[id] == sig.Recv() {
835 return method.FullName(), true
836 }
837 }
838 return "", false
839 }
840
841
842 func isStringer(sig *types.Signature) bool {
843 return sig.Params().Len() == 0 &&
844 sig.Results().Len() == 1 &&
845 sig.Results().At(0).Type() == types.Typ[types.String]
846 }
847
848
849
850 func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
851 if typ := pass.TypesInfo.Types[e].Type; typ != nil {
852
853
854 _, ok := typ.(*types.Signature)
855 return ok
856 }
857 return false
858 }
859
860
861
862
863 func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, argIndex, firstArg int, operation *fmtstr.Operation, name string) bool {
864 if argIndex <= 0 {
865
866 panic("negative argIndex")
867 }
868 if argIndex < len(call.Args)-1 {
869 return true
870 }
871 if call.Ellipsis.IsValid() {
872 return false
873 }
874 if argIndex < len(call.Args) {
875 return true
876 }
877
878
879 arg := argIndex - firstArg + 1
880 pass.ReportRangef(rng, "%s format %s reads arg #%d, but call has %v", name, operation.Text, arg, count(len(call.Args)-firstArg, "arg"))
881 return false
882 }
883
884
885
886
887 var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE)
888
889 const (
890 flagsRE = `[+\-#]*`
891 indexOptRE = `(\[[0-9]+\])?`
892 numOptRE = `([0-9]+|` + indexOptRE + `\*)?`
893 verbRE = `[bcdefgopqstvxEFGTUX]`
894 )
895
896
897 func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
898 firstArg := 0
899 typ := pass.TypesInfo.Types[call.Fun].Type
900 if typ == nil {
901
902 return
903 }
904 if sig, ok := typ.Underlying().(*types.Signature); ok {
905 if !sig.Variadic() {
906
907 return
908 }
909 params := sig.Params()
910 firstArg = params.Len() - 1
911
912 typ := params.At(firstArg).Type()
913 typ = typ.(*types.Slice).Elem()
914 it, ok := types.Unalias(typ).(*types.Interface)
915 if !ok || !it.Empty() {
916
917 return
918 }
919 }
920 args := call.Args
921 if len(args) <= firstArg {
922
923 return
924 }
925 args = args[firstArg:]
926
927 if firstArg == 0 {
928 if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
929 if x, ok := sel.X.(*ast.Ident); ok {
930 if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
931 pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, analysisinternal.Format(pass.Fset, call.Args[0]))
932 }
933 }
934 }
935 }
936
937 arg := args[0]
938 if s, ok := stringConstantExpr(pass, arg); ok {
939
940
941 s = strings.TrimSuffix(s, "%")
942 if strings.Contains(s, "%") {
943 for _, m := range printFormatRE.FindAllString(s, -1) {
944
945
946 if len(m) >= 3 && isHex(m[1]) && isHex(m[2]) {
947 continue
948 }
949 pass.ReportRangef(call, "%s call has possible Printf formatting directive %s", name, m)
950 break
951 }
952 }
953 }
954 if strings.HasSuffix(name, "ln") {
955
956 arg = args[len(args)-1]
957 if s, ok := stringConstantExpr(pass, arg); ok {
958 if strings.HasSuffix(s, "\n") {
959 pass.ReportRangef(call, "%s arg list ends with redundant newline", name)
960 }
961 }
962 }
963 for _, arg := range args {
964 if isFunctionValue(pass, arg) {
965 pass.ReportRangef(call, "%s arg %s is a func value, not called", name, analysisinternal.Format(pass.Fset, arg))
966 }
967 if methodName, ok := recursiveStringer(pass, arg); ok {
968 pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, analysisinternal.Format(pass.Fset, arg), methodName)
969 }
970 }
971 }
972
973
974
975 func count(n int, what string) string {
976 if n == 1 {
977 return "1 " + what
978 }
979 return fmt.Sprintf("%d %ss", n, what)
980 }
981
982
983
984 type stringSet map[string]bool
985
986 func (ss stringSet) String() string {
987 var list []string
988 for name := range ss {
989 list = append(list, name)
990 }
991 sort.Strings(list)
992 return strings.Join(list, ",")
993 }
994
995 func (ss stringSet) Set(flag string) error {
996 for _, name := range strings.Split(flag, ",") {
997 if len(name) == 0 {
998 return fmt.Errorf("empty string")
999 }
1000 if !strings.Contains(name, ".") {
1001 name = strings.ToLower(name)
1002 }
1003 ss[name] = true
1004 }
1005 return nil
1006 }
1007
1008
1009
1010
1011
1012
1013
1014
1015 var suppressNonconstants bool
1016
1017
1018 func isHex(b byte) bool {
1019 return '0' <= b && b <= '9' ||
1020 'A' <= b && b <= 'F' ||
1021 'a' <= b && b <= 'f'
1022 }
1023
View as plain text