yaegi icon indicating copy to clipboard operation
yaegi copied to clipboard

Interpreter.Eval returns value from previous evaluation

Open firelizzard18 opened this issue 3 years ago • 0 comments

  1. Define a function symbol pkg.Fn (via (*Interpreter).Use) that does not return a value, e.g. func (interface{})
  2. Evaluate import "pkg" (via (*Interpreter).Eval)
    • Returns (*interface{})(nil)
  3. Evaluate str := "foobar"
    • Returns (string) "foobar"
  4. Evaluate pkg.Fn(1)
    • Returns (string) "foobar"

I would expect the result of step 4 to be nil, an invalid reflect.Value, or something similar. As far as I can tell, this only happens if pkg.Fn returns nothing and is defined via (*Interpreter).Use. Using Eval to define a function with no return does not act the same. If steps 3 and 4 are done as part of a single evaluation, the result is also (string) "foobar".

This is primarily concerning because I am using Yaegi as the backend for a VSCode notebook extension. In cases where the user evaluates a simple expression, I want to display the results. But for more complex statements, I don't. Particularly, I've been playing with database queries, and this behavior is causing my password to be displayed:

pw := vscode.Prompt(&vscode.PromptOptions{
	Prompt: "MySQL Password",
	Password: true,
})
db, err := sql.Open("mysql", "ethan:" + pw + "@/Test")
must(err)

// See "Important settings" section.
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)

When evaluated, the result is (string) "my password". Subsequent evaluations also may result in revealing my password, as in the example above.

I created a simple REPL to reproduce this:

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"reflect"

	"github.com/davecgh/go-spew/spew"
	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)

func main() {
	rOut, wOut := io.Pipe()
	rErr, wErr := io.Pipe()

	go scan("1>", rOut)
	go scan("2>", rErr)

	I := interp.New(interp.Options{
		Stdout: wOut,
		Stderr: wErr,
	})

	I.Use(stdlib.Symbols)

	I.Use(interp.Exports{
		"notebook": map[string]reflect.Value{
			"Show": reflect.ValueOf(func(v interface{}) {
				fmt.Printf(" > %s", spew.Sdump(v))
			}),
		},
	})

	s := bufio.NewScanner(os.Stdin)

	fmt.Print(": ")
	for s.Scan() {
		v, err := I.Eval(s.Text())
		if err != nil {
			fmt.Printf("!> %v\n", err)
		}
		if v.IsValid() {
			fmt.Printf("=> %s", spew.Sdump(v.Interface()))
		}
		fmt.Print(": ")
	}
}

func scan(prefix string, r io.Reader) {
	s := bufio.NewScanner(r)
	for s.Scan() {
		line := s.Text()
		fmt.Printf("%s %s", prefix, line)
	}
}
$ go run main.go
<: import "notebook"
=> (*interface {})(0xc0001bc890)(<nil>)
<: str := "foobar"
=> (string) (len=6) "foobar"
<: notebook.Show(1)
 > (int) 1
=> (string) (len=6) "foobar"

firelizzard18 avatar Mar 26 '21 21:03 firelizzard18