fix: allow yaegi command to interpret itself

Since the introduction of restricted stdlib and syscall symbols, the
capability of yaegi to interpret itself was broken.
The use of unrestricted symbols is now also controlled by environment
variables, to allow propagation accross nested interpreters.
The interpreter Panic symbol was not wrapped, this is fixed now.
the import path resolution was failing if the working directory was
outside of GOPATH.
The documentation and readme have been ajusted.

Fixes #890.
This commit is contained in:
Marc Vertes
2020-10-09 11:48:04 +02:00
committed by GitHub
parent 155ca4e6ad
commit a83ec1f925
8 changed files with 49 additions and 26 deletions

View File

@@ -128,10 +128,20 @@ Hello World
>
```
Or interpret Go files:
Note that in interactive mode, all stdlib package are pre-imported,
you can use them directly:
```console
$ yaegi cmd/yaegi/yaegi.go
$ yaegi
> reflect.TypeOf(time.Date)
: func(int, time.Month, int, int, int, int, int, *time.Location) time.Time
>
```
Or interpret Go packages, directories or files, including itself:
```console
$ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi
>
```

View File

@@ -6,6 +6,7 @@ import (
"go/build"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/traefik/yaegi/interp"
@@ -17,19 +18,21 @@ import (
func run(arg []string) error {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
var err error
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
rflag := flag.NewFlagSet("run", flag.ContinueOnError)
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
rflag.BoolVar(&useSyscall, "syscall", useSyscall, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "include unrestricted symbols")
rflag.StringVar(&tags, "tags", "", "set a list of build tags")
rflag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
rflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "include unsafe symbols")
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
rflag.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]")
@@ -46,13 +49,23 @@ func run(arg []string) error {
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if useUnrestricted {
// Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them.
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if cmd != "" {

View File

@@ -71,7 +71,13 @@ Options:
-unsafe
include unsafe symbols.
Debugging support (may be removed at any time):
Environment variables:
YAEGI_SYSCALL=1
Include syscall symbols (same as -syscall flag).
YAEGI_UNRESTRICTED=1
Include unrestricted symbols (same as -unrestricted flag).
YAEGI_UNSAFE=1
Include unsafe symbols (same as -unsafe flag).
YAEGI_PROMPT=1
Force enable the printing of the REPL prompt and the result of last instruction,
even if stdin is not a terminal.

View File

@@ -2,8 +2,6 @@ package interp
import "reflect"
const hooksPath = "github.com/traefik/yaegi"
// convertFn is the signature of a symbol converter.
type convertFn func(from, to reflect.Type) func(src, dest reflect.Value)

View File

@@ -155,8 +155,9 @@ type Interpreter struct {
}
const (
mainID = "main"
selfPath = "github.com/traefik/yaegi/interp"
mainID = "main"
selfPrefix = "github.com/traefik/yaegi"
selfPath = selfPrefix + "/interp"
// DefaultSourceName is the name used by default when the name of the input
// source file has not been specified for an Eval.
// TODO(mpl): something even more special as a name?
@@ -175,6 +176,7 @@ var Symbols = Exports{
"Interpreter": reflect.ValueOf((*Interpreter)(nil)),
"Options": reflect.ValueOf((*Options)(nil)),
"Panic": reflect.ValueOf((*Panic)(nil)),
},
}
@@ -603,7 +605,7 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
// they can be used in interpreted code.
func (interp *Interpreter) Use(values Exports) {
for k, v := range values {
if k == hooksPath {
if k == selfPrefix {
interp.hooks.Parse(v)
continue
}

View File

@@ -2037,7 +2037,7 @@ func doCompositeBinStruct(n *node, hasType bool) {
}
} else {
fieldIndex[i] = []int{i}
if c.typ.cat == funcT {
if c.typ.cat == funcT && len(c.child) > 1 {
convertLiteralValue(c.child[1], typ.Field(i).Type)
values[i] = genFunctionWrapper(c.child[1])
} else {

View File

@@ -33,17 +33,12 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath)
} else {
var root string
if rPath == mainID {
root, err = interp.rootFromSourceLocation()
if err != nil {
return "", err
}
} else {
root = rPath
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
// Try again, assuming a root dir at the source location.
if rPath, err = interp.rootFromSourceLocation(); err != nil {
return "", err
}
if dir, rPath, err = pkgDir(interp.context.GOPATH, root, importPath); err != nil {
if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
return "", err
}
}

View File

@@ -559,7 +559,6 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
} else {
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
panic(err)
}
case srcPkgT:
pkg := interp.srcPkg[lt.path]