Compare commits

...

17 Commits

Author SHA1 Message Date
mpl
563270ca02 interp: support yet another vendoring case
* interp: support another vendoring case

Namely, when the vendor dir is a sibling (or an uncle) relative to the
current pkg

Fixes #758

* make linter happier

* address review comments

* fix, cleanup, add unit tests

* add dummy files to force dirs into git
2020-07-15 15:35:04 +02:00
Marc Vertes
5eecbe515b fix: compositeSparse handles fields of interface kind
Fixes #776.
2020-07-13 17:55:04 +02:00
Marc Vertes
0a79069dfc fix: correct control flow graph for range init expression
The range init AST execution was skipped, and range could work
only over variables or direct function calls. By setting the
start node to the start of init and not init itself, we ensure
that the init AST is always taken into account.

Fixes #775.
2020-07-13 15:35:04 +02:00
Marc Vertes
0c8f538cd9 fix: apply method receiver offset when generating interface wrapper
Fixes #772.
2020-07-12 14:20:03 +02:00
Nicholas Wiersma
ca80ada849 fix: deal with untyped in type check 2020-07-10 11:55:04 +02:00
Nicholas Wiersma
3c6df504df fix: dont allow calling init 2020-07-09 14:35:04 +02:00
Nicholas Wiersma
98eacf3610 fix: execute global variables in the correct order
* fix: constant definition loop on out of order vars

* fix: do not wire global varDecl

* fix: wire and execute global vars

* chore: add tests

* fix: refactor and lint
2020-07-09 14:05:03 +02:00
Marc Vertes
16ff52a949 fix: avoid a panic in CFG in case of incomplete type
By returning early in case of incomplete type in CFG, we avoid
a panic, and let a chance to a new attempt after the missing
type has been parsed.

Fixes 763.
2020-07-09 13:05:04 +02:00
Marc Vertes
640d1429e5 fix: type assert when status is _
If the status is _, there is no storage allocated in frame, and
the status assign operation should be skipped.

Fixes #761.
2020-07-09 08:45:03 +02:00
Nicholas Wiersma
659913eebe fix: convert type properly to the correct type 2020-07-08 22:55:03 +02:00
Marc Vertes
b3766509cc feature: restrict symbols which can exit the interpreter process
* feature: restrict symbols which can exit the interpreter process

Some symbols such as os.Exit or log.Fatal, which make the current process
to exit, are now restricted. They are replaced by a version which panics
instead of exiting, as panics are recovered by Eval.

The restricted os.FindProcess version is identical to the original
except it errors when trying to return the self process, in order to
forbid killing or signaling the interpreter process from script.

The os/exec symbols are available only through unrestricted package.

The original symbols are stored in an unrestricted package, which
requires an explicit Use, as for unsafe and syscall packages.

The Use() interpreter method has been slightly modified to allow inplace
updating of package symbols, allowing to replace some symbols but not
the entire imported package.

A command line option -unrestricted has been added to yaegi CLI to use
the unrestricted symbols.

Fixes #486.

* fix: lint
2020-07-08 22:35:04 +02:00
Nicholas Wiersma
bc2b224bae fix: make a copy of defined before detecting recursivness 2020-07-07 12:05:03 +02:00
Nicholas Wiersma
9d4685deea fix: handle interfaces in composite sparce (#749)
Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-07-06 15:41:27 +02:00
Marc Vertes
2a70a71dc2 fix: avoid infinite loop when parsing recursive types
Mark all visited types as such when walking down struct fields.

Fixes #750.
Updates #652.
2020-07-06 15:30:04 +02:00
Nicholas Wiersma
851444453c fix: assert switch type from valueT in struct case (#747)
* fix: switch type from valueT in struct case

In a struct case in type assertion, if the source is a valueT, we still need to take the struct type to allow method and field resolution.

* fix: handle all ptr structs as well
2020-07-06 15:09:48 +02:00
Nicholas Wiersma
a8b1c2a017 fix: a const, non-const equality check must convert 2020-07-06 11:25:03 +02:00
Nicholas Wiersma
d229c2a2c7 fix: handle Func Value in genFunctionWrapper params
* fix: handle Func value in func wrapper params

* fix: lint
2020-07-03 12:25:04 +02:00
51 changed files with 1046 additions and 451 deletions

14
_test/comp2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
type delta int32
func main() {
a := delta(-1)
println(a != -1)
println(a == -1)
}
// Output:
// false
// true

15
_test/composite11.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"image/color"
)
func main() {
c := color.NRGBA64{1, 1, 1, 1}
fmt.Println(c)
}
// Output:
// {1 1 1 1}

17
_test/const15.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type T1 t1
type t1 int8
const (
P2 T1 = 2
P3 T1 = 3
)
func main() {
println(P3)
}
// Output:
// 3

12
_test/init1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func init() {
println("here")
}
func main() {
init()
}
// Error:
// _test/init1.go:8:2: undefined: init

19
_test/interface44.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type S struct {
a int
}
func main() {
var i interface{} = S{a: 1}
s, ok := i.(S)
if !ok {
println("bad")
return
}
println(s.a)
}
// Output:
// 1

13
_test/interface45.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "fmt"
func main() {
var i interface{} = 1
var s struct{}
s, _ = i.(struct{})
fmt.Println(s)
}
// Output:
// {}

31
_test/issue-772.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"log"
"os"
"text/template"
)
type Message struct {
Data string
}
func main() {
tmpl := template.New("name")
_, err := tmpl.Parse("{{.Data}}")
if err != nil {
log.Fatal(err)
}
err = tmpl.Execute(os.Stdout, Message{
Data: "Hello, World!!",
})
if err != nil {
log.Fatal(err)
}
}
// Output:
// Hello, World!!

18
_test/issue-775.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"net/http/httptest"
)
func main() {
recorder := httptest.NewRecorder()
recorder.Header().Add("Foo", "Bar")
for key, value := range recorder.Header() {
fmt.Println(key, value)
}
}
// Output:
// Foo [Bar]

39
_test/issue-776.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
// Filter is a filter
type Filter interface {
Foo()
}
// GIFT is a gift
type GIFT struct {
Filters []Filter
}
// New is a new filter list
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
}
}
// List lists filters
func (g *GIFT) List() {
fmt.Printf("Hello from List!\n")
}
// MyFilter is one of the filters
type MyFilter struct{}
// Foo is a foo
func (f *MyFilter) Foo() {}
func main() {
g := New(&MyFilter{})
g.List()
}
// Output:
// Hello from List!

12
_test/math2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const c uint64 = 2
func main() {
if c&(1<<(uint64(1))) > 0 {
println("yes")
}
}
// Output:
// yes

18
_test/restricted0.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
log.Fatal("log.Fatal does not exit")
println("not printed")
}
// Output:
// recover: log.Fatal does not exit

18
_test/restricted1.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"os"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
os.Exit(1)
println("not printed")
}
// Output:
// recover: os.Exit(1)

14
_test/restricted2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
p, err := os.FindProcess(os.Getpid())
fmt.Println(p, err)
}
// Output:
// <nil> restricted

23
_test/restricted3.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bytes"
"fmt"
"log"
)
var (
buf bytes.Buffer
logger = log.New(&buf, "logger: ", log.Lshortfile)
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r, buf.String())
}()
logger.Fatal("test log")
}
// Output:
// recover: test log logger: restricted.go:39: test log

23
_test/struct53.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "fmt"
type T1 struct {
P []*T
}
type T2 struct {
P2 *T
}
type T struct {
*T1
S1 *T
}
func main() {
fmt.Println(T2{})
}
// Output:
// {<nil>}

26
_test/struct54.go Normal file
View File

@@ -0,0 +1,26 @@
package main
type S struct {
t *T
}
func newS() *S {
return &S{
t: &T{u: map[string]*U{}},
}
}
type T struct {
u map[string]*U
}
type U struct {
a int
}
func main() {
s := newS()
_ = s
println("ok")
}

44
_test/type25.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"errors"
"sync/atomic"
)
type wrappedError struct {
wrapped error
}
func (e wrappedError) Error() string {
return "some outer error"
}
func (e wrappedError) Unwrap() error {
return e.wrapped
}
var err atomic.Value
func getWrapped() *wrappedError {
if v := err.Load(); v != nil {
err := v.(wrappedError)
if err.wrapped != nil {
return &err
}
}
return nil
}
func main() {
err.Store(wrappedError{wrapped: errors.New("test")})
e := getWrapped()
if e != nil {
println(e.Error())
println(e.wrapped.Error())
}
}
// Output:
// some outer error
// test

44
_test/type26.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"errors"
"sync/atomic"
)
type wrappedError struct {
wrapped error
}
func (e *wrappedError) Error() string {
return "some outer error"
}
func (e *wrappedError) Unwrap() error {
return e.wrapped
}
var err atomic.Value
func getWrapped() *wrappedError {
if v := err.Load(); v != nil {
err := v.(*wrappedError)
if err.wrapped != nil {
return err
}
}
return nil
}
func main() {
err.Store(&wrappedError{wrapped: errors.New("test")})
e := getWrapped()
if e != nil {
println(e.Error())
println(e.wrapped.Error())
}
}
// Output:
// some outer error
// test

13
_test/var12.go Normal file
View File

@@ -0,0 +1,13 @@
package main
var (
a = b
b = "hello"
)
func main() {
println(a)
}
// Output:
// hello

23
_test/var13.go Normal file
View File

@@ -0,0 +1,23 @@
package main
var (
a = concat("hello", b)
b = concat(" ", c, "!")
c = d
d = "world"
)
func concat(a ...string) string {
var s string
for _, ss := range a {
s += ss
}
return s
}
func main() {
println(a)
}
// Output:
// hello world!

10
_test/var14.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "github.com/containous/yaegi/_test/vars"
func main() {
println(vars.A)
}
// Output:
// hello world!

14
_test/vars/first.go Normal file
View File

@@ -0,0 +1,14 @@
package vars
var (
A = concat("hello", B)
C = D
)
func concat(a ...string) string {
var s string
for _, ss := range a {
s += ss
}
return s
}

6
_test/vars/second.go Normal file
View File

@@ -0,0 +1,6 @@
package vars
var (
B = concat(" ", C, "!")
D = "world"
)

View File

@@ -115,6 +115,17 @@ type Wrap struct {
Method []Method
}
// restricted map defines symbols for which a special implementation is provided.
var restricted = map[string]bool{
"osExit": true,
"osFindProcess": true,
"logFatal": true,
"logFatalf": true,
"logFatalln": true,
"logLogger": true,
"logNew": true,
}
func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, error) {
p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil {
@@ -150,6 +161,10 @@ func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, er
if skip[pname] {
continue
}
if rname := path.Base(pkgName) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
}
switch o := o.(type) {
case *types.Const:

View File

@@ -96,17 +96,20 @@ import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
)
func main() {
var interactive bool
var useUnsafe bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
flag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
@@ -128,6 +131,10 @@ func main() {
if useUnsafe {
i.Use(unsafe.Symbols)
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
i.Use(unrestricted.Symbols)
}
if cmd != `` {
i.REPL(strings.NewReader(cmd), os.Stderr)

View File

@@ -0,0 +1,11 @@
package main
import (
"fmt"
"guthib.com/foo/pkg"
)
func main() {
fmt.Printf("%s", pkg.NewSample()())
}

View File

@@ -0,0 +1,17 @@
package pkg
import (
"fmt"
"guthib.com/bar"
)
func Here() string {
return "hello"
}
func NewSample() func() string {
return func() string {
return fmt.Sprintf("%s %s", bar.Bar(), Here())
}
}

View File

@@ -0,0 +1,6 @@
package bar
// Bar is bar
func Bar() string {
return "Yo"
}

View File

@@ -83,6 +83,18 @@ func TestPackages(t *testing.T) {
expected: "Fromage",
evalFile: "./_pkg11/src/foo/foo.go",
},
{
desc: "vendor dir is a sibling or an uncle",
goPath: "./_pkg12/",
expected: "Yo hello",
topImport: "guthib.com/foo/pkg",
},
{
desc: "eval main with vendor as a sibling",
goPath: "./_pkg12/",
expected: "Yo hello",
evalFile: "./_pkg12/src/guthib.com/foo/main.go",
},
}
for _, test := range testCases {
@@ -116,7 +128,7 @@ func TestPackages(t *testing.T) {
os.Stdout = pw
if _, err := i.Eval(string(data)); err != nil {
t.Fatal(err)
fatalStderrf(t, "%v", err)
}
var buf bytes.Buffer
@@ -127,10 +139,10 @@ func TestPackages(t *testing.T) {
}()
if err := pw.Close(); err != nil {
t.Fatal(err)
fatalStderrf(t, "%v", err)
}
if err := <-errC; err != nil {
t.Fatal(err)
fatalStderrf(t, "%v", err)
}
msg = buf.String()
} else {
@@ -153,12 +165,17 @@ func TestPackages(t *testing.T) {
}
if msg != test.expected {
t.Errorf("Got %q, want %q", msg, test.expected)
fatalStderrf(t, "Got %q, want %q", msg, test.expected)
}
})
}
}
func fatalStderrf(t *testing.T, format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
t.FailNow()
}
func TestPackagesError(t *testing.T) {
testCases := []struct {
desc string

View File

@@ -412,6 +412,8 @@ func {{$name}}(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
@@ -432,7 +434,7 @@ func {{$name}}(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
@@ -454,7 +456,7 @@ func {{$name}}(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
@@ -478,13 +480,14 @@ func {{$name}}(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
}
return
}
{{- end}}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):

View File

@@ -476,6 +476,9 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest.typ = sc.fixType(src.typ)
}
}
if dest.typ.incomplete {
return
}
if dest.typ.sizedef {
dest.typ.size = arrayTypeLen(src)
dest.typ.rtype = nil
@@ -696,6 +699,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if !isShiftNode(n) && !isConstVal(c) && !c0.typ.equals(c1.typ) && t0 != nil && t1 != nil {
switch {
case isConstVal(c0) && isNumber(t1) || isConstVal(c1) && isNumber(t0): // const <-> numberic case
case isNumber(t0) && isNumber(t1) && (c0.typ.untyped || c1.typ.untyped):
case t0.Kind() == reflect.Uint8 && t1.Kind() == reflect.Int32 || t1.Kind() == reflect.Uint8 && t0.Kind() == reflect.Int32: // byte <-> rune case
case isInterface(c0.typ) && isInterface(c1.typ): // interface <-> interface case
default:
@@ -732,6 +736,18 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.typ = c0.typ
case aEqual, aNotEqual:
n.typ = sc.getType("bool")
if isConstVal(c0) && !isConstVal(c1) || !isConstVal(c0) && isConstVal(c1) {
// if either node is a constant value, but the other is not, the constant
// must be converted into the non-constants type.
switch {
case isConstVal(c0):
convertConstantValue(c0)
c0.rval = c0.rval.Convert(c1.typ.TypeOf())
default:
convertConstantValue(c1)
c1.rval = c1.rval.Convert(c0.typ.TypeOf())
}
}
if n.child[0].sym == nilSym || n.child[1].sym == nilSym {
if n.action == aEqual {
n.gen = isNil
@@ -830,7 +846,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
sc = sc.pop()
case constDecl, varDecl:
case constDecl:
wireChild(n)
case varDecl:
// Global varDecl do not need to be wired as this
// will be handled after cfg.
if n.anc.kind == fileStmt {
break
}
wireChild(n)
case declStmt, exprStmt, sendStmt:
@@ -1018,7 +1042,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case fileStmt:
wireChild(n)
wireChild(n, varDecl)
sc = sc.pop()
n.findex = -1
@@ -1130,11 +1154,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.types = sc.types
sc = sc.pop()
funcName := n.child[1].ident
if !isMethod(n) {
interp.scopes[pkgID].sym[funcName].index = -1 // to force value to n.val
interp.scopes[pkgID].sym[funcName].typ = n.typ
interp.scopes[pkgID].sym[funcName].kind = funcSym
interp.scopes[pkgID].sym[funcName].node = n
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
sym.index = -1 // to force value to n.val
sym.typ = n.typ
sym.kind = funcSym
sym.node = n
}
case funcLit:
@@ -1149,6 +1173,10 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if isKey(n) || isNewDefine(n, sc) {
break
}
if n.anc.kind == funcDecl && n.anc.child[1] == n {
// Dont process a function name identExpr.
break
}
sym, level, found := sc.lookup(n.ident)
if !found {
@@ -1306,7 +1334,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case rangeStmt:
if sc.rangeChanType(n) != nil {
n.start = n.child[1] // Get chan
n.start = n.child[1].start // Get chan
n.child[1].tnext = n // then go to range function
n.tnext = n.child[2].start // then go to range body
n.child[2].tnext = n // then body go to range function (loop)
@@ -1318,7 +1346,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
} else {
k, o, body = n.child[0], n.child[1], n.child[2]
}
n.start = o // Get array or map object
n.start = o.start // Get array or map object
o.tnext = k.start // then go to iterator init
k.tnext = n // then go to range function
n.tnext = body.start // then go to range body
@@ -1703,7 +1731,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
if n.anc.action != aAssignX {
if n.child[0].typ.cat == valueT {
if n.child[0].typ.cat == valueT && !isStruct(n.child[1].typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: n.child[1].typ.TypeOf()}
} else {
@@ -1926,6 +1954,90 @@ func genRun(nod *node) error {
return err
}
func genGlobalVars(roots []*node, sc *scope) (*node, error) {
var vars []*node
for _, n := range roots {
vars = append(vars, getVars(n)...)
}
if len(vars) == 0 {
return nil, nil
}
varNode, err := genGlobalVarDecl(vars, sc)
if err != nil {
return nil, err
}
setExec(varNode.start)
return varNode, nil
}
func getVars(n *node) (vars []*node) {
for _, child := range n.child {
if child.kind == varDecl {
vars = append(vars, child.child...)
}
}
return vars
}
func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) {
varNode := &node{kind: varDecl, action: aNop, gen: nop}
deps := map[*node][]*node{}
for _, n := range nodes {
deps[n] = getVarDependencies(n, sc)
}
inited := map[*node]bool{}
revisit := []*node{}
for {
for _, n := range nodes {
canInit := true
for _, d := range deps[n] {
if !inited[d] {
canInit = false
}
}
if !canInit {
revisit = append(revisit, n)
continue
}
varNode.child = append(varNode.child, n)
inited[n] = true
}
if len(revisit) == 0 || equalNodes(nodes, revisit) {
break
}
nodes = revisit
revisit = []*node{}
}
if len(revisit) > 0 {
return nil, revisit[0].cfgErrorf("variable definition loop")
}
wireChild(varNode)
return varNode, nil
}
func getVarDependencies(nod *node, sc *scope) (deps []*node) {
nod.Walk(func(n *node) bool {
if n.kind == identExpr {
if sym, _, ok := sc.lookup(n.ident); ok {
if sym.kind != varSym || !sym.global || sym.node == nod {
return false
}
deps = append(deps, sym.node)
}
}
return true
}, nil)
return deps
}
// setFnext sets the cond fnext field to next, propagates it for parenthesis blocks
// and sets the action to branch.
func setFNext(cond, next *node) {
@@ -1985,47 +2097,68 @@ func (n *node) isType(sc *scope) bool {
}
// wireChild wires AST nodes for CFG in subtree.
func wireChild(n *node) {
func wireChild(n *node, exclude ...nkind) {
child := excludeNodeKind(n.child, exclude)
// Set start node, in subtree (propagated to ancestors by post-order processing)
for _, child := range n.child {
switch child.kind {
for _, c := range child {
switch c.kind {
case arrayType, chanType, chanTypeRecv, chanTypeSend, funcDecl, importDecl, mapType, basicLit, identExpr, typeDecl:
continue
default:
n.start = child.start
n.start = c.start
}
break
}
// Chain sequential operations inside a block (next is right sibling)
for i := 1; i < len(n.child); i++ {
switch n.child[i].kind {
for i := 1; i < len(child); i++ {
switch child[i].kind {
case funcDecl:
n.child[i-1].tnext = n.child[i]
child[i-1].tnext = child[i]
default:
switch n.child[i-1].kind {
switch child[i-1].kind {
case breakStmt, continueStmt, gotoStmt, returnStmt:
// tnext is already computed, no change
default:
n.child[i-1].tnext = n.child[i].start
child[i-1].tnext = child[i].start
}
}
}
// Chain subtree next to self
for i := len(n.child) - 1; i >= 0; i-- {
switch n.child[i].kind {
for i := len(child) - 1; i >= 0; i-- {
switch child[i].kind {
case arrayType, chanType, chanTypeRecv, chanTypeSend, importDecl, mapType, funcDecl, basicLit, identExpr, typeDecl:
continue
case breakStmt, continueStmt, gotoStmt, returnStmt:
// tnext is already computed, no change
default:
n.child[i].tnext = n
child[i].tnext = n
}
break
}
}
func excludeNodeKind(child []*node, kinds []nkind) []*node {
if len(kinds) == 0 {
return child
}
var res []*node
for _, c := range child {
exclude := false
for _, k := range kinds {
if c.kind == k {
exclude = true
}
}
if !exclude {
res = append(res, c)
}
}
return res
}
func (n *node) name() (s string) {
switch {
case n.ident != "":

View File

@@ -78,8 +78,8 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if typ.isBinMethod {
typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true, scope: sc}
}
if sc.sym[dest.ident] == nil {
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val}
if sc.sym[dest.ident] == nil || sc.sym[dest.ident].typ.incomplete {
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n}
}
if n.anc.kind == constDecl {
sc.sym[dest.ident].kind = constSym
@@ -112,7 +112,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
sym1, exists1 := sc.sym[asImportName]
sym2, exists2 := sc.sym[c.ident]
if !exists1 && !exists2 {
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ}
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n}
continue
}
@@ -138,11 +138,13 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
return false
}
if isMethod(n) {
ident := n.child[1].ident
switch {
case isMethod(n):
// TODO(mpl): redeclaration detection
// Add a method symbol in the receiver type name space
var rcvrtype *itype
n.ident = n.child[1].ident
n.ident = ident
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typeName := rtn.ident
@@ -167,33 +169,32 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
rcvrtype.method = append(rcvrtype.method, n)
n.child[0].child[0].lastChild().typ = rcvrtype
} else {
ident := n.child[1].ident
case ident == "init":
// TODO(mpl): use constant instead of hardcoded string?
if ident != "init" {
asImportName := filepath.Join(ident, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
sym, exists := sc.sym[ident]
if exists {
// Make sure the symbol we found seems to be about another node, before calling
// it a redeclaration.
if sym.typ.isComplete() {
// TODO(mpl): this check might be too permissive?
if sym.kind != funcSym || sym.typ.cat != n.typ.cat || sym.node != n || sym.index != -1 {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
// init functions do not get declared as per the Go spec.
default:
asImportName := filepath.Join(ident, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
sym, exists := sc.sym[ident]
if exists {
// Make sure the symbol we found seems to be about another node, before calling
// it a redeclaration.
if sym.typ.isComplete() {
// TODO(mpl): this check might be too permissive?
if sym.kind != funcSym || sym.typ.cat != n.typ.cat || sym.node != n || sym.index != -1 {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
}
}
// Add a function symbol in the package name space
// Add a function symbol in the package name space except for init
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
if !n.typ.isComplete() {

View File

@@ -405,6 +405,13 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
// Execute node closures
interp.run(root, nil)
// Wire and execute global vars
n, err := genGlobalVars([]*node{root}, interp.scopes[interp.Name])
if err != nil {
return res, err
}
interp.run(n, nil)
for _, n := range initNodes {
interp.run(n, interp.frame)
}
@@ -474,7 +481,14 @@ func (interp *Interpreter) Use(values Exports) {
continue
}
interp.binPkg[k] = v
if interp.binPkg[k] == nil {
interp.binPkg[k] = v
continue
}
for s, sym := range v {
interp.binPkg[k][s] = sym
}
}
}

View File

@@ -45,6 +45,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "fun22.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error
file.Name() == "io0.go" || // use random number
file.Name() == "op1.go" || // expect error
file.Name() == "op7.go" || // expect error
@@ -75,6 +76,10 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "redeclaration-global4.go" || // expect error
file.Name() == "redeclaration-global5.go" || // expect error
file.Name() == "redeclaration-global6.go" || // expect error
file.Name() == "restricted0.go" || // expect error
file.Name() == "restricted1.go" || // expect error
file.Name() == "restricted2.go" || // expect error
file.Name() == "restricted3.go" || // expect error
file.Name() == "server6.go" || // syntax parsing
file.Name() == "server5.go" || // syntax parsing
file.Name() == "server4.go" || // syntax parsing

View File

@@ -385,6 +385,25 @@ func TestEvalChan(t *testing.T) {
})
}
func TestEvalFunctionCallWithFunctionParam(t *testing.T) {
i := interp.New(interp.Options{})
eval(t, i, `
func Bar(s string, fn func(string)string) string { return fn(s) }
`)
v := eval(t, i, "Bar")
bar := v.Interface().(func(string, func(string) string) string)
got := bar("hello ", func(s string) string {
return s + "world!"
})
want := "hello world!"
if got != want {
t.Errorf("unexpected result of function eval: got %q, want %q", got, want)
}
}
func TestEvalMissingSymbol(t *testing.T) {
defer func() {
r := recover()

View File

@@ -2053,7 +2053,7 @@ func equal(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 == i1)
return tnext
}
}
@@ -2075,7 +2075,7 @@ func equal(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 == i1)
return tnext
}
}
@@ -2099,7 +2099,7 @@ func equal(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 == i1)
return tnext
}
}
@@ -2536,80 +2536,6 @@ func greater(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
}
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -2899,80 +2825,6 @@ func greaterEqual(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
}
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -3262,80 +3114,6 @@ func lower(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
}
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -3625,80 +3403,6 @@ func lowerEqual(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
}
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {

View File

@@ -84,6 +84,9 @@ func init() {
}
func (interp *Interpreter) run(n *node, cf *frame) {
if n == nil {
return
}
var f *frame
if cf == nil {
f = interp.frame
@@ -230,10 +233,11 @@ func typeAssert(n *node) {
func typeAssert2(n *node) {
c0, c1 := n.child[0], n.child[1]
value := genValue(c0) // input value
value0 := genValue(n.anc.child[0]) // returned result
value1 := genValue(n.anc.child[1]) // returned status
typ := c1.typ // type to assert or convert to
value := genValue(c0) // input value
value0 := genValue(n.anc.child[0]) // returned result
value1 := genValue(n.anc.child[1]) // returned status
setStatus := n.anc.child[1].ident != "_" // do not assign status to "_"
typ := c1.typ // type to assert or convert to
typID := typ.id()
rtype := typ.rtype // type to assert
next := getExec(n.tnext)
@@ -247,7 +251,9 @@ func typeAssert2(n *node) {
} else {
ok = false
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
case isInterface(typ):
@@ -257,7 +263,9 @@ func typeAssert2(n *node) {
if ok {
value0(f).Set(v)
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
case n.child[0].typ.cat == valueT:
@@ -267,7 +275,9 @@ func typeAssert2(n *node) {
if ok {
value0(f).Set(v)
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
default:
@@ -277,13 +287,18 @@ func typeAssert2(n *node) {
if ok {
value0(f).Set(v.value)
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
}
}
func canAssertTypes(src, dest reflect.Type) bool {
if dest == nil {
return false
}
if src == dest {
return true
}
@@ -679,9 +694,13 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
// Copy function input arguments in local frame
for i, arg := range in {
if def.typ.arg[i].cat == interfaceT {
typ := def.typ.arg[i]
switch {
case typ.cat == interfaceT:
d[i].Set(reflect.ValueOf(valueInterface{value: arg.Elem()}))
} else {
case typ.cat == funcT && arg.Kind() == reflect.Func:
d[i].Set(reflect.ValueOf(genFunctionNode(arg)))
default:
d[i].Set(arg)
}
}
@@ -705,6 +724,10 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
}
}
func genFunctionNode(v reflect.Value) *node {
return &node{kind: funcType, action: aNop, rval: v, typ: &itype{cat: valueT, rtype: v.Type()}}
}
func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
value := genValue(n)
if typ == nil || typ.Kind() != reflect.Interface || typ.NumMethod() == 0 || n.typ.cat == valueT {
@@ -1057,7 +1080,7 @@ func callBin(n *node) {
}
for i, c := range child {
defType := funcType.In(pindex(i, variadic))
defType := funcType.In(rcvrOffset + pindex(i, variadic))
switch {
case isBinCall(c):
// Handle nested function calls: pass returned values as arguments
@@ -2003,10 +2026,11 @@ func compositeBinStruct(n *node) {
}
} else {
fieldIndex[i] = []int{i}
convertLiteralValue(c.child[1], typ.Field(i).Type)
if c.typ.cat == funcT {
convertLiteralValue(c.child[1], typ.Field(i).Type)
values[i] = genFunctionWrapper(c.child[1])
} else {
convertLiteralValue(c, typ.Field(i).Type)
values[i] = genValue(c)
}
}
@@ -2081,6 +2105,7 @@ func doCompositeSparse(n *node, hasType bool) {
if hasType {
child = n.child[1:]
}
destInterface := destType(n).cat == interfaceT
values := make(map[int]func(*frame) reflect.Value)
a, _ := n.typ.zero()
@@ -2091,6 +2116,10 @@ func doCompositeSparse(n *node, hasType bool) {
switch {
case c1.typ.cat == funcT:
values[field] = genFunctionWrapper(c1)
case c1.typ.cat == interfaceT:
values[field] = genValueInterfaceValue(c1)
case isArray(c1.typ) && c1.typ.val != nil && c1.typ.val.cat == interfaceT:
values[field] = genValueInterfaceArray(c1)
case isRecursiveType(n.typ.field[field].typ, n.typ.field[field].typ.rtype):
values[field] = genValueRecursiveInterface(c1, n.typ.field[field].typ.rtype)
default:
@@ -2102,9 +2131,13 @@ func doCompositeSparse(n *node, hasType bool) {
for i, v := range values {
a.Field(i).Set(v(f))
}
if d := value(f); d.Type().Kind() == reflect.Ptr {
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
d.Set(a.Addr())
} else {
case destInterface:
d.Set(reflect.ValueOf(valueInterface{n, a}))
default:
d.Set(a)
}
return next
@@ -2750,7 +2783,7 @@ func convertLiteralValue(n *node, t reflect.Type) {
case n.typ.cat == nilT:
// Create a zero value of target type.
n.rval = reflect.New(t).Elem()
case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface:
case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface || t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface:
// Skip non-constant values, undefined target type or interface target type.
case n.rval.IsValid():
// Convert constant value to target type.

View File

@@ -22,7 +22,7 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
// In all other cases, absolute import paths are resolved from the GOPATH
// and the nested "vendor" directories.
if isPathRelative(path) {
if rPath == "main" {
if rPath == mainID {
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.Name), rPath, path)
@@ -132,6 +132,13 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
interp.run(n, nil)
}
// Wire and execute global vars
n, err := genGlobalVars(rootNodes, interp.scopes[path])
if err != nil {
return "", err
}
interp.run(n, nil)
// Add main to list of functions to run, after all inits
if m := interp.main(); m != nil {
initNodes = append(initNodes, m)
@@ -146,7 +153,7 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
func (interp *Interpreter) rootFromSourceLocation(rPath string) (string, error) {
sourceFile := interp.Name
if rPath != "main" || !strings.HasSuffix(sourceFile, ".go") {
if rPath != mainID || !strings.HasSuffix(sourceFile, ".go") {
return rPath, nil
}
wd, err := os.Getwd()
@@ -181,13 +188,62 @@ func pkgDir(goPath string, root, path string) (string, string, error) {
return "", "", fmt.Errorf("unable to find source related to: %q", path)
}
return pkgDir(goPath, previousRoot(root), path)
rootPath := filepath.Join(goPath, "src", root)
prevRoot, err := previousRoot(rootPath, root)
if err != nil {
return "", "", err
}
return pkgDir(goPath, prevRoot, path)
}
// Find the previous source root (vendor > vendor > ... > GOPATH).
func previousRoot(root string) string {
splitRoot := strings.Split(root, string(filepath.Separator))
const vendor = "vendor"
// Find the previous source root (vendor > vendor > ... > GOPATH).
func previousRoot(rootPath, root string) (string, error) {
rootPath = filepath.Clean(rootPath)
parent, final := filepath.Split(rootPath)
parent = filepath.Clean(parent)
// TODO(mpl): maybe it works for the special case main, but can't be bothered for now.
if root != mainID && final != vendor {
root = strings.TrimSuffix(root, string(filepath.Separator))
prefix := strings.TrimSuffix(rootPath, root)
// look for the closest vendor in one of our direct ancestors, as it takes priority.
var vendored string
for {
fi, err := os.Lstat(filepath.Join(parent, vendor))
if err == nil && fi.IsDir() {
vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator))
break
}
if !os.IsNotExist(err) {
return "", err
}
// stop when we reach GOPATH/src/blah
parent = filepath.Dir(parent)
if parent == prefix {
break
}
// just an additional failsafe, stop if we reach the filesystem root.
// TODO(mpl): It should probably be a critical error actually,
// as we shouldn't have gone that high up in the tree.
if parent == string(filepath.Separator) {
break
}
}
if vendored != "" {
return vendored, nil
}
}
// TODO(mpl): the algorithm below might be redundant with the one above,
// but keeping it for now. Investigate/simplify/remove later.
splitRoot := strings.Split(root, string(filepath.Separator))
var index int
for i := len(splitRoot) - 1; i >= 0; i-- {
if splitRoot[i] == "vendor" {
@@ -197,10 +253,10 @@ func previousRoot(root string) string {
}
if index == 0 {
return ""
return "", nil
}
return filepath.Join(splitRoot[:index]...)
return filepath.Join(splitRoot[:index]...), nil
}
func effectivePkg(root, path string) string {

View File

@@ -196,9 +196,10 @@ func Test_pkgDir(t *testing.T) {
func Test_previousRoot(t *testing.T) {
testCases := []struct {
desc string
root string
expected string
desc string
root string
rootPathSuffix string
expected string
}{
{
desc: "GOPATH",
@@ -215,6 +216,18 @@ func Test_previousRoot(t *testing.T) {
root: "github.com/foo/pkg/vendor/guthib.com/containous/fromage/vendor/guthib.com/containous/fuu",
expected: "github.com/foo/pkg/vendor/guthib.com/containous/fromage",
},
{
desc: "vendor is sibling",
root: "github.com/foo/bar",
rootPathSuffix: "testdata/src/github.com/foo/bar",
expected: "github.com/foo",
},
{
desc: "vendor is uncle",
root: "github.com/foo/bar/baz",
rootPathSuffix: "testdata/src/github.com/foo/bar/baz",
expected: "github.com/foo",
},
}
for _, test := range testCases {
@@ -222,7 +235,20 @@ func Test_previousRoot(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := previousRoot(test.root)
var rootPath string
if test.rootPathSuffix != "" {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
rootPath = filepath.Join(wd, test.rootPathSuffix)
} else {
rootPath = vendor
}
p, err := previousRoot(rootPath, test.root)
if err != nil {
t.Error(err)
}
if p != test.expected {
t.Errorf("got: %s, want: %s", p, test.expected)

View File

@@ -0,0 +1 @@
package baz

View File

@@ -0,0 +1 @@
package whatever

View File

@@ -1260,7 +1260,7 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
if defined[name] != nil && defined[name].rtype != nil {
return defined[name].rtype
}
if t.val != nil && t.val.cat == structT && t.val.rtype == nil && hasRecursiveStruct(t.val, defined) {
if t.val != nil && t.val.cat == structT && t.val.rtype == nil && hasRecursiveStruct(t.val, copyDefined(defined)) {
// Replace reference to self (direct or indirect) by an interface{} to handle
// recursive types with reflect.
typ := *t.val
@@ -1367,6 +1367,14 @@ func (t *itype) implements(it *itype) bool {
return t.methods().contains(it.methods())
}
func copyDefined(m map[string]*itype) map[string]*itype {
n := make(map[string]*itype, len(m))
for k, v := range m {
n[k] = v
}
return n
}
// hasRecursiveStruct determines if a struct is a recursion or a recursion
// intermediate. A recursion intermediate is a struct that contains a recursive
// struct.
@@ -1385,6 +1393,8 @@ func hasRecursiveStruct(t *itype, defined map[string]*itype) bool {
if defined[typ.path+"/"+typ.name] != nil {
return true
}
defined[typ.path+"/"+typ.name] = typ
for _, f := range typ.field {
if hasRecursiveStruct(f.typ, defined) {
return true
@@ -1531,6 +1541,10 @@ func isSendChan(t *itype) bool {
return rt.Kind() == reflect.Chan && rt.ChanDir() == reflect.SendDir
}
func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map }
func isArray(t *itype) bool {
k := t.TypeOf().Kind()
return k == reflect.Array || k == reflect.Slice
}
func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))

View File

@@ -14,9 +14,9 @@ import (
func init() {
Symbols["log"] = map[string]reflect.Value{
// function, constant and variable definitions
"Fatal": reflect.ValueOf(log.Fatal),
"Fatalf": reflect.ValueOf(log.Fatalf),
"Fatalln": reflect.ValueOf(log.Fatalln),
"Fatal": reflect.ValueOf(logFatal),
"Fatalf": reflect.ValueOf(logFatalf),
"Fatalln": reflect.ValueOf(logFatalln),
"Flags": reflect.ValueOf(log.Flags),
"LUTC": reflect.ValueOf(constant.MakeFromLiteral("32", token.INT, 0)),
"Ldate": reflect.ValueOf(constant.MakeFromLiteral("1", token.INT, 0)),
@@ -25,7 +25,7 @@ func init() {
"Lshortfile": reflect.ValueOf(constant.MakeFromLiteral("16", token.INT, 0)),
"LstdFlags": reflect.ValueOf(constant.MakeFromLiteral("3", token.INT, 0)),
"Ltime": reflect.ValueOf(constant.MakeFromLiteral("2", token.INT, 0)),
"New": reflect.ValueOf(log.New),
"New": reflect.ValueOf(logNew),
"Output": reflect.ValueOf(log.Output),
"Panic": reflect.ValueOf(log.Panic),
"Panicf": reflect.ValueOf(log.Panicf),
@@ -40,6 +40,6 @@ func init() {
"Writer": reflect.ValueOf(log.Writer),
// type definitions
"Logger": reflect.ValueOf((*log.Logger)(nil)),
"Logger": reflect.ValueOf((*logLogger)(nil)),
}
}

View File

@@ -31,10 +31,10 @@ func init() {
"ErrNotExist": reflect.ValueOf(&os.ErrNotExist).Elem(),
"ErrPermission": reflect.ValueOf(&os.ErrPermission).Elem(),
"Executable": reflect.ValueOf(os.Executable),
"Exit": reflect.ValueOf(os.Exit),
"Exit": reflect.ValueOf(osExit),
"Expand": reflect.ValueOf(os.Expand),
"ExpandEnv": reflect.ValueOf(os.ExpandEnv),
"FindProcess": reflect.ValueOf(os.FindProcess),
"FindProcess": reflect.ValueOf(osFindProcess),
"Getegid": reflect.ValueOf(os.Getegid),
"Getenv": reflect.ValueOf(os.Getenv),
"Geteuid": reflect.ValueOf(os.Geteuid),

View File

@@ -1,25 +0,0 @@
// Code generated by 'goexports os/exec'. DO NOT EDIT.
// +build go1.13,!go1.14
package stdlib
import (
"os/exec"
"reflect"
)
func init() {
Symbols["os/exec"] = map[string]reflect.Value{
// function, constant and variable definitions
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
// type definitions
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
}

View File

@@ -14,9 +14,9 @@ import (
func init() {
Symbols["log"] = map[string]reflect.Value{
// function, constant and variable definitions
"Fatal": reflect.ValueOf(log.Fatal),
"Fatalf": reflect.ValueOf(log.Fatalf),
"Fatalln": reflect.ValueOf(log.Fatalln),
"Fatal": reflect.ValueOf(logFatal),
"Fatalf": reflect.ValueOf(logFatalf),
"Fatalln": reflect.ValueOf(logFatalln),
"Flags": reflect.ValueOf(log.Flags),
"LUTC": reflect.ValueOf(constant.MakeFromLiteral("32", token.INT, 0)),
"Ldate": reflect.ValueOf(constant.MakeFromLiteral("1", token.INT, 0)),
@@ -26,7 +26,7 @@ func init() {
"Lshortfile": reflect.ValueOf(constant.MakeFromLiteral("16", token.INT, 0)),
"LstdFlags": reflect.ValueOf(constant.MakeFromLiteral("3", token.INT, 0)),
"Ltime": reflect.ValueOf(constant.MakeFromLiteral("2", token.INT, 0)),
"New": reflect.ValueOf(log.New),
"New": reflect.ValueOf(logNew),
"Output": reflect.ValueOf(log.Output),
"Panic": reflect.ValueOf(log.Panic),
"Panicf": reflect.ValueOf(log.Panicf),
@@ -41,6 +41,6 @@ func init() {
"Writer": reflect.ValueOf(log.Writer),
// type definitions
"Logger": reflect.ValueOf((*log.Logger)(nil)),
"Logger": reflect.ValueOf((*logLogger)(nil)),
}
}

View File

@@ -31,10 +31,10 @@ func init() {
"ErrNotExist": reflect.ValueOf(&os.ErrNotExist).Elem(),
"ErrPermission": reflect.ValueOf(&os.ErrPermission).Elem(),
"Executable": reflect.ValueOf(os.Executable),
"Exit": reflect.ValueOf(os.Exit),
"Exit": reflect.ValueOf(osExit),
"Expand": reflect.ValueOf(os.Expand),
"ExpandEnv": reflect.ValueOf(os.ExpandEnv),
"FindProcess": reflect.ValueOf(os.FindProcess),
"FindProcess": reflect.ValueOf(osFindProcess),
"Getegid": reflect.ValueOf(os.Getegid),
"Getenv": reflect.ValueOf(os.Getenv),
"Geteuid": reflect.ValueOf(os.Geteuid),

View File

@@ -1,25 +0,0 @@
// Code generated by 'goexports os/exec'. DO NOT EDIT.
// +build go1.14,!go1.15
package stdlib
import (
"os/exec"
"reflect"
)
func init() {
Symbols["os/exec"] = map[string]reflect.Value{
// function, constant and variable definitions
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
// type definitions
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
}

55
stdlib/restricted.go Normal file
View File

@@ -0,0 +1,55 @@
package stdlib
import (
"errors"
"io"
"log"
"os"
"strconv"
)
var errRestricted = errors.New("restricted")
// osExit invokes panic instead of exit.
func osExit(code int) { panic("os.Exit(" + strconv.Itoa(code) + ")") }
// osFindProcess returns os.FindProcess, except for self process.
func osFindProcess(pid int) (*os.Process, error) {
if pid == os.Getpid() {
return nil, errRestricted
}
return os.FindProcess(pid)
}
// The following functions call Panic instead of Fatal to avoid exit.
func logFatal(v ...interface{}) { log.Panic(v...) }
func logFatalf(f string, v ...interface{}) { log.Panicf(f, v...) }
func logFatalln(v ...interface{}) { log.Panicln(v...) }
type logLogger struct {
l *log.Logger
}
// logNew Returns a wrapped logger.
func logNew(out io.Writer, prefix string, flag int) *logLogger {
return &logLogger{log.New(out, prefix, flag)}
}
// The following methods call Panic instead of Fatal to avoid exit.
func (l *logLogger) Fatal(v ...interface{}) { l.l.Panic(v...) }
func (l *logLogger) Fatalf(f string, v ...interface{}) { l.l.Panicf(f, v...) }
func (l *logLogger) Fatalln(v ...interface{}) { l.l.Panicln(v...) }
// The following methods just forward to wrapped logger.
func (l *logLogger) Flags() int { return l.l.Flags() }
func (l *logLogger) Output(d int, s string) error { return l.l.Output(d, s) }
func (l *logLogger) Panic(v ...interface{}) { l.l.Panic(v...) }
func (l *logLogger) Panicf(f string, v ...interface{}) { l.l.Panicf(f, v...) }
func (l *logLogger) Panicln(v ...interface{}) { l.l.Panicln(v...) }
func (l *logLogger) Prefix() string { return l.l.Prefix() }
func (l *logLogger) Print(v ...interface{}) { l.l.Print(v...) }
func (l *logLogger) Printf(f string, v ...interface{}) { l.l.Printf(f, v...) }
func (l *logLogger) Println(v ...interface{}) { l.l.Println(v...) }
func (l *logLogger) SetFlags(flag int) { l.l.SetFlags(flag) }
func (l *logLogger) SetOutput(w io.Writer) { l.l.SetOutput(w) }
func (l *logLogger) Writer() io.Writer { return l.l.Writer() }

View File

@@ -43,7 +43,7 @@ func init() {
//go:generate ../cmd/goexports/goexports net net/http net/http/cgi net/http/cookiejar net/http/fcgi
//go:generate ../cmd/goexports/goexports net/http/httptest net/http/httptrace net/http/httputil net/http/pprof
//go:generate ../cmd/goexports/goexports net/mail net/rpc net/rpc/jsonrpc net/smtp net/textproto net/url
//go:generate ../cmd/goexports/goexports os os/exec os/signal os/user
//go:generate ../cmd/goexports/goexports os os/signal os/user
//go:generate ../cmd/goexports/goexports path path/filepath reflect regexp regexp/syntax
//go:generate ../cmd/goexports/goexports runtime runtime/debug runtime/pprof runtime/trace
//go:generate ../cmd/goexports/goexports sort strconv strings sync sync/atomic

View File

@@ -0,0 +1,41 @@
// Package unrestricted provides the original version of standard library symbols which may cause the interpreter process to exit.
package unrestricted
import (
"log"
"os"
"os/exec"
"reflect"
)
// Symbols stores the map of syscall package symbols.
var Symbols = map[string]map[string]reflect.Value{}
func init() {
Symbols["os"] = map[string]reflect.Value{
"Exit": reflect.ValueOf(os.Exit),
"FindProcess": reflect.ValueOf(os.FindProcess),
}
Symbols["os/exec"] = map[string]reflect.Value{
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
Symbols["log"] = map[string]reflect.Value{
"Fatal": reflect.ValueOf(log.Fatal),
"Fatalf": reflect.ValueOf(log.Fatalf),
"Fatalln": reflect.ValueOf(log.Fatalln),
"New": reflect.ValueOf(log.New),
"Logger": reflect.ValueOf((*log.Logger)(nil)),
}
Symbols["github.com/containous/yaegi/stdlib/unrestricted"] = map[string]reflect.Value{
"Symbols": reflect.ValueOf(Symbols),
}
}