github.com/goplus/igop@v0.17.0/load/linkname.go (about) 1 /* 2 * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package load 18 19 import ( 20 "fmt" 21 "go/ast" 22 "go/token" 23 "strings" 24 ) 25 26 type LinkSym struct { 27 Kind ast.ObjKind 28 PkgPath string 29 Name string 30 Linkname Linkname 31 } 32 33 func (l *LinkSym) String() string { 34 return l.PkgPath + "." + l.Name + "->" + l.Linkname.PkgPath + "." + l.Linkname.Name 35 } 36 37 type Linkname struct { 38 PkgPath string 39 Name string 40 Recv string 41 Method string 42 } 43 44 // ParseLinkname parse ast files go:linkname 45 // //go:linkname <localname> <importpath>.<name> 46 // //go:linkname <localname> <importpath>.<type>.<name> 47 // //go:linkname <localname> <importpath>.<(*type)>.<name> 48 func ParseLinkname(fset *token.FileSet, pkgPath string, files []*ast.File) ([]*LinkSym, error) { 49 var links []*LinkSym 50 for _, file := range files { 51 var hasUnsafe bool 52 for _, imp := range file.Imports { 53 if imp.Path.Value == `"unsafe"` { 54 hasUnsafe = true 55 } 56 } 57 for _, cg := range file.Comments { 58 for _, c := range cg.List { 59 link, err := parseLinknameComment(pkgPath, file, c, hasUnsafe) 60 if err != nil { 61 return nil, fmt.Errorf("%s: %w", fset.Position(c.Pos()), err) 62 } 63 if link != nil { 64 links = append(links, link) 65 } 66 } 67 } 68 } 69 return links, nil 70 } 71 72 func parseLinknameComment(pkgPath string, file *ast.File, comment *ast.Comment, hasUnsafe bool) (*LinkSym, error) { 73 if !strings.HasPrefix(comment.Text, "//go:linkname ") { 74 return nil, nil 75 } 76 if !hasUnsafe { 77 return nil, fmt.Errorf(`//go:linkname only allowed in Go files that import "unsafe"`) 78 } 79 fields := strings.Fields(comment.Text) 80 if len(fields) != 3 { 81 return nil, fmt.Errorf(`usage: //go:linkname localname [linkname]`) 82 } 83 84 localName := fields[1] 85 linkPkg, linkName := "", fields[2] 86 if pos := strings.LastIndexByte(linkName, '/'); pos != -1 { 87 if idx := strings.IndexByte(linkName[pos+1:], '.'); idx != -1 { 88 linkPkg, linkName = linkName[0:pos+idx+1], linkName[pos+idx+2:] 89 } 90 } else if idx := strings.IndexByte(linkName, '.'); idx != -1 { 91 linkPkg, linkName = linkName[0:idx], linkName[idx+1:] 92 } 93 94 obj := file.Scope.Lookup(localName) 95 if obj == nil || (obj.Kind != ast.Fun && obj.Kind != ast.Var) { 96 return nil, fmt.Errorf("//go:linkname must refer to declared function or variable") 97 } 98 99 if pkgPath == linkPkg && localName == linkName { 100 return nil, nil 101 } 102 103 var recv, method string 104 pos := strings.IndexByte(linkName, '.') 105 if pos != -1 { 106 recv, method = linkName[:pos], linkName[pos+1:] 107 size := len(recv) 108 if size > 2 && recv[0] == '(' && recv[size-1] == ')' { 109 recv = recv[1 : size-1] 110 } 111 } 112 return &LinkSym{ 113 Kind: obj.Kind, 114 PkgPath: pkgPath, 115 Name: localName, 116 Linkname: Linkname{PkgPath: linkPkg, Name: linkName, Recv: recv, Method: method}, 117 }, nil 118 }