diff --git a/contextcheck.go b/contextcheck.go index 6747d46..008914f 100644 --- a/contextcheck.go +++ b/contextcheck.go @@ -2,7 +2,6 @@ package contextcheck import ( "go/ast" - "go/token" "go/types" "strconv" "strings" @@ -29,14 +28,27 @@ func NewAnalyzer() *analysis.Analyzer { const ( ctxPkg = "context" ctxName = "Context" + + httpPkg = "net/http" + httpRes = "ResponseWriter" + httpReq = "Request" ) const ( CtxIn int = 1 << iota // ctx in function's param CtxOut // ctx in function's results CtxInField // ctx in function's field param + HttpRes // http.ResponseWriter in function's param + HttpReq // *http.Request in function's param + + HttpHandler = HttpRes | HttpReq +) + +const ( + EntryWithCtx int = 1 << iota // has ctx in + EntryWithHttpHandler // is http handler - CtxInOut = CtxIn | CtxOut + Entry = EntryWithCtx | EntryWithHttpHandler ) type resInfo struct { @@ -53,9 +65,11 @@ type runner struct { pass *analysis.Pass ctxTyp *types.Named ctxPTyp *types.Pointer - cmpPath string skipFile map[*ast.File]bool + httpResTyps []types.Type + httpReqTyps []types.Type + currentFact ctxFact } @@ -77,33 +91,27 @@ func NewRun(pkgs []*packages.Package) func(pass *analysis.Pass) (interface{}, er func (r *runner) run(pass *analysis.Pass) { r.pass = pass - r.cmpPath = strings.Split(pass.Pkg.Path(), "/")[0] pssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) funcs := pssa.SrcFuncs - name := pass.Pkg.Path() - _ = name - - pkg := pssa.Pkg.Prog.ImportedPackage(ctxPkg) - if pkg == nil { - return - } - ctxType := pkg.Type(ctxName) - if ctxType == nil { + // collect ctx obj + var ok bool + r.ctxTyp, r.ctxPTyp, ok = r.getRequiedType(pssa, ctxPkg, ctxName) + if !ok { return } - if resNamed, ok := ctxType.Object().Type().(*types.Named); !ok { - return - } else { - r.ctxTyp = resNamed - r.ctxPTyp = types.NewPointer(resNamed) - } + // collect http obj + r.collectHttpTyps(pssa) r.skipFile = make(map[*ast.File]bool) r.currentFact = make(ctxFact) - var tmpFuncs []*ssa.Function + type entryInfo struct { + f *ssa.Function // entryfunc + tp int // entrytype + } + var tmpFuncs []entryInfo for _, f := range funcs { // skip checked function key := f.RelString(nil) @@ -111,19 +119,19 @@ func (r *runner) run(pass *analysis.Pass) { continue } - if !r.checkIsEntry(f, f.Pos()) { + if entryType := r.checkIsEntry(f); entryType&Entry == 0 { // record the result of nomal function checkingMap := make(map[string]bool) checkingMap[key] = true r.setFact(key, r.checkFuncWithoutCtx(f, checkingMap), f.Name()) continue + } else { + tmpFuncs = append(tmpFuncs, entryInfo{f: f, tp: entryType}) } - - tmpFuncs = append(tmpFuncs, f) } - for _, f := range tmpFuncs { - r.checkFuncWithCtx(f) + for _, v := range tmpFuncs { + r.checkFuncWithCtx(v.f, v.tp) } if len(r.currentFact) > 0 { @@ -131,7 +139,38 @@ func (r *runner) run(pass *analysis.Pass) { } } -func (r *runner) noImportedContext(f *ssa.Function) (ret bool) { +func (r *runner) getRequiedType(pssa *buildssa.SSA, path, name string) (obj *types.Named, pobj *types.Pointer, ok bool) { + pkg := pssa.Pkg.Prog.ImportedPackage(path) + if pkg == nil { + return + } + + objTyp := pkg.Type(name) + if objTyp == nil { + return + } + obj, ok = objTyp.Object().Type().(*types.Named) + if !ok { + return + } + pobj = types.NewPointer(obj) + + return +} + +func (r *runner) collectHttpTyps(pssa *buildssa.SSA) { + objRes, pobjRes, ok := r.getRequiedType(pssa, httpPkg, httpRes) + if ok { + r.httpResTyps = append(r.httpResTyps, objRes, pobjRes, types.NewPointer(pobjRes)) + } + + objReq, pobjReq, ok := r.getRequiedType(pssa, httpPkg, httpReq) + if ok { + r.httpReqTyps = append(r.httpReqTyps, objReq, pobjReq, types.NewPointer(pobjReq)) + } +} + +func (r *runner) noImportedContextAndHttp(f *ssa.Function) (ret bool) { if !f.Pos().IsValid() { return false } @@ -154,7 +193,7 @@ func (r *runner) noImportedContext(f *ssa.Function) (ret bool) { continue } path = analysisutil.RemoveVendor(path) - if path == ctxPkg { + if path == ctxPkg || path == httpPkg { return false } } @@ -162,16 +201,35 @@ func (r *runner) noImportedContext(f *ssa.Function) (ret bool) { return true } -func (r *runner) checkIsEntry(f *ssa.Function, pos token.Pos) (ret bool) { - if r.noImportedContext(f) { - return false +func (r *runner) checkIsEntry(f *ssa.Function) (entryType int) { + if r.noImportedContextAndHttp(f) { + return } + ctxIn, ctxOut := r.checkIsCtx(f) + if ctxOut { + // skip the function which generate ctx + return + } else if ctxIn { + // has ctx in, ignore *http.Request.Context() + entryType |= EntryWithCtx + return + } + + // check is `func handler(w http.ResponseWriter, r *http.Request) {}` + if r.checkIsHttpHandler(f) { + entryType |= EntryWithHttpHandler + } + + return +} + +func (r *runner) checkIsCtx(f *ssa.Function) (in, out bool) { // check params tuple := f.Signature.Params() for i := 0; i < tuple.Len(); i++ { if r.isCtxType(tuple.At(i).Type()) { - ret = true + in = true break } } @@ -179,7 +237,7 @@ func (r *runner) checkIsEntry(f *ssa.Function, pos token.Pos) (ret bool) { // check freevars for _, param := range f.FreeVars { if r.isCtxType(param.Type()) { - ret = true + in = true break } } @@ -187,17 +245,56 @@ func (r *runner) checkIsEntry(f *ssa.Function, pos token.Pos) (ret bool) { // check results tuple = f.Signature.Results() for i := 0; i < tuple.Len(); i++ { - // skip the function which generate ctx if r.isCtxType(tuple.At(i).Type()) { - ret = false + out = true break } } - return } -func (r *runner) collectCtxRef(f *ssa.Function) (refMap map[ssa.Instruction]bool, ok bool) { +func (r *runner) checkIsHttpHandler(f *ssa.Function) bool { + // must has no result + if f.Signature.Results().Len() > 0 { + return false + } + + // must has http.ResponseWriter and *http.Request in param or freevar + var tp int + + // check params + tuple := f.Signature.Params() + for i := 0; i < tuple.Len(); i++ { + if r.isCtxType(tuple.At(i).Type()) { + return false + } else if r.isHttpReqType(tuple.At(i).Type()) { + tp |= HttpReq + } else if r.isHttpResType(tuple.At(i).Type()) { + tp |= HttpRes + } + if tp == HttpHandler { + return true + } + } + + // check freevars + for _, param := range f.FreeVars { + if r.isCtxType(param.Type()) { + return false + } else if r.isHttpReqType(param.Type()) { + tp |= HttpReq + } else if r.isHttpResType(param.Type()) { + tp |= HttpRes + } + if tp == HttpHandler { + return true + } + } + + return false +} + +func (r *runner) collectCtxRef(f *ssa.Function, isHttpHandler bool) (refMap map[ssa.Instruction]bool, ok bool) { ok = true refMap = make(map[ssa.Instruction]bool) checkedRefMap := make(map[ssa.Value]bool) @@ -289,15 +386,98 @@ func (r *runner) collectCtxRef(f *ssa.Function) (refMap map[ssa.Instruction]bool } } + if !isHttpHandler { + return + } + + for _, v := range r.getHttpReqCtx(f) { + checkRefs(v, false) + } + return } -func (r *runner) checkIsSameRepo(s string) bool { - return strings.HasPrefix(s, r.cmpPath+"/") +func (r *runner) getHttpReqCtx(f *ssa.Function) (rets []ssa.Value) { + checkedRefMap := make(map[ssa.Value]bool) + + var checkRefs func(val ssa.Value, fromAddr bool) + var checkInstr func(instr ssa.Instruction, fromAddr bool) + + checkRefs = func(val ssa.Value, fromAddr bool) { + if val == nil || val.Referrers() == nil { + return + } + + if checkedRefMap[val] { + return + } + checkedRefMap[val] = true + + for _, instr := range *val.Referrers() { + checkInstr(instr, fromAddr) + } + } + + checkInstr = func(instr ssa.Instruction, fromAddr bool) { + switch i := instr.(type) { + case ssa.CallInstruction: + // find r.Context() + if r.getCallInstrCtxType(i)&CtxOut != CtxOut { + break + } + + for _, v := range i.Common().Args { + if !r.isHttpReqType(v.Type()) { + continue + } + + f := r.getFunction(instr) + if f == nil { + continue + } + + // check is r.Context + if f.Signature.Recv() != nil && r.isHttpReqType(f.Signature.Recv().Type()) && f.Name() == ctxName { + // collect the return of r.Context + rets = append(rets, i.Value()) + } + } + case *ssa.Store: + if !fromAddr { + checkRefs(i.Addr, true) + } + case *ssa.UnOp: + if r.isHttpReqType(i.Type()) { + checkRefs(i, false) + } + case *ssa.MakeClosure: + case *ssa.Phi: + if r.isHttpReqType(i.Type()) { + checkRefs(i, false) + } + case *ssa.Extract: + // http.Request can only be input + } + } + + for _, param := range f.Params { + if r.isHttpReqType(param.Type()) { + checkRefs(param, false) + } + } + + for _, param := range f.FreeVars { + if r.isHttpReqType(param.Type()) { + checkRefs(param, false) + } + } + + return } -func (r *runner) checkFuncWithCtx(f *ssa.Function) { - refMap, ok := r.collectCtxRef(f) +func (r *runner) checkFuncWithCtx(f *ssa.Function, tp int) { + isHttpHandler := tp&EntryWithHttpHandler != 0 + refMap, ok := r.collectCtxRef(f, isHttpHandler) if !ok { return } @@ -316,10 +496,15 @@ func (r *runner) checkFuncWithCtx(f *ssa.Function) { if tp&CtxIn != 0 { if !refMap[instr] { - r.pass.Reportf(instr.Pos(), "Non-inherited new context, use function like `context.WithXXX` instead") + r.pass.Reportf(instr.Pos(), "Non-inherited new context, use function like `context.WithXXX` or `r.Context` instead") } } + // only check if the ctx used in the current function is r.Context() + if isHttpHandler { + continue + } + ff := r.getFunction(instr) if ff == nil { continue @@ -379,7 +564,7 @@ func (r *runner) checkFuncWithoutCtx(f *ssa.Function, checkingMap map[string]boo continue } - if !r.checkIsEntry(ff, instr.Pos()) { + if entryType := r.checkIsEntry(ff); entryType&Entry == 0 { // cannot get info from fact, skip if ff.Blocks == nil { continue @@ -495,6 +680,26 @@ func (r *runner) isCtxType(tp types.Type) bool { return types.Identical(tp, r.ctxTyp) || types.Identical(tp, r.ctxPTyp) } +func (r *runner) isHttpResType(tp types.Type) bool { + var ok bool + for _, v := range r.httpResTyps { + if ok = types.Identical(v, v); ok { + break + } + } + return ok +} + +func (r *runner) isHttpReqType(tp types.Type) bool { + var ok bool + for _, v := range r.httpReqTyps { + if ok = types.Identical(tp, v); ok { + break + } + } + return ok +} + func (r *runner) getValue(key string, f *ssa.Function) (res resInfo, ok bool) { res, ok = r.currentFact[key] if ok { diff --git a/go.mod b/go.mod index 95b9ffa..7e45143 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.15 require ( github.com/gostaticanalysis/analysisutil v0.7.1 - golang.org/x/tools v0.1.5 + golang.org/x/tools v0.1.12 ) diff --git a/go.sum b/go.sum index 08eb2c7..e5eafc9 100644 --- a/go.sum +++ b/go.sum @@ -22,39 +22,44 @@ github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testdata/src/a/a.go b/testdata/src/a/a.go index 5db336e..ac9e401 100644 --- a/testdata/src/a/a.go +++ b/testdata/src/a/a.go @@ -1,6 +1,9 @@ package a // want package:"ctxCheck" -import "context" +import ( + "context" + "net/http" +) type MyString string @@ -45,7 +48,7 @@ func f1(ctx context.Context) { f2(ctx) }(ctx) - f2(context.Background()) // want "Non-inherited new context, use function like `context.WithXXX` instead" + f2(context.Background()) // want "Non-inherited new context, use function like `context.WithXXX` or `r.Context` instead" thunk := MyInt.F thunk(0) @@ -63,7 +66,7 @@ func f3() { func f4(ctx context.Context) { f2(ctx) ctx = context.Background() - f2(ctx) // want "Non-inherited new context, use function like `context.WithXXX` instead" + f2(ctx) // want "Non-inherited new context, use function like `context.WithXXX` or `r.Context` instead" } func f5(ctx context.Context) { @@ -89,3 +92,46 @@ func f7(ctx context.Context) { func getNewCtx(ctx context.Context) (newCtx context.Context, cancel context.CancelFunc) { return context.WithCancel(ctx) } + +/* ----------------- http ----------------- */ + +func f8(ctx context.Context, w http.ResponseWriter, r *http.Request) { +} + +func f9(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + f8(ctx, w, r) + f8(context.Background(), w, r) // want "Non-inherited new context, use function like `context.WithXXX` or `r.Context` instead" +} + +func f10() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + f9(w, r) + f8(r.Context(), w, r) + f8(context.Background(), w, r) // want "Non-inherited new context, use function like `context.WithXXX` or `r.Context` instead" + }) +} + +/* ----------------- generics ----------------- */ + +type MySlice[T int | float32] []T + +func (s MySlice[T]) f11(ctx context.Context) T { + f3() // generics, Block is nil, wont report + + var sum T + for _, value := range s { + sum += value + } + return sum +} + +func f12[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](ctx context.Context, a, b T) T { + f3() // generics, Block is nil, wont report + + if a > b { + return a + } + + return b +}