Compare commits

...

53 Commits

Author SHA1 Message Date
Marc Vertes
fbee2baf9d interp: fix wrapping of returned closure passed to runtime
Fixes #1333.
2021-12-21 17:44:06 +01:00
Marc Vertes
2819b4167b interp: fix derivation of type of slice expression of binary object
Fixes #1328.
2021-12-20 15:46:05 +01:00
Marc Vertes
2af660cb1f interp: improve method resolution on embedded fields
The capability to dereference pointers has been added to
methodByName(), improving method lookup on binary values.

Wrapping to valueInterface is performed in a missing use case at
return of function calls. It was done in the nested call, but not
at assign.

Fixes #1330.
2021-12-20 15:06:11 +01:00
Marc Vertes
8323068414 interp: fix nested calls with variadic interfaces
Fixes #1326
2021-12-08 17:16:05 +01:00
Marc Vertes
5b62f9fdb6 interp: fix setting of interface value from nested function calls
Fixes #1320 and #1294.
2021-11-30 15:44:05 +01:00
Marc Vertes
4f66e3fe6c interp: fix type switch when the case type is an interpreted interface
Fixes #1315.

With this PR, the package gopkg.in/yaml.v3 works in yaegi (fixes #1296).
2021-11-26 12:24:07 +01:00
Marc Vertes
1335b4c64f stdlib: add wrappers for go/build/constraint package 2021-11-26 12:10:08 +01:00
Marc Vertes
da03c922ca interp: get rid of deprecated stdlib package ioutil
No functional change.
2021-11-26 11:58:07 +01:00
Marc Vertes
9620116c30 interp: replace value.Type().Kind() by value.Kind()
No functional change. Just simpler and faster.
2021-11-26 11:42:07 +01:00
Marc Vertes
dd7197f2a2 interp: fix assign of multiple return function call
A runtime builtin assignFromCall is added to handle multiple values returned at once. It is necessary if some of the values require to be set to interface values in the caller space, which is performed by reflect.Set in assignFromCall.
 
Fixes #1311.
2021-11-19 15:30:05 +01:00
Marc Vertes
9214806342 interp: fix populating array of interfaces
Fixes #1308.
2021-11-09 11:26:11 +01:00
Marc Vertes
348e713a8e interp: avoid collision between type and variable names in assign
Fixes #1306
2021-11-09 11:14:09 +01:00
Marc Vertes
cb81fe41ab interp: fix type processing to support multiple recursive fields
Fixes #1304
2021-11-08 20:46:12 +01:00
Marc Vertes
a876bb3673 interp: virtualize environment in restricted mode
In restricted mode, replace environment related symbols in
stdlib os package by a version which operates on a private copy
per interpreter context.

It allows to have concurrent interpreters in the same process
operating each in their own environment without affecting each
other or the host.

If unrestricted opt is set, this behaviour is disabled, and the
default symbols from stdlib are used.

Note also that no modification is done for syscall package, as it
should be not used in restricted mode.
2021-11-08 09:58:10 +01:00
Marc Vertes
afa46daccd interp: fix assignment to a dereferenced struct pointer
disable the optimization of skipping assign operation in that case,
as this step is necessary in case of writing to a pointer.

Fixes #1300.
2021-11-02 15:56:13 +01:00
Marc Vertes
3c00da291e stdlib: add wrappers for standard lib runtime/metrics package
This package was forgotten. No reason to not include it.
2021-11-02 15:20:06 +01:00
Marc Vertes
c847481184 interp: fix comparison operators in if statement
At generation of operator closures, the comparison expression
was hard-coded instead of being derived from the operator name,
leading to a wrong result.

Fixes #1297.
2021-11-02 15:02:07 +01:00
Marc Vertes
f46ef67180 interp: fix computation of ellipsis array length
Fixes #1287.
2021-10-20 14:36:05 +02:00
Marc Vertes
229ddfdae1 interp: fix use of builtins in type definitions
Make len() and cap() work on pointers. Preserve scope in case of
nested calls of cfg.

Fixes #1285.
2021-10-18 10:50:14 +02:00
Marc Vertes
aa7f0849e3 interp: fix goto to a label with no statement
Fixes #1288.
2021-10-18 10:36:12 +02:00
Bai-Yingjie
7617b8a090 interp: add args option to let each interpreter have seperate args
In some cases, we use many yaegi instances in one process, each interpreter will expect its own os.Args. This is done in interp.go as an option in interp.Options besides Stdin Stdout and Stderr.
2021-10-11 16:58:07 +02:00
Marc Vertes
4e06abe002 interp: fix unsafe2 to work on 32 bits architectures
Fixes #1279.
2021-10-11 11:38:12 +02:00
Bai-Yingjie
b1a758dd5a extract: add an option to be able to set build tag to the created file
Add a tag option in the extract program to optionally give an opportunity to tailor some packages in for example the stdlib, for people who is interested in keeping the final binary size as smaller as possible.
Below go generate
```
//go:generate ../cmd/extract/extract -name stdlib -tag stdmime mime mime/multipart mime/quotedprintable
```
produces a header to stdlib/mime-multipart.go
```
// Code generated by 'yaegi extract mime/multipart'. DO NOT EDIT.

// +build go1.16,!go1.17,stdmime
```
2021-10-11 10:50:11 +02:00
Marc Vertes
5bf4daef2d interp: fix type check of methods with a receiver of interface kind
Fixes #1280.
2021-10-08 17:44:09 +02:00
Marc Vertes
e56db3b82e interp: improve interface wrappers when used by reflect
When an interpreter type implementing an interface is
used by the runtime, the runtime can extract its type
and create new values using reflect, and call methods
on it. The problem is that there will be no interpreted
method counterpart in this case, which makes wrapper panic.

Allow the String() method wrapper to always succeed and
return an empty string if no interpreted method is present.

This allows scripts to define custom flag.Value types on
which the runtime internally instantiates values using
reflect (see isZeroValue in Go src/flag/flag.go).

This workaround could be generalized to all wrappers if
necessary. At this moment, it is convenient to keep the
default behavior of expecting instantiated interpreter
methods, in order to catch interpreter bugs related to
interfaces.

Fixes #1276.
2021-10-08 15:56:06 +02:00
Marc Vertes
d3bbe01d5c interp: improve support of composed interfaces
Fixes #1260.
2021-10-07 16:28:05 +02:00
Nicholas Wiersma
286d6c6359 interp: handle recursive and incomplete seen types in nodeType
This comes from experiments looking into #1259 where incomplete twice seen types are marked as complete. To mitigate the problem instead of a map of seen types in `nodeType` a slice is used as a cheap way to keep track of our current path through the node tree.
2021-09-27 10:20:13 +02:00
Nicholas Wiersma
84424b52bc interp: handle alias of an alias
When dealing with an alias of an alias, the actual underlying type of the source alias should be used.
2021-09-27 10:08:11 +02:00
Marc Vertes
98c2dcd3e5 interp: fix interface wrapper for struct types
This change fixes a regression introduced by PR #1192 in a program using
https://github.com/NYTimes/gziphandler which defines several types
implementing stdlib interfaces. We do not implement a wrapper
if we see that a type already implements an interface, except that
it can be falsly reported by reflect in case of a struct with
embedded interface field. We need to force the wrapper generation
in this case.
The problem occurs only for wrappers on struct, not on pointers or
other indirection types.
2021-09-24 13:20:12 +02:00
Ethan Reesor
808f0bde9d interp: add a function to directly compile Go AST
Adds CompileAST, which can be used to compile Go AST directly. This
allows users to delegate parsing of source to their own code instead of
relying on the interpreter.

CLoses #1251
2021-09-23 12:34:12 +02:00
Nicholas Wiersma
c5c6012947 interp: support type spec assign
This supports type spec assign types which disallows the attachment of methods onto the alias type as per the Go spec.

Fixes #1154
2021-09-23 12:16:11 +02:00
Nicholas Wiersma
836060c8ad interp: allow GTA revisit for unresolved define statements
When a define statement relies on a selector or type that may exist in another file it should revisit once GTA is complete. This allows that revisit.

**Note:** In order to keep the original GTA error for the define statement, so the error received is correct and meaningful, I have added a node `meta` property. I decided to make it generic as it may be useful in future. There may be a better way to stash errors across the GTA runs and am open to suggestion here.

Fixes #1253
2021-09-20 12:04:06 +02:00
Nicholas Wiersma
7a54353c7b chore: update linter to v1.42.1
* chore: update linter to v1.42.1

* fix: linter issues
2021-09-20 10:18:14 +02:00
Marc Vertes
b591ba0e78 interp: do not export RealFS, used internally only 2021-09-15 16:22:07 +02:00
Nicholas Wiersma
5af51aefe6 interp: fix type selector precedence
The current `nodeType` selector precedence is heavy handed in favour of package type. It seems to often create `typeSym` symbols as variable types in the scope will never be found. To fix this if the ancestor node is a field expression, the package type is searched for. After this, if the type is still `nil` the normal scope is searched using `nodeType2`.

Fixes #1158
2021-09-15 10:42:08 +02:00
mpl
e7c0f68bab interp: fix Clone documentation 2021-09-14 12:30:13 +02:00
Marc Vertes
bd9a6a4f8a interp: improve processing of recursive types
Make sure to keep always a single copy of incomplete type structures.
Remove remnants of recursive types processing.

Now `import "go.uber.org/zap"` works again (see #1172), fixing regressions
introduced since #1236.
2021-09-13 18:24:10 +02:00
Nicholas Wiersma
3eb2c79fd8 interp: fix nil funcs in composite literals
When a nil are used in a func composite literal, the nil type is a `func` not a `*node`. This handles this case.

Fixes #1249
2021-09-13 16:32:12 +02:00
Nicholas Wiersma
4653d87298 interp: types should not recover data for aliases
When `nodeType` recovers names and methods, it can overwrite the data if the type is an aliasT. When aliasing a type, do not recover the methods, this will be done in the GTA typeSpec pass.

Related to #1158
2021-09-06 18:24:04 +02:00
Nicholas Wiersma
45d569c215 interp: fix parsing of late binding consts
When a const is late binding and specified with a type, the GTA defineStmt was creating the symbol with the current scopes `iota` which is incorrect. The symbol should be created with the source nodes `rval`.

Related to #1158
2021-09-06 17:30:12 +02:00
Nicholas Wiersma
c33caeb573 interp: improve handling of wrapped interface values
This test (assert2.go) display 2 separate issues:
1. assert2.go L28: Type assert tries to set an `interface{}` to a `valueInterface`. The typing here is complex, we have a valueT(strings.Builder) wrapped in a ptrT wrapped in a src iface wrapped in a valueT(interface{}). Type assert fails to realise that the `valueT` `interface{}` is wrapping the `valueInterface`.
2. assert2.go L29: `genValueBinMethodOnInterface` does not try and get the bin method, as the `typ.node` (`ptrT` or a `valueT`(`string.Builder`)) is set. In this case the src iface is called with a receiver argument. To fix this the method is looked for first if possible, and only if not found does it fall back to the `defaultGen`.

Fixes #1227
2021-09-06 17:16:11 +02:00
Ethan Reesor
91a55cc4c5 interp: add debug interface
Adds an interface to `interp` that can be used to build an interactive debugger, such as those used by IDEs.

Closes #1188

All basic debugger features work, with one exception: breakpoints in some locations don't work, due to `setExec` creating a temporary `bltn` (https://github.com/traefik/yaegi/issues/1188#issuecomment-886107905).

Example, using a Debug Adapter implementation with VSCode:

![image](https://user-images.githubusercontent.com/879055/128620736-94b6efde-2e5f-43ad-82c9-d919c3fe401f.png)
2021-09-06 15:32:10 +02:00
Nicholas Wiersma
05f08d776a interp: complete type constructors
This completes the types constructors, cleaning up `nodeType`.
2021-09-01 14:46:08 +02:00
Nicholas Wiersma
d2569a85a6 interp: fix default types for runes
When using an untyped rune in an interface, the default type was blindly untyping it. This fixes this issue.

Fixes #1238
2021-08-31 12:32:10 +02:00
Nicholas Wiersma
772cd68fea interp: make use of type constructors
It was initially assumed that `nodeType` needed to rebuild the type from scratch, but this is not the case anymore. The existing type constructors are now used in `nodeType` to make it more readable. In doing this some bugs in type strings were found and fixed, along with adding the real package name to the type.

As `ptrOf` is now the only place that pointer types are constructed, it is feasible to cache the pointer type on the value type. To ensures that we have a consistent pointer type for any value type.
2021-08-31 10:34:12 +02:00
Nicholas Wiersma
4af992bccb interp: create real recursive types with unsafe type swapping
As the unsafe and pointer methods in `reflect` are to be depreciated, and seeing no replacement functions, it is now forced that some unsafe is needed to replace this as when and interface is dereferenced it is made unsettable by reflect.

With this in mind, this adds real recursive types by hot swapping the struct field type on the fly. This removes a lot of compensation code, simplifying all previous cases.

**Note:** While the struct field type is swapped for the real type, the type string is not changed. Due to this, unsafe will recreate the same type.
2021-08-30 18:38:12 +02:00
Nicholas Wiersma
da922ce90b interp: build type strings on the fly
This adds `itype.str` which is a string representation of the type built when the type is built. The goal is to make type comparison simpler and centralise the creation of types just to constructors and `nodeType`. `nodeType` continues to build types in parts so to reuse underlying types better.
2021-08-27 19:44:05 +02:00
Aloïs Micard
7b77b0fa22 test: only enable race detector when supported
Closes: #1228
2021-08-27 15:50:13 +02:00
Marc Vertes
b7f9a39eff interp: fix support of 32 bits and big-endian arch
Force recompute of bits.UintSize, make related tests portable.

Fixes #1230.
2021-08-27 14:54:05 +02:00
Marc Vertes
d2b25a7426 interp: fix append with 1 argument
Fixes #1224.
2021-08-24 12:16:08 +02:00
Johnny
b5bf4ef31a interp: allow for reading source files from diverse filesystems
Make use of fs.FS (new to go 1.16) to allow for reading source files from diverse filesystems (local, embed, custom).

* `Options` has a new field `SourcecodeFilesystem fs.FS` so users can supply their own read-only filesystem containing source code.
* Defaults to the local filesystems (via `RealFS` - a thin `os.Open` wrapper complying with `fs.FS`) so regular users should see no change in behaviour.
* When no filesystem is available (e.g. WASM, or if you want to embed files to retain single binary distribution) an alternative filesystem is preferable to using `Eval(string)` as that requires the stringy code to be a single file monolith instead of multiple files. By using an `fs.FS` we can use `EvalPath()` and gain the ability to handle multiple files and packages.
* You can make use of embed filesystems (https://pkg.go.dev/embed) and custom filesystems obeying the `fs.FS` interface (I use one for http served zip files when targeting wasm as there is no local filesystem on wasm). Tests can make use of `fstest.Map`.
* NOTE: This does NOT affect what the running yaegi code considers its local filesystem, this is only for the interpreter finding the source code.

See `example/fs/fs_test.go` for an example.

Fixes #1200.
2021-08-19 11:28:13 +02:00
Nicholas Wiersma
a69b9bc2dc stdlib: support go1.17 unsafe functions
This adds support for go1.17 `unsafe.Add` and `unsafe.Slice`.
2021-08-19 10:38:05 +02:00
Marc Vertes
b84278dcc6 stdlib: remove wrapper of runtime/cgo
Temporarily removing this wrapper which causes a failure
on freebsd system at build.

Fixes #1221.
2021-08-17 16:38:12 +02:00
283 changed files with 7293 additions and 2152 deletions

View File

@@ -8,7 +8,7 @@ on:
env:
GO_VERSION: 1.17
GOLANGCI_LINT_VERSION: v1.41.1
GOLANGCI_LINT_VERSION: v1.42.1
jobs:

View File

@@ -63,10 +63,10 @@
exclude = []
[[issues.exclude-rules]]
path = "interp/.+_test\\.go"
path = ".+_test\\.go"
linters = ["goconst"]
[[issues.exclude-rules]]
path = "interp/.+_test\\.go"
path = ".+_test\\.go"
text = "var-declaration:"
[[issues.exclude-rules]]
@@ -75,6 +75,9 @@
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`out` can be `io.Writer`"
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`Panic` should conform to the `XxxError` format"
[[issues.exclude-rules]]
path = "interp/interp_eval_test.go"
linters = ["thelper"]

View File

@@ -6,16 +6,18 @@ const (
zero = iota
one
two
three
)
func main() {
a := [...]string{
zero: "zero",
one: "one",
two: "two",
zero: "zero",
one: "one",
three: "three",
three + 2: "five",
}
fmt.Printf("%v %T\n", a, a)
}
// Output:
// [zero one two] [3]string
// [zero one three five] [6]string

18
_test/alias2.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "fmt"
func (t MyT) Test() string {
return "hello"
}
type MyT int
func main() {
t := MyT(1)
fmt.Println(t.Test())
}
// Output:
// hello

22
_test/alias3.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "github.com/traefik/yaegi/_test/alias3"
var globalT *T
func init() {
globalT = &T{A: "test"}
}
type T alias3.T
func (t *T) PrintT() {
(*alias3.T)(t).Print()
}
func main() {
globalT.PrintT()
}
// Output:
// test

9
_test/alias3/alias3.go Normal file
View File

@@ -0,0 +1,9 @@
package alias3
type T struct {
A string
}
func (t *T) Print() {
println(t.A)
}

27
_test/alias4.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"fmt"
"net/http"
)
type A http.Header
func (a A) Test1() {
fmt.Println("test1")
}
type B A
func (b B) Test2() {
fmt.Println("test2")
}
func main() {
b := B{}
b.Test2()
}
// Output:
// test2

37
_test/assert2.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"strings"
"sync"
)
// Defined an interface of stringBuilder that compatible with
// strings.Builder(go 1.10) and bytes.Buffer(< go 1.10)
type stringBuilder interface {
WriteRune(r rune) (n int, err error)
WriteString(s string) (int, error)
Reset()
Grow(n int)
String() string
}
var builderPool = sync.Pool{New: func() interface{} {
return newStringBuilder()
}}
func newStringBuilder() stringBuilder {
return &strings.Builder{}
}
func main() {
i := builderPool.Get()
sb := i.(stringBuilder)
_, _ = sb.WriteString("hello")
println(sb.String())
builderPool.Put(i)
}
// Output:
// hello

View File

@@ -8,4 +8,4 @@ func main() {
}
// Error:
// _test/assign15.go:5:26: cannot use type chan<- struct{} as type <-chan struct{} in assignment
// _test/assign15.go:5:26: cannot use type chan<- struct {} as type <-chan struct {} in assignment

View File

@@ -1,13 +0,0 @@
// A test program
// +build darwin,linux !arm
// +build go1.12 !go1.13
package main
func main() {
println("hello world")
}
// Output:
// hello world

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@@ -13,7 +13,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@@ -21,7 +21,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -13,7 +13,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -40,7 +40,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -40,7 +40,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -41,7 +41,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

32
_test/composite18.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import "fmt"
type fn func(string, string) bool
var funcs = []fn{
cmpLessFn,
cmpGreaterFn,
nil,
}
func cmpLessFn(a string, b string) bool {
return a < b
}
func cmpGreaterFn(a string, b string) bool {
return a > b
}
func main() {
for _, f := range funcs {
if f == nil {
continue
}
fmt.Println(f("a", "b"))
}
}
// Output:
// true
// false

33
_test/composite19.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import "fmt"
type fn func(string, string) bool
var funcs = map[string]fn{
"less": cmpLessFn,
"greater": cmpGreaterFn,
"none": nil,
}
func cmpLessFn(a string, b string) bool {
return a < b
}
func cmpGreaterFn(a string, b string) bool {
return a > b
}
func main() {
for _, n := range []string{"less", "greater", "none"} {
f := funcs[n]
if f == nil {
continue
}
fmt.Println(f("a", "b"))
}
}
// Output:
// true
// false

View File

@@ -9,7 +9,7 @@ func main() {
const huge = 1 << 100
const large = huge >> 38
fmt.Println(large)
fmt.Println(int64(large))
}
// Output:

View File

@@ -2,7 +2,7 @@ package main
import "fmt"
const maxLen = int64(int(^uint(0) >> 1))
const maxLen = int64(int64(^uint64(0) >> 1))
func main() {
fmt.Println(maxLen)

28
_test/const26.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
)
func init() {
fmt.Println(constString)
fmt.Println(const2)
fmt.Println(varString)
}
const constString string = "hello"
const (
const1 = iota + 10
const2
const3
)
var varString string = "test"
func main() {}
// Output:
// hello
// 11
// test

View File

@@ -2,12 +2,11 @@ package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
file, err := ioutil.TempFile("", "yeagibench")
file, err := os.CreateTemp("", "yeagibench")
if err != nil {
panic(err)
}
@@ -23,7 +22,7 @@ func main() {
panic(err)
}
b, err := ioutil.ReadFile(file.Name())
b, err := os.ReadFile(file.Name())
if err != nil {
panic(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"strings"
)
@@ -10,7 +10,7 @@ import (
func main() {
r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")
b, err := ioutil.ReadAll(r)
b, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}

View File

@@ -10,4 +10,4 @@ func main() {
}
// Error:
// 9:6: cannot use type string as type int in assignment
// 9:6: cannot use type untyped string as type int in assignment

View File

@@ -2,7 +2,6 @@ package main
import (
"io"
"io/ioutil"
"os"
)
@@ -13,7 +12,7 @@ type sink interface {
func newSink() sink {
// return os.Stdout // Stdout is special in yaegi tests
file, err := ioutil.TempFile("", "yaegi-test.*")
file, err := os.CreateTemp("", "yaegi-test.*")
if err != nil {
panic(err)
}

67
_test/issue-1260.go Normal file
View File

@@ -0,0 +1,67 @@
package main
import (
"fmt"
"io"
"os"
)
type WriteSyncer interface {
io.Writer
Sync() error
}
type Sink interface {
WriteSyncer
io.Closer
}
func newFileSink(path string) (Sink, error) {
return os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
}
type Sink1 struct{ name string }
func (s Sink1) Write(b []byte) (int, error) { println("in Write"); return 0, nil }
func (s Sink1) Sync() error { println("in Sync"); return nil }
func (s Sink1) Close() error { println("in Close", s.name); return nil }
func newS1(name string) Sink { return Sink1{name} }
func newS1p(name string) Sink { return &Sink1{name} }
type Sink2 struct{ name string }
func (s *Sink2) Write(b []byte) (int, error) { println("in Write"); return 0, nil }
func (s *Sink2) Sync() error { println("in Sync"); return nil }
func (s *Sink2) Close() error { println("in Close", s.name); return nil }
func newS2(name string) Sink { return Sink1{name} }
func main() {
tmpfile, err := os.CreateTemp("", "xxx")
if err != nil {
panic(err)
}
defer os.Remove(tmpfile.Name())
closers := []io.Closer{}
sink, err := newFileSink(tmpfile.Name())
if err != nil {
panic(err)
}
closers = append(closers, sink)
s1p := newS1p("ptr")
s1 := newS1("struct")
s2 := newS2("ptr2")
closers = append(closers, s1p, s1, s2)
for _, closer := range closers {
fmt.Println(closer.Close())
}
}
// Output:
// <nil>
// in Close ptr
// <nil>
// in Close struct
// <nil>
// in Close ptr2
// <nil>

24
_test/issue-1276.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"flag"
)
type customFlag struct{}
func (cf customFlag) String() string {
return "custom flag"
}
func (cf customFlag) Set(string) error {
return nil
}
func main() {
flag.Var(customFlag{}, "cf", "custom flag")
flag.Parse()
println("Hello, playground")
}
// Output:
// Hello, playground

30
_test/issue-1280.go Normal file
View File

@@ -0,0 +1,30 @@
package main
import (
"io"
"log"
"os"
)
type DBReader interface {
io.ReadCloser
io.ReaderAt
}
type DB struct {
f DBReader
}
func main() {
f, err := os.Open("/dev/null")
if err != nil {
log.Fatal(err)
}
d := &DB{f}
data := make([]byte, 1)
_, _ = d.f.ReadAt(data, 0)
println("bye")
}
// Output:
// bye

25
_test/issue-1285.go Normal file
View File

@@ -0,0 +1,25 @@
package main
type (
T1 struct{ Path [12]int8 }
T2 struct{ Path *[12]int8 }
)
var (
t11 = &T1{}
t21 = &T2{}
)
func main() {
b := [12]byte{}
t12 := &T1{}
t22 := &T2{}
b11 := (*[len(t11.Path)]byte)(&b)
b12 := (*[len(t12.Path)]byte)(&b)
b21 := (*[len(t21.Path)]byte)(&b)
b22 := (*[len(t22.Path)]byte)(&b)
println(len(b11), len(b12), len(b21), len(b22))
}
// Output:
// 12 12 12 12

10
_test/issue-1288.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
println("Hi")
goto done
done:
}
// Output:
// Hi

20
_test/issue-1300.go Normal file
View File

@@ -0,0 +1,20 @@
package main
const buflen = 512
type T struct {
buf []byte
}
func f(t *T) { *t = T{buf: make([]byte, 0, buflen)} }
func main() {
s := T{}
println(cap(s.buf))
f(&s)
println(cap(s.buf))
}
// Output:
// 0
// 512

16
_test/issue-1304.go Normal file
View File

@@ -0,0 +1,16 @@
package main
type Node struct {
Name string
Alias *Node
Child []*Node
}
func main() {
n := &Node{Name: "parent"}
n.Child = append(n.Child, &Node{Name: "child"})
println(n.Name, n.Child[0].Name)
}
// Output:
// parent child

15
_test/issue-1306.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
func check() (result bool, err error) {
return true, nil
}
func main() {
result, error := check()
fmt.Println(result, error)
}
// Output:
// true <nil>

27
_test/issue-1308.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import "fmt"
type test struct {
v interface{}
s string
}
type T struct {
name string
}
func main() {
t := []test{
{
v: []interface{}{
T{"hello"},
},
s: "world",
},
}
fmt.Println(t)
}
// Output:
// [{[{hello}] world}]

19
_test/issue-1311.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type T struct {
v interface{}
}
func f() (ret int64, err error) {
ret += 2
return
}
func main() {
t := &T{}
t.v, _ = f()
println(t.v.(int64))
}
// Output:
// 2

32
_test/issue-1315.go Normal file
View File

@@ -0,0 +1,32 @@
package main
type Intf interface {
M()
}
type T struct {
s string
}
func (t *T) M() { println("in M") }
func f(i interface{}) {
switch j := i.(type) {
case Intf:
j.M()
default:
println("default")
}
}
func main() {
var i Intf
var k interface{} = 1
i = &T{"hello"}
f(i)
f(k)
}
// Output:
// in M
// default

29
_test/issue-1320.go Normal file
View File

@@ -0,0 +1,29 @@
package main
type Pooler interface {
Get() string
}
type baseClient struct {
connPool Pooler
}
type connPool struct {
name string
}
func (c *connPool) Get() string { return c.name }
func newBaseClient(i int, p Pooler) *baseClient {
return &baseClient{connPool: p}
}
func newConnPool() *connPool { return &connPool{name: "connPool"} }
func main() {
b := newBaseClient(0, newConnPool())
println(b.connPool.(*connPool).name)
}
// Output:
// connPool

42
_test/issue-1326.go Normal file
View File

@@ -0,0 +1,42 @@
package main
type Option interface {
apply(*T)
}
type T struct {
s string
}
type opt struct {
name string
}
func (o *opt) apply(t *T) {
println(o.name)
}
func BuildOptions() []Option {
return []Option{
&opt{"opt1"},
&opt{"opt2"},
}
}
func NewT(name string, options ...Option) *T {
t := &T{name}
for _, opt := range options {
opt.apply(t)
}
return t
}
func main() {
t := NewT("hello", BuildOptions()...)
println(t.s)
}
// Output:
// opt1
// opt2
// hello

16
_test/issue-1328.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"crypto/sha1"
"encoding/hex"
)
func main() {
script := "hello"
sumRaw := sha1.Sum([]byte(script))
sum := hex.EncodeToString(sumRaw[:])
println(sum)
}
// Output:
// aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d

42
_test/issue-1330.go Normal file
View File

@@ -0,0 +1,42 @@
package main
import (
"fmt"
"io"
"net"
)
type wrappedConn struct {
net.Conn
}
func main() {
_, err := net.Listen("tcp", "127.0.0.1:49153")
if err != nil {
panic(err)
}
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
},
}
conn, err := dialer.Dial("tcp", "127.0.0.1:49153")
if err != nil {
panic(err)
}
defer conn.Close()
t := &wrappedConn{conn}
var w io.Writer = t
if n, err := w.Write([]byte("hello")); err != nil {
fmt.Println(err)
} else {
fmt.Println(n)
}
}
// Output:
// 5

37
_test/issue-1333.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
func mock(name string) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprint(rw, "Hello ", name)
}
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
panic(err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
func main() {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
mux.Handle("/", mock("foo"))
client(server.URL)
}
// Output:
// Hello foo

View File

@@ -3,7 +3,6 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"strings"
)
@@ -36,7 +35,7 @@ type pipe struct {
func newReadAutoCloser(r io.Reader) readAutoCloser {
if _, ok := r.(io.Closer); !ok {
return readAutoCloser{ioutil.NopCloser(r)}
return readAutoCloser{io.NopCloser(r)}
}
return readAutoCloser{r.(io.ReadCloser)}
}
@@ -44,7 +43,7 @@ func newReadAutoCloser(r io.Reader) readAutoCloser {
func main() {
p := &pipe{}
p.Reader = newReadAutoCloser(strings.NewReader("test"))
b, err := ioutil.ReadAll(p.Reader)
b, err := io.ReadAll(p.Reader)
if err != nil {
log.Fatal(err)
}

14
_test/method38.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"github.com/traefik/yaegi/_test/method38"
)
func main() {
fmt.Println(method38.Get())
}
// Output:
// &{[] {<nil>}}

19
_test/method38/a.go Normal file
View File

@@ -0,0 +1,19 @@
package method38
import "sync"
func NewPool() Pool { return Pool{} }
type Buffer struct {
bs []byte
pool Pool
}
type Pool struct {
p *sync.Pool
}
var (
_pool = NewPool()
Get = _pool.Get
)

3
_test/method38/b.go Normal file
View File

@@ -0,0 +1,3 @@
package method38
func (p Pool) Get() *Buffer { return &Buffer{} }

32
_test/method39.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"fmt"
"sync"
"github.com/traefik/yaegi/_test/method38"
)
func NewPool() Pool { return Pool{} }
type Buffer struct {
bs []byte
pool Pool
}
type Pool struct {
p *sync.Pool
}
var (
_pool = NewPool()
Get = _pool.Get
)
func main() {
fmt.Println(Get())
}
// Error:
// 17:11: undefined selector Get

View File

@@ -7,4 +7,4 @@ func main() {
}
// Error:
// 5:2: invalid operation: mismatched types int and float64
// 5:2: invalid operation: mismatched types int and untyped float

View File

@@ -2,11 +2,11 @@ package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
_, err := ioutil.ReadFile("__NotExisting__")
_, err := os.ReadFile("__NotExisting__")
if err != nil {
fmt.Println(err.Error())
}

22
_test/rune2.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
const majorVersion = '2'
type hashed struct {
major byte
}
func main() {
fmt.Println(majorVersion)
p := new(hashed)
p.major = majorVersion
fmt.Println(p)
}
// Output:
// 50
// &{50}

View File

@@ -9,10 +9,20 @@ func test(time string, t time.Time) string {
return time
}
var zero = time.Time{}
func test2(time string) time.Time {
return zero
}
func main() {
str := test("test", time.Now())
fmt.Println(str)
str2 := test2("test2")
fmt.Println(str2)
}
// Output:
// test
// 0001-01-01 00:00:00 +0000 UTC

View File

@@ -8,6 +8,7 @@ type A struct {
}
type D struct {
F *A
E *A
}

22
_test/struct61.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
type A struct {
B string
D
}
type D struct {
*A
E *A
}
func main() {
a := &A{B: "b"}
a.D = D{E: a}
fmt.Println(a.D.E.B)
}
// Output:
// b

11
_test/struct62.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
type A struct{ *A }
v := &A{}
v.A = v
println("v.A.A = v", v.A.A == v)
}
// Output:
// v.A.A = v true

17
_test/type27.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type Foo = int
func (f Foo) Bar() int {
return f * f
}
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 7:1: cannot define new methods on non-local type int

17
_test/type28.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type Foo = int
func (f *Foo) Bar() int {
return *f * *f
}
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 7:1: cannot define new methods on non-local type int

12
_test/type29.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var Foo int
func (f Foo) Bar() int {
return 1
}
func main() {}
// Error:
// 5:1: cannot define new methods on non-local type int

12
_test/type30.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var Foo *int
func (f Foo) Bar() int {
return 1
}
func main() {}
// Error:
// 5:1: cannot define new methods on non-local type int

17
_test/type31.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
func (f Foo) Bar() int {
return f * f
}
type Foo = int
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 5:1: cannot define new methods on non-local type int

17
_test/type32.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
func (f *Foo) Bar() int {
return *f * *f
}
type Foo = int
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 5:1: cannot define new methods on non-local type int

11
_test/type33.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func (f *Foo) Bar() int {
return *f * *f
}
func main() {
}
// Error:
// 3:1: undefined: Foo

View File

@@ -1,11 +1,14 @@
package main
import (
"fmt"
"math/bits"
"unsafe"
)
const SSize = 16
const (
SSize = 16
WSize = bits.UintSize / 8
)
type S struct {
X int
@@ -13,14 +16,19 @@ type S struct {
}
func main() {
bigEndian := (*(*[2]uint8)(unsafe.Pointer(&[]uint16{1}[0])))[0] == 0
var sBuf [SSize]byte
s := (*S)(unsafe.Pointer(&sBuf[0]))
s.X = 2
s.Y = 4
fmt.Println(sBuf)
if bigEndian {
println(sBuf[0+WSize-1], sBuf[WSize+WSize-1])
} else {
println(sBuf[0], sBuf[WSize])
}
}
// Output:
// [2 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0]
// 2 4

View File

@@ -2,10 +2,11 @@ package main
import (
"fmt"
"math/bits"
"unsafe"
)
const SSize = 24
const WSize = bits.UintSize / 8
type S struct {
X int
@@ -20,7 +21,8 @@ func main() {
{X: 3},
}
addr := unsafe.Pointer(&arr[0])
s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
// s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
s := *(*S)(unsafe.Pointer(uintptr(addr) + WSize*6))
fmt.Println(s.X)
}

View File

@@ -1,10 +1,12 @@
package main
import (
"fmt"
"math/bits"
"unsafe"
)
const WSize = bits.UintSize / 8
type S struct {
X int
Y int
@@ -13,12 +15,12 @@ type S struct {
func main() {
x := S{}
size := unsafe.Sizeof(x)
align := unsafe.Alignof(x.Y)
offset := unsafe.Offsetof(x.Z)
size := unsafe.Sizeof(x) / WSize
align := unsafe.Alignof(x.Y) / WSize
offset := unsafe.Offsetof(x.Z) / WSize
fmt.Println(size, align, offset)
println(size, align, offset)
}
// Output:
// 24 8 16
// 3 1 2

23
_test/unsafe6.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"fmt"
"unsafe"
)
type S struct {
X int
Y int
Z int
}
func main() {
x := S{Z: 5}
ptr := unsafe.Pointer(&x)
offset := int(unsafe.Offsetof(x.Z))
p := unsafe.Add(ptr, offset)
i := *(*int)(p)
fmt.Println(i)
}

20
_test/unsafe7.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"unsafe"
)
type S struct {
X int
Y int
Z int
}
func main() {
x := [2]S{{Z: 5}, {Z: 10}}
s := unsafe.Slice(&x[0], 2)
fmt.Println(s)
}

View File

@@ -18,12 +18,14 @@ func extractCmd(arg []string) error {
var name string
var exclude string
var include string
var tag string
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
eflag.StringVar(&name, "name", "", "the namespace for the extracted symbols")
eflag.StringVar(&exclude, "exclude", "", "comma separated list of regexp matching symbols to exclude")
eflag.StringVar(&include, "include", "", "comma separated list of regexp matching symbols to include")
eflag.StringVar(&tag, "tag", "", "comma separated list of build tags to be added to the created package")
eflag.Usage = func() {
fmt.Println("Usage: yaegi extract [options] packages...")
fmt.Println("Options:")
@@ -56,6 +58,9 @@ func extractCmd(arg []string) error {
Dest: name,
License: license,
}
if tag != "" {
ext.Tag = strings.Split(tag, ",")
}
if exclude != "" {
ext.Exclude = strings.Split(exclude, ",")

View File

@@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"reflect"
"strconv"
@@ -47,7 +46,12 @@ func run(arg []string) error {
}
args := rflag.Args()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i := interp.New(interp.Options{
GoPath: build.Default.GOPATH,
BuildTags: strings.Split(tags, ","),
Env: os.Environ(),
Unrestricted: useUnrestricted,
})
if err := i.Use(stdlib.Symbols); err != nil {
return err
}
@@ -130,7 +134,7 @@ func isFile(path string) bool {
}
func runFile(i *interp.Interpreter, path string, noAutoImport bool) error {
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
if err != nil {
return err
}

View File

@@ -116,7 +116,12 @@ func test(arg []string) (err error) {
return err
}
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i := interp.New(interp.Options{
GoPath: build.Default.GOPATH,
BuildTags: strings.Split(tags, ","),
Env: os.Environ(),
Unrestricted: useUnrestricted,
})
if err := i.Use(stdlib.Symbols); err != nil {
return err
}

View File

@@ -3,10 +3,10 @@ package main
import (
"bytes"
"context"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
@@ -37,7 +37,7 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
}
func TestYaegiCmdCancel(t *testing.T) {
tmp, err := ioutil.TempDir("", "yaegi-")
tmp, err := os.MkdirTemp("", "yaegi-")
if err != nil {
t.Fatalf("failed to create tmp directory: %v", err)
}
@@ -49,7 +49,15 @@ func TestYaegiCmdCancel(t *testing.T) {
}()
yaegi := filepath.Join(tmp, "yaegi")
build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
args := []string{"build"}
if raceDetectorSupported(runtime.GOOS, runtime.GOARCH) {
args = append(args, "-race")
}
args = append(args, "-o", yaegi, ".")
build := exec.Command("go", args...)
out, err := build.CombinedOutput()
if err != nil {
t.Fatalf("failed to build yaegi command: %v: %s", err, out)
@@ -115,3 +123,16 @@ func TestYaegiCmdCancel(t *testing.T) {
}
}
}
func raceDetectorSupported(goos, goarch string) bool {
switch goos {
case "linux":
return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64"
case "darwin":
return goarch == "amd64" || goarch == "arm64"
case "freebsd", "netbsd", "openbsd", "windows":
return goarch == "amd64"
default:
return false
}
}

67
example/fs/fs_test.go Normal file
View File

@@ -0,0 +1,67 @@
package fs1
import (
"testing"
// only available from 1.16.
"testing/fstest"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
var testFilesystem = fstest.MapFS{
"main.go": &fstest.MapFile{
Data: []byte(`package main
import (
"foo/bar"
"./localfoo"
)
func main() {
bar.PrintSomething()
localfoo.PrintSomethingElse()
}
`),
},
"_pkg/src/foo/bar/bar.go": &fstest.MapFile{
Data: []byte(`package bar
import (
"fmt"
)
func PrintSomething() {
fmt.Println("I am a virtual filesystem printing something from _pkg/src/foo/bar/bar.go!")
}
`),
},
"localfoo/foo.go": &fstest.MapFile{
Data: []byte(`package localfoo
import (
"fmt"
)
func PrintSomethingElse() {
fmt.Println("I am virtual filesystem printing else from localfoo/foo.go!")
}
`),
},
}
func TestFilesystemMapFS(t *testing.T) {
i := interp.New(interp.Options{
GoPath: "./_pkg",
SourcecodeFilesystem: testFilesystem,
})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
_, err := i.EvalPath(`main.go`)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -80,7 +80,14 @@ func init() {
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} {
{{- if eq $m.Name "String"}}
if W.WString == nil {
return ""
}
{{end -}}
{{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}}
}
{{end}}
{{end}}
`
@@ -270,6 +277,15 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
}
}
for _, t := range e.Tag {
if len(t) != 0 {
buildTags += "," + t
}
}
if len(buildTags) != 0 && buildTags[0] == ',' {
buildTags = buildTags[1:]
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": e.Dest,
@@ -336,6 +352,7 @@ type Extractor struct {
License string // License text to be included in the created package, optional.
Exclude []string // Comma separated list of regexp matching symbols to exclude.
Include []string // Comma separated list of regexp matching symbols to include.
Tag []string // Comma separated of build tags to be added to the created package.
}
// importPath checks whether pkgIdent is an existing directory relative to

2
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/traefik/yaegi
go 1.12
go 1.16

View File

@@ -3,8 +3,8 @@ package main
import (
"bytes"
"go/format"
"io/ioutil"
"log"
"os"
"strings"
"text/template"
)
@@ -557,7 +557,7 @@ func {{$name}}(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
@@ -579,7 +579,7 @@ func {{$name}}(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
@@ -602,7 +602,7 @@ func {{$name}}(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
@@ -1183,7 +1183,7 @@ func main() {
log.Fatal(err)
}
if err = ioutil.WriteFile("op.go", source, 0666); err != nil {
if err = os.WriteFile("op.go", source, 0o666); err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,61 @@
package unsafe2
import (
"reflect"
"unsafe"
)
type dummy struct{}
// DummyType represents a stand-in for a recursive type.
var DummyType = reflect.TypeOf(dummy{})
// the following type sizes must match their original definition in Go src/reflect/type.go.
type rtype struct {
_ uintptr
_ uintptr
_ uint32
_ uint32
_ uintptr
_ uintptr
_ uint32
_ uint32
}
type emptyInterface struct {
typ *rtype
_ unsafe.Pointer
}
type structField struct {
_ uintptr
typ *rtype
_ uintptr
}
type structType struct {
rtype
_ uintptr
fields []structField
}
// SetFieldType sets the type of the struct field at the given index, to the given type.
//
// The struct type must have been created at runtime. This is very unsafe.
func SetFieldType(s reflect.Type, idx int, t reflect.Type) {
if s.Kind() != reflect.Struct || idx >= s.NumField() {
return
}
rtyp := unpackType(s)
styp := (*structType)(unsafe.Pointer(rtyp))
f := styp.fields[idx]
f.typ = unpackType(t)
styp.fields[idx] = f
}
func unpackType(t reflect.Type) *rtype {
v := reflect.New(t).Elem().Interface()
return (*emptyInterface)(unsafe.Pointer(&v)).typ
}

View File

@@ -0,0 +1,33 @@
package unsafe2_test
import (
"reflect"
"testing"
"github.com/traefik/yaegi/internal/unsafe2"
)
func TestSwapFieldType(t *testing.T) {
f := []reflect.StructField{
{
Name: "A",
Type: reflect.TypeOf(int(0)),
},
{
Name: "B",
Type: reflect.PtrTo(unsafe2.DummyType),
},
{
Name: "C",
Type: reflect.TypeOf(int64(0)),
},
}
typ := reflect.StructOf(f)
ntyp := reflect.PtrTo(typ)
unsafe2.SetFieldType(typ, 1, ntyp)
if typ.Field(1).Type != ntyp {
t.Fatalf("unexpected field type: want %s; got %s", ntyp, typ.Field(1).Type)
}
}

View File

@@ -1,7 +1,6 @@
package interp
import (
"errors"
"fmt"
"go/ast"
"go/constant"
@@ -93,7 +92,8 @@ const (
switchIfStmt
typeAssertExpr
typeDecl
typeSpec
typeSpec // type A int
typeSpecAssign // type A = int
typeSwitch
unaryExpr
valueSpec
@@ -176,6 +176,7 @@ var kinds = [...]string{
typeAssertExpr: "typeAssertExpr",
typeDecl: "typeDecl",
typeSpec: "typeSpec",
typeSpecAssign: "typeSpecAssign",
typeSwitch: "typeSwitch",
unaryExpr: "unaryExpr",
valueSpec: "valueSpec",
@@ -360,21 +361,14 @@ func wrapInMain(src string) string {
return fmt.Sprintf("package main; func main() {%s\n}", src)
}
// Note: no type analysis is performed at this stage, it is done in pre-order
// processing of CFG, in order to accommodate forward type declarations.
// ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned.
// The given name is used to set the filename of the relevant source file in the
// interpreter's FileSet.
func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error) {
var inFunc bool
func (interp *Interpreter) parse(src, name string, inc bool) (node ast.Node, err error) {
mode := parser.DeclarationErrors
// Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or
// declarations will be always evaluated in the global scope.
var tok token.Token
var inFunc bool
if inc {
tok = interp.firstToken(src)
switch tok {
@@ -391,18 +385,18 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
}
if ok, err := interp.buildOk(&interp.context, name, src); !ok || err != nil {
return "", nil, err // skip source not matching build constraints
return nil, err // skip source not matching build constraints
}
f, err := parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
// only retry if we're on an expression/statement about a func
if !inc || tok != token.FUNC {
return "", nil, err
return nil, err
}
// do not bother retrying if we know it's an error we're going to ignore later on.
if ignoreError(err, src) {
return "", nil, err
return nil, err
}
// do not lose initial error, in case retrying fails.
initialError := err
@@ -410,16 +404,32 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
src := wrapInMain(strings.TrimPrefix(src, "package main;"))
f, err = parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
return "", nil, initialError
return nil, initialError
}
}
setYaegiTags(&interp.context, f.Comments)
if inFunc {
// return the body of the wrapper main function
return f.Decls[0].(*ast.FuncDecl).Body, nil
}
setYaegiTags(&interp.context, f.Comments)
return f, nil
}
// Note: no type analysis is performed at this stage, it is done in pre-order
// processing of CFG, in order to accommodate forward type declarations.
// ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned.
// The given name is used to set the filename of the relevant source file in the
// interpreter's FileSet.
func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
var err error
var root *node
var anc astNode
var st nodestack
var pkgName string
pkgName := "main"
addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node {
var i interface{}
@@ -627,7 +637,7 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
}
st.push(addChild(&root, anc, pos, kind, aNop), nod)
case *ast.CommentGroup:
case *ast.CommentGroup, *ast.EmptyStmt:
return false
case *ast.CompositeLit:
@@ -826,6 +836,10 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
st.push(addChild(&root, anc, pos, typeAssertExpr, aTypeAssert), nod)
case *ast.TypeSpec:
if a.Assign.IsValid() {
st.push(addChild(&root, anc, pos, typeSpecAssign, aNop), nod)
break
}
st.push(addChild(&root, anc, pos, typeSpec, aNop), nod)
case *ast.TypeSwitchStmt:
@@ -892,15 +906,8 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
}
return true
})
if inFunc {
// Incremental parsing: statements were inserted in a pseudo function.
// Set root to function body so its statements are evaluated in global scope.
root = root.child[1].child[3]
root.anc = nil
}
if pkgName == "" {
return "", root, errors.New("no package name found")
}
interp.roots = append(interp.roots, root)
return pkgName, root, err
}

View File

@@ -49,9 +49,11 @@ const nilIdent = "nil"
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
// Following this pass, the CFG is ready to run.
func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc := interp.initScopePkg(importPath)
check := typecheck{}
func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string) ([]*node, error) {
if sc == nil {
sc = interp.initScopePkg(importPath, pkgName)
}
check := typecheck{scope: sc}
var initNodes []*node
var err error
@@ -62,6 +64,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if err != nil {
return false
}
if n.scope == nil {
n.scope = sc
}
switch n.kind {
case binaryExpr, unaryExpr, parenExpr:
if isBoolAction(n) {
@@ -145,10 +150,10 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
switch typ.Kind() {
case reflect.Map:
n.anc.gen = rangeMap
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil)))
sc.add(ityp)
ktyp = &itype{cat: valueT, rtype: typ.Key()}
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
ktyp = valueTOf(typ.Key())
vtyp = valueTOf(typ.Elem())
case reflect.String:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
sc.add(sc.getType("int")) // Add a dummy type to store index for range
@@ -157,11 +162,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case reflect.Array, reflect.Slice:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
ktyp = sc.getType("int")
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
vtyp = valueTOf(typ.Elem())
}
case mapT:
n.anc.gen = rangeMap
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil)))
sc.add(ityp)
ktyp = o.typ.key
vtyp = o.typ.val
@@ -169,7 +174,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
ktyp = sc.getType("int")
vtyp = o.typ.val
if vtyp.cat == valueT {
vtyp = &itype{cat: valueT, rtype: vtyp.rtype.Elem()}
vtyp = valueTOf(vtyp.rtype.Elem())
} else {
vtyp = vtyp.val
}
@@ -284,19 +289,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case compositeLitExpr:
if len(n.child) > 0 && n.child[0].isType(sc) {
// Get type from 1st child
// Get type from 1st child.
if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil {
return false
}
// Indicate that the first child is the type
// Indicate that the first child is the type.
n.nleft = 1
} else {
// Get type from ancestor (implicit type)
if n.anc.kind == keyValueExpr && n == n.anc.child[0] {
n.typ = n.anc.typ.key
} else if atyp := n.anc.typ; atyp != nil {
if atyp.cat == valueT {
n.typ = &itype{cat: valueT, rtype: atyp.rtype.Elem()}
if atyp.cat == valueT && hasElem(atyp.rtype) {
n.typ = valueTOf(atyp.rtype.Elem())
} else {
n.typ = atyp.val
}
@@ -410,11 +415,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc.loop = n
case importSpec:
// already all done in gta
// Already all done in GTA.
return false
case typeSpec:
// processing already done in GTA pass for global types, only parses inlined types
// Processing already done in GTA pass for global types, only parses inlined types.
if sc.def == nil {
return false
}
@@ -424,13 +429,16 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return false
}
if typ.incomplete {
err = n.cfgErrorf("invalid type declaration")
return false
// Type may still be incomplete in case of a local recursive struct declaration.
if typ, err = typ.finalize(); err != nil {
err = n.cfgErrorf("invalid type declaration")
return false
}
}
switch n.child[1].kind {
case identExpr, selectorExpr:
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
n.typ = namedOf(typ, pkgName, typeName)
default:
n.typ = typ
n.typ.name = typeName
@@ -443,7 +451,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// values which may be used in further declarations.
if !sc.global {
for _, c := range n.child {
if _, err = interp.cfg(c, importPath); err != nil {
if _, err = interp.cfg(c, sc, importPath, pkgName); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
}
@@ -477,7 +485,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
break
}
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
n.typ = ptrOf(n.child[0].typ)
n.findex = sc.add(n.typ)
case assignStmt, defineStmt:
@@ -513,7 +521,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return
}
if src.typ.isBinMethod {
dest.typ = &itype{cat: valueT, rtype: src.typ.methodCallType()}
dest.typ = valueTOf(src.typ.methodCallType())
} else {
// In a new definition, propagate the source type to the destination
// type. If the source is an untyped constant, make sure that the
@@ -572,7 +580,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
dest.gen = nop
case isFuncField(dest):
// Setting a struct field of function type requires an extra step. Do not optimize.
case isCall(src) && !isInterfaceSrc(dest.typ) && !isRecursiveField(dest) && n.kind != defineStmt:
case isCall(src) && !isInterfaceSrc(dest.typ) && n.kind != defineStmt:
// Call action may perform the assignment directly.
if dest.typ.id() != src.typ.id() {
// Skip optimitization if returned type doesn't match assigned one.
@@ -594,8 +602,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Skip optimisation for assigned interface.
break
}
if dest.action == aGetIndex {
// Skip optimization, as it does not work when assigning to a struct field.
if dest.action == aGetIndex || dest.action == aStar {
// Skip optimization, as it does not work when assigning to a struct field or a dereferenced pointer.
break
}
n.gen = nop
@@ -657,7 +665,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if r := lc.child[0].typ.numOut(); r != l {
err = n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, lc.child[0].name(), r)
}
n.gen = nop
if isBinCall(lc, sc) {
n.gen = nop
} else {
// TODO (marc): skip if no conversion or wrapping is needed.
n.gen = assignFromCall
}
case indexExpr:
lc.gen = getIndexMap2
n.gen = nop
@@ -762,7 +775,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case ptrT:
n.typ = t.val
if t.val.cat == valueT {
n.typ = &itype{cat: valueT, rtype: t.val.rtype.Elem()}
n.typ = valueTOf(t.val.rtype.Elem())
} else {
n.typ = t.val.val
}
@@ -772,7 +785,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if t.rtype.Kind() == reflect.String {
n.typ = sc.getType("byte")
} else {
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()}
n.typ = valueTOf(t.rtype.Elem())
}
default:
n.typ = t.val
@@ -858,7 +871,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case labeledStmt:
wireChild(n)
n.start = n.child[1].start
if len(n.child) > 1 {
n.start = n.child[1].start
}
gotoLabel(n.sym)
case callExpr:
@@ -885,7 +900,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Store result directly to frame output location, to avoid a frame copy.
n.findex = 0
case bname == "cap" && isInConstOrTypeDecl(n):
switch n.child[1].typ.TypeOf().Kind() {
t := n.child[1].typ.TypeOf()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
switch t.Kind() {
case reflect.Array, reflect.Chan:
capConst(n)
default:
@@ -894,7 +913,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.findex = notInFrame
n.gen = nop
case bname == "len" && isInConstOrTypeDecl(n):
switch n.child[1].typ.TypeOf().Kind() {
t := n.child[1].typ.TypeOf()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.String:
lenConst(n)
default:
@@ -955,7 +978,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.typ = c0.typ
n.findex = sc.add(n.typ)
}
case isBinCall(n):
case isBinCall(n, sc):
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
if err != nil {
break
@@ -973,13 +996,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc.add(funcType.ret[i])
}
} else {
n.typ = &itype{cat: valueT, rtype: typ.Out(0)}
n.typ = valueTOf(typ.Out(0))
if n.anc.kind == returnStmt {
n.findex = childPos(n)
} else {
n.findex = sc.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
sc.add(valueTOf(typ.Out(i)))
}
}
}
@@ -995,7 +1018,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
err = n.cfgErrorf("struct does not contain field: %s", c1.child[1].ident)
break
}
n.typ = &itype{cat: valueT, rtype: reflect.TypeOf(field.Offset)}
n.typ = valueTOf(reflect.TypeOf(field.Offset))
n.rval = reflect.ValueOf(field.Offset)
n.gen = nop
default:
@@ -1081,8 +1104,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case reflect.Struct:
err = check.structBinLitExpr(child, rtype)
case reflect.Map:
ktyp := &itype{cat: valueT, rtype: rtype.Key()}
vtyp := &itype{cat: valueT, rtype: rtype.Elem()}
ktyp := valueTOf(rtype.Key())
vtyp := valueTOf(rtype.Elem())
err = check.mapLitExpr(child, ktyp, vtyp)
}
}
@@ -1223,7 +1246,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case funcDecl:
n.start = n.child[3].start
n.types = sc.types
n.types, n.scope = sc.types, sc
sc = sc.pop()
funcName := n.child[1].ident
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
@@ -1234,7 +1257,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
case funcLit:
n.types = sc.types
n.types, n.scope = sc.types, sc
sc = sc.pop()
err = genRun(n)
@@ -1255,7 +1278,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// retry with the filename, in case ident is a package name.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found {
err = n.cfgErrorf("undefined: %s", n.ident)
err = n.cfgErrorf("undefined: %s %d", n.ident, n.index)
break
}
}
@@ -1480,9 +1503,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
pkg := n.child[0].sym.typ.path
if s, ok := interp.binPkg[pkg][name]; ok {
if isBinType(s) {
n.typ = &itype{cat: valueT, rtype: s.Type().Elem()}
n.typ = valueTOf(s.Type().Elem())
} else {
n.typ = &itype{cat: valueT, rtype: fixPossibleConstType(s.Type()), untyped: isValueUntyped(s)}
n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s)))
n.rval = s
}
n.action = aGetSym
@@ -1534,16 +1557,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = getPtrIndexSeq
if n.typ.cat == funcT {
// Function in a struct field is always wrapped in reflect.Value.
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ)
}
default:
n.gen = getIndexSeq
n.typ = n.typ.fieldSeq(ti)
if n.typ.cat == funcT {
// Function in a struct field is always wrapped in reflect.Value.
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ)
}
}
break
@@ -1565,7 +1586,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
}
n.val = lind
n.typ = &itype{cat: valueT, rtype: s.Type}
n.typ = valueTOf(s.Type)
break
}
// No field (embedded or not) matched. Try to match a method.
@@ -1582,13 +1603,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
n.typ = valueTOf(method.Type, isBinMethod())
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.rtype.Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
break
@@ -1596,7 +1617,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
case n.typ.rtype.Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getIndexSeq
break
@@ -1608,7 +1629,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = &itype{cat: valueT, rtype: m2.Type, recv: &itype{cat: valueT, rtype: pt}, isBinMethod: true}
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
break
@@ -1619,18 +1640,18 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Handle pointer on object defined in runtime
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.typ = &itype{cat: valueT, rtype: method.Type, recv: n.typ, isBinMethod: true}
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinElemMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = &itype{cat: valueT, rtype: method.Type, recv: &itype{cat: valueT, rtype: reflect.PtrTo(n.typ.val.rtype)}, isBinMethod: true}
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
@@ -1665,7 +1686,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = &itype{cat: valueT, rtype: m.Type, recv: n.child[0].typ, isBinMethod: true}
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
@@ -1734,7 +1755,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case n.child[0].isType(sc):
// pointer type expression
n.gen = nop
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
n.typ = ptrOf(n.child[0].typ)
default:
// dereference expression
wireChild(n)
@@ -1745,7 +1766,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
if c0 := n.child[0]; c0.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: c0.typ.rtype.Elem()}
n.typ = valueTOf(c0.typ.rtype.Elem())
} else {
n.typ = c0.typ.val
}
@@ -1879,7 +1900,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if n.anc.action != aAssignX {
if c0.typ.cat == valueT && isFunc(c1.typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: c1.typ.TypeOf()}
n.typ = valueTOf(c1.typ.TypeOf())
} else {
n.typ = c1.typ
}
@@ -1911,7 +1932,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if n.action == aRecv {
// Channel receive operation: set type to the channel data type
if n.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: n.typ.rtype.Elem()}
n.typ = valueTOf(n.typ.rtype.Elem())
} else {
n.typ = n.typ.val
}
@@ -1995,18 +2016,23 @@ func compDefineX(sc *scope, n *node) error {
if funtype.cat == valueT {
// Handle functions imported from runtime.
for i := 0; i < funtype.rtype.NumOut(); i++ {
types = append(types, &itype{cat: valueT, rtype: funtype.rtype.Out(i)})
types = append(types, valueTOf(funtype.rtype.Out(i)))
}
} else {
types = funtype.ret
}
if n.child[l-1].isType(sc) {
if n.anc.kind == varDecl && n.child[l-1].isType(sc) {
l--
}
if len(types) != l {
return n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, src.child[0].name(), len(types))
}
n.gen = nop
if isBinCall(src, sc) {
n.gen = nop
} else {
// TODO (marc): skip if no conversion or wrapping is needed.
n.gen = assignFromCall
}
case indexExpr:
types = append(types, src.typ, sc.getType("bool"))
@@ -2405,25 +2431,11 @@ func isField(n *node) bool {
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
}
func isRecursiveField(n *node) bool {
if !isField(n) {
return false
}
t := n.typ
for t != nil {
if t.recursive {
return true
}
t = t.val
}
return false
}
func isInConstOrTypeDecl(n *node) bool {
anc := n.anc
for anc != nil {
switch anc.kind {
case constDecl, typeDecl:
case constDecl, typeDecl, arrayType, chanType:
return true
case varDecl, funcDecl:
return false
@@ -2469,8 +2481,19 @@ func isCall(n *node) bool {
return n.action == aCall || n.action == aCallSlice
}
func isBinCall(n *node) bool {
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
func isBinCall(n *node, sc *scope) bool {
if !isCall(n) || len(n.child) == 0 {
return false
}
c0 := n.child[0]
if c0.typ == nil {
// If called early in parsing, child type may not be known yet.
c0.typ, _ = nodeType(n.interp, sc, c0)
if c0.typ == nil {
return false
}
}
return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func
}
func isOffsetof(n *node) bool {
@@ -2576,7 +2599,7 @@ func gotoLabel(s *symbol) {
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
switch typ.cat {
case aliasT, ptrT:
gen = compositeGenerator(n, n.typ.val, rtyp)
gen = compositeGenerator(n, typ.val, rtyp)
case arrayT, sliceT:
gen = arrayLit
case mapT:
@@ -2630,23 +2653,54 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
// arrayTypeLen returns the node's array length. If the expression is an
// array variable it is determined from the value's type, otherwise it is
// computed from the source definition.
func arrayTypeLen(n *node) int {
func arrayTypeLen(n *node, sc *scope) (int, error) {
if n.typ != nil && n.typ.cat == arrayT {
return n.typ.length
return n.typ.length, nil
}
max := -1
for i, c := range n.child[1:] {
r := i
if c.kind == keyValueExpr {
if v := c.child[0].rval; v.IsValid() {
r = int(c.child[0].rval.Int())
for _, c := range n.child[1:] {
var r int
if c.kind != keyValueExpr {
r = max + 1
max = r
continue
}
c0 := c.child[0]
v := c0.rval
if v.IsValid() {
r = int(v.Int())
} else {
// Resolve array key value as a constant.
if c0.kind == identExpr {
// Key is defined by a symbol which must be a constant integer.
sym, _, ok := sc.lookup(c0.ident)
if !ok {
return 0, c0.cfgErrorf("undefined: %s", c0.ident)
}
if sym.kind != constSym {
return 0, c0.cfgErrorf("non-constant array bound %q", c0.ident)
}
r = int(vInt(sym.rval))
} else {
// Key is defined by a numeric constant expression.
if _, err := c0.interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil {
return 0, err
}
cv, ok := c0.rval.Interface().(constant.Value)
if !ok {
return 0, c0.cfgErrorf("non-constant expression")
}
r = constToInt(cv)
}
}
if r > max {
max = r
}
}
return max + 1
return max + 1, nil
}
// isValueUntyped returns true if value is untyped.

84
interp/compile_test.go Normal file
View File

@@ -0,0 +1,84 @@
package interp
import (
"go/ast"
"go/parser"
"go/token"
"testing"
"github.com/traefik/yaegi/stdlib"
)
func TestCompileAST(t *testing.T) {
file, err := parser.ParseFile(token.NewFileSet(), "_.go", `
package main
import "fmt"
type Foo struct{}
var foo Foo
const bar = "asdf"
func main() {
fmt.Println(1)
}
`, 0)
if err != nil {
panic(err)
}
if len(file.Imports) != 1 || len(file.Decls) != 5 {
panic("wrong number of imports or decls")
}
dType := file.Decls[1].(*ast.GenDecl)
dVar := file.Decls[2].(*ast.GenDecl)
dConst := file.Decls[3].(*ast.GenDecl)
dFunc := file.Decls[4].(*ast.FuncDecl)
if dType.Tok != token.TYPE {
panic("decl[1] is not a type")
}
if dVar.Tok != token.VAR {
panic("decl[2] is not a var")
}
if dConst.Tok != token.CONST {
panic("decl[3] is not a const")
}
cases := []struct {
desc string
node ast.Node
skip string
}{
{desc: "file", node: file},
{desc: "import", node: file.Imports[0]},
{desc: "type", node: dType},
{desc: "var", node: dVar, skip: "not supported"},
{desc: "const", node: dConst},
{desc: "func", node: dFunc},
{desc: "block", node: dFunc.Body},
{desc: "expr", node: dFunc.Body.List[0]},
}
i := New(Options{})
_ = i.Use(stdlib.Symbols)
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
if c.skip != "" {
t.Skip(c.skip)
}
i := i
if _, ok := c.node.(*ast.File); ok {
i = New(Options{})
_ = i.Use(stdlib.Symbols)
}
_, err := i.CompileAST(c.node)
if err != nil {
t.Fatalf("Failed to compile %s: %v", c.desc, err)
}
})
}
}

730
interp/debugger.go Normal file
View File

@@ -0,0 +1,730 @@
package interp
import (
"context"
"errors"
"fmt"
"go/token"
"reflect"
"sort"
"sync"
)
var (
// ErrNotLive indicates that the specified ID does not refer to a (live) Go
// routine.
ErrNotLive = errors.New("not live")
// ErrRunning indicates that the specified Go routine is running.
ErrRunning = errors.New("running")
// ErrNotRunning indicates that the specified Go routine is running.
ErrNotRunning = errors.New("not running")
)
var rNodeType = reflect.TypeOf((*node)(nil)).Elem()
// A Debugger can be used to debug a Yaegi program.
type Debugger struct {
interp *Interpreter
events func(*DebugEvent)
context context.Context
cancel context.CancelFunc
gWait *sync.WaitGroup
gLock *sync.Mutex
gID int
gLive map[int]*debugRoutine
result reflect.Value
err error
}
// go routine debug state.
type debugRoutine struct {
id int
mode DebugEventReason
running bool
resume chan struct{}
fDepth int
fStep int
}
// node debug state.
type nodeDebugData struct {
program *Program
breakOnLine bool
breakOnCall bool
}
// frame debug state.
type frameDebugData struct {
g *debugRoutine
node *node
name string
kind frameKind
scope *scope
}
// frame kind.
type frameKind int
const (
// interpreter root frame.
frameRoot frameKind = iota + 1
// function call frame.
frameCall
// closure capture frame.
frameClosure
)
// DebugOptions are the debugger options.
type DebugOptions struct {
// If true, Go routine IDs start at 1 instead of 0.
GoRoutineStartAt1 bool
}
// A DebugEvent is an event generated by a debugger.
type DebugEvent struct {
debugger *Debugger
reason DebugEventReason
frame *frame
}
// DebugFrame provides access to stack frame information while debugging a
// program.
type DebugFrame struct {
event *DebugEvent
frames []*frame
}
// DebugFrameScope provides access to scoped variables while debugging a
// program.
type DebugFrameScope struct {
frame *frame
}
// DebugVariable is the name and value of a variable from a debug session.
type DebugVariable struct {
Name string
Value reflect.Value
}
// DebugGoRoutine provides access to information about a Go routine while
// debugging a program.
type DebugGoRoutine struct {
id int
}
// Breakpoint is the result of attempting to set a breakpoint.
type Breakpoint struct {
// Valid indicates whether the breakpoint was successfully set.
Valid bool
// Position indicates the source position of the breakpoint.
Position token.Position
}
// DebugEventReason is the reason a debug event occurred.
type DebugEventReason int
const (
// continue execution normally.
debugRun DebugEventReason = iota
// DebugPause is emitted when a pause request is completed. Can be used with
// Interrupt to request a pause.
DebugPause
// DebugBreak is emitted when a debug target hits a breakpoint.
DebugBreak
// DebugEntry is emitted when a debug target starts executing. Can be used
// with Step to produce a corresponding event when execution starts.
DebugEntry
// DebugStepInto is emitted when a stepInto request is completed. Can be
// used with Step or Interrupt to request a stepInto.
DebugStepInto
// DebugStepOver is emitted when a stepOver request is completed. Can be
// used with Step or Interrupt to request a stepOver.
DebugStepOver
// DebugStepOut is emitted when a stepOut request is completed. Can be used
// with Step or Interrupt to request a stepOut.
DebugStepOut
// DebugTerminate is emitted when a debug target terminates. Can be used
// with Interrupt to attempt to terminate the program.
DebugTerminate
// DebugEnterGoRoutine is emitted when a Go routine is entered.
DebugEnterGoRoutine
// DebugExitGoRoutine is emitted when a Go routine is exited.
DebugExitGoRoutine
)
// Debug initializes a debugger for the given program.
//
// The program will not start running until Step or Continue has been called. If
// Step is called with DebugEntry, an entry event will be generated before the
// first statement is executed. Otherwise, the debugger will behave as usual.
func (interp *Interpreter) Debug(ctx context.Context, prog *Program, events func(*DebugEvent), opts *DebugOptions) *Debugger {
dbg := new(Debugger)
dbg.interp = interp
dbg.events = events
dbg.context, dbg.cancel = context.WithCancel(ctx)
dbg.gWait = new(sync.WaitGroup)
dbg.gLock = new(sync.Mutex)
dbg.gLive = make(map[int]*debugRoutine, 1)
if opts == nil {
opts = new(DebugOptions)
}
if opts.GoRoutineStartAt1 {
dbg.gID = 1
}
mainG := dbg.enterGoRoutine()
mainG.mode = DebugEntry
interp.debugger = dbg
interp.frame.debug = &frameDebugData{kind: frameRoot, g: mainG}
prog.root.Walk(func(n *node) bool {
n.setProgram(prog)
return true
}, nil)
go func() {
defer func() { interp.debugger = nil }()
defer events(&DebugEvent{reason: DebugTerminate})
defer dbg.cancel()
<-mainG.resume
dbg.events(&DebugEvent{dbg, DebugEnterGoRoutine, interp.frame})
dbg.result, dbg.err = interp.ExecuteWithContext(ctx, prog)
dbg.exitGoRoutine(mainG)
dbg.events(&DebugEvent{dbg, DebugExitGoRoutine, interp.frame})
dbg.gWait.Wait()
}()
return dbg
}
// Wait blocks until all Go routines launched by the program have terminated.
// Wait returns the results of `(*Interpreter).Execute`.
func (dbg *Debugger) Wait() (reflect.Value, error) {
<-dbg.context.Done()
return dbg.result, dbg.err
}
// mark entry into a go routine.
func (dbg *Debugger) enterGoRoutine() *debugRoutine {
g := new(debugRoutine)
g.resume = make(chan struct{})
dbg.gWait.Add(1)
dbg.gLock.Lock()
g.id = dbg.gID
dbg.gID++
dbg.gLive[g.id] = g
dbg.gLock.Unlock()
return g
}
// mark exit from a go routine.
func (dbg *Debugger) exitGoRoutine(g *debugRoutine) {
dbg.gLock.Lock()
delete(dbg.gLive, g.id)
dbg.gLock.Unlock()
dbg.gWait.Done()
}
// get the state for a given go routine, if it's live.
func (dbg *Debugger) getGoRoutine(id int) (*debugRoutine, bool) {
dbg.gLock.Lock()
g, ok := dbg.gLive[id]
dbg.gLock.Unlock()
return g, ok
}
// mark entry into a function call.
func (dbg *Debugger) enterCall(nFunc, nCall *node, f *frame) {
if f.debug != nil {
f.debug.g.fDepth++
return
}
f.debug = new(frameDebugData)
f.debug.g = f.anc.debug.g
f.debug.scope = nFunc.scope
switch nFunc.kind {
case funcLit:
f.debug.kind = frameCall
if nFunc.frame != nil {
nFunc.frame.debug.kind = frameClosure
nFunc.frame.debug.node = nFunc
}
case funcDecl:
f.debug.kind = frameCall
f.debug.name = nFunc.child[1].ident
}
if nCall != nil && nCall.anc.kind == goStmt {
f.debug.g = dbg.enterGoRoutine()
dbg.events(&DebugEvent{dbg, DebugEnterGoRoutine, f})
}
f.debug.g.fDepth++
}
// mark exit from a function call.
func (dbg *Debugger) exitCall(nFunc, nCall *node, f *frame) {
_ = nFunc // ignore unused, so exitCall can have the same signature as enterCall
f.debug.g.fDepth--
if nCall != nil && nCall.anc.kind == goStmt {
dbg.exitGoRoutine(f.debug.g)
dbg.events(&DebugEvent{dbg, DebugExitGoRoutine, f})
}
}
// called by the interpreter prior to executing the node.
func (dbg *Debugger) exec(n *node, f *frame) (stop bool) {
f.debug.node = n
if n != nil && n.pos == token.NoPos {
return false
}
g := f.debug.g
defer func() { g.running = true }()
e := &DebugEvent{dbg, g.mode, f}
switch {
case g.mode == DebugTerminate:
dbg.cancel()
return true
case n.shouldBreak():
e.reason = DebugBreak
case g.mode == debugRun:
return false
case g.mode == DebugStepOut:
if g.fDepth >= g.fStep {
return false
}
case g.mode == DebugStepOver:
if g.fDepth > g.fStep {
return false
}
}
dbg.events(e)
g.running = false
select {
case <-g.resume:
return false
case <-dbg.context.Done():
return true
}
}
// Continue continues execution of the specified Go routine. Continue returns
// ErrNotLive if there is no Go routine with the corresponding ID, or if it is not
// live.
func (dbg *Debugger) Continue(id int) error {
g, ok := dbg.getGoRoutine(id)
if !ok {
return ErrNotLive
}
g.mode = debugRun
g.resume <- struct{}{}
return nil
}
// update the exec mode of this routine.
func (g *debugRoutine) setMode(reason DebugEventReason) {
if g.mode == DebugTerminate {
return
}
if g.mode == DebugEntry && reason == DebugEntry {
return
}
switch reason {
case DebugStepInto, DebugStepOver, DebugStepOut:
g.mode, g.fStep = reason, g.fDepth
default:
g.mode = DebugPause
}
}
// Step issues a stepInto, stepOver, or stepOut request to a stopped Go routine.
// Step returns ErrRunning if the Go routine is running. Step returns ErrNotLive
// if there is no Go routine with the corresponding ID, or if it is not live.
func (dbg *Debugger) Step(id int, reason DebugEventReason) error {
g, ok := dbg.getGoRoutine(id)
if !ok {
return ErrNotLive
}
if g.running {
return ErrRunning
}
g.setMode(reason)
g.resume <- struct{}{}
return nil
}
// Interrupt issues a stepInto, stepOver, or stepOut request to a running Go
// routine. Interrupt returns ErrRunning if the Go routine is running. Interrupt
// returns ErrNotLive if there is no Go routine with the corresponding ID, or if
// it is not live.
func (dbg *Debugger) Interrupt(id int, reason DebugEventReason) bool {
g, ok := dbg.getGoRoutine(id)
if !ok {
return false
}
g.setMode(reason)
return true
}
// Terminate attempts to terminate the program.
func (dbg *Debugger) Terminate() {
dbg.gLock.Lock()
g := dbg.gLive
dbg.gLive = nil
dbg.gLock.Unlock()
for _, g := range g {
g.mode = DebugTerminate
close(g.resume)
}
}
// BreakpointTarget is the target of a request to set breakpoints.
type BreakpointTarget func(*Debugger, func(*node))
// PathBreakpointTarget is used to set breapoints on compiled code by path. This
// can be used to set breakpoints on code compiled with EvalPath, or source
// packages loaded by Yaegi.
func PathBreakpointTarget(path string) BreakpointTarget {
return func(dbg *Debugger, cb func(*node)) {
for _, r := range dbg.interp.roots {
f := dbg.interp.fset.File(r.pos)
if f != nil && f.Name() == path {
cb(r)
return
}
}
}
}
// ProgramBreakpointTarget is used to set breakpoints on a Program.
func ProgramBreakpointTarget(prog *Program) BreakpointTarget {
return func(_ *Debugger, cb func(*node)) {
cb(prog.root)
}
}
// AllBreakpointTarget is used to set breakpoints on all compiled code. Do not
// use with LineBreakpoint.
func AllBreakpointTarget() BreakpointTarget {
return func(dbg *Debugger, cb func(*node)) {
for _, r := range dbg.interp.roots {
cb(r)
}
}
}
type breakpointSetup struct {
roots []*node
lines map[int]int
funcs map[string]int
}
// BreakpointRequest is a request to set a breakpoint.
type BreakpointRequest func(*breakpointSetup, int)
// LineBreakpoint requests a breakpoint on the given line.
func LineBreakpoint(line int) BreakpointRequest {
return func(b *breakpointSetup, i int) {
b.lines[line] = i
}
}
// FunctionBreakpoint requests a breakpoint on the named function.
func FunctionBreakpoint(name string) BreakpointRequest {
return func(b *breakpointSetup, i int) {
b.funcs[name] = i
}
}
// SetBreakpoints sets breakpoints for the given target. The returned array has
// an entry for every request, in order. If a given breakpoint request cannot be
// satisfied, the corresponding entry will be marked invalid. If the target
// cannot be found, all entries will be marked invalid.
func (dbg *Debugger) SetBreakpoints(target BreakpointTarget, requests ...BreakpointRequest) []Breakpoint {
// start with all breakpoints unverified
results := make([]Breakpoint, len(requests))
// prepare all the requests
setup := new(breakpointSetup)
target(dbg, func(root *node) {
setup.roots = append(setup.roots, root)
setup.lines = make(map[int]int, len(requests))
setup.funcs = make(map[string]int, len(requests))
for i, rq := range requests {
rq(setup, i)
}
})
// find breakpoints
for _, root := range setup.roots {
root.Walk(func(n *node) bool {
// function breakpoints
if len(setup.funcs) > 0 && n.kind == funcDecl {
// reset stale breakpoints
n.start.setBreakOnCall(false)
if i, ok := setup.funcs[n.child[1].ident]; ok && !results[i].Valid {
results[i].Valid = true
results[i].Position = dbg.interp.fset.Position(n.start.pos)
n.start.setBreakOnCall(true)
return true
}
}
// line breakpoints
if len(setup.lines) > 0 && n.pos.IsValid() && n.action != aNop && getExec(n) != nil {
// reset stale breakpoints
n.setBreakOnLine(false)
pos := dbg.interp.fset.Position(n.pos)
if i, ok := setup.lines[pos.Line]; ok && !results[i].Valid {
results[i].Valid = true
results[i].Position = pos
n.setBreakOnLine(true)
return true
}
}
return true
}, nil)
}
return results
}
// GoRoutines returns an array of live Go routines.
func (dbg *Debugger) GoRoutines() []*DebugGoRoutine {
dbg.gLock.Lock()
r := make([]*DebugGoRoutine, 0, len(dbg.gLive))
for id := range dbg.gLive {
r = append(r, &DebugGoRoutine{id})
}
dbg.gLock.Unlock()
sort.Slice(r, func(i, j int) bool { return r[i].id < r[j].id })
return r
}
// ID returns the ID of the Go routine.
func (r *DebugGoRoutine) ID() int { return r.id }
// Name returns "Goroutine {ID}".
func (r *DebugGoRoutine) Name() string { return fmt.Sprintf("Goroutine %d", r.id) }
// GoRoutine returns the ID of the Go routine that generated the event.
func (evt *DebugEvent) GoRoutine() int {
if evt.frame.debug == nil {
return 0
}
return evt.frame.debug.g.id
}
// Reason returns the reason for the event.
func (evt *DebugEvent) Reason() DebugEventReason {
return evt.reason
}
// Walk the stack trace frames. The root frame is included if and only if it is
// the only frame. Closure frames are rolled up into the following call frame.
func (evt *DebugEvent) walkFrames(fn func([]*frame) bool) {
if evt.frame == evt.frame.root {
fn([]*frame{evt.frame})
return
}
var g *debugRoutine
if evt.frame.debug != nil {
g = evt.frame.debug.g
}
var frames []*frame
for f := evt.frame; f != nil && f != f.root && (f.debug == nil || f.debug.g == g); f = f.anc {
if f.debug == nil || f.debug.kind != frameCall {
frames = append(frames, f)
continue
}
if len(frames) > 0 {
if !fn(frames) {
return
}
}
frames = frames[:0]
frames = append(frames, f)
}
if len(frames) > 0 {
fn(frames)
}
}
// FrameDepth returns the number of call frames in the stack trace.
func (evt *DebugEvent) FrameDepth() int {
if evt.frame == evt.frame.root {
return 1
}
var n int
evt.walkFrames(func([]*frame) bool { n++; return true })
return n
}
// Frames returns the call frames in the range [start, end).
func (evt *DebugEvent) Frames(start, end int) []*DebugFrame {
count := end - start
if count < 0 {
return nil
}
frames := []*DebugFrame{}
evt.walkFrames(func(f []*frame) bool {
df := &DebugFrame{evt, make([]*frame, len(f))}
copy(df.frames, f)
frames = append(frames, df)
return len(frames) < count
})
return frames
}
// Name returns the name of the stack frame. For function calls to named
// functions, this is the function name.
func (f *DebugFrame) Name() string {
d := f.frames[0].debug
if d == nil {
return "<unknown>"
}
switch d.kind {
case frameRoot:
return "<init>"
case frameClosure:
return "<closure>"
case frameCall:
if d.name == "" {
return "<anonymous>"
}
return d.name
default:
return "<unknown>"
}
}
// Position returns the current position of the frame. This is effectively the
// program counter/link register. May return `Position{}`.
func (f *DebugFrame) Position() token.Position {
d := f.frames[0].debug
if d == nil || d.node == nil {
return token.Position{}
}
return f.event.debugger.interp.fset.Position(d.node.pos)
}
// Program returns the program associated with the current position of the
// frame. May return nil.
func (f *DebugFrame) Program() *Program {
d := f.frames[0].debug
if d == nil || d.node == nil {
return nil
}
return d.node.debug.program
}
// Scopes returns the variable scopes of the frame.
func (f *DebugFrame) Scopes() []*DebugFrameScope {
s := make([]*DebugFrameScope, len(f.frames))
for i, f := range f.frames {
s[i] = &DebugFrameScope{f}
}
return s
}
// IsClosure returns true if this is the capture scope of a closure.
func (f *DebugFrameScope) IsClosure() bool {
return f.frame.debug != nil && f.frame.debug.kind == frameClosure
}
// Variables returns the names and values of the variables of the scope.
func (f *DebugFrameScope) Variables() []*DebugVariable {
d := f.frame.debug
if d == nil || d.scope == nil {
return nil
}
index := map[int]string{}
scanScope(d.scope, index)
m := make([]*DebugVariable, 0, len(f.frame.data))
for i, v := range f.frame.data {
if typ := v.Type(); typ.AssignableTo(rNodeType) || typ.Kind() == reflect.Ptr && typ.Elem().AssignableTo(rNodeType) {
continue
}
name, ok := index[i]
if !ok {
continue
}
m = append(m, &DebugVariable{name, v})
}
return m
}
func scanScope(sc *scope, index map[int]string) {
for name, sym := range sc.sym {
if _, ok := index[sym.index]; ok {
continue
}
index[sym.index] = name
}
for _, ch := range sc.child {
if ch.def != sc.def {
continue
}
scanScope(ch, index)
}
}

View File

@@ -3,7 +3,6 @@ package interp
import (
"fmt"
"io"
"io/ioutil"
"log"
"os/exec"
"path/filepath"
@@ -69,7 +68,7 @@ func (nopCloser) Close() error { return nil }
// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format.
func dotWriter(dotCmd string) io.WriteCloser {
if dotCmd == "" {
return nopCloser{ioutil.Discard}
return nopCloser{io.Discard}
}
fields := strings.Fields(dotCmd)
cmd := exec.Command(fields[0], fields[1:]...)

View File

@@ -3,7 +3,6 @@ package interp
import (
"path"
"path/filepath"
"reflect"
)
// gta performs a global types analysis on the AST, registering types,
@@ -11,8 +10,8 @@ import (
// All function bodies are skipped. GTA is necessary to handle out of
// order declarations and multiple source files packages.
// rpath is the relative path to the directory containing the source for the package.
func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, error) {
sc := interp.initScopePkg(importPath)
func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([]*node, error) {
sc := interp.initScopePkg(importPath, pkgName)
var err error
var revisit []*node
@@ -26,7 +25,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
case constDecl:
// Early parse of constDecl subtree, to compute all constant
// values which may be used in further declarations.
if _, err = interp.cfg(n, importPath); err != nil {
if _, err = interp.cfg(n, sc, importPath, pkgName); err != nil {
// No error processing here, to allow recovery in subtree nodes.
// TODO(marc): check for a non recoverable error and return it for better diagnostic.
err = nil
@@ -38,10 +37,17 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
}
case defineStmt:
var atyp *itype
var (
atyp *itype
err2 error
)
if n.nleft+n.nright < len(n.child) {
// Type is declared explicitly in the assign expression.
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
if atyp, err2 = nodeType(interp, sc, n.child[n.nleft]); err2 != nil {
// The type does not exist yet, stash the error and come back
// when the type is known.
n.meta = err2
revisit = append(revisit, n)
return false
}
}
@@ -53,9 +59,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
val := reflect.ValueOf(sc.iota)
val := src.rval
if n.anc.kind == constDecl {
if _, err2 := interp.cfg(n, importPath); err2 != nil {
if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil {
// Constant value can not be computed yet.
// Come back when child dependencies are known.
revisit = append(revisit, n)
@@ -64,7 +70,11 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
}
typ := atyp
if typ == nil {
if typ, err = nodeType(interp, sc, src); err != nil {
if typ, err2 = nodeType(interp, sc, src); err2 != nil || typ == nil {
// The type does is not known yet, stash the error and come back
// when the type is known.
n.meta = err2
revisit = append(revisit, n)
return false
}
val = src.rval
@@ -79,7 +89,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
return false
}
if typ.isBinMethod {
typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true, scope: sc}
typ = valueTOf(typ.methodCallType(), isBinMethod(), withScope(sc))
}
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n}
if n.anc.kind == constDecl {
@@ -139,27 +149,28 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
n.ident = ident
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typeName := rtn.ident
if typeName == "" {
// The receiver is a pointer, retrieve typeName from indirection
typeName = rtn.child[0].ident
elementType := sc.getType(typeName)
if elementType == nil {
// Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: importPath, incomplete: true, node: rtn.child[0], scope: sc}}
elementType = sc.sym[typeName].typ
}
rcvrtype = &itype{cat: ptrT, val: elementType, incomplete: elementType.incomplete, node: rtn, scope: sc}
elementType.method = append(elementType.method, n)
} else {
rcvrtype = sc.getType(typeName)
if rcvrtype == nil {
// Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: importPath, incomplete: true, node: rtn, scope: sc}}
rcvrtype = sc.sym[typeName].typ
}
typName, typPtr := rtn.ident, false
if typName == "" {
typName, typPtr = rtn.child[0].ident, true
}
rcvrtype.method = append(rcvrtype.method, n)
sym, _, found := sc.lookup(typName)
if !found {
n.meta = n.cfgErrorf("undefined: %s", typName)
revisit = append(revisit, n)
return false
}
if sym.kind != typeSym || (sym.node != nil && sym.node.kind == typeSpecAssign) {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id())
return false
}
rcvrtype = sym.typ
if typPtr {
elementType := sym.typ
rcvrtype = ptrOf(elementType, withNode(rtn), withScope(sc))
rcvrtype.incomplete = elementType.incomplete
elementType.addMethod(n)
}
rcvrtype.addMethod(n)
n.child[0].child[0].lastChild().typ = rcvrtype
case ident == "init":
// init functions do not get declared as per the Go spec.
@@ -200,7 +211,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
if isBinType(v) {
typ = typ.Elem()
}
sc.sym[n] = &symbol{kind: binSym, typ: &itype{cat: valueT, rtype: typ, scope: sc}, rval: v}
sc.sym[n] = &symbol{kind: binSym, typ: valueTOf(typ, withScope(sc)), rval: v}
}
default: // import symbols in package namespace
if name == "" {
@@ -254,7 +265,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
err = n.cfgErrorf("import %q error: %v", ipath, err)
}
case typeSpec:
case typeSpec, typeSpecAssign:
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
@@ -265,13 +276,16 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
switch n.child[1].kind {
case identExpr, selectorExpr:
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: importPath, field: typ.field, incomplete: typ.incomplete, scope: sc, node: n.child[0]}
n.typ = namedOf(typ, pkgName, typeName, withNode(n.child[0]), withScope(sc))
n.typ.incomplete = typ.incomplete
n.typ.field = typ.field
copy(n.typ.method, typ.method)
default:
n.typ = typ
n.typ.name = typeName
n.typ.path = importPath
n.typ.path = pkgName
}
n.typ.str = n.typ.path + "." + n.typ.name
asImportName := filepath.Join(typeName, baseName)
if _, exists := sc.sym[asImportName]; exists {
@@ -281,15 +295,21 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
}
sym, exists := sc.sym[typeName]
if !exists {
sc.sym[typeName] = &symbol{kind: typeSym}
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
} else {
if sym.typ != nil && (len(sym.typ.method) > 0) {
if n.kind == typeSpecAssign {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(typ).id())
return false
}
// Type has already been seen as a receiver in a method function
n.typ.method = append(n.typ.method, sym.typ.method...)
for _, m := range sym.typ.method {
n.typ.addMethod(m)
}
} else {
// TODO(mpl): figure out how to detect redeclarations without breaking type aliases.
// Allow redeclarations for now.
sc.sym[typeName] = &symbol{kind: typeSym}
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
}
}
sc.sym[typeName].typ = n.typ
@@ -307,12 +327,23 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
return revisit, err
}
func baseType(t *itype) *itype {
for {
switch t.cat {
case ptrT, aliasT:
t = t.val
default:
return t
}
}
}
// gtaRetry (re)applies gta until all global constants and types are defined.
func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error {
func (interp *Interpreter) gtaRetry(nodes []*node, importPath, pkgName string) error {
revisit := []*node{}
for {
for _, n := range nodes {
list, err := interp.gta(n, importPath, importPath)
list, err := interp.gta(n, importPath, importPath, pkgName)
if err != nil {
return err
}
@@ -329,10 +360,15 @@ func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error {
if len(revisit) > 0 {
n := revisit[0]
if n.kind == typeSpec {
switch n.kind {
case typeSpec, typeSpecAssign:
if err := definedType(n.typ); err != nil {
return err
}
case defineStmt, funcDecl:
if err, ok := n.meta.(error); ok {
return err
}
}
return n.cfgErrorf("constant definition loop")
}

View File

@@ -7,18 +7,18 @@ import (
"flag"
"fmt"
"go/build"
"go/constant"
"go/scanner"
"go/token"
"io"
"io/ioutil"
"io/fs"
"log"
"math/bits"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
@@ -27,6 +27,7 @@ import (
// Interpreter node structure for AST and CFG.
type node struct {
debug *nodeDebugData // debug info
child []*node // child subtrees (AST)
anc *node // ancestor (AST)
start *node // entry point in subtree (CFG)
@@ -45,12 +46,53 @@ type node struct {
typ *itype // type of value in frame, or nil
recv *receiver // method receiver node for call, or nil
types []reflect.Type // frame types, used by function literals only
scope *scope // frame scope
action action // action
exec bltn // generated function to execute
gen bltnGenerator // generator function to produce above bltn
val interface{} // static generic value (CFG execution)
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
ident string // set if node is a var or func
meta interface{} // meta stores meta information between gta runs, like errors
}
func (n *node) shouldBreak() bool {
if n == nil || n.debug == nil {
return false
}
if n.debug.breakOnLine || n.debug.breakOnCall {
return true
}
return false
}
func (n *node) setProgram(p *Program) {
if n.debug == nil {
n.debug = new(nodeDebugData)
}
n.debug.program = p
}
func (n *node) setBreakOnCall(v bool) {
if n.debug == nil {
if !v {
return
}
n.debug = new(nodeDebugData)
}
n.debug.breakOnCall = v
}
func (n *node) setBreakOnLine(v bool) {
if n.debug == nil {
if !v {
return
}
n.debug = new(nodeDebugData)
}
n.debug.breakOnLine = v
}
// receiver stores method receiver object access path.
@@ -67,6 +109,8 @@ type frame struct {
// Located at start of struct to ensure proper aligment.
id uint64
debug *frameDebugData
root *frame // global space
anc *frame // ancestor frame (caller space)
data []reflect.Value // values
@@ -104,6 +148,7 @@ func (f *frame) clone(fork bool) *frame {
recovered: f.recovered,
id: f.runid(),
done: f.done,
debug: f.debug,
}
if fork {
nf.data = make([]reflect.Value, len(f.data))
@@ -124,18 +169,22 @@ type imports map[string]map[string]*symbol
// opt stores interpreter options.
type opt struct {
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
// dotCmd is the command to process the dot graph produced when astDot and/or
// cfgDot is enabled. It defaults to 'dot -Tdot -o <filename>.dot'.
dotCmd string
noRun bool // compile, but do not run
fastChan bool // disable cancellable chan operations
context build.Context // build context: GOPATH, build constraints
specialStdio bool // Allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
stdin io.Reader // standard input
stdout io.Writer // standard output
stderr io.Writer // standard error
context build.Context // build context: GOPATH, build constraints
stdin io.Reader // standard input
stdout io.Writer // standard output
stderr io.Writer // standard error
args []string // cmdline args
env map[string]string // environment of interpreter, entries in form of "key=value"
filesystem fs.FS // filesystem containing sources
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
noRun bool // compile, but do not run
fastChan bool // disable cancellable chan operations
specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
unrestricted bool // allow use of non sandboxed symbols
}
// Interpreter contains global resources and state.
@@ -166,8 +215,11 @@ type Interpreter struct {
srcPkg imports // source packages used in interpreter, indexed by path
pkgNames map[string]string // package names, indexed by import path
done chan struct{} // for cancellation of channel operations
roots []*node
hooks *hooks // symbol hooks
debugger *Debugger
}
const (
@@ -253,12 +305,27 @@ type Options struct {
// They default to os.Stdin, os.Stdout and os.Stderr respectively.
Stdin io.Reader
Stdout, Stderr io.Writer
// Cmdline args, defaults to os.Args.
Args []string
// Environment of interpreter. Entries are in the form "key=values".
Env []string
// SourcecodeFilesystem is where the _sourcecode_ is loaded from and does
// NOT affect the filesystem of scripts when they run.
// It can be any fs.FS compliant filesystem (e.g. embed.FS, or fstest.MapFS for testing)
// See example/fs/fs_test.go for an example.
SourcecodeFilesystem fs.FS
// Unrestricted allows to run non sandboxed stdlib symbols such as os/exec and environment
Unrestricted bool
}
// New returns a new interpreter.
func New(options Options) *Interpreter {
i := Interpreter{
opt: opt{context: build.Default},
opt: opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}},
frame: newFrame(nil, 0, 0),
fset: token.NewFileSet(),
universe: initUniverse(),
@@ -282,6 +349,28 @@ func New(options Options) *Interpreter {
i.opt.stderr = os.Stderr
}
if i.opt.args = options.Args; i.opt.args == nil {
i.opt.args = os.Args
}
// unrestricted allows to use non sandboxed stdlib symbols and env.
if options.Unrestricted {
i.opt.unrestricted = true
} else {
for _, e := range options.Env {
a := strings.SplitN(e, "=", 2)
if len(a) == 2 {
i.opt.env[a[0]] = a[1]
} else {
i.opt.env[a[0]] = ""
}
}
}
if options.SourcecodeFilesystem != nil {
i.opt.filesystem = options.SourcecodeFilesystem
}
i.opt.context.GOPATH = options.GoPath
if len(options.BuildTags) > 0 {
i.opt.context.BuildTags = options.BuildTags
@@ -332,27 +421,27 @@ const (
func initUniverse() *scope {
sc := &scope{global: true, sym: map[string]*symbol{
// predefined Go types
"bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool"}},
"byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8"}},
"complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64"}},
"complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128"}},
"error": {kind: typeSym, typ: &itype{cat: errorT, name: "error"}},
"float32": {kind: typeSym, typ: &itype{cat: float32T, name: "float32"}},
"float64": {kind: typeSym, typ: &itype{cat: float64T, name: "float64"}},
"int": {kind: typeSym, typ: &itype{cat: intT, name: "int"}},
"int8": {kind: typeSym, typ: &itype{cat: int8T, name: "int8"}},
"int16": {kind: typeSym, typ: &itype{cat: int16T, name: "int16"}},
"int32": {kind: typeSym, typ: &itype{cat: int32T, name: "int32"}},
"int64": {kind: typeSym, typ: &itype{cat: int64T, name: "int64"}},
"interface{}": {kind: typeSym, typ: &itype{cat: interfaceT}},
"rune": {kind: typeSym, typ: &itype{cat: int32T, name: "int32"}},
"string": {kind: typeSym, typ: &itype{cat: stringT, name: "string"}},
"uint": {kind: typeSym, typ: &itype{cat: uintT, name: "uint"}},
"uint8": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8"}},
"uint16": {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16"}},
"uint32": {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32"}},
"uint64": {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64"}},
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr"}},
"bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}},
"byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
"complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}},
"complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}},
"error": {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}},
"float32": {kind: typeSym, typ: &itype{cat: float32T, name: "float32", str: "float32"}},
"float64": {kind: typeSym, typ: &itype{cat: float64T, name: "float64", str: "float64"}},
"int": {kind: typeSym, typ: &itype{cat: intT, name: "int", str: "int"}},
"int8": {kind: typeSym, typ: &itype{cat: int8T, name: "int8", str: "int8"}},
"int16": {kind: typeSym, typ: &itype{cat: int16T, name: "int16", str: "int16"}},
"int32": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}},
"int64": {kind: typeSym, typ: &itype{cat: int64T, name: "int64", str: "int64"}},
"interface{}": {kind: typeSym, typ: &itype{cat: interfaceT, str: "interface{}"}},
"rune": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}},
"string": {kind: typeSym, typ: &itype{cat: stringT, name: "string", str: "string"}},
"uint": {kind: typeSym, typ: &itype{cat: uintT, name: "uint", str: "uint"}},
"uint8": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
"uint16": {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16", str: "uint16"}},
"uint32": {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32", str: "uint32"}},
"uint64": {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64", str: "uint64"}},
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}},
// predefined Go constants
"false": {kind: constSym, typ: untypedBool(), rval: reflect.ValueOf(false)},
@@ -360,7 +449,7 @@ func initUniverse() *scope {
"iota": {kind: constSym, typ: untypedInt()},
// predefined Go zero value
"nil": {typ: &itype{cat: nilT, untyped: true}},
"nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}},
// predefined Go builtins
bltnAppend: {kind: bltnSym, builtin: _append},
@@ -407,18 +496,42 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
// by the interpreter, and a non nil error in case of failure.
// The main function of the main package is executed if present.
func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error) {
if !isFile(path) {
if !isFile(interp.opt.filesystem, path) {
_, err := interp.importSrc(mainID, path, NoTest)
return res, err
}
b, err := ioutil.ReadFile(path)
b, err := fs.ReadFile(interp.filesystem, path)
if err != nil {
return res, err
}
return interp.eval(string(b), path, false)
}
// EvalPathWithContext evaluates Go code located at path and returns the last
// result computed by the interpreter, and a non nil error in case of failure.
// The main function of the main package is executed if present.
func (interp *Interpreter) EvalPathWithContext(ctx context.Context, path string) (res reflect.Value, err error) {
interp.mutex.Lock()
interp.done = make(chan struct{})
interp.cancelChan = !interp.opt.fastChan
interp.mutex.Unlock()
done := make(chan struct{})
go func() {
defer close(done)
res, err = interp.EvalPath(path)
}()
select {
case <-ctx.Done():
interp.stop()
return reflect.Value{}, ctx.Err()
case <-done:
}
return res, err
}
// EvalTest evaluates Go code located at path, including test files with "_test.go" suffix.
// A non nil error is returned in case of failure.
// The main function, test functions and benchmark functions are internally compiled but not
@@ -484,129 +597,22 @@ func (interp *Interpreter) Symbols(importPath string) Exports {
return m
}
func isFile(path string) bool {
fi, err := os.Stat(path)
func isFile(filesystem fs.FS, path string) bool {
fi, err := fs.Stat(filesystem, path)
return err == nil && fi.Mode().IsRegular()
}
func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) {
if name != "" {
interp.name = name
}
if interp.name == "" {
interp.name = DefaultSourceName
}
defer func() {
r := recover()
if r != nil {
var pc [64]uintptr // 64 frames should be enough.
n := runtime.Callers(1, pc[:])
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
}
}()
// Parse source to AST.
pkgName, root, err := interp.ast(src, interp.name, inc)
if err != nil || root == nil {
return res, err
}
if interp.astDot {
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.name, "yaegi-ast-")
}
root.astDot(dotWriter(dotCmd), interp.name)
if interp.noRun {
return res, err
}
}
// Perform global types analysis.
if err = interp.gtaRetry([]*node{root}, pkgName); err != nil {
return res, err
}
// Annotate AST with CFG informations.
initNodes, err := interp.cfg(root, pkgName)
prog, err := interp.compileSrc(src, name, inc)
if err != nil {
if interp.cfgDot {
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
}
root.cfgDot(dotWriter(dotCmd))
}
return res, err
}
if root.kind != fileStmt {
// REPL may skip package statement.
setExec(root.start)
}
interp.mutex.Lock()
gs := interp.scopes[pkgName]
if interp.universe.sym[pkgName] == nil {
// Make the package visible under a path identical to its name.
interp.srcPkg[pkgName] = gs.sym
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
interp.pkgNames[pkgName] = pkgName
}
interp.mutex.Unlock()
// Add main to list of functions to run, after all inits.
if m := gs.sym[mainID]; pkgName == mainID && m != nil {
initNodes = append(initNodes, m.node)
}
if interp.cfgDot {
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
}
root.cfgDot(dotWriter(dotCmd))
}
if interp.noRun {
return res, err
}
// Generate node exec closures.
if err = genRun(root); err != nil {
return res, err
}
// Init interpreter execution memory frame.
interp.frame.setrunid(interp.runid())
interp.frame.mutex.Lock()
interp.resizeFrame()
interp.frame.mutex.Unlock()
// Execute node closures.
interp.run(root, nil)
// Wire and execute global vars.
n, err := genGlobalVars([]*node{root}, interp.scopes[pkgName])
if err != nil {
return res, err
}
interp.run(n, nil)
for _, n := range initNodes {
interp.run(n, interp.frame)
}
v := genValue(root)
res = v(interp.frame)
// If result is an interpreter node, wrap it in a runtime callable function.
if res.IsValid() {
if n, ok := res.Interface().(*node); ok {
res = genFunctionWrapper(n)(interp.frame)
}
}
return res, err
return interp.Execute(prog)
}
// EvalWithContext evaluates Go code represented as a string. It returns
@@ -685,17 +691,17 @@ func (interp *Interpreter) Use(values Exports) error {
// Checks if input values correspond to stdlib packages by looking for one
// well known stdlib package path.
if _, ok := values["fmt/fmt"]; ok {
fixStdio(interp)
fixStdlib(interp)
}
return nil
}
// fixStdio redefines interpreter stdlib symbols to use the standard input,
// fixStdlib redefines interpreter stdlib symbols to use the standard input,
// output and errror assigned to the interpreter. The changes are limited to
// the interpreter only.
// Note that it is possible to escape the virtualized stdio by
// read/write directly to file descriptors 0, 1, 2.
func fixStdio(interp *Interpreter) {
func fixStdlib(interp *Interpreter) {
p := interp.binPkg["fmt"]
if p == nil {
return
@@ -740,6 +746,7 @@ func fixStdio(interp *Interpreter) {
}
if p = interp.binPkg["os"]; p != nil {
p["Args"] = reflect.ValueOf(&interp.args).Elem()
if interp.specialStdio {
// Inherit streams from interpreter even if they do not have a file descriptor.
p["Stdin"] = reflect.ValueOf(&stdin).Elem()
@@ -757,6 +764,27 @@ func fixStdio(interp *Interpreter) {
p["Stderr"] = reflect.ValueOf(&s).Elem()
}
}
if !interp.unrestricted {
// In restricted mode, scripts can only access to a passed virtualized env, and can not write the real one.
getenv := func(key string) string { return interp.env[key] }
p["Clearenv"] = reflect.ValueOf(func() { interp.env = map[string]string{} })
p["ExpandEnv"] = reflect.ValueOf(func(s string) string { return os.Expand(s, getenv) })
p["Getenv"] = reflect.ValueOf(getenv)
p["LookupEnv"] = reflect.ValueOf(func(key string) (s string, ok bool) { s, ok = interp.env[key]; return })
p["Setenv"] = reflect.ValueOf(func(key, value string) error { interp.env[key] = value; return nil })
p["Unsetenv"] = reflect.ValueOf(func(key string) error { delete(interp.env, key); return nil })
p["Environ"] = reflect.ValueOf(func() (a []string) {
for k, v := range interp.env {
a = append(a, k+"="+v)
}
return
})
}
}
if p = interp.binPkg["math/bits"]; p != nil {
// Do not trust extracted value maybe from another arch.
p["UintSize"] = reflect.ValueOf(constant.MakeInt64(bits.UintSize))
}
}

View File

@@ -2,7 +2,7 @@ package interp_test
import (
"go/build"
"io/ioutil"
"io"
"os"
"os/exec"
"path/filepath"
@@ -20,13 +20,13 @@ func TestInterpConsistencyBuild(t *testing.T) {
}
dir := filepath.Join("..", "_test", "tmp")
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.Mkdir(dir, 0700); err != nil {
if err := os.Mkdir(dir, 0o700); err != nil {
t.Fatal(err)
}
}
baseDir := filepath.Join("..", "_test")
files, err := ioutil.ReadDir(baseDir)
files, err := os.ReadDir(baseDir)
if err != nil {
t.Fatal(err)
}
@@ -51,11 +51,14 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "init1.go" || // expect error
file.Name() == "io0.go" || // use random number
file.Name() == "issue-1093.go" || // expect error
file.Name() == "issue-1276.go" || // expect error
file.Name() == "issue-1330.go" || // expect error
file.Name() == "op1.go" || // expect error
file.Name() == "op7.go" || // expect error
file.Name() == "op9.go" || // expect error
file.Name() == "bltn0.go" || // expect error
file.Name() == "method16.go" || // private struct field
file.Name() == "method39.go" || // expect error
file.Name() == "switch8.go" || // expect error
file.Name() == "switch9.go" || // expect error
file.Name() == "switch13.go" || // expect error
@@ -98,7 +101,16 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "server1.go" || // syntax parsing
file.Name() == "server0.go" || // syntax parsing
file.Name() == "server.go" || // syntax parsing
file.Name() == "range9.go" { // expect error
file.Name() == "range9.go" || // expect error
file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
file.Name() == "type27.go" || // expect error
file.Name() == "type28.go" || // expect error
file.Name() == "type29.go" || // expect error
file.Name() == "type30.go" || // expect error
file.Name() == "type31.go" || // expect error
file.Name() == "type32.go" || // expect error
file.Name() == "type33.go" { // expect error
continue
}
@@ -134,7 +146,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
if err = w.Close(); err != nil {
t.Fatal(err)
}
outInterp, err := ioutil.ReadAll(r)
outInterp, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
@@ -218,12 +230,12 @@ func TestInterpErrorConsistency(t *testing.T) {
},
{
fileName: "issue-1093.go",
expectedInterp: "9:6: cannot use type string as type int in assignment",
expectedInterp: "9:6: cannot use type untyped string as type int in assignment",
expectedExec: `9:4: cannot use "a" + b() (type string) as type int in assignment`,
},
{
fileName: "op1.go",
expectedInterp: "5:2: invalid operation: mismatched types int and float64",
expectedInterp: "5:2: invalid operation: mismatched types int and untyped float",
expectedExec: "5:4: constant 1.3 truncated to integer",
},
{

View File

@@ -6,7 +6,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@@ -43,8 +42,8 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "add_FI", src: "2.3 + 3", res: "5.3"},
{desc: "add_IF", src: "2 + 3.3", res: "5.3"},
{desc: "add_SS", src: `"foo" + "bar"`, res: "foobar"},
{desc: "add_SI", src: `"foo" + 1`, err: "1:28: invalid operation: mismatched types string and int"},
{desc: "sub_SS", src: `"foo" - "bar"`, err: "1:28: invalid operation: operator - not defined on string"},
{desc: "add_SI", src: `"foo" + 1`, err: "1:28: invalid operation: mismatched types untyped string and untyped int"},
{desc: "sub_SS", src: `"foo" - "bar"`, err: "1:28: invalid operation: operator - not defined on untyped string"},
{desc: "sub_II", src: "7 - 3", res: "4"},
{desc: "sub_FI", src: "7.2 - 3", res: "4.2"},
{desc: "sub_IF", src: "7 - 3.2", res: "3.8"},
@@ -52,23 +51,23 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "mul_FI", src: "2.2 * 3", res: "6.6"},
{desc: "mul_IF", src: "3 * 2.2", res: "6.6"},
{desc: "quo_Z", src: "3 / 0", err: "1:28: invalid operation: division by zero"},
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: invalid operation: operator % not defined on float64"},
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: invalid operation: operator % not defined on untyped float"},
{desc: "rem_Z", src: "8 % 0", err: "1:28: invalid operation: division by zero"},
{desc: "shl_II", src: "1 << 8", res: "256"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: invalid operation: shift count type int, must be integer"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: invalid operation: shift count type untyped int, must be integer"},
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: invalid operation: shift count type float64, must be integer"},
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: invalid operation: shift count type untyped float, must be integer"},
{desc: "shl_IF2", src: "1.0 << 1", res: "2"},
{desc: "shr_II", src: "1 >> 8", res: "0"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: invalid operation: shift count type int, must be integer"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: invalid operation: shift count type untyped int, must be integer"},
{desc: "shr_IF", src: "1 >> 1.0", res: "0"},
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: invalid operation: shift count type float64, must be integer"},
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: invalid operation: shift count type untyped float, must be integer"},
{desc: "neg_I", src: "-2", res: "-2"},
{desc: "pos_I", src: "+2", res: "2"},
{desc: "bitnot_I", src: "^2", res: "-3"},
{desc: "bitnot_F", src: "^0.2", err: "1:28: invalid operation: operator ^ not defined on float64"},
{desc: "bitnot_F", src: "^0.2", err: "1:28: invalid operation: operator ^ not defined on untyped float"},
{desc: "not_B", src: "!false", res: "true"},
{desc: "not_I", src: "!0", err: "1:28: invalid operation: operator ! not defined on int"},
{desc: "not_I", src: "!0", err: "1:28: invalid operation: operator ! not defined on untyped int"},
})
}
@@ -76,7 +75,7 @@ func TestEvalShift(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: "a, b, m := uint32(1), uint32(2), uint32(0); m = a + (1 << b)", res: "5"},
{src: "c := uint(1); d := uint(+(-(1 << c)))", res: "18446744073709551614"},
{src: "c := uint(1); d := uint64(+(-(1 << c)))", res: "18446744073709551614"},
{src: "e, f := uint32(0), uint32(0); f = 1 << -(e * 2)", res: "1"},
{src: "p := uint(0xdead); byte((1 << (p & 7)) - 1)", res: "31"},
{pre: func() { eval(t, i, "const k uint = 1 << 17") }, src: "int(k)", res: "131072"},
@@ -119,10 +118,10 @@ func TestEvalAssign(t *testing.T) {
runTests(t, i, []testCase{
{src: `a := "Hello"; a += " world"`, res: "Hello world"},
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and int"},
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and untyped int"},
{src: `c := "Hello"; c -= " world"`, err: "1:42: invalid operation: operator -= not defined on string"},
{src: "e := 64.4; e %= 64", err: "1:39: invalid operation: operator %= not defined on float64"},
{src: "f := int64(3.2)", err: "1:39: cannot convert expression of type float64 to type int64"},
{src: "f := int64(3.2)", err: "1:39: cannot convert expression of type untyped float to type int64"},
{src: "g := 1; g <<= 8", res: "256"},
{src: "h := 1; h >>= 8", res: "0"},
{src: "i := 1; j := &i; (*j) = 2", res: "2"},
@@ -139,7 +138,9 @@ func TestEvalBuiltin(t *testing.T) {
{src: `c := []int{1}; d := []int{2, 3}; c = append(c, d...); c`, res: "[1 2 3]"},
{src: `string(append([]byte("hello "), "world"...))`, res: "hello world"},
{src: `e := "world"; string(append([]byte("hello "), e...))`, res: "hello world"},
{src: `b := []int{1}; b = append(1, 2, 3); b`, err: "1:54: first argument to append must be slice; have int"},
{src: `b := []int{1}; b = append(1, 2, 3); b`, err: "1:54: first argument to append must be slice; have untyped int"},
{src: `a1 := []int{0,1,2}; append(a1)`, res: "[0 1 2]"},
{src: `append(nil)`, err: "first argument to append must be slice; have nil"},
{src: `g := len(a)`, res: "1"},
{src: `g := cap(a)`, res: "1"},
{src: `g := len("test")`, res: "4"},
@@ -161,10 +162,10 @@ func TestEvalBuiltin(t *testing.T) {
{src: `k := []int{3, 4}; copy(k, []int{1,2}); k`, res: "[1 2]"},
{src: `f := []byte("Hello"); copy(f, "world"); string(f)`, res: "world"},
{src: `copy(g, g)`, err: "1:28: copy expects slice arguments"},
{src: `copy(a, "world")`, err: "1:28: arguments to copy have different element types []int and string"},
{src: `copy(a, "world")`, err: "1:28: arguments to copy have different element types []int and untyped string"},
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, "a"); l`, res: "map[b:2]"},
{src: `delete(a, 1)`, err: "1:35: first argument to delete must be map; have []int"},
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, 1)`, err: "1:75: cannot use int as type string in delete"},
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, 1)`, err: "1:75: cannot use untyped int as type string in delete"},
{src: `a := []int{1,2}; println(a...)`, err: "invalid use of ... with builtin println"},
{src: `m := complex(3, 2); real(m)`, res: "3"},
{src: `m := complex(3, 2); imag(m)`, res: "2"},
@@ -416,6 +417,8 @@ func TestEvalComparison(t *testing.T) {
{src: `2 > 1`, res: "true"},
{src: `1.2 > 1.1`, res: "true"},
{src: `"hhh" > "ggg"`, res: "true"},
{src: `a, b, c := 1, 1, false; if a == b { c = true }; c`, res: "true"},
{src: `a, b, c := 1, 2, false; if a != b { c = true }; c`, res: "true"},
{
desc: "mismatched types",
src: `
@@ -438,11 +441,12 @@ func TestEvalCompositeArray(t *testing.T) {
{src: "a := []int{1, 2, 7: 20, 30}", res: "[1 2 0 0 0 0 0 20 30]"},
{src: `a := []int{1, 1.2}`, err: "1:42: 6/5 truncated to int"},
{src: `a := []int{0:1, 0:1}`, err: "1:46: duplicate index 0 in array or slice literal"},
{src: `a := []int{1.1:1, 1.2:"test"}`, err: "1:39: index float64 must be integer constant"},
{src: `a := []int{1.1:1, 1.2:"test"}`, err: "1:39: index untyped float must be integer constant"},
{src: `a := [2]int{1, 1.2}`, err: "1:43: 6/5 truncated to int"},
{src: `a := [1]int{1, 2}`, err: "1:43: index 1 is out of bounds (>= 1)"},
{src: `b := [l]int{1, 2}`, res: "[1 2 0 0 0 0 0 0 0 0]"},
{src: `i := 10; a := [i]int{1, 2}`, err: "1:43: non-constant array bound \"i\""},
{src: `c := [...]float64{1, 3: 3.4, 5}`, res: "[1 0 0 3.4 5]"},
})
}
@@ -505,7 +509,7 @@ func TestEvalConversion(t *testing.T) {
{src: `a := uint64(1)`, res: "1"},
{src: `i := 1.1; a := uint64(i)`, res: "1"},
{src: `b := string(49)`, res: "1"},
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type float64 to type uint64"},
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type untyped float to type uint64"},
})
}
@@ -634,7 +638,7 @@ func TestEvalCall(t *testing.T) {
a := test([]int{1}...)`, err: "2:10: invalid use of ..., corresponding parameter is non-variadic"},
{src: ` test := func(a ...int) int { return 1 }
blah := func() (int, int) { return 1, 1 }
a := test(blah()...)`, err: "3:15: cannot use ... with 2-valued func()(int,int)"},
a := test(blah()...)`, err: "3:15: cannot use ... with 2-valued func() (int,int)"},
{src: ` test := func(a, b int) int { return a }
blah := func() (int, int) { return 1, 1 }
a := test(blah())`, res: "1"},
@@ -643,10 +647,10 @@ func TestEvalCall(t *testing.T) {
a := test(blah(), blah())`, res: "1"},
{src: ` test := func(a, b, c, d int) int { return a }
blah := func() (int, int) { return 1, 1 }
a := test(blah(), blah())`, err: "3:15: cannot use func()(int,int) as type int"},
a := test(blah(), blah())`, err: "3:15: cannot use func() (int,int) as type int"},
{src: ` test := func(a, b int) int { return a }
blah := func() (int, float64) { return 1, 1.1 }
a := test(blah())`, err: "3:15: cannot use func()(int,float64) as type (int,int)"},
a := test(blah())`, err: "3:15: cannot use func() (int,float64) as type (int,int)"},
})
}
@@ -880,7 +884,7 @@ func TestMultiEval(t *testing.T) {
if err = w.Close(); err != nil {
t.Fatal(err)
}
outInterp, err := ioutil.ReadAll(r)
outInterp, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
@@ -911,7 +915,7 @@ func TestMultiEvalNoName(t *testing.T) {
t.Fatal(err)
}
for k, v := range names {
data, err := ioutil.ReadFile(filepath.Join(f.Name(), v))
data, err := os.ReadFile(filepath.Join(f.Name(), v))
if err != nil {
t.Fatal(err)
}
@@ -1710,3 +1714,38 @@ func TestIssue1151(t *testing.T) {
{src: "x := pkg.Array{1}", res: "[1]"},
})
}
func TestPassArgs(t *testing.T) {
i := interp.New(interp.Options{Args: []string{"arg0", "arg1"}})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
i.ImportUsed()
runTests(t, i, []testCase{
{src: "os.Args", res: "[arg0 arg1]"},
})
}
func TestRestrictedEnv(t *testing.T) {
i := interp.New(interp.Options{Env: []string{"foo=bar"}})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
i.ImportUsed()
runTests(t, i, []testCase{
{src: `os.Getenv("foo")`, res: "bar"},
{src: `s, ok := os.LookupEnv("foo"); s`, res: "bar"},
{src: `s, ok := os.LookupEnv("foo"); ok`, res: "true"},
{src: `s, ok := os.LookupEnv("PATH"); s`, res: ""},
{src: `s, ok := os.LookupEnv("PATH"); ok`, res: "false"},
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
{src: `os.ExpandEnv("foo is ${foo}")`, res: "foo is baz"},
{src: `os.Unsetenv("foo"); os.Environ()`, res: "[]"},
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
{src: `os.Clearenv(); os.Environ()`, res: "[]"},
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
})
if s, ok := os.LookupEnv("foo"); ok {
t.Fatal("expected \"\", got " + s)
}
}

View File

@@ -5,7 +5,6 @@ import (
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -26,7 +25,7 @@ func TestFile(t *testing.T) {
_ = os.Setenv("YAEGI_SPECIAL_STDIO", "1")
baseDir := filepath.Join("..", "_test")
files, err := ioutil.ReadDir(baseDir)
files, err := os.ReadDir(baseDir)
if err != nil {
t.Fatal(err)
}

View File

@@ -2644,7 +2644,7 @@ func equal(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
if i0 == i1 {
dest(f).SetBool(true)
return tnext
}
@@ -2666,7 +2666,7 @@ func equal(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
if i0 == i1 {
dest(f).SetBool(true)
return tnext
}
@@ -2689,7 +2689,7 @@ func equal(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
if i0 == i1 {
dest(f).SetBool(true)
return tnext
}

192
interp/program.go Normal file
View File

@@ -0,0 +1,192 @@
package interp
import (
"context"
"go/ast"
"os"
"reflect"
"runtime"
"runtime/debug"
)
// A Program is Go code that has been parsed and compiled.
type Program struct {
pkgName string
root *node
init []*node
}
// Compile parses and compiles a Go code represented as a string.
func (interp *Interpreter) Compile(src string) (*Program, error) {
return interp.compileSrc(src, "", true)
}
// CompilePath parses and compiles a Go code located at the given path.
func (interp *Interpreter) CompilePath(path string) (*Program, error) {
if !isFile(interp.filesystem, path) {
_, err := interp.importSrc(mainID, path, NoTest)
return nil, err
}
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return interp.compileSrc(string(b), path, false)
}
func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, error) {
if name != "" {
interp.name = name
}
if interp.name == "" {
interp.name = DefaultSourceName
}
// Parse source to AST.
n, err := interp.parse(src, interp.name, inc)
if err != nil {
return nil, err
}
return interp.CompileAST(n)
}
// CompileAST builds a Program for the given Go code AST. Files and block
// statements can be compiled, as can most expressions. Var declaration nodes
// cannot be compiled.
func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) {
// Convert AST.
pkgName, root, err := interp.ast(n)
if err != nil || root == nil {
return nil, err
}
if interp.astDot {
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.name, "yaegi-ast-")
}
root.astDot(dotWriter(dotCmd), interp.name)
if interp.noRun {
return nil, err
}
}
// Perform global types analysis.
if err = interp.gtaRetry([]*node{root}, pkgName, pkgName); err != nil {
return nil, err
}
// Annotate AST with CFG informations.
initNodes, err := interp.cfg(root, nil, pkgName, pkgName)
if err != nil {
if interp.cfgDot {
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
}
root.cfgDot(dotWriter(dotCmd))
}
return nil, err
}
if root.kind != fileStmt {
// REPL may skip package statement.
setExec(root.start)
}
interp.mutex.Lock()
gs := interp.scopes[pkgName]
if interp.universe.sym[pkgName] == nil {
// Make the package visible under a path identical to its name.
interp.srcPkg[pkgName] = gs.sym
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
interp.pkgNames[pkgName] = pkgName
}
interp.mutex.Unlock()
// Add main to list of functions to run, after all inits.
if m := gs.sym[mainID]; pkgName == mainID && m != nil {
initNodes = append(initNodes, m.node)
}
if interp.cfgDot {
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-")
}
root.cfgDot(dotWriter(dotCmd))
}
return &Program{pkgName, root, initNodes}, nil
}
// Execute executes compiled Go code.
func (interp *Interpreter) Execute(p *Program) (res reflect.Value, err error) {
defer func() {
r := recover()
if r != nil {
var pc [64]uintptr // 64 frames should be enough.
n := runtime.Callers(1, pc[:])
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
}
}()
// Generate node exec closures.
if err = genRun(p.root); err != nil {
return res, err
}
// Init interpreter execution memory frame.
interp.frame.setrunid(interp.runid())
interp.frame.mutex.Lock()
interp.resizeFrame()
interp.frame.mutex.Unlock()
// Execute node closures.
interp.run(p.root, nil)
// Wire and execute global vars.
n, err := genGlobalVars([]*node{p.root}, interp.scopes[p.pkgName])
if err != nil {
return res, err
}
interp.run(n, nil)
for _, n := range p.init {
interp.run(n, interp.frame)
}
v := genValue(p.root)
res = v(interp.frame)
// If result is an interpreter node, wrap it in a runtime callable function.
if res.IsValid() {
if n, ok := res.Interface().(*node); ok {
res = genFunctionWrapper(n)(interp.frame)
}
}
return res, err
}
// ExecuteWithContext executes compiled Go code.
func (interp *Interpreter) ExecuteWithContext(ctx context.Context, p *Program) (res reflect.Value, err error) {
interp.mutex.Lock()
interp.done = make(chan struct{})
interp.cancelChan = !interp.opt.fastChan
interp.mutex.Unlock()
done := make(chan struct{})
go func() {
defer close(done)
res, err = interp.Execute(p)
}()
select {
case <-ctx.Done():
interp.stop()
return reflect.Value{}, ctx.Err()
case <-done:
}
return res, err
}

21
interp/realfs.go Normal file
View File

@@ -0,0 +1,21 @@
package interp
import (
"io/fs"
"os"
)
// realFS complies with the fs.FS interface (go 1.16 onwards)
// We use this rather than os.DirFS as DirFS has no concept of
// what the current working directory is, whereas this simple
// passthru to os.Open knows about working dir automagically.
type realFS struct{}
// Open complies with the fs.FS interface.
func (dir realFS) Open(name string) (fs.File, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
return f, nil
}

View File

@@ -10,7 +10,6 @@ import (
"regexp"
"strings"
"sync"
"unsafe"
)
// bltn type defines functions which run at CFG execution.
@@ -118,7 +117,17 @@ func (interp *Interpreter) run(n *node, cf *frame) {
for i, t := range n.types {
f.data[i] = reflect.New(t).Elem()
}
runCfg(n.start, f)
runCfg(n.start, f, n, nil)
}
func isExecNode(n *node, exec bltn) bool {
if n == nil || n.exec == nil || exec == nil {
return false
}
a1 := reflect.ValueOf(n.exec).Pointer()
a2 := reflect.ValueOf(exec).Pointer()
return a1 == a2
}
// originalExecNode looks in the tree of nodes for the node which has exec,
@@ -166,7 +175,7 @@ func originalExecNode(n *node, exec bltn) *node {
// Functions set to run during execution of CFG.
// runCfg executes a node AST by walking its CFG and running node builtin at each step.
func runCfg(n *node, f *frame) {
func runCfg(n *node, f *frame, funcNode, callNode *node) {
var exec bltn
defer func() {
f.mutex.Lock()
@@ -186,8 +195,44 @@ func runCfg(n *node, f *frame) {
f.mutex.Unlock()
}()
for exec = n.exec; exec != nil && f.runid() == n.interp.runid(); {
dbg := n.interp.debugger
if dbg == nil {
for exec := n.exec; exec != nil && f.runid() == n.interp.runid(); {
exec = exec(f)
}
return
}
if n.exec == nil {
return
}
dbg.enterCall(funcNode, callNode, f)
defer dbg.exitCall(funcNode, callNode, f)
for m, exec := n, n.exec; f.runid() == n.interp.runid(); {
if dbg.exec(m, f) {
break
}
exec = exec(f)
if exec == nil {
break
}
if m == nil {
m = originalExecNode(n, exec)
continue
}
switch {
case isExecNode(m.tnext, exec):
m = m.tnext
case isExecNode(m.fnext, exec):
m = m.fnext
default:
m = originalExecNode(m, exec)
}
}
}
@@ -252,6 +297,9 @@ func typeAssert(n *node, withResult, withOk bool) {
}
return next
}
if c0.typ.cat == valueT {
valf = reflect.ValueOf(v)
}
if v.node.typ.id() == typID {
if withResult {
value0(f).Set(valf)
@@ -568,15 +616,31 @@ func convert(n *node) {
}
}
func isRecursiveType(t *itype, rtype reflect.Type) bool {
if t.cat == structT && rtype.Kind() == reflect.Interface {
return true
// assignFromCall assigns values from a function call.
func assignFromCall(n *node) {
ncall := n.lastChild()
l := len(n.child) - 1
if n.anc.kind == varDecl && n.child[l-1].isType(n.scope) {
// Ignore the type in the assignment if it is part of a variable declaration.
l--
}
switch t.cat {
case aliasT, arrayT, mapT, ptrT, sliceT:
return isRecursiveType(t.val, t.val.rtype)
default:
return false
dvalue := make([]func(*frame) reflect.Value, l)
for i := range dvalue {
if n.child[i].ident == "_" {
continue
}
dvalue[i] = genValue(n.child[i])
}
next := getExec(n.tnext)
n.exec = func(f *frame) bltn {
for i, v := range dvalue {
if v == nil {
continue
}
s := f.data[ncall.findex+i]
v(f).Set(s)
}
return next
}
}
@@ -916,24 +980,32 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
d[i] = reflect.New(t).Elem()
}
// Copy method receiver as first argument, if defined.
if rcvr != nil {
if rcvr == nil {
d = d[numRet:]
} else {
// Copy method receiver as first argument.
src, dest := rcvr(f), d[numRet]
if src.Type().Kind() != dest.Type().Kind() {
sk, dk := src.Kind(), dest.Kind()
switch {
case sk == reflect.Ptr && dk != reflect.Ptr:
dest.Set(src.Elem())
case sk != reflect.Ptr && dk == reflect.Ptr:
dest.Set(src.Addr())
} else {
default:
if wrappedSrc, ok := src.Interface().(valueInterface); ok {
src = wrappedSrc.value
}
dest.Set(src)
}
d = d[numRet+1:]
} else {
d = d[numRet:]
}
// Copy function input arguments in local frame.
for i, arg := range in {
if i >= len(d) {
// In case of unused arg, there may be not even a frame entry allocated, just skip.
break
}
typ := def.typ.arg[i]
switch {
case isEmptyInterface(typ):
@@ -948,7 +1020,7 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
}
// Interpreter code execution.
runCfg(start, fr)
runCfg(start, fr, def, n)
result := fr.data[:numRet]
for i, r := range result {
@@ -962,7 +1034,7 @@ 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()}}
return &node{kind: funcType, action: aNop, rval: v, typ: valueTOf(v.Type())}
}
func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
@@ -970,9 +1042,14 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
if typ == nil || typ.Kind() != reflect.Interface || typ.NumMethod() == 0 || n.typ.cat == valueT {
return value
}
nt := n.typ.frameType()
if nt != nil && nt.Implements(typ) {
return value
tc := n.typ.cat
if tc != structT {
// Always force wrapper generation for struct types, as they may contain
// embedded interface fields which require wrapping, even if reported as
// implementing typ by reflect.
if nt := n.typ.frameType(); nt != nil && nt.Implements(typ) {
return value
}
}
mn := typ.NumMethod()
names := make([]string, mn)
@@ -990,35 +1067,41 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
return func(f *frame) reflect.Value {
v := value(f)
if v.Type().Implements(typ) {
if tc != structT && v.Type().Implements(typ) {
return v
}
vv := v
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if v.IsNil() {
return reflect.New(typ).Elem()
}
if v.Kind() == reflect.Ptr {
vv = v.Elem()
}
}
var n2 *node
if vi, ok := v.Interface().(valueInterface); ok {
n2 = vi.node
}
v = getConcreteValue(v)
w := reflect.New(wrap).Elem()
w.Field(0).Set(v)
for i, m := range methods {
if m == nil {
if r := v.MethodByName(names[i]); r.IsValid() {
// First direct method lookup on field.
if r := methodByName(v, names[i], indexes[i]); r.IsValid() {
w.Field(i + 1).Set(r)
continue
}
o := vv.FieldByIndex(indexes[i])
if r := o.MethodByName(names[i]); r.IsValid() {
w.Field(i + 1).Set(r)
} else {
if n2 == nil {
panic(n.cfgErrorf("method not found: %s", names[i]))
}
continue
// Method lookup in embedded valueInterface.
m2, i2 := n2.typ.lookupMethod(names[i])
if m2 != nil {
nod := *m2
nod.recv = &receiver{n, v, i2}
w.Field(i + 1).Set(genFunctionWrapper(&nod)(f))
continue
}
panic(n.cfgErrorf("method not found: %s", names[i]))
}
nod := *m
nod.recv = &receiver{n, v, indexes[i]}
@@ -1028,62 +1111,119 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
}
}
// methodByName returns the method corresponding to name on value, or nil if not found.
// The search is extended on valueInterface wrapper if present.
// If valid, the returned value is a method function with the receiver already set
// (no need to pass it at call).
func methodByName(value reflect.Value, name string, index []int) (v reflect.Value) {
if vi, ok := value.Interface().(valueInterface); ok {
if v = getConcreteValue(vi.value).MethodByName(name); v.IsValid() {
return
}
}
if v = value.MethodByName(name); v.IsValid() {
return
}
for value.Kind() == reflect.Ptr {
value = value.Elem()
if checkFieldIndex(value.Type(), index) {
value = value.FieldByIndex(index)
}
if v = value.MethodByName(name); v.IsValid() {
return
}
}
return
}
func checkFieldIndex(typ reflect.Type, index []int) bool {
if len(index) == 0 {
return false
}
t := typ
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return false
}
i := index[0]
if i >= t.NumField() {
return false
}
if len(index) > 1 {
return checkFieldIndex(t.Field(i).Type, index[1:])
}
return true
}
func call(n *node) {
goroutine := n.anc.kind == goStmt
var method bool
value := genValue(n.child[0])
c0 := n.child[0]
value := genValue(c0)
var values []func(*frame) reflect.Value
recvIndexLater := false
switch {
case n.child[0].recv != nil:
case c0.recv != nil:
// Compute method receiver value.
if isRecursiveType(n.child[0].recv.node.typ, n.child[0].recv.node.typ.rtype) {
values = append(values, genValueRecvInterfacePtr(n.child[0]))
} else {
values = append(values, genValueRecv(n.child[0]))
}
values = append(values, genValueRecv(c0))
method = true
case len(n.child[0].child) > 0 && n.child[0].child[0].typ != nil && isInterfaceSrc(n.child[0].child[0].typ):
case len(c0.child) > 0 && c0.child[0].typ != nil && isInterfaceSrc(c0.child[0].typ):
recvIndexLater = true
values = append(values, genValueBinRecv(n.child[0], &receiver{node: n.child[0].child[0]}))
values = append(values, genValueBinRecv(c0, &receiver{node: c0.child[0]}))
value = genValueBinMethodOnInterface(n, value)
method = true
case n.child[0].action == aMethod:
case c0.action == aMethod:
// Add a place holder for interface method receiver.
values = append(values, nil)
method = true
}
numRet := len(n.child[0].typ.ret)
numRet := len(c0.typ.ret)
variadic := variadicPos(n)
child := n.child[1:]
tnext := getExec(n.tnext)
fnext := getExec(n.fnext)
hasVariadicArgs := n.action == aCallSlice // callSlice implies variadic call with ellipsis.
// Compute input argument value functions.
for i, c := range child {
var arg *itype
if variadic >= 0 && i >= variadic {
arg = c0.typ.arg[variadic].val
} else {
arg = c0.typ.arg[i]
}
switch {
case isBinCall(c):
case isBinCall(c, c.scope):
// Handle nested function calls: pass returned values as arguments.
numOut := c.child[0].typ.rtype.NumOut()
for j := 0; j < numOut; j++ {
ind := c.findex + j
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) {
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
continue
}
values = append(values, func(f *frame) reflect.Value {
return reflect.ValueOf(valueInterface{value: f.data[ind]})
})
}
case isRegularCall(c):
// Arguments are return values of a nested function call.
for j := range c.child[0].typ.ret {
cc0 := c.child[0]
for j := range cc0.typ.ret {
ind := c.findex + j
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) {
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
continue
}
values = append(values, func(f *frame) reflect.Value {
return reflect.ValueOf(valueInterface{node: cc0.typ.ret[j].node, value: f.data[ind]})
})
}
default:
var arg *itype
if variadic >= 0 && i >= variadic {
arg = n.child[0].typ.arg[variadic].val
} else {
arg = n.child[0].typ.arg[i]
}
if c.kind == basicLit || c.rval.IsValid() {
argType := arg.TypeOf()
convertLiteralValue(c, argType)
@@ -1091,13 +1231,10 @@ func call(n *node) {
switch {
case isEmptyInterface(arg):
values = append(values, genValue(c))
case isInterfaceSrc(arg) && n.action != aCallSlice:
// callSlice implies variadic call with ellipsis, do not wrap in valueInterface.
case isInterfaceSrc(arg) && !hasVariadicArgs:
values = append(values, genValueInterface(c))
case isInterfaceBin(arg):
values = append(values, genInterfaceWrapper(c, arg.rtype))
case isRecursiveType(c.typ, c.typ.rtype):
values = append(values, genValueRecursiveInterfacePtrValue(c))
default:
values = append(values, genValue(c))
}
@@ -1105,10 +1242,11 @@ func call(n *node) {
}
// Compute output argument value functions.
rtypes := n.child[0].typ.ret
rtypes := c0.typ.ret
rvalues := make([]func(*frame) reflect.Value, len(rtypes))
switch n.anc.kind {
case defineXStmt, assignXStmt:
l := n.level
for i := range rvalues {
c := n.anc.child[i]
switch {
@@ -1117,7 +1255,8 @@ func call(n *node) {
case isInterfaceSrc(c.typ) && !isEmptyInterface(c.typ) && !isInterfaceSrc(rtypes[i]):
rvalues[i] = genValueInterfaceValue(c)
default:
rvalues[i] = genValue(c)
j := n.findex + i
rvalues[i] = func(f *frame) reflect.Value { return getFrame(f, l).data[j] }
}
}
case returnStmt:
@@ -1138,7 +1277,7 @@ func call(n *node) {
if n.anc.kind == deferStmt {
// Store function call in frame for deferred execution.
value = genFunctionWrapper(n.child[0])
value = genFunctionWrapper(c0)
if method {
// The receiver is already passed in the function wrapper, skip it.
values = values[1:]
@@ -1268,7 +1407,7 @@ func call(n *node) {
// The !val.IsZero is to work around a recursive struct zero interface
// issue. Once there is a better way to handle this case, the dest
// can just be set.
if !val.IsZero() || dest[i].Type().Kind() == reflect.Interface {
if !val.IsZero() || dest[i].Kind() == reflect.Interface {
dest[i].Set(val)
}
}
@@ -1277,10 +1416,10 @@ func call(n *node) {
// Execute function body
if goroutine {
go runCfg(def.child[3].start, nf)
go runCfg(def.child[3].start, nf, def, n)
return tnext
}
runCfg(def.child[3].start, nf)
runCfg(def.child[3].start, nf, def, n)
// Handle branching according to boolean result
if fnext != nil && !nf.data[0].Bool() {
@@ -1344,7 +1483,7 @@ func callBin(n *node) {
}
switch {
case isBinCall(c):
case isBinCall(c, c.scope):
// Handle nested function calls: pass returned values as arguments
numOut := c.child[0].typ.rtype.NumOut()
for j := 0; j < numOut; j++ {
@@ -1457,12 +1596,13 @@ func callBin(n *node) {
rvalues := make([]func(*frame) reflect.Value, funcType.NumOut())
for i := range rvalues {
c := n.anc.child[i]
if c.ident != "_" {
if isInterfaceSrc(c.typ) {
rvalues[i] = genValueInterfaceValue(c)
} else {
rvalues[i] = genValue(c)
}
if c.ident == "_" {
continue
}
if isInterfaceSrc(c.typ) {
rvalues[i] = genValueInterfaceValue(c)
} else {
rvalues[i] = genValue(c)
}
}
n.exec = func(f *frame) bltn {
@@ -1489,7 +1629,11 @@ func callBin(n *node) {
}
out := callFn(value(f), in)
for i, v := range out {
f.data[b+i].Set(v)
dest := f.data[b+i]
if _, ok := dest.Interface().(valueInterface); ok {
v = reflect.ValueOf(valueInterface{value: v})
}
dest.Set(v)
}
return tnext
}
@@ -1501,11 +1645,16 @@ func callBin(n *node) {
}
out := callFn(value(f), in)
for i := 0; i < len(out); i++ {
if out[i].Type().Kind() == reflect.Func {
getFrame(f, n.level).data[n.findex+i] = out[i]
} else {
getFrame(f, n.level).data[n.findex+i].Set(out[i])
r := out[i]
if r.Kind() == reflect.Func {
getFrame(f, n.level).data[n.findex+i] = r
continue
}
dest := getFrame(f, n.level).data[n.findex+i]
if _, ok := dest.Interface().(valueInterface); ok {
r = reflect.ValueOf(valueInterface{value: r})
}
dest.Set(r)
}
return tnext
}
@@ -1817,7 +1966,7 @@ func getMethodByName(n *node) {
}
return next
}
m, li := val.node.typ.lookupMethod(name)
m, li := typ.lookupMethod(name)
if m == nil {
panic(n.cfgErrorf("method not found: %s", name))
}
@@ -1852,9 +2001,6 @@ func getIndexSeq(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
v := value(f)
if v.Type().Kind() == reflect.Interface && n.child[0].typ.recursive {
v = writableDeref(v)
}
r := v.FieldByIndex(index)
getFrame(f, l).data[i] = r
if r.Bool() {
@@ -1865,34 +2011,16 @@ func getIndexSeq(n *node) {
} else {
n.exec = func(f *frame) bltn {
v := value(f)
if v.Type().Kind() == reflect.Interface && n.child[0].typ.recursive {
v = writableDeref(v)
}
getFrame(f, l).data[i] = v.FieldByIndex(index)
return tnext
}
}
}
//go:nocheckptr
func writableDeref(v reflect.Value) reflect.Value {
// Here we have an interface to a struct. Any attempt to dereference it will
// make a copy of the struct. We need to get a Value to the actual struct.
// TODO: using unsafe is a temporary measure. Rethink this.
// TODO: InterfaceData has been depreciated, this is even less of a good idea now.
return reflect.NewAt(v.Elem().Type(), unsafe.Pointer(v.InterfaceData()[1])).Elem() //nolint:govet,staticcheck
}
func getPtrIndexSeq(n *node) {
index := n.val.([]int)
tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if isRecursiveType(n.child[0].typ, n.child[0].typ.rtype) {
v := genValue(n.child[0])
value = func(f *frame) reflect.Value { return v(f).Elem().Elem() }
} else {
value = genValue(n.child[0])
}
value := genValue(n.child[0])
i := n.findex
l := n.level
@@ -2247,9 +2375,13 @@ func _return(n *node) {
}
values[i] = genValueInterface(c)
case valueT:
if t.rtype.Kind() == reflect.Interface {
switch t.rtype.Kind() {
case reflect.Interface:
values[i] = genInterfaceWrapper(c, t.rtype)
break
continue
case reflect.Func:
values[i] = genFunctionWrapper(c)
continue
}
fallthrough
default:
@@ -2489,7 +2621,7 @@ func doCompositeBinStruct(n *node, hasType bool) {
}
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
case d.Kind() == reflect.Ptr:
d.Set(s.Addr())
default:
d.Set(s)
@@ -2544,10 +2676,8 @@ func doComposite(n *node, hasType bool, keyed bool) {
values[fieldIndex] = func(*frame) reflect.Value { return reflect.New(rft).Elem() }
case isFuncSrc(val.typ):
values[fieldIndex] = genValueAsFunctionWrapper(val)
case isArray(val.typ) && val.typ.val != nil && isInterfaceSrc(val.typ.val):
case isArray(val.typ) && val.typ.val != nil && isInterfaceSrc(val.typ.val) && !isEmptyInterface(val.typ.val):
values[fieldIndex] = genValueInterfaceArray(val)
case isRecursiveType(ft, rft):
values[fieldIndex] = genValueRecursiveInterface(val, rft)
case isInterfaceSrc(ft) && !isEmptyInterface(ft):
values[fieldIndex] = genValueInterface(val)
case isInterface(ft):
@@ -2569,7 +2699,7 @@ func doComposite(n *node, hasType bool, keyed bool) {
}
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
case d.Kind() == reflect.Ptr:
d.Set(a.Addr())
case destInterface:
if len(destType(n).field) > 0 {
@@ -2825,12 +2955,13 @@ func _case(n *node) {
if typ.cat == nilT && v.IsNil() {
return tnext
}
if typ.TypeOf().String() == t.String() {
rtyp := typ.TypeOf()
if rtyp != nil && rtyp.String() == t.String() && implementsInterface(v, typ) {
destValue(f).Set(v.Elem())
return tnext
}
ival := v.Interface()
if ival != nil && typ.TypeOf().String() == reflect.TypeOf(ival).String() {
if ival != nil && rtyp != nil && rtyp.String() == reflect.TypeOf(ival).String() {
destValue(f).Set(v.Elem())
return tnext
}
@@ -2893,6 +3024,22 @@ func _case(n *node) {
}
}
func implementsInterface(v reflect.Value, t *itype) bool {
rt := v.Type()
if t.cat == valueT {
return rt.Implements(t.rtype)
}
vt := &itype{cat: valueT, rtype: rt}
if vt.methods().contains(t.methods()) {
return true
}
vi, ok := v.Interface().(valueInterface)
if !ok {
return false
}
return vi.node != nil && vi.node.typ.methods().contains(t.methods())
}
func appendSlice(n *node) {
dest := genValueOutput(n, n.typ.rtype)
next := getExec(n.tnext)
@@ -2928,20 +3075,24 @@ func _append(n *node) {
value := genValue(n.child[1])
next := getExec(n.tnext)
if len(n.child) > 3 {
switch l := len(n.child); {
case l == 2:
n.exec = func(f *frame) bltn {
dest(f).Set(value(f))
return next
}
case l > 3:
args := n.child[2:]
l := len(args)
values := make([]func(*frame) reflect.Value, l)
for i, arg := range args {
switch {
case isEmptyInterface(n.typ.val):
switch elem := n.typ.elem(); {
case isEmptyInterface(elem):
values[i] = genValue(arg)
case isInterfaceSrc(n.typ.val):
case isInterfaceSrc(elem):
values[i] = genValueInterface(arg)
case isInterfaceBin(n.typ.val):
values[i] = genInterfaceWrapper(arg, n.typ.val.rtype)
case isRecursiveType(n.typ.val, n.typ.val.rtype):
values[i] = genValueRecursiveInterface(arg, n.typ.val.rtype)
case isInterfaceBin(elem):
values[i] = genInterfaceWrapper(arg, elem.rtype)
case arg.typ.untyped:
values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem())
default:
@@ -2957,7 +3108,7 @@ func _append(n *node) {
dest(f).Set(reflect.Append(value(f), sl...))
return next
}
} else {
default:
var value0 func(*frame) reflect.Value
switch elem := n.typ.elem(); {
case isEmptyInterface(elem):
@@ -2966,8 +3117,6 @@ func _append(n *node) {
value0 = genValueInterface(n.child[2])
case isInterfaceBin(elem):
value0 = genInterfaceWrapper(n.child[2], elem.rtype)
case isRecursiveType(elem, elem.rtype):
value0 = genValueRecursiveInterface(n.child[2], elem.rtype)
case n.child[2].typ.untyped:
value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem())
default:
@@ -3100,23 +3249,37 @@ func _delete(n *node) {
}
func capConst(n *node) {
n.rval = reflect.New(reflect.TypeOf(int(0))).Elem()
// There is no Cap() method for reflect.Type, just return Len() instead.
n.rval.SetInt(int64(n.child[1].typ.TypeOf().Len()))
lenConst(n)
}
func lenConst(n *node) {
n.rval = reflect.New(reflect.TypeOf(int(0))).Elem()
if c1 := n.child[1]; c1.rval.IsValid() {
c1 := n.child[1]
if c1.rval.IsValid() {
n.rval.SetInt(int64(len(vString(c1.rval))))
} else {
n.rval.SetInt(int64(c1.typ.TypeOf().Len()))
return
}
t := c1.typ.TypeOf()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
n.rval.SetInt(int64(t.Len()))
}
func _len(n *node) {
dest := genValueOutput(n, reflect.TypeOf(int(0)))
value := genValue(n.child[1])
if isPtr(n.child[1].typ) {
val := value
value = func(f *frame) reflect.Value {
v := val(f).Elem()
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
return v
}
}
next := getExec(n.tnext)
if wantEmptyInterface(n) {

View File

@@ -78,6 +78,7 @@ type scope struct {
loop *node // loop exit node for break statement
loopRestart *node // loop restart node for continue statement
pkgID string // unique id of package in which scope is defined
pkgName string // package name for the package
types []reflect.Type // frame layout, may be shared by same level scopes
level int // frame level: number of frame indirections to access var during execution
sym map[string]*symbol // map of symbols defined in this current scope
@@ -172,7 +173,14 @@ func (s *scope) rangeChanType(n *node) *itype {
case c.typ.cat == chanT, c.typ.cat == chanRecvT:
return c.typ
case c.typ.cat == valueT && c.typ.rtype.Kind() == reflect.Chan:
return &itype{cat: chanT, val: &itype{cat: valueT, rtype: c.typ.rtype.Elem()}}
dir := chanSendRecv
switch c.typ.rtype.ChanDir() {
case reflect.RecvDir:
dir = chanRecv
case reflect.SendDir:
dir = chanSend
}
return chanOf(valueTOf(c.typ.rtype.Elem()), dir)
}
return nil
@@ -220,7 +228,7 @@ func (s *scope) add(typ *itype) (index int) {
return
}
func (interp *Interpreter) initScopePkg(pkgID string) *scope {
func (interp *Interpreter) initScopePkg(pkgID, pkgName string) *scope {
sc := interp.universe
interp.mutex.Lock()
@@ -229,6 +237,7 @@ func (interp *Interpreter) initScopePkg(pkgID string) *scope {
}
sc = interp.scopes[pkgID]
sc.pkgID = pkgID
sc.pkgName = pkgName
interp.mutex.Unlock()
return sc
}

View File

@@ -2,7 +2,7 @@ package interp
import (
"fmt"
"io/ioutil"
"io/fs"
"os"
"path/filepath"
"strings"
@@ -48,7 +48,7 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
}
interp.rdir[importPath] = true
files, err := ioutil.ReadDir(dir)
files, err := fs.ReadDir(interp.opt.filesystem, dir)
if err != nil {
return "", err
}
@@ -69,12 +69,20 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
name = filepath.Join(dir, name)
var buf []byte
if buf, err = ioutil.ReadFile(name); err != nil {
if buf, err = fs.ReadFile(interp.opt.filesystem, name); err != nil {
return "", err
}
n, err := interp.parse(string(buf), name, false)
if err != nil {
return "", err
}
if n == nil {
continue
}
var pname string
if pname, root, err = interp.ast(string(buf), name, false); err != nil {
if pname, root, err = interp.ast(n); err != nil {
return "", err
}
if root == nil {
@@ -97,7 +105,7 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
subRPath := effectivePkg(rPath, importPath)
var list []*node
list, err = interp.gta(root, subRPath, importPath)
list, err = interp.gta(root, subRPath, importPath, pkgName)
if err != nil {
return "", err
}
@@ -106,7 +114,7 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
// Revisit incomplete nodes where GTA could not complete.
for _, nodes := range revisit {
if err = interp.gtaRetry(nodes, importPath); err != nil {
if err = interp.gtaRetry(nodes, importPath, pkgName); err != nil {
return "", err
}
}
@@ -114,7 +122,7 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
// Generate control flow graphs.
for _, root := range rootNodes {
var nodes []*node
if nodes, err = interp.cfg(root, importPath); err != nil {
if nodes, err = interp.cfg(root, nil, importPath, pkgName); err != nil {
return "", err
}
initNodes = append(initNodes, nodes...)
@@ -185,13 +193,13 @@ func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (strin
rPath := filepath.Join(root, "vendor")
dir := filepath.Join(goPath, "src", rPath, importPath)
if _, err := os.Stat(dir); err == nil {
if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil {
return dir, rPath, nil // found!
}
dir = filepath.Join(goPath, "src", effectivePkg(root, importPath))
if _, err := os.Stat(dir); err == nil {
if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil {
return dir, root, nil // found!
}
@@ -203,7 +211,7 @@ func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (strin
}
rootPath := filepath.Join(goPath, "src", root)
prevRoot, err := previousRoot(rootPath, root)
prevRoot, err := previousRoot(interp.opt.filesystem, rootPath, root)
if err != nil {
return "", "", err
}
@@ -214,7 +222,7 @@ func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (strin
const vendor = "vendor"
// Find the previous source root (vendor > vendor > ... > GOPATH).
func previousRoot(rootPath, root string) (string, error) {
func previousRoot(filesystem fs.FS, rootPath, root string) (string, error) {
rootPath = filepath.Clean(rootPath)
parent, final := filepath.Split(rootPath)
parent = filepath.Clean(parent)
@@ -227,7 +235,7 @@ func previousRoot(rootPath, root string) (string, error) {
// 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))
fi, err := fs.Stat(filesystem, filepath.Join(parent, vendor))
if err == nil && fi.IsDir() {
vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator))
break

View File

@@ -1,7 +1,6 @@
package interp
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -50,7 +49,7 @@ func Test_effectivePkg(t *testing.T) {
func Test_pkgDir(t *testing.T) {
// create GOPATH
goPath, err := ioutil.TempDir("", "pkdir")
goPath, err := os.MkdirTemp("", "pkdir")
if err != nil {
t.Fatal(err)
}
@@ -60,7 +59,7 @@ func Test_pkgDir(t *testing.T) {
// Create project
project := filepath.Join(goPath, "src", "guthib.com", "foo", "root")
if err := os.MkdirAll(project, 0700); err != nil {
if err := os.MkdirAll(project, 0o700); err != nil {
t.Fatal(err)
}
@@ -81,7 +80,7 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: "",
setup: func() error {
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "bar"),
@@ -93,7 +92,7 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root"),
setup: func() error {
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bar"),
@@ -105,7 +104,7 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root"),
setup: func() error {
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "bar"),
@@ -117,10 +116,10 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bir"),
setup: func() error {
if err := os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0700); err != nil {
if err := os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0o700); err != nil {
return err
}
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bar"),
@@ -132,10 +131,10 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bir"),
setup: func() error {
if err := os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0700); err != nil {
if err := os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0o700); err != nil {
return err
}
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "bar"),
@@ -149,10 +148,10 @@ func Test_pkgDir(t *testing.T) {
setup: func() error {
if err := os.MkdirAll(
filepath.Join(goPath, "src", "guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bir", "vendor", "guthib.com", "foo", "bur"),
0700); err != nil {
0o700); err != nil {
return err
}
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(project, "vendor", "guthib.com", "foo", "bar"),
@@ -161,7 +160,11 @@ func Test_pkgDir(t *testing.T) {
},
}
interp := &Interpreter{}
interp := &Interpreter{
opt: opt{
filesystem: &realFS{},
},
}
for _, test := range testCases {
test := test
@@ -169,7 +172,7 @@ func Test_pkgDir(t *testing.T) {
if err := os.RemoveAll(goPath); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(goPath, 0700); err != nil {
if err := os.MkdirAll(goPath, 0o700); err != nil {
t.Fatal(err)
}
@@ -247,7 +250,7 @@ func Test_previousRoot(t *testing.T) {
} else {
rootPath = vendor
}
p, err := previousRoot(rootPath, test.root)
p, err := previousRoot(&realFS{}, rootPath, test.root)
if err != nil {
t.Error(err)
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,9 @@ type opPredicates map[action]func(reflect.Type) bool
// Due to variant type systems (itype vs reflect.Type) a single
// type system should used, namely reflect.Type with exception
// of the untyped flag on itype.
type typecheck struct{}
type typecheck struct {
scope *scope
}
// op type checks an expression against a set of expression predicates.
func (check typecheck) op(p opPredicates, a action, n, c *node, t reflect.Type) error {
@@ -40,7 +42,7 @@ func (check typecheck) assignment(n *node, typ *itype, context string) error {
if typ == nil && n.typ.cat == nilT {
return n.cfgErrorf("use of untyped nil in %s", context)
}
typ = n.typ.defaultType(n.rval)
typ = n.typ.defaultType(n.rval, check.scope)
}
if err := check.convertUntyped(n, typ); err != nil {
return err
@@ -51,11 +53,7 @@ func (check typecheck) assignment(n *node, typ *itype, context string) error {
return nil
}
if typ.isIndirectRecursive() || n.typ.isIndirectRecursive() {
return nil
}
if !n.typ.assignableTo(typ) {
if !n.typ.assignableTo(typ) && typ.str != "*unsafe2.dummy" {
if context == "" {
return n.cfgErrorf("cannot use type %s as type %s", n.typ.id(), typ.id())
}
@@ -72,7 +70,7 @@ func (check typecheck) assignExpr(n, dest, src *node) error {
isConst := n.anc.kind == constDecl
if !isConst {
// var operations must be typed
dest.typ = dest.typ.defaultType(src.rval)
dest.typ = dest.typ.defaultType(src.rval, check.scope)
}
return check.assignment(src, dest.typ, "assignment")
@@ -167,7 +165,7 @@ func (check typecheck) shift(n *node) error {
switch {
case c1.typ.untyped:
if err := check.convertUntyped(c1, &itype{cat: uintT, name: "uint"}); err != nil {
if err := check.convertUntyped(c1, check.scope.getType("uint")); err != nil {
return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
}
case isInt(t1):
@@ -277,7 +275,7 @@ func zeroConst(n *node) bool {
}
func (check typecheck) index(n *node, max int) error {
if err := check.convertUntyped(n, &itype{cat: intT, name: "int"}); err != nil {
if err := check.convertUntyped(n, check.scope.getType("int")); err != nil {
return err
}
@@ -440,7 +438,7 @@ func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
return c.cfgErrorf("unknown field %s in struct literal", name)
}
if err := check.assignment(val, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
if err := check.assignment(val, valueTOf(field.Type), "struct literal"); err != nil {
return err
}
@@ -466,7 +464,7 @@ func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
return c.cfgErrorf("implicit assignment to unexported field %s in %s literal", field.Name, typ)
}
if err := check.assignment(c, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
if err := check.assignment(c, valueTOf(field.Type), "struct literal"); err != nil {
return err
}
}
@@ -651,7 +649,7 @@ func (check typecheck) conversion(n *node, typ *itype) error {
return nil
}
if isInterface(typ) || !isConstType(typ) {
typ = n.typ.defaultType(n.rval)
typ = n.typ.defaultType(n.rval, check.scope)
}
return check.convertUntyped(n, typ)
}
@@ -735,29 +733,28 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
case bltnAppend:
typ := params[0].Type()
t := typ.TypeOf()
if t.Kind() != reflect.Slice {
if t == nil || t.Kind() != reflect.Slice {
return params[0].nod.cfgErrorf("first argument to append must be slice; have %s", typ.id())
}
if nparams == 1 {
return nil
}
// Special case append([]byte, "test"...) is allowed.
t1 := params[1].Type()
if nparams == 2 && ellipsis && t.Elem().Kind() == reflect.Uint8 && t1.TypeOf().Kind() == reflect.String {
if t1.untyped {
return check.convertUntyped(params[1].nod, &itype{cat: stringT, name: "string"})
return check.convertUntyped(params[1].nod, check.scope.getType("string"))
}
return nil
}
// We cannot check a recursive type.
if isRecursiveType(typ, typ.TypeOf()) {
return nil
}
fun := &node{
typ: &itype{
cat: funcT,
arg: []*itype{
typ,
{cat: variadicT, val: &itype{cat: valueT, rtype: t.Elem()}},
{cat: variadicT, val: valueTOf(t.Elem())},
},
ret: []*itype{typ},
},
@@ -796,7 +793,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
case !typ0.untyped && typ1.untyped:
err = check.convertUntyped(p1.nod, typ0)
case typ0.untyped && typ1.untyped:
fltType := &itype{cat: float64T, name: "float64"}
fltType := check.scope.getType("float64")
err = check.convertUntyped(p0.nod, fltType)
if err != nil {
break
@@ -819,7 +816,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
p := params[0]
typ := p.Type()
if typ.untyped {
if err := check.convertUntyped(p.nod, &itype{cat: complex128T, name: "complex128"}); err != nil {
if err := check.convertUntyped(p.nod, check.scope.getType("complex128")); err != nil {
return err
}
}
@@ -853,7 +850,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
return params[0].nod.cfgErrorf("first argument to delete must be map; have %s", typ.id())
}
ktyp := params[1].Type()
if !ktyp.assignableTo(typ.key) {
if typ.key != nil && !ktyp.assignableTo(typ.key) {
return params[1].nod.cfgErrorf("cannot use %s as type %s in delete", ktyp.id(), typ.key.id())
}
case bltnMake:
@@ -886,7 +883,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
}
case bltnPanic:
return check.assignment(params[0].nod, &itype{cat: interfaceT}, "argument to panic")
return check.assignment(params[0].nod, check.scope.getType("interface{}"), "argument to panic")
case bltnPrint, bltnPrintln:
for _, param := range params {
if param.typ != nil {
@@ -910,7 +907,7 @@ func arrayDeref(typ *itype) *itype {
if typ.cat == valueT && typ.TypeOf().Kind() == reflect.Ptr {
t := typ.TypeOf()
if t.Elem().Kind() == reflect.Array {
return &itype{cat: valueT, rtype: t.Elem()}
return valueTOf(t.Elem())
}
return typ
}
@@ -970,8 +967,8 @@ func (check typecheck) argument(p param, ftyp *itype, i, l int, ellipsis bool) e
return p.nod.cfgErrorf("can only use ... with matching parameter")
}
t := p.Type().TypeOf()
if t.Kind() != reflect.Slice || !(&itype{cat: valueT, rtype: t.Elem()}).assignableTo(atyp) {
return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.typ.id(), (&itype{cat: sliceT, val: atyp}).id())
if t.Kind() != reflect.Slice || !(valueTOf(t.Elem())).assignableTo(atyp) {
return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.typ.id(), (sliceOf(atyp)).id())
}
return nil
}
@@ -994,7 +991,7 @@ func getArg(ftyp *itype, i int) *itype {
case i < l:
return ftyp.in(i)
case ftyp.cat == valueT && i < ftyp.rtype.NumIn():
return &itype{cat: valueT, rtype: ftyp.rtype.In(i)}
return valueTOf(ftyp.rtype.In(i))
default:
return nil
}
@@ -1054,7 +1051,7 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error {
if len(n.typ.methods()) > 0 { // untyped cannot be set to iface
return convErr
}
ityp = n.typ.defaultType(n.rval)
ityp = n.typ.defaultType(n.rval, check.scope)
rtyp = ntyp
case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ):
// TODO(nick): above we are acting on itype, but really it is an rtype check. This is not clear which type

40
interp/typestring.go Normal file
View File

@@ -0,0 +1,40 @@
package interp
import "strings"
func paramsTypeString(params []*itype) string {
strs := make([]string, 0, len(params))
for _, param := range params {
strs = append(strs, param.str)
}
return strings.Join(strs, ",")
}
func methodsTypeString(fields []structField) string {
strs := make([]string, 0, len(fields))
for _, field := range fields {
if field.embed {
str := methodsTypeString(field.typ.field)
if str != "" {
strs = append(strs, str)
}
continue
}
strs = append(strs, field.name+field.typ.str[4:])
}
return strings.Join(strs, "; ")
}
func fieldsTypeString(fields []structField) string {
strs := make([]string, 0, len(fields))
for _, field := range fields {
var repr strings.Builder
if !field.embed {
repr.WriteString(field.name)
repr.WriteByte(' ')
}
repr.WriteString(field.typ.str)
strs = append(strs, repr.String())
}
return strings.Join(strs, "; ")
}

View File

@@ -65,22 +65,30 @@ func genValueBinMethodOnInterface(n *node, defaultGen func(*frame) reflect.Value
nod = vi.node
}
if nod == nil {
if nod == nil || nod.typ.rtype == nil {
return defaultGen(f)
}
typ := nod.typ
if typ.node != nil || typ.cat != valueT {
// Try to get the bin method, if it doesnt exist, fall back to
// the default generator function.
meth, ok := nod.typ.rtype.MethodByName(c0.child[1].ident)
if !ok {
return defaultGen(f)
}
meth, _ := typ.rtype.MethodByName(c0.child[1].ident)
return meth.Func
}
}
func genValueRecvIndirect(n *node) func(*frame) reflect.Value {
v := genValueRecv(n)
return func(f *frame) reflect.Value { return v(f).Elem() }
vr := genValueRecv(n)
return func(f *frame) reflect.Value {
v := vr(f)
if vi, ok := v.Interface().(valueInterface); ok {
return vi.value
}
return v.Elem()
}
}
func genValueRecv(n *node) func(*frame) reflect.Value {
@@ -129,25 +137,6 @@ func genValueBinRecv(n *node, recv *receiver) func(*frame) reflect.Value {
}
}
func genValueRecvInterfacePtr(n *node) func(*frame) reflect.Value {
v := genValue(n.recv.node)
fi := n.recv.index
return func(f *frame) reflect.Value {
r := v(f)
r = r.Elem().Elem()
if len(fi) == 0 {
return r
}
if r.Kind() == reflect.Ptr {
r = r.Elem()
}
return r.FieldByIndex(fi)
}
}
func genValueAsFunctionWrapper(n *node) func(*frame) reflect.Value {
value := genValue(n)
typ := n.typ.TypeOf()
@@ -158,7 +147,7 @@ func genValueAsFunctionWrapper(n *node) func(*frame) reflect.Value {
return reflect.New(typ).Elem()
}
vn, ok := v.Interface().(*node)
if ok && vn.rval.IsValid() && vn.rval.Type().Kind() == reflect.Func {
if ok && vn.rval.Kind() == reflect.Func {
// The node value is already a callable func, no need to wrap it.
return vn.rval
}
@@ -171,7 +160,7 @@ func genValueAs(n *node, t reflect.Type) func(*frame) reflect.Value {
return func(f *frame) reflect.Value {
v := value(f)
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice, reflect.UnsafePointer:
if v.IsNil() {
return reflect.New(t).Elem()
@@ -232,7 +221,7 @@ func genDestValue(typ *itype, n *node) func(*frame) reflect.Value {
switch {
case isInterfaceSrc(typ) && !isEmptyInterface(typ):
return genValueInterface(n)
case isFuncSrc(typ) && n.typ.cat == valueT:
case isFuncSrc(typ) && (n.typ.cat == valueT || n.typ.cat == nilT):
return genValueNode(n)
case typ.cat == valueT && isFuncSrc(n.typ):
return genFunctionWrapper(n)
@@ -240,10 +229,6 @@ func genDestValue(typ *itype, n *node) func(*frame) reflect.Value {
return genInterfaceWrapper(n, typ.rtype)
case n.kind == basicLit && n.val == nil:
return func(*frame) reflect.Value { return reflect.New(typ.rtype).Elem() }
case isRecursiveType(typ, typ.rtype):
return genValueRecursiveInterface(n, typ.rtype)
case isRecursiveType(n.typ, n.typ.rtype):
return genValueRecursiveInterfacePtrValue(n)
case n.typ.untyped && isComplex(typ.TypeOf()):
return genValueComplex(n)
case n.typ.untyped && !typ.untyped:
@@ -333,7 +318,7 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
}
// empty interface, do not wrap.
if nod.typ.cat == interfaceT && len(nod.typ.field) == 0 {
if nod != nil && isEmptyInterface(nod.typ) {
return v
}
@@ -353,13 +338,13 @@ func getConcreteValue(val reflect.Value) reflect.Value {
if v.NumMethod() > 0 {
return v
}
if v.Type().Kind() != reflect.Struct {
if v.Kind() != reflect.Struct {
return v
}
// Search a concrete value in fields of an emulated interface.
for i := v.NumField() - 1; i >= 0; i-- {
vv := v.Field(i)
if vv.Type().Kind() == reflect.Interface {
if vv.Kind() == reflect.Interface {
vv = vv.Elem()
}
if vv.IsValid() {
@@ -370,7 +355,7 @@ func getConcreteValue(val reflect.Value) reflect.Value {
}
func zeroInterfaceValue() reflect.Value {
n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true}}
n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true, str: "nil"}}
v := reflect.New(interf).Elem()
return reflect.ValueOf(valueInterface{n, v})
}
@@ -440,69 +425,12 @@ func genValueNode(n *node) func(*frame) reflect.Value {
}
}
func genValueRecursiveInterface(n *node, t reflect.Type) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
vv := value(f)
v := reflect.New(t).Elem()
toRecursive(v, vv)
return v
}
}
func toRecursive(dest, src reflect.Value) {
if !src.IsValid() {
return
}
switch dest.Kind() {
case reflect.Map:
v := reflect.MakeMapWithSize(dest.Type(), src.Len())
for _, kv := range src.MapKeys() {
vv := reflect.New(dest.Type().Elem()).Elem()
toRecursive(vv, src.MapIndex(kv))
vv.SetMapIndex(kv, vv)
}
dest.Set(v)
case reflect.Slice:
l := src.Len()
v := reflect.MakeSlice(dest.Type(), l, l)
for i := 0; i < l; i++ {
toRecursive(v.Index(i), src.Index(i))
}
dest.Set(v)
case reflect.Ptr:
v := reflect.New(dest.Type().Elem()).Elem()
s := src
if s.Elem().Kind() != reflect.Struct { // In the case of *interface{}, we want *struct{}
s = s.Elem()
}
toRecursive(v, s)
dest.Set(v.Addr())
default:
dest.Set(src)
}
}
func genValueRecursiveInterfacePtrValue(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := value(f)
if v.IsZero() {
return v
}
return v.Elem().Elem()
}
}
func vInt(v reflect.Value) (i int64) {
if c := vConstantValue(v); c != nil {
i, _ = constant.Int64Val(constant.ToInt(c))
return i
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = v.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -520,7 +448,7 @@ func vUint(v reflect.Value) (i uint64) {
i, _ = constant.Uint64Val(constant.ToInt(c))
return i
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = uint64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -540,7 +468,7 @@ func vComplex(v reflect.Value) (c complex128) {
img, _ := constant.Float64Val(constant.Imag(c))
return complex(rel, img)
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
c = complex(float64(v.Int()), 0)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -558,7 +486,7 @@ func vFloat(v reflect.Value) (i float64) {
i, _ = constant.Float64Val(constant.ToFloat(c))
return i
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = float64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:

View File

@@ -47,8 +47,12 @@ type _compress_flate_Reader struct {
WReadByte func() (byte, error)
}
func (W _compress_flate_Reader) Read(p []byte) (n int, err error) { return W.WRead(p) }
func (W _compress_flate_Reader) ReadByte() (byte, error) { return W.WReadByte() }
func (W _compress_flate_Reader) Read(p []byte) (n int, err error) {
return W.WRead(p)
}
func (W _compress_flate_Reader) ReadByte() (byte, error) {
return W.WReadByte()
}
// _compress_flate_Resetter is an interface wrapper for Resetter type
type _compress_flate_Resetter struct {
@@ -56,4 +60,6 @@ type _compress_flate_Resetter struct {
WReset func(r io.Reader, dict []byte) error
}
func (W _compress_flate_Resetter) Reset(r io.Reader, dict []byte) error { return W.WReset(r, dict) }
func (W _compress_flate_Resetter) Reset(r io.Reader, dict []byte) error {
return W.WReset(r, dict)
}

View File

@@ -44,4 +44,6 @@ type _compress_zlib_Resetter struct {
WReset func(r io.Reader, dict []byte) error
}
func (W _compress_zlib_Resetter) Reset(r io.Reader, dict []byte) error { return W.WReset(r, dict) }
func (W _compress_zlib_Resetter) Reset(r io.Reader, dict []byte) error {
return W.WReset(r, dict)
}

View File

@@ -36,8 +36,18 @@ type _container_heap_Interface struct {
WSwap func(i int, j int)
}
func (W _container_heap_Interface) Len() int { return W.WLen() }
func (W _container_heap_Interface) Less(i int, j int) bool { return W.WLess(i, j) }
func (W _container_heap_Interface) Pop() interface{} { return W.WPop() }
func (W _container_heap_Interface) Push(x interface{}) { W.WPush(x) }
func (W _container_heap_Interface) Swap(i int, j int) { W.WSwap(i, j) }
func (W _container_heap_Interface) Len() int {
return W.WLen()
}
func (W _container_heap_Interface) Less(i int, j int) bool {
return W.WLess(i, j)
}
func (W _container_heap_Interface) Pop() interface{} {
return W.WPop()
}
func (W _container_heap_Interface) Push(x interface{}) {
W.WPush(x)
}
func (W _container_heap_Interface) Swap(i int, j int) {
W.WSwap(i, j)
}

View File

@@ -40,7 +40,15 @@ type _context_Context struct {
WValue func(key interface{}) interface{}
}
func (W _context_Context) Deadline() (deadline time.Time, ok bool) { return W.WDeadline() }
func (W _context_Context) Done() <-chan struct{} { return W.WDone() }
func (W _context_Context) Err() error { return W.WErr() }
func (W _context_Context) Value(key interface{}) interface{} { return W.WValue(key) }
func (W _context_Context) Deadline() (deadline time.Time, ok bool) {
return W.WDeadline()
}
func (W _context_Context) Done() <-chan struct{} {
return W.WDone()
}
func (W _context_Context) Err() error {
return W.WErr()
}
func (W _context_Context) Value(key interface{}) interface{} {
return W.WValue(key)
}

Some files were not shown because too many files have changed in this diff Show More