Skip to content

Commit 531b58c

Browse files
committed
Add main.go
1 parent cfdd15e commit 531b58c

File tree

2 files changed

+120
-26
lines changed

2 files changed

+120
-26
lines changed

go/analysis/passes/printf/printf.go

+26-26
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,31 @@ var isPrint = stringSet{
370370
"(testing.TB).Skipf": true,
371371
}
372372

373+
// formatStringIndex returns the index of the format string (the last
374+
// non-variadic parameter) within the given printf-like call
375+
// expression, or -1 if unknown.
376+
func formatStringIndex(pass *analysis.Pass, call *ast.CallExpr) int {
377+
typ := pass.TypesInfo.Types[call.Fun].Type
378+
if typ == nil {
379+
return -1 // missing type
380+
}
381+
sig, ok := typ.(*types.Signature)
382+
if !ok {
383+
return -1 // ill-typed
384+
}
385+
if !sig.Variadic() {
386+
// Skip checking non-variadic functions.
387+
return -1
388+
}
389+
idx := sig.Params().Len() - 2
390+
if idx < 0 {
391+
// Skip checking variadic functions without
392+
// fixed arguments.
393+
return -1
394+
}
395+
return idx
396+
}
397+
373398
// stringConstantExpr returns expression's string constant value.
374399
//
375400
// ("", false) is returned if expression isn't a string
@@ -457,34 +482,9 @@ func isFormatter(typ types.Type) bool {
457482
types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
458483
}
459484

460-
// formatStringIndex returns the index of the format string (the last
461-
// non-variadic parameter) within the given printf-like call
462-
// expression, or -1 if unknown.
463-
func formatStringIndex(info *types.Info, call *ast.CallExpr) int {
464-
typ := info.Types[call.Fun].Type
465-
if typ == nil {
466-
return -1 // missing type
467-
}
468-
sig, ok := typ.(*types.Signature)
469-
if !ok {
470-
return -1 // ill-typed
471-
}
472-
if !sig.Variadic() {
473-
// Skip checking non-variadic functions.
474-
return -1
475-
}
476-
idx := sig.Params().Len() - 2
477-
if idx < 0 {
478-
// Skip checking variadic functions without
479-
// fixed arguments.
480-
return -1
481-
}
482-
return idx
483-
}
484-
485485
// checkPrintf checks a call to a formatted print routine such as Printf.
486486
func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string) {
487-
idx := formatStringIndex(pass.TypesInfo, call)
487+
idx := formatStringIndex(pass, call)
488488
if idx < 0 || idx >= len(call.Args) {
489489
return
490490
}

internal/fmtstr/main.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2024 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+
//go:build ignore
6+
7+
// The fmtstr command parses the format strings of calls to selected
8+
// printf-like functions in the specified source file, and prints the
9+
// formatting operations and their operands.
10+
//
11+
// It is intended only for debugging and is not a supported interface.
12+
package main
13+
14+
import (
15+
"flag"
16+
"fmt"
17+
"go/ast"
18+
"go/parser"
19+
"go/printer"
20+
"go/token"
21+
"log"
22+
"strconv"
23+
"strings"
24+
25+
"golang.org/x/tools/internal/fmtstr"
26+
)
27+
28+
func main() {
29+
log.SetPrefix("fmtstr: ")
30+
log.SetFlags(0)
31+
flag.Parse()
32+
33+
fset := token.NewFileSet()
34+
f, err := parser.ParseFile(fset, flag.Args()[0], nil, 0)
35+
if err != nil {
36+
log.Fatal(err)
37+
}
38+
39+
functions := map[string]int{
40+
"fmt.Errorf": 0,
41+
"fmt.Fprintf": 1,
42+
"fmt.Printf": 0,
43+
"fmt.Sprintf": 0,
44+
"log.Printf": 0,
45+
}
46+
47+
ast.Inspect(f, func(n ast.Node) bool {
48+
if call, ok := n.(*ast.CallExpr); ok && !call.Ellipsis.IsValid() {
49+
if sel, ok := call.Fun.(*ast.SelectorExpr); ok && is[*ast.Ident](sel.X) {
50+
name := sel.X.(*ast.Ident).Name + "." + sel.Sel.Name // e.g. "fmt.Printf"
51+
if fmtstrIndex, ok := functions[name]; ok &&
52+
len(call.Args) > fmtstrIndex {
53+
// Is it a string literal?
54+
if fmtstrArg, ok := call.Args[fmtstrIndex].(*ast.BasicLit); ok &&
55+
fmtstrArg.Kind == token.STRING {
56+
// Have fmt.Printf("format", ...)
57+
format, _ := strconv.Unquote(fmtstrArg.Value)
58+
59+
ops, err := fmtstr.Parse(format, 0)
60+
if err != nil {
61+
log.Printf("%s: %v", fset.Position(fmtstrArg.Pos()), err)
62+
return true
63+
}
64+
65+
fmt.Printf("%s: %s(%s, ...)\n",
66+
fset.Position(fmtstrArg.Pos()),
67+
name,
68+
fmtstrArg.Value)
69+
for _, op := range ops {
70+
// TODO(adonovan): show more detail.
71+
fmt.Printf("\t%q\t%v\n",
72+
op.Text,
73+
formatNode(fset, call.Args[op.Verb.ArgIndex]))
74+
}
75+
}
76+
}
77+
}
78+
}
79+
return true
80+
})
81+
}
82+
83+
func is[T any](x any) bool {
84+
_, ok := x.(T)
85+
return ok
86+
}
87+
88+
func formatNode(fset *token.FileSet, n ast.Node) string {
89+
var buf strings.Builder
90+
if err := printer.Fprint(&buf, fset, n); err != nil {
91+
return "<error>"
92+
}
93+
return buf.String()
94+
}

0 commit comments

Comments
 (0)