Skip to content
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

cmd/compile: package path of distinct types with unexported fields is the same #16616

Closed
icza opened this issue Aug 5, 2016 · 13 comments
Closed

Comments

@icza
Copy link

icza commented Aug 5, 2016

Go version and environment:
go version go1.6.3 windows/amd64

Issue:
If we have an exported function in a package, which has a parameter of anonymous struct type with unexported fields, e.g.:

package subplay

import "fmt"

func PrintAnonymous(v struct{ i int }) {
    fmt.Printf("PrintAnonymous: %d\n", v.i)
}

We can't call this function from another package, trying to pass an anonymous struct value (with matching fields), e.g.:

value := struct{ i int }{1}
subplay.PrintAnonymous(value)

We get a compile-time error:

cannot use value (type struct { i int }) as type struct { i int } in argument to subplay.PrintAnonymous

Understandable, as the fields of the anonymous struct are unexported.

It is also understandable that we can use reflection to create a zero value of this type, and call subplay.PrintAnonymous() with this:

v := reflect.ValueOf(subplay.PrintAnonymous)
paramt := v.Type().In(0)
v.Call([]reflect.Value{reflect.New(paramt).Elem()})

Which runs, and doesn't panic.

But now if I try to pass a reflect.Value of a value of my own anonymous struct (with matching, unexported fields), with non-zero values, it still runs without panic:

value := struct{ i int }{1}
v := reflect.ValueOf(subplay.PrintAnonymous)
v.Call([]reflect.Value{reflect.ValueOf(value)})

My question: is this normal? If yes, then this should be possible without using reflection, too. If without reflection the compile-time error is justified, then using reflection should result in runtime panic.

Inspired by this StackOverflow question.

@ianlancetaylor ianlancetaylor changed the title Possible security issue with anonymous struct + reflection reflect: assignability check does not check PkgPath of struct fields Aug 5, 2016
@ianlancetaylor ianlancetaylor added this to the Go1.8 milestone Aug 5, 2016
@icza
Copy link
Author

icza commented Aug 5, 2016

One note to the title change:

The reflect.Type for an anonymous struct will contain empty name and empty package path. So it may be package path is checked (I haven't looked into it), but even if it is, it will match (as both will have an empty string package path).

@ianlancetaylor
Copy link
Contributor

I'm not suggesting a PkgPath check for the struct itself, but rather for the fields of the struct. But I'm not sure I'm right--I'm looking further.

@ianlancetaylor
Copy link
Contributor

I think something is wrong in the export data generated by the compiler.

a.go:

package a

import "fmt"

func F(v struct { i int }) {
    fmt.Printf("F: %d\n", v.i)
}

main.go:

package main

import (
    "fmt"
    "reflect"

    "issue16616/a"
)

func main() {
    s := struct{i int}{1}
    v := reflect.ValueOf(a.F)
    fmt.Printf("%#v\n", reflect.ValueOf(s).Type().Field(0))
    fmt.Printf("%#v\n", v.Type().In(0).Field(0))
    v.Call([]reflect.Value{reflect.ValueOf(s)})
}

I would expect the PkgPath field of the first line to be main, and the PkgPath field of the second line to be a. But instead, with 1.6 I see this:

reflect.StructField{Name:"i", PkgPath:"issue16616/a", Type:(*reflect.rtype)(0x4b84e0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"i", PkgPath:"issue16616/a", Type:(*reflect.rtype)(0x4b84e0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
F: 1

and with 1.7 I see this:

reflect.StructField{Name:"i", PkgPath:"main", Type:(*reflect.rtype)(0x4adc40), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"i", PkgPath:"main", Type:(*reflect.rtype)(0x4adc40), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
F: 1

Current guess: the type descriptors for the two different types are being merged even though that should not happen.

CC @griesemer @crawshaw

@ianlancetaylor ianlancetaylor changed the title reflect: assignability check does not check PkgPath of struct fields cmd/compile: package path of distinct types with unexported fields is the same Aug 5, 2016
@icza
Copy link
Author

icza commented Aug 5, 2016

If I remember well, checking PkgPath for the fields' type is already implemented.

But for example PkgPath is empty for predeclared types (such as int). So in an anonymous type like in the example struct{ i int } it won't show any difference.

Storing PkgPath for an anonymous type itself could make a difference; not sure if this would be wise (as an anonymous type has no name-- it's the empty string).

@ianlancetaylor
Copy link
Contributor

gccgo gives a very interesting error message for this test case:

go1: error: Two symbols with same comdat_group are not linked by the same_comdat_group list.
__go_td_S1_iN3_inte/25 (__go_td_S1_iN3_inte) @0x7f2dca8dac80
  Type: variable definition analyzed
  Visibility: public weak comdat_group:__go_td_S1_iN3_inte one_only section:.rodata.__go_td_S1_iN3_inte (implicit_section) artificial
  previous sharing asm name: 16
  References: __go_type_hash_identity_descriptor/56 (addr)__go_type_equal_identity_descriptor/57 (addr)__go_td_S1_iN3_inte$gc/20 (addr)C12/21 (addr)C15/24 (addr)
  Referring: main.main/44 (addr)main.main/44 (addr)main.main/44 (addr)main.main/44 (addr)
  Availability: not-ready
  Varpool flags: initialized read-only
__go_td_S1_iN3_inte/16 (__go_td_S1_iN3_inte) @0x7f2dca8da800
  Type: variable definition analyzed
  Visibility: public weak comdat_group:__go_td_S1_iN3_inte one_only section:.rodata.__go_td_S1_iN3_inte (implicit_section) artificial
  next sharing asm name: 25
  References: __go_type_hash_identity_descriptor/52 (addr)__go_type_equal_identity_descriptor/53 (addr)__go_td_S1_iN3_inte$gc/2 (addr)C1/3 (addr)C9/15 (addr)
  Referring: C10/17 (addr)
  Availability: not-ready
  Varpool flags: initialized read-only
go1: internal compiler error: symtab_node::verify failed
0x708d3e symtab_node::verify_symtab_nodes()
    ../../gccgo3/gcc/symtab.c:1219
0x71b2a4 symtab_node::checking_verify_symtab_nodes()
    ../../gccgo3/gcc/cgraph.h:602
0x71b2a4 symbol_table::compile()
    ../../gccgo3/gcc/cgraphunit.c:2382
0x71d839 symbol_table::finalize_compilation_unit()
    ../../gccgo3/gcc/cgraphunit.c:2564
Please submit a full bug report,
with preprocessed source if appropriate.
Please include the complete backtrace with any bug report.
See <http://gcc.gnu.org/bugs.html> for instructions.

@ianlancetaylor
Copy link
Contributor

Again, I'm not talking about the PkgPath for struct { i int }. I'm talking about the PkgPath for the field i within struct { i int }. As you can see from the sample program above, that PkgPath is not the empty string. What is strange is that it is the same for the struct { i int } defined in the main package and in the imported package. That makes no sense.

@griesemer griesemer self-assigned this Aug 11, 2016
@griesemer griesemer modified the milestones: Go1.8Early, Go1.8 Aug 11, 2016
@griesemer
Copy link
Contributor

@ianlancetaylor The export data seems correct since in both 1.6 and 1.7 it is not possible to call a.F(s) (given your example). The reason is that the exported package for field i is indeed different for th different types. But it is strange reflection doesn't show that difference.

@griesemer
Copy link
Contributor

Simpler test case:

$ cat a.go
package a

var V struct { i int }

$ cat b.go 
package main

import (
    "./a"
    "reflect"
)

var V struct { i int }

func main() {
    println("   a.V.i", reflect.ValueOf(a.V).Type().Field(0).PkgPath)
    println("main.V.i", reflect.ValueOf(  V).Type().Field(0).PkgPath)
}

Running it (go tool compile a.go && go tool compile b.go && go tool link b.o && a.out) prints:

   a.V.i main
main.V.i main

The package path reported via reflect is wrong here as well. It appears to be correct in the export data.

@ianlancetaylor
Copy link
Contributor

CC @crawshaw

@griesemer
Copy link
Contributor

The types supplied to reflect.ValueOf for a.V and V are identical, which leads to identical results.

@griesemer
Copy link
Contributor

Preliminary analysis: It looks like the compiler canonicalizes unnamed types such as struct { i int } because it uses its string representation to identify the respective type information. Thus, the interfaces for both a.V and V have the same type information (which can also readily be seen from the assembly output generated by the compiler for b.go above).

A quick hack to fully qualify unexported field names in the compiler's fmt.go (lines 1576ff) leads to different type strings for the two structs and in return to different types encountered by reflect.Value. However it doesn't quite fix the problem yet (I'm probably missing another place where the field names are encoded).

Either way, the problem here is unrelated to export data but has to do with how unnamed type symbols are referenced.

Assigning to @crawshaw for more insight.

@griesemer griesemer assigned crawshaw and unassigned griesemer Aug 24, 2016
@crawshaw
Copy link
Member

@griesemer's analysis looks right to me. And it looks like this is not a new bug but the choice of which type is treated as canonical jumped around between releases. I'll see if I can fix it.

@gopherbot
Copy link

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

@golang golang locked and limited conversation to collaborators Aug 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

5 participants