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  }