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

View File

@@ -71,7 +71,13 @@ Options:
-unsafe -unsafe
include unsafe symbols. 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 YAEGI_PROMPT=1
Force enable the printing of the REPL prompt and the result of last instruction, Force enable the printing of the REPL prompt and the result of last instruction,
even if stdin is not a terminal. even if stdin is not a terminal.

View File

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

View File

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

View File

@@ -2037,7 +2037,7 @@ func doCompositeBinStruct(n *node, hasType bool) {
} }
} else { } else {
fieldIndex[i] = []int{i} 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) convertLiteralValue(c.child[1], typ.Field(i).Type)
values[i] = genFunctionWrapper(c.child[1]) values[i] = genFunctionWrapper(c.child[1])
} else { } else {

View File

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

View File

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