From 0302d83cfae923133cc1aea513aeaa61ff80a7ca Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Sat, 10 Oct 2026 12:39:38 +0800 Subject: [PATCH] cmd/cgo: don't use Go parameter names in exported C wrapper body Since CL 222619 (Go 1.15), the C wrapper that cgo writes to _cgo_export.c for an exported function uses the Go parameter names as the C parameter names. The wrapper body declares a struct whose fields use the C types of the parameters and results, so a parameter whose name is the same as a C type name shadows that type and the wrapper fails to compile: //export F func F(writeFunc C.writeFunc) { ... } _cgo_export.c: error: expected specifier-qualifier-list before 'writeFunc' Similarly, a parameter named r conflicts with the local variable that holds multiple results. Keep the Go parameter names in the declaration in _cgo_export.h, which is what CL 222619 was for, but use the generated names p0, p1, ... in the definition, as was done before Go 1.15. C does not require the parameter names of a declaration and a definition to match, so the header seen by users is unchanged. AI disclosure: Claude Opus 5.5 assisted with the analysis, the code and the test in this change. Fixes #44648 --- src/cmd/cgo/internal/test/issue44648.go | 32 ++++++++++++++++ src/cmd/cgo/out.go | 50 +++++++++++++++---------- 2 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 src/cmd/cgo/internal/test/issue44648.go diff --git a/src/cmd/cgo/internal/test/issue44648.go b/src/cmd/cgo/internal/test/issue44648.go new file mode 100644 index 00000000000000..4c320bec4399e8 --- /dev/null +++ b/src/cmd/cgo/internal/test/issue44648.go @@ -0,0 +1,32 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Issue 44648. The C wrapper for an exported Go function failed to +// compile if a parameter had the same name as a C type used by the +// function. No runtime test; just make sure it compiles. + +package cgotest + +/* +typedef int issue44648T; +typedef int (*issue44648F)(void *); +*/ +import "C" + +import "unsafe" + +//export Issue44648A +func Issue44648A(issue44648F C.issue44648F, data unsafe.Pointer) C.int { + return 0 +} + +//export Issue44648B +func Issue44648B(issue44648T C.issue44648T) (C.issue44648T, C.issue44648T) { + return issue44648T, issue44648T +} + +//export Issue44648C +func Issue44648C(r C.int) (a, b C.int) { + return r, r +} diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 41dfb065e4602e..7b592845d8227e 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -1047,25 +1047,35 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { } // Build the wrapper function compiled by gcc. - var s strings.Builder - fmt.Fprintf(&s, "%s %s(", gccResult, exp.ExpName) - if fn.Recv != nil { - s.WriteString(p.cgoType(fn.Recv.List[0].Type).C.String()) - s.WriteString(" recv") - } + // The declaration in the header uses the Go parameter + // names, but the definition uses generated names so + // that a parameter cannot shadow a C type or variable + // used in the body of the wrapper (issue 44648). + cdecl := func(paramName func(aname string, i int) string) string { + var s strings.Builder + fmt.Fprintf(&s, "%s %s(", gccResult, exp.ExpName) + if fn.Recv != nil { + s.WriteString(p.cgoType(fn.Recv.List[0].Type).C.String()) + s.WriteString(" recv") + } - if len(fntype.Params.List) > 0 { - forFieldList(fntype.Params, - func(i int, aname string, atype ast.Expr) { - if i > 0 || fn.Recv != nil { - s.WriteString(", ") - } - fmt.Fprintf(&s, "%s %s", p.cgoType(atype).C, exportParamName(aname, i)) - }) - } else { - s.WriteString("void") + if len(fntype.Params.List) > 0 { + forFieldList(fntype.Params, + func(i int, aname string, atype ast.Expr) { + if i > 0 || fn.Recv != nil { + s.WriteString(", ") + } + fmt.Fprintf(&s, "%s %s", p.cgoType(atype).C, paramName(aname, i)) + }) + } else { + s.WriteString("void") + } + s.WriteByte(')') + return s.String() + } + genParamName := func(aname string, i int) string { + return fmt.Sprintf("p%d", i) } - s.WriteByte(')') if len(exp.Doc) > 0 { fmt.Fprintf(fgcch, "\n%s", exp.Doc) @@ -1073,11 +1083,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { fmt.Fprint(fgcch, "\n") } } - fmt.Fprintf(fgcch, "extern %s;\n", s.String()) + fmt.Fprintf(fgcch, "extern %s;\n", cdecl(exportParamName)) fmt.Fprintf(fgcc, "extern void _cgoexp%s_%s(void *);\n", cPrefix, exp.ExpName) fmt.Fprintf(fgcc, "\nCGO_NO_SANITIZE_THREAD") - fmt.Fprintf(fgcc, "\n%s\n", s.String()) + fmt.Fprintf(fgcc, "\n%s\n", cdecl(genParamName)) fmt.Fprintf(fgcc, "{\n") fmt.Fprintf(fgcc, "\tsize_t _cgo_ctxt = _cgo_wait_runtime_init_done();\n") // The results part of the argument structure must be @@ -1104,7 +1114,7 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) { } forFieldList(fntype.Params, func(i int, aname string, atype ast.Expr) { - fmt.Fprintf(fgcc, "\t_cgo_a.p%d = %s;\n", i, exportParamName(aname, i)) + fmt.Fprintf(fgcc, "\t_cgo_a.p%d = p%d;\n", i, i) }) fmt.Fprintf(fgcc, "\t_cgo_tsan_release();\n") fmt.Fprintf(fgcc, "\tcrosscall2(_cgoexp%s_%s, &_cgo_a, %d, _cgo_ctxt);\n", cPrefix, exp.ExpName, off)