-
Notifications
You must be signed in to change notification settings - Fork 17.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Than McIntosh <thanm@google.com>
- Loading branch information
1 parent
b064eb7
commit 72ee5ba
Showing
5 changed files
with
252 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// Copyright 2020 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. | ||
|
||
// Package pkgpath determines the package path used by gccgo/GoLLVM symbols. | ||
// This package is not used for the gc compiler. | ||
package pkgpath | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
) | ||
|
||
// ToSymbolFunc returns a function that may be used to convert a | ||
// package path into a string suitable for use as a symbol. | ||
// cmd is the gccgo/GoLLVM compiler in use, and tmpdir is a temporary | ||
// directory to pass to ioutil.TempFile. | ||
// For example, this returns a function that converts "net/http" | ||
// into a string like "net..z2fhttp". The actual string varies for | ||
// different gccgo/GoLLVM versions, which is why this returns a function | ||
// that does the conversion appropriate for the compiler in use. | ||
func ToSymbolFunc(cmd, tmpdir string) (func(string) string, error) { | ||
// To determine the scheme used by cmd, we compile a small | ||
// file and examine the assembly code. Older versions of gccgo | ||
// use a simple mangling scheme where there can be collisions | ||
// between packages whose paths are different but mangle to | ||
// the same string. More recent versions use a new mangler | ||
// that avoids these collisions. | ||
const filepat = "*_gccgo_manglechck.go" | ||
f, err := ioutil.TempFile(tmpdir, filepat) | ||
if err != nil { | ||
return nil, err | ||
} | ||
gofilename := f.Name() | ||
f.Close() | ||
defer os.Remove(gofilename) | ||
|
||
if err := ioutil.WriteFile(gofilename, []byte(mangleCheckCode), 0644); err != nil { | ||
return nil, err | ||
} | ||
|
||
command := exec.Command(cmd, "-S", "-o", "-", gofilename) | ||
buf, err := command.Output() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// New mangling: expect go.l..u00e4ufer.Run | ||
// Old mangling: expect go.l__ufer.Run | ||
if bytes.Contains(buf, []byte("go.l..u00e4ufer.Run")) { | ||
return toSymbolV2, nil | ||
} else if bytes.Contains(buf, []byte("go.l__ufer.Run")) { | ||
return toSymbolV1, nil | ||
} else { | ||
return nil, errors.New(cmd + ": unrecognized mangling scheme") | ||
} | ||
} | ||
|
||
// mangleCheckCode is the package we compile to determine the mangling scheme. | ||
const mangleCheckCode = ` | ||
package läufer | ||
func Run(x int) int { | ||
return 1 | ||
} | ||
` | ||
|
||
// toSymbolV1 converts a package path using the original mangling scheme. | ||
func toSymbolV1(ppath string) string { | ||
clean := func(r rune) rune { | ||
switch { | ||
case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z', | ||
'0' <= r && r <= '9': | ||
return r | ||
} | ||
return '_' | ||
} | ||
return strings.Map(clean, ppath) | ||
} | ||
|
||
// toSymbolV2 converts a package path using the newer mangling scheme. | ||
func toSymbolV2(ppath string) string { | ||
// This has to build at boostrap time, so it has to build | ||
// with Go 1.4, so we don't use strings.Builder. | ||
bsl := make([]byte, 0, len(ppath)) | ||
changed := false | ||
for _, c := range ppath { | ||
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' { | ||
bsl = append(bsl, byte(c)) | ||
continue | ||
} | ||
var enc string | ||
switch { | ||
case c == '.': | ||
enc = ".x2e" | ||
case c < 0x80: | ||
enc = fmt.Sprintf("..z%02x", c) | ||
case c < 0x10000: | ||
enc = fmt.Sprintf("..u%04x", c) | ||
default: | ||
enc = fmt.Sprintf("..U%08x", c) | ||
} | ||
bsl = append(bsl, enc...) | ||
changed = true | ||
} | ||
if !changed { | ||
return ppath | ||
} | ||
return string(bsl) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Copyright 2020 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. | ||
|
||
package pkgpath | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
) | ||
|
||
const testEnvName = "GO_PKGPATH_TEST_COMPILER" | ||
|
||
// This init function supports TestToSymbolFunc. For simplicity, | ||
// we use the test binary itself as a sample gccgo driver. | ||
// We set an environment variable to specify how it should behave. | ||
func init() { | ||
switch os.Getenv(testEnvName) { | ||
case "": | ||
return | ||
case "v1": | ||
os.Stdout.WriteString(`.string "go.l__ufer.Run"`) | ||
os.Exit(0) | ||
case "v2": | ||
os.Stdout.WriteString(`.string "go.l..u00e4ufer.Run"`) | ||
os.Exit(0) | ||
case "error": | ||
os.Stdout.WriteString(`unknown string`) | ||
os.Exit(0) | ||
} | ||
} | ||
|
||
func TestToSymbolFunc(t *testing.T) { | ||
const input = "pä世🜃" | ||
tests := []struct { | ||
env string | ||
fail bool | ||
mangled string | ||
}{ | ||
{ | ||
env: "v1", | ||
mangled: "p___", | ||
}, | ||
{ | ||
env: "v2", | ||
mangled: "p..u00e4..u4e16..U0001f703", | ||
}, | ||
{ | ||
env: "error", | ||
fail: true, | ||
}, | ||
} | ||
|
||
cmd := os.Args[0] | ||
tmpdir := t.TempDir() | ||
|
||
defer os.Unsetenv(testEnvName) | ||
|
||
for _, test := range tests { | ||
t.Run(test.env, func(t *testing.T) { | ||
os.Setenv(testEnvName, test.env) | ||
|
||
fn, err := ToSymbolFunc(cmd, tmpdir) | ||
if err != nil { | ||
if !test.fail { | ||
t.Errorf("ToSymbolFunc(%q, %q): unexpected error %v", cmd, tmpdir, err) | ||
} | ||
} else if test.fail { | ||
t.Errorf("ToSymbolFunc(%q, %q) succeeded but expected to fail", cmd, tmpdir) | ||
} else if got, want := fn(input), test.mangled; got != want { | ||
t.Errorf("ToSymbolFunc(%q, %q)(%q) = %q, want %q", cmd, tmpdir, input, got, want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
var symbolTests = []struct { | ||
input, v1, v2 string | ||
}{ | ||
{ | ||
"", | ||
"", | ||
"", | ||
}, | ||
{ | ||
"bytes", | ||
"bytes", | ||
"bytes", | ||
}, | ||
{ | ||
"net/http", | ||
"net_http", | ||
"net..z2fhttp", | ||
}, | ||
{ | ||
"golang.org/x/net/http", | ||
"golang_org_x_net_http", | ||
"golang.x2eorg..z2fx..z2fnet..z2fhttp", | ||
}, | ||
{ | ||
"pä世.🜃", | ||
"p____", | ||
"p..u00e4..u4e16.x2e..U0001f703", | ||
}, | ||
} | ||
|
||
func TestV1(t *testing.T) { | ||
for _, test := range symbolTests { | ||
if got, want := toSymbolV1(test.input), test.v1; got != want { | ||
t.Errorf("toSymbolV1(%q) = %q, want %q", test.input, got, want) | ||
} | ||
} | ||
} | ||
|
||
func TestV2(t *testing.T) { | ||
for _, test := range symbolTests { | ||
if got, want := toSymbolV2(test.input), test.v2; got != want { | ||
t.Errorf("toSymbolV2(%q) = %q, want %q", test.input, got, want) | ||
} | ||
} | ||
} |