@@ -75,6 +75,80 @@ func (n SymName) IsMethod() (recv string, method string, ok bool) {
75
75
return
76
76
}
77
77
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
+
78
152
// parseGoLinknames processed comments in a source file and extracts //go:linkname
79
153
// compiler directive from the comments.
80
154
//
@@ -98,63 +172,36 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go
98
172
isUnsafe := astutil .ImportsUnsafe (file )
99
173
100
174
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
103
178
}
104
179
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
-
109
180
if ! isUnsafe {
110
181
return fmt .Errorf (`//go:linkname is only allowed in Go files that import "unsafe"` )
111
182
}
112
183
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 )
129
185
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 )
131
187
}
132
188
133
189
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 ) {
138
191
return nil
139
192
}
140
193
return fmt .Errorf ("gopherjs: //go:linkname is only supported for functions, got %q" , obj .Kind )
141
194
}
142
195
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 ) {
149
198
return nil
150
199
}
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 )
152
201
}
202
+
153
203
// 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 )
158
205
return nil
159
206
}
160
207
0 commit comments