499 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			499 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package goanalysis
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/parser"
 | |
| 	"go/scanner"
 | |
| 	"go/types"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"sync"
 | |
| 	"sync/atomic"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| 	"golang.org/x/tools/go/gcexportdata"
 | |
| 	"golang.org/x/tools/go/packages"
 | |
| 
 | |
| 	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load"
 | |
| 	"github.com/golangci/golangci-lint/pkg/logutils"
 | |
| )
 | |
| 
 | |
| const unsafePkgName = "unsafe"
 | |
| 
 | |
| type loadingPackage struct {
 | |
| 	pkg         *packages.Package
 | |
| 	imports     map[string]*loadingPackage
 | |
| 	isInitial   bool
 | |
| 	log         logutils.Log
 | |
| 	actions     []*action // all actions with this package
 | |
| 	loadGuard   *load.Guard
 | |
| 	dependents  int32 // number of depending on it packages
 | |
| 	analyzeOnce sync.Once
 | |
| 	decUseMutex sync.Mutex
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) analyzeRecursive(loadMode LoadMode, loadSem chan struct{}) {
 | |
| 	lp.analyzeOnce.Do(func() {
 | |
| 		// Load the direct dependencies, in parallel.
 | |
| 		var wg sync.WaitGroup
 | |
| 		wg.Add(len(lp.imports))
 | |
| 		for _, imp := range lp.imports {
 | |
| 			go func(imp *loadingPackage) {
 | |
| 				imp.analyzeRecursive(loadMode, loadSem)
 | |
| 				wg.Done()
 | |
| 			}(imp)
 | |
| 		}
 | |
| 		wg.Wait()
 | |
| 		lp.analyze(loadMode, loadSem)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) analyze(loadMode LoadMode, loadSem chan struct{}) {
 | |
| 	loadSem <- struct{}{}
 | |
| 	defer func() {
 | |
| 		<-loadSem
 | |
| 	}()
 | |
| 
 | |
| 	// Save memory on unused more fields.
 | |
| 	defer lp.decUse(loadMode < LoadModeWholeProgram)
 | |
| 
 | |
| 	if err := lp.loadWithFacts(loadMode); err != nil {
 | |
| 		werr := errors.Wrapf(err, "failed to load package %s", lp.pkg.Name)
 | |
| 		// Don't need to write error to errCh, it will be extracted and reported on another layer.
 | |
| 		// Unblock depending on actions and propagate error.
 | |
| 		for _, act := range lp.actions {
 | |
| 			close(act.analysisDoneCh)
 | |
| 			act.err = werr
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var actsWg sync.WaitGroup
 | |
| 	actsWg.Add(len(lp.actions))
 | |
| 	for _, act := range lp.actions {
 | |
| 		go func(act *action) {
 | |
| 			defer actsWg.Done()
 | |
| 
 | |
| 			act.waitUntilDependingAnalyzersWorked()
 | |
| 
 | |
| 			act.analyzeSafe()
 | |
| 		}(act)
 | |
| 	}
 | |
| 	actsWg.Wait()
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) loadFromSource(loadMode LoadMode) error {
 | |
| 	pkg := lp.pkg
 | |
| 
 | |
| 	// Many packages have few files, much fewer than there
 | |
| 	// are CPU cores. Additionally, parsing each individual file is
 | |
| 	// very fast. A naive parallel implementation of this loop won't
 | |
| 	// be faster, and tends to be slower due to extra scheduling,
 | |
| 	// bookkeeping and potentially false sharing of cache lines.
 | |
| 	pkg.Syntax = make([]*ast.File, 0, len(pkg.CompiledGoFiles))
 | |
| 	for _, file := range pkg.CompiledGoFiles {
 | |
| 		f, err := parser.ParseFile(pkg.Fset, file, nil, parser.ParseComments)
 | |
| 		if err != nil {
 | |
| 			pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
 | |
| 			continue
 | |
| 		}
 | |
| 		pkg.Syntax = append(pkg.Syntax, f)
 | |
| 	}
 | |
| 	if len(pkg.Errors) != 0 {
 | |
| 		pkg.IllTyped = true
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if loadMode == LoadModeSyntax {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Call NewPackage directly with explicit name.
 | |
| 	// This avoids skew between golist and go/types when the files'
 | |
| 	// package declarations are inconsistent.
 | |
| 	// Subtle: we populate all Types fields with an empty Package
 | |
| 	// before loading export data so that export data processing
 | |
| 	// never has to create a types.Package for an indirect dependency,
 | |
| 	// which would then require that such created packages be explicitly
 | |
| 	// inserted back into the Import graph as a final step after export data loading.
 | |
| 	pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
 | |
| 
 | |
| 	pkg.IllTyped = true
 | |
| 
 | |
| 	pkg.TypesInfo = &types.Info{
 | |
| 		Types:      make(map[ast.Expr]types.TypeAndValue),
 | |
| 		Instances:  make(map[*ast.Ident]types.Instance),
 | |
| 		Defs:       make(map[*ast.Ident]types.Object),
 | |
| 		Uses:       make(map[*ast.Ident]types.Object),
 | |
| 		Implicits:  make(map[ast.Node]types.Object),
 | |
| 		Scopes:     make(map[ast.Node]*types.Scope),
 | |
| 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 | |
| 	}
 | |
| 
 | |
| 	importer := func(path string) (*types.Package, error) {
 | |
| 		if path == unsafePkgName {
 | |
| 			return types.Unsafe, nil
 | |
| 		}
 | |
| 		if path == "C" {
 | |
| 			// go/packages doesn't tell us that cgo preprocessing
 | |
| 			// failed. When we subsequently try to parse the package,
 | |
| 			// we'll encounter the raw C import.
 | |
| 			return nil, errors.New("cgo preprocessing failed")
 | |
| 		}
 | |
| 		imp := pkg.Imports[path]
 | |
| 		if imp == nil {
 | |
| 			return nil, nil
 | |
| 		}
 | |
| 		if len(imp.Errors) > 0 {
 | |
| 			return nil, imp.Errors[0]
 | |
| 		}
 | |
| 		return imp.Types, nil
 | |
| 	}
 | |
| 	tc := &types.Config{
 | |
| 		Importer: importerFunc(importer),
 | |
| 		Error: func(err error) {
 | |
| 			pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
 | |
| 		},
 | |
| 	}
 | |
| 	_ = types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
 | |
| 	// Don't handle error here: errors are adding by tc.Error function.
 | |
| 
 | |
| 	illTyped := len(pkg.Errors) != 0
 | |
| 	if !illTyped {
 | |
| 		for _, imp := range lp.imports {
 | |
| 			if imp.pkg.IllTyped {
 | |
| 				illTyped = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	pkg.IllTyped = illTyped
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) loadFromExportData() error {
 | |
| 	pkg := lp.pkg
 | |
| 
 | |
| 	// Call NewPackage directly with explicit name.
 | |
| 	// This avoids skew between golist and go/types when the files'
 | |
| 	// package declarations are inconsistent.
 | |
| 	// Subtle: we populate all Types fields with an empty Package
 | |
| 	// before loading export data so that export data processing
 | |
| 	// never has to create a types.Package for an indirect dependency,
 | |
| 	// which would then require that such created packages be explicitly
 | |
| 	// inserted back into the Import graph as a final step after export data loading.
 | |
| 	pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
 | |
| 
 | |
| 	pkg.IllTyped = true
 | |
| 	for path, pkg := range pkg.Imports {
 | |
| 		if pkg.Types == nil {
 | |
| 			return fmt.Errorf("dependency %q hasn't been loaded yet", path)
 | |
| 		}
 | |
| 	}
 | |
| 	if pkg.ExportFile == "" {
 | |
| 		return fmt.Errorf("no export data for %q", pkg.ID)
 | |
| 	}
 | |
| 	f, err := os.Open(pkg.ExportFile)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	r, err := gcexportdata.NewReader(f)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	view := make(map[string]*types.Package)  // view seen by gcexportdata
 | |
| 	seen := make(map[*packages.Package]bool) // all visited packages
 | |
| 	var visit func(pkgs map[string]*packages.Package)
 | |
| 	visit = func(pkgs map[string]*packages.Package) {
 | |
| 		for _, pkg := range pkgs {
 | |
| 			if !seen[pkg] {
 | |
| 				seen[pkg] = true
 | |
| 				view[pkg.PkgPath] = pkg.Types
 | |
| 				visit(pkg.Imports)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	visit(pkg.Imports)
 | |
| 	tpkg, err := gcexportdata.Read(r, pkg.Fset, view, pkg.PkgPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	pkg.Types = tpkg
 | |
| 	pkg.IllTyped = false
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) loadWithFacts(loadMode LoadMode) error {
 | |
| 	pkg := lp.pkg
 | |
| 
 | |
| 	if pkg.PkgPath == unsafePkgName {
 | |
| 		// Fill in the blanks to avoid surprises.
 | |
| 		pkg.Syntax = []*ast.File{}
 | |
| 		if loadMode >= LoadModeTypesInfo {
 | |
| 			pkg.Types = types.Unsafe
 | |
| 			pkg.TypesInfo = new(types.Info)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if pkg.TypesInfo != nil {
 | |
| 		// Already loaded package, e.g. because another not go/analysis linter required types for deps.
 | |
| 		// Try load cached facts for it.
 | |
| 
 | |
| 		for _, act := range lp.actions {
 | |
| 			if !act.loadCachedFacts() {
 | |
| 				// Cached facts loading failed: analyze later the action from source.
 | |
| 				act.needAnalyzeSource = true
 | |
| 				factsCacheDebugf("Loading of facts for already loaded %s failed, analyze it from source later", act)
 | |
| 				act.markDepsForAnalyzingSource()
 | |
| 			}
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if lp.isInitial {
 | |
| 		// No need to load cached facts: the package will be analyzed from source
 | |
| 		// because it's the initial.
 | |
| 		return lp.loadFromSource(loadMode)
 | |
| 	}
 | |
| 
 | |
| 	return lp.loadImportedPackageWithFacts(loadMode)
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) loadImportedPackageWithFacts(loadMode LoadMode) error {
 | |
| 	pkg := lp.pkg
 | |
| 
 | |
| 	// Load package from export data
 | |
| 	if loadMode >= LoadModeTypesInfo {
 | |
| 		if err := lp.loadFromExportData(); err != nil {
 | |
| 			// We asked Go to give us up-to-date export data, yet
 | |
| 			// we can't load it. There must be something wrong.
 | |
| 			//
 | |
| 			// Attempt loading from source. This should fail (because
 | |
| 			// otherwise there would be export data); we just want to
 | |
| 			// get the compile errors. If loading from source succeeds
 | |
| 			// we discard the result, anyway. Otherwise, we'll fail
 | |
| 			// when trying to reload from export data later.
 | |
| 
 | |
| 			// Otherwise, it panics because uses already existing (from exported data) types.
 | |
| 			pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
 | |
| 			if srcErr := lp.loadFromSource(loadMode); srcErr != nil {
 | |
| 				return srcErr
 | |
| 			}
 | |
| 			// Make sure this package can't be imported successfully
 | |
| 			pkg.Errors = append(pkg.Errors, packages.Error{
 | |
| 				Pos:  "-",
 | |
| 				Msg:  fmt.Sprintf("could not load export data: %s", err),
 | |
| 				Kind: packages.ParseError,
 | |
| 			})
 | |
| 			return errors.Wrap(err, "could not load export data")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	needLoadFromSource := false
 | |
| 	for _, act := range lp.actions {
 | |
| 		if act.loadCachedFacts() {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Cached facts loading failed: analyze later the action from source.
 | |
| 		factsCacheDebugf("Loading of facts for %s failed, analyze it from source later", act)
 | |
| 		act.needAnalyzeSource = true // can't be set in parallel
 | |
| 		needLoadFromSource = true
 | |
| 
 | |
| 		act.markDepsForAnalyzingSource()
 | |
| 	}
 | |
| 
 | |
| 	if needLoadFromSource {
 | |
| 		// Cached facts loading failed: analyze later the action from source. To perform
 | |
| 		// the analysis we need to load the package from source code.
 | |
| 
 | |
| 		// Otherwise, it panics because uses already existing (from exported data) types.
 | |
| 		if loadMode >= LoadModeTypesInfo {
 | |
| 			pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
 | |
| 		}
 | |
| 		return lp.loadFromSource(loadMode)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) decUse(canClearTypes bool) {
 | |
| 	lp.decUseMutex.Lock()
 | |
| 	defer lp.decUseMutex.Unlock()
 | |
| 
 | |
| 	for _, act := range lp.actions {
 | |
| 		pass := act.pass
 | |
| 		if pass == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		pass.Files = nil
 | |
| 		pass.TypesInfo = nil
 | |
| 		pass.TypesSizes = nil
 | |
| 		pass.ResultOf = nil
 | |
| 		pass.Pkg = nil
 | |
| 		pass.OtherFiles = nil
 | |
| 		pass.AllObjectFacts = nil
 | |
| 		pass.AllPackageFacts = nil
 | |
| 		pass.ImportObjectFact = nil
 | |
| 		pass.ExportObjectFact = nil
 | |
| 		pass.ImportPackageFact = nil
 | |
| 		pass.ExportPackageFact = nil
 | |
| 		act.pass = nil
 | |
| 		act.deps = nil
 | |
| 		if act.result != nil {
 | |
| 			if isMemoryDebug {
 | |
| 				debugf("%s: decUse: nilling act result of size %d bytes", act, sizeOfValueTreeBytes(act.result))
 | |
| 			}
 | |
| 			act.result = nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	lp.pkg.Syntax = nil
 | |
| 	lp.pkg.TypesInfo = nil
 | |
| 	lp.pkg.TypesSizes = nil
 | |
| 
 | |
| 	// Can't set lp.pkg.Imports to nil because of loadFromExportData.visit.
 | |
| 
 | |
| 	dependents := atomic.AddInt32(&lp.dependents, -1)
 | |
| 	if dependents != 0 {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if canClearTypes {
 | |
| 		// canClearTypes is set to true if we can discard type
 | |
| 		// information after the package and its dependents have been
 | |
| 		// processed. This is the case when no whole program checkers (unused) are
 | |
| 		// being run.
 | |
| 		lp.pkg.Types = nil
 | |
| 	}
 | |
| 	lp.pkg = nil
 | |
| 
 | |
| 	for _, imp := range lp.imports {
 | |
| 		imp.decUse(canClearTypes)
 | |
| 	}
 | |
| 	lp.imports = nil
 | |
| 
 | |
| 	for _, act := range lp.actions {
 | |
| 		if !lp.isInitial {
 | |
| 			act.pkg = nil
 | |
| 		}
 | |
| 		act.packageFacts = nil
 | |
| 		act.objectFacts = nil
 | |
| 	}
 | |
| 	lp.actions = nil
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) convertError(err error) []packages.Error {
 | |
| 	var errs []packages.Error
 | |
| 	// taken from go/packages
 | |
| 	switch err := err.(type) {
 | |
| 	case packages.Error:
 | |
| 		// from driver
 | |
| 		errs = append(errs, err)
 | |
| 
 | |
| 	case *os.PathError:
 | |
| 		// from parser
 | |
| 		errs = append(errs, packages.Error{
 | |
| 			Pos:  err.Path + ":1",
 | |
| 			Msg:  err.Err.Error(),
 | |
| 			Kind: packages.ParseError,
 | |
| 		})
 | |
| 
 | |
| 	case scanner.ErrorList:
 | |
| 		// from parser
 | |
| 		for _, err := range err {
 | |
| 			errs = append(errs, packages.Error{
 | |
| 				Pos:  err.Pos.String(),
 | |
| 				Msg:  err.Msg,
 | |
| 				Kind: packages.ParseError,
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 	case types.Error:
 | |
| 		// from type checker
 | |
| 		errs = append(errs, packages.Error{
 | |
| 			Pos:  err.Fset.Position(err.Pos).String(),
 | |
| 			Msg:  err.Msg,
 | |
| 			Kind: packages.TypeError,
 | |
| 		})
 | |
| 
 | |
| 	default:
 | |
| 		// unexpected impoverished error from parser?
 | |
| 		errs = append(errs, packages.Error{
 | |
| 			Pos:  "-",
 | |
| 			Msg:  err.Error(),
 | |
| 			Kind: packages.UnknownError,
 | |
| 		})
 | |
| 
 | |
| 		// If you see this error message, please file a bug.
 | |
| 		lp.log.Warnf("Internal error: error %q (%T) without position", err, err)
 | |
| 	}
 | |
| 	return errs
 | |
| }
 | |
| 
 | |
| func (lp *loadingPackage) String() string {
 | |
| 	return fmt.Sprintf("%s@%s", lp.pkg.PkgPath, lp.pkg.Name)
 | |
| }
 | |
| 
 | |
| type importerFunc func(path string) (*types.Package, error)
 | |
| 
 | |
| func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
 | |
| 
 | |
| func sizeOfValueTreeBytes(v interface{}) int {
 | |
| 	return sizeOfReflectValueTreeBytes(reflect.ValueOf(v), map[uintptr]struct{}{})
 | |
| }
 | |
| 
 | |
| func sizeOfReflectValueTreeBytes(rv reflect.Value, visitedPtrs map[uintptr]struct{}) int {
 | |
| 	switch rv.Kind() {
 | |
| 	case reflect.Ptr:
 | |
| 		ptrSize := int(rv.Type().Size())
 | |
| 		if rv.IsNil() {
 | |
| 			return ptrSize
 | |
| 		}
 | |
| 		ptr := rv.Pointer()
 | |
| 		if _, ok := visitedPtrs[ptr]; ok {
 | |
| 			return 0
 | |
| 		}
 | |
| 		visitedPtrs[ptr] = struct{}{}
 | |
| 		return ptrSize + sizeOfReflectValueTreeBytes(rv.Elem(), visitedPtrs)
 | |
| 	case reflect.Interface:
 | |
| 		if rv.IsNil() {
 | |
| 			return 0
 | |
| 		}
 | |
| 		return sizeOfReflectValueTreeBytes(rv.Elem(), visitedPtrs)
 | |
| 	case reflect.Struct:
 | |
| 		ret := 0
 | |
| 		for i := 0; i < rv.NumField(); i++ {
 | |
| 			ret += sizeOfReflectValueTreeBytes(rv.Field(i), visitedPtrs)
 | |
| 		}
 | |
| 		return ret
 | |
| 	case reflect.Slice, reflect.Array, reflect.Chan:
 | |
| 		return int(rv.Type().Size()) + rv.Cap()*int(rv.Type().Elem().Size())
 | |
| 	case reflect.Map:
 | |
| 		ret := 0
 | |
| 		for _, key := range rv.MapKeys() {
 | |
| 			mv := rv.MapIndex(key)
 | |
| 			ret += sizeOfReflectValueTreeBytes(key, visitedPtrs)
 | |
| 			ret += sizeOfReflectValueTreeBytes(mv, visitedPtrs)
 | |
| 		}
 | |
| 		return ret
 | |
| 	case reflect.String:
 | |
| 		return rv.Len()
 | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
 | |
| 		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
 | |
| 		reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64,
 | |
| 		reflect.Complex64, reflect.Complex128, reflect.Func, reflect.UnsafePointer:
 | |
| 		return int(rv.Type().Size())
 | |
| 	case reflect.Invalid:
 | |
| 		return 0
 | |
| 	default:
 | |
| 		panic("unknown rv of type " + rv.String())
 | |
| 	}
 | |
| }
 | 
