github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/go/loader/loader.go (about)

     1  package loader
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/parser"
     8  	"go/scanner"
     9  	"go/token"
    10  	"go/types"
    11  	"os"
    12  	"time"
    13  
    14  	"github.com/amarpal/go-tools/config"
    15  	"github.com/amarpal/go-tools/lintcmd/cache"
    16  
    17  	"golang.org/x/tools/go/gcexportdata"
    18  	"golang.org/x/tools/go/packages"
    19  )
    20  
    21  const MaxFileSize = 50 * 1024 * 1024 // 50 MB
    22  
    23  var errMaxFileSize = errors.New("file exceeds max file size")
    24  
    25  type PackageSpec struct {
    26  	ID      string
    27  	Name    string
    28  	PkgPath string
    29  	// Errors that occurred while building the import graph. These will
    30  	// primarily be parse errors or failure to resolve imports, but
    31  	// may also be other errors.
    32  	Errors          []packages.Error
    33  	GoFiles         []string
    34  	CompiledGoFiles []string
    35  	OtherFiles      []string
    36  	ExportFile      string
    37  	Imports         map[string]*PackageSpec
    38  	TypesSizes      types.Sizes
    39  	Hash            cache.ActionID
    40  	Module          *packages.Module
    41  
    42  	Config config.Config
    43  }
    44  
    45  func (spec *PackageSpec) String() string {
    46  	return spec.ID
    47  }
    48  
    49  type Package struct {
    50  	*PackageSpec
    51  
    52  	// Errors that occurred while loading the package. These will
    53  	// primarily be parse or type errors, but may also be lower-level
    54  	// failures such as file-system ones.
    55  	Errors    []packages.Error
    56  	Types     *types.Package
    57  	Fset      *token.FileSet
    58  	Syntax    []*ast.File
    59  	TypesInfo *types.Info
    60  }
    61  
    62  // Graph resolves patterns and returns packages with all the
    63  // information required to later load type information, and optionally
    64  // syntax trees.
    65  //
    66  // The provided config can set any setting with the exception of Mode.
    67  func Graph(c *cache.Cache, cfg *packages.Config, patterns ...string) ([]*PackageSpec, error) {
    68  	var dcfg packages.Config
    69  	if cfg != nil {
    70  		dcfg = *cfg
    71  	}
    72  	dcfg.Mode = packages.NeedName |
    73  		packages.NeedImports |
    74  		packages.NeedDeps |
    75  		packages.NeedExportFile |
    76  		packages.NeedFiles |
    77  		packages.NeedCompiledGoFiles |
    78  		packages.NeedTypesSizes |
    79  		packages.NeedModule
    80  	pkgs, err := packages.Load(&dcfg, patterns...)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	m := map[*packages.Package]*PackageSpec{}
    86  	packages.Visit(pkgs, nil, func(pkg *packages.Package) {
    87  		spec := &PackageSpec{
    88  			ID:              pkg.ID,
    89  			Name:            pkg.Name,
    90  			PkgPath:         pkg.PkgPath,
    91  			Errors:          pkg.Errors,
    92  			GoFiles:         pkg.GoFiles,
    93  			CompiledGoFiles: pkg.CompiledGoFiles,
    94  			OtherFiles:      pkg.OtherFiles,
    95  			ExportFile:      pkg.ExportFile,
    96  			Imports:         map[string]*PackageSpec{},
    97  			TypesSizes:      pkg.TypesSizes,
    98  			Module:          pkg.Module,
    99  		}
   100  		for path, imp := range pkg.Imports {
   101  			spec.Imports[path] = m[imp]
   102  		}
   103  		if cdir := config.Dir(pkg.GoFiles); cdir != "" {
   104  			cfg, err := config.Load(cdir)
   105  			if err != nil {
   106  				spec.Errors = append(spec.Errors, convertError(err)...)
   107  			}
   108  			spec.Config = cfg
   109  		} else {
   110  			spec.Config = config.DefaultConfig
   111  		}
   112  		spec.Hash, err = computeHash(c, spec)
   113  		if err != nil {
   114  			spec.Errors = append(spec.Errors, convertError(err)...)
   115  		}
   116  		m[pkg] = spec
   117  	})
   118  	out := make([]*PackageSpec, 0, len(pkgs))
   119  	for _, pkg := range pkgs {
   120  		if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" {
   121  			// If a package consists only of test files, then
   122  			// go/packages incorrectly(?) returns an empty package for
   123  			// the non-test variant. Get rid of those packages. See
   124  			// #646.
   125  			//
   126  			// Do not, however, skip packages that have errors. Those,
   127  			// too, may have no files, but we want to print the
   128  			// errors.
   129  			continue
   130  		}
   131  		out = append(out, m[pkg])
   132  	}
   133  
   134  	return out, nil
   135  }
   136  
   137  type program struct {
   138  	fset     *token.FileSet
   139  	packages map[string]*types.Package
   140  }
   141  
   142  type Stats struct {
   143  	Source time.Duration
   144  	Export map[*PackageSpec]time.Duration
   145  }
   146  
   147  // Load loads the package described in spec. Imports will be loaded
   148  // from export data, while the package itself will be loaded from
   149  // source.
   150  //
   151  // An error will only be returned for system failures, such as failure
   152  // to read export data from disk. Syntax and type errors, among
   153  // others, will only populate the returned package's Errors field.
   154  func Load(spec *PackageSpec) (*Package, Stats, error) {
   155  	prog := &program{
   156  		fset:     token.NewFileSet(),
   157  		packages: map[string]*types.Package{},
   158  	}
   159  
   160  	stats := Stats{
   161  		Export: map[*PackageSpec]time.Duration{},
   162  	}
   163  	for _, imp := range spec.Imports {
   164  		if imp.PkgPath == "unsafe" {
   165  			continue
   166  		}
   167  		t := time.Now()
   168  		_, err := prog.loadFromExport(imp)
   169  		stats.Export[imp] = time.Since(t)
   170  		if err != nil {
   171  			return nil, stats, err
   172  		}
   173  	}
   174  	t := time.Now()
   175  	pkg, err := prog.loadFromSource(spec)
   176  	if err == errMaxFileSize {
   177  		pkg, err = prog.loadFromExport(spec)
   178  	}
   179  	stats.Source = time.Since(t)
   180  	return pkg, stats, err
   181  }
   182  
   183  // loadFromExport loads a package from export data.
   184  func (prog *program) loadFromExport(spec *PackageSpec) (*Package, error) {
   185  	// log.Printf("Loading package %s from export", spec)
   186  	if spec.ExportFile == "" {
   187  		return nil, fmt.Errorf("no export data for %q", spec.ID)
   188  	}
   189  	f, err := os.Open(spec.ExportFile)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	defer f.Close()
   194  
   195  	r, err := gcexportdata.NewReader(f)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	tpkg, err := gcexportdata.Read(r, prog.fset, prog.packages, spec.PkgPath)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	pkg := &Package{
   204  		PackageSpec: spec,
   205  		Types:       tpkg,
   206  		Fset:        prog.fset,
   207  	}
   208  	// runtime.SetFinalizer(pkg, func(pkg *Package) {
   209  	// 	log.Println("Unloading package", pkg.PkgPath)
   210  	// })
   211  	return pkg, nil
   212  }
   213  
   214  // loadFromSource loads a package from source. All of its dependencies
   215  // must have been loaded already.
   216  func (prog *program) loadFromSource(spec *PackageSpec) (*Package, error) {
   217  	if len(spec.Errors) > 0 {
   218  		panic("LoadFromSource called on package with errors")
   219  	}
   220  
   221  	pkg := &Package{
   222  		PackageSpec: spec,
   223  		Types:       types.NewPackage(spec.PkgPath, spec.Name),
   224  		Syntax:      make([]*ast.File, len(spec.CompiledGoFiles)),
   225  		Fset:        prog.fset,
   226  		TypesInfo: &types.Info{
   227  			Types:      make(map[ast.Expr]types.TypeAndValue),
   228  			Defs:       make(map[*ast.Ident]types.Object),
   229  			Uses:       make(map[*ast.Ident]types.Object),
   230  			Implicits:  make(map[ast.Node]types.Object),
   231  			Scopes:     make(map[ast.Node]*types.Scope),
   232  			Selections: make(map[*ast.SelectorExpr]*types.Selection),
   233  			Instances:  map[*ast.Ident]types.Instance{},
   234  		},
   235  	}
   236  	// runtime.SetFinalizer(pkg, func(pkg *Package) {
   237  	// 	log.Println("Unloading package", pkg.PkgPath)
   238  	// })
   239  
   240  	// OPT(dh): many packages have few files, much fewer than there
   241  	// are CPU cores. Additionally, parsing each individual file is
   242  	// very fast. A naive parallel implementation of this loop won't
   243  	// be faster, and tends to be slower due to extra scheduling,
   244  	// bookkeeping and potentially false sharing of cache lines.
   245  	for i, file := range spec.CompiledGoFiles {
   246  		f, err := os.Open(file)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  		fi, err := f.Stat()
   251  		if err != nil {
   252  			return nil, err
   253  		}
   254  		if fi.Size() >= MaxFileSize {
   255  			return nil, errMaxFileSize
   256  		}
   257  		af, err := parser.ParseFile(prog.fset, file, f, parser.ParseComments|parser.SkipObjectResolution)
   258  		f.Close()
   259  		if err != nil {
   260  			pkg.Errors = append(pkg.Errors, convertError(err)...)
   261  			return pkg, nil
   262  		}
   263  		pkg.Syntax[i] = af
   264  	}
   265  	importer := func(path string) (*types.Package, error) {
   266  		if path == "unsafe" {
   267  			return types.Unsafe, nil
   268  		}
   269  		if path == "C" {
   270  			// go/packages doesn't tell us that cgo preprocessing
   271  			// failed. When we subsequently try to parse the package,
   272  			// we'll encounter the raw C import.
   273  			return nil, errors.New("cgo preprocessing failed")
   274  		}
   275  		ispecpkg := spec.Imports[path]
   276  		if ispecpkg == nil {
   277  			return nil, fmt.Errorf("trying to import %q in the context of %q returned nil PackageSpec", path, spec)
   278  		}
   279  		ipkg := prog.packages[ispecpkg.PkgPath]
   280  		if ipkg == nil {
   281  			return nil, fmt.Errorf("trying to import %q (%q) in the context of %q returned nil PackageSpec", ispecpkg.PkgPath, path, spec)
   282  		}
   283  		return ipkg, nil
   284  	}
   285  	tc := &types.Config{
   286  		Importer: importerFunc(importer),
   287  		Error: func(err error) {
   288  			pkg.Errors = append(pkg.Errors, convertError(err)...)
   289  		},
   290  	}
   291  	if spec.Module != nil && spec.Module.GoVersion != "" {
   292  		tc.GoVersion = "go" + spec.Module.GoVersion
   293  	}
   294  	types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
   295  	return pkg, nil
   296  }
   297  
   298  func convertError(err error) []packages.Error {
   299  	var errs []packages.Error
   300  	// taken from go/packages
   301  	switch err := err.(type) {
   302  	case packages.Error:
   303  		// from driver
   304  		errs = append(errs, err)
   305  
   306  	case *os.PathError:
   307  		// from parser
   308  		errs = append(errs, packages.Error{
   309  			Pos:  err.Path + ":1",
   310  			Msg:  err.Err.Error(),
   311  			Kind: packages.ParseError,
   312  		})
   313  
   314  	case scanner.ErrorList:
   315  		// from parser
   316  		for _, err := range err {
   317  			errs = append(errs, packages.Error{
   318  				Pos:  err.Pos.String(),
   319  				Msg:  err.Msg,
   320  				Kind: packages.ParseError,
   321  			})
   322  		}
   323  
   324  	case types.Error:
   325  		// from type checker
   326  		errs = append(errs, packages.Error{
   327  			Pos:  err.Fset.Position(err.Pos).String(),
   328  			Msg:  err.Msg,
   329  			Kind: packages.TypeError,
   330  		})
   331  
   332  	case config.ParseError:
   333  		errs = append(errs, packages.Error{
   334  			Pos:  fmt.Sprintf("%s:%d", err.Filename, err.Position.Line),
   335  			Msg:  fmt.Sprintf("%s (last key parsed: %q)", err.Message, err.LastKey),
   336  			Kind: packages.ParseError,
   337  		})
   338  	default:
   339  		errs = append(errs, packages.Error{
   340  			Pos:  "-",
   341  			Msg:  err.Error(),
   342  			Kind: packages.UnknownError,
   343  		})
   344  	}
   345  	return errs
   346  }
   347  
   348  type importerFunc func(path string) (*types.Package, error)
   349  
   350  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }