open2b/scriggo

compiler,runtime: panic in case of Scriggo type as map key when calling builtin 'delete'

Closed this issue · 5 comments

cannot delete from a map when the key is a complex type.

Consider the following snippet:

package main

type key struct {
        X int
        Y int
}

func main() {
        println("starting population")
        mpbasic := map[int]int{}
        mpcomplex := map[key]int{}
        for i := 0; i < 256; i++ {
                mpbasic[i] = i * 2
                mpcomplex[key{X: i, Y: i + 1}] = i * 2
        }
        println("starting delete simple")
        for k := range mpbasic {
                delete(mpbasic, k)
        }
        println("starting delete complex")
        for k := range mpcomplex {
                if mpcomplex[k] > 2 {
                        delete(mpcomplex, k)
                }
        }
        println("done")
}

When run using the GC we get the following output (runs to completion):

starting population
starting delete simple
starting delete complex
done

When run using scriggo we get the following output with a panic:

panic: reflect.Value.SetMapIndex: value of type types.emptyInterfaceProxy is not assignable to type struct { X int; Y int }

goroutine 1 [running]:
github.com/open2b/scriggo/internal/runtime.(*VM).Run(0xc0000e4240, 0xc0000a5520, 0xc00009e970, 0x0, 0x0, 0x0, 0x0, 0x0)
	/home/kris/mygo/pkg/mod/github.com/open2b/scriggo@v0.53.2-0.20211019155432-8c493a4aa1f8/internal/runtime/vm.go:161 +0x169
github.com/open2b/scriggo.(*Program).Run(0xc0000a1380, 0x0, 0x0, 0xc0000a1380)
	/home/kris/mygo/pkg/mod/github.com/open2b/scriggo@v0.53.2-0.20211019155432-8c493a4aa1f8/programs.go:143 +0xd6
main.main()
	/tmp/scriggo_test/main.go:19 +0x13f

I am building off of the tip of master (commit 8c493a4)

It appears that the range iterator on a map hands back some sort of pointer when using complex map key types, but the delete function cannot handle that pointer. Using native types for the key this completes fine.

Complete test program

package main

import (
        "fmt"

        "github.com/open2b/scriggo"
)

func main() {
        // Create a file system with the file of the program to run.
        fsys := scriggo.Files{"main.go": []byte(src)}

        // Build the program.
        program, err := scriggo.Build(fsys, nil)
        if err != nil {
                panic(err)
        }
        // Run the program.
        err = program.Run(nil)
        if err != nil {
                fmt.Println("RUN ERROR", err)
        }
        fmt.Println("DONE")
}

const src = `
        package main

        type key struct {
                X int
                Y int
        }

        func main() {
            println("starting population")
            mpbasic := map[int]int{}
            mpcomplex := map[key]int{}
            for i := 0; i < 256; i++ {
                mpbasic[i] = i*2
                mpcomplex[key{X: i, Y: i+1}] = i*2
            }
            println("starting delete simple")
            for k := range mpbasic {
                delete(mpbasic, k)
            }
            println("starting delete complex")
            for k := range mpcomplex {
                if mpcomplex[k] > 2 {
                        delete(mpcomplex, k)
                }
            }
            println("done")
        }

`

This is the minimal program that reproduces the issue

package main

type T struct {}

func main() {
	delete(map[T]int{}, T{})
}

This is the minimal program that reproduces the issue

package main

type T struct {}

func main() {
	delete(map[T]int{}, T{})
}

This code seems to fail for another reason (still not sure, i'm investigating) related to this code:

key := em.emitExpr(args[1], emptyInterfaceType)

I opened a new issue for that.

After investigating this issue, it turned out that it is strictly related to #913, which will be closed in favour of this.

Consider this code:

package main

type T int

func main() {
	delete(map[T]int{}, T(0))
}

the runtime reads the key from a general registers:

// Delete
case OpDelete:
vm.general(a).SetMapIndex(vm.general(b), reflect.Value{})

So:

  • if the key is not typified, it remains in the int registers and thus it's not found
  • if the key is typified, it is moved to the general register but is is converted to a proxy, so it cannot be passed to the SetMapIndex method of the reflect.

wow, great work guys! Thank you very much.