Skip to content

Commit 72ee5ba

Browse files
cmd/cgo: split gofrontend mangling checks into cmd/internal/pkgpath
This is a step toward porting https://golang.org/cl/219817 from the gofrontend repo to the main repo. Note that this also corrects the implementation of the v2 mangling scheme to use ..u and ..U where appropriate. For #37272 Change-Id: I64a1e7ca1c84348efcbf1cf62049eeb05c830ed8 Reviewed-on: https://go-review.googlesource.com/c/go/+/259298 Trust: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent b064eb7 commit 72ee5ba

File tree

5 files changed

+252
-105
lines changed

5 files changed

+252
-105
lines changed

src/cmd/cgo/main.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,7 @@ var exportHeader = flag.String("exportheader", "", "where to write export header
224224
var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
225225
var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo")
226226
var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo")
227-
var gccgoMangleCheckDone bool
228-
var gccgoNewmanglingInEffect bool
227+
var gccgoMangler func(string) string
229228
var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
230229
var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code")
231230
var goarch, goos string

src/cmd/cgo/out.go

+15-103
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package main
66

77
import (
88
"bytes"
9+
"cmd/internal/pkgpath"
910
"debug/elf"
1011
"debug/macho"
1112
"debug/pe"
@@ -15,7 +16,6 @@ import (
1516
"go/token"
1617
"internal/xcoff"
1718
"io"
18-
"io/ioutil"
1919
"os"
2020
"os/exec"
2121
"path/filepath"
@@ -1282,112 +1282,24 @@ func (p *Package) writeExportHeader(fgcch io.Writer) {
12821282
fmt.Fprintf(fgcch, "%s\n", p.gccExportHeaderProlog())
12831283
}
12841284

1285-
// gccgoUsesNewMangling reports whether gccgo uses the new collision-free
1286-
// packagepath mangling scheme (see determineGccgoManglingScheme for more
1287-
// info).
1288-
func gccgoUsesNewMangling() bool {
1289-
if !gccgoMangleCheckDone {
1290-
gccgoNewmanglingInEffect = determineGccgoManglingScheme()
1291-
gccgoMangleCheckDone = true
1292-
}
1293-
return gccgoNewmanglingInEffect
1294-
}
1295-
1296-
const mangleCheckCode = `
1297-
package läufer
1298-
func Run(x int) int {
1299-
return 1
1300-
}
1301-
`
1302-
1303-
// determineGccgoManglingScheme performs a runtime test to see which
1304-
// flavor of packagepath mangling gccgo is using. Older versions of
1305-
// gccgo use a simple mangling scheme where there can be collisions
1306-
// between packages whose paths are different but mangle to the same
1307-
// string. More recent versions of gccgo use a new mangler that avoids
1308-
// these collisions. Return value is whether gccgo uses the new mangling.
1309-
func determineGccgoManglingScheme() bool {
1310-
1311-
// Emit a small Go file for gccgo to compile.
1312-
filepat := "*_gccgo_manglecheck.go"
1313-
var f *os.File
1314-
var err error
1315-
if f, err = ioutil.TempFile(*objDir, filepat); err != nil {
1316-
fatalf("%v", err)
1317-
}
1318-
gofilename := f.Name()
1319-
defer os.Remove(gofilename)
1320-
1321-
if err = ioutil.WriteFile(gofilename, []byte(mangleCheckCode), 0666); err != nil {
1322-
fatalf("%v", err)
1323-
}
1324-
1325-
// Compile with gccgo, capturing generated assembly.
1326-
gccgocmd := os.Getenv("GCCGO")
1327-
if gccgocmd == "" {
1328-
gpath, gerr := exec.LookPath("gccgo")
1329-
if gerr != nil {
1330-
fatalf("unable to locate gccgo: %v", gerr)
1331-
}
1332-
gccgocmd = gpath
1333-
}
1334-
cmd := exec.Command(gccgocmd, "-S", "-o", "-", gofilename)
1335-
buf, cerr := cmd.CombinedOutput()
1336-
if cerr != nil {
1337-
fatalf("%s", cerr)
1338-
}
1339-
1340-
// New mangling: expect go.l..u00e4ufer.Run
1341-
// Old mangling: expect go.l__ufer.Run
1342-
return regexp.MustCompile(`go\.l\.\.u00e4ufer\.Run`).Match(buf)
1343-
}
1344-
1345-
// gccgoPkgpathToSymbolNew converts a package path to a gccgo-style
1346-
// package symbol.
1347-
func gccgoPkgpathToSymbolNew(ppath string) string {
1348-
bsl := []byte{}
1349-
changed := false
1350-
for _, c := range []byte(ppath) {
1351-
switch {
1352-
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z',
1353-
'0' <= c && c <= '9', c == '_':
1354-
bsl = append(bsl, c)
1355-
case c == '.':
1356-
bsl = append(bsl, ".x2e"...)
1357-
default:
1358-
changed = true
1359-
encbytes := []byte(fmt.Sprintf("..z%02x", c))
1360-
bsl = append(bsl, encbytes...)
1361-
}
1362-
}
1363-
if !changed {
1364-
return ppath
1365-
}
1366-
return string(bsl)
1367-
}
1368-
1369-
// gccgoPkgpathToSymbolOld converts a package path to a gccgo-style
1370-
// package symbol using the older mangling scheme.
1371-
func gccgoPkgpathToSymbolOld(ppath string) string {
1372-
clean := func(r rune) rune {
1373-
switch {
1374-
case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z',
1375-
'0' <= r && r <= '9':
1376-
return r
1377-
}
1378-
return '_'
1379-
}
1380-
return strings.Map(clean, ppath)
1381-
}
1382-
13831285
// gccgoPkgpathToSymbol converts a package path to a mangled packagepath
13841286
// symbol.
13851287
func gccgoPkgpathToSymbol(ppath string) string {
1386-
if gccgoUsesNewMangling() {
1387-
return gccgoPkgpathToSymbolNew(ppath)
1388-
} else {
1389-
return gccgoPkgpathToSymbolOld(ppath)
1288+
if gccgoMangler == nil {
1289+
var err error
1290+
cmd := os.Getenv("GCCGO")
1291+
if cmd == "" {
1292+
cmd, err = exec.LookPath("gccgo")
1293+
if err != nil {
1294+
fatalf("unable to locate gccgo: %v", err)
1295+
}
1296+
}
1297+
gccgoMangler, err = pkgpath.ToSymbolFunc(cmd, *objDir)
1298+
if err != nil {
1299+
fatalf("%v", err)
1300+
}
13901301
}
1302+
return gccgoMangler(ppath)
13911303
}
13921304

13931305
// Return the package prefix when using gccgo.

src/cmd/dist/buildtool.go

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ var bootstrapDirs = []string{
6767
"cmd/internal/obj/s390x",
6868
"cmd/internal/obj/x86",
6969
"cmd/internal/obj/wasm",
70+
"cmd/internal/pkgpath",
7071
"cmd/internal/src",
7172
"cmd/internal/sys",
7273
"cmd/link",

src/cmd/internal/pkgpath/pkgpath.go

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package pkgpath determines the package path used by gccgo/GoLLVM symbols.
6+
// This package is not used for the gc compiler.
7+
package pkgpath
8+
9+
import (
10+
"bytes"
11+
"errors"
12+
"fmt"
13+
"io/ioutil"
14+
"os"
15+
"os/exec"
16+
"strings"
17+
)
18+
19+
// ToSymbolFunc returns a function that may be used to convert a
20+
// package path into a string suitable for use as a symbol.
21+
// cmd is the gccgo/GoLLVM compiler in use, and tmpdir is a temporary
22+
// directory to pass to ioutil.TempFile.
23+
// For example, this returns a function that converts "net/http"
24+
// into a string like "net..z2fhttp". The actual string varies for
25+
// different gccgo/GoLLVM versions, which is why this returns a function
26+
// that does the conversion appropriate for the compiler in use.
27+
func ToSymbolFunc(cmd, tmpdir string) (func(string) string, error) {
28+
// To determine the scheme used by cmd, we compile a small
29+
// file and examine the assembly code. Older versions of gccgo
30+
// use a simple mangling scheme where there can be collisions
31+
// between packages whose paths are different but mangle to
32+
// the same string. More recent versions use a new mangler
33+
// that avoids these collisions.
34+
const filepat = "*_gccgo_manglechck.go"
35+
f, err := ioutil.TempFile(tmpdir, filepat)
36+
if err != nil {
37+
return nil, err
38+
}
39+
gofilename := f.Name()
40+
f.Close()
41+
defer os.Remove(gofilename)
42+
43+
if err := ioutil.WriteFile(gofilename, []byte(mangleCheckCode), 0644); err != nil {
44+
return nil, err
45+
}
46+
47+
command := exec.Command(cmd, "-S", "-o", "-", gofilename)
48+
buf, err := command.Output()
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
// New mangling: expect go.l..u00e4ufer.Run
54+
// Old mangling: expect go.l__ufer.Run
55+
if bytes.Contains(buf, []byte("go.l..u00e4ufer.Run")) {
56+
return toSymbolV2, nil
57+
} else if bytes.Contains(buf, []byte("go.l__ufer.Run")) {
58+
return toSymbolV1, nil
59+
} else {
60+
return nil, errors.New(cmd + ": unrecognized mangling scheme")
61+
}
62+
}
63+
64+
// mangleCheckCode is the package we compile to determine the mangling scheme.
65+
const mangleCheckCode = `
66+
package läufer
67+
func Run(x int) int {
68+
return 1
69+
}
70+
`
71+
72+
// toSymbolV1 converts a package path using the original mangling scheme.
73+
func toSymbolV1(ppath string) string {
74+
clean := func(r rune) rune {
75+
switch {
76+
case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z',
77+
'0' <= r && r <= '9':
78+
return r
79+
}
80+
return '_'
81+
}
82+
return strings.Map(clean, ppath)
83+
}
84+
85+
// toSymbolV2 converts a package path using the newer mangling scheme.
86+
func toSymbolV2(ppath string) string {
87+
// This has to build at boostrap time, so it has to build
88+
// with Go 1.4, so we don't use strings.Builder.
89+
bsl := make([]byte, 0, len(ppath))
90+
changed := false
91+
for _, c := range ppath {
92+
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' {
93+
bsl = append(bsl, byte(c))
94+
continue
95+
}
96+
var enc string
97+
switch {
98+
case c == '.':
99+
enc = ".x2e"
100+
case c < 0x80:
101+
enc = fmt.Sprintf("..z%02x", c)
102+
case c < 0x10000:
103+
enc = fmt.Sprintf("..u%04x", c)
104+
default:
105+
enc = fmt.Sprintf("..U%08x", c)
106+
}
107+
bsl = append(bsl, enc...)
108+
changed = true
109+
}
110+
if !changed {
111+
return ppath
112+
}
113+
return string(bsl)
114+
}
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkgpath
6+
7+
import (
8+
"os"
9+
"testing"
10+
)
11+
12+
const testEnvName = "GO_PKGPATH_TEST_COMPILER"
13+
14+
// This init function supports TestToSymbolFunc. For simplicity,
15+
// we use the test binary itself as a sample gccgo driver.
16+
// We set an environment variable to specify how it should behave.
17+
func init() {
18+
switch os.Getenv(testEnvName) {
19+
case "":
20+
return
21+
case "v1":
22+
os.Stdout.WriteString(`.string "go.l__ufer.Run"`)
23+
os.Exit(0)
24+
case "v2":
25+
os.Stdout.WriteString(`.string "go.l..u00e4ufer.Run"`)
26+
os.Exit(0)
27+
case "error":
28+
os.Stdout.WriteString(`unknown string`)
29+
os.Exit(0)
30+
}
31+
}
32+
33+
func TestToSymbolFunc(t *testing.T) {
34+
const input = "pä世🜃"
35+
tests := []struct {
36+
env string
37+
fail bool
38+
mangled string
39+
}{
40+
{
41+
env: "v1",
42+
mangled: "p___",
43+
},
44+
{
45+
env: "v2",
46+
mangled: "p..u00e4..u4e16..U0001f703",
47+
},
48+
{
49+
env: "error",
50+
fail: true,
51+
},
52+
}
53+
54+
cmd := os.Args[0]
55+
tmpdir := t.TempDir()
56+
57+
defer os.Unsetenv(testEnvName)
58+
59+
for _, test := range tests {
60+
t.Run(test.env, func(t *testing.T) {
61+
os.Setenv(testEnvName, test.env)
62+
63+
fn, err := ToSymbolFunc(cmd, tmpdir)
64+
if err != nil {
65+
if !test.fail {
66+
t.Errorf("ToSymbolFunc(%q, %q): unexpected error %v", cmd, tmpdir, err)
67+
}
68+
} else if test.fail {
69+
t.Errorf("ToSymbolFunc(%q, %q) succeeded but expected to fail", cmd, tmpdir)
70+
} else if got, want := fn(input), test.mangled; got != want {
71+
t.Errorf("ToSymbolFunc(%q, %q)(%q) = %q, want %q", cmd, tmpdir, input, got, want)
72+
}
73+
})
74+
}
75+
}
76+
77+
var symbolTests = []struct {
78+
input, v1, v2 string
79+
}{
80+
{
81+
"",
82+
"",
83+
"",
84+
},
85+
{
86+
"bytes",
87+
"bytes",
88+
"bytes",
89+
},
90+
{
91+
"net/http",
92+
"net_http",
93+
"net..z2fhttp",
94+
},
95+
{
96+
"golang.org/x/net/http",
97+
"golang_org_x_net_http",
98+
"golang.x2eorg..z2fx..z2fnet..z2fhttp",
99+
},
100+
{
101+
"pä世.🜃",
102+
"p____",
103+
"p..u00e4..u4e16.x2e..U0001f703",
104+
},
105+
}
106+
107+
func TestV1(t *testing.T) {
108+
for _, test := range symbolTests {
109+
if got, want := toSymbolV1(test.input), test.v1; got != want {
110+
t.Errorf("toSymbolV1(%q) = %q, want %q", test.input, got, want)
111+
}
112+
}
113+
}
114+
115+
func TestV2(t *testing.T) {
116+
for _, test := range symbolTests {
117+
if got, want := toSymbolV2(test.input), test.v2; got != want {
118+
t.Errorf("toSymbolV2(%q) = %q, want %q", test.input, got, want)
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)