 6a979fb40d
			
		
	
	
		6a979fb40d
		
			
		
	
	
	
	
		
			
			* update staticcheck Don't fork staticcheck: use the upstream version. Remove unneeded SSA loading. * Cache go/analysis facts Don't load unneeded packages for go/analysis. Repeated run of go/analysis linters now 10x faster (2s vs 20s on this repo) than before.
		
			
				
	
	
		
			176 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package facts
 | |
| 
 | |
| import (
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"reflect"
 | |
| 
 | |
| 	"golang.org/x/tools/go/analysis"
 | |
| 	"honnef.co/go/tools/functions"
 | |
| 	"honnef.co/go/tools/internal/passes/buildssa"
 | |
| 	"honnef.co/go/tools/ssa"
 | |
| )
 | |
| 
 | |
| type IsPure struct{}
 | |
| 
 | |
| func (*IsPure) AFact()           {}
 | |
| func (d *IsPure) String() string { return "is pure" }
 | |
| 
 | |
| type PurityResult map[*types.Func]*IsPure
 | |
| 
 | |
| var Purity = &analysis.Analyzer{
 | |
| 	Name:       "fact_purity",
 | |
| 	Doc:        "Mark pure functions",
 | |
| 	Run:        purity,
 | |
| 	Requires:   []*analysis.Analyzer{buildssa.Analyzer},
 | |
| 	FactTypes:  []analysis.Fact{(*IsPure)(nil)},
 | |
| 	ResultType: reflect.TypeOf(PurityResult{}),
 | |
| }
 | |
| 
 | |
| var pureStdlib = map[string]struct{}{
 | |
| 	"errors.New":                      {},
 | |
| 	"fmt.Errorf":                      {},
 | |
| 	"fmt.Sprintf":                     {},
 | |
| 	"fmt.Sprint":                      {},
 | |
| 	"sort.Reverse":                    {},
 | |
| 	"strings.Map":                     {},
 | |
| 	"strings.Repeat":                  {},
 | |
| 	"strings.Replace":                 {},
 | |
| 	"strings.Title":                   {},
 | |
| 	"strings.ToLower":                 {},
 | |
| 	"strings.ToLowerSpecial":          {},
 | |
| 	"strings.ToTitle":                 {},
 | |
| 	"strings.ToTitleSpecial":          {},
 | |
| 	"strings.ToUpper":                 {},
 | |
| 	"strings.ToUpperSpecial":          {},
 | |
| 	"strings.Trim":                    {},
 | |
| 	"strings.TrimFunc":                {},
 | |
| 	"strings.TrimLeft":                {},
 | |
| 	"strings.TrimLeftFunc":            {},
 | |
| 	"strings.TrimPrefix":              {},
 | |
| 	"strings.TrimRight":               {},
 | |
| 	"strings.TrimRightFunc":           {},
 | |
| 	"strings.TrimSpace":               {},
 | |
| 	"strings.TrimSuffix":              {},
 | |
| 	"(*net/http.Request).WithContext": {},
 | |
| }
 | |
| 
 | |
| func purity(pass *analysis.Pass) (interface{}, error) {
 | |
| 	seen := map[*ssa.Function]struct{}{}
 | |
| 	ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg
 | |
| 	var check func(ssafn *ssa.Function) (ret bool)
 | |
| 	check = func(ssafn *ssa.Function) (ret bool) {
 | |
| 		if ssafn.Object() == nil {
 | |
| 			// TODO(dh): support closures
 | |
| 			return false
 | |
| 		}
 | |
| 		if pass.ImportObjectFact(ssafn.Object(), new(IsPure)) {
 | |
| 			return true
 | |
| 		}
 | |
| 		if ssafn.Pkg != ssapkg {
 | |
| 			// Function is in another package but wasn't marked as
 | |
| 			// pure, ergo it isn't pure
 | |
| 			return false
 | |
| 		}
 | |
| 		// Break recursion
 | |
| 		if _, ok := seen[ssafn]; ok {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		seen[ssafn] = struct{}{}
 | |
| 		defer func() {
 | |
| 			if ret {
 | |
| 				pass.ExportObjectFact(ssafn.Object(), &IsPure{})
 | |
| 			}
 | |
| 		}()
 | |
| 
 | |
| 		if functions.IsStub(ssafn) {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		if _, ok := pureStdlib[ssafn.Object().(*types.Func).FullName()]; ok {
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		if ssafn.Signature.Results().Len() == 0 {
 | |
| 			// A function with no return values is empty or is doing some
 | |
| 			// work we cannot see (for example because of build tags);
 | |
| 			// don't consider it pure.
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		for _, param := range ssafn.Params {
 | |
| 			if _, ok := param.Type().Underlying().(*types.Basic); !ok {
 | |
| 				return false
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if ssafn.Blocks == nil {
 | |
| 			return false
 | |
| 		}
 | |
| 		checkCall := func(common *ssa.CallCommon) bool {
 | |
| 			if common.IsInvoke() {
 | |
| 				return false
 | |
| 			}
 | |
| 			builtin, ok := common.Value.(*ssa.Builtin)
 | |
| 			if !ok {
 | |
| 				if common.StaticCallee() != ssafn {
 | |
| 					if common.StaticCallee() == nil {
 | |
| 						return false
 | |
| 					}
 | |
| 					if !check(common.StaticCallee()) {
 | |
| 						return false
 | |
| 					}
 | |
| 				}
 | |
| 			} else {
 | |
| 				switch builtin.Name() {
 | |
| 				case "len", "cap", "make", "new":
 | |
| 				default:
 | |
| 					return false
 | |
| 				}
 | |
| 			}
 | |
| 			return true
 | |
| 		}
 | |
| 		for _, b := range ssafn.Blocks {
 | |
| 			for _, ins := range b.Instrs {
 | |
| 				switch ins := ins.(type) {
 | |
| 				case *ssa.Call:
 | |
| 					if !checkCall(ins.Common()) {
 | |
| 						return false
 | |
| 					}
 | |
| 				case *ssa.Defer:
 | |
| 					if !checkCall(&ins.Call) {
 | |
| 						return false
 | |
| 					}
 | |
| 				case *ssa.Select:
 | |
| 					return false
 | |
| 				case *ssa.Send:
 | |
| 					return false
 | |
| 				case *ssa.Go:
 | |
| 					return false
 | |
| 				case *ssa.Panic:
 | |
| 					return false
 | |
| 				case *ssa.Store:
 | |
| 					return false
 | |
| 				case *ssa.FieldAddr:
 | |
| 					return false
 | |
| 				case *ssa.UnOp:
 | |
| 					if ins.Op == token.MUL || ins.Op == token.AND {
 | |
| 						return false
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return true
 | |
| 	}
 | |
| 	for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
 | |
| 		check(ssafn)
 | |
| 	}
 | |
| 
 | |
| 	out := PurityResult{}
 | |
| 	for _, fact := range pass.AllObjectFacts() {
 | |
| 		out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
 | |
| 	}
 | |
| 	return out, nil
 | |
| }
 |