Skip to content

Commit cdd523b

Browse files
Updated the linkname to handle new directives
1 parent 0285af6 commit cdd523b

File tree

3 files changed

+167
-40
lines changed

3 files changed

+167
-40
lines changed

compiler/linkname.go

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,80 @@ 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+
if len(fields) != 3 {
89+
if len(fields) == 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+
}
97+
return nil, fmt.Errorf(`gopherjs: usage requires 2 arguments: //go:linkname localname importpath.extname`)
98+
}
99+
100+
localPkg, localName := pkgPath, fields[1]
101+
extPkg, extName := ``, fields[2]
102+
103+
if localName == extName {
104+
// Ignore self referencing links, //go:linkname <localname> <localname>
105+
// These function similar to one-argument links.
106+
return nil, nil
107+
}
108+
109+
pathOffset := 0
110+
if pos := strings.LastIndexByte(extName, '/'); pos != -1 {
111+
pathOffset = pos + 1
112+
}
113+
114+
if idx := strings.IndexByte(extName[pathOffset:], '.'); idx != -1 {
115+
extPkg, extName = extName[:pathOffset+idx], extName[pathOffset+idx+1:]
116+
}
117+
118+
return &GoLinkname{
119+
Reference: SymName{PkgPath: localPkg, Name: localName},
120+
Implementation: SymName{PkgPath: extPkg, Name: extName},
121+
}, nil
122+
}
123+
124+
// isMitigatedVarLinkname checks if the given go:linkname directive on
125+
// a variable, which GopherJS doesn't support, is known about.
126+
// We silently ignore such directives, since it doesn't seem to cause any problems.
127+
func isMitigatedVarLinkname(sym SymName) bool {
128+
mitigatedLinks := map[string]bool{
129+
`reflect.zeroVal`: true,
130+
`math/bits.overflowError`: true, // Defaults in bits_errors_bootstrap.go
131+
`math/bits.divideError`: true, // Defaults in bits_errors_bootstrap.go
132+
}
133+
return mitigatedLinks[sym.String()]
134+
}
135+
136+
// isMitigatedInsertLinkname checks if the given go:linkname directive
137+
// on a function with a body is known about.
138+
// These are unsupported "insert"-style go:linkname directives,
139+
// that we ignore as a link and handle case-by-case in native overrides.
140+
func isMitigatedInsertLinkname(sym SymName) bool {
141+
mitigatedPkg := map[string]bool{
142+
`runtime`: true, // Lots of "insert"-style links
143+
`internal/fuzz`: true, // Defaults to no-op stubs
144+
}
145+
mitigatedLinks := map[string]bool{
146+
`internal/bytealg.runtime_cmpstring`: true,
147+
`os.net_newUnixFile`: true,
148+
}
149+
return mitigatedPkg[sym.PkgPath] || mitigatedLinks[sym.String()]
150+
}
151+
78152
// parseGoLinknames processed comments in a source file and extracts //go:linkname
79153
// compiler directive from the comments.
80154
//
@@ -98,63 +172,36 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go
98172
isUnsafe := astutil.ImportsUnsafe(file)
99173

100174
processComment := func(comment *ast.Comment) error {
101-
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
102-
return nil // Not a linkname compiler directive.
175+
link, err := readLinknameFromComment(pkgPath, comment)
176+
if err != nil || link == nil {
177+
return err
103178
}
104179

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-
109180
if !isUnsafe {
110181
return fmt.Errorf(`//go:linkname is only allowed in Go files that import "unsafe"`)
111182
}
112183

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)
184+
obj := file.Scope.Lookup(link.Reference.Name)
129185
if obj == nil {
130-
return fmt.Errorf("//go:linkname local symbol %q is not found in the current source file", localName)
186+
return fmt.Errorf("//go:linkname local symbol %q is not found in the current source file", link.Reference.Name)
131187
}
132188

133189
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.
190+
if isMitigatedVarLinkname(link.Reference) {
138191
return nil
139192
}
140193
return fmt.Errorf("gopherjs: //go:linkname is only supported for functions, got %q", obj.Kind)
141194
}
142195

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.
196+
if decl := obj.Decl.(*ast.FuncDecl); decl.Body != nil {
197+
if isMitigatedInsertLinkname(link.Reference) {
149198
return nil
150199
}
151-
return fmt.Errorf("gopherjs: //go:linkname can not insert local implementation into an external package %q", extPkg)
200+
return fmt.Errorf("gopherjs: //go:linkname can not insert local implementation into an external package %q", link.Implementation.PkgPath)
152201
}
202+
153203
// 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-
})
204+
directives = append(directives, *link)
158205
return nil
159206
}
160207

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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build js
2+
// +build js
3+
4+
package net
5+
6+
import "os"
7+
8+
// Reversing the linkname direction
9+
//
10+
//go:linkname newUnixFile os.net_newUnixFile
11+
func newUnixFile(fd uintptr, name string) *os.File

0 commit comments

Comments
 (0)