Skip to content

Commit 40bdc56

Browse files
committed
go/types: resolve cgo base type names
When associating methods with their receiver base, we need to implement the same indirection through Cgo types as is done for selector expressions. This fixes a bug where methods declared on aliases of Cgo types were not associated with their receiver. While porting to types2, align the types2 testFiles helper with the go/types implementation. In order to avoid call-site bloat, switch to an options pattern for configuring the Config used to type-check. Fixes #59944 Change-Id: Id14101f01c122b6c856ae5453bd00ec07e83f414 Reviewed-on: https://go-review.googlesource.com/c/go/+/493877 Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent f81c885 commit 40bdc56

File tree

6 files changed

+298
-92
lines changed

6 files changed

+298
-92
lines changed

src/cmd/compile/internal/types2/check_test.go

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"internal/testenv"
3838
"os"
3939
"path/filepath"
40+
"reflect"
4041
"regexp"
4142
"strconv"
4243
"strings"
@@ -50,12 +51,14 @@ var (
5051
verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual")
5152
)
5253

53-
func parseFiles(t *testing.T, filenames []string, mode syntax.Mode) ([]*syntax.File, []error) {
54+
func parseFiles(t *testing.T, filenames []string, srcs [][]byte, mode syntax.Mode) ([]*syntax.File, []error) {
5455
var files []*syntax.File
5556
var errlist []error
5657
errh := func(err error) { errlist = append(errlist, err) }
57-
for _, filename := range filenames {
58-
file, err := syntax.ParseFile(filename, errh, nil, mode)
58+
for i, filename := range filenames {
59+
base := syntax.NewFileBase(filename)
60+
r := bytes.NewReader(srcs[i])
61+
file, err := syntax.Parse(base, r, errh, nil, mode)
5962
if file == nil {
6063
t.Fatalf("%s: %s", filename, err)
6164
}
@@ -83,30 +86,10 @@ func absDiff(x, y uint) uint {
8386
return x - y
8487
}
8588

86-
// Note: parseFlags is identical to the version in go/types which is
87-
// why it has a src argument even though here it is always nil.
88-
89-
// parseFlags parses flags from the first line of the given source
90-
// (from src if present, or by reading from the file) if the line
91-
// starts with "//" (line comment) followed by "-" (possibly with
92-
// spaces between). Otherwise the line is ignored.
93-
func parseFlags(filename string, src []byte, flags *flag.FlagSet) error {
94-
// If there is no src, read from the file.
95-
const maxLen = 256
96-
if len(src) == 0 {
97-
f, err := os.Open(filename)
98-
if err != nil {
99-
return err
100-
}
101-
102-
var buf [maxLen]byte
103-
n, err := f.Read(buf[:])
104-
if err != nil {
105-
return err
106-
}
107-
src = buf[:n]
108-
}
109-
89+
// parseFlags parses flags from the first line of the given source if the line
90+
// starts with "//" (line comment) followed by "-" (possibly with spaces
91+
// between). Otherwise the line is ignored.
92+
func parseFlags(src []byte, flags *flag.FlagSet) error {
11093
// we must have a line comment that starts with a "-"
11194
const prefix = "//"
11295
if !bytes.HasPrefix(src, []byte(prefix)) {
@@ -117,14 +100,24 @@ func parseFlags(filename string, src []byte, flags *flag.FlagSet) error {
117100
return nil // comment doesn't start with a "-"
118101
}
119102
end := bytes.Index(src, []byte("\n"))
103+
const maxLen = 256
120104
if end < 0 || end > maxLen {
121105
return fmt.Errorf("flags comment line too long")
122106
}
123107

124108
return flags.Parse(strings.Fields(string(src[:end])))
125109
}
126110

127-
func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
111+
// testFiles type-checks the package consisting of the given files, and
112+
// compares the resulting errors with the ERROR annotations in the source.
113+
//
114+
// The srcs slice contains the file content for the files named in the
115+
// filenames slice. The colDelta parameter specifies the tolerance for position
116+
// mismatch when comparing errors. The manual parameter specifies whether this
117+
// is a 'manual' test.
118+
//
119+
// If provided, opts may be used to mutate the Config before type-checking.
120+
func testFiles(t *testing.T, filenames []string, srcs [][]byte, colDelta uint, manual bool, opts ...func(*Config)) {
128121
if len(filenames) == 0 {
129122
t.Fatal("no source files")
130123
}
@@ -133,11 +126,11 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
133126
flags := flag.NewFlagSet("", flag.PanicOnError)
134127
flags.StringVar(&conf.GoVersion, "lang", "", "")
135128
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
136-
if err := parseFlags(filenames[0], nil, flags); err != nil {
129+
if err := parseFlags(srcs[0], flags); err != nil {
137130
t.Fatal(err)
138131
}
139132

140-
files, errlist := parseFiles(t, filenames, 0)
133+
files, errlist := parseFiles(t, filenames, srcs, 0)
141134

142135
pkgName := "<no package>"
143136
if len(files) > 0 {
@@ -165,6 +158,11 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
165158
}
166159
errlist = append(errlist, err)
167160
}
161+
162+
for _, opt := range opts {
163+
opt(&conf)
164+
}
165+
168166
conf.Check(pkgName, files, nil)
169167

170168
if listErrors {
@@ -173,16 +171,10 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
173171

174172
// collect expected errors
175173
errmap := make(map[string]map[uint][]syntax.Error)
176-
for _, filename := range filenames {
177-
f, err := os.Open(filename)
178-
if err != nil {
179-
t.Error(err)
180-
continue
181-
}
182-
if m := syntax.CommentMap(f, regexp.MustCompile("^ ERRORx? ")); len(m) > 0 {
174+
for i, filename := range filenames {
175+
if m := syntax.CommentMap(bytes.NewReader(srcs[i]), regexp.MustCompile("^ ERRORx? ")); len(m) > 0 {
183176
errmap[filename] = m
184177
}
185-
f.Close()
186178
}
187179

188180
// match against found errors
@@ -280,6 +272,13 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
280272
}
281273
}
282274

275+
// boolFieldAddr(conf, name) returns the address of the boolean field conf.<name>.
276+
// For accessing unexported fields.
277+
func boolFieldAddr(conf *Config, name string) *bool {
278+
v := reflect.Indirect(reflect.ValueOf(conf))
279+
return (*bool)(v.FieldByName(name).Addr().UnsafePointer())
280+
}
281+
283282
// TestManual is for manual testing of a package - either provided
284283
// as a list of filenames belonging to the package, or a directory
285284
// name containing the package files - after the test arguments
@@ -314,7 +313,7 @@ func TestManual(t *testing.T) {
314313
}
315314
testDir(t, filenames[0], 0, true)
316315
} else {
317-
testFiles(t, filenames, 0, true)
316+
testPkg(t, filenames, 0, true)
318317
}
319318
}
320319

@@ -351,7 +350,7 @@ func testDirFiles(t *testing.T, dir string, colDelta uint, manual bool) {
351350
testDir(t, path, colDelta, manual)
352351
} else {
353352
t.Run(filepath.Base(path), func(t *testing.T) {
354-
testFiles(t, []string{path}, colDelta, manual)
353+
testPkg(t, []string{path}, colDelta, manual)
355354
})
356355
}
357356
}
@@ -370,6 +369,18 @@ func testDir(t *testing.T, dir string, colDelta uint, manual bool) {
370369
}
371370

372371
t.Run(filepath.Base(dir), func(t *testing.T) {
373-
testFiles(t, filenames, colDelta, manual)
372+
testPkg(t, filenames, colDelta, manual)
374373
})
375374
}
375+
376+
func testPkg(t *testing.T, filenames []string, colDelta uint, manual bool) {
377+
srcs := make([][]byte, len(filenames))
378+
for i, filename := range filenames {
379+
src, err := os.ReadFile(filename)
380+
if err != nil {
381+
t.Fatalf("could not read %s: %v", filename, err)
382+
}
383+
srcs[i] = src
384+
}
385+
testFiles(t, filenames, srcs, colDelta, manual)
386+
}

src/cmd/compile/internal/types2/issues_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"internal/testenv"
1313
"regexp"
14+
"runtime"
1415
"sort"
1516
"strings"
1617
"testing"
@@ -804,3 +805,71 @@ func (S) M5(struct {S;t}) {}
804805
test(t.main, t.b, t.want)
805806
}
806807
}
808+
809+
func TestIssue59944(t *testing.T) {
810+
if runtime.GOARCH == "wasm" {
811+
// While we don't use the cgo tool directly in this test, we must have the
812+
// syscall package.
813+
t.Skip("cgo generated code does not compile on wasm")
814+
}
815+
// The typechecker should resolve methods declared on aliases of cgo types.
816+
const src = `
817+
package p
818+
819+
/*
820+
struct layout {
821+
int field;
822+
};
823+
*/
824+
import "C"
825+
826+
type Layout = C.struct_layout
827+
828+
func (l *Layout) Binding() {}
829+
830+
func _() {
831+
_ = (*Layout).Binding
832+
}
833+
`
834+
835+
// code generated by cmd/cgo for the above source.
836+
const cgoTypes = `
837+
// Code generated by cmd/cgo; DO NOT EDIT.
838+
839+
package p
840+
841+
import "unsafe"
842+
843+
import "syscall"
844+
845+
import _cgopackage "runtime/cgo"
846+
847+
type _ _cgopackage.Incomplete
848+
var _ syscall.Errno
849+
func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }
850+
851+
//go:linkname _Cgo_always_false runtime.cgoAlwaysFalse
852+
var _Cgo_always_false bool
853+
//go:linkname _Cgo_use runtime.cgoUse
854+
func _Cgo_use(interface{})
855+
type _Ctype_int int32
856+
857+
type _Ctype_struct_layout struct {
858+
field _Ctype_int
859+
}
860+
861+
type _Ctype_void [0]byte
862+
863+
//go:linkname _cgo_runtime_cgocall runtime.cgocall
864+
func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
865+
866+
//go:linkname _cgoCheckPointer runtime.cgoCheckPointer
867+
func _cgoCheckPointer(interface{}, interface{})
868+
869+
//go:linkname _cgoCheckResult runtime.cgoCheckResult
870+
func _cgoCheckResult(interface{})
871+
`
872+
testFiles(t, []string{"p.go", "_cgo_gotypes.go"}, [][]byte{[]byte(src), []byte(cgoTypes)}, 0, false, func(cfg *Config) {
873+
*boolFieldAddr(cfg, "go115UsesCgo") = true
874+
})
875+
}

src/cmd/compile/internal/types2/resolver.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ func (check *Checker) collectObjects() {
497497
for i := range methods {
498498
m := &methods[i]
499499
// Determine the receiver base type and associate m with it.
500-
ptr, base := check.resolveBaseTypeName(m.ptr, m.recv)
500+
ptr, base := check.resolveBaseTypeName(m.ptr, m.recv, fileScopes)
501501
if base != nil {
502502
m.obj.hasPtrRecv_ = ptr
503503
check.methods[base] = append(check.methods[base], m.obj)
@@ -571,7 +571,7 @@ L: // unpack receiver type
571571
// there was a pointer indirection to get to it. The base type name must be declared
572572
// in package scope, and there can be at most one pointer indirection. If no such type
573573
// name exists, the returned base is nil.
574-
func (check *Checker) resolveBaseTypeName(seenPtr bool, typ syntax.Expr) (ptr bool, base *TypeName) {
574+
func (check *Checker) resolveBaseTypeName(seenPtr bool, typ syntax.Expr, fileScopes []*Scope) (ptr bool, base *TypeName) {
575575
// Algorithm: Starting from a type expression, which may be a name,
576576
// we follow that type through alias declarations until we reach a
577577
// non-alias type name. If we encounter anything but pointer types or
@@ -580,8 +580,6 @@ func (check *Checker) resolveBaseTypeName(seenPtr bool, typ syntax.Expr) (ptr bo
580580
ptr = seenPtr
581581
var seen map[*TypeName]bool
582582
for {
583-
typ = unparen(typ)
584-
585583
// check if we have a pointer type
586584
// if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil {
587585
if pexpr, _ := typ.(*syntax.Operation); pexpr != nil && pexpr.Op == syntax.Mul && pexpr.Y == nil {
@@ -593,15 +591,43 @@ func (check *Checker) resolveBaseTypeName(seenPtr bool, typ syntax.Expr) (ptr bo
593591
typ = unparen(pexpr.X) // continue with pointer base type
594592
}
595593

596-
// typ must be a name
597-
name, _ := typ.(*syntax.Name)
598-
if name == nil {
594+
// typ must be a name, or a C.name cgo selector.
595+
var name string
596+
switch typ := typ.(type) {
597+
case *syntax.Name:
598+
name = typ.Value
599+
case *syntax.SelectorExpr:
600+
// C.struct_foo is a valid type name for packages using cgo.
601+
//
602+
// Detect this case, and adjust name so that the correct TypeName is
603+
// resolved below.
604+
if ident, _ := typ.X.(*syntax.Name); ident != nil && ident.Value == "C" {
605+
// Check whether "C" actually resolves to an import of "C", by looking
606+
// in the appropriate file scope.
607+
var obj Object
608+
for _, scope := range fileScopes {
609+
if scope.Contains(ident.Pos()) {
610+
obj = scope.Lookup(ident.Value)
611+
}
612+
}
613+
// If Config.go115UsesCgo is set, the typechecker will resolve Cgo
614+
// selectors to their cgo name. We must do the same here.
615+
if pname, _ := obj.(*PkgName); pname != nil {
616+
if pname.imported.cgo { // only set if Config.go115UsesCgo is set
617+
name = "_Ctype_" + typ.Sel.Value
618+
}
619+
}
620+
}
621+
if name == "" {
622+
return false, nil
623+
}
624+
default:
599625
return false, nil
600626
}
601627

602628
// name must denote an object found in the current package scope
603629
// (note that dot-imported objects are not in the package scope!)
604-
obj := check.pkg.scope.Lookup(name.Value)
630+
obj := check.pkg.scope.Lookup(name)
605631
if obj == nil {
606632
return false, nil
607633
}

0 commit comments

Comments
 (0)