src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

callcheck.go (3658B)


      1 // Package callcheck provides a framework for validating arguments in function calls.
      2 package callcheck
      3 
      4 import (
      5 	"fmt"
      6 	"go/ast"
      7 	"go/constant"
      8 	"go/types"
      9 
     10 	"golang.org/x/tools/go/analysis"
     11 	"honnef.co/go/tools/analysis/report"
     12 	"honnef.co/go/tools/go/ir"
     13 	"honnef.co/go/tools/go/ir/irutil"
     14 	"honnef.co/go/tools/go/types/typeutil"
     15 	"honnef.co/go/tools/internal/passes/buildir"
     16 )
     17 
     18 type Call struct {
     19 	Pass  *analysis.Pass
     20 	Instr ir.CallInstruction
     21 	Args  []*Argument
     22 
     23 	Parent *ir.Function
     24 
     25 	invalids []string
     26 }
     27 
     28 func (c *Call) Invalid(msg string) {
     29 	c.invalids = append(c.invalids, msg)
     30 }
     31 
     32 type Argument struct {
     33 	Value    Value
     34 	invalids []string
     35 }
     36 
     37 type Value struct {
     38 	Value ir.Value
     39 }
     40 
     41 func (arg *Argument) Invalid(msg string) {
     42 	arg.invalids = append(arg.invalids, msg)
     43 }
     44 
     45 type Check func(call *Call)
     46 
     47 func Analyzer(rules map[string]Check) func(pass *analysis.Pass) (any, error) {
     48 	return func(pass *analysis.Pass) (any, error) {
     49 		return checkCalls(pass, rules)
     50 	}
     51 }
     52 
     53 func checkCalls(pass *analysis.Pass, rules map[string]Check) (any, error) {
     54 	cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
     55 		obj, ok := callee.Object().(*types.Func)
     56 		if !ok {
     57 			return
     58 		}
     59 
     60 		r, ok := rules[typeutil.FuncName(obj)]
     61 		if !ok {
     62 			return
     63 		}
     64 		var args []*Argument
     65 		irargs := site.Common().Args
     66 		if callee.Signature.Recv() != nil {
     67 			irargs = irargs[1:]
     68 		}
     69 		for _, arg := range irargs {
     70 			if iarg, ok := arg.(*ir.MakeInterface); ok {
     71 				arg = iarg.X
     72 			}
     73 			args = append(args, &Argument{Value: Value{arg}})
     74 		}
     75 		call := &Call{
     76 			Pass:   pass,
     77 			Instr:  site,
     78 			Args:   args,
     79 			Parent: site.Parent(),
     80 		}
     81 		r(call)
     82 
     83 		var astcall *ast.CallExpr
     84 		switch source := site.Source().(type) {
     85 		case *ast.CallExpr:
     86 			astcall = source
     87 		case *ast.DeferStmt:
     88 			astcall = source.Call
     89 		case *ast.GoStmt:
     90 			astcall = source.Call
     91 		case nil:
     92 			// TODO(dh): I am not sure this can actually happen. If it
     93 			// can't, we should remove this case, and also stop
     94 			// checking for astcall == nil in the code that follows.
     95 		default:
     96 			panic(fmt.Sprintf("unhandled case %T", source))
     97 		}
     98 
     99 		for idx, arg := range call.Args {
    100 			for _, e := range arg.invalids {
    101 				if astcall != nil {
    102 					if idx < len(astcall.Args) {
    103 						report.Report(pass, astcall.Args[idx], e)
    104 					} else {
    105 						// this is an instance of fn1(fn2()) where fn2
    106 						// returns multiple values. Report the error
    107 						// at the next-best position that we have, the
    108 						// first argument. An example of a check that
    109 						// triggers this is checkEncodingBinaryRules.
    110 						report.Report(pass, astcall.Args[0], e)
    111 					}
    112 				} else {
    113 					report.Report(pass, site, e)
    114 				}
    115 			}
    116 		}
    117 		for _, e := range call.invalids {
    118 			report.Report(pass, call.Instr, e)
    119 		}
    120 	}
    121 	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
    122 		eachCall(fn, cb)
    123 	}
    124 	return nil, nil
    125 }
    126 
    127 func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
    128 	for _, b := range fn.Blocks {
    129 		for _, instr := range b.Instrs {
    130 			if site, ok := instr.(ir.CallInstruction); ok {
    131 				if g := site.Common().StaticCallee(); g != nil {
    132 					cb(fn, site, g)
    133 				}
    134 			}
    135 		}
    136 	}
    137 }
    138 
    139 func ExtractConstExpectKind(v Value, kind constant.Kind) *ir.Const {
    140 	k := extractConst(v.Value)
    141 	if k == nil || k.Value == nil || k.Value.Kind() != kind {
    142 		return nil
    143 	}
    144 	return k
    145 }
    146 
    147 func ExtractConst(v Value) *ir.Const {
    148 	return extractConst(v.Value)
    149 }
    150 
    151 func extractConst(v ir.Value) *ir.Const {
    152 	v = irutil.Flatten(v)
    153 	switch v := v.(type) {
    154 	case *ir.Const:
    155 		return v
    156 	case *ir.MakeInterface:
    157 		return extractConst(v.X)
    158 	default:
    159 		return nil
    160 	}
    161 }