mirror of
https://github.com/traefik/yaegi.git
synced 2026-05-02 18:32:32 +00:00
Fix nil pointer dereference when sending to binary channel type alias
## Description
When sending a value to a channel that is a type alias defined in a binary package, yaegi panics with a nil pointer dereference.
This PR was created with AI assistance (Claude) as I hit this issue in a downstream project using yaegi.
The fix and tests have been reviewed and verified for correctness and functionality by myself.
## Root Cause
In the `send` function (`interp/run.go`), the code directly accesses `c0.typ.val` to get the channel element type:
```go
value1 := genDestValue(c0.typ.val, c1)
```
For source-defined channels (category `chanT`), `.val` contains the element type. But for binary channel type aliases (category `valueT`), `.val` is nil - the element type is only available via reflection in `.rtype`.
## Fix
Use the existing `elem()` method which correctly handles both cases:
```go
value1 := genDestValue(c0.typ.elem(), c1)
```
The `elem()` method (in `type.go`) already handles this properly:
```go
func (t *itype) elem() *itype {
if t.cat == valueT {
return valueTOf(t.rtype.Elem()) // Binary types: create from reflect
}
return t.val // Source types: use existing itype with all metadata
}
```
This is a one-line fix that fixes binary channel type aliases by using reflection to get the element type
## Test Results
| Test | Without Fix | With Fix |
|------|-------------|----------|
| `TestSendToSourceDefinedChannel` | PASS | PASS |
| `TestSendToSourceDefinedChannelTypeAlias` | PASS | PASS |
| `TestSendToBinaryChannelTypeAlias` | FAIL | PASS |
## Minimal Reproduction
### mypkg/mypkg.go
```go
package mypkg
// IntChan is a channel type alias
type IntChan chan int
// NewIntChan creates a new IntChan
func NewIntChan() IntChan {
return make(IntChan, 1)
}
```
### symbols.go
```go
package main
import (
"reflect"
"yaegi-repro/mypkg"
)
var Symbols = map[string]map[string]reflect.Value{}
func init() {
Symbols["yaegi-repro/mypkg/mypkg"] = map[string]reflect.Value{
"IntChan": reflect.ValueOf((*mypkg.IntChan)(nil)),
"NewIntChan": reflect.ValueOf(mypkg.NewIntChan),
}
}
```
### main.go
```go
package main
import (
"fmt"
"log"
"github.com/traefik/yaegi/interp"
)
func main() {
i := interp.New(interp.Options{})
if err := i.Use(Symbols); err != nil {
log.Fatal(err)
}
code := `
package main
import "yaegi-repro/mypkg"
func main() {
ch := mypkg.NewIntChan()
ch <- 42 // This line panics without the fix
val := <-ch
println("Received:", val)
}
`
_, err := i.Eval(code)
if err != nil {
log.Fatal(err)
}
fmt.Println("Success!")
}
```
## Results
**Without fix:**
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x128 pc=0x...]
goroutine 1 [running]:
github.com/traefik/yaegi/interp.(*itype).refType(0x0?, ...)
.../interp/type.go:2065 +0x7a
github.com/traefik/yaegi/interp.(*itype).TypeOf(...)
.../interp/type.go:2223
github.com/traefik/yaegi/interp.genDestValue(0x0, ...)
.../interp/value.go:157 +0x25
github.com/traefik/yaegi/interp.send(...)
.../interp/run.go:3766 +0x...
```
**With fix:**
```
Received: 42
Success!
```
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package interp_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/traefik/yaegi/interp"
|
||||
)
|
||||
|
||||
// IntChan is a channel type alias for testing.
|
||||
type IntChan chan int
|
||||
|
||||
// NewIntChan creates a new IntChan.
|
||||
func NewIntChan() IntChan {
|
||||
return make(IntChan, 1)
|
||||
}
|
||||
|
||||
func TestSendToBinaryChannelTypeAlias(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
|
||||
err := i.Use(interp.Exports{
|
||||
"mypkg/mypkg": {
|
||||
"IntChan": reflect.ValueOf((*IntChan)(nil)),
|
||||
"NewIntChan": reflect.ValueOf(NewIntChan),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = i.Eval(`
|
||||
package main
|
||||
|
||||
import "mypkg"
|
||||
|
||||
func main() {
|
||||
ch := mypkg.NewIntChan()
|
||||
ch <- 42
|
||||
val := <-ch
|
||||
if val != 42 {
|
||||
panic("unexpected value")
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendToSourceDefinedChannel(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
|
||||
// Test with a channel defined purely in interpreted code
|
||||
_, err := i.Eval(`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
ch := make(chan int, 1)
|
||||
ch <- 42
|
||||
val := <-ch
|
||||
if val != 42 {
|
||||
panic("unexpected value")
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendToSourceDefinedChannelTypeAlias(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
|
||||
// Test with a channel type alias defined in interpreted code
|
||||
_, err := i.Eval(`
|
||||
package main
|
||||
|
||||
type MyChan chan int
|
||||
|
||||
func main() {
|
||||
ch := make(MyChan, 1)
|
||||
ch <- 42
|
||||
val := <-ch
|
||||
if val != 42 {
|
||||
panic("unexpected value")
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3763,7 +3763,7 @@ func send(n *node) {
|
||||
next := getExec(n.tnext)
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
value0 := genValue(c0) // Send channel.
|
||||
value1 := genDestValue(c0.typ.val, c1)
|
||||
value1 := genDestValue(c0.typ.elem(), c1)
|
||||
|
||||
if !n.interp.cancelChan {
|
||||
// Send is non-cancellable, has the least overhead.
|
||||
|
||||
Reference in New Issue
Block a user