Skip to content

Commit a1c6032

Browse files
authored
Merge pull request #1282 from Workiva/updateLinkname
[go1.20] Updated the linkname to handle new directives
2 parents 0285af6 + 5c45b9a commit a1c6032

File tree

3 files changed

+173
-40
lines changed

3 files changed

+173
-40
lines changed

compiler/linkname.go

Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,83 @@ func (n SymName) IsMethod() (recv string, method string, ok bool) {
7575
return
7676
}
7777

78+
// readLinknameFromComment reads the given comment to determine if it's a go:linkname
79+
// directive then returns the linkname information, otherwise returns nil.
80+
func readLinknameFromComment(pkgPath string, comment *ast.Comment) (*GoLinkname, error) {
81+
if !strings.HasPrefix(comment.Text, `//go:linkname `) {
82+
return nil, nil // Not a linkname compiler directive.
83+
}
84+
85+
fields := strings.Fields(comment.Text)
86+
87+
// Check that the directive comment has both parts and is on the line by itself.
88+
switch len(fields) {
89+
case 2:
90+
// Ignore one-argument form //go:linkname localName
91+
// This is typically used with "insert"-style links to
92+
// suppresses the usual error for a function that lacks a body.
93+
// The "insert"-style links aren't supported by GopherJS so
94+
// these bodiless functions have to be overridden in the natives anyway.
95+
return nil, nil
96+
case 3:
97+
// Continue for two-argument form //go:linkname localName importPath.extName
98+
break
99+
default:
100+
return nil, fmt.Errorf(`gopherjs: usage requires 2 arguments: //go:linkname localName importPath.extName`)
101+
}
102+
103+
localPkg, localName := pkgPath, fields[1]
104+
extPkg, extName := ``, fields[2]
105+
106+
if localName == extName {
107+
// Ignore self referencing links, //go:linkname localName localName
108+
// These function similar to one-argument links.
109+
return nil, nil
110+
}
111+
112+
pathOffset := 0
113+
if pos := strings.LastIndexByte(extName, '/'); pos != -1 {
114+
pathOffset = pos + 1
115+
}
116+
117+
if idx := strings.IndexByte(extName[pathOffset:], '.'); idx != -1 {
118+
extPkg, extName = extName[:pathOffset+idx], extName[pathOffset+idx+1:]
119+
}
120+
121+
return &GoLinkname{
122+
Reference: SymName{PkgPath: localPkg, Name: localName},
123+
Implementation: SymName{PkgPath: extPkg, Name: extName},
124+
}, nil
125+
}
126+
127+
// isMitigatedVarLinkname checks if the given go:linkname directive on
128+
// a variable, which GopherJS doesn't support, is known about.
129+
// We silently ignore such directives, since it doesn't seem to cause any problems.
130+
func isMitigatedVarLinkname(sym SymName) bool {
131+
mitigatedLinks := map[string]bool{
132+
`reflect.zeroVal`: true,
133+
`math/bits.overflowError`: true, // Defaults in bits_errors_bootstrap.go
134+
`math/bits.divideError`: true, // Defaults in bits_errors_bootstrap.go
135+
}
136+
return mitigatedLinks[sym.String()]
137+
}
138+
139+
// isMitigatedInsertLinkname checks if the given go:linkname directive
140+
// on a function, where the function has a body, is known about.
141+
// These are unsupported "insert"-style go:linkname directives,
142+
// that we ignore as a link and handle case-by-case in native overrides.
143+
func isMitigatedInsertLinkname(sym SymName) bool {
144+
mitigatedPkg := map[string]bool{
145+
`runtime`: true, // Lots of "insert"-style links
146+
`internal/fuzz`: true, // Defaults to no-op stubs
147+
}
148+
mitigatedLinks := map[string]bool{
149+
`internal/bytealg.runtime_cmpstring`: true,
150+
`os.net_newUnixFile`: true,
151+
}
152+
return mitigatedPkg[sym.PkgPath] || mitigatedLinks[sym.String()]
153+
}
154+
78155
// parseGoLinknames processed comments in a source file and extracts //go:linkname
79156
// compiler directive from the comments.
80157
//
@@ -98,63 +175,36 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go
98175
isUnsafe := astutil.ImportsUnsafe(file)
99176

100177
processComment := func(comment *ast.Comment) error {
101-
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
102-
return nil // Not a linkname compiler directive.
178+
link, err := readLinknameFromComment(pkgPath, comment)
179+
if err != nil || link == nil {
180+
return err
103181
}
104182

105-
// TODO(nevkontakte): Ideally we should check that the directive comment
106-
// is on a line by itself, line Go compiler does, but ast.Comment doesn't
107-
// provide an easy way to find that out.
108-
109183
if !isUnsafe {
110184
return fmt.Errorf(`//go:linkname is only allowed in Go files that import "unsafe"`)
111185
}
112186

113-
fields := strings.Fields(comment.Text)
114-
if len(fields) != 3 {
115-
return fmt.Errorf(`usage (all fields required): //go:linkname localname importpath.extname`)
116-
}
117-
118-
localPkg, localName := pkgPath, fields[1]
119-
extPkg, extName := "", fields[2]
120-
if pos := strings.LastIndexByte(extName, '/'); pos != -1 {
121-
if idx := strings.IndexByte(extName[pos+1:], '.'); idx != -1 {
122-
extPkg, extName = extName[0:pos+idx+1], extName[pos+idx+2:]
123-
}
124-
} else if idx := strings.IndexByte(extName, '.'); idx != -1 {
125-
extPkg, extName = extName[0:idx], extName[idx+1:]
126-
}
127-
128-
obj := file.Scope.Lookup(localName)
187+
obj := file.Scope.Lookup(link.Reference.Name)
129188
if obj == nil {
130-
return fmt.Errorf("//go:linkname local symbol %q is not found in the current source file", localName)
189+
return fmt.Errorf("//go:linkname local symbol %q is not found in the current source file", link.Reference.Name)
131190
}
132191

133192
if obj.Kind != ast.Fun {
134-
if pkgPath == "math/bits" || pkgPath == "reflect" {
135-
// These standard library packages are known to use go:linkname with
136-
// variables, which GopherJS doesn't support. We silently ignore such
137-
// directives, since it doesn't seem to cause any problems.
193+
if isMitigatedVarLinkname(link.Reference) {
138194
return nil
139195
}
140196
return fmt.Errorf("gopherjs: //go:linkname is only supported for functions, got %q", obj.Kind)
141197
}
142198

143-
decl := obj.Decl.(*ast.FuncDecl)
144-
if decl.Body != nil {
145-
if pkgPath == "runtime" || pkgPath == "internal/bytealg" || pkgPath == "internal/fuzz" {
146-
// These standard library packages are known to use unsupported
147-
// "insert"-style go:linkname directives, which we ignore here and handle
148-
// case-by-case in native overrides.
199+
if decl := obj.Decl.(*ast.FuncDecl); decl.Body != nil {
200+
if isMitigatedInsertLinkname(link.Reference) {
149201
return nil
150202
}
151-
return fmt.Errorf("gopherjs: //go:linkname can not insert local implementation into an external package %q", extPkg)
203+
return fmt.Errorf("gopherjs: //go:linkname can not insert local implementation into an external package %q", link.Implementation.PkgPath)
152204
}
205+
153206
// Local function has no body, treat it as a reference to an external implementation.
154-
directives = append(directives, GoLinkname{
155-
Reference: SymName{PkgPath: localPkg, Name: localName},
156-
Implementation: SymName{PkgPath: extPkg, Name: extName},
157-
})
207+
directives = append(directives, *link)
158208
return nil
159209
}
160210

compiler/linkname_test.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func TestSymName(t *testing.T) {
8787
func TestParseGoLinknames(t *testing.T) {
8888
tests := []struct {
8989
desc string
90+
pkgPath string
9091
src string
9192
wantError string
9293
wantDirectives []GoLinkname
@@ -148,14 +149,24 @@ func TestParseGoLinknames(t *testing.T) {
148149
`,
149150
wantError: `import "unsafe"`,
150151
}, {
151-
desc: "gopherjs: both parameters are required",
152+
desc: "gopherjs: ignore one-argument linknames",
152153
src: `package testcase
153154
154155
import _ "unsafe"
155156
156157
//go:linkname a
157158
func a()
158159
`,
160+
wantDirectives: []GoLinkname{},
161+
}, {
162+
desc: `gopherjs: linkname has too many arguments`,
163+
src: `package testcase
164+
165+
import _ "unsafe"
166+
167+
//go:linkname a other/package.a too/many.args
168+
func a()
169+
`,
159170
wantError: "usage",
160171
}, {
161172
desc: "referenced function doesn't exist",
@@ -177,6 +188,17 @@ func TestParseGoLinknames(t *testing.T) {
177188
var a string = "foo"
178189
`,
179190
wantError: `is only supported for functions`,
191+
}, {
192+
desc: `gopherjs: ignore know referenced variables`,
193+
pkgPath: `reflect`,
194+
src: `package reflect
195+
196+
import _ "unsafe"
197+
198+
//go:linkname zeroVal other/package.zeroVal
199+
var zeroVal []bytes
200+
`,
201+
wantDirectives: []GoLinkname{},
180202
}, {
181203
desc: "gopherjs: can not insert local implementation",
182204
src: `package testcase
@@ -187,13 +209,60 @@ func TestParseGoLinknames(t *testing.T) {
187209
func a() { println("do a") }
188210
`,
189211
wantError: `can not insert local implementation`,
212+
}, {
213+
desc: `gopherjs: ignore known local implementation insert`,
214+
pkgPath: `runtime`, // runtime is known and ignored
215+
src: `package runtime
216+
217+
import _ "unsafe"
218+
219+
//go:linkname a other/package.a
220+
func a() { println("do a") }
221+
`,
222+
wantDirectives: []GoLinkname{},
223+
}, {
224+
desc: `gopherjs: link to function with receiver`,
225+
// //go:linkname <localname> <importpath>.<type>.<name>
226+
src: `package testcase
227+
228+
import _ "unsafe"
229+
230+
//go:linkname a other/package.b.a
231+
func a()
232+
`,
233+
wantDirectives: []GoLinkname{
234+
{
235+
Reference: SymName{PkgPath: `testcase`, Name: `a`},
236+
Implementation: SymName{PkgPath: `other/package`, Name: `b.a`},
237+
},
238+
},
239+
}, {
240+
desc: `gopherjs: link to function with pointer receiver`,
241+
// //go:linkname <localname> <importpath>.<(*type)>.<name>
242+
src: `package testcase
243+
244+
import _ "unsafe"
245+
246+
//go:linkname a other/package.*b.a
247+
func a()
248+
`,
249+
wantDirectives: []GoLinkname{
250+
{
251+
Reference: SymName{PkgPath: `testcase`, Name: `a`},
252+
Implementation: SymName{PkgPath: `other/package`, Name: `*b.a`},
253+
},
254+
},
190255
},
191256
}
192257

193258
for _, test := range tests {
194259
t.Run(test.desc, func(t *testing.T) {
195260
file, fset := parseSource(t, test.src)
196-
directives, err := parseGoLinknames(fset, "testcase", file)
261+
pkgPath := `testcase`
262+
if len(test.pkgPath) > 0 {
263+
pkgPath = test.pkgPath
264+
}
265+
directives, err := parseGoLinknames(fset, pkgPath, file)
197266

198267
if test.wantError != "" {
199268
if err == nil {

compiler/natives/src/net/fd_unix.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build js
2+
// +build js
3+
4+
package net
5+
6+
import (
7+
"os"
8+
_ "unsafe" // for go:linkname
9+
)
10+
11+
// Reversing the linkname direction
12+
//
13+
//go:linkname newUnixFile os.net_newUnixFile
14+
func newUnixFile(fd uintptr, name string) *os.File

0 commit comments

Comments
 (0)