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

View as plain text