Files
garble/scripts/gen_go_std_tables.go
T
Daniel Martí 7114403786 drop Go 1.25, support Go 1.26
Beyond the usual updating of version strings, re-running `go generate`,
and updating the patches for the linker, we needed extra changes.

First, the linker started being stricter about which packages
are allowed to linkname runtime names by import path.
Stop obfuscating import paths in runtimeAndLinknamed.

Second, the compiler's intrinsics code adds an "add" function
much like the existing "addF", so tweak the regular expression.

Third, note that there is a new runtimeAndDeps windows-only package,
which we find thanks to the recent improvement to cover many platforms.

Fourth, the code around magic numbers in the runtime has changed,
so rewrite the code which patches it via go/ast.

Fixes #990.
2026-03-15 23:36:56 +01:00

277 lines
8.0 KiB
Go

// Copyright (c) 2024, The Garble Authors.
// See LICENSE for licensing information.
//go:build ignore
// This is a program used with `go generate`, so it handles errors via panic.
package main
import (
"bytes"
"cmp"
"fmt"
"go/format"
"go/version"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
"text/template"
)
type goPlatform struct {
GOOS, GOARCH string
Priority int // smaller wins when deduplicating
}
func (p goPlatform) String() string {
return fmt.Sprintf("%s/%s", p.GOOS, p.GOARCH)
}
// Cover the three main OSes and a variety of x86/Arm and 64/32 bit platforms
// when calling `go list` commands to ensure we find all relevant packages.
var goPlatforms = []goPlatform{
{"linux", "amd64", 1},
{"darwin", "arm64", 2},
{"windows", "386", 3},
}
var goVersions = []string{"go1.26.1"}
var tmplTables = template.Must(template.New("").Parse(`
// Code generated by scripts/gen_go_std_tables.go; DO NOT EDIT.
// Generated from Go versions {{ .GoVersions }}.
package main
// runtimeAndDeps contains the runtime package and all of its transitive dependencies
// as reported by 'go list -deps'.
var runtimeAndDeps = map[string]bool{
{{- range $path := .RuntimeAndDeps }}
"{{ $path.String }}": true, // {{ $path.GoVersionLang }} {{ $path.Platform }}
{{- end }}
}
// runtimeAndLinknamed contains the runtime package and all the packages
// which it points to via //go:linkname directives.
// We need to track these as some are not imported as transitive dependencies,
// and we need to load these to properly obfuscate the linkname target names.
//
// Note that runtimeAndLinknamed may contain duplicates with runtimeAndDeps.
// This is on purpose; some packages are in runtimeAndDeps via 'go list -deps'
// but not transitively imported on some platforms, even though they are used
// from the runtime package via //go:linkname directives on those platforms.
// To make sure we have coverage on all platforms, we allow duplicates.
var runtimeAndLinknamed = map[string]bool{
{{- range $path := .RuntimeAndLinknamed }}
"{{ $path.String }}": true, // {{ $path.GoVersionLang }}
{{- end }}
// The net package linknames to the runtime, not the other way around.
"net": true,
// The testing package uses a //go:linkname directive pointing to testing/synctest,
// but it doesn't import the package, presumably to avoid an import cycle.
"testing/synctest": true,
}
var compilerIntrinsics = map[string]map[string]bool{
{{- range $intr := .CompilerIntrinsics }}
"{{ $intr.Path }}": {
{{- range $name := $intr.Names }}
"{{ $name.String }}": true, // {{ $name.GoVersionLang }}
{{- end }}
},
{{- end }}
}
var reflectSkipPkg = map[string]bool{
"fmt": true,
}
`[1:]))
type tmplData struct {
GoVersions []string
RuntimeAndDeps []versionedString
RuntimeAndLinknamed []versionedString
CompilerIntrinsics []tmplIntrinsic
}
type tmplIntrinsic struct {
Path string
Names []versionedString
}
func (t tmplIntrinsic) Compare(t2 tmplIntrinsic) int {
return cmp.Compare(t.Path, t2.Path)
}
func (t tmplIntrinsic) Equal(t2 tmplIntrinsic) bool {
return t.Compare(t2) == 0
}
type versionedString struct {
String string
GoVersionLang string
Platform goPlatform
}
func (v versionedString) Compare(v2 versionedString) int {
if c := cmp.Compare(v.String, v2.String); c != 0 {
return c
}
// Negated so that newer Go versions go first.
if c := -cmp.Compare(v.GoVersionLang, v2.GoVersionLang); c != 0 {
return c
}
// Platforms with a lower priority go first.
return cmp.Compare(v.Platform.Priority, v2.Platform.Priority)
}
func (v versionedString) Equal(v2 versionedString) bool {
// Note that we do equality based on String alone,
// because we only need one String entry with the latest version.
return v.String == v2.String
}
func cmdGo(goVersion string, platform goPlatform, args ...string) versionedString {
cmd := exec.Command("go", args...)
cmd.Env = append(cmd.Environ(), "GOTOOLCHAIN="+goVersion)
if platform.GOOS != "" {
cmd.Env = append(cmd.Env, "GOOS="+platform.GOOS, "GOARCH="+platform.GOARCH)
}
out, err := cmd.Output()
if err != nil {
panic(err)
}
return versionedString{
String: string(bytes.TrimSpace(out)), // no trailing newline
GoVersionLang: version.Lang(goVersion),
Platform: platform,
}
}
func readFile(path string) string {
data, err := os.ReadFile(path)
if err != nil {
panic(err)
}
return string(data)
}
func lines(vs versionedString) []versionedString {
split := strings.Split(vs.String, "\n")
var versioned []versionedString
for _, s := range split {
versioned = append(versioned, versionedString{
String: s,
GoVersionLang: vs.GoVersionLang,
Platform: vs.Platform,
})
}
return versioned
}
var rxLinkname = regexp.MustCompile(`^//go:linkname .* ([^.]*)\.[^.]*$`)
var rxIntrinsic = regexp.MustCompile(`\b(add|addF|alias)\("([^"]*)", "([^"]*)",`)
func main() {
var runtimeAndDeps []versionedString
for _, goVersion := range goVersions {
for _, platform := range goPlatforms {
runtimeAndDeps = append(runtimeAndDeps, lines(cmdGo(goVersion, platform, "list", "-deps", "runtime"))...)
}
}
slices.SortFunc(runtimeAndDeps, versionedString.Compare)
runtimeAndDeps = slices.CompactFunc(runtimeAndDeps, versionedString.Equal)
var goroots []versionedString
for _, goVersion := range goVersions {
goroots = append(goroots, cmdGo(goVersion, goPlatform{}, "env", "GOROOT"))
}
// All packages that the runtime linknames to, except runtime and its dependencies.
// This resulting list is what we need to "go list" when obfuscating the runtime,
// as they are the packages that we may be missing.
var runtimeAndLinknamed []versionedString
for _, goroot := range goroots {
runtimeGoFiles, err := filepath.Glob(filepath.Join(goroot.String, "src", "runtime", "*.go"))
if err != nil {
panic(err)
}
for _, goFile := range runtimeGoFiles {
for line := range strings.SplitSeq(readFile(goFile), "\n") {
m := rxLinkname.FindStringSubmatch(line)
if m == nil {
continue
}
path := m[1]
switch path {
case "main", "runtime/metrics_test":
continue
}
runtimeAndLinknamed = append(runtimeAndLinknamed, versionedString{
String: path,
GoVersionLang: goroot.GoVersionLang,
})
}
}
}
slices.SortFunc(runtimeAndLinknamed, versionedString.Compare)
runtimeAndLinknamed = slices.CompactFunc(runtimeAndLinknamed, versionedString.Equal)
compilerIntrinsicsIndexByPath := make(map[string]int)
var compilerIntrinsics []tmplIntrinsic
for _, goroot := range goroots {
for line := range strings.SplitSeq(readFile(filepath.Join(
goroot.String, "src", "cmd", "compile", "internal", "ssagen", "intrinsics.go",
)), "\n") {
m := rxIntrinsic.FindStringSubmatch(line)
if m == nil {
continue
}
path, name := m[2], m[3]
vs := versionedString{
String: name,
GoVersionLang: goroot.GoVersionLang,
}
if i, ok := compilerIntrinsicsIndexByPath[path]; !ok {
compilerIntrinsicsIndexByPath[path] = len(compilerIntrinsics)
compilerIntrinsics = append(compilerIntrinsics, tmplIntrinsic{
Path: path,
Names: []versionedString{vs},
})
} else {
compilerIntrinsics[i].Names = append(compilerIntrinsics[i].Names, vs)
}
}
}
slices.SortFunc(compilerIntrinsics, tmplIntrinsic.Compare)
compilerIntrinsics = slices.CompactFunc(compilerIntrinsics, tmplIntrinsic.Equal)
for path := range compilerIntrinsics {
intr := &compilerIntrinsics[path]
slices.SortFunc(intr.Names, versionedString.Compare)
intr.Names = slices.CompactFunc(intr.Names, versionedString.Equal)
}
var buf bytes.Buffer
if err := tmplTables.Execute(&buf, tmplData{
GoVersions: goVersions,
RuntimeAndDeps: runtimeAndDeps,
RuntimeAndLinknamed: runtimeAndLinknamed,
CompilerIntrinsics: compilerIntrinsics,
}); err != nil {
panic(err)
}
out := buf.Bytes()
formatted, err := format.Source(out)
if err != nil {
fmt.Println(string(out))
panic(err)
}
if err := os.WriteFile("go_std_tables.go", formatted, 0o666); err != nil {
panic(err)
}
}