Trying to add an `InterfaceOf` to `reflect` package but segmentation violation returned

206 views
Skip to first unread message

Wenhua Shi

unread,
May 23, 2022, 12:51:14 PM5/23/22
to golang-nuts
I'm to create an object (whatever it is) satisfying certain interface dynamically. There's no way I can do it so I'm thinking maybe I can add a func in `reflect` package to cover my need.

The `reflect.StructOf`  func creates a struct type but without adding methods which gives a good example. I imitated and created an `InterfaceOf` func. However the following error appeared,

```
unexpected fault address 0xc0000b8120 fatal error: fault [signal SIGSEGV: segmentation violation code=0x2 addr=0xc0000b8120 pc=0xc0000b8120]
```

Here is the file I added into the `reflect` package,

```golang
package reflect

import (
    "sort"
    "unsafe"
)

type InterfaceMethod struct {
    Name string
    Func Value
}

func InterfaceOf(funcs []InterfaceMethod) Value {
    if len(funcs) == 0 {
        return New(TypeOf("Interface {}"))
    }

    var (
        repr     = []byte("struct { /* non-invertible */ }")
        hash     = fnv1(0, []byte("struct { /* non-invertible */ }")...)
        size     uintptr
        typalign uint8
        methods  []method
    )

    // add sorted methods here
    {
        for idx := range funcs {
            if funcs[idx].Func.Type().Kind() != Func {
                panic("funcs contains non-func")
            }
            c := funcs[idx].Name[0]
            if !('A' <= c && c <= 'Z') {
                panic("funcs contains unexported func")
            }
        }

        sort.Slice(funcs, func(i, j int) bool {
            return funcs[i].Name < funcs[j].Name
        })

        for idx := range funcs {
            ft := funcs[idx].Func.Type().common()
            ft.uncommon()

            ifn := funcs[idx].Func
            methods = append(methods, method{
                name: resolveReflectName(newName(funcs[idx].Name, "", true)),
                mtyp: resolveReflectType(ft),
                ifn:  resolveReflectText(unsafe.Pointer(&ifn)),
                tfn:  resolveReflectText(unsafe.Pointer(&ifn)),
            })
        }
    }

    var typ *structType
    var ut *uncommonType

    {
        // A *rtype representing a struct is followed directly in memory by an
        // array of method objects representing the methods attached to the
        // struct. To get the same layout for a run time generated type, we
        // need an array directly following the uncommonType memory.
        // A similar strategy is used for funcTypeFixed4, ...funcTypeFixedN.
        tt := New(StructOf([]StructField{
            {Name: "S", Type: TypeOf(structType{})},
            {Name: "U", Type: TypeOf(uncommonType{})},
            {Name: "M", Type: ArrayOf(len(methods), TypeOf(methods[0]))},
        }))

        typ = (*structType)(tt.Elem().Field(0).Addr().UnsafePointer())
        ut = (*uncommonType)(tt.Elem().Field(1).Addr().UnsafePointer())

        copy(tt.Elem().Field(2).Slice(0, len(methods)).Interface().([]method), methods)
    }

    ut.mcount = uint16(len(methods))
    ut.xcount = ut.mcount // we only have exported methods
    ut.moff = uint32(unsafe.Sizeof(uncommonType{}))
    str := string(repr)

    // Round the size up to be a multiple of the alignment.
    size = align(size, uintptr(typalign))

    // Make the struct type.
    var istruct any = struct{}{}
    prototype := *(**structType)(unsafe.Pointer(&istruct))
    *typ = *prototype

    typ.str = resolveReflectName(newName(str, "", false))
    typ.tflag = 0 // TODO: set tflagRegularMemory
    typ.hash = hash
    typ.size = size
    typ.ptrdata = typeptrdata(typ.common())
    typ.align = typalign
    typ.fieldAlign = typalign
    typ.ptrToThis = 0
    typ.tflag |= tflagUncommon

    {
        typ.kind &^= kindGCProg
        bv := new(bitVector)
        addTypeBits(bv, 0, typ.common())
        if len(bv.data) > 0 {
            typ.gcdata = &bv.data[0]
        }
    }

    typ.equal = nil

    typ.kind &^= kindDirectIface

    return Zero(&typ.rtype)
}

```

And a corresponding testing file,

```golang
package main

import (
    "fmt"
    "reflect"
    "testing"
)

func TestInterfaceOf(t *testing.T) {
    type AliceInterface interface {
        BobMethod() (error, string)
    }
    Alice := reflect.TypeOf((*AliceInterface)(nil)).Elem()
    Bob := Alice.Method(0)

    Carol := reflect.MakeFunc(Bob.Type, func(args []reflect.Value) []reflect.Value {
        fmt.Println("[wow]")
        nilError := reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())
        return []reflect.Value{nilError, reflect.ValueOf("haha")}
    })

    Dan := reflect.InterfaceOf([]reflect.InterfaceMethod{
        {
            Name: "BobMethod",
            Func: Carol,
        },
    })

    Dan.Method(0).Call([]reflect.Value{}) // panic

    dan := Dan.Interface().(AliceInterface)
    dan.BobMethod() // also panic

}
```

Ian Lance Taylor

unread,
May 23, 2022, 6:03:49 PM5/23/22
to Wenhua Shi, golang-nuts
On Mon, May 23, 2022 at 9:51 AM Wenhua Shi <marc...@gmail.com> wrote:
>
> I'm to create an object (whatever it is) satisfying certain interface dynamically. There's no way I can do it so I'm thinking maybe I can add a func in `reflect` package to cover my need.
>
> The `reflect.StructOf` func creates a struct type but without adding methods which gives a good example. I imitated and created an `InterfaceOf` func. However the following error appeared,
>
> ```
> unexpected fault address 0xc0000b8120 fatal error: fault [signal SIGSEGV: segmentation violation code=0x2 addr=0xc0000b8120 pc=0xc0000b8120]
> ```

The full stack trace should show where the problem is happening. What
is the full stack trace, and what line is causing the fault address?


> func InterfaceOf(funcs []InterfaceMethod) Value {
> if len(funcs) == 0 {
> return New(TypeOf("Interface {}"))

I don't understand this line. TypeOf doesn't take Go code. It takes
a Go value and returns the type of that value. Passing "interface {}"
to TypeOf is not going to give you an interface type, it's going to
give you a string type, because "interface {}" is a string. If you
pass a string to reflect.TypeOf, then you get a string type. The
actual contents of the string don't matter.

Perhaps fixing that misunderstanding will help you move forward.

Ian

Wenhua Shi

unread,
May 23, 2022, 8:14:43 PM5/23/22
to golang-nuts
>  return New(TypeOf("Interface {}"))
Yes, it gives a string. It doesn't matter here since it's only an edge case.

>  What is the full stack trace
Here it is,
```
=== RUN   TestInterfaceOf
unexpected fault address 0xc0000b8090
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0xc0000b8090 pc=0xc0000b8090]

goroutine 18 [running]:
runtime.throw({0x5d79de?, 0x1?})
    /home/debian/go/src/runtime/panic.go:992 +0x71 fp=0xc00008d768 sp=0xc00008d738 pc=0x435e91
runtime.sigpanic()
    /home/debian/go/src/runtime/signal_unix.go:825 +0x305 fp=0xc00008d7b8 sp=0xc00008d768 pc=0x44c265
runtime.call16(0xc0000a2630, 0xc0000a8088, 0x0, 0x0, 0x0, 0x8, 0xc00008dce8)
    /home/debian/go/src/runtime/asm_amd64.s:701 +0x49 fp=0xc00008d7d8 sp=0xc00008d7b8 pc=0x466a49
runtime.reflectcall(0xc0000a6120?, 0x1?, 0x1?, 0xc0278?, 0xc0?, 0x1?, 0x1?)
    <autogenerated>:1 +0x3c fp=0xc00008d818 sp=0xc00008d7d8 pc=0x46ae1c
reflect.Value.call({0xc0000ae380?, 0x75b600?, 0x0?}, {0x5d7760, 0x4}, {0xc00008dee0, 0x0, 0x4cb9ae?})
    /home/debian/go/src/reflect/value.go:556 +0x845 fp=0xc00008de08 sp=0xc00008d818 pc=0x4c5885
    *** call(frametype, fn, stackArgs, uint32(frametype.size), uint32(abi.retOffset), uint32(frameSize), &regArgs) ***
reflect.Value.Call({0xc0000ae380?, 0x75b600?, 0x1?}, {0xc00008dee0, 0x0, 0x0})
    /home/debian/go/src/reflect/value.go:339 +0xbf fp=0xc00008de80 sp=0xc00008de08 pc=0x4c4dff
    *** return v.call("Call", in) ***
testmod/pkg/testproject.TestInterfaceOf(0x0?)
    /home/debian/projects/testmod/pkg/testproject/testproject_test.go:45 +0x154 fp=0xc00008df70 sp=0xc00008de80 pc=0x58be54
    *** Dan.Method(0).Call([]reflect.Value{}) // panic ***
testing.tRunner(0xc000083520, 0x5e6f18)
    /home/debian/go/src/testing/testing.go:1439 +0x102 fp=0xc00008dfc0 sp=0xc00008df70 pc=0x4fc602
testing.(*T).Run.func1()
    /home/debian/go/src/testing/testing.go:1486 +0x2a fp=0xc00008dfe0 sp=0xc00008dfc0 pc=0x4fd4aa
runtime.goexit()
    /home/debian/go/src/runtime/asm_amd64.s:1571 +0x1 fp=0xc00008dfe8 sp=0xc00008dfe0 pc=0x4685e1
created by testing.(*T).Run
    /home/debian/go/src/testing/testing.go:1486 +0x35f

goroutine 1 [chan receive]:
testing.(*T).Run(0xc000083380, {0x5da04c?, 0x4fbfe5?}, 0x5e6f18)
    /home/debian/go/src/testing/testing.go:1487 +0x37a
testing.runTests.func1(0xc0000a2420?)
    /home/debian/go/src/testing/testing.go:1839 +0x6e
testing.tRunner(0xc000083380, 0xc000093cd8)
    /home/debian/go/src/testing/testing.go:1439 +0x102
testing.runTests(0xc0000c21e0?, {0x722ae0, 0x2, 0x2}, {0x7fe2dfd795b8?, 0x40?, 0x72b620?})
    /home/debian/go/src/testing/testing.go:1837 +0x457
testing.(*M).Run(0xc0000c21e0)
    /home/debian/go/src/testing/testing.go:1719 +0x5d9
main.main()
    _testmain.go:49 +0x1aa
FAIL    testmod/pkg/testproject 0.005s
```

Ian Lance Taylor

unread,
May 23, 2022, 8:46:46 PM5/23/22
to Wenhua Shi, golang-nuts
On Mon, May 23, 2022 at 5:14 PM Wenhua Shi <marc...@gmail.com> wrote:
>
> > return New(TypeOf("Interface {}"))
> Yes, it gives a string. It doesn't matter here since it's only an edge case.
>
> > What is the full stack trace

Thanks.

I don't understand what is going on here:


for idx := range funcs {
ft := funcs[idx].Func.Type().common()
ft.uncommon()

ifn := funcs[idx].Func
methods = append(methods, method{
name: resolveReflectName(newName(funcs[idx].Name, "", true)),
mtyp: resolveReflectType(ft),
ifn: resolveReflectText(unsafe.Pointer(&ifn)),
tfn: resolveReflectText(unsafe.Pointer(&ifn)),
})
}

It looks like ifn will be type reflect.Value. But you are setting ifn
to the address of that Value. That doesn't make sense to me. ifn and
tfn need to refer to executable code.

Ian

Wenhua Shi

unread,
May 24, 2022, 4:48:44 AM5/24/22
to golang-nuts
I'm really confusing.

> That doesn't make sense to me. ifn and tfn need to refer to executable code.

>  ifn and tfn need to refer to executable code.
I followed your advice and changed the code to `tfn:  resolveReflectText(ifn.ptr)`, but it still gave the same error.

After I changed the `methodReceiver` func in `value.go` file, calling `Dan.Method(0).Call([]reflect.Value{})` didn't panic any more,
```
ifn := v.typ.textOff(m.ifn)
fn = unsafe.Pointer(&ifn)
```
to 
```
fn := v.typ.textOff(m.ifn)
```
Why is `methodReceiver` taking address of `ifn` considering ifn is already a pointer to the function?

Ian Lance Taylor

unread,
May 24, 2022, 5:47:23 PM5/24/22
to Wenhua Shi, golang-nuts
On Tue, May 24, 2022 at 1:48 AM Wenhua Shi <marc...@gmail.com> wrote:
>
> I'm really confusing.
>
> > That doesn't make sense to me. ifn and tfn need to refer to executable code.
> The code is copied from here https://github.com/golang/go/blob/0da4dbe2322eb3b6224df35ce3e9fc83f104762b/src/reflect/type.go#L2368 , why is it working here?

Sorry, I see what you mean. I'm not sure what is going on here and I
don't have the time to dig into it in detail myself. Perhaps someone
else will take a look.

Ian


> > ifn and tfn need to refer to executable code.
> I followed your advice and changed the code to `tfn: resolveReflectText(ifn.ptr)`, but it still gave the same error.
>
> After I changed the `methodReceiver` func in `value.go` file, calling `Dan.Method(0).Call([]reflect.Value{})` didn't panic any more,
> ```
> ifn := v.typ.textOff(m.ifn)
> fn = unsafe.Pointer(&ifn)
> ```
> to
> ```
> fn := v.typ.textOff(m.ifn)
> ```
> Why is `methodReceiver` taking address of `ifn` considering ifn is already a pointer to the function?
>
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/b773eb03-8b01-4a8f-a979-0451712b7e35n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages