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  }