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  }