diff --git a/src/cmd/cgo/ast.go b/src/cmd/cgo/ast.go index 81060c67edd792..6a1cf387208e5e 100644 --- a/src/cmd/cgo/ast.go +++ b/src/cmd/cgo/ast.go @@ -9,6 +9,7 @@ package main import ( "fmt" "go/ast" + "go/format" "go/parser" "go/scanner" "go/token" @@ -62,29 +63,48 @@ func (f *File) ParseGo(abspath string, src []byte) { // In ast1, find the import "C" line and get any extra C preamble. sawC := false for _, decl := range ast1.Decls { - d, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - for _, spec := range d.Specs { - s, ok := spec.(*ast.ImportSpec) - if !ok || s.Path.Value != `"C"` { - continue - } - sawC = true - if s.Name != nil { - error_(s.Path.Pos(), `cannot rename import "C"`) - } - cg := s.Doc - if cg == nil && len(d.Specs) == 1 { - cg = d.Doc + switch decl := decl.(type) { + case *ast.GenDecl: + for _, spec := range decl.Specs { + s, ok := spec.(*ast.ImportSpec) + if !ok || s.Path.Value != `"C"` { + continue + } + sawC = true + if s.Name != nil { + error_(s.Path.Pos(), `cannot rename import "C"`) + } + cg := s.Doc + if cg == nil && len(decl.Specs) == 1 { + cg = decl.Doc + } + if cg != nil { + f.Preamble += fmt.Sprintf("#line %d %q\n", sourceLine(cg), abspath) + f.Preamble += commentText(cg) + "\n" + f.Preamble += "#line 1 \"cgo-generated-wrapper\"\n" + } } - if cg != nil { - f.Preamble += fmt.Sprintf("#line %d %q\n", sourceLine(cg), abspath) - f.Preamble += commentText(cg) + "\n" - f.Preamble += "#line 1 \"cgo-generated-wrapper\"\n" + + case *ast.FuncDecl: + // Also, reject attempts to declare methods on C.T or *C.T. + // (The generated code would otherwise accept this + // invalid input; see issue #57926.) + if decl.Recv != nil && len(decl.Recv.List) > 0 { + recvType := decl.Recv.List[0].Type + if recvType != nil { + t := recvType + if star, ok := unparen(t).(*ast.StarExpr); ok { + t = star.X + } + if sel, ok := unparen(t).(*ast.SelectorExpr); ok { + var buf strings.Builder + format.Node(&buf, fset, recvType) + error_(sel.Pos(), `cannot define new methods on non-local type %s`, &buf) + } + } } } + } if !sawC { error_(ast1.Package, `cannot find import "C"`) @@ -542,3 +562,11 @@ func (f *File) walk(x interface{}, context astContext, visit func(*File, interfa } } } + +// If x is of the form (T), unparen returns unparen(T), otherwise it returns x. +func unparen(x ast.Expr) ast.Expr { + if p, isParen := x.(*ast.ParenExpr); isParen { + x = unparen(p.X) + } + return x +} diff --git a/src/cmd/go/testdata/script/cgo_badmethod_issue57926.txt b/src/cmd/go/testdata/script/cgo_badmethod_issue57926.txt new file mode 100644 index 00000000000000..81ef850cb960ca --- /dev/null +++ b/src/cmd/go/testdata/script/cgo_badmethod_issue57926.txt @@ -0,0 +1,31 @@ +[short] skip +[!cgo] skip + +# Test that cgo rejects attempts to declare methods +# on the types C.T or *C.T; see issue #57926. + +! go build +stderr 'cannot define new methods on non-local type C.T' +stderr 'cannot define new methods on non-local type \*C.T' +! stderr 'Alias' + +-- go.mod -- +module example.com +go 1.12 + +-- a.go -- +package a + +/* +typedef int T; +*/ +import "C" + +func (C.T) f() {} +func (recv *C.T) g() {} + +// The check is more education than enforcement, +// and is easily defeated using a type alias. +type Alias = C.T +func (Alias) h() {} +func (*Alias) i() {}