diff options
Diffstat (limited to 'src/internal')
32 files changed, 590 insertions, 142 deletions
diff --git a/src/internal/abi/abi.go b/src/internal/abi/abi.go new file mode 100644 index 0000000000..6700facc04 --- /dev/null +++ b/src/internal/abi/abi.go @@ -0,0 +1,53 @@ +// Copyright 2020 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 abi + +import "unsafe" + +// RegArgs is a struct that has space for each argument +// and return value register on the current architecture. +// +// Assembly code knows the layout of the first two fields +// of RegArgs. +// +// RegArgs also contains additional space to hold pointers +// when it may not be safe to keep them only in the integer +// register space otherwise. +type RegArgs struct { + Ints [IntArgRegs]uintptr // untyped integer registers + Floats [FloatArgRegs]uint64 // untyped float registers + + // Fields above this point are known to assembly. + + // Ptrs is a space that duplicates Ints but with pointer type, + // used to make pointers passed or returned in registers + // visible to the GC by making the type unsafe.Pointer. + Ptrs [IntArgRegs]unsafe.Pointer + + // ReturnIsPtr is a bitmap that indicates which registers + // contain or will contain pointers on the return path from + // a reflectcall. The i'th bit indicates whether the i'th + // register contains or will contain a valid Go pointer. + ReturnIsPtr IntArgRegBitmap +} + +// IntArgRegBitmap is a bitmap large enough to hold one bit per +// integer argument/return register. +type IntArgRegBitmap [(IntArgRegs + 7) / 8]uint8 + +// Set sets the i'th bit of the bitmap to 1. +func (b *IntArgRegBitmap) Set(i int) { + b[i/8] |= uint8(1) << (i % 8) +} + +// Get returns whether the i'th bit of the bitmap is set. +// +// nosplit because it's called in extremely sensitive contexts, like +// on the reflectcall return path. +// +//go:nosplit +func (b *IntArgRegBitmap) Get(i int) bool { + return b[i/8]&(uint8(1)<<(i%8)) != 0 +} diff --git a/src/internal/abi/abi_amd64.go b/src/internal/abi/abi_amd64.go new file mode 100644 index 0000000000..70e2ed1feb --- /dev/null +++ b/src/internal/abi/abi_amd64.go @@ -0,0 +1,24 @@ +// Copyright 2020 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. + +// +build goexperiment.regabi + +package abi + +const ( + // See abi_generic.go. + + // Currently these values are zero because whatever uses + // them will expect the register ABI, which isn't ready + // yet. + + // RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11. + IntArgRegs = 0 // 9 + + // X0 -> X14. + FloatArgRegs = 0 // 15 + + // We use SSE2 registers which support 64-bit float operations. + EffectiveFloatRegSize = 0 // 8 +) diff --git a/src/internal/abi/abi_generic.go b/src/internal/abi/abi_generic.go new file mode 100644 index 0000000000..5ef9883dc6 --- /dev/null +++ b/src/internal/abi/abi_generic.go @@ -0,0 +1,38 @@ +// Copyright 2020 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. + +// +build !goexperiment.regabi + +package abi + +const ( + // ABI-related constants. + // + // In the generic case, these are all zero + // which lets them gracefully degrade to ABI0. + + // IntArgRegs is the number of registers dedicated + // to passing integer argument values. Result registers are identical + // to argument registers, so this number is used for those too. + IntArgRegs = 0 + + // FloatArgRegs is the number of registers dedicated + // to passing floating-point argument values. Result registers are + // identical to argument registers, so this number is used for + // those too. + FloatArgRegs = 0 + + // EffectiveFloatRegSize describes the width of floating point + // registers on the current platform from the ABI's perspective. + // + // Since Go only supports 32-bit and 64-bit floating point primitives, + // this number should be either 0, 4, or 8. 0 indicates no floating + // point registers for the ABI or that floating point values will be + // passed via the softfloat ABI. + // + // For platforms that support larger floating point register widths, + // such as x87's 80-bit "registers" (not that we support x87 currently), + // use 8. + EffectiveFloatRegSize = 0 +) diff --git a/src/internal/cpu/cpu_android.go b/src/internal/cpu/cpu_android.go deleted file mode 100644 index d995e8d5a7..0000000000 --- a/src/internal/cpu/cpu_android.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2020 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 cpu - -const GOOS = "android" diff --git a/src/internal/cpu/cpu_arm64.go b/src/internal/cpu/cpu_arm64.go index 4e9ea8ca96..f64d9e4dd3 100644 --- a/src/internal/cpu/cpu_arm64.go +++ b/src/internal/cpu/cpu_arm64.go @@ -6,21 +6,6 @@ package cpu const CacheLinePadSize = 64 -// HWCap may be initialized by archauxv and -// should not be changed after it was initialized. -var HWCap uint - -// HWCAP bits. These are exposed by Linux. -const ( - hwcap_AES = 1 << 3 - hwcap_PMULL = 1 << 4 - hwcap_SHA1 = 1 << 5 - hwcap_SHA2 = 1 << 6 - hwcap_CRC32 = 1 << 7 - hwcap_ATOMICS = 1 << 8 - hwcap_CPUID = 1 << 11 -) - func doinit() { options = []option{ {Name: "aes", Feature: &ARM64.HasAES}, @@ -34,86 +19,8 @@ func doinit() { {Name: "isZeus", Feature: &ARM64.IsZeus}, } - switch GOOS { - case "linux", "android": - // HWCap was populated by the runtime from the auxillary vector. - // Use HWCap information since reading aarch64 system registers - // is not supported in user space on older linux kernels. - ARM64.HasAES = isSet(HWCap, hwcap_AES) - ARM64.HasPMULL = isSet(HWCap, hwcap_PMULL) - ARM64.HasSHA1 = isSet(HWCap, hwcap_SHA1) - ARM64.HasSHA2 = isSet(HWCap, hwcap_SHA2) - ARM64.HasCRC32 = isSet(HWCap, hwcap_CRC32) - ARM64.HasCPUID = isSet(HWCap, hwcap_CPUID) - - // The Samsung S9+ kernel reports support for atomics, but not all cores - // actually support them, resulting in SIGILL. See issue #28431. - // TODO(elias.naur): Only disable the optimization on bad chipsets on android. - ARM64.HasATOMICS = isSet(HWCap, hwcap_ATOMICS) && GOOS != "android" - - // Check to see if executing on a NeoverseN1 and in order to do that, - // check the AUXV for the CPUID bit. The getMIDR function executes an - // instruction which would normally be an illegal instruction, but it's - // trapped by the kernel, the value sanitized and then returned. Without - // the CPUID bit the kernel will not trap the instruction and the process - // will be terminated with SIGILL. - if ARM64.HasCPUID { - midr := getMIDR() - part_num := uint16((midr >> 4) & 0xfff) - implementor := byte((midr >> 24) & 0xff) - - if implementor == 'A' && part_num == 0xd0c { - ARM64.IsNeoverseN1 = true - } - if implementor == 'A' && part_num == 0xd40 { - ARM64.IsZeus = true - } - } - - case "freebsd": - // Retrieve info from system register ID_AA64ISAR0_EL1. - isar0 := getisar0() - - // ID_AA64ISAR0_EL1 - switch extractBits(isar0, 4, 7) { - case 1: - ARM64.HasAES = true - case 2: - ARM64.HasAES = true - ARM64.HasPMULL = true - } - - switch extractBits(isar0, 8, 11) { - case 1: - ARM64.HasSHA1 = true - } - - switch extractBits(isar0, 12, 15) { - case 1, 2: - ARM64.HasSHA2 = true - } - - switch extractBits(isar0, 16, 19) { - case 1: - ARM64.HasCRC32 = true - } - - switch extractBits(isar0, 20, 23) { - case 2: - ARM64.HasATOMICS = true - } - default: - // Other operating systems do not support reading HWCap from auxillary vector - // or reading privileged aarch64 system registers in user space. - } -} - -func extractBits(data uint64, start, end uint) uint { - return (uint)(data>>start) & ((1 << (end - start + 1)) - 1) -} - -func isSet(hwc uint, value uint) bool { - return hwc&value != 0 + // arm64 uses different ways to detect CPU features at runtime depending on the operating system. + osInit() } func getisar0() uint64 diff --git a/src/internal/cpu/cpu_linux.go b/src/internal/cpu/cpu_arm64_android.go index ec0b84c510..3c9e57c52a 100644 --- a/src/internal/cpu/cpu_linux.go +++ b/src/internal/cpu/cpu_arm64_android.go @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !android +// +build arm64 package cpu -const GOOS = "linux" +func osInit() { + hwcapInit("android") +} diff --git a/src/internal/cpu/cpu_arm64_darwin.go b/src/internal/cpu/cpu_arm64_darwin.go new file mode 100644 index 0000000000..e094b97f97 --- /dev/null +++ b/src/internal/cpu/cpu_arm64_darwin.go @@ -0,0 +1,34 @@ +// Copyright 2020 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. + +// +build arm64 +// +build darwin +// +build !ios + +package cpu + +func osInit() { + ARM64.HasATOMICS = sysctlEnabled([]byte("hw.optional.armv8_1_atomics\x00")) + ARM64.HasCRC32 = sysctlEnabled([]byte("hw.optional.armv8_crc32\x00")) + + // There are no hw.optional sysctl values for the below features on Mac OS 11.0 + // to detect their supported state dynamically. Assume the CPU features that + // Apple Silicon M1 supports to be available as a minimal set of features + // to all Go programs running on darwin/arm64. + ARM64.HasAES = true + ARM64.HasPMULL = true + ARM64.HasSHA1 = true + ARM64.HasSHA2 = true +} + +//go:noescape +func getsysctlbyname(name []byte) (int32, int32) + +func sysctlEnabled(name []byte) bool { + ret, value := getsysctlbyname(name) + if ret < 0 { + return false + } + return value > 0 +} diff --git a/src/internal/cpu/cpu_arm64_freebsd.go b/src/internal/cpu/cpu_arm64_freebsd.go new file mode 100644 index 0000000000..9de2005c2e --- /dev/null +++ b/src/internal/cpu/cpu_arm64_freebsd.go @@ -0,0 +1,45 @@ +// Copyright 2020 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. + +// +build arm64 + +package cpu + +func osInit() { + // Retrieve info from system register ID_AA64ISAR0_EL1. + isar0 := getisar0() + + // ID_AA64ISAR0_EL1 + switch extractBits(isar0, 4, 7) { + case 1: + ARM64.HasAES = true + case 2: + ARM64.HasAES = true + ARM64.HasPMULL = true + } + + switch extractBits(isar0, 8, 11) { + case 1: + ARM64.HasSHA1 = true + } + + switch extractBits(isar0, 12, 15) { + case 1, 2: + ARM64.HasSHA2 = true + } + + switch extractBits(isar0, 16, 19) { + case 1: + ARM64.HasCRC32 = true + } + + switch extractBits(isar0, 20, 23) { + case 2: + ARM64.HasATOMICS = true + } +} + +func extractBits(data uint64, start, end uint) uint { + return (uint)(data>>start) & ((1 << (end - start + 1)) - 1) +} diff --git a/src/internal/cpu/cpu_arm64_hwcap.go b/src/internal/cpu/cpu_arm64_hwcap.go new file mode 100644 index 0000000000..fdaf43e1a2 --- /dev/null +++ b/src/internal/cpu/cpu_arm64_hwcap.go @@ -0,0 +1,63 @@ +// Copyright 2020 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. + +// +build arm64 +// +build linux + +package cpu + +// HWCap may be initialized by archauxv and +// should not be changed after it was initialized. +var HWCap uint + +// HWCAP bits. These are exposed by Linux. +const ( + hwcap_AES = 1 << 3 + hwcap_PMULL = 1 << 4 + hwcap_SHA1 = 1 << 5 + hwcap_SHA2 = 1 << 6 + hwcap_CRC32 = 1 << 7 + hwcap_ATOMICS = 1 << 8 + hwcap_CPUID = 1 << 11 +) + +func hwcapInit(os string) { + // HWCap was populated by the runtime from the auxiliary vector. + // Use HWCap information since reading aarch64 system registers + // is not supported in user space on older linux kernels. + ARM64.HasAES = isSet(HWCap, hwcap_AES) + ARM64.HasPMULL = isSet(HWCap, hwcap_PMULL) + ARM64.HasSHA1 = isSet(HWCap, hwcap_SHA1) + ARM64.HasSHA2 = isSet(HWCap, hwcap_SHA2) + ARM64.HasCRC32 = isSet(HWCap, hwcap_CRC32) + ARM64.HasCPUID = isSet(HWCap, hwcap_CPUID) + + // The Samsung S9+ kernel reports support for atomics, but not all cores + // actually support them, resulting in SIGILL. See issue #28431. + // TODO(elias.naur): Only disable the optimization on bad chipsets on android. + ARM64.HasATOMICS = isSet(HWCap, hwcap_ATOMICS) && os != "android" + + // Check to see if executing on a NeoverseN1 and in order to do that, + // check the AUXV for the CPUID bit. The getMIDR function executes an + // instruction which would normally be an illegal instruction, but it's + // trapped by the kernel, the value sanitized and then returned. Without + // the CPUID bit the kernel will not trap the instruction and the process + // will be terminated with SIGILL. + if ARM64.HasCPUID { + midr := getMIDR() + part_num := uint16((midr >> 4) & 0xfff) + implementor := byte((midr >> 24) & 0xff) + + if implementor == 'A' && part_num == 0xd0c { + ARM64.IsNeoverseN1 = true + } + if implementor == 'A' && part_num == 0xd40 { + ARM64.IsZeus = true + } + } +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} diff --git a/src/internal/cpu/cpu_other.go b/src/internal/cpu/cpu_arm64_linux.go index 8a15fbe79d..2f7411ff1e 100644 --- a/src/internal/cpu/cpu_other.go +++ b/src/internal/cpu/cpu_arm64_linux.go @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux -// +build !freebsd +// +build arm64 +// +build linux // +build !android package cpu -const GOOS = "other" +func osInit() { + hwcapInit("linux") +} diff --git a/src/internal/cpu/cpu_arm64_other.go b/src/internal/cpu/cpu_arm64_other.go new file mode 100644 index 0000000000..f191db28d2 --- /dev/null +++ b/src/internal/cpu/cpu_arm64_other.go @@ -0,0 +1,17 @@ +// Copyright 2020 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. + +// +build arm64 +// +build !linux +// +build !freebsd +// +build !android +// +build !darwin ios + +package cpu + +func osInit() { + // Other operating systems do not support reading HWCap from auxiliary vector, + // reading privileged aarch64 system registers or sysctl in user space to detect + // CPU features at runtime. +} diff --git a/src/internal/cpu/cpu_freebsd.go b/src/internal/cpu/cpu_freebsd.go deleted file mode 100644 index dc37173dac..0000000000 --- a/src/internal/cpu/cpu_freebsd.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2020 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 cpu - -const GOOS = "freebsd" diff --git a/src/internal/cpu/cpu_s390x_test.go b/src/internal/cpu/cpu_s390x_test.go index d910bbe695..ad86858db0 100644 --- a/src/internal/cpu/cpu_s390x_test.go +++ b/src/internal/cpu/cpu_s390x_test.go @@ -7,13 +7,13 @@ package cpu_test import ( "errors" . "internal/cpu" - "io/ioutil" + "os" "regexp" "testing" ) func getFeatureList() ([]string, error) { - cpuinfo, err := ioutil.ReadFile("/proc/cpuinfo") + cpuinfo, err := os.ReadFile("/proc/cpuinfo") if err != nil { return nil, err } diff --git a/src/internal/cpu/cpu_test.go b/src/internal/cpu/cpu_test.go index 919bbd5ed7..2de7365732 100644 --- a/src/internal/cpu/cpu_test.go +++ b/src/internal/cpu/cpu_test.go @@ -18,7 +18,7 @@ func TestMinimalFeatures(t *testing.T) { // TODO: maybe do MustSupportFeatureDectection(t) ? if runtime.GOARCH == "arm64" { switch runtime.GOOS { - case "linux", "android": + case "linux", "android", "darwin": default: t.Skipf("%s/%s is not supported", runtime.GOOS, runtime.GOARCH) } @@ -38,10 +38,7 @@ func MustHaveDebugOptionsSupport(t *testing.T) { } func MustSupportFeatureDectection(t *testing.T) { - if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { - t.Skipf("CPU feature detection is not supported on %s/%s", runtime.GOOS, runtime.GOARCH) - } - // TODO: maybe there are other platforms? + // TODO: add platforms that do not have CPU feature detection support. } func runDebugOptionsTest(t *testing.T, test string, options string) { diff --git a/src/internal/cpu/cpu_x86.go b/src/internal/cpu/cpu_x86.go index fb414adaf8..ba6bf69034 100644 --- a/src/internal/cpu/cpu_x86.go +++ b/src/internal/cpu/cpu_x86.go @@ -75,13 +75,22 @@ func doinit() { X86.HasSSE3 = isSet(ecx1, cpuid_SSE3) X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ) X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3) - X86.HasFMA = isSet(ecx1, cpuid_FMA) X86.HasSSE41 = isSet(ecx1, cpuid_SSE41) X86.HasSSE42 = isSet(ecx1, cpuid_SSE42) X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT) X86.HasAES = isSet(ecx1, cpuid_AES) + + // OSXSAVE can be false when using older Operating Systems + // or when explicitly disabled on newer Operating Systems by + // e.g. setting the xsavedisable boot option on Windows 10. X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE) + // The FMA instruction set extension only has VEX prefixed instructions. + // VEX prefixed instructions require OSXSAVE to be enabled. + // See Intel 64 and IA-32 Architecture Software Developer’s Manual Volume 2 + // Section 2.4 "AVX and SSE Instruction Exception Specification" + X86.HasFMA = isSet(ecx1, cpuid_FMA) && X86.HasOSXSAVE + osSupportsAVX := false // For XGETBV, OSXSAVE bit is required and sufficient. if X86.HasOSXSAVE { diff --git a/src/internal/execabs/execabs.go b/src/internal/execabs/execabs.go new file mode 100644 index 0000000000..9a05d971da --- /dev/null +++ b/src/internal/execabs/execabs.go @@ -0,0 +1,70 @@ +// 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 execabs is a drop-in replacement for os/exec +// that requires PATH lookups to find absolute paths. +// That is, execabs.Command("cmd") runs the same PATH lookup +// as exec.Command("cmd"), but if the result is a path +// which is relative, the Run and Start methods will report +// an error instead of running the executable. +package execabs + +import ( + "context" + "fmt" + "os/exec" + "path/filepath" + "reflect" + "unsafe" +) + +var ErrNotFound = exec.ErrNotFound + +type ( + Cmd = exec.Cmd + Error = exec.Error + ExitError = exec.ExitError +) + +func relError(file, path string) error { + return fmt.Errorf("%s resolves to executable relative to current directory (.%c%s)", file, filepath.Separator, path) +} + +func LookPath(file string) (string, error) { + path, err := exec.LookPath(file) + if err != nil { + return "", err + } + if filepath.Base(file) == file && !filepath.IsAbs(path) { + return "", relError(file, path) + } + return path, nil +} + +func fixCmd(name string, cmd *exec.Cmd) { + if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) { + // exec.Command was called with a bare binary name and + // exec.LookPath returned a path which is not absolute. + // Set cmd.lookPathErr and clear cmd.Path so that it + // cannot be run. + lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer())) + if *lookPathErr == nil { + *lookPathErr = relError(name, cmd.Path) + } + cmd.Path = "" + } +} + +func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { + cmd := exec.CommandContext(ctx, name, arg...) + fixCmd(name, cmd) + return cmd + +} + +func Command(name string, arg ...string) *exec.Cmd { + cmd := exec.Command(name, arg...) + fixCmd(name, cmd) + return cmd +} diff --git a/src/internal/execabs/execabs_test.go b/src/internal/execabs/execabs_test.go new file mode 100644 index 0000000000..b71458587c --- /dev/null +++ b/src/internal/execabs/execabs_test.go @@ -0,0 +1,104 @@ +// Copyright 2020 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 execabs + +import ( + "context" + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" +) + +func TestFixCmd(t *testing.T) { + cmd := &exec.Cmd{Path: "hello"} + fixCmd("hello", cmd) + if cmd.Path != "" { + t.Error("fixCmd didn't clear cmd.Path") + } + expectedErr := fmt.Sprintf("hello resolves to executable relative to current directory (.%chello)", filepath.Separator) + if err := cmd.Run(); err == nil { + t.Fatal("Command.Run didn't fail") + } else if err.Error() != expectedErr { + t.Fatalf("Command.Run returned unexpected error: want %q, got %q", expectedErr, err.Error()) + } +} + +func TestCommand(t *testing.T) { + testenv.MustHaveExec(t) + + for _, cmd := range []func(string) *Cmd{ + func(s string) *Cmd { return Command(s) }, + func(s string) *Cmd { return CommandContext(context.Background(), s) }, + } { + tmpDir := t.TempDir() + executable := "execabs-test" + if runtime.GOOS == "windows" { + executable += ".exe" + } + if err := ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil { + t.Fatalf("ioutil.WriteFile failed: %s", err) + } + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("os.Getwd failed: %s", err) + } + defer os.Chdir(cwd) + if err = os.Chdir(tmpDir); err != nil { + t.Fatalf("os.Chdir failed: %s", err) + } + if runtime.GOOS != "windows" { + // add "." to PATH so that exec.LookPath looks in the current directory on + // non-windows platforms as well + origPath := os.Getenv("PATH") + defer os.Setenv("PATH", origPath) + os.Setenv("PATH", fmt.Sprintf(".:%s", origPath)) + } + expectedErr := fmt.Sprintf("execabs-test resolves to executable relative to current directory (.%c%s)", filepath.Separator, executable) + if err = cmd("execabs-test").Run(); err == nil { + t.Fatalf("Command.Run didn't fail when exec.LookPath returned a relative path") + } else if err.Error() != expectedErr { + t.Errorf("Command.Run returned unexpected error: want %q, got %q", expectedErr, err.Error()) + } + } +} + +func TestLookPath(t *testing.T) { + testenv.MustHaveExec(t) + + tmpDir := t.TempDir() + executable := "execabs-test" + if runtime.GOOS == "windows" { + executable += ".exe" + } + if err := ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil { + t.Fatalf("ioutil.WriteFile failed: %s", err) + } + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("os.Getwd failed: %s", err) + } + defer os.Chdir(cwd) + if err = os.Chdir(tmpDir); err != nil { + t.Fatalf("os.Chdir failed: %s", err) + } + if runtime.GOOS != "windows" { + // add "." to PATH so that exec.LookPath looks in the current directory on + // non-windows platforms as well + origPath := os.Getenv("PATH") + defer os.Setenv("PATH", origPath) + os.Setenv("PATH", fmt.Sprintf(".:%s", origPath)) + } + expectedErr := fmt.Sprintf("execabs-test resolves to executable relative to current directory (.%c%s)", filepath.Separator, executable) + if _, err := LookPath("execabs-test"); err == nil { + t.Fatalf("LookPath didn't fail when finding a non-relative path") + } else if err.Error() != expectedErr { + t.Errorf("LookPath returned unexpected error: want %q, got %q", expectedErr, err.Error()) + } +} diff --git a/src/internal/fmtsort/sort.go b/src/internal/fmtsort/sort.go index b01229bd06..7127ba6ac3 100644 --- a/src/internal/fmtsort/sort.go +++ b/src/internal/fmtsort/sort.go @@ -130,7 +130,7 @@ func compare(aVal, bVal reflect.Value) int { default: return -1 } - case reflect.Ptr: + case reflect.Ptr, reflect.UnsafePointer: a, b := aVal.Pointer(), bVal.Pointer() switch { case a < b: diff --git a/src/internal/fmtsort/sort_test.go b/src/internal/fmtsort/sort_test.go index aaa0004666..5c4db1c5fa 100644 --- a/src/internal/fmtsort/sort_test.go +++ b/src/internal/fmtsort/sort_test.go @@ -11,6 +11,7 @@ import ( "reflect" "strings" "testing" + "unsafe" ) var compareTests = [][]reflect.Value{ @@ -32,6 +33,7 @@ var compareTests = [][]reflect.Value{ ct(reflect.TypeOf(complex128(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i), ct(reflect.TypeOf(false), false, true), ct(reflect.TypeOf(&ints[0]), &ints[0], &ints[1], &ints[2]), + ct(reflect.TypeOf(unsafe.Pointer(&ints[0])), unsafe.Pointer(&ints[0]), unsafe.Pointer(&ints[1]), unsafe.Pointer(&ints[2])), ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]), ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}), ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}), @@ -119,6 +121,10 @@ var sortTests = []sortTest{ "PTR0:0 PTR1:1 PTR2:2", }, { + unsafePointerMap(), + "UNSAFEPTR0:0 UNSAFEPTR1:1 UNSAFEPTR2:2", + }, + { map[toy]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"}, "{3 4}:34 {7 1}:71 {7 2}:72", }, @@ -159,6 +165,14 @@ func sprintKey(key reflect.Value) string { } } return "PTR???" + case "unsafe.Pointer": + ptr := key.Interface().(unsafe.Pointer) + for i := range ints { + if ptr == unsafe.Pointer(&ints[i]) { + return fmt.Sprintf("UNSAFEPTR%d", i) + } + } + return "UNSAFEPTR???" case "chan int": c := key.Interface().(chan int) for i := range chans { @@ -185,6 +199,14 @@ func pointerMap() map[*int]string { return m } +func unsafePointerMap() map[unsafe.Pointer]string { + m := make(map[unsafe.Pointer]string) + for i := 2; i >= 0; i-- { + m[unsafe.Pointer(&ints[i])] = fmt.Sprint(i) + } + return m +} + func chanMap() map[chan int]string { m := make(map[chan int]string) for i := 2; i >= 0; i-- { diff --git a/src/internal/goroot/gc.go b/src/internal/goroot/gc.go index 0f541d734b..ce72bc3896 100644 --- a/src/internal/goroot/gc.go +++ b/src/internal/goroot/gc.go @@ -7,8 +7,8 @@ package goroot import ( + exec "internal/execabs" "os" - "os/exec" "path/filepath" "strings" "sync" diff --git a/src/internal/goversion/goversion.go b/src/internal/goversion/goversion.go index 513be456bd..4cc15688c0 100644 --- a/src/internal/goversion/goversion.go +++ b/src/internal/goversion/goversion.go @@ -9,4 +9,4 @@ package goversion // // It should be updated at the start of each development cycle to be // the version of the next Go 1.x release. See golang.org/issue/40705. -const Version = 16 +const Version = 17 diff --git a/src/internal/obscuretestdata/obscuretestdata.go b/src/internal/obscuretestdata/obscuretestdata.go index 06cd1df22c..5ea2cdf5d1 100644 --- a/src/internal/obscuretestdata/obscuretestdata.go +++ b/src/internal/obscuretestdata/obscuretestdata.go @@ -10,7 +10,6 @@ package obscuretestdata import ( "encoding/base64" "io" - "io/ioutil" "os" ) @@ -24,7 +23,7 @@ func DecodeToTempFile(name string) (path string, err error) { } defer f.Close() - tmp, err := ioutil.TempFile("", "obscuretestdata-decoded-") + tmp, err := os.CreateTemp("", "obscuretestdata-decoded-") if err != nil { return "", err } diff --git a/src/internal/poll/copy_file_range_linux.go b/src/internal/poll/copy_file_range_linux.go index 24bee614a6..01b242a4ea 100644 --- a/src/internal/poll/copy_file_range_linux.go +++ b/src/internal/poll/copy_file_range_linux.go @@ -10,15 +10,61 @@ import ( "syscall" ) -var copyFileRangeSupported int32 = 1 // accessed atomically +var copyFileRangeSupported int32 = -1 // accessed atomically const maxCopyFileRangeRound = 1 << 30 +func kernelVersion() (major int, minor int) { + var uname syscall.Utsname + if err := syscall.Uname(&uname); err != nil { + return + } + + rl := uname.Release + var values [2]int + vi := 0 + value := 0 + for _, c := range rl { + if '0' <= c && c <= '9' { + value = (value * 10) + int(c-'0') + } else { + // Note that we're assuming N.N.N here. If we see anything else we are likely to + // mis-parse it. + values[vi] = value + vi++ + if vi >= len(values) { + break + } + value = 0 + } + } + switch vi { + case 0: + return 0, 0 + case 1: + return values[0], 0 + case 2: + return values[0], values[1] + } + return +} + // CopyFileRange copies at most remain bytes of data from src to dst, using // the copy_file_range system call. dst and src must refer to regular files. func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err error) { - if atomic.LoadInt32(©FileRangeSupported) == 0 { + if supported := atomic.LoadInt32(©FileRangeSupported); supported == 0 { return 0, false, nil + } else if supported == -1 { + major, minor := kernelVersion() + if major > 5 || (major == 5 && minor >= 3) { + atomic.StoreInt32(©FileRangeSupported, 1) + } else { + // copy_file_range(2) is broken in various ways on kernels older than 5.3, + // see issue #42400 and + // https://man7.org/linux/man-pages/man2/copy_file_range.2.html#VERSIONS + atomic.StoreInt32(©FileRangeSupported, 0) + return 0, false, nil + } } for remain > 0 { max := remain @@ -66,7 +112,15 @@ func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err return 0, false, nil case nil: if n == 0 { - // src is at EOF, which means we are done. + // If we did not read any bytes at all, + // then this file may be in a file system + // where copy_file_range silently fails. + // https://lore.kernel.org/linux-fsdevel/20210126233840.GG4626@dread.disaster.area/T/#m05753578c7f7882f6e9ffe01f981bc223edef2b0 + if written == 0 { + return 0, false, nil + } + // Otherwise src is at EOF, which means + // we are done. return written, true, nil } remain -= n diff --git a/src/internal/poll/read_test.go b/src/internal/poll/read_test.go index 2d4ef97da0..598a52ee44 100644 --- a/src/internal/poll/read_test.go +++ b/src/internal/poll/read_test.go @@ -5,7 +5,6 @@ package poll_test import ( - "io/ioutil" "os" "runtime" "sync" @@ -22,7 +21,7 @@ func TestRead(t *testing.T) { go func(p string) { defer wg.Done() for i := 0; i < 100; i++ { - if _, err := ioutil.ReadFile(p); err != nil { + if _, err := os.ReadFile(p); err != nil { t.Error(err) return } diff --git a/src/internal/poll/sendfile_bsd.go b/src/internal/poll/sendfile_bsd.go index a24e41dcaa..66005a9f5c 100644 --- a/src/internal/poll/sendfile_bsd.go +++ b/src/internal/poll/sendfile_bsd.go @@ -18,6 +18,10 @@ func SendFile(dstFD *FD, src int, pos, remain int64) (int64, error) { return 0, err } defer dstFD.writeUnlock() + if err := dstFD.pd.prepareWrite(dstFD.isFile); err != nil { + return 0, err + } + dst := int(dstFD.Sysfd) var written int64 var err error diff --git a/src/internal/poll/sendfile_linux.go b/src/internal/poll/sendfile_linux.go index d64283007d..d6442e8666 100644 --- a/src/internal/poll/sendfile_linux.go +++ b/src/internal/poll/sendfile_linux.go @@ -16,6 +16,9 @@ func SendFile(dstFD *FD, src int, remain int64) (int64, error) { return 0, err } defer dstFD.writeUnlock() + if err := dstFD.pd.prepareWrite(dstFD.isFile); err != nil { + return 0, err + } dst := int(dstFD.Sysfd) var written int64 diff --git a/src/internal/poll/sendfile_solaris.go b/src/internal/poll/sendfile_solaris.go index 762992e9eb..748c85131e 100644 --- a/src/internal/poll/sendfile_solaris.go +++ b/src/internal/poll/sendfile_solaris.go @@ -20,6 +20,9 @@ func SendFile(dstFD *FD, src int, pos, remain int64) (int64, error) { return 0, err } defer dstFD.writeUnlock() + if err := dstFD.pd.prepareWrite(dstFD.isFile); err != nil { + return 0, err + } dst := int(dstFD.Sysfd) var written int64 diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index 1f40c11820..f8965d0bab 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -342,3 +342,5 @@ func LoadGetFinalPathNameByHandle() error { //sys CreateEnvironmentBlock(block **uint16, token syscall.Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock //sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock + +//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036 diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 170b239486..aaad4a5b94 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -52,6 +52,7 @@ var ( procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") procRevertToSelf = modadvapi32.NewProc("RevertToSelf") procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation") + procSystemFunction036 = modadvapi32.NewProc("SystemFunction036") procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") procGetACP = modkernel32.NewProc("GetACP") procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") @@ -140,6 +141,18 @@ func SetTokenInformation(tokenHandle syscall.Token, tokenInformationClass uint32 return } +func RtlGenRandom(buf []byte) (err error) { + var _p0 *byte + if len(buf) > 0 { + _p0 = &buf[0] + } + r1, _, e1 := syscall.Syscall(procSystemFunction036.Addr(), 2, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0) if r0 != 0 { diff --git a/src/internal/testenv/testenv_windows.go b/src/internal/testenv/testenv_windows.go index eb8d6ac165..4802b13951 100644 --- a/src/internal/testenv/testenv_windows.go +++ b/src/internal/testenv/testenv_windows.go @@ -5,7 +5,6 @@ package testenv import ( - "io/ioutil" "os" "path/filepath" "sync" @@ -16,7 +15,7 @@ var symlinkOnce sync.Once var winSymlinkErr error func initWinHasSymlink() { - tmpdir, err := ioutil.TempDir("", "symtest") + tmpdir, err := os.MkdirTemp("", "symtest") if err != nil { panic("failed to create temp directory: " + err.Error()) } diff --git a/src/internal/trace/gc_test.go b/src/internal/trace/gc_test.go index 4f9c77041a..9b9771e7b0 100644 --- a/src/internal/trace/gc_test.go +++ b/src/internal/trace/gc_test.go @@ -6,8 +6,8 @@ package trace import ( "bytes" - "io/ioutil" "math" + "os" "testing" "time" ) @@ -84,7 +84,7 @@ func TestMMUTrace(t *testing.T) { t.Skip("skipping in -short mode") } - data, err := ioutil.ReadFile("testdata/stress_1_10_good") + data, err := os.ReadFile("testdata/stress_1_10_good") if err != nil { t.Fatalf("failed to read input file: %v", err) } @@ -126,7 +126,7 @@ func TestMMUTrace(t *testing.T) { } func BenchmarkMMU(b *testing.B) { - data, err := ioutil.ReadFile("testdata/stress_1_10_good") + data, err := os.ReadFile("testdata/stress_1_10_good") if err != nil { b.Fatalf("failed to read input file: %v", err) } diff --git a/src/internal/trace/parser_test.go b/src/internal/trace/parser_test.go index 6d87970157..cdab95a59e 100644 --- a/src/internal/trace/parser_test.go +++ b/src/internal/trace/parser_test.go @@ -6,7 +6,6 @@ package trace import ( "bytes" - "io/ioutil" "os" "path/filepath" "strings" @@ -34,20 +33,20 @@ func TestCorruptedInputs(t *testing.T) { } func TestParseCanned(t *testing.T) { - files, err := ioutil.ReadDir("./testdata") + files, err := os.ReadDir("./testdata") if err != nil { t.Fatalf("failed to read ./testdata: %v", err) } for _, f := range files { - name := filepath.Join("./testdata", f.Name()) - info, err := os.Stat(name) + info, err := f.Info() if err != nil { t.Fatal(err) } if testing.Short() && info.Size() > 10000 { continue } - data, err := ioutil.ReadFile(name) + name := filepath.Join("./testdata", f.Name()) + data, err := os.ReadFile(name) if err != nil { t.Fatal(err) } |
