go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/interpreter/interpreter.go (about)

     1  // Copyright 2018 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package interpreter contains customized Starlark interpreter.
    16  //
    17  // It is an opinionated wrapper around the basic single-file interpreter
    18  // provided by go.starlark.net library. It implements 'load' and a new built-in
    19  // 'exec' in a specific way to support running Starlark programs that consist of
    20  // many files that reference each other, thus allowing decomposing code into
    21  // small logical chunks and enabling code reuse.
    22  //
    23  // # Modules and packages
    24  //
    25  // Two main new concepts are modules and packages. A package is a collection
    26  // of Starlark files living under the same root. A module is just one such
    27  // Starlark file. Furthermore, modules can either be "library-like" (executed
    28  // via 'load' statement) or "script-like" (executed via 'exec' function).
    29  //
    30  // Library-like modules can load other library-like modules via 'load', but may
    31  // not call 'exec'. Script-like modules may use both 'load' and 'exec'.
    32  //
    33  // Modules within a single package can refer to each other (in 'load' and
    34  // 'exec') using their relative or absolute (if start with "//") paths.
    35  //
    36  // Packages also have identifiers, though they are local to the interpreter
    37  // (e.g. not defined anywhere in the package itself). For that reason they are
    38  // called "package aliases".
    39  //
    40  // Modules from one package may refer to modules from another package by using
    41  // the following syntax:
    42  //
    43  //	load("@<package alias>//<path within the package>", ...)
    44  //
    45  // Presently the mapping between a package alias (e.g. 'stdlib') and package's
    46  // source code (a collection of *.star files under a single root) is static and
    47  // supplied by Go code that uses the interpreter. This may change in the future
    48  // to allow loading packages dynamically, e.g. over the network.
    49  //
    50  // # Special packages
    51  //
    52  // There are two special package aliases recognized natively by the interpreter:
    53  // '__main__' and 'stdlib'.
    54  //
    55  // '__main__' is a conventional name of the package with top level user-supplied
    56  // code. When printing module paths in stack traces and error message,
    57  // "@__main__//<path>" are shown as simply "//<path>" to avoid confusing users
    58  // who don't know what "@<alias>//" might mean. There's no other special
    59  // handling.
    60  //
    61  // Module '@stdlib//builtins.star' (if present) is loaded before any other code.
    62  // All its exported symbols that do not start with '_' are made available in the
    63  // global namespace of all other modules (except ones loaded by
    64  // '@stdlib//builtins.star' itself). This allows to implement in Starlark
    65  // built-in global functions exposed by the interpreter.
    66  //
    67  // Exec'ing modules
    68  //
    69  // The built-in function 'exec' can be used to execute a module for its side
    70  // effects and get its global dict as a return value. It works similarly to
    71  // 'load', except it doesn't import any symbols into the caller's namespace, and
    72  // it is not idempotent: calling 'exec' on already executed module is an error.
    73  //
    74  // Each module is executed with its own instance of starlark.Thread. Modules
    75  // are either loaded as libraries (via 'load' call or LoadModule) or executed
    76  // as scripts (via 'exec' or ExecModule), but not both. Attempting to load
    77  // a module that was previous exec'ed (and vice versa) is an error.
    78  //
    79  // Consequently, each Starlark thread created by the nterpreter is either
    80  // executing some 'load' or some 'exec'. This distinction is available to
    81  // builtins through GetThreadKind function. Some builtins may check it. For
    82  // example, a builtin that mutates a global state may return an error when it is
    83  // called from a 'load' thread.
    84  //
    85  // Dicts of modules loaded via 'load' are reused, e.g. if two different scripts
    86  // load the exact same module, they'll get the exact same symbols as a result.
    87  // The loaded code always executes only once. The interpreter MAY load modules
    88  // in parallel in the future, libraries must not rely on their loading order and
    89  // must not have side effects.
    90  //
    91  // On the other hand, modules executed via 'exec' are guaranteed to be executed
    92  // sequentially, and only once. Thus 'exec'-ed scripts essentially form a tree,
    93  // traversed exactly once in the depth first order.
    94  //
    95  // # Built-in symbols
    96  //
    97  // In addition to 'exec' builtin implemented by the interpreter itself, users
    98  // of the interpreter can also supply arbitrary "predeclared" symbols they want
    99  // to be available in the global scope of all modules. Predeclared symbols and
   100  // '@stdlib//builtins.star' module explained above, are the primary mechanisms
   101  // of making the interpreter do something useful.
   102  package interpreter
   103  
   104  import (
   105  	"context"
   106  	"errors"
   107  	"fmt"
   108  	"os"
   109  	"path"
   110  	"strings"
   111  
   112  	"go.starlark.net/starlark"
   113  	"go.starlark.net/starlarkstruct"
   114  )
   115  
   116  var (
   117  	// ErrNoModule is returned by loaders when they can't find a source code of
   118  	// the requested module. It is also returned by LoadModule when it can't find
   119  	// a module within an existing package.
   120  	ErrNoModule = errors.New("no such module")
   121  
   122  	// ErrNoPackage is returned by LoadModule when it can't find a package.
   123  	ErrNoPackage = errors.New("no such package")
   124  )
   125  
   126  // ThreadKind is enumeration describing possible uses of a thread.
   127  type ThreadKind int
   128  
   129  const (
   130  	// ThreadUnknown indicates a thread used in some custom way, not via
   131  	// LoadModule or ExecModule.
   132  	ThreadUnknown ThreadKind = iota
   133  
   134  	// ThreadLoading indicates a thread that is evaluating a module referred to by
   135  	// some load(...) statement.
   136  	ThreadLoading
   137  
   138  	// ThreadExecing indicates a thread that is evaluating a module referred to by
   139  	// some exec(...) statement.
   140  	ThreadExecing
   141  )
   142  
   143  const (
   144  	// MainPkg is an alias of the package with user-supplied code.
   145  	MainPkg = "__main__"
   146  	// StdlibPkg is an alias of the package with the standard library.
   147  	StdlibPkg = "stdlib"
   148  )
   149  
   150  const (
   151  	// Key of context.Context inside starlark.Thread's local store.
   152  	threadCtxKey = "interpreter.Context"
   153  	// Key with *Interpreter that created the thread.
   154  	threadIntrKey = "interpreter.Interpreter"
   155  	// Key with TheadKind of the thread.
   156  	threadKindKey = "interpreter.ThreadKind"
   157  	// Key with the ModuleKey of the currently executing module.
   158  	threadModKey = "interpreter.ModuleKey"
   159  )
   160  
   161  // Context returns a context of the thread created through Interpreter.
   162  //
   163  // Panics if the Starlark thread wasn't created through Interpreter.Thread().
   164  func Context(th *starlark.Thread) context.Context {
   165  	if ctx := th.Local(threadCtxKey); ctx != nil {
   166  		return ctx.(context.Context)
   167  	}
   168  	panic("not an Interpreter thread, no context in its locals")
   169  }
   170  
   171  // GetThreadInterpreter returns Interpreter that created the Starlark thread.
   172  //
   173  // Panics if the Starlark thread wasn't created through Interpreter.Thread().
   174  func GetThreadInterpreter(th *starlark.Thread) *Interpreter {
   175  	if intr := th.Local(threadIntrKey); intr != nil {
   176  		return intr.(*Interpreter)
   177  	}
   178  	panic("not an Interpreter thread, no Interpreter in its locals")
   179  }
   180  
   181  // GetThreadKind tells what sort of thread 'th' is: it is either inside some
   182  // load(...), some exec(...) or some custom call (probably a callback called
   183  // from native code).
   184  //
   185  // Panics if the Starlark thread wasn't created through Interpreter.Thread().
   186  func GetThreadKind(th *starlark.Thread) ThreadKind {
   187  	if k := th.Local(threadKindKey); k != nil {
   188  		return k.(ThreadKind)
   189  	}
   190  	panic("not an Interpreter thread, no ThreadKind in its locals")
   191  }
   192  
   193  // GetThreadModuleKey returns a ModuleKey with the location of the module being
   194  // processed by a current load(...) or exec(...) statement.
   195  //
   196  // It has no relation to the module that holds the top-level stack frame. For
   197  // example, if a currently loading module 'A' calls a function in module 'B' and
   198  // this function calls GetThreadModuleKey, it will see module 'A' as the result,
   199  // even though the call goes through code in module 'B'.
   200  //
   201  // Returns nil if the current thread is not executing any load(...) or
   202  // exec(...), i.e. it has ThreadUnknown kind.
   203  func GetThreadModuleKey(th *starlark.Thread) *ModuleKey {
   204  	if modKey, ok := th.Local(threadModKey).(ModuleKey); ok {
   205  		return &modKey
   206  	}
   207  	return nil
   208  }
   209  
   210  // Loader knows how to load modules of some concrete package.
   211  //
   212  // It takes a module path relative to the package and returns either module's
   213  // dict (e.g. for go native modules) or module's source code, to be interpreted.
   214  //
   215  // Returns ErrNoModule if there's no such module in the package.
   216  //
   217  // The source code is returned as 'string' to guarantee immutability and
   218  // allowing efficient use of string Go constants.
   219  type Loader func(path string) (dict starlark.StringDict, src string, err error)
   220  
   221  // Interpreter knows how to execute starlark modules that can load or execute
   222  // other starlark modules.
   223  type Interpreter struct {
   224  	// Predeclared is a dict with predeclared symbols that are available globally.
   225  	//
   226  	// They are available when loading stdlib and when executing user modules. Can
   227  	// be used to extend the interpreter with native Go functions.
   228  	Predeclared starlark.StringDict
   229  
   230  	// Packages is a mapping from a package alias to a loader with package code.
   231  	//
   232  	// Users of Interpreter are expected to supply a loader for at least __main__
   233  	// package.
   234  	Packages map[string]Loader
   235  
   236  	// Logger is called by Starlark's print(...) function.
   237  	//
   238  	// The callback takes the position in the starlark source code where
   239  	// print(...) was called and the message it received.
   240  	//
   241  	// The default implementation just prints the message to stderr.
   242  	Logger func(file string, line int, message string)
   243  
   244  	// ThreadModifier is called whenever interpreter makes a new starlark.Thread.
   245  	//
   246  	// It can inject additional state into thread locals. Useful when hooking up
   247  	// a thread to starlarktest's reporter in unit tests.
   248  	ThreadModifier func(th *starlark.Thread)
   249  
   250  	// PreExec is called before launching code through some 'exec' or ExecModule.
   251  	//
   252  	// It may modify the thread or some other global state in preparation for
   253  	// executing a script.
   254  	//
   255  	// 'load' calls do not trigger PreExec/PostExec hooks.
   256  	PreExec func(th *starlark.Thread, module ModuleKey)
   257  
   258  	// PostExec is called after finishing running code through some 'exec' or
   259  	// ExecModule.
   260  	//
   261  	// It is always called, even if the 'exec' failed. May restore the state
   262  	// modified by PreExec. Note that PreExec/PostExec calls can nest (if an
   263  	// 'exec'-ed script calls 'exec' itself).
   264  	//
   265  	// 'load' calls do not trigger PreExec/PostExec hooks.
   266  	PostExec func(th *starlark.Thread, module ModuleKey)
   267  
   268  	modules map[ModuleKey]*loadedModule // cache of the loaded modules
   269  	execed  map[ModuleKey]struct{}      // a set of modules that were ever exec'ed
   270  	visited []ModuleKey                 // all modules, in order of visits
   271  	globals starlark.StringDict         // global symbols exposed to all modules
   272  }
   273  
   274  // ModuleKey is a key of a module within a cache of loaded modules.
   275  //
   276  // It identifies a package and a file within the package.
   277  type ModuleKey struct {
   278  	Package string // a package name, e.g. "stdlib"
   279  	Path    string // path within the package, e.g. "abc/script.star"
   280  }
   281  
   282  // String returns a fully-qualified module name to use in error messages.
   283  //
   284  // If is either "@pkg//path" or just "//path" if pkg is "__main__". We omit the
   285  // name of the top-level package with user-supplied code ("__main__") to avoid
   286  // confusing users who are oblivious of packages.
   287  func (key ModuleKey) String() string {
   288  	pkg := ""
   289  	if key.Package != MainPkg {
   290  		pkg = "@" + key.Package
   291  	}
   292  	return pkg + "//" + key.Path
   293  }
   294  
   295  // MakeModuleKey takes '[@pkg]//<path>' or '<path>', parses and normalizes it.
   296  //
   297  // Converts the path to be relative to the package root. Does some light
   298  // validation, in particular checking the resulting path doesn't start with
   299  // '../'. Module loaders are expected to validate module paths more rigorously
   300  // (since they interpret them anyway).
   301  //
   302  // 'th' is used to get the name of the currently executing package and a path to
   303  // the currently executing module within it. It is required if 'ref' is not
   304  // given as an absolute path (i.e. does NOT look like '@pkg//path').
   305  func MakeModuleKey(th *starlark.Thread, ref string) (key ModuleKey, err error) {
   306  	defer func() {
   307  		if err == nil && (strings.HasPrefix(key.Path, "../") || key.Path == "..") {
   308  			err = errors.New("outside the package root")
   309  		}
   310  	}()
   311  
   312  	// 'th' can be nil here if MakeModuleKey is called by LoadModule or
   313  	// ExecModule: they are entry points into Starlark code, there's no thread
   314  	// yet when they start.
   315  	var current *ModuleKey
   316  	if th != nil {
   317  		current = GetThreadModuleKey(th)
   318  	}
   319  
   320  	// Absolute paths start with '//' or '@'. Everything else is a relative path.
   321  	hasPkg := strings.HasPrefix(ref, "@")
   322  	isAbs := hasPkg || strings.HasPrefix(ref, "//")
   323  
   324  	if !isAbs {
   325  		if current == nil {
   326  			err = errors.New("can't resolve relative module path: no current module information in thread locals")
   327  		} else {
   328  			key = ModuleKey{
   329  				Package: current.Package,
   330  				Path:    path.Join(path.Dir(current.Path), ref),
   331  			}
   332  		}
   333  		return
   334  	}
   335  
   336  	idx := strings.Index(ref, "//")
   337  	if idx == -1 || (!hasPkg && idx != 0) {
   338  		err = errors.New("a module path should be either '//<path>', '<path>' or '@<package>//<path>'")
   339  		return
   340  	}
   341  
   342  	if hasPkg {
   343  		if key.Package = ref[1:idx]; key.Package == "" {
   344  			err = errors.New("a package alias can't be empty")
   345  			return
   346  		}
   347  	}
   348  	key.Path = path.Clean(ref[idx+2:])
   349  
   350  	// Grab the package name from thread locals, if given.
   351  	if !hasPkg {
   352  		if current == nil {
   353  			err = errors.New("no current package name in thread locals")
   354  		} else {
   355  			key.Package = current.Package
   356  		}
   357  	}
   358  	return
   359  }
   360  
   361  // loadedModule represents an executed starlark module.
   362  type loadedModule struct {
   363  	dict starlark.StringDict // global dict of the module after we executed it
   364  	err  error               // non-nil if this module could not be loaded
   365  }
   366  
   367  // Init initializes the interpreter and loads '@stdlib//builtins.star'.
   368  //
   369  // Registers whatever was passed via Predeclared plus 'exec'. Then loads
   370  // '@stdlib//builtins.star', which may define more symbols or override already
   371  // defined ones. Whatever symbols not starting with '_' end up in the global
   372  // dict of '@stdlib//builtins.star' module will become available as global
   373  // symbols in all modules.
   374  //
   375  // The context ends up available to builtins through Context(...).
   376  func (intr *Interpreter) Init(ctx context.Context) error {
   377  	intr.modules = map[ModuleKey]*loadedModule{}
   378  	intr.execed = map[ModuleKey]struct{}{}
   379  
   380  	intr.globals = make(starlark.StringDict, len(intr.Predeclared)+1)
   381  	for k, v := range intr.Predeclared {
   382  		intr.globals[k] = v
   383  	}
   384  	intr.globals["exec"] = intr.execBuiltin()
   385  
   386  	// Load the stdlib, if any.
   387  	top, err := intr.LoadModule(ctx, StdlibPkg, "builtins.star")
   388  	if err != nil && err != ErrNoModule && err != ErrNoPackage {
   389  		return err
   390  	}
   391  	for k, v := range top {
   392  		if !strings.HasPrefix(k, "_") {
   393  			intr.globals[k] = v
   394  		}
   395  	}
   396  	return nil
   397  }
   398  
   399  // LoadModule finds and loads a starlark module, returning its dict.
   400  //
   401  // This is similar to load(...) statement: caches the result of the execution.
   402  // Modules are always loaded only once.
   403  //
   404  // 'pkg' is a package alias, it will be used to lookup the package loader in
   405  // intr.Packages. 'path' is a module path (without leading '//') within
   406  // the package.
   407  //
   408  // The context ends up available to builtins through Context(...).
   409  func (intr *Interpreter) LoadModule(ctx context.Context, pkg, path string) (starlark.StringDict, error) {
   410  	key, err := MakeModuleKey(nil, fmt.Sprintf("@%s//%s", pkg, path))
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  
   415  	// If the module has been 'exec'-ed previously it is not allowed to be loaded.
   416  	// Modules are either 'library-like' or 'script-like', not both.
   417  	if _, yes := intr.execed[key]; yes {
   418  		return nil, errors.New("the module has been exec'ed before and therefore is not loadable")
   419  	}
   420  
   421  	switch m, ok := intr.modules[key]; {
   422  	case m != nil: // already loaded or attempted and failed
   423  		return m.dict, m.err
   424  	case ok:
   425  		// This module is being loaded right now, Starlark stack trace will show
   426  		// the sequence of load(...) calls that led to this cycle.
   427  		return nil, errors.New("cycle in the module dependency graph")
   428  	}
   429  
   430  	// Add a placeholder to indicate we are loading this module to detect cycles.
   431  	intr.modules[key] = nil
   432  
   433  	m := &loadedModule{
   434  		err: fmt.Errorf("panic when loading %q", key), // overwritten on non-panic
   435  	}
   436  	defer func() { intr.modules[key] = m }()
   437  
   438  	m.dict, m.err = intr.runModule(ctx, key, ThreadLoading)
   439  	return m.dict, m.err
   440  }
   441  
   442  // ExecModule finds and executes a starlark module, returning its dict.
   443  //
   444  // This is similar to exec(...) statement: always executes the module.
   445  //
   446  // 'pkg' is a package alias, it will be used to lookup the package loader in
   447  // intr.Packages. 'path' is a module path (without leading '//') within
   448  // the package.
   449  //
   450  // The context ends up available to builtins through Context(...).
   451  func (intr *Interpreter) ExecModule(ctx context.Context, pkg, path string) (starlark.StringDict, error) {
   452  	key, err := MakeModuleKey(nil, fmt.Sprintf("@%s//%s", pkg, path))
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  
   457  	// If the module has been loaded previously it is not allowed to be 'exec'-ed.
   458  	// Modules are either 'library-like' or 'script-like', not both.
   459  	if _, yes := intr.modules[key]; yes {
   460  		return nil, errors.New("the module has been loaded before and therefore is not executable")
   461  	}
   462  
   463  	// Reexecing a module is forbidden.
   464  	if _, yes := intr.execed[key]; yes {
   465  		return nil, errors.New("the module has already been executed, 'exec'-ing same code twice is forbidden")
   466  	}
   467  	intr.execed[key] = struct{}{}
   468  
   469  	// Actually execute the code.
   470  	return intr.runModule(ctx, key, ThreadExecing)
   471  }
   472  
   473  // Visited returns a list of modules visited by the interpreter.
   474  //
   475  // Includes both loaded and execed modules, successfully or not.
   476  func (intr *Interpreter) Visited() []ModuleKey {
   477  	return intr.visited
   478  }
   479  
   480  // LoadSource returns a body of a file inside a package.
   481  //
   482  // It doesn't have to be a Starlark file, can be any text file as long as
   483  // the corresponding package Loader can find it.
   484  //
   485  // 'ref' is either an absolute reference to the file, in the same format as
   486  // accepted by 'load' and 'exec' (i.e. "[@pkg]//path"), or a path relative to
   487  // the currently executing module (i.e. just "path").
   488  //
   489  // Only Starlark threads started via LoadModule or ExecModule can be used with
   490  // this function. Other threads don't have enough context to resolve paths
   491  // correctly.
   492  func (intr *Interpreter) LoadSource(th *starlark.Thread, ref string) (string, error) {
   493  	if kind := GetThreadKind(th); kind != ThreadLoading && kind != ThreadExecing {
   494  		return "", errors.New("wrong kind of thread (not enough information to " +
   495  			"resolve the file reference), only threads that do 'load' and 'exec' can call this function")
   496  	}
   497  
   498  	target, err := MakeModuleKey(th, ref)
   499  	if err != nil {
   500  		return "", err
   501  	}
   502  
   503  	dict, src, err := intr.invokeLoader(target)
   504  	if err == nil && dict != nil {
   505  		err = fmt.Errorf("it is a native Go module")
   506  	}
   507  	if err != nil {
   508  		// Avoid term "module" since LoadSource is often used to load non-star
   509  		// files.
   510  		if err == ErrNoModule {
   511  			err = fmt.Errorf("no such file")
   512  		}
   513  		return "", fmt.Errorf("cannot load %s: %s", target, err)
   514  	}
   515  	return src, nil
   516  }
   517  
   518  // Thread creates a new Starlark thread associated with the given context.
   519  //
   520  // Thread() can be used, for example, to invoke callbacks registered by the
   521  // loaded Starlark code.
   522  //
   523  // The context ends up available to builtins through Context(...).
   524  //
   525  // The returned thread has no implementation of load(...) or exec(...). Use
   526  // LoadModule or ExecModule to load top-level Starlark code instead. Note that
   527  // load(...) statements are forbidden inside Starlark functions anyway.
   528  func (intr *Interpreter) Thread(ctx context.Context) *starlark.Thread {
   529  	th := &starlark.Thread{
   530  		Print: func(th *starlark.Thread, msg string) {
   531  			position := th.CallFrame(1).Pos
   532  			if intr.Logger != nil {
   533  				intr.Logger(position.Filename(), int(position.Line), msg)
   534  			} else {
   535  				fmt.Fprintf(os.Stderr, "[%s:%d] %s\n", position.Filename(), position.Line, msg)
   536  			}
   537  		},
   538  	}
   539  	th.SetLocal(threadCtxKey, ctx)
   540  	th.SetLocal(threadIntrKey, intr)
   541  	th.SetLocal(threadKindKey, ThreadUnknown)
   542  	if intr.ThreadModifier != nil {
   543  		intr.ThreadModifier(th)
   544  	}
   545  	return th
   546  }
   547  
   548  // execBuiltin returns exec(...) builtin symbol.
   549  func (intr *Interpreter) execBuiltin() *starlark.Builtin {
   550  	return starlark.NewBuiltin("exec", func(th *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   551  		var module starlark.String
   552  		if err := starlark.UnpackArgs("exec", args, kwargs, "module", &module); err != nil {
   553  			return nil, err
   554  		}
   555  		mod := module.GoString()
   556  
   557  		// Only threads started via 'exec' (or equivalently ExecModule) can exec
   558  		// other scripts. Modules that are loaded via load(...), or custom callbacks
   559  		// from native code aren't allowed to call exec, since exec's impurity may
   560  		// lead to unexpected results.
   561  		if GetThreadKind(th) != ThreadExecing {
   562  			return nil, fmt.Errorf("exec %s: forbidden in this context, only exec'ed scripts can exec other scripts", mod)
   563  		}
   564  
   565  		// See also th.Load in runModule.
   566  		key, err := MakeModuleKey(th, mod)
   567  		if err != nil {
   568  			return nil, err
   569  		}
   570  		dict, err := intr.ExecModule(Context(th), key.Package, key.Path)
   571  		if err != nil {
   572  			// Starlark stringifies errors using Error(). For EvalError, Error()
   573  			// string is just a root cause, it does not include the backtrace where
   574  			// the execution failed. Preserve it explicitly by sticking the backtrace
   575  			// into the error message.
   576  			//
   577  			// Note that returning 'err' as is will preserve the backtrace inside
   578  			// the execed module, but we'll lose the backtrace of the 'exec' call
   579  			// itself, since EvalError object will just bubble to the top of the Go
   580  			// call stack unmodified.
   581  			if evalErr, ok := err.(*starlark.EvalError); ok {
   582  				return nil, fmt.Errorf("exec %s failed: %s", mod, evalErr.Backtrace())
   583  			}
   584  			return nil, fmt.Errorf("cannot exec %s: %s", mod, err)
   585  		}
   586  
   587  		return starlarkstruct.FromStringDict(starlark.String("execed"), dict), nil
   588  	})
   589  }
   590  
   591  // invokeLoader loads the module via the loader associated with the package.
   592  func (intr *Interpreter) invokeLoader(key ModuleKey) (dict starlark.StringDict, src string, err error) {
   593  	loader, ok := intr.Packages[key.Package]
   594  	if !ok {
   595  		return nil, "", ErrNoPackage
   596  	}
   597  	return loader(key.Path)
   598  }
   599  
   600  // runModule really loads and executes the module, used by both LoadModule and
   601  // ExecModule.
   602  func (intr *Interpreter) runModule(ctx context.Context, key ModuleKey, kind ThreadKind) (starlark.StringDict, error) {
   603  	// Grab the source code.
   604  	dict, src, err := intr.invokeLoader(key)
   605  	switch {
   606  	case err != nil:
   607  		return nil, err
   608  	case dict != nil:
   609  		// This is a native module constructed in Go, no need to interpret it.
   610  		return dict, nil
   611  	}
   612  
   613  	// Otherwise make a thread for executing the code. We do not reuse threads
   614  	// between modules. All global state is passed through intr.globals,
   615  	// intr.modules and ctx.
   616  	th := intr.Thread(ctx)
   617  	th.Load = func(th *starlark.Thread, module string) (starlark.StringDict, error) {
   618  		key, err := MakeModuleKey(th, module)
   619  		if err != nil {
   620  			return nil, err
   621  		}
   622  		dict, err := intr.LoadModule(ctx, key.Package, key.Path)
   623  		// See comment in execBuiltin about why we extract EvalError backtrace into
   624  		// new error.
   625  		if evalErr, ok := err.(*starlark.EvalError); ok {
   626  			err = fmt.Errorf("%s", evalErr.Backtrace())
   627  		}
   628  		return dict, err
   629  	}
   630  
   631  	// Let builtins know what this thread is doing. Some calls (most notably Exec
   632  	// itself) are allowed only from exec'ing threads, not from load'ing ones.
   633  	th.SetLocal(threadKindKey, kind)
   634  	// Let builtins (and in particular MakeModuleKey and LoadSource) know the
   635  	// package and the module that the thread executes.
   636  	th.SetLocal(threadModKey, key)
   637  
   638  	if kind == ThreadExecing {
   639  		if intr.PreExec != nil {
   640  			intr.PreExec(th, key)
   641  		}
   642  		if intr.PostExec != nil {
   643  			defer intr.PostExec(th, key)
   644  		}
   645  	}
   646  
   647  	// Record we've been here.
   648  	intr.visited = append(intr.visited, key)
   649  
   650  	// Execute the module. It may 'load' or 'exec' other modules inside, which
   651  	// will either call LoadModule or ExecModule.
   652  	//
   653  	// Use user-friendly module name (with omitted "@__main__") for error messages
   654  	// and stack traces to avoid confusing the user.
   655  	return starlark.ExecFile(th, key.String(), src, intr.globals)
   656  }