workgroups/vendor/github.com/sonatard/noctx/reqwithoutctx/ssa.go
Marvin Preuss 1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
ci: drone yaml with reusable anchors
2021-09-24 17:34:17 +02:00

181 lines
3.6 KiB
Go

package reqwithoutctx
import (
"go/types"
"github.com/gostaticanalysis/analysisutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
)
type Analyzer struct {
Funcs []*ssa.Function
newRequestType types.Type
requestType types.Type
}
func NewAnalyzer(pass *analysis.Pass) *Analyzer {
newRequestType := analysisutil.TypeOf(pass, "net/http", "NewRequest")
requestType := analysisutil.TypeOf(pass, "net/http", "*Request")
srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs
return &Analyzer{
Funcs: srcFuncs,
newRequestType: newRequestType,
requestType: requestType,
}
}
func (a *Analyzer) Exec() []*Report {
if a.newRequestType == nil || a.requestType == nil {
return []*Report{}
}
usedReqs := a.usedReqs()
newReqs := a.requestsByNewRequest()
return a.report(usedReqs, newReqs)
}
func (a *Analyzer) report(usedReqs map[string]*ssa.Extract, newReqs map[*ssa.Call]*ssa.Extract) []*Report {
var reports []*Report
for _, fReq := range usedReqs {
for newRequest, req := range newReqs {
if fReq == req {
reports = append(reports, &Report{Instruction: newRequest})
}
}
}
return reports
}
func (a *Analyzer) usedReqs() map[string]*ssa.Extract {
reqExts := make(map[string]*ssa.Extract)
for _, f := range a.Funcs {
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch i := instr.(type) {
case *ssa.Call:
exts := a.usedReqByCall(i)
for _, ext := range exts {
key := i.String() + ext.String()
reqExts[key] = ext
}
case *ssa.UnOp:
ext := a.usedReqByUnOp(i)
if ext != nil {
key := i.String() + ext.String()
reqExts[key] = ext
}
case *ssa.Return:
exts := a.usedReqByReturn(i)
for _, ext := range exts {
key := i.String() + ext.String()
reqExts[key] = ext
}
}
}
}
}
return reqExts
}
func (a *Analyzer) usedReqByCall(call *ssa.Call) []*ssa.Extract {
var exts []*ssa.Extract
// skip net/http.Request method call
if call.Common().Signature().Recv() != nil && types.Identical(call.Value().Type(), a.requestType) {
return exts
}
args := call.Common().Args
if len(args) == 0 {
return exts
}
for _, arg := range args {
ext, ok := arg.(*ssa.Extract)
if !ok {
continue
}
if !types.Identical(ext.Type(), a.requestType) {
continue
}
exts = append(exts, ext)
}
return exts
}
func (a *Analyzer) usedReqByUnOp(op *ssa.UnOp) *ssa.Extract {
if ext, ok := op.X.(*ssa.Extract); ok && types.Identical(ext.Type(), a.requestType) {
return ext
}
return nil
}
func (a *Analyzer) usedReqByReturn(ret *ssa.Return) []*ssa.Extract {
rets := ret.Results
exts := make([]*ssa.Extract, 0, len(rets))
for _, ret := range rets {
ext, ok := ret.(*ssa.Extract)
if !ok {
continue
}
if types.Identical(ext.Type(), a.requestType) {
exts = append(exts, ext)
}
}
return exts
}
func (a *Analyzer) requestsByNewRequest() map[*ssa.Call]*ssa.Extract {
reqs := make(map[*ssa.Call]*ssa.Extract)
for _, f := range a.Funcs {
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
ext, ok := instr.(*ssa.Extract)
if !ok {
continue
}
if !types.Identical(ext.Type(), a.requestType) {
continue
}
operands := ext.Operands([]*ssa.Value{})
if len(operands) != 1 {
continue
}
operand := *operands[0]
f, ok := operand.(*ssa.Call)
if !ok {
continue
}
if types.Identical(f.Call.Value.Type(), a.newRequestType) {
reqs[f] = ext
}
}
}
}
return reqs
}