github.com/goplus/igop@v0.25.0/context.go (about)

     1  /*
     2   * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package igop
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"flag"
    23  	"fmt"
    24  	"go/ast"
    25  	"go/build"
    26  	"go/parser"
    27  	"go/token"
    28  	"go/types"
    29  	"io"
    30  	"log"
    31  	"os"
    32  	"path/filepath"
    33  	"reflect"
    34  	"runtime"
    35  	"sort"
    36  	"strconv"
    37  	"strings"
    38  	"sync"
    39  	"time"
    40  
    41  	"github.com/goplus/igop/load"
    42  	"golang.org/x/tools/go/ssa"
    43  )
    44  
    45  // Mode is a bitmask of options affecting the interpreter.
    46  type Mode uint
    47  
    48  const (
    49  	DisableRecover        Mode = 1 << iota // Disable recover() in target programs; show interpreter crash instead.
    50  	DisableCustomBuiltin                   // Disable load custom builtin func
    51  	EnableDumpImports                      // print import packages
    52  	EnableDumpInstr                        // Print packages & SSA instruction code
    53  	EnableTracing                          // Print a trace of all instructions as they are interpreted.
    54  	EnablePrintAny                         // Enable builtin print for any type ( struct/array )
    55  	EnableNoStrict                         // Enable no strict mode
    56  	ExperimentalSupportGC                  // experimental support runtime.GC
    57  	SupportMultipleInterp                  // Support multiple interp, must manual release interp reflectx icall.
    58  	CheckGopOverloadFunc                   // Check and skip gop overload func
    59  )
    60  
    61  // Loader types loader interface
    62  type Loader interface {
    63  	Import(path string) (*types.Package, error)
    64  	Installed(path string) (*Package, bool)
    65  	Packages() []*types.Package
    66  	LookupReflect(typ types.Type) (reflect.Type, bool)
    67  	LookupTypes(typ reflect.Type) (types.Type, bool)
    68  	SetImport(path string, pkg *types.Package, load func() error) error
    69  }
    70  
    71  // Context ssa context
    72  type Context struct {
    73  	Loader       Loader                                                   // types loader
    74  	BuildContext build.Context                                            // build context, default build.Default
    75  	RunContext   context.Context                                          // run context, default unset
    76  	output       io.Writer                                                // capture print/println output
    77  	FileSet      *token.FileSet                                           // file set
    78  	sizes        types.Sizes                                              // types unsafe sizes
    79  	Lookup       func(root, path string) (dir string, found bool)         // lookup external import
    80  	evalCallFn   func(interp *Interp, call *ssa.Call, res ...interface{}) // internal eval func for repl
    81  	debugFunc    func(*DebugInfo)                                         // debug func
    82  	pkgs         map[string]*sourcePackage                                // imports
    83  	override     map[string]reflect.Value                                 // override function
    84  	evalInit     map[string]bool                                          // eval init check
    85  	nestedMap    map[*types.Named]int                                     // nested named index
    86  	root         string                                                   // project root
    87  	callForPool  int                                                      // least call count for enable function pool
    88  	Mode         Mode                                                     // mode
    89  	BuilderMode  ssa.BuilderMode                                          // ssa builder mode
    90  	evalMode     bool                                                     // eval mode
    91  }
    92  
    93  func (ctx *Context) setRoot(root string) {
    94  	ctx.root = root
    95  }
    96  
    97  func (ctx *Context) lookupPath(path string) (dir string, found bool) {
    98  	if ctx.Lookup != nil {
    99  		dir, found = ctx.Lookup(ctx.root, path)
   100  	}
   101  	if !found {
   102  		bp, err := build.Import(path, ctx.root, build.FindOnly)
   103  		if err == nil && bp.ImportPath == path {
   104  			return bp.Dir, true
   105  		}
   106  	}
   107  	return
   108  }
   109  
   110  type sourcePackage struct {
   111  	Context  *Context
   112  	Package  *types.Package
   113  	Info     *types.Info
   114  	Files    []*ast.File
   115  	Links    []*load.LinkSym
   116  	Dir      string
   117  	Register bool // register package
   118  }
   119  
   120  func (sp *sourcePackage) Load() (err error) {
   121  	if sp.Info == nil {
   122  		sp.Info = newTypesInfo()
   123  		conf := &types.Config{
   124  			Sizes:    sp.Context.sizes,
   125  			Importer: NewImporter(sp.Context),
   126  		}
   127  		if sp.Context.evalMode {
   128  			conf.DisableUnusedImportCheck = true
   129  		}
   130  		if sp.Context.Mode&EnableNoStrict != 0 {
   131  			conf.Error = func(e error) {
   132  				if te, ok := e.(types.Error); ok {
   133  					if strings.HasSuffix(te.Msg, errDeclaredNotUsed) || strings.HasSuffix(te.Msg, errImportedNotUsed) {
   134  						println(fmt.Sprintf("igop warning: %v", e))
   135  						return
   136  					}
   137  				}
   138  				if err == nil {
   139  					err = e
   140  				}
   141  			}
   142  		} else {
   143  			conf.Error = func(e error) {
   144  				if err == nil {
   145  					err = e
   146  				}
   147  			}
   148  		}
   149  		types.NewChecker(conf, sp.Context.FileSet, sp.Package, sp.Info).Files(sp.Files)
   150  		if err == nil {
   151  			sp.Links, err = load.ParseLinkname(sp.Context.FileSet, sp.Package.Path(), sp.Files)
   152  		}
   153  	}
   154  	return
   155  }
   156  
   157  // NewContext create a new Context
   158  func NewContext(mode Mode) *Context {
   159  	ctx := &Context{
   160  		FileSet:      token.NewFileSet(),
   161  		Mode:         mode,
   162  		BuilderMode:  0, //ssa.SanityCheckFunctions,
   163  		BuildContext: build.Default,
   164  		pkgs:         make(map[string]*sourcePackage),
   165  		override:     make(map[string]reflect.Value),
   166  		nestedMap:    make(map[*types.Named]int),
   167  		callForPool:  64,
   168  	}
   169  	ctx.Loader = NewTypesLoader(ctx, mode)
   170  	if mode&EnableDumpInstr != 0 {
   171  		ctx.BuilderMode |= ssa.PrintFunctions
   172  	}
   173  	if mode&ExperimentalSupportGC != 0 {
   174  		ctx.RegisterExternal("runtime.GC", runtimeGC)
   175  	}
   176  	ctx.sizes = types.SizesFor("gc", runtime.GOARCH)
   177  	ctx.Lookup = new(load.ListDriver).Lookup
   178  
   179  	return ctx
   180  }
   181  
   182  func (ctx *Context) UnsafeRelease() {
   183  	ctx.pkgs = nil
   184  	ctx.Loader = nil
   185  	ctx.override = nil
   186  }
   187  
   188  func (ctx *Context) IsEvalMode() bool {
   189  	return ctx.evalMode
   190  }
   191  
   192  func (ctx *Context) SetEvalMode(b bool) {
   193  	ctx.evalMode = b
   194  }
   195  
   196  // SetUnsafeSizes set the sizing functions for package unsafe.
   197  func (ctx *Context) SetUnsafeSizes(sizes types.Sizes) {
   198  	ctx.sizes = sizes
   199  }
   200  
   201  // SetLeastCallForEnablePool set least call count for enable function pool, default 64
   202  func (ctx *Context) SetLeastCallForEnablePool(count int) {
   203  	ctx.callForPool = count
   204  }
   205  
   206  func (ctx *Context) SetDebug(fn func(*DebugInfo)) {
   207  	ctx.BuilderMode |= ssa.GlobalDebug
   208  	ctx.debugFunc = fn
   209  }
   210  
   211  // RegisterExternal register external value must variable address or func.
   212  func (ctx *Context) RegisterExternal(key string, i interface{}) {
   213  	if i == nil {
   214  		delete(ctx.override, key)
   215  		return
   216  	}
   217  	v := reflect.ValueOf(i)
   218  	switch v.Kind() {
   219  	case reflect.Func, reflect.Ptr:
   220  		ctx.override[key] = v
   221  	default:
   222  		log.Printf("register external must variable address or func. not %v\n", v.Kind())
   223  	}
   224  }
   225  
   226  // SetPrintOutput is captured builtin print/println output
   227  func (ctx *Context) SetPrintOutput(output *bytes.Buffer) {
   228  	ctx.output = output
   229  }
   230  
   231  func (ctx *Context) writeOutput(data []byte) (n int, err error) {
   232  	if ctx.output != nil {
   233  		return ctx.output.Write(data)
   234  	}
   235  	return os.Stdout.Write(data)
   236  }
   237  
   238  func (ctx *Context) LoadDir(dir string, test bool) (pkg *ssa.Package, err error) {
   239  	bp, err := ctx.BuildContext.ImportDir(dir, 0)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	importPath, err := load.GetImportPath(bp.Name, dir)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	bp.ImportPath = importPath
   248  	var sp *sourcePackage
   249  	if test {
   250  		sp, err = ctx.loadTestPackage(bp, importPath, dir)
   251  	} else {
   252  		sp, err = ctx.loadPackage(bp, importPath, dir)
   253  	}
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	if ctx.Mode&DisableCustomBuiltin == 0 {
   258  		if f, err := ParseBuiltin(ctx.FileSet, sp.Package.Name()); err == nil {
   259  			sp.Files = append([]*ast.File{f}, sp.Files...)
   260  		}
   261  	}
   262  	ctx.setRoot(dir)
   263  	if dir != "." {
   264  		if wd, err := os.Getwd(); err == nil {
   265  			os.Chdir(dir)
   266  			defer os.Chdir(wd)
   267  		}
   268  	}
   269  	err = sp.Load()
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	return ctx.buildPackage(sp)
   274  }
   275  
   276  func RegisterFileProcess(ext string, fn SourceProcessFunc) {
   277  	sourceProcessor[ext] = fn
   278  }
   279  
   280  type SourceProcessFunc func(ctx *Context, filename string, src interface{}) ([]byte, error)
   281  
   282  var (
   283  	sourceProcessor = make(map[string]SourceProcessFunc)
   284  )
   285  
   286  func (ctx *Context) AddImportFile(path string, filename string, src interface{}) (err error) {
   287  	_, err = ctx.addImportFile(path, filename, src)
   288  	return
   289  }
   290  
   291  func (ctx *Context) AddImport(path string, dir string) (err error) {
   292  	_, err = ctx.addImport(path, dir)
   293  	return
   294  }
   295  
   296  func (ctx *Context) addImportFile(path string, filename string, src interface{}) (*sourcePackage, error) {
   297  	tp, err := ctx.loadPackageFile(path, filename, src)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	ctx.Loader.SetImport(path, tp.Package, tp.Load)
   302  	return tp, nil
   303  }
   304  
   305  func (ctx *Context) addImport(path string, dir string) (*sourcePackage, error) {
   306  	bp, err := ctx.BuildContext.ImportDir(dir, 0)
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  	bp.ImportPath = path
   311  	tp, err := ctx.loadPackage(bp, path, dir)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	ctx.Loader.SetImport(path, tp.Package, tp.Load)
   316  	return tp, nil
   317  }
   318  
   319  func (ctx *Context) loadPackageFile(path string, filename string, src interface{}) (*sourcePackage, error) {
   320  	file, err := ctx.ParseFile(filename, src)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  	pkg := types.NewPackage(path, file.Name.Name)
   325  	tp := &sourcePackage{
   326  		Context: ctx,
   327  		Package: pkg,
   328  		Files:   []*ast.File{file},
   329  	}
   330  	ctx.pkgs[path] = tp
   331  	return tp, nil
   332  }
   333  
   334  func (ctx *Context) loadPackage(bp *build.Package, path string, dir string) (*sourcePackage, error) {
   335  	files, err := ctx.parseGoFiles(dir, append(bp.GoFiles, bp.CgoFiles...))
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  	embed, err := load.Embed(bp, ctx.FileSet, files, false, false)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	if embed != nil {
   344  		files = append(files, embed)
   345  	}
   346  	if bp.Name == "main" {
   347  		path = "main"
   348  	}
   349  	tp := &sourcePackage{
   350  		Package: types.NewPackage(path, bp.Name),
   351  		Files:   files,
   352  		Dir:     dir,
   353  		Context: ctx,
   354  	}
   355  	ctx.pkgs[path] = tp
   356  	return tp, nil
   357  }
   358  
   359  func (ctx *Context) loadTestPackage(bp *build.Package, path string, dir string) (*sourcePackage, error) {
   360  	if len(bp.TestGoFiles) == 0 && len(bp.XTestGoFiles) == 0 {
   361  		return nil, ErrNoTestFiles
   362  	}
   363  	files, err := ctx.parseGoFiles(dir, append(append(bp.GoFiles, bp.CgoFiles...), bp.TestGoFiles...))
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  	embed, err := load.Embed(bp, ctx.FileSet, files, true, false)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  	if embed != nil {
   372  		files = append(files, embed)
   373  	}
   374  	tp := &sourcePackage{
   375  		Package: types.NewPackage(path, bp.Name),
   376  		Files:   files,
   377  		Dir:     dir,
   378  		Context: ctx,
   379  	}
   380  	ctx.pkgs[path] = tp
   381  	if len(bp.XTestGoFiles) > 0 {
   382  		files, err := ctx.parseGoFiles(dir, bp.XTestGoFiles)
   383  		if err != nil {
   384  			return nil, err
   385  		}
   386  		embed, err := load.Embed(bp, ctx.FileSet, files, false, true)
   387  		if err != nil {
   388  			return nil, err
   389  		}
   390  		if embed != nil {
   391  			files = append(files, embed)
   392  		}
   393  		tp := &sourcePackage{
   394  			Package: types.NewPackage(path+"_test", bp.Name+"_test"),
   395  			Files:   files,
   396  			Dir:     dir,
   397  			Context: ctx,
   398  		}
   399  		ctx.pkgs[path+"_test"] = tp
   400  	}
   401  	data, err := load.TestMain(bp)
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  	f, err := parser.ParseFile(ctx.FileSet, "_testmain.go", data, parser.ParseComments)
   406  	if err != nil {
   407  		return nil, err
   408  	}
   409  	return &sourcePackage{
   410  		Package: types.NewPackage(path+".test", "main"),
   411  		Files:   []*ast.File{f},
   412  		Dir:     dir,
   413  		Context: ctx,
   414  	}, nil
   415  }
   416  
   417  func (ctx *Context) parseGoFiles(dir string, filenames []string) ([]*ast.File, error) {
   418  	files := make([]*ast.File, len(filenames))
   419  	errors := make([]error, len(filenames))
   420  
   421  	var wg sync.WaitGroup
   422  	wg.Add(len(filenames))
   423  	for i, filename := range filenames {
   424  		go func(i int, filepath string) {
   425  			defer wg.Done()
   426  			files[i], errors[i] = parser.ParseFile(ctx.FileSet, filepath, nil, parser.ParseComments)
   427  		}(i, filepath.Join(dir, filename))
   428  	}
   429  	wg.Wait()
   430  
   431  	for _, err := range errors {
   432  		if err != nil {
   433  			return nil, err
   434  		}
   435  	}
   436  	return files, nil
   437  }
   438  
   439  func (ctx *Context) LoadInterp(filename string, src interface{}) (*Interp, error) {
   440  	pkg, err := ctx.LoadFile(filename, src)
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  	return ctx.NewInterp(pkg)
   445  }
   446  
   447  func (ctx *Context) LoadFile(filename string, src interface{}) (*ssa.Package, error) {
   448  	file, err := ctx.ParseFile(filename, src)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  	root, _ := filepath.Split(filename)
   453  	ctx.setRoot(root)
   454  	return ctx.LoadAstFile(file.Name.Name, file)
   455  }
   456  
   457  func (ctx *Context) ParseFile(filename string, src interface{}) (*ast.File, error) {
   458  	if ext := filepath.Ext(filename); ext != "" {
   459  		if fn, ok := sourceProcessor[ext]; ok {
   460  			data, err := fn(ctx, filename, src)
   461  			if err != nil {
   462  				return nil, err
   463  			}
   464  			src = data
   465  		}
   466  	}
   467  	return parser.ParseFile(ctx.FileSet, filename, src, parser.ParseComments)
   468  }
   469  
   470  func (ctx *Context) LoadAstFile(path string, file *ast.File) (*ssa.Package, error) {
   471  	files := []*ast.File{file}
   472  	if ctx.Mode&DisableCustomBuiltin == 0 {
   473  		if f, err := ParseBuiltin(ctx.FileSet, file.Name.Name); err == nil {
   474  			files = []*ast.File{f, file}
   475  		}
   476  	}
   477  	dir, _ := filepath.Split(ctx.FileSet.Position(file.Package).Filename)
   478  	if dir == "" {
   479  		dir, _ = os.Getwd()
   480  	}
   481  	embed, err := load.EmbedFiles(file.Name.Name, filepath.Clean(dir), ctx.FileSet, files)
   482  	if err != nil {
   483  		return nil, err
   484  	}
   485  	if embed != nil {
   486  		files = append(files, embed)
   487  	}
   488  	sp := &sourcePackage{
   489  		Context: ctx,
   490  		Package: types.NewPackage(path, file.Name.Name),
   491  		Files:   files,
   492  	}
   493  	if err := sp.Load(); err != nil {
   494  		return nil, err
   495  	}
   496  	ctx.pkgs[path] = sp
   497  	return ctx.buildPackage(sp)
   498  }
   499  
   500  func (ctx *Context) LoadAstPackage(path string, apkg *ast.Package) (*ssa.Package, error) {
   501  	var files []*ast.File
   502  	for _, f := range apkg.Files {
   503  		files = append(files, f)
   504  	}
   505  	if ctx.Mode&DisableCustomBuiltin == 0 {
   506  		if f, err := ParseBuiltin(ctx.FileSet, apkg.Name); err == nil {
   507  			files = append([]*ast.File{f}, files...)
   508  		}
   509  	}
   510  	sp := &sourcePackage{
   511  		Context: ctx,
   512  		Package: types.NewPackage(path, apkg.Name),
   513  		Files:   files,
   514  	}
   515  	err := sp.Load()
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	return ctx.buildPackage(sp)
   520  }
   521  
   522  func (ctx *Context) RunPkg(mainPkg *ssa.Package, input string, args []string) (exitCode int, err error) {
   523  	interp, err := ctx.NewInterp(mainPkg)
   524  	if err != nil {
   525  		return 2, err
   526  	}
   527  	return ctx.RunInterp(interp, input, args)
   528  }
   529  
   530  func (ctx *Context) RunInterp(interp *Interp, input string, args []string) (exitCode int, err error) {
   531  	if ctx.RunContext != nil {
   532  		return ctx.runInterpWithContext(interp, input, args, ctx.RunContext)
   533  	}
   534  	return ctx.runInterp(interp, input, args)
   535  }
   536  
   537  func (p *Context) runInterpWithContext(interp *Interp, input string, args []string, ctx context.Context) (int, error) {
   538  	var exitCode int
   539  	var err error
   540  	ch := make(chan error, 1)
   541  	interp.cherror = make(chan PanicError)
   542  	go func() {
   543  		exitCode, err = p.runInterp(interp, input, args)
   544  		ch <- err
   545  	}()
   546  	select {
   547  	case <-ctx.Done():
   548  		interp.Abort()
   549  		select {
   550  		case <-time.After(1e9):
   551  			exitCode = 2
   552  			err = fmt.Errorf("interrupt timeout: all goroutines are asleep - deadlock!")
   553  		case <-ch:
   554  			err = ctx.Err()
   555  		}
   556  	case err = <-ch:
   557  	case e := <-interp.cherror:
   558  		switch v := e.Value.(type) {
   559  		case exitPanic:
   560  			exitCode = int(v)
   561  		default:
   562  			err = e
   563  			interp.Abort()
   564  			exitCode = 2
   565  		}
   566  	}
   567  	return exitCode, err
   568  }
   569  
   570  func (ctx *Context) runInterp(interp *Interp, input string, args []string) (exitCode int, err error) {
   571  	// reset os args and flag
   572  	os.Args = []string{input}
   573  	if args != nil {
   574  		os.Args = append(os.Args, args...)
   575  	}
   576  	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
   577  	if err = interp.RunInit(); err != nil {
   578  		return 2, err
   579  	}
   580  	return interp.RunMain()
   581  }
   582  
   583  func (ctx *Context) RunFunc(mainPkg *ssa.Package, fnname string, args ...Value) (ret Value, err error) {
   584  	interp, err := ctx.NewInterp(mainPkg)
   585  	if err != nil {
   586  		return nil, err
   587  	}
   588  	return interp.RunFunc(fnname, args...)
   589  }
   590  
   591  func (ctx *Context) NewInterp(mainPkg *ssa.Package) (*Interp, error) {
   592  	return NewInterp(ctx, mainPkg)
   593  }
   594  
   595  func (ctx *Context) TestPkg(pkg *ssa.Package, input string, args []string) error {
   596  	var failed bool
   597  	start := time.Now()
   598  	defer func() {
   599  		sec := time.Since(start).Seconds()
   600  		if failed {
   601  			fmt.Fprintf(os.Stdout, "FAIL\t%s %0.3fs\n", pkg.Pkg.Path(), sec)
   602  		} else {
   603  			fmt.Fprintf(os.Stdout, "ok\t%s %0.3fs\n", pkg.Pkg.Path(), sec)
   604  		}
   605  	}()
   606  	os.Args = []string{input}
   607  	if args != nil {
   608  		os.Args = append(os.Args, args...)
   609  	}
   610  	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
   611  	interp, err := NewInterp(ctx, pkg)
   612  	if err != nil {
   613  		failed = true
   614  		fmt.Printf("create interp failed: %v\n", err)
   615  	}
   616  	if err = interp.RunInit(); err != nil {
   617  		failed = true
   618  		fmt.Printf("init error: %v\n", err)
   619  	}
   620  	exitCode, _ := interp.RunMain()
   621  	if exitCode != 0 {
   622  		failed = true
   623  	}
   624  	if failed {
   625  		return ErrTestFailed
   626  	}
   627  	return nil
   628  }
   629  
   630  func (ctx *Context) RunFile(filename string, src interface{}, args []string) (exitCode int, err error) {
   631  	pkg, err := ctx.LoadFile(filename, src)
   632  	if err != nil {
   633  		return 2, err
   634  	}
   635  	return ctx.RunPkg(pkg, filename, args)
   636  }
   637  
   638  func (ctx *Context) Run(path string, args []string) (exitCode int, err error) {
   639  	if strings.HasSuffix(path, ".go") {
   640  		return ctx.RunFile(path, nil, args)
   641  	}
   642  	pkg, err := ctx.LoadDir(path, false)
   643  	if err != nil {
   644  		return 2, err
   645  	}
   646  	if !isMainPkg(pkg) {
   647  		return 2, ErrNotFoundMain
   648  	}
   649  	return ctx.RunPkg(pkg, path, args)
   650  }
   651  
   652  func isMainPkg(pkg *ssa.Package) bool {
   653  	return pkg.Pkg.Name() == "main" && pkg.Func("main") != nil
   654  }
   655  
   656  func (ctx *Context) RunTest(dir string, args []string) error {
   657  	pkg, err := ctx.LoadDir(dir, true)
   658  	if err != nil {
   659  		if err == ErrNoTestFiles {
   660  			fmt.Println("?", err)
   661  			return nil
   662  		}
   663  		return err
   664  	}
   665  	if filepath.IsAbs(dir) {
   666  		os.Chdir(dir)
   667  	}
   668  	return ctx.TestPkg(pkg, dir, args)
   669  }
   670  
   671  func (ctx *Context) buildPackage(sp *sourcePackage) (pkg *ssa.Package, err error) {
   672  	if ctx.Mode&DisableRecover == 0 {
   673  		defer func() {
   674  			if e := recover(); e != nil {
   675  				err = fmt.Errorf("build SSA package error: %v", e)
   676  			}
   677  		}()
   678  	}
   679  	mode := ctx.BuilderMode
   680  	if enabledTypeParam {
   681  		mode |= ssa.InstantiateGenerics
   682  	}
   683  	prog := ssa.NewProgram(ctx.FileSet, mode)
   684  	// Create SSA packages for all imports.
   685  	// Order is not significant.
   686  	created := make(map[*types.Package]bool)
   687  	var createAll func(pkgs []*types.Package)
   688  	createAll = func(pkgs []*types.Package) {
   689  		for _, p := range pkgs {
   690  			if !created[p] {
   691  				created[p] = true
   692  				createAll(p.Imports())
   693  				if pkg, ok := ctx.pkgs[p.Path()]; ok {
   694  					if ctx.Mode&EnableDumpImports != 0 {
   695  						if pkg.Dir != "" {
   696  							fmt.Println("# source", p.Path(), pkg.Dir)
   697  						} else if pkg.Register {
   698  							fmt.Println("# package", p.Path(), "<generic>")
   699  						} else {
   700  							fmt.Println("# source", p.Path(), "<memory>")
   701  						}
   702  					}
   703  					prog.CreatePackage(p, pkg.Files, pkg.Info, true).Build()
   704  					ctx.checkNested(pkg.Package, pkg.Info)
   705  				} else {
   706  					var indirect bool
   707  					if !p.Complete() {
   708  						indirect = true
   709  						p.MarkComplete()
   710  					}
   711  					if ctx.Mode&EnableDumpImports != 0 {
   712  						if indirect {
   713  							fmt.Println("# virtual", p.Path())
   714  						} else {
   715  							fmt.Println("# package", p.Path())
   716  						}
   717  					}
   718  					prog.CreatePackage(p, nil, nil, true).Build()
   719  				}
   720  			}
   721  		}
   722  	}
   723  	var addin []*types.Package
   724  	for _, pkg := range ctx.Loader.Packages() {
   725  		if !pkg.Complete() {
   726  			addin = append(addin, pkg)
   727  		}
   728  	}
   729  	if len(addin) > 0 {
   730  		sort.Slice(addin, func(i, j int) bool {
   731  			return addin[i].Path() < addin[j].Path()
   732  		})
   733  		createAll(addin)
   734  	}
   735  	createAll(sp.Package.Imports())
   736  	if ctx.Mode&EnableDumpImports != 0 {
   737  		if sp.Dir != "" {
   738  			fmt.Println("# package", sp.Package.Path(), sp.Dir)
   739  		} else {
   740  			fmt.Println("# package", sp.Package.Path(), "<source>")
   741  		}
   742  	}
   743  	// Create and build the primary package.
   744  	pkg = prog.CreatePackage(sp.Package, sp.Files, sp.Info, false)
   745  	pkg.Build()
   746  	ctx.checkNested(sp.Package, sp.Info)
   747  	return
   748  }
   749  
   750  func (ctx *Context) checkNested(pkg *types.Package, info *types.Info) {
   751  	var nestedList []*types.Named
   752  	for k, v := range info.Scopes {
   753  		switch k.(type) {
   754  		case *ast.BlockStmt, *ast.FuncType:
   755  			for _, name := range v.Names() {
   756  				obj := v.Lookup(name)
   757  				if named, ok := obj.Type().(*types.Named); ok && named.Obj().Pkg() == pkg {
   758  					nestedList = append(nestedList, named)
   759  				}
   760  			}
   761  		}
   762  	}
   763  	if len(nestedList) == 0 {
   764  		return
   765  	}
   766  	sort.Slice(nestedList, func(i, j int) bool {
   767  		return nestedList[i].Obj().Pos() < nestedList[j].Obj().Pos()
   768  	})
   769  	for i, named := range nestedList {
   770  		ctx.nestedMap[named] = i + 1
   771  	}
   772  }
   773  
   774  func RunFile(filename string, src interface{}, args []string, mode Mode) (exitCode int, err error) {
   775  	ctx := NewContext(mode)
   776  	return ctx.RunFile(filename, src, args)
   777  }
   778  
   779  func Run(path string, args []string, mode Mode) (exitCode int, err error) {
   780  	ctx := NewContext(mode)
   781  	return ctx.Run(path, args)
   782  }
   783  
   784  func RunTest(path string, args []string, mode Mode) error {
   785  	ctx := NewContext(mode)
   786  	return ctx.RunTest(path, args)
   787  }
   788  
   789  var (
   790  	builtinPkg = &Package{
   791  		Name:          "builtin",
   792  		Path:          "github.com/goplus/igop/builtin",
   793  		Deps:          make(map[string]string),
   794  		Interfaces:    map[string]reflect.Type{},
   795  		NamedTypes:    map[string]reflect.Type{},
   796  		AliasTypes:    map[string]reflect.Type{},
   797  		Vars:          map[string]reflect.Value{},
   798  		Funcs:         map[string]reflect.Value{},
   799  		TypedConsts:   map[string]TypedConst{},
   800  		UntypedConsts: map[string]UntypedConst{},
   801  	}
   802  	builtinPrefix = "Builtin_"
   803  )
   804  
   805  func init() {
   806  	RegisterPackage(builtinPkg)
   807  }
   808  
   809  func RegisterCustomBuiltin(key string, fn interface{}) error {
   810  	v := reflect.ValueOf(fn)
   811  	switch v.Kind() {
   812  	case reflect.Func:
   813  		if !strings.HasPrefix(key, builtinPrefix) {
   814  			key = builtinPrefix + key
   815  		}
   816  		builtinPkg.Funcs[key] = v
   817  		typ := v.Type()
   818  		for i := 0; i < typ.NumIn(); i++ {
   819  			checkBuiltinDeps(typ.In(i))
   820  		}
   821  		for i := 0; i < typ.NumOut(); i++ {
   822  			checkBuiltinDeps(typ.Out(i))
   823  		}
   824  		return nil
   825  	}
   826  	return ErrNoFunction
   827  }
   828  
   829  func checkBuiltinDeps(typ reflect.Type) {
   830  	if path := typ.PkgPath(); path != "" {
   831  		v := strings.Split(path, "/")
   832  		builtinPkg.Deps[path] = v[len(v)-1]
   833  	}
   834  }
   835  
   836  func builtinFuncList() []string {
   837  	var list []string
   838  	for k, v := range builtinPkg.Funcs {
   839  		if strings.HasPrefix(k, builtinPrefix) {
   840  			name := k[len(builtinPrefix):]
   841  			typ := v.Type()
   842  			var ins []string
   843  			var outs []string
   844  			var call []string
   845  			numIn := typ.NumIn()
   846  			numOut := typ.NumOut()
   847  			if typ.IsVariadic() {
   848  				for i := 0; i < numIn-1; i++ {
   849  					ins = append(ins, fmt.Sprintf("p%v %v", i, typ.In(i).String()))
   850  					call = append(call, fmt.Sprintf("p%v", i))
   851  				}
   852  				ins = append(ins, fmt.Sprintf("p%v ...%v", numIn-1, typ.In(numIn-1).Elem().String()))
   853  				call = append(call, fmt.Sprintf("p%v...", numIn-1))
   854  			} else {
   855  				for i := 0; i < numIn; i++ {
   856  					ins = append(ins, fmt.Sprintf("p%v %v", i, typ.In(i).String()))
   857  					call = append(call, fmt.Sprintf("p%v", i))
   858  				}
   859  			}
   860  			for i := 0; i < numOut; i++ {
   861  				outs = append(outs, typ.Out(i).String())
   862  			}
   863  			var str string
   864  			if numOut > 0 {
   865  				str = fmt.Sprintf("func %v(%v)(%v) { return builtin.%v(%v) }",
   866  					name, strings.Join(ins, ","), strings.Join(outs, ","), k, strings.Join(call, ","))
   867  			} else {
   868  				str = fmt.Sprintf("func %v(%v) { builtin.%v(%v) }",
   869  					name, strings.Join(ins, ","), k, strings.Join(call, ","))
   870  			}
   871  			list = append(list, str)
   872  		}
   873  	}
   874  	return list
   875  }
   876  
   877  func ParseBuiltin(fset *token.FileSet, pkg string) (*ast.File, error) {
   878  	list := builtinFuncList()
   879  	if len(list) == 0 {
   880  		return nil, os.ErrInvalid
   881  	}
   882  	var deps []string
   883  	for k := range builtinPkg.Deps {
   884  		deps = append(deps, strconv.Quote(k))
   885  	}
   886  	sort.Strings(deps)
   887  	src := fmt.Sprintf(`package %v
   888  import (
   889  	"github.com/goplus/igop/builtin"
   890  	%v
   891  )
   892  %v
   893  `, pkg, strings.Join(deps, "\n"), strings.Join(list, "\n"))
   894  	return parser.ParseFile(fset, "gossa_builtin.go", src, 0)
   895  }