Go: spec: struct can implement an interface with an inherited method but attempting to call that method can cause an unexpected panic

Created on 16 Jun 2020  路  7Comments  路  Source: golang/go

What version of Go are you using (go version)?

$ go version
go version go1.14.4 linux/amd64

Does this issue reproduce with the latest release?

I believe go1.14.4 is the latest stable release, so yes.

What operating system and processor architecture are you using (go env)?

go env Output

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/finnb/.cache/go-build"
GOENV="/home/finnb/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/finnb/go:/home/finnb/git/finnbear/mazean"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/snap/go/5830"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/snap/go/5830/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build362984802=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Playground link: https://play.golang.org/p/Xf3mskprGZV

package main

import (
    "fmt"
)

type Nilable interface {
    IsNil() bool
}

type Child struct {

}

func (child *Child) IsNil() bool {
    return child == nil
}

type Parent struct {
    Child
}

func main() {
    var something Nilable

    var child *Child
    something = child
    fmt.Println(something.IsNil())

    var parent *Parent
    something = parent
    fmt.Println(something.IsNil()) // Panic here
}

What did you expect to see?

true

What did you see instead?

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4919f5]

goroutine 1 [running]:
main.(*Parent).IsNil(0x0, 0xc0000b6008)
    <autogenerated>:1 +0x5
main.main()
    /tmp/sandbox080043792/prog.go:36 +0xad

I suspect what is happening is that calling IsNil on parent acts on parent.Child, thus dereferencing Child from a nil pointer parent, but this happens silently. I think one of the following should be done:
1) Assuming my theory is correct, the panic message should be improved to not act as if the issue occurred in the (*Child) IsNil receiver but in the intermediate step of accessing parent.Child
2) The documentation should be updated to warn about this behavior
3) In the unlikely event that this is a bug in Go, it should be fixed

Documentation NeedsInvestigation

All 7 comments

@griesemer @ianlancetaylor

This is intended behavior. Dereferencing a nil pointer in order to access an embedded field panics.

But in a quick glance at the language spec I don't see that documented anywhere, so I guess we ought to have an additional sentence in the spec somewhere.

I think the panic message is as good as it is going to get. The IsNil method is promoted to become a method of *Parent, and that is where the crash happens. There isn't an intermediate step that could appear in the stack trace or the panic message.

I now realize my statement

the panic message should be improved to not act as if the issue occurred in the (*Child) IsNil receiver

was wrong, since the panic actually states the location was in main.(*Parent).IsNil and it does say <autogenerated> which is a decent hint.

An additional line in the spec/doc would be nice though.

Also, how is it even possible that Parent *Parent can implement Nilable if it contains Child not *Child and only *Child has IsNil? I'm guessing that the receiver acts like a pointer receiver but there is no actual *Child?

Parent doesn't implement Nilable, *Parent does.

related: https://github.com/golang/go/issues/32021 https://github.com/golang/go/issues/32035

Parent doesn't implement Nilable, *Parent does.

I misspoke, my question was directed at why *Parent can implement Nilable. I have to do more research into whether *Parent has an internal/pseudo Child (like Parent does), *Child (which would be weird, I think), or something else...

I believe this is exactly the case described in #18617.
(Note that that issue is a proposal for a backward-incompatible change to the language.)

@finnbear
By the current rules,

  • type struct{T} gets all the methods of type T,
  • type *struct{T}, type struct{*T}, and type *struct{*T} get all the methods of type *T (as the method set of *T is always a super-set of T, so the three types also get all the methods of T)
Was this page helpful?
0 / 5 - 0 ratings