diff --git a/.gitmodules b/.gitmodules index 5dcf2a0b56d..ad144c41ffc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,8 @@ [submodule "go"] path = go url = https://github.com/golang/go + urlInternal = https://dnceng@dev.azure.com/dnceng/internal/_git/microsoft-go-mirror +[submodule "eng/modules/golang.org/x/crypto"] + path = modules/golang.org/x/crypto + url = https://github.com/dagood/golang_xcrypto + urlInternal = https://dnceng@dev.azure.com/dnceng/internal/_git/microsoft-go-xcrypto-fork-placeholder diff --git a/eng/_core/cmd/build/build.go b/eng/_core/cmd/build/build.go index abe731e7e30..dddb41e6ab1 100644 --- a/eng/_core/cmd/build/build.go +++ b/eng/_core/cmd/build/build.go @@ -50,6 +50,15 @@ func main() { &o.Refresh, "refresh", false, "Refresh Go submodule: clean untracked files, reset tracked files, and apply patches before building.\n"+ "For more refresh options, use the top level 'submodule-refresh' command instead of 'build'.") + flag.BoolVar( + &o.SkipMSMod, "skipmsmod", false, + "Skip creating the _ms_mod directory with the x/crypto fork to be used by the XCryptoSwap experiment.\n"+ + "Note: tests will fail. The patched standard library test suite will notice if the the backend isn't used for x/crypto.\n"+ + "A alternate way to fix tests without _ms_mod generation is to set GOMSMODROOT to a manually created fork.") + flag.BoolVar( + &o.CleanMSMod, "cleanmsmod", false, + "Remove _ms_mod directory when the build is complete.\n"+ + "This may be useful during development to run tests without polluting the Go submodule's working tree.") flag.StringVar(&o.Experiment, "experiment", "", "Include this string in GOEXPERIMENT.") @@ -84,6 +93,8 @@ type options struct { PackBuild bool PackSource bool Refresh bool + SkipMSMod bool + CleanMSMod bool Experiment string MaxMakeAttempts int @@ -120,6 +131,21 @@ func build(o *options) error { } } + if !o.SkipMSMod { + fmt.Println("---- Generating _ms_mod...") + if err := submodule.GenerateMSMod(rootDir); err != nil { + return fmt.Errorf("failed to generate _ms_mod: %v", err) + } + } + if o.CleanMSMod { + defer func() { + fmt.Println("---- Cleanup: removing _ms_mod directory...") + if err := submodule.RemoveMSMod(rootDir); err != nil { + fmt.Printf("---- Error removing _ms_mod directory: %v\n", err) + } + }() + } + // Get the target platform information. If the environment variable is different from the // runtime value, this means we're doing a cross-compiled build. These values are used for // capability checks and to make sure that if Pack is enabled, the output archive is formatted diff --git a/eng/_core/cmd/submodule-refresh/submodule-refresh.go b/eng/_core/cmd/submodule-refresh/submodule-refresh.go index 9566fb11511..f06bff0a732 100644 --- a/eng/_core/cmd/submodule-refresh/submodule-refresh.go +++ b/eng/_core/cmd/submodule-refresh/submodule-refresh.go @@ -20,7 +20,7 @@ applies patches to the stage by default, or optionally as commits. var commits = flag.Bool("commits", false, "Apply the patches as commits.") var skipPatch = flag.Bool("skip-patch", false, "Skip applying patches.") -var origin = flag.String("origin", "", "Use this origin instead of the default defined in '.gitmodules' to fetch the repository.") +var internal = flag.Bool("internal", false, "Use the .gitmodules urlInternal instead of url to clone submodules.") var shallow = flag.Bool("shallow", false, "Clone the submodule with depth 1.") var fetchBearerToken = flag.String("fetch-bearer-token", "", "Use this bearer token to fetch the submodule repository.") @@ -50,7 +50,7 @@ func main() { } func refresh(rootDir string) error { - if err := submodule.Init(rootDir, *origin, *fetchBearerToken, *shallow); err != nil { + if err := submodule.Init(rootDir, *internal, *fetchBearerToken, *shallow); err != nil { return err } diff --git a/eng/_core/go.mod b/eng/_core/go.mod index 62207065ccf..23a7e777323 100644 --- a/eng/_core/go.mod +++ b/eng/_core/go.mod @@ -4,4 +4,8 @@ module github.com/microsoft/go/_core -go 1.16 +go 1.18 + +require github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d + +require golang.org/x/tools v0.1.12 // indirect diff --git a/eng/_core/go.sum b/eng/_core/go.sum new file mode 100644 index 00000000000..58499e86700 --- /dev/null +++ b/eng/_core/go.sum @@ -0,0 +1,5 @@ +github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d h1:2dYhK+YQIwmN5n/AImhvU6Krn48vgWGCcuvwLb1wEJ4= +github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d/go.mod h1:hGTlq0ZBZVmZwHgV+JezVdi8jZ2f4/UydeYvMVjAJLM= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/eng/_core/submodule/submodule.go b/eng/_core/submodule/submodule.go index 6fb4d54bb03..8cec5752d33 100644 --- a/eng/_core/submodule/submodule.go +++ b/eng/_core/submodule/submodule.go @@ -9,17 +9,25 @@ import ( "os" "os/exec" "path/filepath" + "strings" + + "github.com/microsoft/go-infra/executil" + "github.com/microsoft/go-infra/xcryptofork" ) // Init initializes and updates the submodule, but does not clean it. This func offers more options // for initialization than Reset. If origin is defined, fetch the submodule from there instead of // the default defined in '.gitmodules'. If fetchBearerToken is nonempty, use it as a bearer token // during the fetch. If shallow is true, clone the submodule with depth 1. -func Init(rootDir, origin, fetchBearerToken string, shallow bool) error { +func Init(rootDir string, internal bool, fetchBearerToken string, shallow bool) error { // Update the submodule commit, and initialize if it hasn't been done already. command := []string{"git"} - if origin != "" { - command = append(command, "-c", "submodule.go.url="+origin) + if internal { + var err error + command, err = appendURLInternalGitConfigArgs(rootDir, command) + if err != nil { + return err + } } if fetchBearerToken != "" { command = append(command, "-c", "http.extraheader=AUTHORIZATION: bearer "+fetchBearerToken) @@ -44,23 +52,10 @@ func Reset(rootDir string) error { return err } - // Find toplevel directories (Git working tree roots) for the outer repo and what we expect to - // be the Go submodule. If the toplevel directory is the same for both, make sure not to clean! - // The submodule likely wasn't set up properly, and cleaning could result in unexpectedly losing - // work in the outer repo when the command spills over. - rootToplevel, err := getToplevel(rootDir) - if err != nil { - return err - } - goToplevel, err := getToplevel(goDir) - if err != nil { + if err := assertSubmoduleInitialized(rootDir, goDir); err != nil { return err } - if rootToplevel == goToplevel { - return fmt.Errorf("go submodule (%v) toplevel is the same as root (%v) toplevel: %v", goDir, rootDir, goToplevel) - } - // Reset the index and working directory. This doesn't clean up new untracked files. if err := run(goDir, "git", "reset", "--hard"); err != nil { return err @@ -74,6 +69,170 @@ func Reset(rootDir string) error { return nil } +// GenerateMSMod creates the _ms_mod directory with x/crypto fork and generates +// the crypto backend proxies based on the backends currently in the Go +// submodule source tree. +func GenerateMSMod(rootDir string) error { + cryptoSrcDir := filepath.Join(rootDir, "modules", "golang.org", "x", "crypto") + if err := assertSubmoduleInitialized(rootDir, cryptoSrcDir); err != nil { + return err + } + goDir := filepath.Join(rootDir, "go") + if err := assertSubmoduleInitialized(rootDir, goDir); err != nil { + return err + } + // Target directory for the x/crypto fork. + cryptoDir := filepath.Join(goDir, "_ms_mod", "golang.org", "x", "crypto") + // Clean it up. No prompt: there shouldn't be any need to do dev work in + // this directory. + if err := os.RemoveAll(cryptoDir); err != nil { + return err + } + if err := os.MkdirAll(cryptoDir, 0o777); err != nil { + return err + } + if err := xcryptofork.GitCheckoutTo(cryptoSrcDir, cryptoDir); err != nil { + return err + } + // Generate the backend proxies and the nobackend file based on the backends + // in the active Go tree. The placeholder in x/crypto is ignored: it's only + // there so the x/crypto fork will compile outside this context. + backendDir := filepath.Join(goDir, "src", "crypto", "internal", "backend") + backends, err := xcryptofork.FindBackendFiles(backendDir) + if err != nil { + return fmt.Errorf("failed to find backend files in %q: %v", backendDir, err) + } + proxyDir := filepath.Join(cryptoDir, "internal", "backend") + if err := os.RemoveAll(proxyDir); err != nil { + return err + } + // First, find the nobackend. It defines the API for the backend proxies. + const nobackendBase = "nobackend.go" + var backendAPI *xcryptofork.BackendFile + for _, b := range backends { + if filepath.Base(b.Filename) == nobackendBase { + if err := b.APITrim(); err != nil { + return fmt.Errorf("failed to trim %q into an API for proxies: %v", b.Filename, err) + } + // If someone uses the x/crypto fork but doesn't use a backend, they + // will need this nobackend.go for their build to succeed. + if err := writeBackend(b, filepath.Join(proxyDir, nobackendBase)); err != nil { + return fmt.Errorf("failed to write API based on %q: %v", b.Filename, err) + } + backendAPI = b + break + } + } + if backendAPI == nil { + return fmt.Errorf("%q not found in %v", nobackendBase, backendDir) + } + // Create a proxy for each backend. + for _, b := range backends { + if b == backendAPI { + continue + } + proxy, err := b.ProxyAPI(backendAPI) + if err != nil { + return fmt.Errorf("failed to turn %q into a proxy: %v", b.Filename, err) + } + if err := writeBackend(proxy, filepath.Join(proxyDir, filepath.Base(b.Filename))); err != nil { + return fmt.Errorf("failed to write proxy based on %q: %v", b.Filename, err) + } + } + // Also generate a placeholder based on nobackend for the x/crypto fork + // itself. This is intended to be used during dev so x/crypto will build, + // but avoid making it specific to the proxies generated for our fork. + backendAPI.Constraint = "" + placeholderDir := filepath.Join(rootDir, "eng", "artifacts", "xcrypto-backend-placeholder") + if err := os.MkdirAll(placeholderDir, 0o777); err != nil { + return err + } + if err := writeBackend(backendAPI, filepath.Join(placeholderDir, "placeholder.go")); err != nil { + return fmt.Errorf("failed to write placeholder backend based on %q: %v", backendAPI.Filename, err) + } + return nil +} + +func RemoveMSMod(rootDir string) error { + msMod := filepath.Join(rootDir, "go", "_ms_mod") + entries, err := os.ReadDir(msMod) + if err != nil { + return err + } + for _, e := range entries { + if !e.IsDir() { + continue + } + if err := os.RemoveAll(filepath.Join(msMod, e.Name())); err != nil { + return err + } + } + return nil +} + +func writeBackend(b xcryptofork.FormattedWriterTo, path string) error { + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { + return err + } + apiFile, err := os.Create(path) + if err != nil { + return err + } + err = b.Format(apiFile) + if err2 := apiFile.Close(); err == nil { + err = err2 + } + return err +} + +func appendURLInternalGitConfigArgs(rootDir string, args []string) ([]string, error) { + cmd := exec.Command( + "git", "config", + "-f", ".gitmodules", + "-z", // Null char separator: avoid confusion with newlines in values. + "--get-regexp", `submodule\..*\.urlInternal`) + cmd.Dir = rootDir + out, err := executil.CombinedOutput(cmd) + if err != nil { + return nil, err + } + pairs := strings.Split(out, "\x00") + for _, pair := range pairs { + key, value, ok := strings.Cut(pair, "\n") + if !ok { + return nil, fmt.Errorf("invalid key-value pair: %v", pair) + } + args = append(args, "-c", strings.TrimSuffix(key, "Internal")+"="+value) + } + return args, nil +} + +// assertSubmoduleInitialized runs a basic check to ensure the submodule within +// the specified root repo is initialized. It finds toplevel directories (Git +// working tree roots) for the outer repo and what we expect to be the Go +// submodule. If the toplevel directory is the same for both, the submodule +// likely wasn't set up properly, and (e.g.) cleaning the submodule dir could +// result in unexpectedly losing work in the rootDir when the command spills +// over and affects the outer repo. +// +// There may be other ways to check whether the submodule is initialized, but +// this check at the very least helps at the most painful potential symptom of +// an uninitialized submodule: lost work. +func assertSubmoduleInitialized(rootDir, submoduleRootDir string) error { + rootToplevel, err := getToplevel(rootDir) + if err != nil { + return err + } + submoduleToplevel, err := getToplevel(submoduleRootDir) + if err != nil { + return err + } + if rootToplevel == submoduleToplevel { + return fmt.Errorf("go submodule (%v) toplevel is the same as root (%v) toplevel: %v", submoduleToplevel, rootDir, submoduleToplevel) + } + return nil +} + func getToplevel(dir string) (string, error) { c := exec.Command("git", "rev-parse", "--show-toplevel") c.Dir = dir diff --git a/eng/_core/vendor/github.com/microsoft/go-infra/LICENSE b/eng/_core/vendor/github.com/microsoft/go-infra/LICENSE new file mode 100644 index 00000000000..9e841e7a26e --- /dev/null +++ b/eng/_core/vendor/github.com/microsoft/go-infra/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/eng/_core/vendor/github.com/microsoft/go-infra/executil/executil.go b/eng/_core/vendor/github.com/microsoft/go-infra/executil/executil.go new file mode 100644 index 00000000000..fd46a6d13fe --- /dev/null +++ b/eng/_core/vendor/github.com/microsoft/go-infra/executil/executil.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package executil contains some common wrappers for simple use of exec.Cmd. +package executil + +import ( + "fmt" + "os" + "os/exec" + "strings" + "time" +) + +// Run sets up the command to log directly to our stdout/stderr streams, then runs it. +func Run(c *exec.Cmd) error { + c.Stdout = os.Stdout + c.Stderr = os.Stderr + return RunQuiet(c) +} + +// RunQuiet logs the command line and runs the given command, but sends the output to os.DevNull. +func RunQuiet(c *exec.Cmd) error { + fmt.Printf("---- Running command: %v %v\n", c.Path, c.Args) + return c.Run() +} + +// CombinedOutput runs a command and returns the output string of c.CombinedOutput. +func CombinedOutput(c *exec.Cmd) (string, error) { + fmt.Printf("---- Running command: %v %v\n", c.Path, c.Args) + out, err := c.CombinedOutput() + if err != nil { + return "", err + } + return string(out), nil +} + +// SpaceTrimmedCombinedOutput runs CombinedOutput and trims leading/trailing spaces from the result. +func SpaceTrimmedCombinedOutput(c *exec.Cmd) (string, error) { + out, err := CombinedOutput(c) + if err != nil { + return "", err + } + return strings.TrimSpace(out), nil +} + +// Dir returns a command that runs in the given dir. The command can be passed to one of the other +// funcs in this package to evaluate it and optionally get the output as a string. Dir is useful to +// construct one-liner command calls, because setting the dir is commonly needed and not settable +// with exec.Command directly. +func Dir(dir, name string, args ...string) *exec.Cmd { + cmd := exec.Command(name, args...) + cmd.Dir = dir + return cmd +} + +// MakeWorkDir creates a unique path inside the given root dir to use as a workspace. The name +// starts with the local time in a sortable format to help with browsing multiple workspaces. This +// function allows a command to run multiple times in sequence without overwriting or deleting the +// old data, for diagnostic purposes. This function uses os.MkdirAll to ensure the root dir exists. +func MakeWorkDir(rootDir string) (string, error) { + pathDate := time.Now().Format("2006-01-02_15-04-05") + if err := os.MkdirAll(rootDir, os.ModePerm); err != nil { + return "", err + } + return os.MkdirTemp(rootDir, fmt.Sprintf("%s_*", pathDate)) +} diff --git a/eng/_core/vendor/github.com/microsoft/go-infra/xcryptofork/backend.go b/eng/_core/vendor/github.com/microsoft/go-infra/xcryptofork/backend.go new file mode 100644 index 00000000000..18dfd045f60 --- /dev/null +++ b/eng/_core/vendor/github.com/microsoft/go-infra/xcryptofork/backend.go @@ -0,0 +1,535 @@ +package xcryptofork + +import ( + "errors" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io" + "path/filepath" + "strconv" + "strings" + + "golang.org/x/tools/go/ast/astutil" +) + +// XCryptoBackendProxyPath is the path within an x/crypto fork of the backend proxy. +var XCryptoBackendProxyPath = filepath.Join("internal", "backend") + +// xCryptoBackendMapPrefix is the prefix for command comments. It would be nice +// to omit the " ", but the Go formatter adds it back in. (Sometimes? It does +// in VS Code. It doesn't seem like Go formatters should, though.) +const xCryptoBackendMapPrefix = "// xcrypto_backend_map:" + +func commands(n ast.Node) []string { + var cmds []string + ast.Inspect(n, func(n ast.Node) bool { + if n, ok := n.(*ast.Comment); !ok { + return true + } else if cmd, ok := strings.CutPrefix(n.Text, xCryptoBackendMapPrefix); !ok { + return true + } else { + cmds = append(cmds, cmd) + } + return false + }) + return cmds +} + +// FindBackendFiles returns the Go files that appear to be backends in the +// given directory. Returns the parsed trees rather than only the filenames: we +// parsed the file to determine if it's a backend, and the parsed data is +// useful later. +func FindBackendFiles(dir string) ([]*BackendFile, error) { + matches, err := filepath.Glob(filepath.Join(dir, "*.go")) + if err != nil { + return nil, err + } + var backends []*BackendFile + for _, match := range matches { + b, err := NewBackendFile(match) + if err != nil { + if errors.Is(err, errNotBackend) { + continue + } + return nil, err + } + backends = append(backends, b) + } + return backends, nil +} + +type FormattedWriterTo interface { + Format(w io.Writer) error +} + +var errNotBackend = errors.New("not a crypto backend file") + +type BackendFile struct { + // Filename is the absolute path to the original file. + Filename string + Constraint string + + f *ast.File + fset *token.FileSet + + enabledDecl *ast.ValueSpec +} + +func NewBackendFile(filename string) (*BackendFile, error) { + b := &BackendFile{ + Filename: filename, + fset: token.NewFileSet(), + } + f, err := parser.ParseFile(b.fset, filename, nil, parser.ParseComments) + if err != nil { + return nil, err + } + b.f = f + // Super simple heuristic that works for "crypto/internal/backend": does + // the file define "Enabled"? + enabledObj := f.Scope.Lookup("Enabled") + if enabledObj == nil { + return nil, errNotBackend + } + var ok bool + if b.enabledDecl, ok = enabledObj.Decl.(*ast.ValueSpec); !ok { + return nil, fmt.Errorf( + "found Enabled symbol, but not a ValueSpec: %q defined at %v", + enabledObj.Name, b.fset.Position(enabledObj.Pos())) + } + // Preserve the build constraint. + for _, cg := range f.Comments { + for _, c := range cg.List { + if strings.HasPrefix(c.Text, "//go:build ") { + b.Constraint = c.Text + break + } + } + } + return b, nil +} + +// APITrim changes b to include a placeholder API, following conventions that +// assume b is a "nobackend" crypto backend. The placeholder API is buildable, +// but panics if used. +func (b *BackendFile) APITrim() error { + var err error + localPackageType := make(map[string]*ast.TypeSpec) + _ = astutil.Apply(b.f, func(c *astutil.Cursor) bool { + switch n := (c.Node()).(type) { + // Only look into top-level declarations, nothing else. + case *ast.File, *ast.GenDecl: + return true + + case *ast.TypeSpec: + // Remove type names declared in this package and keep track of + // them to remove any functions that use them in another pass. + localPackageType[n.Name.Name] = n + c.Delete() + + case *ast.ValueSpec: + // Remove all var/const declarations other than Enabled. + declaresEnabled := false + for _, name := range n.Names { + if name.Name == "Enabled" { + declaresEnabled = true + } + } + if !declaresEnabled { + c.Delete() + } else if len(n.Names) != 1 { + err = fmt.Errorf( + "declaration for Enabled %v includes multiple names", + b.fset.Position(n.Pos())) + } + // We could detect "const RandReader = ..." and change it to + // "var RandReader io.Reader". go:linkname supports mapping a var + // to a const in this way. However, this is already accessible via + // "crypto/rand" and there is no need to provide direct access. + // So, simply leave it out. + } + return false + }, func(c *astutil.Cursor) bool { + switch n := (c.Node()).(type) { + case *ast.GenDecl: + // Removing a ValueSpec or TypeSpec could leave a node with zero + // specs. format.Node fails if there are zero specs. Clean it up. + if len(n.Specs) == 0 { + c.Delete() + } + } + return true + }) + if err != nil { + return err + } + _ = astutil.Apply(b.f, func(c *astutil.Cursor) bool { + switch n := (c.Node()).(type) { + case *ast.File: + return true + case *ast.FuncDecl: + // Remove unexported functions and all methods. + if !n.Name.IsExported() || n.Recv != nil { + c.Delete() + return false + } + var remove bool + ast.Inspect(n.Type, func(tn ast.Node) bool { + switch tn := tn.(type) { + case *ast.Ident: + if _, ok := localPackageType[tn.Name]; ok { + remove = true + return false + } + } + return true + }) + if remove { + c.Delete() + } + } + return false + }, nil) + return cleanImports(b.f) +} + +// ProxyAPI creates a proxy for b implementing each var/func in the given api. +// If b is missing some part of api, it is skipped and recorded in the returned +// BackendProxy to be included in a comment by Format. +// +// If a func in b uses the "noescape" command, the proxy includes +// "//go:noescape" on that func. +func (b *BackendFile) ProxyAPI(api *BackendFile) (*BackendProxy, error) { + p := &BackendProxy{ + backend: b, + api: api, + f: &ast.File{Name: b.f.Name}, + fset: token.NewFileSet(), + } + + // Keep track of the first err hit by each AST walk in this variable. + // Note that walks don't necessarily stop immediately when "return false" is + // used, so take care that an error isn't cleared out by a later iteration. + var err error + failFalse := func(walkErr error) bool { + if err == nil && walkErr != nil { + err = walkErr + } + return false + } + + // Copy the imports that are used to define the API. + // Ignore the imports used by b: those will include internal packages and + // backend-specific packages that we don't have access to. + ast.Inspect(api.f, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.File: + return true + case *ast.GenDecl: + if n.Tok == token.IMPORT { + return true + } + case *ast.ImportSpec: + var name string + if n.Name != nil { + name = n.Name.Name + } + path, err := strconv.Unquote(n.Path.Value) + if err != nil { + return failFalse(err) + } + astutil.AddNamedImport(p.fset, p.f, name, path) + } + return false + }) + if err != nil { + return nil, err + } + + // Add unsafe import needed for go:linkname. + astutil.AddNamedImport(p.fset, p.f, "_", "unsafe") + + // Add Enabled const. + if len(b.enabledDecl.Values) != 1 { + return nil, fmt.Errorf( + "declaration for Enabled %v includes 0 or multiple values", + b.fset.Position(b.enabledDecl.Pos())) + } + v, err := deepCopyExpression(b.enabledDecl.Values[0]) + if err != nil { + return nil, err + } + p.f.Decls = append(p.f.Decls, &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: "Enabled"}}, + Values: []ast.Expr{v}, + }, + }, + }) + + // For each API, find it in b. If exists, generate linkname "proxy" func. + ast.Inspect(api.f, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.File: + return true + case *ast.FuncDecl: + apiFnType, err := deepCopyExpression(n.Type) + if err != nil { + return failFalse(err) + } + // Find the corresponding func in b. + o := b.f.Scope.Lookup(n.Name.Name) + if o == nil { + p.missing = append(p.missing, n) + p.f.Decls = append(p.f.Decls, + newPanicFunc(n, apiFnType, "not implemented by this backend")) + return false + } + fn, ok := o.Decl.(*ast.FuncDecl) + if !ok { + return failFalse(fmt.Errorf( + "found symbol, but not a function: %q defined at %v", + n.Name.Name, api.fset.Position(n.Pos()))) + } + comments := []*ast.Comment{ + {Text: "//go:linkname " + n.Name.Name + " crypto/internal/backend." + n.Name.Name}, + } + for _, cmd := range commands(fn) { + switch cmd { + case "noescape": + comments = append(comments, &ast.Comment{Text: "//go:noescape"}) + default: + return failFalse(fmt.Errorf("unknown command %q (%v)", cmd, b.fset.Position(n.Pos()))) + } + } + proxyFnType, err := deepCopyExpression(fn.Type) + if err != nil { + return failFalse(err) + } + proxyFn := &ast.FuncDecl{ + // Don't use the original data: make sure the token position is + // not copied. Including a non-zero position causes the + // formatter to write the comment in strange locations within + // the function declaration: it tries to reconcile specific + // token positions vs. the zero position of the comment. + Name: ast.NewIdent(n.Name.Name), + Type: proxyFnType, + Doc: &ast.CommentGroup{List: comments}, + } + p.f.Decls = append(p.f.Decls, proxyFn) + } + return false + }) + if err != nil { + return nil, err + } + + if err := cleanImports(p.f); err != nil { + return nil, err + } + return p, nil +} + +func (b *BackendFile) Format(w io.Writer) error { + io.WriteString(w, "// Generated code. DO NOT EDIT.\n\n") + if b.Constraint != "" { + io.WriteString(w, b.Constraint) + io.WriteString(w, "\n\n") + } + return write(b.f, b.fset, w) +} + +type BackendProxy struct { + backend *BackendFile + api *BackendFile + + f *ast.File + fset *token.FileSet + + missing []*ast.FuncDecl +} + +func (p *BackendProxy) Format(w io.Writer) error { + io.WriteString(w, "// Generated code. DO NOT EDIT.\n\n") + io.WriteString(w, "// This file implements a proxy that links into a specific crypto backend.\n\n") + if p.backend.Constraint != "" { + io.WriteString(w, p.backend.Constraint) + io.WriteString(w, "\n\n") + } + if len(p.missing) > 0 { + io.WriteString(w, "// The following functions defined in the API are not implemented by the backend and panic instead:\n//\n") + for _, fn := range p.missing { + io.WriteString(w, "//\t") + io.WriteString(w, fn.Name.Name) + io.WriteString(w, "\n") + } + io.WriteString(w, "\n") + } + return write(p.f, p.fset, w) +} + +func write(f *ast.File, fset *token.FileSet, w io.Writer) error { + // Force the printer to use the comments associated with the nodes by + // clearing the cache-like (but not just a cache) Comments slice. + f.Comments = nil + return format.Node(w, fset, f) +} + +func cleanImports(f *ast.File) error { + var err error + var cleanedImports []ast.Spec + _ = astutil.Apply(f, func(c *astutil.Cursor) bool { + switch n := (c.Node()).(type) { + case *ast.GenDecl: + // Support multiple import declarations. Import blocks can't be + // nested, so simply reset the slice. + if n.Tok == token.IMPORT { + cleanedImports = cleanedImports[:0] + } + case *ast.ImportSpec: + var p string + if p, err = strconv.Unquote(n.Path.Value); err != nil { + return false + } + if n.Name != nil && n.Name.Name == "_" || astutil.UsesImport(f, p) { + // Reset the position to remove unnecessary newlines when + // imports are omitted. + n.Path.ValuePos = 0 + cleanedImports = append(cleanedImports, n) + } + return false + } + return true + }, func(c *astutil.Cursor) bool { + switch n := (c.Node()).(type) { + case *ast.GenDecl: + if n.Tok == token.IMPORT { + n.Specs = cleanedImports + } + } + return true + }) + return nil +} + +func newPanicFunc(n *ast.FuncDecl, fnType *ast.FuncType, message string) *ast.FuncDecl { + return &ast.FuncDecl{ + Name: ast.NewIdent(n.Name.Name), + Type: fnType, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{{Text: "// Not implemented by this backend."}}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.Ident{Name: "panic"}, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(message), + }, + }, + }, + }, + }, + }, + } +} + +// Deep copy functions for the AST, but without copying token positions. + +func deepCopyFieldList(src *ast.FieldList) (*ast.FieldList, error) { + var dst ast.FieldList + for _, x := range src.List { + xCopy, err := deepCopyField(x) + if err != nil { + return nil, err + } + dst.List = append(dst.List, xCopy) + } + return &dst, nil +} + +func deepCopyField(src *ast.Field) (*ast.Field, error) { + var dst ast.Field + for _, n := range src.Names { + nCopy, err := deepCopyExpression(n) + if err != nil { + return nil, err + } + dst.Names = append(dst.Names, nCopy) + } + var err error + dst.Type, err = deepCopyExpression(src.Type) + if err != nil { + return nil, err + } + return &dst, nil +} + +func deepCopyExpression[T ast.Expr](src T) (T, error) { + var err error + var f func(ast.Expr) ast.Expr + f = func(src ast.Expr) ast.Expr { + if src == nil { + return nil + } + switch src := src.(type) { + + case *ast.ArrayType: + return &ast.ArrayType{ + Elt: f(src.Elt), + Len: f(src.Len), + } + + case *ast.FuncType: + if src.TypeParams != nil { + err = fmt.Errorf("unsupported type params %v", src.TypeParams) + return nil + } + var ft ast.FuncType + ft.Params, err = deepCopyFieldList(src.Params) + if err != nil { + return nil + } + ft.Results, err = deepCopyFieldList(src.Results) + if err != nil { + return nil + } + return &ft + + case *ast.Ident: + return ast.NewIdent(src.Name) + + case *ast.SelectorExpr: + return &ast.SelectorExpr{ + X: f(src.X), + Sel: f(src.Sel).(*ast.Ident), + } + + case *ast.BasicLit: + return &ast.BasicLit{ + Kind: src.Kind, + Value: src.Value, + } + + case *ast.StarExpr: + return &ast.StarExpr{ + X: f(src.X), + } + } + err = fmt.Errorf("unsupported expression type %T", src) + return nil + } + r := f(src) + if err != nil { + return *new(T), err + } + return r.(T), nil +} diff --git a/eng/_core/vendor/github.com/microsoft/go-infra/xcryptofork/git.go b/eng/_core/vendor/github.com/microsoft/go-infra/xcryptofork/git.go new file mode 100644 index 00000000000..2b77f21451d --- /dev/null +++ b/eng/_core/vendor/github.com/microsoft/go-infra/xcryptofork/git.go @@ -0,0 +1,59 @@ +package xcryptofork + +import ( + "bufio" + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" +) + +func GitCheckoutTo(gitDir, outDir string) error { + outDir, err := filepath.Abs(outDir) + if err != nil { + return err + } + cmd := exec.Command( + "git", + "checkout-index", + "--all", + "-f", + "--prefix="+outDir+"/", + ) + cmd.Dir = gitDir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + log.Printf("In %#q, running %v", cmd.Dir, cmd) + return cmd.Run() +} + +func RemoveDirContent(dir string, prompt bool) error { + if prompt { + fmt.Printf("Delete %#q? [y/N] ", dir) + s := bufio.NewScanner(os.Stdin) + _ = s.Scan() + if s.Text() != "y" { + return fmt.Errorf("aborting: %q not %q\n", s.Text(), "y") + } + if err := s.Err(); err != nil { + return err + } + fmt.Println() + } + entries, err := os.ReadDir(dir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // Nothing to do. + return nil + } + return err + } + for _, entry := range entries { + if err := os.RemoveAll(filepath.Join(dir, entry.Name())); err != nil { + return err + } + } + return nil +} diff --git a/eng/_core/vendor/golang.org/x/tools/LICENSE b/eng/_core/vendor/golang.org/x/tools/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/eng/_core/vendor/golang.org/x/tools/PATENTS b/eng/_core/vendor/golang.org/x/tools/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go new file mode 100644 index 00000000000..9fa5aa192c2 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go @@ -0,0 +1,636 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +// This file defines utilities for working with source positions. + +import ( + "fmt" + "go/ast" + "go/token" + "sort" + + "golang.org/x/tools/internal/typeparams" +) + +// PathEnclosingInterval returns the node that encloses the source +// interval [start, end), and all its ancestors up to the AST root. +// +// The definition of "enclosing" used by this function considers +// additional whitespace abutting a node to be enclosed by it. +// In this example: +// +// z := x + y // add them +// <-A-> +// <----B-----> +// +// the ast.BinaryExpr(+) node is considered to enclose interval B +// even though its [Pos()..End()) is actually only interval A. +// This behaviour makes user interfaces more tolerant of imperfect +// input. +// +// This function treats tokens as nodes, though they are not included +// in the result. e.g. PathEnclosingInterval("+") returns the +// enclosing ast.BinaryExpr("x + y"). +// +// If start==end, the 1-char interval following start is used instead. +// +// The 'exact' result is true if the interval contains only path[0] +// and perhaps some adjacent whitespace. It is false if the interval +// overlaps multiple children of path[0], or if it contains only +// interior whitespace of path[0]. +// In this example: +// +// z := x + y // add them +// <--C--> <---E--> +// ^ +// D +// +// intervals C, D and E are inexact. C is contained by the +// z-assignment statement, because it spans three of its children (:=, +// x, +). So too is the 1-char interval D, because it contains only +// interior whitespace of the assignment. E is considered interior +// whitespace of the BlockStmt containing the assignment. +// +// The resulting path is never empty; it always contains at least the +// 'root' *ast.File. Ideally PathEnclosingInterval would reject +// intervals that lie wholly or partially outside the range of the +// file, but unfortunately ast.File records only the token.Pos of +// the 'package' keyword, but not of the start of the file itself. +func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) { + // fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging + + // Precondition: node.[Pos..End) and adjoining whitespace contain [start, end). + var visit func(node ast.Node) bool + visit = func(node ast.Node) bool { + path = append(path, node) + + nodePos := node.Pos() + nodeEnd := node.End() + + // fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging + + // Intersect [start, end) with interval of node. + if start < nodePos { + start = nodePos + } + if end > nodeEnd { + end = nodeEnd + } + + // Find sole child that contains [start, end). + children := childrenOf(node) + l := len(children) + for i, child := range children { + // [childPos, childEnd) is unaugmented interval of child. + childPos := child.Pos() + childEnd := child.End() + + // [augPos, augEnd) is whitespace-augmented interval of child. + augPos := childPos + augEnd := childEnd + if i > 0 { + augPos = children[i-1].End() // start of preceding whitespace + } + if i < l-1 { + nextChildPos := children[i+1].Pos() + // Does [start, end) lie between child and next child? + if start >= augEnd && end <= nextChildPos { + return false // inexact match + } + augEnd = nextChildPos // end of following whitespace + } + + // fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n", + // i, augPos, augEnd, start, end) // debugging + + // Does augmented child strictly contain [start, end)? + if augPos <= start && end <= augEnd { + _, isToken := child.(tokenNode) + return isToken || visit(child) + } + + // Does [start, end) overlap multiple children? + // i.e. left-augmented child contains start + // but LR-augmented child does not contain end. + if start < childEnd && end > augEnd { + break + } + } + + // No single child contained [start, end), + // so node is the result. Is it exact? + + // (It's tempting to put this condition before the + // child loop, but it gives the wrong result in the + // case where a node (e.g. ExprStmt) and its sole + // child have equal intervals.) + if start == nodePos && end == nodeEnd { + return true // exact match + } + + return false // inexact: overlaps multiple children + } + + // Ensure [start,end) is nondecreasing. + if start > end { + start, end = end, start + } + + if start < root.End() && end > root.Pos() { + if start == end { + end = start + 1 // empty interval => interval of size 1 + } + exact = visit(root) + + // Reverse the path: + for i, l := 0, len(path); i < l/2; i++ { + path[i], path[l-1-i] = path[l-1-i], path[i] + } + } else { + // Selection lies within whitespace preceding the + // first (or following the last) declaration in the file. + // The result nonetheless always includes the ast.File. + path = append(path, root) + } + + return +} + +// tokenNode is a dummy implementation of ast.Node for a single token. +// They are used transiently by PathEnclosingInterval but never escape +// this package. +type tokenNode struct { + pos token.Pos + end token.Pos +} + +func (n tokenNode) Pos() token.Pos { + return n.pos +} + +func (n tokenNode) End() token.Pos { + return n.end +} + +func tok(pos token.Pos, len int) ast.Node { + return tokenNode{pos, pos + token.Pos(len)} +} + +// childrenOf returns the direct non-nil children of ast.Node n. +// It may include fake ast.Node implementations for bare tokens. +// it is not safe to call (e.g.) ast.Walk on such nodes. +func childrenOf(n ast.Node) []ast.Node { + var children []ast.Node + + // First add nodes for all true subtrees. + ast.Inspect(n, func(node ast.Node) bool { + if node == n { // push n + return true // recur + } + if node != nil { // push child + children = append(children, node) + } + return false // no recursion + }) + + // Then add fake Nodes for bare tokens. + switch n := n.(type) { + case *ast.ArrayType: + children = append(children, + tok(n.Lbrack, len("[")), + tok(n.Elt.End(), len("]"))) + + case *ast.AssignStmt: + children = append(children, + tok(n.TokPos, len(n.Tok.String()))) + + case *ast.BasicLit: + children = append(children, + tok(n.ValuePos, len(n.Value))) + + case *ast.BinaryExpr: + children = append(children, tok(n.OpPos, len(n.Op.String()))) + + case *ast.BlockStmt: + children = append(children, + tok(n.Lbrace, len("{")), + tok(n.Rbrace, len("}"))) + + case *ast.BranchStmt: + children = append(children, + tok(n.TokPos, len(n.Tok.String()))) + + case *ast.CallExpr: + children = append(children, + tok(n.Lparen, len("(")), + tok(n.Rparen, len(")"))) + if n.Ellipsis != 0 { + children = append(children, tok(n.Ellipsis, len("..."))) + } + + case *ast.CaseClause: + if n.List == nil { + children = append(children, + tok(n.Case, len("default"))) + } else { + children = append(children, + tok(n.Case, len("case"))) + } + children = append(children, tok(n.Colon, len(":"))) + + case *ast.ChanType: + switch n.Dir { + case ast.RECV: + children = append(children, tok(n.Begin, len("<-chan"))) + case ast.SEND: + children = append(children, tok(n.Begin, len("chan<-"))) + case ast.RECV | ast.SEND: + children = append(children, tok(n.Begin, len("chan"))) + } + + case *ast.CommClause: + if n.Comm == nil { + children = append(children, + tok(n.Case, len("default"))) + } else { + children = append(children, + tok(n.Case, len("case"))) + } + children = append(children, tok(n.Colon, len(":"))) + + case *ast.Comment: + // nop + + case *ast.CommentGroup: + // nop + + case *ast.CompositeLit: + children = append(children, + tok(n.Lbrace, len("{")), + tok(n.Rbrace, len("{"))) + + case *ast.DeclStmt: + // nop + + case *ast.DeferStmt: + children = append(children, + tok(n.Defer, len("defer"))) + + case *ast.Ellipsis: + children = append(children, + tok(n.Ellipsis, len("..."))) + + case *ast.EmptyStmt: + // nop + + case *ast.ExprStmt: + // nop + + case *ast.Field: + // TODO(adonovan): Field.{Doc,Comment,Tag}? + + case *ast.FieldList: + children = append(children, + tok(n.Opening, len("(")), // or len("[") + tok(n.Closing, len(")"))) // or len("]") + + case *ast.File: + // TODO test: Doc + children = append(children, + tok(n.Package, len("package"))) + + case *ast.ForStmt: + children = append(children, + tok(n.For, len("for"))) + + case *ast.FuncDecl: + // TODO(adonovan): FuncDecl.Comment? + + // Uniquely, FuncDecl breaks the invariant that + // preorder traversal yields tokens in lexical order: + // in fact, FuncDecl.Recv precedes FuncDecl.Type.Func. + // + // As a workaround, we inline the case for FuncType + // here and order things correctly. + // + children = nil // discard ast.Walk(FuncDecl) info subtrees + children = append(children, tok(n.Type.Func, len("func"))) + if n.Recv != nil { + children = append(children, n.Recv) + } + children = append(children, n.Name) + if tparams := typeparams.ForFuncType(n.Type); tparams != nil { + children = append(children, tparams) + } + if n.Type.Params != nil { + children = append(children, n.Type.Params) + } + if n.Type.Results != nil { + children = append(children, n.Type.Results) + } + if n.Body != nil { + children = append(children, n.Body) + } + + case *ast.FuncLit: + // nop + + case *ast.FuncType: + if n.Func != 0 { + children = append(children, + tok(n.Func, len("func"))) + } + + case *ast.GenDecl: + children = append(children, + tok(n.TokPos, len(n.Tok.String()))) + if n.Lparen != 0 { + children = append(children, + tok(n.Lparen, len("(")), + tok(n.Rparen, len(")"))) + } + + case *ast.GoStmt: + children = append(children, + tok(n.Go, len("go"))) + + case *ast.Ident: + children = append(children, + tok(n.NamePos, len(n.Name))) + + case *ast.IfStmt: + children = append(children, + tok(n.If, len("if"))) + + case *ast.ImportSpec: + // TODO(adonovan): ImportSpec.{Doc,EndPos}? + + case *ast.IncDecStmt: + children = append(children, + tok(n.TokPos, len(n.Tok.String()))) + + case *ast.IndexExpr: + children = append(children, + tok(n.Lbrack, len("[")), + tok(n.Rbrack, len("]"))) + + case *typeparams.IndexListExpr: + children = append(children, + tok(n.Lbrack, len("[")), + tok(n.Rbrack, len("]"))) + + case *ast.InterfaceType: + children = append(children, + tok(n.Interface, len("interface"))) + + case *ast.KeyValueExpr: + children = append(children, + tok(n.Colon, len(":"))) + + case *ast.LabeledStmt: + children = append(children, + tok(n.Colon, len(":"))) + + case *ast.MapType: + children = append(children, + tok(n.Map, len("map"))) + + case *ast.ParenExpr: + children = append(children, + tok(n.Lparen, len("(")), + tok(n.Rparen, len(")"))) + + case *ast.RangeStmt: + children = append(children, + tok(n.For, len("for")), + tok(n.TokPos, len(n.Tok.String()))) + + case *ast.ReturnStmt: + children = append(children, + tok(n.Return, len("return"))) + + case *ast.SelectStmt: + children = append(children, + tok(n.Select, len("select"))) + + case *ast.SelectorExpr: + // nop + + case *ast.SendStmt: + children = append(children, + tok(n.Arrow, len("<-"))) + + case *ast.SliceExpr: + children = append(children, + tok(n.Lbrack, len("[")), + tok(n.Rbrack, len("]"))) + + case *ast.StarExpr: + children = append(children, tok(n.Star, len("*"))) + + case *ast.StructType: + children = append(children, tok(n.Struct, len("struct"))) + + case *ast.SwitchStmt: + children = append(children, tok(n.Switch, len("switch"))) + + case *ast.TypeAssertExpr: + children = append(children, + tok(n.Lparen-1, len(".")), + tok(n.Lparen, len("(")), + tok(n.Rparen, len(")"))) + + case *ast.TypeSpec: + // TODO(adonovan): TypeSpec.{Doc,Comment}? + + case *ast.TypeSwitchStmt: + children = append(children, tok(n.Switch, len("switch"))) + + case *ast.UnaryExpr: + children = append(children, tok(n.OpPos, len(n.Op.String()))) + + case *ast.ValueSpec: + // TODO(adonovan): ValueSpec.{Doc,Comment}? + + case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt: + // nop + } + + // TODO(adonovan): opt: merge the logic of ast.Inspect() into + // the switch above so we can make interleaved callbacks for + // both Nodes and Tokens in the right order and avoid the need + // to sort. + sort.Sort(byPos(children)) + + return children +} + +type byPos []ast.Node + +func (sl byPos) Len() int { + return len(sl) +} +func (sl byPos) Less(i, j int) bool { + return sl[i].Pos() < sl[j].Pos() +} +func (sl byPos) Swap(i, j int) { + sl[i], sl[j] = sl[j], sl[i] +} + +// NodeDescription returns a description of the concrete type of n suitable +// for a user interface. +// +// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident, +// StarExpr) we could be much more specific given the path to the AST +// root. Perhaps we should do that. +func NodeDescription(n ast.Node) string { + switch n := n.(type) { + case *ast.ArrayType: + return "array type" + case *ast.AssignStmt: + return "assignment" + case *ast.BadDecl: + return "bad declaration" + case *ast.BadExpr: + return "bad expression" + case *ast.BadStmt: + return "bad statement" + case *ast.BasicLit: + return "basic literal" + case *ast.BinaryExpr: + return fmt.Sprintf("binary %s operation", n.Op) + case *ast.BlockStmt: + return "block" + case *ast.BranchStmt: + switch n.Tok { + case token.BREAK: + return "break statement" + case token.CONTINUE: + return "continue statement" + case token.GOTO: + return "goto statement" + case token.FALLTHROUGH: + return "fall-through statement" + } + case *ast.CallExpr: + if len(n.Args) == 1 && !n.Ellipsis.IsValid() { + return "function call (or conversion)" + } + return "function call" + case *ast.CaseClause: + return "case clause" + case *ast.ChanType: + return "channel type" + case *ast.CommClause: + return "communication clause" + case *ast.Comment: + return "comment" + case *ast.CommentGroup: + return "comment group" + case *ast.CompositeLit: + return "composite literal" + case *ast.DeclStmt: + return NodeDescription(n.Decl) + " statement" + case *ast.DeferStmt: + return "defer statement" + case *ast.Ellipsis: + return "ellipsis" + case *ast.EmptyStmt: + return "empty statement" + case *ast.ExprStmt: + return "expression statement" + case *ast.Field: + // Can be any of these: + // struct {x, y int} -- struct field(s) + // struct {T} -- anon struct field + // interface {I} -- interface embedding + // interface {f()} -- interface method + // func (A) func(B) C -- receiver, param(s), result(s) + return "field/method/parameter" + case *ast.FieldList: + return "field/method/parameter list" + case *ast.File: + return "source file" + case *ast.ForStmt: + return "for loop" + case *ast.FuncDecl: + return "function declaration" + case *ast.FuncLit: + return "function literal" + case *ast.FuncType: + return "function type" + case *ast.GenDecl: + switch n.Tok { + case token.IMPORT: + return "import declaration" + case token.CONST: + return "constant declaration" + case token.TYPE: + return "type declaration" + case token.VAR: + return "variable declaration" + } + case *ast.GoStmt: + return "go statement" + case *ast.Ident: + return "identifier" + case *ast.IfStmt: + return "if statement" + case *ast.ImportSpec: + return "import specification" + case *ast.IncDecStmt: + if n.Tok == token.INC { + return "increment statement" + } + return "decrement statement" + case *ast.IndexExpr: + return "index expression" + case *typeparams.IndexListExpr: + return "index list expression" + case *ast.InterfaceType: + return "interface type" + case *ast.KeyValueExpr: + return "key/value association" + case *ast.LabeledStmt: + return "statement label" + case *ast.MapType: + return "map type" + case *ast.Package: + return "package" + case *ast.ParenExpr: + return "parenthesized " + NodeDescription(n.X) + case *ast.RangeStmt: + return "range loop" + case *ast.ReturnStmt: + return "return statement" + case *ast.SelectStmt: + return "select statement" + case *ast.SelectorExpr: + return "selector" + case *ast.SendStmt: + return "channel send" + case *ast.SliceExpr: + return "slice expression" + case *ast.StarExpr: + return "*-operation" // load/store expr or pointer type + case *ast.StructType: + return "struct type" + case *ast.SwitchStmt: + return "switch statement" + case *ast.TypeAssertExpr: + return "type assertion" + case *ast.TypeSpec: + return "type specification" + case *ast.TypeSwitchStmt: + return "type switch" + case *ast.UnaryExpr: + return fmt.Sprintf("unary %s operation", n.Op) + case *ast.ValueSpec: + return "value specification" + + } + panic(fmt.Sprintf("unexpected node type: %T", n)) +} diff --git a/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/imports.go new file mode 100644 index 00000000000..18d1adb05dd --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/imports.go @@ -0,0 +1,485 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package astutil contains common utilities for working with the Go AST. +package astutil // import "golang.org/x/tools/go/ast/astutil" + +import ( + "fmt" + "go/ast" + "go/token" + "strconv" + "strings" +) + +// AddImport adds the import path to the file f, if absent. +func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) { + return AddNamedImport(fset, f, "", path) +} + +// AddNamedImport adds the import with the given name and path to the file f, if absent. +// If name is not empty, it is used to rename the import. +// +// For example, calling +// +// AddNamedImport(fset, f, "pathpkg", "path") +// +// adds +// +// import pathpkg "path" +func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) { + if imports(f, name, path) { + return false + } + + newImport := &ast.ImportSpec{ + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(path), + }, + } + if name != "" { + newImport.Name = &ast.Ident{Name: name} + } + + // Find an import decl to add to. + // The goal is to find an existing import + // whose import path has the longest shared + // prefix with path. + var ( + bestMatch = -1 // length of longest shared prefix + lastImport = -1 // index in f.Decls of the file's final import decl + impDecl *ast.GenDecl // import decl containing the best match + impIndex = -1 // spec index in impDecl containing the best match + + isThirdPartyPath = isThirdParty(path) + ) + for i, decl := range f.Decls { + gen, ok := decl.(*ast.GenDecl) + if ok && gen.Tok == token.IMPORT { + lastImport = i + // Do not add to import "C", to avoid disrupting the + // association with its doc comment, breaking cgo. + if declImports(gen, "C") { + continue + } + + // Match an empty import decl if that's all that is available. + if len(gen.Specs) == 0 && bestMatch == -1 { + impDecl = gen + } + + // Compute longest shared prefix with imports in this group and find best + // matched import spec. + // 1. Always prefer import spec with longest shared prefix. + // 2. While match length is 0, + // - for stdlib package: prefer first import spec. + // - for third party package: prefer first third party import spec. + // We cannot use last import spec as best match for third party package + // because grouped imports are usually placed last by goimports -local + // flag. + // See issue #19190. + seenAnyThirdParty := false + for j, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + p := importPath(impspec) + n := matchLen(p, path) + if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { + bestMatch = n + impDecl = gen + impIndex = j + } + seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p) + } + } + } + + // If no import decl found, add one after the last import. + if impDecl == nil { + impDecl = &ast.GenDecl{ + Tok: token.IMPORT, + } + if lastImport >= 0 { + impDecl.TokPos = f.Decls[lastImport].End() + } else { + // There are no existing imports. + // Our new import, preceded by a blank line, goes after the package declaration + // and after the comment, if any, that starts on the same line as the + // package declaration. + impDecl.TokPos = f.Package + + file := fset.File(f.Package) + pkgLine := file.Line(f.Package) + for _, c := range f.Comments { + if file.Line(c.Pos()) > pkgLine { + break + } + // +2 for a blank line + impDecl.TokPos = c.End() + 2 + } + } + f.Decls = append(f.Decls, nil) + copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) + f.Decls[lastImport+1] = impDecl + } + + // Insert new import at insertAt. + insertAt := 0 + if impIndex >= 0 { + // insert after the found import + insertAt = impIndex + 1 + } + impDecl.Specs = append(impDecl.Specs, nil) + copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) + impDecl.Specs[insertAt] = newImport + pos := impDecl.Pos() + if insertAt > 0 { + // If there is a comment after an existing import, preserve the comment + // position by adding the new import after the comment. + if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil { + pos = spec.Comment.End() + } else { + // Assign same position as the previous import, + // so that the sorter sees it as being in the same block. + pos = impDecl.Specs[insertAt-1].Pos() + } + } + if newImport.Name != nil { + newImport.Name.NamePos = pos + } + newImport.Path.ValuePos = pos + newImport.EndPos = pos + + // Clean up parens. impDecl contains at least one spec. + if len(impDecl.Specs) == 1 { + // Remove unneeded parens. + impDecl.Lparen = token.NoPos + } else if !impDecl.Lparen.IsValid() { + // impDecl needs parens added. + impDecl.Lparen = impDecl.Specs[0].Pos() + } + + f.Imports = append(f.Imports, newImport) + + if len(f.Decls) <= 1 { + return true + } + + // Merge all the import declarations into the first one. + var first *ast.GenDecl + for i := 0; i < len(f.Decls); i++ { + decl := f.Decls[i] + gen, ok := decl.(*ast.GenDecl) + if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { + continue + } + if first == nil { + first = gen + continue // Don't touch the first one. + } + // We now know there is more than one package in this import + // declaration. Ensure that it ends up parenthesized. + first.Lparen = first.Pos() + // Move the imports of the other import declaration to the first one. + for _, spec := range gen.Specs { + spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() + first.Specs = append(first.Specs, spec) + } + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + i-- + } + + return true +} + +func isThirdParty(importPath string) bool { + // Third party package import path usually contains "." (".com", ".org", ...) + // This logic is taken from golang.org/x/tools/imports package. + return strings.Contains(importPath, ".") +} + +// DeleteImport deletes the import path from the file f, if present. +// If there are duplicate import declarations, all matching ones are deleted. +func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { + return DeleteNamedImport(fset, f, "", path) +} + +// DeleteNamedImport deletes the import with the given name and path from the file f, if present. +// If there are duplicate import declarations, all matching ones are deleted. +func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { + var delspecs []*ast.ImportSpec + var delcomments []*ast.CommentGroup + + // Find the import nodes that import path, if any. + for i := 0; i < len(f.Decls); i++ { + decl := f.Decls[i] + gen, ok := decl.(*ast.GenDecl) + if !ok || gen.Tok != token.IMPORT { + continue + } + for j := 0; j < len(gen.Specs); j++ { + spec := gen.Specs[j] + impspec := spec.(*ast.ImportSpec) + if importName(impspec) != name || importPath(impspec) != path { + continue + } + + // We found an import spec that imports path. + // Delete it. + delspecs = append(delspecs, impspec) + deleted = true + copy(gen.Specs[j:], gen.Specs[j+1:]) + gen.Specs = gen.Specs[:len(gen.Specs)-1] + + // If this was the last import spec in this decl, + // delete the decl, too. + if len(gen.Specs) == 0 { + copy(f.Decls[i:], f.Decls[i+1:]) + f.Decls = f.Decls[:len(f.Decls)-1] + i-- + break + } else if len(gen.Specs) == 1 { + if impspec.Doc != nil { + delcomments = append(delcomments, impspec.Doc) + } + if impspec.Comment != nil { + delcomments = append(delcomments, impspec.Comment) + } + for _, cg := range f.Comments { + // Found comment on the same line as the import spec. + if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line { + delcomments = append(delcomments, cg) + break + } + } + + spec := gen.Specs[0].(*ast.ImportSpec) + + // Move the documentation right after the import decl. + if spec.Doc != nil { + for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line { + fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) + } + } + for _, cg := range f.Comments { + if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line { + for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line { + fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) + } + break + } + } + } + if j > 0 { + lastImpspec := gen.Specs[j-1].(*ast.ImportSpec) + lastLine := fset.PositionFor(lastImpspec.Path.ValuePos, false).Line + line := fset.PositionFor(impspec.Path.ValuePos, false).Line + + // We deleted an entry but now there may be + // a blank line-sized hole where the import was. + if line-lastLine > 1 || !gen.Rparen.IsValid() { + // There was a blank line immediately preceding the deleted import, + // so there's no need to close the hole. The right parenthesis is + // invalid after AddImport to an import statement without parenthesis. + // Do nothing. + } else if line != fset.File(gen.Rparen).LineCount() { + // There was no blank line. Close the hole. + fset.File(gen.Rparen).MergeLine(line) + } + } + j-- + } + } + + // Delete imports from f.Imports. + for i := 0; i < len(f.Imports); i++ { + imp := f.Imports[i] + for j, del := range delspecs { + if imp == del { + copy(f.Imports[i:], f.Imports[i+1:]) + f.Imports = f.Imports[:len(f.Imports)-1] + copy(delspecs[j:], delspecs[j+1:]) + delspecs = delspecs[:len(delspecs)-1] + i-- + break + } + } + } + + // Delete comments from f.Comments. + for i := 0; i < len(f.Comments); i++ { + cg := f.Comments[i] + for j, del := range delcomments { + if cg == del { + copy(f.Comments[i:], f.Comments[i+1:]) + f.Comments = f.Comments[:len(f.Comments)-1] + copy(delcomments[j:], delcomments[j+1:]) + delcomments = delcomments[:len(delcomments)-1] + i-- + break + } + } + } + + if len(delspecs) > 0 { + panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) + } + + return +} + +// RewriteImport rewrites any import of path oldPath to path newPath. +func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) { + for _, imp := range f.Imports { + if importPath(imp) == oldPath { + rewrote = true + // record old End, because the default is to compute + // it using the length of imp.Path.Value. + imp.EndPos = imp.End() + imp.Path.Value = strconv.Quote(newPath) + } + } + return +} + +// UsesImport reports whether a given import is used. +func UsesImport(f *ast.File, path string) (used bool) { + spec := importSpec(f, path) + if spec == nil { + return + } + + name := spec.Name.String() + switch name { + case "": + // If the package name is not explicitly specified, + // make an educated guess. This is not guaranteed to be correct. + lastSlash := strings.LastIndex(path, "/") + if lastSlash == -1 { + name = path + } else { + name = path[lastSlash+1:] + } + case "_", ".": + // Not sure if this import is used - err on the side of caution. + return true + } + + ast.Walk(visitFn(func(n ast.Node) { + sel, ok := n.(*ast.SelectorExpr) + if ok && isTopName(sel.X, name) { + used = true + } + }), f) + + return +} + +type visitFn func(node ast.Node) + +func (fn visitFn) Visit(node ast.Node) ast.Visitor { + fn(node) + return fn +} + +// imports reports whether f has an import with the specified name and path. +func imports(f *ast.File, name, path string) bool { + for _, s := range f.Imports { + if importName(s) == name && importPath(s) == path { + return true + } + } + return false +} + +// importSpec returns the import spec if f imports path, +// or nil otherwise. +func importSpec(f *ast.File, path string) *ast.ImportSpec { + for _, s := range f.Imports { + if importPath(s) == path { + return s + } + } + return nil +} + +// importName returns the name of s, +// or "" if the import is not named. +func importName(s *ast.ImportSpec) string { + if s.Name == nil { + return "" + } + return s.Name.Name +} + +// importPath returns the unquoted import path of s, +// or "" if the path is not properly quoted. +func importPath(s *ast.ImportSpec) string { + t, err := strconv.Unquote(s.Path.Value) + if err != nil { + return "" + } + return t +} + +// declImports reports whether gen contains an import of path. +func declImports(gen *ast.GenDecl, path string) bool { + if gen.Tok != token.IMPORT { + return false + } + for _, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + if importPath(impspec) == path { + return true + } + } + return false +} + +// matchLen returns the length of the longest path segment prefix shared by x and y. +func matchLen(x, y string) int { + n := 0 + for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { + if x[i] == '/' { + n++ + } + } + return n +} + +// isTopName returns true if n is a top-level unresolved identifier with the given name. +func isTopName(n ast.Expr, name string) bool { + id, ok := n.(*ast.Ident) + return ok && id.Name == name && id.Obj == nil +} + +// Imports returns the file imports grouped by paragraph. +func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec { + var groups [][]*ast.ImportSpec + + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || genDecl.Tok != token.IMPORT { + break + } + + group := []*ast.ImportSpec{} + + var lastLine int + for _, spec := range genDecl.Specs { + importSpec := spec.(*ast.ImportSpec) + pos := importSpec.Path.ValuePos + line := fset.Position(pos).Line + if lastLine > 0 && pos > 0 && line-lastLine > 1 { + groups = append(groups, group) + group = []*ast.ImportSpec{} + } + group = append(group, importSpec) + lastLine = line + } + groups = append(groups, group) + } + + return groups +} diff --git a/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go new file mode 100644 index 00000000000..f430b21b9b9 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go @@ -0,0 +1,488 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "fmt" + "go/ast" + "reflect" + "sort" + + "golang.org/x/tools/internal/typeparams" +) + +// An ApplyFunc is invoked by Apply for each node n, even if n is nil, +// before and/or after the node's children, using a Cursor describing +// the current node and providing operations on it. +// +// The return value of ApplyFunc controls the syntax tree traversal. +// See Apply for details. +type ApplyFunc func(*Cursor) bool + +// Apply traverses a syntax tree recursively, starting with root, +// and calling pre and post for each node as described below. +// Apply returns the syntax tree, possibly modified. +// +// If pre is not nil, it is called for each node before the node's +// children are traversed (pre-order). If pre returns false, no +// children are traversed, and post is not called for that node. +// +// If post is not nil, and a prior call of pre didn't return false, +// post is called for each node after its children are traversed +// (post-order). If post returns false, traversal is terminated and +// Apply returns immediately. +// +// Only fields that refer to AST nodes are considered children; +// i.e., token.Pos, Scopes, Objects, and fields of basic types +// (strings, etc.) are ignored. +// +// Children are traversed in the order in which they appear in the +// respective node's struct definition. A package's files are +// traversed in the filenames' alphabetical order. +func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) { + parent := &struct{ ast.Node }{root} + defer func() { + if r := recover(); r != nil && r != abort { + panic(r) + } + result = parent.Node + }() + a := &application{pre: pre, post: post} + a.apply(parent, "Node", nil, root) + return +} + +var abort = new(int) // singleton, to signal termination of Apply + +// A Cursor describes a node encountered during Apply. +// Information about the node and its parent is available +// from the Node, Parent, Name, and Index methods. +// +// If p is a variable of type and value of the current parent node +// c.Parent(), and f is the field identifier with name c.Name(), +// the following invariants hold: +// +// p.f == c.Node() if c.Index() < 0 +// p.f[c.Index()] == c.Node() if c.Index() >= 0 +// +// The methods Replace, Delete, InsertBefore, and InsertAfter +// can be used to change the AST without disrupting Apply. +type Cursor struct { + parent ast.Node + name string + iter *iterator // valid if non-nil + node ast.Node +} + +// Node returns the current Node. +func (c *Cursor) Node() ast.Node { return c.node } + +// Parent returns the parent of the current Node. +func (c *Cursor) Parent() ast.Node { return c.parent } + +// Name returns the name of the parent Node field that contains the current Node. +// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns +// the filename for the current Node. +func (c *Cursor) Name() string { return c.name } + +// Index reports the index >= 0 of the current Node in the slice of Nodes that +// contains it, or a value < 0 if the current Node is not part of a slice. +// The index of the current node changes if InsertBefore is called while +// processing the current node. +func (c *Cursor) Index() int { + if c.iter != nil { + return c.iter.index + } + return -1 +} + +// field returns the current node's parent field value. +func (c *Cursor) field() reflect.Value { + return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name) +} + +// Replace replaces the current Node with n. +// The replacement node is not walked by Apply. +func (c *Cursor) Replace(n ast.Node) { + if _, ok := c.node.(*ast.File); ok { + file, ok := n.(*ast.File) + if !ok { + panic("attempt to replace *ast.File with non-*ast.File") + } + c.parent.(*ast.Package).Files[c.name] = file + return + } + + v := c.field() + if i := c.Index(); i >= 0 { + v = v.Index(i) + } + v.Set(reflect.ValueOf(n)) +} + +// Delete deletes the current Node from its containing slice. +// If the current Node is not part of a slice, Delete panics. +// As a special case, if the current node is a package file, +// Delete removes it from the package's Files map. +func (c *Cursor) Delete() { + if _, ok := c.node.(*ast.File); ok { + delete(c.parent.(*ast.Package).Files, c.name) + return + } + + i := c.Index() + if i < 0 { + panic("Delete node not contained in slice") + } + v := c.field() + l := v.Len() + reflect.Copy(v.Slice(i, l), v.Slice(i+1, l)) + v.Index(l - 1).Set(reflect.Zero(v.Type().Elem())) + v.SetLen(l - 1) + c.iter.step-- +} + +// InsertAfter inserts n after the current Node in its containing slice. +// If the current Node is not part of a slice, InsertAfter panics. +// Apply does not walk n. +func (c *Cursor) InsertAfter(n ast.Node) { + i := c.Index() + if i < 0 { + panic("InsertAfter node not contained in slice") + } + v := c.field() + v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) + l := v.Len() + reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l)) + v.Index(i + 1).Set(reflect.ValueOf(n)) + c.iter.step++ +} + +// InsertBefore inserts n before the current Node in its containing slice. +// If the current Node is not part of a slice, InsertBefore panics. +// Apply will not walk n. +func (c *Cursor) InsertBefore(n ast.Node) { + i := c.Index() + if i < 0 { + panic("InsertBefore node not contained in slice") + } + v := c.field() + v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) + l := v.Len() + reflect.Copy(v.Slice(i+1, l), v.Slice(i, l)) + v.Index(i).Set(reflect.ValueOf(n)) + c.iter.index++ +} + +// application carries all the shared data so we can pass it around cheaply. +type application struct { + pre, post ApplyFunc + cursor Cursor + iter iterator +} + +func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) { + // convert typed nil into untyped nil + if v := reflect.ValueOf(n); v.Kind() == reflect.Ptr && v.IsNil() { + n = nil + } + + // avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead + saved := a.cursor + a.cursor.parent = parent + a.cursor.name = name + a.cursor.iter = iter + a.cursor.node = n + + if a.pre != nil && !a.pre(&a.cursor) { + a.cursor = saved + return + } + + // walk children + // (the order of the cases matches the order of the corresponding node types in go/ast) + switch n := n.(type) { + case nil: + // nothing to do + + // Comments and fields + case *ast.Comment: + // nothing to do + + case *ast.CommentGroup: + if n != nil { + a.applyList(n, "List") + } + + case *ast.Field: + a.apply(n, "Doc", nil, n.Doc) + a.applyList(n, "Names") + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Tag", nil, n.Tag) + a.apply(n, "Comment", nil, n.Comment) + + case *ast.FieldList: + a.applyList(n, "List") + + // Expressions + case *ast.BadExpr, *ast.Ident, *ast.BasicLit: + // nothing to do + + case *ast.Ellipsis: + a.apply(n, "Elt", nil, n.Elt) + + case *ast.FuncLit: + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Body", nil, n.Body) + + case *ast.CompositeLit: + a.apply(n, "Type", nil, n.Type) + a.applyList(n, "Elts") + + case *ast.ParenExpr: + a.apply(n, "X", nil, n.X) + + case *ast.SelectorExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Sel", nil, n.Sel) + + case *ast.IndexExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Index", nil, n.Index) + + case *typeparams.IndexListExpr: + a.apply(n, "X", nil, n.X) + a.applyList(n, "Indices") + + case *ast.SliceExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Low", nil, n.Low) + a.apply(n, "High", nil, n.High) + a.apply(n, "Max", nil, n.Max) + + case *ast.TypeAssertExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Type", nil, n.Type) + + case *ast.CallExpr: + a.apply(n, "Fun", nil, n.Fun) + a.applyList(n, "Args") + + case *ast.StarExpr: + a.apply(n, "X", nil, n.X) + + case *ast.UnaryExpr: + a.apply(n, "X", nil, n.X) + + case *ast.BinaryExpr: + a.apply(n, "X", nil, n.X) + a.apply(n, "Y", nil, n.Y) + + case *ast.KeyValueExpr: + a.apply(n, "Key", nil, n.Key) + a.apply(n, "Value", nil, n.Value) + + // Types + case *ast.ArrayType: + a.apply(n, "Len", nil, n.Len) + a.apply(n, "Elt", nil, n.Elt) + + case *ast.StructType: + a.apply(n, "Fields", nil, n.Fields) + + case *ast.FuncType: + if tparams := typeparams.ForFuncType(n); tparams != nil { + a.apply(n, "TypeParams", nil, tparams) + } + a.apply(n, "Params", nil, n.Params) + a.apply(n, "Results", nil, n.Results) + + case *ast.InterfaceType: + a.apply(n, "Methods", nil, n.Methods) + + case *ast.MapType: + a.apply(n, "Key", nil, n.Key) + a.apply(n, "Value", nil, n.Value) + + case *ast.ChanType: + a.apply(n, "Value", nil, n.Value) + + // Statements + case *ast.BadStmt: + // nothing to do + + case *ast.DeclStmt: + a.apply(n, "Decl", nil, n.Decl) + + case *ast.EmptyStmt: + // nothing to do + + case *ast.LabeledStmt: + a.apply(n, "Label", nil, n.Label) + a.apply(n, "Stmt", nil, n.Stmt) + + case *ast.ExprStmt: + a.apply(n, "X", nil, n.X) + + case *ast.SendStmt: + a.apply(n, "Chan", nil, n.Chan) + a.apply(n, "Value", nil, n.Value) + + case *ast.IncDecStmt: + a.apply(n, "X", nil, n.X) + + case *ast.AssignStmt: + a.applyList(n, "Lhs") + a.applyList(n, "Rhs") + + case *ast.GoStmt: + a.apply(n, "Call", nil, n.Call) + + case *ast.DeferStmt: + a.apply(n, "Call", nil, n.Call) + + case *ast.ReturnStmt: + a.applyList(n, "Results") + + case *ast.BranchStmt: + a.apply(n, "Label", nil, n.Label) + + case *ast.BlockStmt: + a.applyList(n, "List") + + case *ast.IfStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Cond", nil, n.Cond) + a.apply(n, "Body", nil, n.Body) + a.apply(n, "Else", nil, n.Else) + + case *ast.CaseClause: + a.applyList(n, "List") + a.applyList(n, "Body") + + case *ast.SwitchStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Tag", nil, n.Tag) + a.apply(n, "Body", nil, n.Body) + + case *ast.TypeSwitchStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Assign", nil, n.Assign) + a.apply(n, "Body", nil, n.Body) + + case *ast.CommClause: + a.apply(n, "Comm", nil, n.Comm) + a.applyList(n, "Body") + + case *ast.SelectStmt: + a.apply(n, "Body", nil, n.Body) + + case *ast.ForStmt: + a.apply(n, "Init", nil, n.Init) + a.apply(n, "Cond", nil, n.Cond) + a.apply(n, "Post", nil, n.Post) + a.apply(n, "Body", nil, n.Body) + + case *ast.RangeStmt: + a.apply(n, "Key", nil, n.Key) + a.apply(n, "Value", nil, n.Value) + a.apply(n, "X", nil, n.X) + a.apply(n, "Body", nil, n.Body) + + // Declarations + case *ast.ImportSpec: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Name", nil, n.Name) + a.apply(n, "Path", nil, n.Path) + a.apply(n, "Comment", nil, n.Comment) + + case *ast.ValueSpec: + a.apply(n, "Doc", nil, n.Doc) + a.applyList(n, "Names") + a.apply(n, "Type", nil, n.Type) + a.applyList(n, "Values") + a.apply(n, "Comment", nil, n.Comment) + + case *ast.TypeSpec: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Name", nil, n.Name) + if tparams := typeparams.ForTypeSpec(n); tparams != nil { + a.apply(n, "TypeParams", nil, tparams) + } + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Comment", nil, n.Comment) + + case *ast.BadDecl: + // nothing to do + + case *ast.GenDecl: + a.apply(n, "Doc", nil, n.Doc) + a.applyList(n, "Specs") + + case *ast.FuncDecl: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Recv", nil, n.Recv) + a.apply(n, "Name", nil, n.Name) + a.apply(n, "Type", nil, n.Type) + a.apply(n, "Body", nil, n.Body) + + // Files and packages + case *ast.File: + a.apply(n, "Doc", nil, n.Doc) + a.apply(n, "Name", nil, n.Name) + a.applyList(n, "Decls") + // Don't walk n.Comments; they have either been walked already if + // they are Doc comments, or they can be easily walked explicitly. + + case *ast.Package: + // collect and sort names for reproducible behavior + var names []string + for name := range n.Files { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + a.apply(n, name, nil, n.Files[name]) + } + + default: + panic(fmt.Sprintf("Apply: unexpected node type %T", n)) + } + + if a.post != nil && !a.post(&a.cursor) { + panic(abort) + } + + a.cursor = saved +} + +// An iterator controls iteration over a slice of nodes. +type iterator struct { + index, step int +} + +func (a *application) applyList(parent ast.Node, name string) { + // avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead + saved := a.iter + a.iter.index = 0 + for { + // must reload parent.name each time, since cursor modifications might change it + v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name) + if a.iter.index >= v.Len() { + break + } + + // element x may be nil in a bad AST - be cautious + var x ast.Node + if e := v.Index(a.iter.index); e.IsValid() { + x = e.Interface().(ast.Node) + } + + a.iter.step = 1 + a.apply(parent, name, &a.iter, x) + a.iter.index += a.iter.step + } + a.iter = saved +} diff --git a/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/util.go b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/util.go new file mode 100644 index 00000000000..919d5305ab4 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/go/ast/astutil/util.go @@ -0,0 +1,18 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import "go/ast" + +// Unparen returns e with any enclosing parentheses stripped. +func Unparen(e ast.Expr) ast.Expr { + for { + p, ok := e.(*ast.ParenExpr) + if !ok { + return e + } + e = p.X + } +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/common.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/common.go new file mode 100644 index 00000000000..25a1426d30e --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -0,0 +1,179 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeparams contains common utilities for writing tools that interact +// with generic Go code, as introduced with Go 1.18. +// +// Many of the types and functions in this package are proxies for the new APIs +// introduced in the standard library with Go 1.18. For example, the +// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec +// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go +// versions older than 1.18 these helpers are implemented as stubs, allowing +// users of this package to write code that handles generic constructs inline, +// even if the Go version being used to compile does not support generics. +// +// Additionally, this package contains common utilities for working with the +// new generic constructs, to supplement the standard library APIs. Notably, +// the StructuralTerms API computes a minimal representation of the structural +// restrictions on a type parameter. +// +// An external version of these APIs is available in the +// golang.org/x/exp/typeparams module. +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +// UnpackIndexExpr extracts data from AST nodes that represent index +// expressions. +// +// For an ast.IndexExpr, the resulting indices slice will contain exactly one +// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable +// number of index expressions. +// +// For nodes that don't represent index expressions, the first return value of +// UnpackIndexExpr will be nil. +func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) { + switch e := n.(type) { + case *ast.IndexExpr: + return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack + case *IndexListExpr: + return e.X, e.Lbrack, e.Indices, e.Rbrack + } + return nil, token.NoPos, nil, token.NoPos +} + +// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on +// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0 +// will panic. +func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr { + switch len(indices) { + case 0: + panic("empty indices") + case 1: + return &ast.IndexExpr{ + X: x, + Lbrack: lbrack, + Index: indices[0], + Rbrack: rbrack, + } + default: + return &IndexListExpr{ + X: x, + Lbrack: lbrack, + Indices: indices, + Rbrack: rbrack, + } + } +} + +// IsTypeParam reports whether t is a type parameter. +func IsTypeParam(t types.Type) bool { + _, ok := t.(*TypeParam) + return ok +} + +// OriginMethod returns the origin method associated with the method fn. +// For methods on a non-generic receiver base type, this is just +// fn. However, for methods with a generic receiver, OriginMethod returns the +// corresponding method in the method set of the origin type. +// +// As a special case, if fn is not a method (has no receiver), OriginMethod +// returns fn. +func OriginMethod(fn *types.Func) *types.Func { + recv := fn.Type().(*types.Signature).Recv() + if recv == nil { + + return fn + } + base := recv.Type() + p, isPtr := base.(*types.Pointer) + if isPtr { + base = p.Elem() + } + named, isNamed := base.(*types.Named) + if !isNamed { + // Receiver is a *types.Interface. + return fn + } + if ForNamed(named).Len() == 0 { + // Receiver base has no type parameters, so we can avoid the lookup below. + return fn + } + orig := NamedTypeOrigin(named) + gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name()) + return gfn.(*types.Func) +} + +// GenericAssignableTo is a generalization of types.AssignableTo that +// implements the following rule for uninstantiated generic types: +// +// If V and T are generic named types, then V is considered assignable to T if, +// for every possible instantation of V[A_1, ..., A_N], the instantiation +// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N]. +// +// If T has structural constraints, they must be satisfied by V. +// +// For example, consider the following type declarations: +// +// type Interface[T any] interface { +// Accept(T) +// } +// +// type Container[T any] struct { +// Element T +// } +// +// func (c Container[T]) Accept(t T) { c.Element = t } +// +// In this case, GenericAssignableTo reports that instantiations of Container +// are assignable to the corresponding instantiation of Interface. +func GenericAssignableTo(ctxt *Context, V, T types.Type) bool { + // If V and T are not both named, or do not have matching non-empty type + // parameter lists, fall back on types.AssignableTo. + + VN, Vnamed := V.(*types.Named) + TN, Tnamed := T.(*types.Named) + if !Vnamed || !Tnamed { + return types.AssignableTo(V, T) + } + + vtparams := ForNamed(VN) + ttparams := ForNamed(TN) + if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 { + return types.AssignableTo(V, T) + } + + // V and T have the same (non-zero) number of type params. Instantiate both + // with the type parameters of V. This must always succeed for V, and will + // succeed for T if and only if the type set of each type parameter of V is a + // subset of the type set of the corresponding type parameter of T, meaning + // that every instantiation of V corresponds to a valid instantiation of T. + + // Minor optimization: ensure we share a context across the two + // instantiations below. + if ctxt == nil { + ctxt = NewContext() + } + + var targs []types.Type + for i := 0; i < vtparams.Len(); i++ { + targs = append(targs, vtparams.At(i)) + } + + vinst, err := Instantiate(ctxt, V, targs, true) + if err != nil { + panic("type parameters should satisfy their own constraints") + } + + tinst, err := Instantiate(ctxt, T, targs, true) + if err != nil { + return false + } + + return types.AssignableTo(vinst, tinst) +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/coretype.go new file mode 100644 index 00000000000..993135ec90e --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -0,0 +1,122 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "go/types" +) + +// CoreType returns the core type of T or nil if T does not have a core type. +// +// See https://go.dev/ref/spec#Core_types for the definition of a core type. +func CoreType(T types.Type) types.Type { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return U // for non-interface types, + } + + terms, err := _NormalTerms(U) + if len(terms) == 0 || err != nil { + // len(terms) -> empty type set of interface. + // err != nil => U is invalid, exceeds complexity bounds, or has an empty type set. + return nil // no core type. + } + + U = terms[0].Type().Underlying() + var identical int // i in [0,identical) => Identical(U, terms[i].Type().Underlying()) + for identical = 1; identical < len(terms); identical++ { + if !types.Identical(U, terms[identical].Type().Underlying()) { + break + } + } + + if identical == len(terms) { + // https://go.dev/ref/spec#Core_types + // "There is a single type U which is the underlying type of all types in the type set of T" + return U + } + ch, ok := U.(*types.Chan) + if !ok { + return nil // no core type as identical < len(terms) and U is not a channel. + } + // https://go.dev/ref/spec#Core_types + // "the type chan E if T contains only bidirectional channels, or the type chan<- E or + // <-chan E depending on the direction of the directional channels present." + for chans := identical; chans < len(terms); chans++ { + curr, ok := terms[chans].Type().Underlying().(*types.Chan) + if !ok { + return nil + } + if !types.Identical(ch.Elem(), curr.Elem()) { + return nil // channel elements are not identical. + } + if ch.Dir() == types.SendRecv { + // ch is bidirectional. We can safely always use curr's direction. + ch = curr + } else if curr.Dir() != types.SendRecv && ch.Dir() != curr.Dir() { + // ch and curr are not bidirectional and not the same direction. + return nil + } + } + return ch +} + +// _NormalTerms returns a slice of terms representing the normalized structural +// type restrictions of a type, if any. +// +// For all types other than *types.TypeParam, *types.Interface, and +// *types.Union, this is just a single term with Tilde() == false and +// Type() == typ. For *types.TypeParam, *types.Interface, and *types.Union, see +// below. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration type +// T[P interface{~int; m()}] int the structural restriction of the type +// parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// _NormalTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, _NormalTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the type is +// invalid, exceeds complexity bounds, or has an empty type set. In the latter +// case, _NormalTerms returns ErrEmptyTypeSet. +// +// _NormalTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func _NormalTerms(typ types.Type) ([]*Term, error) { + switch typ := typ.(type) { + case *TypeParam: + return StructuralTerms(typ) + case *Union: + return UnionTermSet(typ) + case *types.Interface: + return InterfaceTermSet(typ) + default: + return []*Term{NewTerm(false, typ)}, nil + } +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go new file mode 100644 index 00000000000..18212390e19 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go @@ -0,0 +1,12 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package typeparams + +// Enabled reports whether type parameters are enabled in the current build +// environment. +const Enabled = false diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go new file mode 100644 index 00000000000..d67148823c4 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go @@ -0,0 +1,15 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.18 +// +build go1.18 + +package typeparams + +// Note: this constant is in a separate file as this is the only acceptable +// diff between the <1.18 API of this package and the 1.18 API. + +// Enabled reports whether type parameters are enabled in the current build +// environment. +const Enabled = true diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/normalize.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/normalize.go new file mode 100644 index 00000000000..9c631b6512d --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/normalize.go @@ -0,0 +1,218 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "errors" + "fmt" + "go/types" + "os" + "strings" +) + +//go:generate go run copytermlist.go + +const debug = false + +var ErrEmptyTypeSet = errors.New("empty type set") + +// StructuralTerms returns a slice of terms representing the normalized +// structural type restrictions of a type parameter, if any. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration +// +// type T[P interface{~int; m()}] int +// +// the structural restriction of the type parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// StructuralTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, StructuralTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the +// constraint interface is invalid, exceeds complexity bounds, or has an empty +// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet. +// +// StructuralTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func StructuralTerms(tparam *TypeParam) ([]*Term, error) { + constraint := tparam.Constraint() + if constraint == nil { + return nil, fmt.Errorf("%s has nil constraint", tparam) + } + iface, _ := constraint.Underlying().(*types.Interface) + if iface == nil { + return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) + } + return InterfaceTermSet(iface) +} + +// InterfaceTermSet computes the normalized terms for a constraint interface, +// returning an error if the term set cannot be computed or is empty. In the +// latter case, the error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func InterfaceTermSet(iface *types.Interface) ([]*Term, error) { + return computeTermSet(iface) +} + +// UnionTermSet computes the normalized terms for a union, returning an error +// if the term set cannot be computed or is empty. In the latter case, the +// error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func UnionTermSet(union *Union) ([]*Term, error) { + return computeTermSet(union) +} + +func computeTermSet(typ types.Type) ([]*Term, error) { + tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) + if err != nil { + return nil, err + } + if tset.terms.isEmpty() { + return nil, ErrEmptyTypeSet + } + if tset.terms.isAll() { + return nil, nil + } + var terms []*Term + for _, term := range tset.terms { + terms = append(terms, NewTerm(term.tilde, term.typ)) + } + return terms, nil +} + +// A termSet holds the normalized set of terms for a given type. +// +// The name termSet is intentionally distinct from 'type set': a type set is +// all types that implement a type (and includes method restrictions), whereas +// a term set just represents the structural restrictions on a type. +type termSet struct { + complete bool + terms termlist +} + +func indentf(depth int, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + +func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { + if t == nil { + panic("nil type") + } + + if debug { + indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + indentf(depth, "=> %s", err) + } else { + indentf(depth, "=> %s", res.terms.String()) + } + }() + } + + const maxTermCount = 100 + if tset, ok := seen[t]; ok { + if !tset.complete { + return nil, fmt.Errorf("cycle detected in the declaration of %s", t) + } + return tset, nil + } + + // Mark the current type as seen to avoid infinite recursion. + tset := new(termSet) + defer func() { + tset.complete = true + }() + seen[t] = tset + + switch u := t.Underlying().(type) { + case *types.Interface: + // The term set of an interface is the intersection of the term sets of its + // embedded types. + tset.terms = allTermlist + for i := 0; i < u.NumEmbeddeds(); i++ { + embedded := u.EmbeddedType(i) + if _, ok := embedded.Underlying().(*TypeParam); ok { + return nil, fmt.Errorf("invalid embedded type %T", embedded) + } + tset2, err := computeTermSetInternal(embedded, seen, depth+1) + if err != nil { + return nil, err + } + tset.terms = tset.terms.intersect(tset2.terms) + } + case *Union: + // The term set of a union is the union of term sets of its terms. + tset.terms = nil + for i := 0; i < u.Len(); i++ { + t := u.Term(i) + var terms termlist + switch t.Type().Underlying().(type) { + case *types.Interface: + tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) + if err != nil { + return nil, err + } + terms = tset2.terms + case *TypeParam, *Union: + // A stand-alone type parameter or union is not permitted as union + // term. + return nil, fmt.Errorf("invalid union term %T", t) + default: + if t.Type() == types.Typ[types.Invalid] { + continue + } + terms = termlist{{t.Tilde(), t.Type()}} + } + tset.terms = tset.terms.union(terms) + if len(tset.terms) > maxTermCount { + return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) + } + } + case *TypeParam: + panic("unreachable") + default: + // For all other types, the term set is just a single non-tilde term + // holding the type itself. + if u != types.Typ[types.Invalid] { + tset.terms = termlist{{false, t}} + } + } + return tset, nil +} + +// under is a facade for the go/types internal function of the same name. It is +// used by typeterm.go. +func under(t types.Type) types.Type { + return t.Underlying() +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/termlist.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/termlist.go new file mode 100644 index 00000000000..933106a23dd --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/termlist.go @@ -0,0 +1,163 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import ( + "bytes" + "go/types" +) + +// A termlist represents the type set represented by the union +// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn. +// A termlist is in normal form if all terms are disjoint. +// termlist operations don't require the operands to be in +// normal form. +type termlist []*term + +// allTermlist represents the set of all types. +// It is in normal form. +var allTermlist = termlist{new(term)} + +// String prints the termlist exactly (without normalization). +func (xl termlist) String() string { + if len(xl) == 0 { + return "∅" + } + var buf bytes.Buffer + for i, x := range xl { + if i > 0 { + buf.WriteString(" ∪ ") + } + buf.WriteString(x.String()) + } + return buf.String() +} + +// isEmpty reports whether the termlist xl represents the empty set of types. +func (xl termlist) isEmpty() bool { + // If there's a non-nil term, the entire list is not empty. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil { + return false + } + } + return true +} + +// isAll reports whether the termlist xl represents the set of all types. +func (xl termlist) isAll() bool { + // If there's a 𝓤 term, the entire list is 𝓤. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil && x.typ == nil { + return true + } + } + return false +} + +// norm returns the normal form of xl. +func (xl termlist) norm() termlist { + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + used := make([]bool, len(xl)) + var rl termlist + for i, xi := range xl { + if xi == nil || used[i] { + continue + } + for j := i + 1; j < len(xl); j++ { + xj := xl[j] + if xj == nil || used[j] { + continue + } + if u1, u2 := xi.union(xj); u2 == nil { + // If we encounter a 𝓤 term, the entire list is 𝓤. + // Exit early. + // (Note that this is not just an optimization; + // if we continue, we may end up with a 𝓤 term + // and other terms and the result would not be + // in normal form.) + if u1.typ == nil { + return allTermlist + } + xi = u1 + used[j] = true // xj is now unioned into xi - ignore it in future iterations + } + } + rl = append(rl, xi) + } + return rl +} + +// union returns the union xl ∪ yl. +func (xl termlist) union(yl termlist) termlist { + return append(xl, yl...).norm() +} + +// intersect returns the intersection xl ∩ yl. +func (xl termlist) intersect(yl termlist) termlist { + if xl.isEmpty() || yl.isEmpty() { + return nil + } + + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + var rl termlist + for _, x := range xl { + for _, y := range yl { + if r := x.intersect(y); r != nil { + rl = append(rl, r) + } + } + } + return rl.norm() +} + +// equal reports whether xl and yl represent the same type set. +func (xl termlist) equal(yl termlist) bool { + // TODO(gri) this should be more efficient + return xl.subsetOf(yl) && yl.subsetOf(xl) +} + +// includes reports whether t ∈ xl. +func (xl termlist) includes(t types.Type) bool { + for _, x := range xl { + if x.includes(t) { + return true + } + } + return false +} + +// supersetOf reports whether y ⊆ xl. +func (xl termlist) supersetOf(y *term) bool { + for _, x := range xl { + if y.subsetOf(x) { + return true + } + } + return false +} + +// subsetOf reports whether xl ⊆ yl. +func (xl termlist) subsetOf(yl termlist) bool { + if yl.isEmpty() { + return xl.isEmpty() + } + + // each term x of xl must be a subset of yl + for _, x := range xl { + if !yl.supersetOf(x) { + return false // x is not a subset yl + } + } + return true +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go new file mode 100644 index 00000000000..b4788978ff4 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go @@ -0,0 +1,197 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +func unsupported() { + panic("type parameters are unsupported at this go version") +} + +// IndexListExpr is a placeholder type, as type parameters are not supported at +// this Go version. Its methods panic on use. +type IndexListExpr struct { + ast.Expr + X ast.Expr // expression + Lbrack token.Pos // position of "[" + Indices []ast.Expr // index expressions + Rbrack token.Pos // position of "]" +} + +// ForTypeSpec returns an empty field list, as type parameters on not supported +// at this Go version. +func ForTypeSpec(*ast.TypeSpec) *ast.FieldList { + return nil +} + +// ForFuncType returns an empty field list, as type parameters are not +// supported at this Go version. +func ForFuncType(*ast.FuncType) *ast.FieldList { + return nil +} + +// TypeParam is a placeholder type, as type parameters are not supported at +// this Go version. Its methods panic on use. +type TypeParam struct{ types.Type } + +func (*TypeParam) Index() int { unsupported(); return 0 } +func (*TypeParam) Constraint() types.Type { unsupported(); return nil } +func (*TypeParam) Obj() *types.TypeName { unsupported(); return nil } + +// TypeParamList is a placeholder for an empty type parameter list. +type TypeParamList struct{} + +func (*TypeParamList) Len() int { return 0 } +func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil } + +// TypeList is a placeholder for an empty type list. +type TypeList struct{} + +func (*TypeList) Len() int { return 0 } +func (*TypeList) At(int) types.Type { unsupported(); return nil } + +// NewTypeParam is unsupported at this Go version, and panics. +func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam { + unsupported() + return nil +} + +// SetTypeParamConstraint is unsupported at this Go version, and panics. +func SetTypeParamConstraint(tparam *TypeParam, constraint types.Type) { + unsupported() +} + +// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or +// typeParams is non-empty. +func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature { + if len(recvTypeParams) != 0 || len(typeParams) != 0 { + panic("signatures cannot have type parameters at this Go version") + } + return types.NewSignature(recv, params, results, variadic) +} + +// ForSignature returns an empty slice. +func ForSignature(*types.Signature) *TypeParamList { + return nil +} + +// RecvTypeParams returns a nil slice. +func RecvTypeParams(sig *types.Signature) *TypeParamList { + return nil +} + +// IsComparable returns false, as no interfaces are type-restricted at this Go +// version. +func IsComparable(*types.Interface) bool { + return false +} + +// IsMethodSet returns true, as no interfaces are type-restricted at this Go +// version. +func IsMethodSet(*types.Interface) bool { + return true +} + +// IsImplicit returns false, as no interfaces are implicit at this Go version. +func IsImplicit(*types.Interface) bool { + return false +} + +// MarkImplicit does nothing, because this Go version does not have implicit +// interfaces. +func MarkImplicit(*types.Interface) {} + +// ForNamed returns an empty type parameter list, as type parameters are not +// supported at this Go version. +func ForNamed(*types.Named) *TypeParamList { + return nil +} + +// SetForNamed panics if tparams is non-empty. +func SetForNamed(_ *types.Named, tparams []*TypeParam) { + if len(tparams) > 0 { + unsupported() + } +} + +// NamedTypeArgs returns nil. +func NamedTypeArgs(*types.Named) *TypeList { + return nil +} + +// NamedTypeOrigin is the identity method at this Go version. +func NamedTypeOrigin(named *types.Named) types.Type { + return named +} + +// Term holds information about a structural type restriction. +type Term struct { + tilde bool + typ types.Type +} + +func (m *Term) Tilde() bool { return m.tilde } +func (m *Term) Type() types.Type { return m.typ } +func (m *Term) String() string { + pre := "" + if m.tilde { + pre = "~" + } + return pre + m.typ.String() +} + +// NewTerm is unsupported at this Go version, and panics. +func NewTerm(tilde bool, typ types.Type) *Term { + return &Term{tilde, typ} +} + +// Union is a placeholder type, as type parameters are not supported at this Go +// version. Its methods panic on use. +type Union struct{ types.Type } + +func (*Union) Len() int { return 0 } +func (*Union) Term(i int) *Term { unsupported(); return nil } + +// NewUnion is unsupported at this Go version, and panics. +func NewUnion(terms []*Term) *Union { + unsupported() + return nil +} + +// InitInstanceInfo is a noop at this Go version. +func InitInstanceInfo(*types.Info) {} + +// Instance is a placeholder type, as type parameters are not supported at this +// Go version. +type Instance struct { + TypeArgs *TypeList + Type types.Type +} + +// GetInstances returns a nil map, as type parameters are not supported at this +// Go version. +func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil } + +// Context is a placeholder type, as type parameters are not supported at +// this Go version. +type Context struct{} + +// NewContext returns a placeholder Context instance. +func NewContext() *Context { + return &Context{} +} + +// Instantiate is unsupported on this Go version, and panics. +func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) { + unsupported() + return nil, nil +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go new file mode 100644 index 00000000000..114a36b866b --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go @@ -0,0 +1,151 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.18 +// +build go1.18 + +package typeparams + +import ( + "go/ast" + "go/types" +) + +// IndexListExpr is an alias for ast.IndexListExpr. +type IndexListExpr = ast.IndexListExpr + +// ForTypeSpec returns n.TypeParams. +func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} + +// ForFuncType returns n.TypeParams. +func ForFuncType(n *ast.FuncType) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} + +// TypeParam is an alias for types.TypeParam +type TypeParam = types.TypeParam + +// TypeParamList is an alias for types.TypeParamList +type TypeParamList = types.TypeParamList + +// TypeList is an alias for types.TypeList +type TypeList = types.TypeList + +// NewTypeParam calls types.NewTypeParam. +func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam { + return types.NewTypeParam(name, constraint) +} + +// SetTypeParamConstraint calls tparam.SetConstraint(constraint). +func SetTypeParamConstraint(tparam *TypeParam, constraint types.Type) { + tparam.SetConstraint(constraint) +} + +// NewSignatureType calls types.NewSignatureType. +func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature { + return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic) +} + +// ForSignature returns sig.TypeParams() +func ForSignature(sig *types.Signature) *TypeParamList { + return sig.TypeParams() +} + +// RecvTypeParams returns sig.RecvTypeParams(). +func RecvTypeParams(sig *types.Signature) *TypeParamList { + return sig.RecvTypeParams() +} + +// IsComparable calls iface.IsComparable(). +func IsComparable(iface *types.Interface) bool { + return iface.IsComparable() +} + +// IsMethodSet calls iface.IsMethodSet(). +func IsMethodSet(iface *types.Interface) bool { + return iface.IsMethodSet() +} + +// IsImplicit calls iface.IsImplicit(). +func IsImplicit(iface *types.Interface) bool { + return iface.IsImplicit() +} + +// MarkImplicit calls iface.MarkImplicit(). +func MarkImplicit(iface *types.Interface) { + iface.MarkImplicit() +} + +// ForNamed extracts the (possibly empty) type parameter object list from +// named. +func ForNamed(named *types.Named) *TypeParamList { + return named.TypeParams() +} + +// SetForNamed sets the type params tparams on n. Each tparam must be of +// dynamic type *types.TypeParam. +func SetForNamed(n *types.Named, tparams []*TypeParam) { + n.SetTypeParams(tparams) +} + +// NamedTypeArgs returns named.TypeArgs(). +func NamedTypeArgs(named *types.Named) *TypeList { + return named.TypeArgs() +} + +// NamedTypeOrigin returns named.Orig(). +func NamedTypeOrigin(named *types.Named) types.Type { + return named.Origin() +} + +// Term is an alias for types.Term. +type Term = types.Term + +// NewTerm calls types.NewTerm. +func NewTerm(tilde bool, typ types.Type) *Term { + return types.NewTerm(tilde, typ) +} + +// Union is an alias for types.Union +type Union = types.Union + +// NewUnion calls types.NewUnion. +func NewUnion(terms []*Term) *Union { + return types.NewUnion(terms) +} + +// InitInstanceInfo initializes info to record information about type and +// function instances. +func InitInstanceInfo(info *types.Info) { + info.Instances = make(map[*ast.Ident]types.Instance) +} + +// Instance is an alias for types.Instance. +type Instance = types.Instance + +// GetInstances returns info.Instances. +func GetInstances(info *types.Info) map[*ast.Ident]Instance { + return info.Instances +} + +// Context is an alias for types.Context. +type Context = types.Context + +// NewContext calls types.NewContext. +func NewContext() *Context { + return types.NewContext() +} + +// Instantiate calls types.Instantiate. +func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) { + return types.Instantiate(ctxt, typ, targs, validate) +} diff --git a/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeterm.go b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeterm.go new file mode 100644 index 00000000000..7ddee28d987 --- /dev/null +++ b/eng/_core/vendor/golang.org/x/tools/internal/typeparams/typeterm.go @@ -0,0 +1,170 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import "go/types" + +// A term describes elementary type sets: +// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +// +type term struct { + tilde bool // valid if typ != nil + typ types.Type +} + +func (x *term) String() string { + switch { + case x == nil: + return "∅" + case x.typ == nil: + return "𝓤" + case x.tilde: + return "~" + x.typ.String() + default: + return x.typ.String() + } +} + +// equal reports whether x and y represent the same type set. +func (x *term) equal(y *term) bool { + // easy cases + switch { + case x == nil || y == nil: + return x == y + case x.typ == nil || y.typ == nil: + return x.typ == y.typ + } + // ∅ ⊂ x, y ⊂ 𝓤 + + return x.tilde == y.tilde && types.Identical(x.typ, y.typ) +} + +// union returns the union x ∪ y: zero, one, or two non-nil terms. +func (x *term) union(y *term) (_, _ *term) { + // easy cases + switch { + case x == nil && y == nil: + return nil, nil // ∅ ∪ ∅ == ∅ + case x == nil: + return y, nil // ∅ ∪ y == y + case y == nil: + return x, nil // x ∪ ∅ == x + case x.typ == nil: + return x, nil // 𝓤 ∪ y == 𝓤 + case y.typ == nil: + return y, nil // x ∪ 𝓤 == 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return x, y // x ∪ y == (x, y) if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∪ ~t == ~t + // ~t ∪ T == ~t + // T ∪ ~t == ~t + // T ∪ T == T + if x.tilde || !y.tilde { + return x, nil + } + return y, nil +} + +// intersect returns the intersection x ∩ y. +func (x *term) intersect(y *term) *term { + // easy cases + switch { + case x == nil || y == nil: + return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅ + case x.typ == nil: + return y // 𝓤 ∩ y == y + case y.typ == nil: + return x // x ∩ 𝓤 == x + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return nil // x ∩ y == ∅ if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∩ ~t == ~t + // ~t ∩ T == T + // T ∩ ~t == T + // T ∩ T == T + if !x.tilde || y.tilde { + return x + } + return y +} + +// includes reports whether t ∈ x. +func (x *term) includes(t types.Type) bool { + // easy cases + switch { + case x == nil: + return false // t ∈ ∅ == false + case x.typ == nil: + return true // t ∈ 𝓤 == true + } + // ∅ ⊂ x ⊂ 𝓤 + + u := t + if x.tilde { + u = under(u) + } + return types.Identical(x.typ, u) +} + +// subsetOf reports whether x ⊆ y. +func (x *term) subsetOf(y *term) bool { + // easy cases + switch { + case x == nil: + return true // ∅ ⊆ y == true + case y == nil: + return false // x ⊆ ∅ == false since x != ∅ + case y.typ == nil: + return true // x ⊆ 𝓤 == true + case x.typ == nil: + return false // 𝓤 ⊆ y == false since y != 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return false // x ⊆ y == false if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ⊆ ~t == true + // ~t ⊆ T == false + // T ⊆ ~t == true + // T ⊆ T == true + return !x.tilde || y.tilde +} + +// disjoint reports whether x ∩ y == ∅. +// x.typ and y.typ must not be nil. +func (x *term) disjoint(y *term) bool { + if debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } + ux := x.typ + if y.tilde { + ux = under(ux) + } + uy := y.typ + if x.tilde { + uy = under(uy) + } + return !types.Identical(ux, uy) +} diff --git a/eng/_core/vendor/modules.txt b/eng/_core/vendor/modules.txt new file mode 100644 index 00000000000..1e80b49eebd --- /dev/null +++ b/eng/_core/vendor/modules.txt @@ -0,0 +1,8 @@ +# github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d +## explicit; go 1.18 +github.com/microsoft/go-infra/executil +github.com/microsoft/go-infra/xcryptofork +# golang.org/x/tools v0.1.12 +## explicit; go 1.18 +golang.org/x/tools/go/ast/astutil +golang.org/x/tools/internal/typeparams diff --git a/eng/_util/go.mod b/eng/_util/go.mod index 8dec8a11486..680ab676ae8 100644 --- a/eng/_util/go.mod +++ b/eng/_util/go.mod @@ -4,13 +4,28 @@ module github.com/microsoft/go/_util -go 1.16 +go 1.18 require ( - github.com/microsoft/go-infra v0.0.0-20220419195018-e437e0d7a6f9 + github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d github.com/microsoft/go/_core v0.0.0 - golang.org/x/sys v0.1.0 + golang.org/x/sys v0.5.0 gotest.tools/gotestsum v1.6.5-0.20210515201937-ecb7c6956f6d ) +require ( + github.com/dnephin/pflag v1.0.7 // indirect + github.com/fatih/color v1.10.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect +) + replace github.com/microsoft/go/_core => ../_core diff --git a/eng/_util/go.sum b/eng/_util/go.sum index 8a2f3a638fa..b9962ee2710 100644 --- a/eng/_util/go.sum +++ b/eng/_util/go.sum @@ -5,20 +5,24 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/microsoft/go-infra v0.0.0-20220419195018-e437e0d7a6f9 h1:TztudEX3sCZvuB9ZrKclZtxNRRE87GF5v7/U7DsLOVo= -github.com/microsoft/go-infra v0.0.0-20220419195018-e437e0d7a6f9/go.mod h1:eGPzRx36eMvtr8J3LLHYyP720NNE6kWLn78BcK/APOs= +github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 h1:YH424zrwLTlyHSH/GzLMJeu5zhYVZSx5RQxGKm1h96s= +github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5/go.mod h1:PoGiBqKSQK1vIfQ+yVaFcGjDySHvym6FM1cNYnwzbrY= +github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d h1:2dYhK+YQIwmN5n/AImhvU6Krn48vgWGCcuvwLb1wEJ4= +github.com/microsoft/go-infra v0.0.0-20230921065609-974951356d4d/go.mod h1:hGTlq0ZBZVmZwHgV+JezVdi8jZ2f4/UydeYvMVjAJLM= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -43,8 +47,8 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/eng/pipeline/steps/init-submodule-task.yml b/eng/pipeline/steps/init-submodule-task.yml index 3ad5392f146..4a01496b7d0 100644 --- a/eng/pipeline/steps/init-submodule-task.yml +++ b/eng/pipeline/steps/init-submodule-task.yml @@ -10,9 +10,7 @@ steps: } if ("$env:FETCH_BEARER_TOKEN") { - fetch_submodule ` - -origin 'https://dnceng@dev.azure.com/dnceng/internal/_git/microsoft-go-mirror' ` - -fetch-bearer-token $env:FETCH_BEARER_TOKEN + fetch_submodule -internal -fetch-bearer-token $env:FETCH_BEARER_TOKEN } else { fetch_submodule } diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 00000000000..899290ee2c0 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,10 @@ +This directory contains sources used to create the toolset override modules. +The Microsoft Go toolset ships with a directory that contains modules that (optionally) replace the modules a `go build` command would use. + +Replacing `x/crypto` with a version of `x/crypto` that uses the OpenSSL and CNG backends is the primary use case. +Some processing is needed to produce the final result that fits the current toolset. +This directory contains the starting point that we plan to share with other Go toolset forks. + +This will change to point at either: +* a shared repo that contains an `x/crypto` submodule and a set of patches, or +* a new repo with pre-applied patches, not a repo that doesn't use patches at all and is maintained as a simple branch fork. diff --git a/modules/golang.org/x/crypto b/modules/golang.org/x/crypto new file mode 160000 index 00000000000..a1aeb9b34eb --- /dev/null +++ b/modules/golang.org/x/crypto @@ -0,0 +1 @@ +Subproject commit a1aeb9b34eb6b8f469bbd66b9cd1c9d905cb3714 diff --git a/patches/0002-Add-crypto-backend-foundation.patch b/patches/0002-Add-crypto-backend-foundation.patch index f404797f2c0..63eaedeaf49 100644 --- a/patches/0002-Add-crypto-backend-foundation.patch +++ b/patches/0002-Add-crypto-backend-foundation.patch @@ -15,11 +15,11 @@ Subject: [PATCH] Add crypto backend foundation src/crypto/ed25519/ed25519_test.go | 2 +- src/crypto/hmac/hmac.go | 2 +- src/crypto/hmac/hmac_test.go | 2 +- - src/crypto/internal/backend/backend_test.go | 30 +++++ + src/crypto/internal/backend/backend_test.go | 30 ++++ src/crypto/internal/backend/bbig/big.go | 17 +++ - src/crypto/internal/backend/common.go | 78 +++++++++++ + src/crypto/internal/backend/common.go | 78 ++++++++++ src/crypto/internal/backend/isrequirefips.go | 9 ++ - src/crypto/internal/backend/nobackend.go | 135 +++++++++++++++++++ + src/crypto/internal/backend/nobackend.go | 143 +++++++++++++++++++ src/crypto/internal/backend/norequirefips.go | 9 ++ src/crypto/internal/backend/stub.s | 10 ++ src/crypto/rand/rand_unix.go | 2 +- @@ -39,11 +39,11 @@ Subject: [PATCH] Add crypto backend foundation src/crypto/tls/handshake_client.go | 25 +++- src/crypto/tls/handshake_server.go | 25 +++- src/crypto/tls/key_schedule.go | 18 ++- - src/crypto/tls/prf.go | 77 +++++++---- + src/crypto/tls/prf.go | 77 +++++++--- src/crypto/tls/prf_test.go | 12 +- src/go/build/deps_test.go | 2 + src/runtime/runtime_boring.go | 5 + - 39 files changed, 445 insertions(+), 65 deletions(-) + 39 files changed, 453 insertions(+), 65 deletions(-) create mode 100644 src/crypto/internal/backend/backend_test.go create mode 100644 src/crypto/internal/backend/bbig/big.go create mode 100644 src/crypto/internal/backend/common.go @@ -360,10 +360,10 @@ index 00000000000000..e5d7570d6d4363 +const isRequireFIPS = true diff --git a/src/crypto/internal/backend/nobackend.go b/src/crypto/internal/backend/nobackend.go new file mode 100644 -index 00000000000000..e3e9817a7c5c40 +index 00000000000000..2a4ad44a34cabc --- /dev/null +++ b/src/crypto/internal/backend/nobackend.go -@@ -0,0 +1,135 @@ +@@ -0,0 +1,143 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. @@ -393,17 +393,25 @@ index 00000000000000..e3e9817a7c5c40 + +func SupportsHash(h crypto.Hash) bool { panic("cryptobackend: not available") } + -+func NewSHA1() hash.Hash { panic("cryptobackend: not available") } -+func NewSHA224() hash.Hash { panic("cryptobackend: not available") } -+func NewSHA256() hash.Hash { panic("cryptobackend: not available") } -+func NewSHA384() hash.Hash { panic("cryptobackend: not available") } -+func NewSHA512() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA1() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA224() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA256() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA384() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA512() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA3_224() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA3_256() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA3_384() hash.Hash { panic("cryptobackend: not available") } ++func NewSHA3_512() hash.Hash { panic("cryptobackend: not available") } + -+func SHA1(p []byte) (sum [20]byte) { panic("cryptobackend: not available") } -+func SHA224(p []byte) (sum [28]byte) { panic("cryptobackend: not available") } -+func SHA256(p []byte) (sum [32]byte) { panic("cryptobackend: not available") } -+func SHA384(p []byte) (sum [48]byte) { panic("cryptobackend: not available") } -+func SHA512(p []byte) (sum [64]byte) { panic("cryptobackend: not available") } ++func SHA1(p []byte) (sum [20]byte) { panic("cryptobackend: not available") } ++func SHA224(p []byte) (sum [28]byte) { panic("cryptobackend: not available") } ++func SHA256(p []byte) (sum [32]byte) { panic("cryptobackend: not available") } ++func SHA384(p []byte) (sum [48]byte) { panic("cryptobackend: not available") } ++func SHA512(p []byte) (sum [64]byte) { panic("cryptobackend: not available") } ++func SHA3_224(p []byte) (sum [28]byte) { panic("cryptobackend: not available") } ++func SHA3_256(p []byte) (sum [32]byte) { panic("cryptobackend: not available") } ++func SHA3_384(p []byte) (sum [48]byte) { panic("cryptobackend: not available") } ++func SHA3_512(p []byte) (sum [64]byte) { panic("cryptobackend: not available") } + +func NewHMAC(h func() hash.Hash, key []byte) hash.Hash { panic("cryptobackend: not available") } + @@ -1088,7 +1096,7 @@ index 8233985a62bd22..f46d4636557714 100644 serverMACString := hex.EncodeToString(serverMAC) clientKeyString := hex.EncodeToString(clientKey) diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go -index ca0c4089a2e505..4a6d42b18c46bc 100644 +index 187dff74cfcb54..0fc11c2fb3ae7b 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -428,6 +428,7 @@ var depsRules = ` diff --git a/patches/0004-Add-OpenSSL-crypto-backend.patch b/patches/0004-Add-OpenSSL-crypto-backend.patch index bd2f300c30b..f87b0d2131f 100644 --- a/patches/0004-Add-OpenSSL-crypto-backend.patch +++ b/patches/0004-Add-OpenSSL-crypto-backend.patch @@ -14,7 +14,7 @@ Subject: [PATCH] Add OpenSSL crypto backend src/crypto/ecdsa/notboring.go | 2 +- src/crypto/internal/backend/bbig/big.go | 2 +- .../internal/backend/bbig/big_openssl.go | 12 + - src/crypto/internal/backend/openssl_linux.go | 261 ++++++++++++++++++ + src/crypto/internal/backend/openssl_linux.go | 277 ++++++++++++++++++ src/crypto/internal/boring/fipstls/stub.s | 2 +- src/crypto/internal/boring/fipstls/tls.go | 2 +- src/crypto/rsa/boring.go | 2 +- @@ -37,7 +37,7 @@ Subject: [PATCH] Add OpenSSL crypto backend .../goexperiment/exp_opensslcrypto_on.go | 9 + src/internal/goexperiment/flags.go | 1 + src/os/exec/exec_test.go | 9 + - 33 files changed, 343 insertions(+), 23 deletions(-) + 33 files changed, 359 insertions(+), 23 deletions(-) create mode 100644 src/crypto/internal/backend/bbig/big_openssl.go create mode 100644 src/crypto/internal/backend/openssl_linux.go create mode 100644 src/internal/goexperiment/exp_opensslcrypto_off.go @@ -190,10 +190,10 @@ index 00000000000000..e6695dd66b1d02 +var Dec = bbig.Dec diff --git a/src/crypto/internal/backend/openssl_linux.go b/src/crypto/internal/backend/openssl_linux.go new file mode 100644 -index 00000000000000..1da89e6645069f +index 00000000000000..39ad98afdb9bc7 --- /dev/null +++ b/src/crypto/internal/backend/openssl_linux.go -@@ -0,0 +1,261 @@ +@@ -0,0 +1,277 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. @@ -322,11 +322,15 @@ index 00000000000000..1da89e6645069f + return openssl.SupportsHash(h) +} + -+func NewSHA1() hash.Hash { return openssl.NewSHA1() } -+func NewSHA224() hash.Hash { return openssl.NewSHA224() } -+func NewSHA256() hash.Hash { return openssl.NewSHA256() } -+func NewSHA384() hash.Hash { return openssl.NewSHA384() } -+func NewSHA512() hash.Hash { return openssl.NewSHA512() } ++func NewSHA1() hash.Hash { return openssl.NewSHA1() } ++func NewSHA224() hash.Hash { return openssl.NewSHA224() } ++func NewSHA256() hash.Hash { return openssl.NewSHA256() } ++func NewSHA384() hash.Hash { return openssl.NewSHA384() } ++func NewSHA512() hash.Hash { return openssl.NewSHA512() } ++func NewSHA3_224() hash.Hash { return openssl.NewSHA3_224() } ++func NewSHA3_256() hash.Hash { return openssl.NewSHA3_256() } ++func NewSHA3_384() hash.Hash { return openssl.NewSHA3_384() } ++func NewSHA3_512() hash.Hash { return openssl.NewSHA3_512() } + +func SHA1(p []byte) (sum [20]byte) { return openssl.SHA1(p) } +func SHA224(p []byte) (sum [28]byte) { return openssl.SHA224(p) } @@ -334,6 +338,18 @@ index 00000000000000..1da89e6645069f +func SHA384(p []byte) (sum [48]byte) { return openssl.SHA384(p) } +func SHA512(p []byte) (sum [64]byte) { return openssl.SHA512(p) } + ++// xcrypto_backend_map:noescape ++func SHA3_224(p []byte) (sum [28]byte) { return openssl.SHA3_224(p) } ++ ++// xcrypto_backend_map:noescape ++func SHA3_256(p []byte) (sum [32]byte) { return openssl.SHA3_256(p) } ++ ++// xcrypto_backend_map:noescape ++func SHA3_384(p []byte) (sum [48]byte) { return openssl.SHA3_384(p) } ++ ++// xcrypto_backend_map:noescape ++func SHA3_512(p []byte) (sum [64]byte) { return openssl.SHA3_512(p) } ++ +func NewHMAC(h func() hash.Hash, key []byte) hash.Hash { return openssl.NewHMAC(h, key) } + +func NewAESCipher(key []byte) (cipher.Block, error) { return openssl.NewAESCipher(key) } @@ -548,7 +564,7 @@ index 1827f764589b58..70baa62d63754a 100644 package tls diff --git a/src/crypto/tls/boring_test.go b/src/crypto/tls/boring_test.go -index ba68f355eb037c..929111d8679cc2 100644 +index 085ff5713ec52f..7b7de66cb7e8c4 100644 --- a/src/crypto/tls/boring_test.go +++ b/src/crypto/tls/boring_test.go @@ -2,7 +2,7 @@ @@ -651,7 +667,7 @@ index c83a7272c9f01f..a0548a7f9179c5 100644 package x509 diff --git a/src/go.mod b/src/go.mod -index beb4d13d8bdc6f..8bc13536fc98c0 100644 +index 021d00b3f6f519..5fe135982d20ee 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,6 +3,7 @@ module std @@ -663,7 +679,7 @@ index beb4d13d8bdc6f..8bc13536fc98c0 100644 golang.org/x/net v0.14.1-0.20230809150940-1e23797619c9 ) diff --git a/src/go.sum b/src/go.sum -index 81b83159f77a36..fca63cfe4a8d1d 100644 +index cae131c06ee904..daaf12048cf9f7 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,3 +1,5 @@ @@ -673,7 +689,7 @@ index 81b83159f77a36..fca63cfe4a8d1d 100644 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/net v0.14.1-0.20230809150940-1e23797619c9 h1:eQR0jFW5dN2q8lFzSF7rjkRCOOnBf0llczNvITm6ICs= diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go -index 4a6d42b18c46bc..0a6be3cc0231fc 100644 +index 0fc11c2fb3ae7b..07bc42530a2cca 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -427,6 +427,8 @@ var depsRules = ` @@ -742,7 +758,7 @@ index 00000000000000..a7f2712e9e1464 +const OpenSSLCrypto = true +const OpenSSLCryptoInt = 1 diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go -index 5d0f5b678b4f9d..416b5002944529 100644 +index 50a1e8ed557a4b..47391d37269b9e 100644 --- a/src/internal/goexperiment/flags.go +++ b/src/internal/goexperiment/flags.go @@ -59,6 +59,7 @@ type Flags struct { diff --git a/patches/0005-Add-CNG-crypto-backend.patch b/patches/0005-Add-CNG-crypto-backend.patch index 5830aab6291..bb7d40bf557 100644 --- a/patches/0005-Add-CNG-crypto-backend.patch +++ b/patches/0005-Add-CNG-crypto-backend.patch @@ -12,7 +12,7 @@ Subject: [PATCH] Add CNG crypto backend src/crypto/internal/backend/backend_test.go | 4 +- src/crypto/internal/backend/bbig/big.go | 2 +- src/crypto/internal/backend/bbig/big_cng.go | 12 + - src/crypto/internal/backend/cng_windows.go | 232 ++++++++++++++++++ + src/crypto/internal/backend/cng_windows.go | 259 ++++++++++++++++++ src/crypto/internal/backend/common.go | 33 ++- src/crypto/internal/boring/fipstls/stub.s | 2 +- src/crypto/internal/boring/fipstls/tls.go | 2 +- @@ -46,7 +46,7 @@ Subject: [PATCH] Add CNG crypto backend .../goexperiment/exp_cngcrypto_off.go | 9 + src/internal/goexperiment/exp_cngcrypto_on.go | 9 + src/internal/goexperiment/flags.go | 1 + - 42 files changed, 422 insertions(+), 40 deletions(-) + 42 files changed, 449 insertions(+), 40 deletions(-) create mode 100644 src/crypto/internal/backend/bbig/big_cng.go create mode 100644 src/crypto/internal/backend/cng_windows.go create mode 100644 src/internal/goexperiment/exp_cngcrypto_off.go @@ -165,10 +165,10 @@ index 00000000000000..92623031fd87d0 +var Dec = bbig.Dec diff --git a/src/crypto/internal/backend/cng_windows.go b/src/crypto/internal/backend/cng_windows.go new file mode 100644 -index 00000000000000..27a0480c8ceb75 +index 00000000000000..b21f212d7d19d2 --- /dev/null +++ b/src/crypto/internal/backend/cng_windows.go -@@ -0,0 +1,232 @@ +@@ -0,0 +1,259 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. @@ -233,6 +233,18 @@ index 00000000000000..27a0480c8ceb75 + return cng.NewSHA512() +} + ++func NewSHA3_256() hash.Hash { ++ return cng.NewSHA3_256() ++} ++ ++func NewSHA3_384() hash.Hash { ++ return cng.NewSHA3_384() ++} ++ ++func NewSHA3_512() hash.Hash { ++ return cng.NewSHA3_512() ++} ++ +func SHA1(p []byte) (sum [20]byte) { + return cng.SHA1(p) +} @@ -251,6 +263,21 @@ index 00000000000000..27a0480c8ceb75 + return cng.SHA512(p) +} + ++// xcrypto_backend_map:noescape ++func SHA3_256(p []byte) (sum [32]byte) { ++ return cng.SHA3_256(p) ++} ++ ++// xcrypto_backend_map:noescape ++func SHA3_384(p []byte) (sum [48]byte) { ++ return cng.SHA3_384(p) ++} ++ ++// xcrypto_backend_map:noescape ++func SHA3_512(p []byte) (sum [64]byte) { ++ return cng.SHA3_512(p) ++} ++ +func NewHMAC(h func() hash.Hash, key []byte) hash.Hash { + return cng.NewHMAC(h, key) +} @@ -897,7 +924,7 @@ index 70baa62d63754a..ecd0f5a7b3e9ed 100644 package tls diff --git a/src/crypto/tls/boring_test.go b/src/crypto/tls/boring_test.go -index 929111d8679cc2..3e63ba6a053c42 100644 +index 7b7de66cb7e8c4..86595e588cf604 100644 --- a/src/crypto/tls/boring_test.go +++ b/src/crypto/tls/boring_test.go @@ -2,7 +2,7 @@ @@ -1016,7 +1043,7 @@ index a0548a7f9179c5..ae6117a1554b7f 100644 package x509 diff --git a/src/go.mod b/src/go.mod -index 8bc13536fc98c0..da1926b3982c3a 100644 +index 5fe135982d20ee..8e312e40abd195 100644 --- a/src/go.mod +++ b/src/go.mod @@ -4,6 +4,7 @@ go 1.22 @@ -1028,7 +1055,7 @@ index 8bc13536fc98c0..da1926b3982c3a 100644 golang.org/x/net v0.14.1-0.20230809150940-1e23797619c9 ) diff --git a/src/go.sum b/src/go.sum -index fca63cfe4a8d1d..0c5126e6ced297 100644 +index daaf12048cf9f7..0323d2b19d2f8f 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,5 +1,7 @@ @@ -1040,7 +1067,7 @@ index fca63cfe4a8d1d..0c5126e6ced297 100644 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/net v0.14.1-0.20230809150940-1e23797619c9 h1:eQR0jFW5dN2q8lFzSF7rjkRCOOnBf0llczNvITm6ICs= diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go -index 0a6be3cc0231fc..d9cf7f503b107b 100644 +index 07bc42530a2cca..416f0c1b83c720 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -427,6 +427,10 @@ var depsRules = ` @@ -1128,7 +1155,7 @@ index 00000000000000..99ee2542ca38a9 +const CNGCrypto = true +const CNGCryptoInt = 1 diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go -index 416b5002944529..9bb027e3c70fe7 100644 +index 47391d37269b9e..88df36e379eece 100644 --- a/src/internal/goexperiment/flags.go +++ b/src/internal/goexperiment/flags.go @@ -60,6 +60,7 @@ type Flags struct { diff --git a/patches/0009-Add-xcryptobackendswap.patch b/patches/0009-Add-xcryptobackendswap.patch new file mode 100644 index 00000000000..9d1d60fe507 --- /dev/null +++ b/patches/0009-Add-xcryptobackendswap.patch @@ -0,0 +1,652 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Davis Goodin +Date: Tue, 22 Aug 2023 17:16:04 -0500 +Subject: [PATCH] Add xcryptobackendswap + +Use '-modfile' for x/crypto replacement when the xcryptobackendswap +experiment is enabled. + +Points at an x/crypto copy in the GOROOT directory or an override +provided by the MS_MODROOT environment variable. + +Includes tests for x/crypto backend swap, using the standard library cmd/go +test framework to run tests on the swapping functionality. + +These can be run without running the other tests using: + + go test cmd/go -testwork -run=Script/^modswap_.*$ +--- + _ms_mod/README.md | 7 + + src/cmd/go/internal/modload/import.go | 6 +- + src/cmd/go/internal/modload/init.go | 10 ++ + src/cmd/go/internal/modload/vendor.go | 16 ++ + src/cmd/go/internal/modload/xcryptoswap.go | 165 ++++++++++++++++++ + src/cmd/go/testdata/script/modswap_basics.txt | 65 +++++++ + .../script/modswap_compatible_vendoring.txt | 40 +++++ + .../go/testdata/script/modswap_nocrypto.txt | 28 +++ + .../testdata/script/modswap_reject_work.txt | 22 +++ + .../testdata/script/modswap_test_openssl.txt | 43 +++++ + .../testdata/script/modswap_uses_openssl.txt | 60 +++++++ + src/internal/cfg/cfg.go | 1 + + .../exp_xcryptobackendswap_off.go | 9 + + .../goexperiment/exp_xcryptobackendswap_on.go | 9 + + src/internal/goexperiment/flags.go | 6 + + 15 files changed, 486 insertions(+), 1 deletion(-) + create mode 100644 _ms_mod/README.md + create mode 100644 src/cmd/go/internal/modload/xcryptoswap.go + create mode 100644 src/cmd/go/testdata/script/modswap_basics.txt + create mode 100644 src/cmd/go/testdata/script/modswap_compatible_vendoring.txt + create mode 100644 src/cmd/go/testdata/script/modswap_nocrypto.txt + create mode 100644 src/cmd/go/testdata/script/modswap_reject_work.txt + create mode 100644 src/cmd/go/testdata/script/modswap_test_openssl.txt + create mode 100644 src/cmd/go/testdata/script/modswap_uses_openssl.txt + create mode 100644 src/internal/goexperiment/exp_xcryptobackendswap_off.go + create mode 100644 src/internal/goexperiment/exp_xcryptobackendswap_on.go + +diff --git a/_ms_mod/README.md b/_ms_mod/README.md +new file mode 100644 +index 00000000000000..3db282dd1701e7 +--- /dev/null ++++ b/_ms_mod/README.md +@@ -0,0 +1,7 @@ ++# _ms_mod: Microsoft modules shipped with the Go toolset ++ ++This dir contains modules that are shipped with the Microsoft Go toolset. ++It is essentially a vendor directory that takes priority over a project's local vendor directory. ++Patches to the toolset enable this behavior if `GOEXPERIMENT` includes `xcryptobackendswap`. ++ ++It begins with `_` to avoid being scanned by the `cmd/internal/moddeps` `TestAllDependencies` test. +diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go +index cc6a482fd450d0..16f056c042481c 100644 +--- a/src/cmd/go/internal/modload/import.go ++++ b/src/cmd/go/internal/modload/import.go +@@ -336,7 +336,11 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M + } + } + +- if HasModRoot() { ++ if dir, vendorDir, ok := toolsetOverridePackage(path); ok { ++ mods = append(mods, vendorPkgModule[path]) ++ dirs = append(dirs, dir) ++ roots = append(roots, vendorDir) ++ } else if HasModRoot() { + vendorDir := VendorDir() + dir, vendorOK, _ := dirInModule(path, "", vendorDir, false) + if vendorOK { +diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go +index e1575de2e0e72b..e2424182cef766 100644 +--- a/src/cmd/go/internal/modload/init.go ++++ b/src/cmd/go/internal/modload/init.go +@@ -481,6 +481,11 @@ func Init() { + // We're in module mode. Set any global variables that need to be set. + cfg.ModulesEnabled = true + setDefaultBuildMod() ++ ++ if err := initXCryptoSwap(); err != nil { ++ base.Fatalf("go: failed to initialize x/crypto replacement: %v", err) ++ } ++ + list := filepath.SplitList(cfg.BuildContext.GOPATH) + if len(list) > 0 && list[0] != "" { + gopath = list[0] +@@ -827,6 +832,11 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error) + // For example, 'go get' does this, since it is expected to resolve paths. + // + // See golang.org/issue/32027. ++ } else if xCryptoSwap { ++ // We duplicated the go.mod file, but keep using the existing go.sum. ++ // The old sum file will contain an unused x/crypto entry, but this is ++ // fine. We still need it for other modules. ++ modfetch.GoSumFile = strings.TrimSuffix(preXCryptoSwapModFile, ".mod") + ".sum" + } else { + modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum" + } +diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go +index b2cb44100ec4d2..23bee33d44d1ba 100644 +--- a/src/cmd/go/internal/modload/vendor.go ++++ b/src/cmd/go/internal/modload/vendor.go +@@ -134,6 +134,22 @@ func readVendorList(vendorDir string) { + } + } + } ++ // Fix "inconsistent vendoring" errors in -mod= and -mod=vendor modes: ++ // ++ // golang.org/x/crypto: is replaced in /tmp/xcrypto-replacement-go-89165251.mod, but not marked as replaced in vendor/modules.txt ++ // ++ // We need to fix this without modifying vendor/modules.txt, so create a ++ // fake replacement here. Doing this in the read func means the rest of ++ // the code should behave as if this is correct, and we don't need to ++ // modify e.g. checkVendorConsistency. ++ if xCryptoSwap { ++ mod.Path = "golang.org/x/crypto" ++ mod.Version = "" ++ meta := vendorMeta[mod] ++ meta.Replacement = module.Version{Path: xCryptoNewModPath()} ++ vendorReplaced = append(vendorReplaced, mod) ++ vendorMeta[mod] = meta ++ } + }) + } + +diff --git a/src/cmd/go/internal/modload/xcryptoswap.go b/src/cmd/go/internal/modload/xcryptoswap.go +new file mode 100644 +index 00000000000000..8262bd64e7c6c5 +--- /dev/null ++++ b/src/cmd/go/internal/modload/xcryptoswap.go +@@ -0,0 +1,165 @@ ++// Copyright 2023 The Go Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++package modload ++ ++import ( ++ "cmd/go/internal/base" ++ "cmd/go/internal/cfg" ++ "cmd/go/internal/lockedfile" ++ "fmt" ++ "io" ++ "os" ++ "path/filepath" ++ "strings" ++ ++ "golang.org/x/mod/modfile" ++) ++ ++var ( ++ // True if x/crypto is being swapped out with a module replacement. ++ xCryptoSwap bool ++ // The path to the original go.mod file before x/crypto was swapped out. ++ // Empty string if no replacement happened. ++ preXCryptoSwapModFile string ++) ++ ++// initXCryptoSwap determines whether x/crypto should be swapped out and if so, performs the swap by ++// creating a temp go.mod with a new replace directive and passing it to cfg.ModFile. ++func initXCryptoSwap() error { ++ // Use build constraint evaluation to find if swapping is enabled in this build context. ++ // This runs the build tag through the same system that is used to evaluate build tags in source ++ // code, so we know it will match up with the build tags the dev intends to use. ++ // ++ // This looks weird: there is no "easy" API to check a build tag. We follow an approach also ++ // used in go/build/build_test.go TestMatchFile: copy the build context to avoid modifying the ++ // original and assign OpenFile to a new func that defines the content of a source file without ++ // actually needing to write anything to disk. ++ backendCheckContext := cfg.BuildContext ++ backendCheckContext.OpenFile = func(path string) (io.ReadCloser, error) { ++ source := "//go:build goexperiment.xcryptobackendswap" ++ return io.NopCloser(strings.NewReader(source)), nil ++ } ++ match, err := backendCheckContext.MatchFile("", "backendcheck.go") ++ if err != nil { ++ return fmt.Errorf("error checking for crypto backend: %v", err) ++ } ++ if !match { ++ return nil ++ } ++ ++ switch cfg.CmdName { ++ // Look for commands where we must not include the replace directive. ++ // ++ // As of writing, this follows the approach of setDefaultBuildMod and checks for specific ++ // command names. When the TODO in setDefaultBuildMod is addressed to handle this more ++ // elegantly (https://github.com/golang/go/issues/40775), we should follow suit. ++ // ++ // E.g. if we included the replacement with "go mod download", it could update the go.sum ++ // file but without the checksum necessary to build with ordinary golang.org/x/crypto. ++ case "get", "mod download", "mod init", "mod tidy", "mod edit", "mod vendor": ++ return nil ++ } ++ if workFilePath != "" { ++ return fmt.Errorf("unable to replace in workspace mode because '-modfile' is not supported") ++ } ++ // At this point there are no known reasons there would be 0 or >1 modRoots, but check just in ++ // case we're missing a scenario. ++ if len(modRoots) != 1 { ++ return fmt.Errorf("expected 1 modroot, found %v, %#v", len(modRoots), modRoots) ++ } ++ ++ oldFilePath := modFilePath(modRoots[0]) ++ data, err := lockedfile.Read(oldFilePath) ++ if err != nil { ++ return fmt.Errorf("failed to read go.mod file: %v", err) ++ } ++ modFile, err := modfile.Parse(oldFilePath, data, nil) ++ if err != nil { ++ return fmt.Errorf("failed to parse go.mod file: %v", err) ++ } ++ // If this is "go build -mod=mod", include the replacement even if the go.mod doesn't include ++ // x/crypto. There might be a transitive dependency we don't know about until imports are ++ // evaluated and find an x/crypto reference. ++ if !modFileUsesXCrypto(modFile) && cfg.BuildMod != "mod" { ++ return nil ++ } ++ // Make sure the replacement actually exists. Perhaps the module was packed ++ // wrong, or MS_MODROOT is set incorrectly. ++ newModPath := xCryptoNewModPath() ++ if fi, err := os.Stat(newModPath); err != nil { ++ return fmt.Errorf("unable to get FileInfo for replacement path: %v", err) ++ } else if !fi.IsDir() { ++ return fmt.Errorf("replacement %#q is not a directory", newModPath) ++ } ++ // Add (or update!) a replace directive. ++ modFile.AddReplace( ++ "golang.org/x/crypto", ++ "", ++ newModPath, ++ "", ++ ) ++ modFile.Cleanup() ++ afterData, err := modFile.Format() ++ if err != nil { ++ return fmt.Errorf("failed to format (marshal) go.mod file: %v", err) ++ } ++ f, err := os.CreateTemp("", "xcrypto-replacement-go-*.mod") ++ if err != nil { ++ return fmt.Errorf("failed to create temp go.mod file: %v", err) ++ } ++ base.AtExit(func() { ++ // Best effort cleanup. ++ if err := os.Remove(f.Name()); err != nil { ++ base.Errorf("go: failed to remove temp go.mod file %q with x/crypto replacement: %v", f.Name(), err) ++ } ++ }) ++ if _, err := f.Write(afterData); err != nil { ++ return fmt.Errorf("failed to write temp go.mod file data: %v", err) ++ } ++ cfg.ModFile = f.Name() ++ xCryptoSwap = true ++ preXCryptoSwapModFile = oldFilePath ++ return nil ++} ++ ++func modFileUsesXCrypto(m *modfile.File) bool { ++ for _, r := range m.Require { ++ if r.Mod.Path == "golang.org/x/crypto" { ++ return true ++ } ++ } ++ return false ++} ++ ++// toolsetOverrideModuleDir returns the path to a directory in GOROOT where ++// copies of modules are kept that may be used when building a program with that ++// particular toolset. It is similar to a vendor directory, but only contains ++// the modules, no modules.txt. For development purposes, the GOMSMODROOT ++// environment variable can redirect to a specified directory. ++func toolsetOverrideModuleDir() string { ++ if msModRoot := cfg.Getenv("GOMSMODROOT"); msModRoot != "" { ++ return msModRoot ++ } ++ return filepath.Join(cfg.BuildContext.GOROOT, "_ms_mod") ++} ++ ++// xCryptoNewModPath returns the module path that x/crypto should be replaced with. ++func xCryptoNewModPath() string { ++ return filepath.Join(toolsetOverrideModuleDir(), "golang.org", "x", "crypto") ++} ++ ++func toolsetOverridePackage(path string) (dir string, vendorDir string, ok bool) { ++ if !HasModRoot() || !xCryptoSwap { ++ return "", "", false ++ } ++ vendorDir = toolsetOverrideModuleDir() ++ // Before checking the main vendor directory, check the toolset's override vendor directory. ++ // This is where the x/crypto fork is located that needs to be swapped in. ++ dir, haveGoFiles, _ := dirInModule(path, "", vendorDir, false) ++ if !haveGoFiles { ++ return "", "", false ++ } ++ return dir, vendorDir, true ++} +diff --git a/src/cmd/go/testdata/script/modswap_basics.txt b/src/cmd/go/testdata/script/modswap_basics.txt +new file mode 100644 +index 00000000000000..c1a938be44dc45 +--- /dev/null ++++ b/src/cmd/go/testdata/script/modswap_basics.txt +@@ -0,0 +1,65 @@ ++# Test the basics of the x/crypto swap. ++ ++[!net:golang.org] skip ++[!git] skip ++ ++env GO111MODULE=on ++env GOPROXY=direct ++env GOSUMDB=off ++ ++go mod tidy ++ ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++ ++# When vendoring isn't involved, the replacement path is used. ++go list -mod= -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++go list -mod=mod -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++go list -mod=readonly -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++ ++# Make sure the build works other than vendor mode. ++go build -mod= . ++go build -mod=mod . ++go build -mod=readonly . ++ ++# Vendor mode shouldn't work because we haven't vendored yet. ++! go list -mod=vendor -deps -f '{{.Dir}}' ++stderr 'inconsistent vendoring' ++! go build -mod=vendor . ++ ++# We must not do the swap when vendoring or else the replacement gets baked in. ++env GOEXPERIMENT= ++go mod vendor ++ ++# Now that vendoring is involved, still use the replacement path. ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++go list -mod= -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++go list -mod=mod -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++go list -mod=readonly -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++go list -mod=vendor -deps -f '{{.Dir}}' ++stdout $GOROOT/_ms_mod/golang.org/x/crypto ++ ++# Make sure the build works and is always the same. ++go build -mod= -o no_mod.out . ++go build -mod=mod -o mod_mod.out . ++go build -mod=readonly -o readonly_mod.out . ++go build -mod=vendor -o vendor_mod.out . ++ ++cmp mod_mod.out no_mod.out ++cmp mod_mod.out readonly_mod.out ++cmp mod_mod.out vendor_mod.out ++ ++-- go.mod -- ++module example.com/modswap ++ ++require golang.org/x/crypto v0.12.0 ++ ++-- imports.go -- ++package replace ++ ++import _ "golang.org/x/crypto/sha3" +diff --git a/src/cmd/go/testdata/script/modswap_compatible_vendoring.txt b/src/cmd/go/testdata/script/modswap_compatible_vendoring.txt +new file mode 100644 +index 00000000000000..9505055005ac9b +--- /dev/null ++++ b/src/cmd/go/testdata/script/modswap_compatible_vendoring.txt +@@ -0,0 +1,40 @@ ++# Test that if we vendor x/crypto while swapping is enabled, the replacement isn't applied and we ++# vendor "normal" x/crypto. ++ ++[!net:golang.org] skip ++[!git] skip ++[!GOOS:linux] skip ++[!GOARCH:amd64] skip ++[!cgo] skip ++ ++env GO111MODULE=on ++env GOPROXY=direct ++env GOSUMDB=off ++ ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++go mod tidy ++go mod vendor ++ ++env GOEXPERIMENT= ++# If we got it wrong and the replacement was applied during "go mod vendor", ++# this will fail due to inconsistent vendoring. ++go test . ++ ++-- go.mod -- ++module example.com/modswap ++ ++require golang.org/x/crypto v0.12.0 ++ ++-- modswap_test.go -- ++package modswap ++ ++import ( ++ "testing" ++ ++ "golang.org/x/crypto/sha3" ++) ++ ++func TestCallSha3_256(t *testing.T) { ++ hash := sha3.New256() ++ hash.Sum([]byte("some data to hash")) ++} +diff --git a/src/cmd/go/testdata/script/modswap_nocrypto.txt b/src/cmd/go/testdata/script/modswap_nocrypto.txt +new file mode 100644 +index 00000000000000..978ef92a7e1700 +--- /dev/null ++++ b/src/cmd/go/testdata/script/modswap_nocrypto.txt +@@ -0,0 +1,28 @@ ++# Test that if there is no use of x/crypto, the swap feature doesn't change the output assembly. ++ ++env GO111MODULE=on ++ ++env GOEXPERIMENT= ++go build -gcflags=-S . ++cp stderr ordinary.out ++ ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++go build -gcflags=-S . ++cp stderr swapped.out ++ ++# We can't directly compare compiled output: the experiment list is embedded ++# in the binary, making it unavoidably different. ++# So, check for the swap by comparing assembly instead. ++cmp ordinary.out swapped.out ++ ++-- go.mod -- ++module example.com/hello ++ ++-- main.go -- ++package replace ++ ++import "fmt" ++ ++func main() { ++ fmt.Println("Hello, world!") ++} +diff --git a/src/cmd/go/testdata/script/modswap_reject_work.txt b/src/cmd/go/testdata/script/modswap_reject_work.txt +new file mode 100644 +index 00000000000000..dc29c609a5c012 +--- /dev/null ++++ b/src/cmd/go/testdata/script/modswap_reject_work.txt +@@ -0,0 +1,22 @@ ++# x/crypto replacement doesn't work with go.work files. Check that it is properly rejected. ++ ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++! go build . ++stderr 'go: failed to initialize x/crypto replacement: unable to replace in workspace mode' ++ ++-- go.work -- ++go 1.22 ++ ++use . ++ ++-- go.mod -- ++module example.com/hello ++ ++-- main.go -- ++package replace ++ ++import "fmt" ++ ++func main() { ++ fmt.Println("Hello, world!") ++} +diff --git a/src/cmd/go/testdata/script/modswap_test_openssl.txt b/src/cmd/go/testdata/script/modswap_test_openssl.txt +new file mode 100644 +index 00000000000000..ee762ee8e36487 +--- /dev/null ++++ b/src/cmd/go/testdata/script/modswap_test_openssl.txt +@@ -0,0 +1,43 @@ ++# Test that "go test" works with the x/crypto swap feature. ++ ++[!net:golang.org] skip ++[!git] skip ++[!GOOS:linux] skip ++[!GOARCH:amd64] skip ++[!cgo] skip ++ ++env GO111MODULE=on ++env GOPROXY=direct ++env GOSUMDB=off ++ ++go mod tidy ++ ++go test . ++ ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++go test . ++ ++-- go.mod -- ++module example.com/modswap ++ ++require golang.org/x/crypto v0.12.0 ++ ++-- modswap_test.go -- ++package modswap ++ ++import ( ++ "bytes" ++ "encoding/hex" ++ "testing" ++ ++ "golang.org/x/crypto/sha3" ++) ++ ++func TestKnownValueSha3_256(t *testing.T) { ++ hash := sha3.New256() ++ r := hash.Sum([]byte("some data to hash")) ++ want, _ := hex.DecodeString("736f6d65206461746120746f2068617368a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a") ++ if !bytes.Equal(r, want) { ++ t.Errorf("want %x, got %x", want, r) ++ } ++} +diff --git a/src/cmd/go/testdata/script/modswap_uses_openssl.txt b/src/cmd/go/testdata/script/modswap_uses_openssl.txt +new file mode 100644 +index 00000000000000..03cbd2ca4a81ac +--- /dev/null ++++ b/src/cmd/go/testdata/script/modswap_uses_openssl.txt +@@ -0,0 +1,60 @@ ++# When x/crypto is swapped (and only then!), the app should use the OpenSSL backend. ++ ++[!net:golang.org] skip ++[!git] skip ++[!GOOS:linux] skip ++[!GOARCH:amd64] skip ++[!cgo] skip ++ ++env GO111MODULE=on ++env GOPROXY=direct ++env GOSUMDB=off ++ ++go mod tidy ++ ++env GOEXPERIMENT= ++go test -run=^TestStandardHasher$ . ++env GOEXPERIMENT=systemcrypto ++go test -run=^TestStandardHasher$ . ++env GOEXPERIMENT=systemcrypto,xcryptobackendswap ++go test -run=^TestOpenSSLHasher$ . ++ ++-- go.mod -- ++module example.com/modswap ++ ++require golang.org/x/crypto v0.12.0 ++ ++-- modswap_test.go -- ++package modswap ++ ++import ( ++ "bytes" ++ "encoding/hex" ++ "reflect" ++ "testing" ++ ++ "golang.org/x/crypto/sha3" ++) ++ ++func TestStandardHasher(t *testing.T) { ++ testHasher(t, "*sha3.state") ++} ++ ++func TestOpenSSLHasher(t *testing.T) { ++ testHasher(t, "*openssl.sha3_256Hash") ++} ++ ++func testHasher(t *testing.T, wantTypeName string) { ++ hash := sha3.New256() ++ // Test that it functions correctly. ++ r := hash.Sum([]byte("some data to hash")) ++ want, _ := hex.DecodeString("736f6d65206461746120746f2068617368a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a") ++ if !bytes.Equal(r, want) { ++ t.Errorf("want %x, got %x", want, r) ++ } ++ // Test that the hasher is from the expected backend. ++ typeName := reflect.TypeOf(hash).String() ++ if typeName != wantTypeName { ++ t.Errorf("want %q, got %q", wantTypeName, typeName) ++ } ++} +diff --git a/src/internal/cfg/cfg.go b/src/internal/cfg/cfg.go +index 2af0ec70786894..5739e06a6aaacd 100644 +--- a/src/internal/cfg/cfg.go ++++ b/src/internal/cfg/cfg.go +@@ -67,4 +67,5 @@ const KnownEnv = ` + GOWORK + GO_EXTLINK_ENABLED + PKG_CONFIG ++ GOMSMODROOT + ` +diff --git a/src/internal/goexperiment/exp_xcryptobackendswap_off.go b/src/internal/goexperiment/exp_xcryptobackendswap_off.go +new file mode 100644 +index 00000000000000..8fceaa28b7e6f0 +--- /dev/null ++++ b/src/internal/goexperiment/exp_xcryptobackendswap_off.go +@@ -0,0 +1,9 @@ ++// Code generated by mkconsts.go. DO NOT EDIT. ++ ++//go:build !goexperiment.xcryptobackendswap ++// +build !goexperiment.xcryptobackendswap ++ ++package goexperiment ++ ++const XCryptoBackendSwap = false ++const XCryptoBackendSwapInt = 0 +diff --git a/src/internal/goexperiment/exp_xcryptobackendswap_on.go b/src/internal/goexperiment/exp_xcryptobackendswap_on.go +new file mode 100644 +index 00000000000000..77018702a444be +--- /dev/null ++++ b/src/internal/goexperiment/exp_xcryptobackendswap_on.go +@@ -0,0 +1,9 @@ ++// Code generated by mkconsts.go. DO NOT EDIT. ++ ++//go:build goexperiment.xcryptobackendswap ++// +build goexperiment.xcryptobackendswap ++ ++package goexperiment ++ ++const XCryptoBackendSwap = true ++const XCryptoBackendSwapInt = 1 +diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go +index 60a1dda7376e38..08e527c2866520 100644 +--- a/src/internal/goexperiment/flags.go ++++ b/src/internal/goexperiment/flags.go +@@ -85,6 +85,12 @@ type Flags struct { + // GOEXPERIMENT=opensslcrypto,allowcryptofallback must be used to succeed. + AllowCryptoFallback bool + ++ // XCryptoBackendSwap enables detection of the golang.org/x/crypto module ++ // and automatically replaces it with an x/crypto module maintained in the ++ // Microsoft Go fork that implements the same API but with the chosen crypto ++ // backend. ++ XCryptoBackendSwap bool ++ + // Regabi is split into several sub-experiments that can be + // enabled individually. Not all combinations work. + // The "regabi" GOEXPERIMENT is an alias for all "working" diff --git a/patches/0010-Add-PBKDF2-MD4.patch b/patches/0010-Add-PBKDF2-MD4.patch new file mode 100644 index 00000000000..9f206ef1a72 --- /dev/null +++ b/patches/0010-Add-PBKDF2-MD4.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Davis Goodin +Date: Thu, 28 Sep 2023 23:39:50 -0500 +Subject: [PATCH] Add PBKDF2, MD4 + +--- + src/crypto/internal/backend/boring_linux.go | 9 +++++++++ + src/crypto/internal/backend/cng_windows.go | 9 +++++++++ + src/crypto/internal/backend/nobackend.go | 9 +++++++++ + src/crypto/internal/backend/openssl_linux.go | 9 +++++++++ + 4 files changed, 36 insertions(+) + +diff --git a/src/crypto/internal/backend/boring_linux.go b/src/crypto/internal/backend/boring_linux.go +index 35e1d00d29980d..86e74b68f87c77 100644 +--- a/src/crypto/internal/backend/boring_linux.go ++++ b/src/crypto/internal/backend/boring_linux.go +@@ -159,3 +159,12 @@ func SupportsTLS1PRF() bool { return false } + func TLS1PRF(result, secret, label, seed []byte, h func() hash.Hash) error { + panic("cryptobackend: not available") + } ++ ++func SupportsPBKDF2() bool { return false } ++ ++func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) ([]byte, error) { ++ panic("cryptobackend: not available") ++} ++ ++func NewMD4() hash.Hash { panic("cryptobackend: not available") } ++func MD4(p []byte) (sum [16]byte) { panic("cryptobackend: not available") } +diff --git a/src/crypto/internal/backend/cng_windows.go b/src/crypto/internal/backend/cng_windows.go +index b21f212d7d19d2..efec8b885694de 100644 +--- a/src/crypto/internal/backend/cng_windows.go ++++ b/src/crypto/internal/backend/cng_windows.go +@@ -257,3 +257,12 @@ func SupportsTLS1PRF() bool { + func TLS1PRF(result, secret, label, seed []byte, h func() hash.Hash) error { + return cng.TLS1PRF(result, secret, label, seed, h) + } ++ ++func SupportsPBKDF2() bool { return true } ++ ++func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) ([]byte, error) { ++ return cng.PBKDF2(password, salt, iter, keyLen, h) ++} ++ ++func NewMD4() hash.Hash { return cng.NewMD4() } ++func MD4(p []byte) (sum [16]byte) { return cng.MD4(p) } +diff --git a/src/crypto/internal/backend/nobackend.go b/src/crypto/internal/backend/nobackend.go +index 4a7c4893b2357f..82ab6505583d34 100644 +--- a/src/crypto/internal/backend/nobackend.go ++++ b/src/crypto/internal/backend/nobackend.go +@@ -141,3 +141,12 @@ func SupportsTLS1PRF() bool { panic("cryptobackend: not available") } + func TLS1PRF(result, secret, label, seed []byte, h func() hash.Hash) error { + panic("cryptobackend: not available") + } ++ ++func SupportsPBKDF2() bool { panic("cryptobackend: not available") } ++ ++func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) ([]byte, error) { ++ panic("cryptobackend: not available") ++} ++ ++func NewMD4() hash.Hash { panic("cryptobackend: not available") } ++func MD4(p []byte) (sum [16]byte) { panic("cryptobackend: not available") } +diff --git a/src/crypto/internal/backend/openssl_linux.go b/src/crypto/internal/backend/openssl_linux.go +index 39ad98afdb9bc7..99802ebfde7ddf 100644 +--- a/src/crypto/internal/backend/openssl_linux.go ++++ b/src/crypto/internal/backend/openssl_linux.go +@@ -275,3 +275,12 @@ func SupportsTLS1PRF() bool { + func TLS1PRF(result, secret, label, seed []byte, h func() hash.Hash) error { + return openssl.TLS1PRF(result, secret, label, seed, h) + } ++ ++func SupportsPBKDF2() bool { return true } ++ ++func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) ([]byte, error) { ++ return openssl.PBKDF2(password, salt, iter, keyLen, h) ++} ++ ++func NewMD4() hash.Hash { return openssl.NewMD4() } ++func MD4(p []byte) (sum [16]byte) { return openssl.MD4(p) }