From 8b4d49324bd596bc9ce0cc71bd07dadd286dd3d4 Mon Sep 17 00:00:00 2001 From: Aaron Beitch Date: Mon, 15 Jul 2019 12:30:53 -0700 Subject: [PATCH] go/analysis/passes/printf: return Result with IsPrint helper printf.Result has IsPrint function telling the caller if a function is a Print/Printf function or a wrapper of one. This aids in developing Ananlyzer's applying checks on Print/Printf functions. Implements @alandonovan suggestion from https://github.com/golang/go/issues/29616#issuecomment-503277625 Change-Id: I57833d5c65c417f2e4a761d9187fc133937f39fb --- go/analysis/passes/printf/printf.go | 98 +++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/go/analysis/passes/printf/printf.go b/go/analysis/passes/printf/printf.go index f59e95dc219..e1595da4a9d 100644 --- a/go/analysis/passes/printf/printf.go +++ b/go/analysis/passes/printf/printf.go @@ -13,6 +13,7 @@ import ( "go/constant" "go/token" "go/types" + "reflect" "regexp" "sort" "strconv" @@ -31,11 +32,12 @@ func init() { } var Analyzer = &analysis.Analyzer{ - Name: "printf", - Doc: doc, - Requires: []*analysis.Analyzer{inspect.Analyzer}, - Run: run, - FactTypes: []analysis.Fact{new(isWrapper)}, + Name: "printf", + Doc: doc, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, + ResultType: reflect.TypeOf((*Result)(nil)), + FactTypes: []analysis.Fact{new(isWrapper)}, } const doc = `check consistency of Printf format strings and arguments @@ -66,6 +68,49 @@ argument list. Otherwise it is assumed to be Print-like, taking a list of arguments with no format string. ` +// Kind is a kind of fmt function behavior +type Kind int + +const ( + KindNone Kind = iota // not a fmt wrapper function + KindPrint // function behaves like fmt.Print + KindPrintf // function behaves like fmt.Printf +) + +func (kind Kind) String() string { + switch kind { + case KindPrint: + return "print" + case KindPrintf: + return "printf" + } + return "" +} + +// Result is the printf analyzer's result type. Clients may query the +// result to learn whether a function is a print wrapper. +type Result struct { + funcs map[*types.Func]Kind +} + +// Kind reports whether fn is a wrapper around fmt.Print or fmt.Printf. +func (r *Result) Kind(fn *types.Func) Kind { + _, ok := isPrint[fn.FullName()] + if !ok { + // Next look up just "printf", for use with -printf.funcs. + _, ok = isPrint[strings.ToLower(fn.Name())] + } + if ok { + if strings.HasSuffix(fn.Name(), "f") { + return KindPrintf + } else { + return KindPrint + } + } + + return r.funcs[fn] +} + // isWrapper is a fact indicating that a function is a print or printf wrapper. type isWrapper struct{ Printf bool } @@ -80,9 +125,12 @@ func (f *isWrapper) String() string { } func run(pass *analysis.Pass) (interface{}, error) { - findPrintfLike(pass) + res := &Result{ + funcs: make(map[*types.Func]Kind), + } + findPrintfLike(pass, res) checkCall(pass) - return nil, nil + return res, nil } type printfWrapper struct { @@ -145,7 +193,7 @@ func maybePrintfWrapper(info *types.Info, decl ast.Decl) *printfWrapper { } // findPrintfLike scans the entire package to find printf-like functions. -func findPrintfLike(pass *analysis.Pass) (interface{}, error) { +func findPrintfLike(pass *analysis.Pass, res *Result) (interface{}, error) { // Gather potential wrappers and call graph between them. byObj := make(map[*types.Func]*printfWrapper) var wrappers []*printfWrapper @@ -200,7 +248,7 @@ func findPrintfLike(pass *analysis.Pass) (interface{}, error) { fn, kind := printfNameAndKind(pass, call) if kind != 0 { - checkPrintfFwd(pass, w, call, kind) + checkPrintfFwd(pass, w, call, kind, res) return true } @@ -223,16 +271,11 @@ func match(info *types.Info, arg ast.Expr, param *types.Var) bool { return ok && info.ObjectOf(id) == param } -const ( - kindPrintf = 1 - kindPrint = 2 -) - // checkPrintfFwd checks that a printf-forwarding wrapper is forwarding correctly. // It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...). -func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind int) { - matched := kind == kindPrint || - kind == kindPrintf && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) +func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind Kind, res *Result) { + matched := kind == KindPrint || + kind == KindPrintf && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) if !matched { return } @@ -253,7 +296,7 @@ func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, k return } desc := "printf" - if kind == kindPrint { + if kind == KindPrint { desc = "print" } pass.Reportf(call.Pos(), "missing ... in args forwarded to %s-like function", desc) @@ -262,10 +305,11 @@ func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, k fn := w.obj var fact isWrapper if !pass.ImportObjectFact(fn, &fact) { - fact.Printf = kind == kindPrintf + fact.Printf = kind == KindPrintf pass.ExportObjectFact(fn, &fact) + res.funcs[fn] = kind for _, caller := range w.callers { - checkPrintfFwd(pass, caller.w, caller.call, kind) + checkPrintfFwd(pass, caller.w, caller.call, kind, res) } } } @@ -414,15 +458,15 @@ func checkCall(pass *analysis.Pass) { call := n.(*ast.CallExpr) fn, kind := printfNameAndKind(pass, call) switch kind { - case kindPrintf: + case KindPrintf: checkPrintf(pass, call, fn) - case kindPrint: + case KindPrint: checkPrint(pass, call, fn) } }) } -func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind int) { +func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind Kind) { fn, _ = typeutil.Callee(pass.TypesInfo, call).(*types.Func) if fn == nil { return nil, 0 @@ -431,9 +475,9 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, var fact isWrapper if pass.ImportObjectFact(fn, &fact) { if fact.Printf { - return fn, kindPrintf + return fn, KindPrintf } else { - return fn, kindPrint + return fn, KindPrint } } @@ -444,9 +488,9 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, } if ok { if strings.HasSuffix(fn.Name(), "f") { - kind = kindPrintf + kind = KindPrintf } else { - kind = kindPrint + kind = KindPrint } } return fn, kind