golang.org/x/tools/gopls@v0.15.3/internal/golang/linkname.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package golang 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "go/token" 12 "strings" 13 14 "golang.org/x/tools/gopls/internal/cache" 15 "golang.org/x/tools/gopls/internal/cache/metadata" 16 "golang.org/x/tools/gopls/internal/protocol" 17 "golang.org/x/tools/gopls/internal/util/safetoken" 18 ) 19 20 // ErrNoLinkname is returned by LinknameDefinition when no linkname 21 // directive is found at a particular position. 22 // As such it indicates that other definitions could be worth checking. 23 var ErrNoLinkname = errors.New("no linkname directive found") 24 25 // LinknameDefinition finds the definition of the linkname directive in m at pos. 26 // If there is no linkname directive at pos, returns ErrNoLinkname. 27 func LinknameDefinition(ctx context.Context, snapshot *cache.Snapshot, m *protocol.Mapper, from protocol.Position) ([]protocol.Location, error) { 28 pkgPath, name, _ := parseLinkname(m, from) 29 if pkgPath == "" { 30 return nil, ErrNoLinkname 31 } 32 33 _, pgf, pos, err := findLinkname(ctx, snapshot, PackagePath(pkgPath), name) 34 if err != nil { 35 return nil, fmt.Errorf("find linkname: %w", err) 36 } 37 loc, err := pgf.PosLocation(pos, pos+token.Pos(len(name))) 38 if err != nil { 39 return nil, fmt.Errorf("location of linkname: %w", err) 40 } 41 return []protocol.Location{loc}, nil 42 } 43 44 // parseLinkname attempts to parse a go:linkname declaration at the given pos. 45 // If successful, it returns 46 // - package path referenced 47 // - object name referenced 48 // - byte offset in mapped file of the start of the link target 49 // of the linkname directives 2nd argument. 50 // 51 // If the position is not in the second argument of a go:linkname directive, 52 // or parsing fails, it returns "", "", 0. 53 func parseLinkname(m *protocol.Mapper, pos protocol.Position) (pkgPath, name string, targetOffset int) { 54 lineStart, err := m.PositionOffset(protocol.Position{Line: pos.Line, Character: 0}) 55 if err != nil { 56 return "", "", 0 57 } 58 lineEnd, err := m.PositionOffset(protocol.Position{Line: pos.Line + 1, Character: 0}) 59 if err != nil { 60 return "", "", 0 61 } 62 63 directive := string(m.Content[lineStart:lineEnd]) 64 // (Assumes no leading spaces.) 65 if !strings.HasPrefix(directive, "//go:linkname") { 66 return "", "", 0 67 } 68 // Sometimes source code (typically tests) has another 69 // comment after the directive, trim that away. 70 if i := strings.LastIndex(directive, "//"); i != 0 { 71 directive = strings.TrimSpace(directive[:i]) 72 } 73 74 // Looking for pkgpath in '//go:linkname f pkgpath.g'. 75 // (We ignore 1-arg linkname directives.) 76 parts := strings.Fields(directive) 77 if len(parts) != 3 { 78 return "", "", 0 79 } 80 81 // Inside 2nd arg [start, end]? 82 // (Assumes no trailing spaces.) 83 offset, err := m.PositionOffset(pos) 84 if err != nil { 85 return "", "", 0 86 } 87 end := lineStart + len(directive) 88 start := end - len(parts[2]) 89 if !(start <= offset && offset <= end) { 90 return "", "", 0 91 } 92 linkname := parts[2] 93 94 // Split the pkg path from the name. 95 dot := strings.LastIndexByte(linkname, '.') 96 if dot < 0 { 97 return "", "", 0 98 } 99 100 return linkname[:dot], linkname[dot+1:], start 101 } 102 103 // findLinkname searches dependencies of packages containing fh for an object 104 // with linker name matching the given package path and name. 105 func findLinkname(ctx context.Context, snapshot *cache.Snapshot, pkgPath PackagePath, name string) (*cache.Package, *ParsedGoFile, token.Pos, error) { 106 // Typically the linkname refers to a forward dependency 107 // or a reverse dependency, but in general it may refer 108 // to any package that is linked with this one. 109 var pkgMeta *metadata.Package 110 metas, err := snapshot.AllMetadata(ctx) 111 if err != nil { 112 return nil, nil, token.NoPos, err 113 } 114 metadata.RemoveIntermediateTestVariants(&metas) 115 for _, meta := range metas { 116 if meta.PkgPath == pkgPath { 117 pkgMeta = meta 118 break 119 } 120 } 121 if pkgMeta == nil { 122 return nil, nil, token.NoPos, fmt.Errorf("cannot find package %q", pkgPath) 123 } 124 125 // When found, type check the desired package (snapshot.TypeCheck in TypecheckFull mode), 126 pkgs, err := snapshot.TypeCheck(ctx, pkgMeta.ID) 127 if err != nil { 128 return nil, nil, token.NoPos, err 129 } 130 pkg := pkgs[0] 131 132 obj := pkg.GetTypes().Scope().Lookup(name) 133 if obj == nil { 134 return nil, nil, token.NoPos, fmt.Errorf("package %q does not define %s", pkgPath, name) 135 } 136 137 objURI := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) 138 pgf, err := pkg.File(protocol.URIFromPath(objURI.Filename)) 139 if err != nil { 140 return nil, nil, token.NoPos, err 141 } 142 143 return pkg, pgf, obj.Pos(), nil 144 }