Source file src/cmd/internal/pkgpath/pkgpath.go

     1  // Copyright 2020 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 pkgpath determines the package path used by gccgo/GoLLVM symbols.
     6  // This package is not used for the gc compiler.
     7  package pkgpath
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"os/exec"
    15  	"strings"
    16  )
    17  
    18  // ToSymbolFunc returns a function that may be used to convert a
    19  // package path into a string suitable for use as a symbol.
    20  // cmd is the gccgo/GoLLVM compiler in use, and tmpdir is a temporary
    21  // directory to pass to os.CreateTemp().
    22  // For example, this returns a function that converts "net/http"
    23  // into a string like "net..z2fhttp". The actual string varies for
    24  // different gccgo/GoLLVM versions, which is why this returns a function
    25  // that does the conversion appropriate for the compiler in use.
    26  func ToSymbolFunc(cmd, tmpdir string) (func(string) string, error) {
    27  	// To determine the scheme used by cmd, we compile a small
    28  	// file and examine the assembly code. Older versions of gccgo
    29  	// use a simple mangling scheme where there can be collisions
    30  	// between packages whose paths are different but mangle to
    31  	// the same string. More recent versions use a new mangler
    32  	// that avoids these collisions.
    33  	const filepat = "*_gccgo_manglechck.go"
    34  	f, err := os.CreateTemp(tmpdir, filepat)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	gofilename := f.Name()
    39  	f.Close()
    40  	defer os.Remove(gofilename)
    41  
    42  	if err := os.WriteFile(gofilename, []byte(mangleCheckCode), 0644); err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	command := exec.Command(cmd, "-S", "-o", "-", gofilename)
    47  	buf, err := command.Output()
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	// Original mangling: go.l__ufer.Run
    53  	// Mangling v2: go.l..u00e4ufer.Run
    54  	// Mangling v3: go_0l_u00e4ufer.Run
    55  	if bytes.Contains(buf, []byte("go_0l_u00e4ufer.Run")) {
    56  		return toSymbolV3, nil
    57  	} else if bytes.Contains(buf, []byte("go.l..u00e4ufer.Run")) {
    58  		return toSymbolV2, nil
    59  	} else if bytes.Contains(buf, []byte("go.l__ufer.Run")) {
    60  		return toSymbolV1, nil
    61  	} else {
    62  		return nil, errors.New(cmd + ": unrecognized mangling scheme")
    63  	}
    64  }
    65  
    66  // mangleCheckCode is the package we compile to determine the mangling scheme.
    67  const mangleCheckCode = `
    68  package läufer
    69  func Run(x int) int {
    70    return 1
    71  }
    72  `
    73  
    74  // toSymbolV1 converts a package path using the original mangling scheme.
    75  func toSymbolV1(ppath string) string {
    76  	clean := func(r rune) rune {
    77  		switch {
    78  		case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z',
    79  			'0' <= r && r <= '9':
    80  			return r
    81  		}
    82  		return '_'
    83  	}
    84  	return strings.Map(clean, ppath)
    85  }
    86  
    87  // toSymbolV2 converts a package path using the second mangling scheme.
    88  func toSymbolV2(ppath string) string {
    89  	var bsl strings.Builder
    90  	changed := false
    91  	for _, c := range ppath {
    92  		if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' {
    93  			bsl.WriteByte(byte(c))
    94  			continue
    95  		}
    96  		var enc string
    97  		switch {
    98  		case c == '.':
    99  			enc = ".x2e"
   100  		case c < 0x80:
   101  			enc = fmt.Sprintf("..z%02x", c)
   102  		case c < 0x10000:
   103  			enc = fmt.Sprintf("..u%04x", c)
   104  		default:
   105  			enc = fmt.Sprintf("..U%08x", c)
   106  		}
   107  		bsl.WriteString(enc)
   108  		changed = true
   109  	}
   110  	if !changed {
   111  		return ppath
   112  	}
   113  	return bsl.String()
   114  }
   115  
   116  // v3UnderscoreCodes maps from a character that supports an underscore
   117  // encoding to the underscore encoding character.
   118  var v3UnderscoreCodes = map[byte]byte{
   119  	'_': '_',
   120  	'.': '0',
   121  	'/': '1',
   122  	'*': '2',
   123  	',': '3',
   124  	'{': '4',
   125  	'}': '5',
   126  	'[': '6',
   127  	']': '7',
   128  	'(': '8',
   129  	')': '9',
   130  	'"': 'a',
   131  	' ': 'b',
   132  	';': 'c',
   133  }
   134  
   135  // toSymbolV3 converts a package path using the third mangling scheme.
   136  func toSymbolV3(ppath string) string {
   137  	var bsl strings.Builder
   138  	changed := false
   139  	for _, c := range ppath {
   140  		if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') {
   141  			bsl.WriteByte(byte(c))
   142  			continue
   143  		}
   144  
   145  		if c < 0x80 {
   146  			if u, ok := v3UnderscoreCodes[byte(c)]; ok {
   147  				bsl.WriteByte('_')
   148  				bsl.WriteByte(u)
   149  				changed = true
   150  				continue
   151  			}
   152  		}
   153  
   154  		var enc string
   155  		switch {
   156  		case c < 0x80:
   157  			enc = fmt.Sprintf("_x%02x", c)
   158  		case c < 0x10000:
   159  			enc = fmt.Sprintf("_u%04x", c)
   160  		default:
   161  			enc = fmt.Sprintf("_U%08x", c)
   162  		}
   163  		bsl.WriteString(enc)
   164  		changed = true
   165  	}
   166  	if !changed {
   167  		return ppath
   168  	}
   169  	return bsl.String()
   170  }
   171  

View as plain text