Update Interpreter.Use API

### Background

#1102 changed how `Interpreter.Use` interprets export paths such that the last path component is stripped and used as the package name. This resulted in #1139 - attempting to Use an export with only one path component, such as `foo`, would result in the import path being `.`.

### Breaking API Change

This PR changes the signature of `Interpreter.Use` from `Use(Exports)` to `Use(Exports) error`.

### Fix for #1139

With this PR, if Use is called with an incomplete export path, such as `foo`, Use will return an error.
This commit is contained in:
Ethan Reesor
2021-06-24 03:00:05 -05:00
committed by GitHub
parent 36594014c9
commit 1df5dc2e93
11 changed files with 183 additions and 51 deletions

View File

@@ -48,24 +48,34 @@ func run(arg []string) error {
args := rflag.Args()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
return err
}
if err := i.Use(interp.Symbols); err != nil {
return err
}
if useSyscall {
i.Use(syscall.Symbols)
if err := i.Use(syscall.Symbols); err != nil {
return err
}
// 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 := i.Use(unsafe.Symbols); err != nil {
return err
}
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 := i.Use(unrestricted.Symbols); err != nil {
return err
}
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}

View File

@@ -117,23 +117,33 @@ func test(arg []string) (err error) {
}
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
return err
}
if err := i.Use(interp.Symbols); err != nil {
return err
}
if useSyscall {
i.Use(syscall.Symbols)
if err := i.Use(syscall.Symbols); err != nil {
return err
}
// 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 useUnrestricted {
i.Use(unrestricted.Symbols)
if err := i.Use(unrestricted.Symbols); err != nil {
return err
}
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := i.Use(unsafe.Symbols); err != nil {
return err
}
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}

View File

@@ -9,7 +9,9 @@ import (
func TestFunctionCall(t *testing.T) {
i := interp.New(interp.Options{GoPath: "./_pkg"})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
_, err := i.Eval(`import "foo/bar"`)
if err != nil {

View File

@@ -10,7 +10,9 @@ import (
func TestGetFunc(t *testing.T) {
i := interp.New(interp.Options{GoPath: "./_gopath/"})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if _, err := i.Eval(`import "github.com/foo/bar"`); err != nil {
t.Fatal(err)

View File

@@ -111,7 +111,10 @@ func TestPackages(t *testing.T) {
var stdout, stderr bytes.Buffer
i := interp.New(interp.Options{GoPath: goPath, Stdout: &stdout, Stderr: &stderr})
i.Use(stdlib.Symbols) // Use binary standard library
// Use binary standard library
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
var msg string
if test.evalFile != "" {
@@ -170,7 +173,10 @@ func TestPackagesError(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
// Init go interpreter
i := interp.New(interp.Options{GoPath: test.goPath})
i.Use(stdlib.Symbols) // Use binary standard library
// Use binary standard library
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
// Load pkg from sources
_, err := i.Eval(`import "github.com/foo/pkg"`)

View File

@@ -653,11 +653,15 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
// Use loads binary runtime symbols in the interpreter context so
// they can be used in interpreted code.
func (interp *Interpreter) Use(values Exports) {
func (interp *Interpreter) Use(values Exports) error {
for k, v := range values {
importPath := path.Dir(k)
packageName := path.Base(k)
if importPath == "." {
return fmt.Errorf("export path %[1]q is missing a package name; did you mean '%[1]s/%[1]s'?", k)
}
if importPath == selfPrefix {
interp.hooks.Parse(v)
continue
@@ -681,6 +685,7 @@ func (interp *Interpreter) Use(values Exports) {
if _, ok := values["fmt/fmt"]; ok {
fixStdio(interp)
}
return nil
}
// fixStdio redefines interpreter stdlib symbols to use the standard input,

View File

@@ -115,9 +115,15 @@ func TestInterpConsistencyBuild(t *testing.T) {
os.Stdout = w
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
i.Use(unsafe.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if err := i.Use(interp.Symbols); err != nil {
t.Fatal(err)
}
if err := i.Use(unsafe.Symbols); err != nil {
t.Fatal(err)
}
_, err = i.EvalPath(filePath)
if err != nil {
@@ -260,7 +266,9 @@ func TestInterpErrorConsistency(t *testing.T) {
filePath := filepath.Join("..", "_test", test.fileName)
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
_, errEval := i.EvalPath(filePath)
if errEval == nil {

View File

@@ -103,11 +103,14 @@ func TestEvalStar(t *testing.T) {
func TestEvalAssign(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(interp.Exports{
if err := i.Use(interp.Exports{
"testpkg/testpkg": {
"val": reflect.ValueOf(int64(11)),
},
})
}); err != nil {
t.Fatal(err)
}
_, e := i.Eval(`import "testpkg"`)
if e != nil {
t.Fatal(e)
@@ -205,7 +208,9 @@ func TestEvalFunc(t *testing.T) {
func TestEvalImport(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
runTests(t, i, []testCase{
{pre: func() { eval(t, i, `import "time"`) }, src: "2 * time.Second", res: "2s"},
})
@@ -214,7 +219,9 @@ func TestEvalImport(t *testing.T) {
func TestEvalStdout(t *testing.T) {
var out, err bytes.Buffer
i := interp.New(interp.Options{Stdout: &out, Stderr: &err})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
_, e := i.Eval(`import "fmt"; func main() { fmt.Println("hello") }`)
if e != nil {
t.Fatal(e)
@@ -227,7 +234,9 @@ func TestEvalStdout(t *testing.T) {
func TestEvalNil(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
runTests(t, i, []testCase{
{desc: "assign nil", src: "a := nil", err: "1:33: use of untyped nil"},
{desc: "return nil", pre: func() { eval(t, i, "func getNil() error {return nil}") }, src: "getNil()", res: "<nil>"},
@@ -375,7 +384,9 @@ var a = T{
func TestEvalCompositeBin0(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
eval(t, i, `
import (
"fmt"
@@ -636,7 +647,9 @@ func TestEvalCall(t *testing.T) {
func TestEvalBinCall(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if _, err := i.Eval(`import "fmt"`); err != nil {
t.Fatal(err)
}
@@ -662,9 +675,11 @@ func TestEvalMissingSymbol(t *testing.T) {
F S2
}
i := interp.New(interp.Options{})
i.Use(interp.Exports{"p/p": map[string]reflect.Value{
if err := i.Use(interp.Exports{"p/p": map[string]reflect.Value{
"S1": reflect.Zero(reflect.TypeOf(&S1{})),
}})
}}); err != nil {
t.Fatal(err)
}
_, err := i.Eval(`import "p"`)
if err != nil {
t.Fatalf("failed to import package: %v", err)
@@ -733,7 +748,9 @@ func TestEvalWithContext(t *testing.T) {
go func() {
defer close(done)
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Error(err)
}
_, err := i.Eval(`import "sync"`)
if err != nil {
t.Errorf(`failed to import "sync": %v`, err)
@@ -836,8 +853,9 @@ func TestMultiEval(t *testing.T) {
os.Stdout = w
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
var err error
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
f, err := os.Open(filepath.Join("testdata", "multi", "731"))
if err != nil {
@@ -875,8 +893,9 @@ func TestMultiEval(t *testing.T) {
func TestMultiEvalNoName(t *testing.T) {
t.Skip("fail in CI only ?")
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
var err error
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
f, err := os.Open(filepath.Join("testdata", "multi", "731"))
if err != nil {
@@ -908,7 +927,9 @@ func TestMultiEvalNoName(t *testing.T) {
func TestImportPathIsKey(t *testing.T) {
// No need to check the results of Eval, as TestFile already does it.
i := interp.New(interp.Options{GoPath: filepath.FromSlash("../_test/testdata/redeclaration-global7")})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
filePath := filepath.Join("..", "_test", "ipp_as_key.go")
if _, err := i.EvalPath(filePath); err != nil {
@@ -987,7 +1008,9 @@ func TestConcurrentEvals(t *testing.T) {
_ = pout.Close()
}()
interpr := interp.New(interp.Options{Stdout: pout})
interpr.Use(stdlib.Symbols)
if err := interpr.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if _, err := interpr.EvalPath("testdata/concurrent/hello1.go"); err != nil {
t.Fatal(err)
@@ -1046,7 +1069,9 @@ func TestConcurrentEvals2(t *testing.T) {
_ = pout.Close()
}()
interpr := interp.New(interp.Options{Stdout: pout})
interpr.Use(stdlib.Symbols)
if err := interpr.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
done := make(chan error)
go func() {
@@ -1108,7 +1133,9 @@ func TestConcurrentEvals3(t *testing.T) {
pinin, poutin := io.Pipe()
pinout, poutout := io.Pipe()
i := interp.New(interp.Options{Stdin: pinin, Stdout: poutout})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
go func() {
_, _ = i.REPL()
@@ -1187,7 +1214,9 @@ func testConcurrentComposite(t *testing.T, filePath string) {
}
pin, pout := io.Pipe()
i := interp.New(interp.Options{Stdout: pout})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
errc := make(chan error)
var output string
@@ -1411,7 +1440,9 @@ func TestREPLCommands(t *testing.T) {
pinin, poutin := io.Pipe()
pinout, poutout := io.Pipe()
i := interp.New(interp.Options{Stdin: pinin, Stdout: poutout})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
go func() {
_, _ = i.REPL()
@@ -1494,7 +1525,9 @@ func TestREPLCommands(t *testing.T) {
func TestStdio(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
i.ImportUsed()
if _, err := i.Eval(`var x = os.Stdout`); err != nil {
t.Fatal(err)
@@ -1519,11 +1552,13 @@ func (v *Issue1149Array) Bar() string { return "foo" }
func TestIssue1149(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(interp.Exports{
if err := i.Use(interp.Exports{
"pkg/pkg": map[string]reflect.Value{
"Type": reflect.ValueOf((*Issue1149Array)(nil)),
},
})
}); err != nil {
t.Fatal(err)
}
i.ImportUsed()
_, err := i.Eval(`
@@ -1577,12 +1612,14 @@ func TestIssue1151(t *testing.T) {
type pkgArray [1]int
i := interp.New(interp.Options{})
i.Use(interp.Exports{
if err := i.Use(interp.Exports{
"pkg/pkg": map[string]reflect.Value{
"Struct": reflect.ValueOf((*pkgStruct)(nil)),
"Array": reflect.ValueOf((*pkgArray)(nil)),
},
})
}); err != nil {
t.Fatal(err)
}
i.ImportUsed()
runTests(t, i, []testCase{

View File

@@ -41,14 +41,53 @@ type Wrap struct {
func (w Wrap) Hello() { w.DoHello() }
func TestExportsSemantics(t *testing.T) {
Foo := &struct{}{}
t.Run("Correct", func(t *testing.T) {
t.Skip()
i := interp.New(interp.Options{})
err := i.Use(interp.Exports{
"foo/foo": {"Foo": reflect.ValueOf(Foo)},
})
if err != nil {
t.Fatal(err)
}
i.ImportUsed()
res, err := i.Eval("foo.Foo")
if err != nil {
t.Fatal(err)
}
if res.Interface() != Foo {
t.Fatalf("expected foo.Foo to equal local Foo")
}
})
t.Run("Incorrect", func(t *testing.T) {
i := interp.New(interp.Options{})
err := i.Use(interp.Exports{
"foo": {"Foo": reflect.ValueOf(Foo)},
})
if err == nil {
t.Fatal("expected error for incorrect Use semantics")
}
})
}
func TestInterface(t *testing.T) {
i := interp.New(interp.Options{})
// export the Wrap type to the interpreter under virtual "wrap" package
i.Use(interp.Exports{
err := i.Use(interp.Exports{
"wrap/wrap": {
"Wrap": reflect.ValueOf((*Wrap)(nil)),
},
})
if err != nil {
t.Fatal(err)
}
eval(t, i, `
import "wrap"
@@ -73,11 +112,14 @@ func (t T) Bar(s ...string) {}
func TestCallBinVariadicMethod(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(interp.Exports{
err := i.Use(interp.Exports{
"mypkg/mypkg": {
"T": reflect.ValueOf((*T)(nil)),
},
})
if err != nil {
t.Fatal(err)
}
eval(t, i, `
package p

View File

@@ -55,9 +55,15 @@ func runCheck(t *testing.T, p string) {
}
var stdout, stderr bytes.Buffer
i := interp.New(interp.Options{GoPath: goPath, Stdout: &stdout, Stderr: &stderr})
i.Use(interp.Symbols)
i.Use(stdlib.Symbols)
i.Use(unsafe.Symbols)
if err := i.Use(interp.Symbols); err != nil {
t.Fatal(err)
}
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if err := i.Use(unsafe.Symbols); err != nil {
t.Fatal(err)
}
_, err := i.EvalPath(p)
if errWanted {

View File

@@ -10,8 +10,12 @@ import (
func ExampleInterpreter_self() {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if err := i.Use(stdlib.Symbols); err != nil {
log.Fatal(err)
}
if err := i.Use(interp.Symbols); err != nil {
log.Fatal(err)
}
_, err := i.Eval(`import (
"fmt"