github.com/goplus/igop@v0.25.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 // //go:linkname <localname> linkname indicate by runtime package 49 func ParseLinkname(fset *token.FileSet, pkgPath string, files []*ast.File) ([]*LinkSym, error) { 50 var links []*LinkSym 51 for _, file := range files { 52 var hasUnsafe bool 53 for _, imp := range file.Imports { 54 if imp.Path.Value == `"unsafe"` { 55 hasUnsafe = true 56 } 57 } 58 for _, cg := range file.Comments { 59 for _, c := range cg.List { 60 link, err := parseLinknameComment(pkgPath, file, c, hasUnsafe) 61 if err != nil { 62 return nil, fmt.Errorf("%s: %w", fset.Position(c.Pos()), err) 63 } 64 if link != nil { 65 links = append(links, link) 66 } 67 } 68 } 69 } 70 return links, nil 71 } 72 73 func parseLinknameComment(pkgPath string, file *ast.File, comment *ast.Comment, hasUnsafe bool) (*LinkSym, error) { 74 if !strings.HasPrefix(comment.Text, "//go:linkname ") { 75 return nil, nil 76 } 77 if !hasUnsafe { 78 return nil, fmt.Errorf(`//go:linkname only allowed in Go files that import "unsafe"`) 79 } 80 fields := strings.Fields(comment.Text) 81 if n := len(fields); n != 3 { 82 if n == 2 { 83 // //go:linkname <localname> 84 return nil, nil 85 } 86 return nil, fmt.Errorf(`usage: //go:linkname localname [linkname]`) 87 } 88 89 localName := fields[1] 90 linkPkg, linkName := "", fields[2] 91 if pos := strings.LastIndexByte(linkName, '/'); pos != -1 { 92 if idx := strings.IndexByte(linkName[pos+1:], '.'); idx != -1 { 93 linkPkg, linkName = linkName[0:pos+idx+1], linkName[pos+idx+2:] 94 } 95 } else if idx := strings.IndexByte(linkName, '.'); idx != -1 { 96 linkPkg, linkName = linkName[0:idx], linkName[idx+1:] 97 } 98 99 obj := file.Scope.Lookup(localName) 100 if obj == nil || (obj.Kind != ast.Fun && obj.Kind != ast.Var) { 101 return nil, fmt.Errorf("//go:linkname must refer to declared function or variable") 102 } 103 104 if pkgPath == linkPkg && localName == linkName { 105 return nil, nil 106 } 107 108 var recv, method string 109 pos := strings.IndexByte(linkName, '.') 110 if pos != -1 { 111 recv, method = linkName[:pos], linkName[pos+1:] 112 size := len(recv) 113 if size > 2 && recv[0] == '(' && recv[size-1] == ')' { 114 recv = recv[1 : size-1] 115 } 116 } 117 return &LinkSym{ 118 Kind: obj.Kind, 119 PkgPath: pkgPath, 120 Name: localName, 121 Linkname: Linkname{PkgPath: linkPkg, Name: linkName, Recv: recv, Method: method}, 122 }, nil 123 }