Skip to content

Commit

Permalink
fix: allow yaegi command to interpret itself
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvertes authored Oct 9, 2020
1 parent 155ca4e commit a83ec1f
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 26 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
>
```

Expand Down
25 changes: 19 additions & 6 deletions cmd/yaegi/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"go/build"
"io/ioutil"
"os"
"strconv"
"strings"

"github.com/traefik/yaegi/interp"
Expand All @@ -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]")
Expand All @@ -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 != "" {
Expand Down
8 changes: 7 additions & 1 deletion cmd/yaegi/yaegi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 0 additions & 2 deletions interp/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 5 additions & 3 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -175,6 +176,7 @@ var Symbols = Exports{

"Interpreter": reflect.ValueOf((*Interpreter)(nil)),
"Options": reflect.ValueOf((*Options)(nil)),
"Panic": reflect.ValueOf((*Panic)(nil)),
},
}

Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion interp/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 5 additions & 10 deletions interp/src.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
1 change: 0 additions & 1 deletion interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit a83ec1f

Please sign in to comment.