pk910 fcb76d1ece 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!
```
2026-02-09 09:56:05 +01:00
2025-05-21 21:22:05 +02:00
2019-07-21 21:51:19 +02:00
2025-05-21 21:22:05 +02:00
2024-03-04 12:00:25 +01:00
2024-03-06 09:12:03 +01:00
2024-03-05 17:56:04 +01:00
2020-09-16 10:58:04 +02:00
2024-03-05 17:56:04 +01:00

Yaegi

release Build Status GoDoc

Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go runtime.

Features

  • Complete support of Go specification
  • Written in pure Go, using only the standard library
  • Simple interpreter API: New(), Eval(), Use()
  • Works everywhere Go works
  • All Go & runtime resources accessible from script (with control)
  • Security: unsafe and syscall packages neither used nor exported by default
  • Support the latest 2 major releases of Go (Go 1.21 and Go 1.22)

Install

Go package

import "github.com/traefik/yaegi/interp"

Command-line executable

go install github.com/traefik/yaegi/cmd/yaegi@latest

Note that you can use rlwrap (install with your favorite package manager), and alias the yaegi command in alias yaegi='rlwrap yaegi' in your ~/.bashrc, to have history and command line edition.

CI Integration

curl -sfL https://raw.githubusercontent.com/traefik/yaegi/master/install.sh | bash -s -- -b $GOPATH/bin v0.9.0

Usage

As an embedded interpreter

Create an interpreter with New(), run Go code with Eval():

package main

import (
	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)

func main() {
	i := interp.New(interp.Options{})

	i.Use(stdlib.Symbols)

	_, err := i.Eval(`import "fmt"`)
	if err != nil {
		panic(err)
	}

	_, err = i.Eval(`fmt.Println("Hello Yaegi")`)
	if err != nil {
		panic(err)
	}
}

Go Playground

As a dynamic extension framework

The following program is compiled ahead of time, except bar() which is interpreted, with the following steps:

  1. use of i.Eval(src) to evaluate the script in the context of interpreter
  2. use of v, err := i.Eval("foo.Bar") to get the symbol from the interpreter context, as a reflect.Value
  3. application of Interface() method and type assertion to convert v into bar, as if it was compiled
package main

import "github.com/traefik/yaegi/interp"

const src = `package foo
func Bar(s string) string { return s + "-Foo" }`

func main() {
	i := interp.New(interp.Options{})

	_, err := i.Eval(src)
	if err != nil {
		panic(err)
	}

	v, err := i.Eval("foo.Bar")
	if err != nil {
		panic(err)
	}

	bar := v.Interface().(func(string) string)

	r := bar("Kung")
	println(r)
}

Go Playground

As a command-line interpreter

The Yaegi command can run an interactive Read-Eval-Print-Loop:

$ yaegi
> 1 + 2
3
> import "fmt"
> fmt.Println("Hello World")
Hello World
>

Note that in interactive mode, all stdlib package are pre-imported, you can use them directly:

$ yaegi
> reflect.TypeOf(time.Date)
: func(int, time.Month, int, int, int, int, int, *time.Location) time.Time
>

Or interpret Go packages, directories or files, including itself:

$ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi
>

Or for Go scripting in the shebang line:

$ cat /tmp/test
#!/usr/bin/env yaegi
package main

import "fmt"

func main() {
	fmt.Println("test")
}
$ ls -la /tmp/test
-rwxr-xr-x 1 dow184 dow184 93 Jan  6 13:38 /tmp/test
$ /tmp/test
test

Documentation

Documentation about Yaegi commands and libraries can be found at usual godoc.org.

Key documentation of the internal design: https://marc.vertes.org/yaegi-internals/ Also see interp/trace.go for helpful printing commands to see what is happening under the hood during compilation.

Limitations

Beside the known bugs which are supposed to be fixed in the short term, there are some limitations not planned to be addressed soon:

  • Assembly files (.s) are not supported.
  • Calling C code is not supported (no virtual "C" package).
  • Directives about the compiler, the linker, or embedding files are not supported.
  • Interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers.
  • Representation of types by reflect and printing values using %T may give different results between compiled mode and interpreted mode.
  • Interpreting computation intensive code is likely to remain significantly slower than in compiled mode.

Go modules are not supported yet. Until that, it is necessary to install the source into $GOPATH/src/github.com/traefik/yaegi to pass all the tests.

Contributing

Contributing guide.

License

Apache 2.0.

S
Description
Yaegi is Another Elegant Go Interpreter
Readme Apache-2.0 38 MiB
Languages
Go 98.4%
Shell 1.2%
GAP 0.2%
Makefile 0.2%