Skip to content

reflect: Type.Method returns nil Type and invalid Func #15673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
reusee opened this issue May 13, 2016 · 19 comments
Closed

reflect: Type.Method returns nil Type and invalid Func #15673

reusee opened this issue May 13, 2016 · 19 comments
Milestone

Comments

@reusee
Copy link

reusee commented May 13, 2016

  1. What version of Go are you using (go version)?
    go version devel +be5782c Fri May 13 07:28:35 2016 +0000 linux/amd64
  2. What operating system and processor architecture are you using (go env)?
    GOARCH="amd64"
    GOBIN=""
    GOEXE=""
    GOHOSTARCH="amd64"
    GOHOSTOS="linux"
    GOOS="linux"
    GOPATH="/home/reus"
    GORACE=""
    GOROOT="/home/reus/go"
    GOTOOLDIR="/home/reus/go/pkg/tool/linux_amd64"
    CC="gcc"
    GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build351884329=/tmp/go-build -gno-record-gcc-switches"
    CXX="g++"
    CGO_ENABLED="1"
  3. What did you do?
package main

import (
    "fmt"
    "reflect"
)

type Foo struct{}

func (f *Foo) foo(arg struct{}) {}
func (f *Foo) bar()             {}

func main() {
    f := new(Foo)
    t := reflect.TypeOf(f)
    nMethods := t.NumMethod()
    for i := 0; i < nMethods; i++ {
        method := t.Method(i)
        fmt.Printf("%s %v %v\n", method.Name, method.Type, method.Func)
    }
}
  1. What did you expect to see?
    non-nil Type and Func field
  2. What did you see instead?
bar unc(*main.Foo) 0x401000
foo <nil> <invalid reflect.Value>
@reusee
Copy link
Author

reusee commented May 13, 2016

The result is different for method foo after adding an exported method Baz

package main

import (
    "fmt"
    "reflect"
)

type Foo struct{}

func (f *Foo) foo(arg struct{}) {}
func (f *Foo) bar()             {}
func (f *Foo) Baz(arg struct{}) {}

func main() {
    f := new(Foo)
    t := reflect.TypeOf(f)
    nMethods := t.NumMethod()
    for i := 0; i < nMethods; i++ {
        method := t.Method(i)
        fmt.Printf("%s %v %v\n", method.Name, method.Type, method.Func)
    }
}

outputs

Baz unc(*main.Foo, struct {}) 0x401000
bar unc(*main.Foo) 0x401000
foo unc(*main.Foo, struct {}) 0x401000

@crawshaw
Copy link
Member

There was a deliberate change here, to remove some information for unexported methods. (Ideally unexported methods would never be seen in the reflect package.) See https://golang.org/cl/21033. However your examples have uncovered at least one bug, maybe two, so I'll dig into this today.

@bradfitz bradfitz added this to the Go1.7 milestone May 13, 2016
@gopherbot
Copy link
Contributor

CL https://golang.org/cl/23103 mentions this issue.

gopherbot pushed a commit that referenced this issue May 13, 2016
By picking up a spurious tFlagExtraStar, the method type was printing
as unc instead of func.

Updates #15673

Change-Id: I0c2c189b99bdd4caeb393693be7520b8e3f342bf
Reviewed-on: https://go-review.googlesource.com/23103
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
@rsc
Copy link
Contributor

rsc commented May 18, 2016

@crawshaw, at tip this program now prints:

bar func(*main.Foo) 0x2000
foo <nil> <invalid reflect.Value>

When we talked about dropping this info I was assuming that NumMethod would return a smaller number, so that the unexported methods are completely invisible in the reflect info. Right now they are half-visible, which is kind of the worst of both worlds. Is it possible to make NumMethod and Method(i) only access exported methods while keeping the necessary unexported state around for interface satisfaction and reflect.Type.Implements?

@rsc rsc changed the title reflect: Type.Method() returns nil Type and invalid Func reflect: Type.Method returns nil Type and invalid Func May 18, 2016
@crawshaw
Copy link
Member

The easy and safe way to do that is to filter in the reflect package, which is now possible. I'm out today, but will send a CL tomorrow.

@rsc
Copy link
Contributor

rsc commented May 18, 2016

Thanks.

@gopherbot
Copy link
Contributor

CL https://golang.org/cl/23253 mentions this issue.

@reusee
Copy link
Author

reusee commented May 21, 2016

go version devel +1f8d276
The first example program now prints

bar func(*main.Foo) 0x401000

Is it expected? The unexported method bar is not hidden.

@reusee
Copy link
Author

reusee commented May 21, 2016

By adding the following method

func (f *Foo) Baz(struct{})     {}

it prints all methods

Baz func(*main.Foo, struct {}) 0x401000
bar func(*main.Foo) 0x401000
foo func(*main.Foo, struct {}) 0x401000

@crawshaw
Copy link
Member

That's right, and I think that's as good as we can get for 1.7.

If I were starting from scratch, I don't think I would surface unexported methods via reflect ever. However they are there in Go 1, and have one valid use: determining interface satisfaction when using reflect inside a package that declares an interface with an unexported method.

So I believe we should keep that case working, which means surfacing any method that might meet that requirement. Unfortunately we do not have perfect information about that yet, so we are conservatively removing some methods we know cannot satisfy an interface. The change in https://golang.org/cl/21087, which won't make it for 1.7 gets even closer, and removes your bar example. Hopefully in 1.8 I can modify it to remove even more methods.

@neild
Copy link
Contributor

neild commented May 24, 2016

reflect.Type.Method output now exposes some unexported methods some of the time. (See #15824)

Either reflect.Type.Method should expose all unexported methods, or it should expose none of them. Anything else is just too confusing. What reflect.Type.Method returns should be a distinct question from reflect.Type.Implements's behavior--the former can elide unexported methods while the latter considers them.

I'm curious how changing the output of reflect.Type.Method is consistent with the compatibility guarantee.

@dsnet
Copy link
Member

dsnet commented May 24, 2016

@crawshaw

In #15673, @rsc said the following:

When we talked about dropping this info I was assuming that NumMethod would return a smaller number, so that the unexported methods are completely invisible in the reflect info. Right now they are half-visible, which is kind of the worst of both worlds. Is it possible to make NumMethod and Method(i) only access exported methods while keeping the necessary unexported state around for interface satisfaction and reflect.Type.Implements?

Several questions:
1.) Although I personally agree with removing unexported methods from what the reflect package reports, isn't this a violation of the compatibility agreement? Yes, it wasn't particularly well documented which methods Method would report, but I'm certain some people depend on the ability to get access to unexported methods. Where is the discussion regarding the removal?
2.) From Russ' comment, he mentions that "unexported methods are [to be] completely invisible in the reflect info". Currently, that is not the case since there are some situations where the unexported methods are visible (which means it is not completely invisible). Furthermore, the fact that a line regarding some seemingly unrelated type causes the unexported method to be visible seems to me to be very strange and surprising behavior.
3.) From Russ' comment, he mentions that "keeping the necessary unexported state around for interface satisfaction and reflect.Type.Implements". Doesn't that mean that reflect.Type.Implements should still be working because it has information about the unexported methods, but not that Method or NumMethod should be reporting them?

@crawshaw
Copy link
Member

I don't see this as covered by the compatibility agreement because unexported method reflect data is of questionable value (and it was entirely undocumented and indeed a surprise to me when I found it).

As to your third point and Damien's, I'd be willing to accept that and remove all the method info for unexported methods from the API. I too view the current state as confusing, but it's not completely broken. We could finish the removal in 1.8.

@neild
Copy link
Contributor

neild commented May 24, 2016

The change to occasionally remove unexported methods from reflect.Type.Method breaks at least one test here. It's probably not a very good test and I'm fine with fixing it, but it does demonstrate that this is a backwards incompatible change that affects some users. I agree that they seem of questionable value.

I do feel quite strongly that if they are to be dropped from reflect.Type.Method, they should be dropped entirely and the behavior documented.

@dsnet
Copy link
Member

dsnet commented May 24, 2016

I also feel strongly, that if we alter the behavior, it should be all or none, and that we shouldn't delay that until 1.8. That is, 1.7 should not be in this weird partial state it currently is in.

@randall77
Copy link
Contributor

I agree with Joe, if reflect used to return unexported methods reliably (did it?), I think we should preserve that behavior. People could be depending on it. Returning only exported methods might have to wait until Go 2.

If you ever try to call such a method, it fails with an unexported field error. So I don' think we need the actual implementation of that function to survive in the binary, just enough info to construct a reflect.Method for the unexported function (pkg, name, type, and a func stub that is the canonical "unexported" func).

@crawshaw
Copy link
Member

It did, but I dispute that someone would depend on it in a useful way, beyond rigid unit tests. And keeping them means keeping their type information, which means following those types and keeping everything attached to them.

I'll cut a CL to remove them all.

@dsnet dsnet reopened this May 24, 2016
@gopherbot
Copy link
Contributor

CL https://golang.org/cl/23410 mentions this issue.

gopherbot pushed a commit that referenced this issue May 25, 2016
Also remove some of the now unnecessary corner case handling and
tests I've been adding recently for unexported method data.

For #15673

Change-Id: Ie0c7b03f2370bbe8508cdc5be765028f08000bd7
Reviewed-on: https://go-review.googlesource.com/23410
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
@gopherbot
Copy link
Contributor

CL https://golang.org/cl/23430 mentions this issue.

gopherbot pushed a commit that referenced this issue May 25, 2016
For #15673

Change-Id: I3ce8d4016854d41860c5a9f05a54cda3de49f337
Reviewed-on: https://go-review.googlesource.com/23430
Reviewed-by: Ian Lance Taylor <iant@golang.org>
@golang golang locked and limited conversation to collaborators May 26, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants