@@ -75,6 +75,83 @@ 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
+ 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
+
78
155
// parseGoLinknames processed comments in a source file and extracts //go:linkname
79
156
// compiler directive from the comments.
80
157
//
@@ -98,63 +175,36 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go
98
175
isUnsafe := astutil .ImportsUnsafe (file )
99
176
100
177
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
103
181
}
104
182
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
183
if ! isUnsafe {
110
184
return fmt .Errorf (`//go:linkname is only allowed in Go files that import "unsafe"` )
111
185
}
112
186
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 )
129
188
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 )
131
190
}
132
191
133
192
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 ) {
138
194
return nil
139
195
}
140
196
return fmt .Errorf ("gopherjs: //go:linkname is only supported for functions, got %q" , obj .Kind )
141
197
}
142
198
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 ) {
149
201
return nil
150
202
}
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 )
152
204
}
205
+
153
206
// 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 )
158
208
return nil
159
209
}
160
210
0 commit comments