From 891682c8250e53e62d5ffe027d5ab63d21f6cd5c Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 29 Oct 2018 13:49:32 -0400 Subject: [PATCH 01/76] bufio: suggest io.ReadFull at (*Reader).Read Many times when using bufio.Reader I imagine, incorrectly, that it implements the retry loop itself, being a high-level buffered wrapper around, say, a file descriptor prone to short reads. This comment would have saved me much time. Change-Id: I34c790e0d7c1515430a76d02ce4739b586a36ba7 Reviewed-on: https://go-review.googlesource.com/c/145577 Reviewed-by: Ian Lance Taylor Reviewed-by: Rob Pike --- src/bufio/bufio.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bufio/bufio.go b/src/bufio/bufio.go index 8d162b34a0..e498dfea1e 100644 --- a/src/bufio/bufio.go +++ b/src/bufio/bufio.go @@ -187,6 +187,8 @@ func (b *Reader) Discard(n int) (discarded int, err error) { // The bytes are taken from at most one Read on the underlying Reader, // hence n may be less than len(p). // At EOF, the count will be zero and err will be io.EOF. +// +// To read exactly len(p) bytes, use io.ReadFull(b, p). func (b *Reader) Read(p []byte) (n int, err error) { n = len(p) if n == 0 { From 455ef3f6bce369bf6a91b181cbd9ef689c850c5d Mon Sep 17 00:00:00 2001 From: Ben Shi Date: Mon, 29 Oct 2018 09:01:14 +0000 Subject: [PATCH 02/76] test/codegen: improve arithmetic tests This CL fixes several typos and adds two more cases to arithmetic test. Change-Id: I086560162ea351e2166866e444e2317da36c1729 Reviewed-on: https://go-review.googlesource.com/c/145210 Run-TryBot: Ben Shi TryBot-Result: Gobot Gobot Reviewed-by: Cherry Zhang --- test/codegen/arithmetic.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/codegen/arithmetic.go b/test/codegen/arithmetic.go index ae80e31df4..2cc294897a 100644 --- a/test/codegen/arithmetic.go +++ b/test/codegen/arithmetic.go @@ -324,10 +324,10 @@ func MULA(a, b, c uint32) (uint32, uint32, uint32) { // arm:`MULA`,-`MUL\s` // arm64:`MADDW`,-`MULW` r0 := a*b + c - // arm:`MULA`-`MUL\s` + // arm:`MULA`,-`MUL\s` // arm64:`MADDW`,-`MULW` r1 := c*79 + a - // arm:`ADD`,-`MULA`-`MUL\s` + // arm:`ADD`,-`MULA`,-`MUL\s` // arm64:`ADD`,-`MADD`,-`MULW` r2 := b*64 + c return r0, r1, r2 @@ -335,12 +335,14 @@ func MULA(a, b, c uint32) (uint32, uint32, uint32) { func MULS(a, b, c uint32) (uint32, uint32, uint32) { // arm/7:`MULS`,-`MUL\s` + // arm/6:`SUB`,`MUL\s`,-`MULS` // arm64:`MSUBW`,-`MULW` r0 := c - a*b - // arm/7:`MULS`-`MUL\s` + // arm/7:`MULS`,-`MUL\s` + // arm/6:`SUB`,`MUL\s`,-`MULS` // arm64:`MSUBW`,-`MULW` r1 := a - c*79 - // arm/7:`SUB`,-`MULS`-`MUL\s` + // arm/7:`SUB`,-`MULS`,-`MUL\s` // arm64:`SUB`,-`MSUBW`,-`MULW` r2 := c - b*64 return r0, r1, r2 From 7bada2cf4676938be46cde13e2b8e11e42b5441c Mon Sep 17 00:00:00 2001 From: Pontus Leitzler Date: Tue, 30 Oct 2018 07:58:59 +0000 Subject: [PATCH 03/76] crypto/tls: clarify documentation on tls.Config.NextProtos This change will aid users to make less mistakes where you, for example, define both HTTP/1.1 and H2, but in the wrong order. package main import ( "crypto/tls" "net" ) func main() { srv := &http.Server{ TLSConfig: &tls.Config{ NextProtos: []string{"http/1.1", "h2"}, }, } srv.ListenAndServeTLS("server.crt", "server.key") } When using major browsers or curl, they will never be served H2 since they also support HTTP/1.0 and the list is processed in order. Change-Id: Id14098b5e48f624ca308137917874d475c2f22a0 GitHub-Last-Rev: f3594a6411bf7dde71c850f3e85a2b5a21974129 GitHub-Pull-Request: golang/go#28367 Reviewed-on: https://go-review.googlesource.com/c/144387 Reviewed-by: Filippo Valsorda --- src/crypto/tls/common.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 50db88eb60..ba47d565a0 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -424,7 +424,8 @@ type Config struct { // If RootCAs is nil, TLS uses the host's root CA set. RootCAs *x509.CertPool - // NextProtos is a list of supported, application level protocols. + // NextProtos is a list of supported application level protocols, in + // order of preference. NextProtos []string // ServerName is used to verify the hostname on the returned From 85143d355493c6bba994d49ed154b4df8b78874b Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Tue, 30 Oct 2018 00:40:24 +0000 Subject: [PATCH 04/76] os: add support for long path names on unix RemoveAll On unix systems, long enough path names will fail when performing syscalls like `Lstat`. The current RemoveAll uses several of these syscalls, and so will fail for long paths. This can be risky, as it can let users "hide" files from the system or otherwise make long enough paths for programs to fail. By using `Unlinkat` and `Openat` syscalls instead, RemoveAll is safer on unix systems. Initially implemented for linux, darwin, and several bsds. Fixes #27029 Co-authored-by: Giuseppe Capizzi Co-authored-by: Julia Nedialkova Change-Id: Id9fcdf4775962b021b7ff438dc51ee6d16bb5f56 GitHub-Last-Rev: b30a621fe359fa2acbb055445b54202b0c508167 GitHub-Pull-Request: golang/go#27871 Reviewed-on: https://go-review.googlesource.com/c/137442 Run-TryBot: Ian Lance Taylor Reviewed-by: Ian Lance Taylor --- src/internal/syscall/unix/at.go | 58 +++++ src/internal/syscall/unix/at_sysnum_darwin.go | 12 + .../syscall/unix/at_sysnum_dragonfly.go | 14 ++ .../syscall/unix/at_sysnum_freebsd.go | 14 ++ .../syscall/unix/at_sysnum_fstatat64_linux.go | 11 + .../syscall/unix/at_sysnum_fstatat_linux.go | 11 + src/internal/syscall/unix/at_sysnum_linux.go | 13 + src/internal/syscall/unix/at_sysnum_netbsd.go | 14 ++ .../unix/at_sysnum_newfstatat_linux.go | 11 + .../syscall/unix/at_sysnum_openbsd.go | 14 ++ src/os/path.go | 99 -------- src/os/path_test.go | 125 --------- src/os/path_unix.go | 28 ++- src/os/removeall_at.go | 139 ++++++++++ src/os/removeall_noat.go | 110 ++++++++ src/os/removeall_test.go | 237 ++++++++++++++++++ 16 files changed, 685 insertions(+), 225 deletions(-) create mode 100644 src/internal/syscall/unix/at.go create mode 100644 src/internal/syscall/unix/at_sysnum_darwin.go create mode 100644 src/internal/syscall/unix/at_sysnum_dragonfly.go create mode 100644 src/internal/syscall/unix/at_sysnum_freebsd.go create mode 100644 src/internal/syscall/unix/at_sysnum_fstatat64_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_fstatat_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_netbsd.go create mode 100644 src/internal/syscall/unix/at_sysnum_newfstatat_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_openbsd.go create mode 100644 src/os/removeall_at.go create mode 100644 src/os/removeall_noat.go create mode 100644 src/os/removeall_test.go diff --git a/src/internal/syscall/unix/at.go b/src/internal/syscall/unix/at.go new file mode 100644 index 0000000000..1c05d2abe3 --- /dev/null +++ b/src/internal/syscall/unix/at.go @@ -0,0 +1,58 @@ +// Copyright 2018 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 linux darwin freebsd openbsd netbsd dragonfly + +package unix + +import ( + "syscall" + "unsafe" +) + +func Unlinkat(dirfd int, path string, flags int) error { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall(unlinkatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags)) + if errno != 0 { + return errno + } + + return nil +} + +func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + fd, _, errno := syscall.Syscall6(openatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) + if errno != 0 { + return 0, errno + } + + return int(fd), nil +} + +func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall6(fstatatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if errno != 0 { + return errno + } + + return nil + +} diff --git a/src/internal/syscall/unix/at_sysnum_darwin.go b/src/internal/syscall/unix/at_sysnum_darwin.go new file mode 100644 index 0000000000..6aa08b4284 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_darwin.go @@ -0,0 +1,12 @@ +// Copyright 2018 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 unix + +const unlinkatTrap = uintptr(472) +const openatTrap = uintptr(463) +const fstatatTrap = uintptr(469) + +const AT_REMOVEDIR = 0x80 +const AT_SYMLINK_NOFOLLOW = 0x0020 diff --git a/src/internal/syscall/unix/at_sysnum_dragonfly.go b/src/internal/syscall/unix/at_sysnum_dragonfly.go new file mode 100644 index 0000000000..cec9abce6a --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_dragonfly.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x2 +const AT_SYMLINK_NOFOLLOW = 0x1 diff --git a/src/internal/syscall/unix/at_sysnum_freebsd.go b/src/internal/syscall/unix/at_sysnum_freebsd.go new file mode 100644 index 0000000000..fe45e296d7 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_freebsd.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x800 +const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go b/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go new file mode 100644 index 0000000000..c6ea206c12 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go @@ -0,0 +1,11 @@ +// Copyright 2018 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 arm mips mipsle 386 + +package unix + +import "syscall" + +const fstatatTrap uintptr = syscall.SYS_FSTATAT64 diff --git a/src/internal/syscall/unix/at_sysnum_fstatat_linux.go b/src/internal/syscall/unix/at_sysnum_fstatat_linux.go new file mode 100644 index 0000000000..580e7997f8 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_fstatat_linux.go @@ -0,0 +1,11 @@ +// Copyright 2018 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 unix + +import "syscall" + +const fstatatTrap uintptr = syscall.SYS_FSTATAT diff --git a/src/internal/syscall/unix/at_sysnum_linux.go b/src/internal/syscall/unix/at_sysnum_linux.go new file mode 100644 index 0000000000..fa7cd75d42 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_linux.go @@ -0,0 +1,13 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT + +const AT_REMOVEDIR = 0x200 +const AT_SYMLINK_NOFOLLOW = 0x100 diff --git a/src/internal/syscall/unix/at_sysnum_netbsd.go b/src/internal/syscall/unix/at_sysnum_netbsd.go new file mode 100644 index 0000000000..fe45e296d7 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_netbsd.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x800 +const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go b/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go new file mode 100644 index 0000000000..e76c1cbdce --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go @@ -0,0 +1,11 @@ +// Copyright 2018 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 amd64 mips64 mips64le ppc64 ppc64le s390x + +package unix + +import "syscall" + +const fstatatTrap uintptr = syscall.SYS_NEWFSTATAT diff --git a/src/internal/syscall/unix/at_sysnum_openbsd.go b/src/internal/syscall/unix/at_sysnum_openbsd.go new file mode 100644 index 0000000000..c2d48b9914 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_openbsd.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x08 +const AT_SYMLINK_NOFOLLOW = 0x02 diff --git a/src/os/path.go b/src/os/path.go index cdfbc18921..e31f64c750 100644 --- a/src/os/path.go +++ b/src/os/path.go @@ -5,7 +5,6 @@ package os import ( - "io" "syscall" ) @@ -58,101 +57,3 @@ func MkdirAll(path string, perm FileMode) error { } return nil } - -// RemoveAll removes path and any children it contains. -// It removes everything it can but returns the first error -// it encounters. If the path does not exist, RemoveAll -// returns nil (no error). -func RemoveAll(path string) error { - // Simple case: if Remove works, we're done. - err := Remove(path) - if err == nil || IsNotExist(err) { - return nil - } - - // Otherwise, is this a directory we need to recurse into? - dir, serr := Lstat(path) - if serr != nil { - if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { - return nil - } - return serr - } - if !dir.IsDir() { - // Not a directory; return the error from Remove. - return err - } - - // Remove contents & return first error. - err = nil - for { - fd, err := Open(path) - if err != nil { - if IsNotExist(err) { - // Already deleted by someone else. - return nil - } - return err - } - - const request = 1024 - names, err1 := fd.Readdirnames(request) - - // Removing files from the directory may have caused - // the OS to reshuffle it. Simply calling Readdirnames - // again may skip some entries. The only reliable way - // to avoid this is to close and re-open the - // directory. See issue 20841. - fd.Close() - - for _, name := range names { - err1 := RemoveAll(path + string(PathSeparator) + name) - if err == nil { - err = err1 - } - } - - if err1 == io.EOF { - break - } - // If Readdirnames returned an error, use it. - if err == nil { - err = err1 - } - if len(names) == 0 { - break - } - - // We don't want to re-open unnecessarily, so if we - // got fewer than request names from Readdirnames, try - // simply removing the directory now. If that - // succeeds, we are done. - if len(names) < request { - err1 := Remove(path) - if err1 == nil || IsNotExist(err1) { - return nil - } - - if err != nil { - // We got some error removing the - // directory contents, and since we - // read fewer names than we requested - // there probably aren't more files to - // remove. Don't loop around to read - // the directory again. We'll probably - // just get the same error. - return err - } - } - } - - // Remove directory. - err1 := Remove(path) - if err1 == nil || IsNotExist(err1) { - return nil - } - if err == nil { - err = err1 - } - return err -} diff --git a/src/os/path_test.go b/src/os/path_test.go index f58c7e746d..6cb25bcaa7 100644 --- a/src/os/path_test.go +++ b/src/os/path_test.go @@ -5,7 +5,6 @@ package os_test import ( - "fmt" "internal/testenv" "io/ioutil" . "os" @@ -76,130 +75,6 @@ func TestMkdirAll(t *testing.T) { } } -func TestRemoveAll(t *testing.T) { - tmpDir := TempDir() - // Work directory. - path := tmpDir + "/_TestRemoveAll_" - fpath := path + "/file" - dpath := path + "/dir" - - // Make directory with 1 file and remove. - if err := MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", path, err) - } - fd, err := Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q (first): %s", path, err) - } - if _, err = Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path) - } - - // Make directory with file and subdirectory and remove. - if err = MkdirAll(dpath, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", dpath, err) - } - fd, err = Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - fd, err = Create(dpath + "/file") - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q (second): %s", path, err) - } - if _, err := Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) - } - - // Determine if we should run the following test. - testit := true - if runtime.GOOS == "windows" { - // Chmod is not supported under windows. - testit = false - } else { - // Test fails as root. - testit = Getuid() != 0 - } - if testit { - // Make directory with file and subdirectory and trigger error. - if err = MkdirAll(dpath, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", dpath, err) - } - - for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { - fd, err = Create(s) - if err != nil { - t.Fatalf("create %q: %s", s, err) - } - fd.Close() - } - if err = Chmod(dpath, 0); err != nil { - t.Fatalf("Chmod %q 0: %s", dpath, err) - } - - // No error checking here: either RemoveAll - // will or won't be able to remove dpath; - // either way we want to see if it removes fpath - // and path/zzz. Reasons why RemoveAll might - // succeed in removing dpath as well include: - // * running as root - // * running on a file system without permissions (FAT) - RemoveAll(path) - Chmod(dpath, 0777) - - for _, s := range []string{fpath, path + "/zzz"} { - if _, err = Lstat(s); err == nil { - t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) - } - } - } - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) - } - if _, err = Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) - } -} - -// Test RemoveAll on a large directory. -func TestRemoveAllLarge(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - - tmpDir := TempDir() - // Work directory. - path := tmpDir + "/_TestRemoveAllLarge_" - - // Make directory with 1000 files and remove. - if err := MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", path, err) - } - for i := 0; i < 1000; i++ { - fpath := fmt.Sprintf("%s/file%d", path, i) - fd, err := Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - } - if err := RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q: %s", path, err) - } - if _, err := Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll", path) - } -} - func TestMkdirAllWithSymlink(t *testing.T) { testenv.MustHaveSymlink(t) diff --git a/src/os/path_unix.go b/src/os/path_unix.go index 3cb0e3acc4..be373a50a9 100644 --- a/src/os/path_unix.go +++ b/src/os/path_unix.go @@ -16,7 +16,7 @@ func IsPathSeparator(c uint8) bool { return PathSeparator == c } -// basename removes trailing slashes and the leading directory name from path name +// basename removes trailing slashes and the leading directory name from path name. func basename(name string) string { i := len(name) - 1 // Remove trailing slashes @@ -34,6 +34,32 @@ func basename(name string) string { return name } +// splitPath returns the base name and parent directory. +func splitPath(path string) (string, string) { + // if no better parent is found, the path is relative from "here" + dirname := "." + // if no slashes in path, base is path + basename := path + + i := len(path) - 1 + + // Remove trailing slashes + for ; i > 0 && path[i] == '/'; i-- { + path = path[:i] + } + + // Remove leading directory path + for i--; i >= 0; i-- { + if path[i] == '/' { + dirname = path[:i+1] + basename = path[i+1:] + break + } + } + + return dirname, basename +} + func fixRootDirectory(p string) string { return p } diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go new file mode 100644 index 0000000000..ec69d40f29 --- /dev/null +++ b/src/os/removeall_at.go @@ -0,0 +1,139 @@ +// Copyright 2018 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 linux darwin freebsd openbsd netbsd dragonfly + +package os + +import ( + "internal/syscall/unix" + "io" + "syscall" +) + +func RemoveAll(path string) error { + // Not allowed in unix + if path == "" || endsWithDot(path) { + return syscall.EINVAL + } + + // RemoveAll recurses by deleting the path base from + // its parent directory + parentDir, base := splitPath(path) + + parent, err := Open(parentDir) + if IsNotExist(err) { + // If parent does not exist, base cannot exist. Fail silently + return nil + } + if err != nil { + return err + } + defer parent.Close() + + return removeAllFrom(parent, base) +} + +func removeAllFrom(parent *File, path string) error { + parentFd := int(parent.Fd()) + // Simple case: if Unlink (aka remove) works, we're done. + err := unix.Unlinkat(parentFd, path, 0) + if err == nil || IsNotExist(err) { + return nil + } + + // If not a "is directory" error, we have a problem + if err != syscall.EISDIR && err != syscall.EPERM { + return err + } + + // Is this a directory we need to recurse into? + var statInfo syscall.Stat_t + statErr := unix.Fstatat(parentFd, path, &statInfo, unix.AT_SYMLINK_NOFOLLOW) + if statErr != nil { + return statErr + } + if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { + // Not a directory; return the error from the Remove + return err + } + + // Remove the directory's entries + var recurseErr error + for { + const request = 1024 + + // Open the directory to recurse into + file, err := openFdAt(parentFd, path) + if err != nil { + if IsNotExist(err) { + return nil + } + return err + } + + names, readErr := file.Readdirnames(request) + // Errors other than EOF should stop us from continuing + if readErr != nil && readErr != io.EOF { + file.Close() + if IsNotExist(readErr) { + return nil + } + return readErr + } + + for _, name := range names { + err := removeAllFrom(file, name) + if err != nil { + recurseErr = err + } + } + + // Removing files from the directory may have caused + // the OS to reshuffle it. Simply calling Readdirnames + // again may skip some entries. The only reliable way + // to avoid this is to close and re-open the + // directory. See issue 20841. + file.Close() + + // Finish when the end of the directory is reached + if len(names) < request { + break + } + } + + // Remove the directory itself + unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR) + if unlinkError == nil || IsNotExist(unlinkError) { + return nil + } + + if recurseErr != nil { + return recurseErr + } + return unlinkError +} + +func openFdAt(fd int, path string) (*File, error) { + fd, err := unix.Openat(fd, path, O_RDONLY, 0) + if err != nil { + return nil, err + } + + return NewFile(uintptr(fd), path), nil +} + +func endsWithDot(path string) bool { + if path == "." || path == ".." { + return true + } + if len(path) >= 2 && path[len(path)-2:] == "/." { + return true + } + if len(path) >= 3 && path[len(path)-3:] == "/.." { + return true + } + + return false +} diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go new file mode 100644 index 0000000000..7cfc33c025 --- /dev/null +++ b/src/os/removeall_noat.go @@ -0,0 +1,110 @@ +// Copyright 2018 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 !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly + +package os + +import ( + "io" + "syscall" +) + +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +func RemoveAll(path string) error { + // Simple case: if Remove works, we're done. + err := Remove(path) + if err == nil || IsNotExist(err) { + return nil + } + + // Otherwise, is this a directory we need to recurse into? + dir, serr := Lstat(path) + if serr != nil { + if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { + return nil + } + return serr + } + if !dir.IsDir() { + // Not a directory; return the error from Remove. + return err + } + + // Remove contents & return first error. + err = nil + for { + fd, err := Open(path) + if err != nil { + if IsNotExist(err) { + // Already deleted by someone else. + return nil + } + return err + } + + const request = 1024 + names, err1 := fd.Readdirnames(request) + + // Removing files from the directory may have caused + // the OS to reshuffle it. Simply calling Readdirnames + // again may skip some entries. The only reliable way + // to avoid this is to close and re-open the + // directory. See issue 20841. + fd.Close() + + for _, name := range names { + err1 := RemoveAll(path + string(PathSeparator) + name) + if err == nil { + err = err1 + } + } + + if err1 == io.EOF { + break + } + // If Readdirnames returned an error, use it. + if err == nil { + err = err1 + } + if len(names) == 0 { + break + } + + // We don't want to re-open unnecessarily, so if we + // got fewer than request names from Readdirnames, try + // simply removing the directory now. If that + // succeeds, we are done. + if len(names) < request { + err1 := Remove(path) + if err1 == nil || IsNotExist(err1) { + return nil + } + + if err != nil { + // We got some error removing the + // directory contents, and since we + // read fewer names than we requested + // there probably aren't more files to + // remove. Don't loop around to read + // the directory again. We'll probably + // just get the same error. + return err + } + } + } + + // Remove directory. + err1 := Remove(path) + if err1 == nil || IsNotExist(err1) { + return nil + } + if err == nil { + err = err1 + } + return err +} diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go new file mode 100644 index 0000000000..0da5d772d1 --- /dev/null +++ b/src/os/removeall_test.go @@ -0,0 +1,237 @@ +// Copyright 2018 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 os_test + +import ( + "fmt" + "io/ioutil" + . "os" + "runtime" + "strings" + "testing" +) + +func TestRemoveAll(t *testing.T) { + tmpDir := TempDir() + // Work directory. + file := "file" + path := tmpDir + "/_TestRemoveAll_" + fpath := path + "/file" + dpath := path + "/dir" + + // Make a regular file and remove + fd, err := Create(file) + if err != nil { + t.Fatalf("create %q: %s", file, err) + } + fd.Close() + if err = RemoveAll(file); err != nil { + t.Fatalf("RemoveAll %q (first): %s", file, err) + } + if _, err = Lstat(file); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file) + } + + // Make directory with 1 file and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + fd, err = Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q (second): %s", path, err) + } + if _, err = Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) + } + + // Make directory with file and subdirectory and remove. + if err = MkdirAll(dpath, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", dpath, err) + } + fd, err = Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + fd, err = Create(dpath + "/file") + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q (third): %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path) + } + + // Determine if we should run the following test. + testit := true + if runtime.GOOS == "windows" { + // Chmod is not supported under windows. + testit = false + } else { + // Test fails as root. + testit = Getuid() != 0 + } + if testit { + // Make directory with file and subdirectory and trigger error. + if err = MkdirAll(dpath, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", dpath, err) + } + + for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { + fd, err = Create(s) + if err != nil { + t.Fatalf("create %q: %s", s, err) + } + fd.Close() + } + if err = Chmod(dpath, 0); err != nil { + t.Fatalf("Chmod %q 0: %s", dpath, err) + } + + // No error checking here: either RemoveAll + // will or won't be able to remove dpath; + // either way we want to see if it removes fpath + // and path/zzz. Reasons why RemoveAll might + // succeed in removing dpath as well include: + // * running as root + // * running on a file system without permissions (FAT) + RemoveAll(path) + Chmod(dpath, 0777) + + for _, s := range []string{fpath, path + "/zzz"} { + if _, err = Lstat(s); err == nil { + t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) + } + } + } + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) + } + if _, err = Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) + } +} + +// Test RemoveAll on a large directory. +func TestRemoveAllLarge(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + tmpDir := TempDir() + // Work directory. + path := tmpDir + "/_TestRemoveAllLarge_" + + // Make directory with 1000 files and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + for i := 0; i < 1000; i++ { + fpath := fmt.Sprintf("%s/file%d", path, i) + fd, err := Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + } + if err := RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q: %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll", path) + } +} + +func TestRemoveAllLongPath(t *testing.T) { + switch runtime.GOOS { + case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly": + break + default: + t.Skip("skipping for not implemented platforms") + } + + prevDir, err := Getwd() + if err != nil { + t.Fatalf("Could not get wd: %s", err) + } + + startPath, err := ioutil.TempDir("", "TestRemoveAllLongPath-") + if err != nil { + t.Fatalf("Could not create TempDir: %s", err) + } + err = Chdir(startPath) + if err != nil { + t.Fatalf("Could not chdir %s: %s", startPath, err) + } + + // Removing paths with over 4096 chars commonly fails + for i := 0; i < 41; i++ { + name := strings.Repeat("a", 100) + + err = Mkdir(name, 0755) + if err != nil { + t.Fatalf("Could not mkdir %s: %s", name, err) + } + + err = Chdir(name) + if err != nil { + t.Fatalf("Could not chdir %s: %s", name, err) + } + } + + err = Chdir(prevDir) + if err != nil { + t.Fatalf("Could not chdir %s: %s", prevDir, err) + } + + err = RemoveAll(startPath) + if err != nil { + t.Errorf("RemoveAll could not remove long file path %s: %s", startPath, err) + } +} + +func TestRemoveAllDot(t *testing.T) { + switch runtime.GOOS { + case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly": + break + default: + t.Skip("skipping for not implemented platforms") + } + + prevDir, err := Getwd() + if err != nil { + t.Fatalf("Could not get wd: %s", err) + } + tempDir, err := ioutil.TempDir("", "TestRemoveAllDot-") + if err != nil { + t.Fatalf("Could not create TempDir: %s", err) + } + err = Chdir(tempDir) + if err != nil { + t.Fatalf("Could not chdir to tempdir: %s", err) + } + + err = RemoveAll(".") + if err == nil { + t.Errorf("RemoveAll succeed to remove .") + } + + err = RemoveAll("..") + if err == nil { + t.Errorf("RemoveAll succeed to remove ..") + } + + err = Chdir(prevDir) + if err != nil { + t.Fatalf("Could not chdir %s: %s", prevDir, err) + } +} From 5dd4d1f820a8e6a6407fac08cb41f1dec6a9f079 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Thu, 25 Oct 2018 18:11:54 +0000 Subject: [PATCH 05/76] runtime: add predecessor method to treap This change adds a method for computing a treap node's predecessor to the treap, which will simplify the implementation of algorithms used for heap growth scavenging. For #14045. Change-Id: Id203e4bd246db3504f2f0c5163ec36f4579167df Reviewed-on: https://go-review.googlesource.com/c/144717 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/mgclarge.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/runtime/mgclarge.go b/src/runtime/mgclarge.go index 11a977d6ba..7bc56259ae 100644 --- a/src/runtime/mgclarge.go +++ b/src/runtime/mgclarge.go @@ -46,6 +46,38 @@ type treapNode struct { priority uint32 // random number used by treap algorithm to keep tree probabilistically balanced } +func (t *treapNode) pred() *treapNode { + if t.left != nil { + // If it has a left child, its predecessor will be + // its right most left (grand)child. + t = t.left + for t.right != nil { + t = t.right + } + return t + } + // If it has no left child, its predecessor will be + // the first grandparent who's right child is its + // ancestor. + // + // We compute this by walking up the treap until the + // current node's parent is its parent's right child. + // + // If we find at any point walking up the treap + // that the current node doesn't have a parent, + // we've hit the root. This means that t is already + // the left-most node in the treap and therefore + // has no predecessor. + for t.parent != nil && t.parent.right != t { + if t.parent.left != t { + println("runtime: predecessor t=", t, "t.spanKey=", t.spanKey) + throw("node is not its parent's child") + } + t = t.parent + } + return t.parent +} + // isSpanInTreap is handy for debugging. One should hold the heap lock, usually // mheap_.lock(). func (t *treapNode) isSpanInTreap(s *mspan) bool { From 239341f3b6b5c921c2352ce8267d74948476d8fa Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Thu, 25 Oct 2018 18:18:53 +0000 Subject: [PATCH 06/76] runtime: add successor method to treap This change adds a method for computing a treap node's successor to the treap, which will simplify the implementation of algorithms used for heap growth scavenging. For #14045. Change-Id: If2af3f2707dbcbef5fb6e42cb2712061f9da5129 Reviewed-on: https://go-review.googlesource.com/c/144718 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/mgclarge.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/runtime/mgclarge.go b/src/runtime/mgclarge.go index 7bc56259ae..ec4f7ead71 100644 --- a/src/runtime/mgclarge.go +++ b/src/runtime/mgclarge.go @@ -78,6 +78,27 @@ func (t *treapNode) pred() *treapNode { return t.parent } +func (t *treapNode) succ() *treapNode { + if t.right != nil { + // If it has a right child, its successor will be + // its left-most right (grand)child. + t = t.right + for t.left != nil { + t = t.left + } + return t + } + // See pred. + for t.parent != nil && t.parent.left != t { + if t.parent.right != t { + println("runtime: predecessor t=", t, "t.spanKey=", t.spanKey) + throw("node is not its parent's child") + } + t = t.parent + } + return t.parent +} + // isSpanInTreap is handy for debugging. One should hold the heap lock, usually // mheap_.lock(). func (t *treapNode) isSpanInTreap(s *mspan) bool { From b46bf0240c0663222f837c78644fe90da932d852 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Tue, 2 Oct 2018 21:39:20 +0000 Subject: [PATCH 07/76] runtime: separate scavenged spans This change adds a new treap to mheap which contains scavenged (i.e. its physical pages were returned to the OS) spans. As of this change, spans may no longer be partially scavenged. For #14045. Change-Id: I0d428a255c6d3f710b9214b378f841b997df0993 Reviewed-on: https://go-review.googlesource.com/c/139298 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/mgclarge.go | 12 ---- src/runtime/mheap.go | 133 +++++++++++++++++++++++++++++++--------- src/runtime/mstats.go | 14 ----- 3 files changed, 104 insertions(+), 55 deletions(-) diff --git a/src/runtime/mgclarge.go b/src/runtime/mgclarge.go index ec4f7ead71..ab665615be 100644 --- a/src/runtime/mgclarge.go +++ b/src/runtime/mgclarge.go @@ -211,7 +211,6 @@ func (root *mTreap) removeNode(t *treapNode) { if t.spanKey.npages != t.npagesKey { throw("span and treap node npages do not match") } - // Rotate t down to be leaf of tree for removal, respecting priorities. for t.right != nil || t.left != nil { if t.right == nil || t.left != nil && t.left.priority < t.right.priority { @@ -281,17 +280,6 @@ func (root *mTreap) removeSpan(span *mspan) { root.removeNode(t) } -// scavengetreap visits each node in the treap and scavenges the -// treapNode's span. -func scavengetreap(treap *treapNode, now, limit uint64) uintptr { - if treap == nil { - return 0 - } - return scavengeTreapNode(treap, now, limit) + - scavengetreap(treap.left, now, limit) + - scavengetreap(treap.right, now, limit) -} - // rotateLeft rotates the tree rooted at node x. // turning (x a (y b c)) into (y (x a b) c). func (root *mTreap) rotateLeft(x *treapNode) { diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index 33a190a4c5..320d84b980 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -30,7 +30,8 @@ const minPhysPageSize = 4096 //go:notinheap type mheap struct { lock mutex - free mTreap // free treap of spans + free mTreap // free and non-scavenged spans + scav mTreap // free and scavenged spans busy mSpanList // busy list of spans sweepgen uint32 // sweep generation, see comment in mspan sweepdone uint32 // all spans are swept @@ -60,7 +61,7 @@ type mheap struct { // on the swept stack. sweepSpans [2]gcSweepBuf - //_ uint32 // align uint64 fields on 32-bit for atomics + _ uint32 // align uint64 fields on 32-bit for atomics // Proportional sweep // @@ -132,7 +133,7 @@ type mheap struct { // (the actual arenas). This is only used on 32-bit. arena linearAlloc - //_ uint32 // ensure 64-bit alignment of central + // _ uint32 // ensure 64-bit alignment of central // central free lists for small size classes. // the padding makes sure that the MCentrals are @@ -840,18 +841,31 @@ func (h *mheap) setSpans(base, npage uintptr, s *mspan) { func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan { var s *mspan - // Best fit in the treap of spans. + // First, attempt to allocate from free spans, then from + // scavenged spans, looking for best fit in each. s = h.free.remove(npage) - if s == nil { - if !h.grow(npage) { - return nil - } - s = h.free.remove(npage) - if s == nil { - return nil - } + if s != nil { + goto HaveSpan } + s = h.scav.remove(npage) + if s != nil { + goto HaveSpan + } + // On failure, grow the heap and try again. + if !h.grow(npage) { + return nil + } + s = h.free.remove(npage) + if s != nil { + goto HaveSpan + } + s = h.scav.remove(npage) + if s != nil { + goto HaveSpan + } + return nil +HaveSpan: // Mark span in use. if s.state != mSpanFree { throw("MHeap_AllocLocked - MSpan not free") @@ -1002,19 +1016,29 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i if unusedsince == 0 { s.unusedsince = nanotime() } - s.npreleased = 0 + + // We scavenge s at the end after coalescing if s or anything + // it merged with is marked scavenged. + needsScavenge := s.npreleased != 0 + prescavenged := s.npreleased * pageSize // number of bytes already scavenged. // Coalesce with earlier, later spans. if before := spanOf(s.base() - 1); before != nil && before.state == mSpanFree { // Now adjust s. s.startAddr = before.startAddr s.npages += before.npages - s.npreleased = before.npreleased // absorb released pages s.needzero |= before.needzero h.setSpan(before.base(), s) + s.npreleased += before.npreleased // absorb released pages // The size is potentially changing so the treap needs to delete adjacent nodes and // insert back as a combined node. - h.free.removeSpan(before) + if before.npreleased == 0 { + h.free.removeSpan(before) + } else { + h.scav.removeSpan(before) + needsScavenge = true + prescavenged += before.npreleased * pageSize + } before.state = mSpanDead h.spanalloc.free(unsafe.Pointer(before)) } @@ -1022,26 +1046,77 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i // Now check to see if next (greater addresses) span is free and can be coalesced. if after := spanOf(s.base() + s.npages*pageSize); after != nil && after.state == mSpanFree { s.npages += after.npages - s.npreleased += after.npreleased s.needzero |= after.needzero h.setSpan(s.base()+s.npages*pageSize-1, s) - h.free.removeSpan(after) + if after.npreleased == 0 { + h.free.removeSpan(after) + } else { + h.scav.removeSpan(after) + needsScavenge = true + prescavenged += after.npreleased * pageSize + } + s.npreleased += after.npreleased after.state = mSpanDead h.spanalloc.free(unsafe.Pointer(after)) } - // Insert s into the free treap. - h.free.insert(s) + if needsScavenge { + // When coalescing spans, some physical pages which + // were not returned to the OS previously because + // they were only partially covered by the span suddenly + // become available for scavenging. We want to make sure + // those holes are filled in, and the span is properly + // scavenged. Rather than trying to detect those holes + // directly, we collect how many bytes were already + // scavenged above and subtract that from heap_released + // before re-scavenging the entire newly-coalesced span, + // which will implicitly bump up heap_released. + memstats.heap_released -= uint64(prescavenged) + s.scavenge() + } + + // Insert s into the appropriate treap. + if s.npreleased != 0 { + h.scav.insert(s) + } else { + h.free.insert(s) + } } -func scavengeTreapNode(t *treapNode, now, limit uint64) uintptr { - s := t.spanKey - if (now-uint64(s.unusedsince)) > limit && s.npreleased != s.npages { - if released := s.scavenge(); released != 0 { - return released - } +// scavengeAll visits each node in the unscav treap and scavenges the +// treapNode's span. It then removes the scavenged span from +// unscav and adds it into scav before continuing. h must be locked. +func (h *mheap) scavengeAll(now, limit uint64) uintptr { + // Compute the left-most child in unscav to start iteration from. + t := h.free.treap + if t == nil { + return 0 } - return 0 + for t.left != nil { + t = t.left + } + // Iterate over the treap be computing t's successor before + // potentially scavenging it. + released := uintptr(0) + for t != nil { + s := t.spanKey + next := t.succ() + if (now-uint64(s.unusedsince)) > limit { + r := s.scavenge() + if r != 0 { + // If we ended up scavenging s, then remove it from unscav + // and add it to scav. This is safe to do since we've already + // moved to t's successor. + h.free.removeNode(t) + h.scav.insert(s) + released += r + } + } + // Move t forward to its successor to iterate over the whole + // treap. + t = next + } + return released } func (h *mheap) scavenge(k int32, now, limit uint64) { @@ -1051,13 +1126,13 @@ func (h *mheap) scavenge(k int32, now, limit uint64) { gp := getg() gp.m.mallocing++ lock(&h.lock) - sumreleased := scavengetreap(h.free.treap, now, limit) + released := h.scavengeAll(now, limit) unlock(&h.lock) gp.m.mallocing-- if debug.gctrace > 0 { - if sumreleased > 0 { - print("scvg", k, ": ", sumreleased>>20, " MB released\n") + if released > 0 { + print("scvg", k, ": ", released>>20, " MB released\n") } print("scvg", k, ": inuse: ", memstats.heap_inuse>>20, ", idle: ", memstats.heap_idle>>20, ", sys: ", memstats.heap_sys>>20, ", released: ", memstats.heap_released>>20, ", consumed: ", (memstats.heap_sys-memstats.heap_released)>>20, " (MB)\n") } diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index 1bd6566052..fd576b7ae0 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -42,20 +42,6 @@ type mstats struct { heap_released uint64 // bytes released to the os heap_objects uint64 // total number of allocated objects - // TODO(austin): heap_released is both useless and inaccurate - // in its current form. It's useless because, from the user's - // and OS's perspectives, there's no difference between a page - // that has not yet been faulted in and a page that has been - // released back to the OS. We could fix this by considering - // newly mapped spans to be "released". It's inaccurate - // because when we split a large span for allocation, we - // "unrelease" all pages in the large span and not just the - // ones we split off for use. This is trickier to fix because - // we currently don't know which pages of a span we've - // released. We could fix it by separating "free" and - // "released" spans, but then we have to allocate from runs of - // free and released spans. - // Statistics about allocation of low-level fixed-size structures. // Protected by FixAlloc locks. stacks_inuse uint64 // bytes in manually-managed stack spans From 78bb91cbd34a8f2f3784ba5df94c0e56c44fc751 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Thu, 4 Oct 2018 15:59:47 +0000 Subject: [PATCH 08/76] runtime: remove npreleased in favor of boolean This change removes npreleased from mspan since spans may now either be scavenged or not scavenged; how many of its pages were actually scavenged doesn't matter. It saves some space in mpsan overhead too, as the boolean fits into what would otherwise be struct padding. For #14045. Change-Id: I63f25a4d98658f5fe21c6a466fc38c59bfc5d0f5 Reviewed-on: https://go-review.googlesource.com/c/139737 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/mheap.go | 77 +++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index 320d84b980..fbf517edfa 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -329,9 +329,9 @@ type mspan struct { needzero uint8 // needs to be zeroed before allocation divShift uint8 // for divide by elemsize - divMagic.shift divShift2 uint8 // for divide by elemsize - divMagic.shift2 + scavenged bool // whether this span has had its pages released to the OS elemsize uintptr // computed from sizeclass or from npages unusedsince int64 // first time spotted by gc in mspanfree state - npreleased uintptr // number of pages released to the os limit uintptr // end of data in span speciallock mutex // guards specials list specials *special // linked list of special records sorted by offset. @@ -350,34 +350,45 @@ func (s *mspan) layout() (size, n, total uintptr) { return } -func (s *mspan) scavenge() uintptr { +// physPageBounds returns the start and end of the span +// rounded in to the physical page size. +func (s *mspan) physPageBounds() (uintptr, uintptr) { start := s.base() end := start + s.npages<<_PageShift if physPageSize > _PageSize { - // We can only release pages in - // physPageSize blocks, so round start - // and end in. (Otherwise, madvise - // will round them *out* and release - // more memory than we want.) + // Round start and end in. start = (start + physPageSize - 1) &^ (physPageSize - 1) end &^= physPageSize - 1 - if end <= start { - // start and end don't span a - // whole physical page. - return 0 - } } - len := end - start - released := len - (s.npreleased << _PageShift) - if physPageSize > _PageSize && released == 0 { + return start, end +} + +func (s *mspan) scavenge() uintptr { + // start and end must be rounded in, otherwise madvise + // will round them *out* and release more memory + // than we want. + start, end := s.physPageBounds() + if end <= start { + // start and end don't span a whole physical page. return 0 } + released := end - start memstats.heap_released += uint64(released) - s.npreleased = len >> _PageShift - sysUnused(unsafe.Pointer(start), len) + s.scavenged = true + sysUnused(unsafe.Pointer(start), released) return released } +// released returns the number of bytes in this span +// which were returned back to the OS. +func (s *mspan) released() uintptr { + if !s.scavenged { + return 0 + } + start, end := s.physPageBounds() + return end - start +} + // recordspan adds a newly allocated span to h.allspans. // // This only happens the first time a span is allocated from @@ -873,10 +884,18 @@ HaveSpan: if s.npages < npage { throw("MHeap_AllocLocked - bad npages") } - if s.npreleased > 0 { + if s.scavenged { + // sysUsed all the pages that are actually available + // in the span, but only drop heap_released by the + // actual amount of pages released. This helps ensure + // that heap_released only increments and decrements + // by the same amounts. It's also fine, because any + // of the pages outside start and end wouldn't have been + // sysUnused in the first place. sysUsed(unsafe.Pointer(s.base()), s.npages<<_PageShift) - memstats.heap_released -= uint64(s.npreleased << _PageShift) - s.npreleased = 0 + start, end := s.physPageBounds() + memstats.heap_released -= uint64(end-start) + s.scavenged = false } if s.npages > npage { @@ -1019,8 +1038,8 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i // We scavenge s at the end after coalescing if s or anything // it merged with is marked scavenged. - needsScavenge := s.npreleased != 0 - prescavenged := s.npreleased * pageSize // number of bytes already scavenged. + needsScavenge := s.scavenged + prescavenged := s.released() // number of bytes already scavenged. // Coalesce with earlier, later spans. if before := spanOf(s.base() - 1); before != nil && before.state == mSpanFree { @@ -1029,15 +1048,14 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i s.npages += before.npages s.needzero |= before.needzero h.setSpan(before.base(), s) - s.npreleased += before.npreleased // absorb released pages // The size is potentially changing so the treap needs to delete adjacent nodes and // insert back as a combined node. - if before.npreleased == 0 { + if !before.scavenged { h.free.removeSpan(before) } else { h.scav.removeSpan(before) needsScavenge = true - prescavenged += before.npreleased * pageSize + prescavenged += before.released() } before.state = mSpanDead h.spanalloc.free(unsafe.Pointer(before)) @@ -1048,14 +1066,13 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i s.npages += after.npages s.needzero |= after.needzero h.setSpan(s.base()+s.npages*pageSize-1, s) - if after.npreleased == 0 { + if !after.scavenged { h.free.removeSpan(after) } else { h.scav.removeSpan(after) needsScavenge = true - prescavenged += after.npreleased * pageSize + prescavenged += after.released() } - s.npreleased += after.npreleased after.state = mSpanDead h.spanalloc.free(unsafe.Pointer(after)) } @@ -1076,7 +1093,7 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i } // Insert s into the appropriate treap. - if s.npreleased != 0 { + if s.scavenged { h.scav.insert(s) } else { h.free.insert(s) @@ -1157,7 +1174,7 @@ func (span *mspan) init(base uintptr, npages uintptr) { span.elemsize = 0 span.state = mSpanDead span.unusedsince = 0 - span.npreleased = 0 + span.scavenged = false span.speciallock.key = 0 span.specials = nil span.needzero = 0 From 61d40c8abc82fd845b5e4340bdf0495de0de7ecd Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Mon, 15 Oct 2018 23:00:58 +0000 Subject: [PATCH 09/76] runtime: extend ReadMemStatsSlow to re-compute HeapReleased This change extends the test function ReadMemStatsSlow to re-compute the HeapReleased statistic such that it is checked in testing to be consistent with the bookkeeping done in the runtime. Change-Id: I49f5c2620f5731edea8e9f768744cf997dcd7c22 Reviewed-on: https://go-review.googlesource.com/c/142397 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/export_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 89f887b765..56dd95e469 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -297,6 +297,7 @@ func ReadMemStatsSlow() (base, slow MemStats) { slow.TotalAlloc = 0 slow.Mallocs = 0 slow.Frees = 0 + slow.HeapReleased = 0 var bySize [_NumSizeClasses]struct { Mallocs, Frees uint64 } @@ -336,6 +337,10 @@ func ReadMemStatsSlow() (base, slow MemStats) { slow.BySize[i].Frees = bySize[i].Frees } + mheap_.scav.treap.walkTreap(func(tn *treapNode) { + slow.HeapReleased += uint64(tn.spanKey.released()) + }) + getg().m.mallocing-- }) From db82a1bc12c54f1b6d32a3d41c4422605b16b7e8 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Fri, 5 Oct 2018 18:11:02 +0000 Subject: [PATCH 10/76] runtime: sysUsed spans after trimming Currently, we mark a whole span as sysUsed before trimming, but this unnecessarily tells the OS that the trimmed section from the span is used when it may have been scavenged, if s was scavenged. Overall, this just makes invocations of sysUsed a little more fine-grained. It does come with the caveat that now heap_released needs to be managed a little more carefully in allocSpanLocked. In this case, we choose to (like before this change) negate any effect the span has on heap_released before trimming, then add it back if the trimmed part is scavengable. For #14045. Change-Id: Ifa384d989611398bfad3ca39d3bb595a5962a3ea Reviewed-on: https://go-review.googlesource.com/c/140198 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/mheap.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index fbf517edfa..ddbc872080 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -884,19 +884,11 @@ HaveSpan: if s.npages < npage { throw("MHeap_AllocLocked - bad npages") } - if s.scavenged { - // sysUsed all the pages that are actually available - // in the span, but only drop heap_released by the - // actual amount of pages released. This helps ensure - // that heap_released only increments and decrements - // by the same amounts. It's also fine, because any - // of the pages outside start and end wouldn't have been - // sysUnused in the first place. - sysUsed(unsafe.Pointer(s.base()), s.npages<<_PageShift) - start, end := s.physPageBounds() - memstats.heap_released -= uint64(end-start) - s.scavenged = false - } + + // First, subtract any memory that was released back to + // the OS from s. We will re-scavenge the trimmed section + // if necessary. + memstats.heap_released -= uint64(s.released()) if s.npages > npage { // Trim extra and put it back in the heap. @@ -907,11 +899,26 @@ HaveSpan: h.setSpan(t.base(), t) h.setSpan(t.base()+t.npages*pageSize-1, t) t.needzero = s.needzero + // If s was scavenged, then t may be scavenged. + start, end := t.physPageBounds() + if s.scavenged && start < end { + memstats.heap_released += uint64(end-start) + t.scavenged = true + } s.state = mSpanManual // prevent coalescing with s t.state = mSpanManual h.freeSpanLocked(t, false, false, s.unusedsince) s.state = mSpanFree } + // "Unscavenge" s only AFTER splitting so that + // we only sysUsed whatever we actually need. + if s.scavenged { + // sysUsed all the pages that are actually available + // in the span. Note that we don't need to decrement + // heap_released since we already did so earlier. + sysUsed(unsafe.Pointer(s.base()), s.npages<<_PageShift) + s.scavenged = false + } s.unusedsince = 0 h.setSpans(s.base(), npage, s) From c803ffc67d0c90b24eb5a60a7d573eecc39e7753 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Thu, 4 Oct 2018 15:33:08 +0000 Subject: [PATCH 11/76] runtime: scavenge large spans before heap growth This change scavenges the largest spans before growing the heap for physical pages to "make up" for the newly-mapped space which, presumably, will be touched. In theory, this approach to scavenging helps reduce the RSS of an application by marking fragments in memory as reclaimable to the OS more eagerly than before. In practice this may not necessarily be true, depending on how sysUnused is implemented for each platform. Fixes #14045. Change-Id: Iab60790be05935865fc71f793cb9323ab00a18bd Reviewed-on: https://go-review.googlesource.com/c/139719 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/mheap.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index ddbc872080..8f6db8eec5 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -945,6 +945,14 @@ func (h *mheap) grow(npage uintptr) bool { return false } + // Scavenge some pages out of the free treap to make up for + // the virtual memory space we just allocated. We prefer to + // scavenge the largest spans first since the cost of scavenging + // is proportional to the number of sysUnused() calls rather than + // the number of pages released, so we make fewer of those calls + // with larger spans. + h.scavengeLargest(size) + // Create a fake "in use" span and free it, so that the // right coalescing happens. s := (*mspan)(h.spanalloc.alloc()) @@ -1107,6 +1115,46 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i } } +// scavengeLargest scavenges nbytes worth of spans in unscav +// starting from the largest span and working down. It then takes those spans +// and places them in scav. h must be locked. +func (h *mheap) scavengeLargest(nbytes uintptr) { + // Find the largest child. + t := h.free.treap + if t == nil { + return + } + for t.right != nil { + t = t.right + } + // Iterate over the treap from the largest child to the smallest by + // starting from the largest and finding its predecessor until we've + // recovered nbytes worth of physical memory, or it no longer has a + // predecessor (meaning the treap is now empty). + released := uintptr(0) + for t != nil && released < nbytes { + s := t.spanKey + r := s.scavenge() + if r == 0 { + // Since we're going in order of largest-to-smallest span, this + // means all other spans are no bigger than s. There's a high + // chance that the other spans don't even cover a full page, + // (though they could) but iterating further just for a handful + // of pages probably isn't worth it, so just stop here. + // + // This check also preserves the invariant that spans that have + // `scavenged` set are only ever in the `scav` treap, and + // those which have it unset are only in the `free` treap. + return + } + prev := t.pred() + h.free.removeNode(t) + t = prev + h.scav.insert(s) + released += r + } +} + // scavengeAll visits each node in the unscav treap and scavenges the // treapNode's span. It then removes the scavenged span from // unscav and adds it into scav before continuing. h must be locked. From 7836457ec3cc128efd9dd54197e8e5c25408c8b0 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Mon, 1 Oct 2018 19:58:01 +0000 Subject: [PATCH 12/76] runtime: add physical memory scavenging test This change introduces a test to malloc_test which checks for overuse of physical memory in the large object treap. Due to fragmentation, there may be many pages of physical memory that are sitting unused in large-object space. For #14045. Change-Id: I3722468f45063b11246dde6301c7ad02ae34be55 Reviewed-on: https://go-review.googlesource.com/c/138918 Run-TryBot: Michael Knyszek TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- src/runtime/malloc_test.go | 8 +++ src/runtime/testdata/testprog/gc.go | 83 +++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/runtime/malloc_test.go b/src/runtime/malloc_test.go index e6afc25ea9..f25bfa48af 100644 --- a/src/runtime/malloc_test.go +++ b/src/runtime/malloc_test.go @@ -168,6 +168,14 @@ func TestTinyAlloc(t *testing.T) { } } +func TestPhysicalMemoryUtilization(t *testing.T) { + got := runTestProg(t, "testprog", "GCPhys") + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got %q", want, got) + } +} + type acLink struct { x [1 << 20]byte } diff --git a/src/runtime/testdata/testprog/gc.go b/src/runtime/testdata/testprog/gc.go index 3ca74ba5fe..fdf08be7e9 100644 --- a/src/runtime/testdata/testprog/gc.go +++ b/src/runtime/testdata/testprog/gc.go @@ -17,6 +17,7 @@ func init() { register("GCFairness", GCFairness) register("GCFairness2", GCFairness2) register("GCSys", GCSys) + register("GCPhys", GCPhys) } func GCSys() { @@ -124,3 +125,85 @@ func GCFairness2() { } fmt.Println("OK") } + +var maybeSaved []byte + +func GCPhys() { + // In this test, we construct a very specific scenario. We first + // allocate N objects and drop half of their pointers on the floor, + // effectively creating N/2 'holes' in our allocated arenas. We then + // try to allocate objects twice as big. At the end, we measure the + // physical memory overhead of large objects. + // + // The purpose of this test is to ensure that the GC scavenges free + // spans eagerly to ensure high physical memory utilization even + // during fragmentation. + const ( + // Unfortunately, measuring actual used physical pages is + // difficult because HeapReleased doesn't include the parts + // of an arena that haven't yet been touched. So, we just + // make objects and size sufficiently large such that even + // 64 MB overhead is relatively small in the final + // calculation. + // + // Currently, we target 480MiB worth of memory for our test, + // computed as size * objects + (size*2) * (objects/2) + // = 2 * size * objects + // + // Size must be also large enough to be considered a large + // object (not in any size-segregated span). + size = 1 << 20 + objects = 240 + ) + // Save objects which we want to survive, and condemn objects which we don't. + // Note that we condemn objects in this way and release them all at once in + // order to avoid having the GC start freeing up these objects while the loop + // is still running and filling in the holes we intend to make. + saved := make([][]byte, 0, objects) + condemned := make([][]byte, 0, objects/2+1) + for i := 0; i < objects; i++ { + // Write into a global, to prevent this from being optimized away by + // the compiler in the future. + maybeSaved = make([]byte, size) + if i%2 == 0 { + saved = append(saved, maybeSaved) + } else { + condemned = append(condemned, maybeSaved) + } + } + condemned = nil + // Clean up the heap. This will free up every other object created above + // (i.e. everything in condemned) creating holes in the heap. + runtime.GC() + // Allocate many new objects of 2x size. + for i := 0; i < objects/2; i++ { + saved = append(saved, make([]byte, size*2)) + } + // Clean up the heap again just to put it in a known state. + runtime.GC() + // heapBacked is an estimate of the amount of physical memory used by + // this test. HeapSys is an estimate of the size of the mapped virtual + // address space (which may or may not be backed by physical pages) + // whereas HeapReleased is an estimate of the amount of bytes returned + // to the OS. Their difference then roughly corresponds to the amount + // of virtual address space that is backed by physical pages. + var stats runtime.MemStats + runtime.ReadMemStats(&stats) + heapBacked := stats.HeapSys - stats.HeapReleased + // If heapBacked exceeds the amount of memory actually used for heap + // allocated objects by 10% (post-GC HeapAlloc should be quite close to + // the size of the working set), then fail. + // + // In the context of this test, that indicates a large amount of + // fragmentation with physical pages that are otherwise unused but not + // returned to the OS. + overuse := (float64(heapBacked) - float64(stats.HeapAlloc)) / float64(stats.HeapAlloc) + if overuse > 0.1 { + fmt.Printf("exceeded physical memory overuse threshold of 10%%: %3.2f%%\n"+ + "(alloc: %d, sys: %d, rel: %d, objs: %d)\n", overuse*100, stats.HeapAlloc, + stats.HeapSys, stats.HeapReleased, len(saved)) + return + } + fmt.Println("OK") + runtime.KeepAlive(saved) +} From f570b54cc02ffeb82e35e72fc818916e8b896c67 Mon Sep 17 00:00:00 2001 From: Yury Smolsky Date: Tue, 30 Oct 2018 17:22:05 +0200 Subject: [PATCH 13/76] crypto/cipher: make stream examples runnable in the playground Updates #9679 Change-Id: I53412cf0142364de5f76e8affc15d607bfa2ad23 Reviewed-on: https://go-review.googlesource.com/c/145838 Run-TryBot: Yury Smolsky TryBot-Result: Gobot Gobot Reviewed-by: Filippo Valsorda --- src/crypto/cipher/example_test.go | 43 ++++++++++++------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/crypto/cipher/example_test.go b/src/crypto/cipher/example_test.go index 6e050a9c0d..9c32d6a934 100644 --- a/src/crypto/cipher/example_test.go +++ b/src/crypto/cipher/example_test.go @@ -5,6 +5,7 @@ package cipher_test import ( + "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" @@ -298,11 +299,8 @@ func ExampleStreamReader() { // package like bcrypt or scrypt. key, _ := hex.DecodeString("6368616e676520746869732070617373") - inFile, err := os.Open("encrypted-file") - if err != nil { - panic(err) - } - defer inFile.Close() + encrypted, _ := hex.DecodeString("cf0495cc6f75dafc23948538e79904a9") + bReader := bytes.NewReader(encrypted) block, err := aes.NewCipher(key) if err != nil { @@ -314,15 +312,9 @@ func ExampleStreamReader() { var iv [aes.BlockSize]byte stream := cipher.NewOFB(block, iv[:]) - outFile, err := os.OpenFile("decrypted-file", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - panic(err) - } - defer outFile.Close() - - reader := &cipher.StreamReader{S: stream, R: inFile} - // Copy the input file to the output file, decrypting as we go. - if _, err := io.Copy(outFile, reader); err != nil { + reader := &cipher.StreamReader{S: stream, R: bReader} + // Copy the input to the output stream, decrypting as we go. + if _, err := io.Copy(os.Stdout, reader); err != nil { panic(err) } @@ -330,6 +322,8 @@ func ExampleStreamReader() { // authentication of the encrypted data. If you were actually to use // StreamReader in this manner, an attacker could flip arbitrary bits in // the output. + + // Output: some secret text } func ExampleStreamWriter() { @@ -339,11 +333,7 @@ func ExampleStreamWriter() { // package like bcrypt or scrypt. key, _ := hex.DecodeString("6368616e676520746869732070617373") - inFile, err := os.Open("plaintext-file") - if err != nil { - panic(err) - } - defer inFile.Close() + bReader := bytes.NewReader([]byte("some secret text")) block, err := aes.NewCipher(key) if err != nil { @@ -355,15 +345,11 @@ func ExampleStreamWriter() { var iv [aes.BlockSize]byte stream := cipher.NewOFB(block, iv[:]) - outFile, err := os.OpenFile("encrypted-file", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - panic(err) - } - defer outFile.Close() + var out bytes.Buffer - writer := &cipher.StreamWriter{S: stream, W: outFile} - // Copy the input file to the output file, encrypting as we go. - if _, err := io.Copy(writer, inFile); err != nil { + writer := &cipher.StreamWriter{S: stream, W: &out} + // Copy the input to the output buffer, encrypting as we go. + if _, err := io.Copy(writer, bReader); err != nil { panic(err) } @@ -371,4 +357,7 @@ func ExampleStreamWriter() { // authentication of the encrypted data. If you were actually to use // StreamReader in this manner, an attacker could flip arbitrary bits in // the decrypted result. + + fmt.Printf("%x\n", out.Bytes()) + // Output: cf0495cc6f75dafc23948538e79904a9 } From 08816cb8d7ed16b9c804587ff02c1ad1c3af6cd5 Mon Sep 17 00:00:00 2001 From: Peter Weinberger Date: Mon, 29 Oct 2018 10:18:05 -0400 Subject: [PATCH 14/76] cmd/trace: use new traceparser to parse the raw trace files Change-Id: I8b224ae48a2f8acd5a64c9ff283e97821479a9a8 Reviewed-on: https://go-review.googlesource.com/c/145457 Run-TryBot: Peter Weinberger TryBot-Result: Gobot Gobot Reviewed-by: Hyang-Ah Hana Kim --- src/cmd/trace/annotations.go | 7 +- src/cmd/trace/annotations_test.go | 14 ++- src/cmd/trace/goroutines.go | 11 +- src/cmd/trace/main.go | 49 ++++----- src/cmd/trace/pprof.go | 64 +++++++----- src/cmd/trace/trace.go | 168 +++++++++++++++--------------- src/cmd/trace/trace_test.go | 46 ++++---- src/cmd/trace/trace_unix_test.go | 12 +-- 8 files changed, 194 insertions(+), 177 deletions(-) diff --git a/src/cmd/trace/annotations.go b/src/cmd/trace/annotations.go index 307da58bd5..9905456b46 100644 --- a/src/cmd/trace/annotations.go +++ b/src/cmd/trace/annotations.go @@ -8,7 +8,6 @@ import ( "bytes" "fmt" "html/template" - "internal/trace" "log" "math" "net/http" @@ -17,6 +16,8 @@ import ( "strconv" "strings" "time" + + trace "internal/traceparser" ) func init() { @@ -308,7 +309,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) { } } // combine region info. - analyzeGoroutines(events) + analyzeGoroutines(res) for goid, stats := range gs { // gs is a global var defined in goroutines.go as a result // of analyzeGoroutines. TODO(hyangah): fix this not to depend @@ -321,7 +322,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) { } var frame trace.Frame if s.Start != nil { - frame = *s.Start.Stk[0] + frame = *res.Stacks[s.Start.StkID][0] } id := regionTypeID{Frame: frame, Type: s.Name} regions[id] = append(regions[id], regionDesc{UserRegionDesc: s, G: goid}) diff --git a/src/cmd/trace/annotations_test.go b/src/cmd/trace/annotations_test.go index a9068d53c1..8b9daabcdb 100644 --- a/src/cmd/trace/annotations_test.go +++ b/src/cmd/trace/annotations_test.go @@ -11,7 +11,7 @@ import ( "context" "flag" "fmt" - traceparser "internal/trace" + "internal/traceparser" "io/ioutil" "reflect" "runtime/debug" @@ -338,10 +338,8 @@ func traceProgram(t *testing.T, f func(), name string) error { trace.Stop() saveTrace(buf, name) - res, err := traceparser.Parse(buf, name+".faketrace") - if err == traceparser.ErrTimeOrder { - t.Skipf("skipping due to golang.org/issue/16755: %v", err) - } else if err != nil { + res, err := traceparser.ParseBuffer(buf) + if err != nil { return err } @@ -370,15 +368,15 @@ func childrenNames(task *taskDesc) (ret []string) { return ret } -func swapLoaderData(res traceparser.ParseResult, err error) { +func swapLoaderData(res *traceparser.Parsed, err error) { // swap loader's data. parseTrace() // fool loader.once. loader.res = res loader.err = err - analyzeGoroutines(nil) // fool gsInit once. - gs = traceparser.GoroutineStats(res.Events) + analyzeGoroutines(res) // fool gsInit once. + gs = res.GoroutineStats() } diff --git a/src/cmd/trace/goroutines.go b/src/cmd/trace/goroutines.go index 548871a82c..c954704a47 100644 --- a/src/cmd/trace/goroutines.go +++ b/src/cmd/trace/goroutines.go @@ -9,7 +9,6 @@ package main import ( "fmt" "html/template" - "internal/trace" "log" "net/http" "reflect" @@ -17,6 +16,8 @@ import ( "strconv" "sync" "time" + + trace "internal/traceparser" ) func init() { @@ -38,15 +39,15 @@ var ( ) // analyzeGoroutines generates statistics about execution of all goroutines and stores them in gs. -func analyzeGoroutines(events []*trace.Event) { +func analyzeGoroutines(res *trace.Parsed) { gsInit.Do(func() { - gs = trace.GoroutineStats(events) + gs = res.GoroutineStats() }) } // httpGoroutines serves list of goroutine groups. func httpGoroutines(w http.ResponseWriter, r *http.Request) { - events, err := parseEvents() + events, err := parseTrace() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -89,7 +90,7 @@ Goroutines:
func httpGoroutine(w http.ResponseWriter, r *http.Request) { // TODO(hyangah): support format=csv (raw data) - events, err := parseEvents() + events, err := parseTrace() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/src/cmd/trace/main.go b/src/cmd/trace/main.go index 0c98b85c37..a33d2f4679 100644 --- a/src/cmd/trace/main.go +++ b/src/cmd/trace/main.go @@ -5,12 +5,12 @@ package main import ( - "bufio" + "bytes" "cmd/internal/browser" "flag" "fmt" "html/template" - "internal/trace" + trace "internal/traceparser" "io" "log" "net" @@ -115,8 +115,22 @@ func main() { dief("%v\n", err) } - if *debugFlag { - trace.Print(res.Events) + if *debugFlag { // match go tool trace -d (except for Offset and Seq) + f := func(ev *trace.Event) { + desc := trace.EventDescriptions[ev.Type] + w := new(bytes.Buffer) + fmt.Fprintf(w, "%v %v p=%v g=%v", ev.Ts, desc.Name, ev.P, ev.G) + for i, a := range desc.Args { + fmt.Fprintf(w, " %v=%v", a, ev.Args[i]) + } + for i, a := range desc.SArgs { + fmt.Fprintf(w, " %v=%v", a, ev.SArgs[i]) + } + fmt.Println(w.String()) + } + for i := 0; i < len(res.Events); i++ { + f(res.Events[i]) + } os.Exit(0) } reportMemoryUsage("after parsing trace") @@ -141,36 +155,23 @@ var ranges []Range var loader struct { once sync.Once - res trace.ParseResult + res *trace.Parsed err error } -// parseEvents is a compatibility wrapper that returns only -// the Events part of trace.ParseResult returned by parseTrace. -func parseEvents() ([]*trace.Event, error) { - res, err := parseTrace() - if err != nil { - return nil, err - } - return res.Events, err -} - -func parseTrace() (trace.ParseResult, error) { +func parseTrace() (*trace.Parsed, error) { loader.once.Do(func() { - tracef, err := os.Open(traceFile) + x, err := trace.New(traceFile) if err != nil { - loader.err = fmt.Errorf("failed to open trace file: %v", err) + loader.err = err return } - defer tracef.Close() - - // Parse and symbolize. - res, err := trace.Parse(bufio.NewReader(tracef), programBinary) + err = x.Parse(0, x.MaxTs, nil) if err != nil { - loader.err = fmt.Errorf("failed to parse trace: %v", err) + loader.err = err return } - loader.res = res + loader.res = x }) return loader.res, loader.err } diff --git a/src/cmd/trace/pprof.go b/src/cmd/trace/pprof.go index 3389d2799b..cf74fe56ae 100644 --- a/src/cmd/trace/pprof.go +++ b/src/cmd/trace/pprof.go @@ -9,7 +9,6 @@ package main import ( "bufio" "fmt" - "internal/trace" "io" "io/ioutil" "net/http" @@ -21,6 +20,8 @@ import ( "strconv" "time" + trace "internal/traceparser" + "github.com/google/pprof/profile" ) @@ -60,22 +61,22 @@ type interval struct { begin, end int64 // nanoseconds. } -func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error { +func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, *trace.Parsed) error) func(w io.Writer, r *http.Request) error { return func(w io.Writer, r *http.Request) error { id := r.FormValue("id") - events, err := parseEvents() + res, err := parseTrace() if err != nil { return err } - gToIntervals, err := pprofMatchingGoroutines(id, events) + gToIntervals, err := pprofMatchingGoroutines(id, res) if err != nil { return err } - return compute(w, gToIntervals, events) + return compute(w, gToIntervals, res) } } -func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error { +func pprofByRegion(compute func(io.Writer, map[uint64][]interval, *trace.Parsed) error) func(w io.Writer, r *http.Request) error { return func(w io.Writer, r *http.Request) error { filter, err := newRegionFilter(r) if err != nil { @@ -85,7 +86,7 @@ func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event if err != nil { return err } - events, _ := parseEvents() + events, _ := parseTrace() return compute(w, gToIntervals, events) } @@ -94,7 +95,7 @@ func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event // pprofMatchingGoroutines parses the goroutine type id string (i.e. pc) // and returns the ids of goroutines of the matching type and its interval. // If the id string is empty, returns nil without an error. -func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]interval, error) { +func pprofMatchingGoroutines(id string, p *trace.Parsed) (map[uint64][]interval, error) { if id == "" { return nil, nil } @@ -102,7 +103,7 @@ func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]int if err != nil { return nil, fmt.Errorf("invalid goroutine type: %v", id) } - analyzeGoroutines(events) + analyzeGoroutines(p) var res map[uint64][]interval for _, g := range gs { if g.PC != pc { @@ -171,17 +172,25 @@ func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) { return gToIntervals, nil } +func stklen(p *trace.Parsed, ev *trace.Event) int { + if ev.StkID == 0 { + return 0 + } + return len(p.Stacks[ev.StkID]) +} + // computePprofIO generates IO pprof-like profile (time spent in IO wait, currently only network blocking event). -func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { - prof := make(map[uint64]Record) +func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error { + events := res.Events + prof := make(map[uint32]Record) for _, ev := range events { - if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { + if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 { continue } overlapping := pprofOverlappingDuration(gToIntervals, ev) if overlapping > 0 { rec := prof[ev.StkID] - rec.stk = ev.Stk + rec.stk = res.Stacks[ev.StkID] rec.n++ rec.time += overlapping.Nanoseconds() prof[ev.StkID] = rec @@ -191,8 +200,9 @@ func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*t } // computePprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives). -func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { - prof := make(map[uint64]Record) +func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error { + events := res.Events + prof := make(map[uint32]Record) for _, ev := range events { switch ev.Type { case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect, @@ -203,13 +213,13 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [ default: continue } - if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { + if ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 { continue } overlapping := pprofOverlappingDuration(gToIntervals, ev) if overlapping > 0 { rec := prof[ev.StkID] - rec.stk = ev.Stk + rec.stk = res.Stacks[ev.StkID] rec.n++ rec.time += overlapping.Nanoseconds() prof[ev.StkID] = rec @@ -219,16 +229,17 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [ } // computePprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls). -func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { - prof := make(map[uint64]Record) +func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error { + events := res.Events + prof := make(map[uint32]Record) for _, ev := range events { - if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { + if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 { continue } overlapping := pprofOverlappingDuration(gToIntervals, ev) if overlapping > 0 { rec := prof[ev.StkID] - rec.stk = ev.Stk + rec.stk = res.Stacks[ev.StkID] rec.n++ rec.time += overlapping.Nanoseconds() prof[ev.StkID] = rec @@ -239,17 +250,18 @@ func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events // computePprofSched generates scheduler latency pprof-like profile // (time between a goroutine become runnable and actually scheduled for execution). -func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { - prof := make(map[uint64]Record) +func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error { + events := res.Events + prof := make(map[uint32]Record) for _, ev := range events { if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) || - ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { + ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 { continue } overlapping := pprofOverlappingDuration(gToIntervals, ev) if overlapping > 0 { rec := prof[ev.StkID] - rec.stk = ev.Stk + rec.stk = res.Stacks[ev.StkID] rec.n++ rec.time += overlapping.Nanoseconds() prof[ev.StkID] = rec @@ -327,7 +339,7 @@ func serveSVGProfile(prof func(w io.Writer, r *http.Request) error) http.Handler } } -func buildProfile(prof map[uint64]Record) *profile.Profile { +func buildProfile(prof map[uint32]Record) *profile.Profile { p := &profile.Profile{ PeriodType: &profile.ValueType{Type: "trace", Unit: "count"}, Period: 1, diff --git a/src/cmd/trace/trace.go b/src/cmd/trace/trace.go index 07fc4333eb..d0e0acd78c 100644 --- a/src/cmd/trace/trace.go +++ b/src/cmd/trace/trace.go @@ -7,7 +7,7 @@ package main import ( "encoding/json" "fmt" - "internal/trace" + trace "internal/traceparser" "io" "log" "math" @@ -23,7 +23,7 @@ import ( func init() { http.HandleFunc("/trace", httpTrace) - http.HandleFunc("/jsontrace", httpJsonTrace) + http.HandleFunc("/jsontrace", httpJSONTrace) http.HandleFunc("/trace_viewer_html", httpTraceViewerHTML) } @@ -38,7 +38,7 @@ func httpTrace(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - html := strings.ReplaceAll(templTrace, "{{PARAMS}}", r.Form.Encode()) + html := strings.Replace(templTrace, "{{PARAMS}}", r.Form.Encode(), -1) w.Write([]byte(html)) } @@ -165,8 +165,8 @@ func httpTraceViewerHTML(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filepath.Join(runtime.GOROOT(), "misc", "trace", "trace_viewer_full.html")) } -// httpJsonTrace serves json trace, requested from within templTrace HTML. -func httpJsonTrace(w http.ResponseWriter, r *http.Request) { +// httpJSONTrace serves json trace, requested from within templTrace HTML. +func httpJSONTrace(w http.ResponseWriter, r *http.Request) { defer debug.FreeOSMemory() defer reportMemoryUsage("after httpJsonTrace") // This is an AJAX handler, so instead of http.Error we use log.Printf to log errors. @@ -188,7 +188,7 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) { log.Printf("failed to parse goid parameter %q: %v", goids, err) return } - analyzeGoroutines(res.Events) + analyzeGoroutines(res) g, ok := gs[goid] if !ok { log.Printf("failed to find goroutine %d", goid) @@ -202,7 +202,7 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) { params.endTime = lastTimestamp() } params.maing = goid - params.gs = trace.RelatedGoroutines(res.Events, goid) + params.gs = res.RelatedGoroutines(goid) } else if taskids := r.FormValue("taskid"); taskids != "" { taskid, err := strconv.ParseUint(taskids, 10, 64) if err != nil { @@ -264,12 +264,13 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) { } c := viewerDataTraceConsumer(w, start, end) - if err := generateTrace(params, c); err != nil { + if err := generateTrace(res, params, c); err != nil { log.Printf("failed to generate trace: %v", err) return } } +// Range is a named range type Range struct { Name string Start int @@ -279,13 +280,13 @@ type Range struct { // splitTrace splits the trace into a number of ranges, // each resulting in approx 100MB of json output // (trace viewer can hardly handle more). -func splitTrace(res trace.ParseResult) []Range { +func splitTrace(res *trace.Parsed) []Range { params := &traceParams{ parsed: res, endTime: math.MaxInt64, } s, c := splittingTraceConsumer(100 << 20) // 100M - if err := generateTrace(params, c); err != nil { + if err := generateTrace(res, params, c); err != nil { dief("%v\n", err) } return s.Ranges @@ -302,7 +303,7 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) { } var ( - data = ViewerData{Frames: make(map[string]ViewerFrame)} + data = viewerData{Frames: make(map[string]viewerFrame)} sizes []eventSz cw countingWriter @@ -314,7 +315,7 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) { consumeTimeUnit: func(unit string) { data.TimeUnit = unit }, - consumeViewerEvent: func(v *ViewerEvent, required bool) { + consumeViewerEvent: func(v *viewerEvent, required bool) { if required { // Store required events inside data // so flush can include them in the required @@ -327,7 +328,7 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) { sizes = append(sizes, eventSz{v.Time, cw.size + 1}) // +1 for ",". cw.size = 0 }, - consumeViewerFrame: func(k string, v ViewerFrame) { + consumeViewerFrame: func(k string, v viewerFrame) { data.Frames[k] = v }, flush: func() { @@ -382,7 +383,7 @@ func (cw *countingWriter) Write(data []byte) (int, error) { } type traceParams struct { - parsed trace.ParseResult + parsed *trace.Parsed mode traceviewMode startTime int64 endTime int64 @@ -399,6 +400,7 @@ const ( ) type traceContext struct { + res *trace.Parsed *traceParams consumer traceConsumer frameTree frameNode @@ -449,16 +451,16 @@ type gInfo struct { markAssist *trace.Event // if non-nil, the mark assist currently running. } -type ViewerData struct { - Events []*ViewerEvent `json:"traceEvents"` - Frames map[string]ViewerFrame `json:"stackFrames"` +type viewerData struct { + Events []*viewerEvent `json:"traceEvents"` + Frames map[string]viewerFrame `json:"stackFrames"` TimeUnit string `json:"displayTimeUnit"` // This is where mandatory part of the trace starts (e.g. thread names) footer int } -type ViewerEvent struct { +type viewerEvent struct { Name string `json:"name,omitempty"` Phase string `json:"ph"` Scope string `json:"s,omitempty"` @@ -474,33 +476,33 @@ type ViewerEvent struct { Category string `json:"cat,omitempty"` } -type ViewerFrame struct { +type viewerFrame struct { Name string `json:"name"` Parent int `json:"parent,omitempty"` } -type NameArg struct { +type nameArg struct { Name string `json:"name"` } -type TaskArg struct { +type taskArg struct { ID uint64 `json:"id"` StartG uint64 `json:"start_g,omitempty"` EndG uint64 `json:"end_g,omitempty"` } -type RegionArg struct { +type regionArg struct { TaskID uint64 `json:"taskid,omitempty"` } -type SortIndexArg struct { +type sortIndexArg struct { Index int `json:"sort_index"` } type traceConsumer struct { consumeTimeUnit func(unit string) - consumeViewerEvent func(v *ViewerEvent, required bool) - consumeViewerFrame func(key string, f ViewerFrame) + consumeViewerEvent func(v *viewerEvent, required bool) + consumeViewerFrame func(key string, f viewerFrame) flush func() } @@ -517,15 +519,15 @@ const ( // If mode==goroutineMode, generate trace for goroutine goid, otherwise whole trace. // startTime, endTime determine part of the trace that we are interested in. // gset restricts goroutines that are included in the resulting trace. -func generateTrace(params *traceParams, consumer traceConsumer) error { +func generateTrace(res *trace.Parsed, params *traceParams, consumer traceConsumer) error { defer consumer.flush() - ctx := &traceContext{traceParams: params} + ctx := &traceContext{res: res, traceParams: params} ctx.frameTree.children = make(map[uint64]frameNode) ctx.consumer = consumer ctx.consumer.consumeTimeUnit("ns") - maxProc := 0 + maxProc := int32(0) ginfos := make(map[uint64]*gInfo) stacks := params.parsed.Stacks @@ -570,12 +572,12 @@ func generateTrace(params *traceParams, consumer traceConsumer) error { newG := ev.Args[0] info := getGInfo(newG) if info.name != "" { - return fmt.Errorf("duplicate go create event for go id=%d detected at offset %d", newG, ev.Off) + return fmt.Errorf("duplicate go create event for go id=%d detected at time %d", newG, ev.Ts) } - stk, ok := stacks[ev.Args[1]] + stk, ok := stacks[uint32(ev.Args[1])] if !ok || len(stk) == 0 { - return fmt.Errorf("invalid go create event: missing stack information for go id=%d at offset %d", newG, ev.Off) + return fmt.Errorf("invalid go create event: missing stack information for go id=%d at time %d", newG, ev.Ts) } fname := stk[0].Fn @@ -746,23 +748,23 @@ func generateTrace(params *traceParams, consumer traceConsumer) error { ctx.emitSectionFooter(procsSection, "PROCS", 2) } - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &NameArg{"GC"}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &SortIndexArg{-6}}) + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &nameArg{"GC"}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.GCP, Arg: &sortIndexArg{-6}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &NameArg{"Network"}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &SortIndexArg{-5}}) + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &nameArg{"Network"}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.NetpollP, Arg: &sortIndexArg{-5}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &NameArg{"Timers"}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &SortIndexArg{-4}}) + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &nameArg{"Timers"}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.TimerP, Arg: &sortIndexArg{-4}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &NameArg{"Syscalls"}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &SortIndexArg{-3}}) + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &nameArg{"Syscalls"}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: trace.SyscallP, Arg: &sortIndexArg{-3}}) // Display rows for Ps if we are in the default trace view mode (not goroutine-oriented presentation) if ctx.mode&modeGoroutineOriented == 0 { - for i := 0; i <= maxProc; i++ { - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &NameArg{fmt.Sprintf("Proc %v", i)}}) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &SortIndexArg{i}}) + for i := 0; i <= int(maxProc); i++ { + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &nameArg{fmt.Sprintf("Proc %v", i)}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: uint64(i), Arg: &sortIndexArg{i}}) } } @@ -800,27 +802,27 @@ func generateTrace(params *traceParams, consumer traceConsumer) error { if !ctx.gs[k] { continue } - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: k, Arg: &NameArg{v.name}}) + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: procsSection, Tid: k, Arg: &nameArg{v.name}}) } // Row for the main goroutine (maing) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: ctx.maing, Arg: &SortIndexArg{-2}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: ctx.maing, Arg: &sortIndexArg{-2}}) // Row for GC or global state (specified with G=0) - ctx.emitFooter(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: 0, Arg: &SortIndexArg{-1}}) + ctx.emitFooter(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: procsSection, Tid: 0, Arg: &sortIndexArg{-1}}) } return nil } -func (ctx *traceContext) emit(e *ViewerEvent) { +func (ctx *traceContext) emit(e *viewerEvent) { ctx.consumer.consumeViewerEvent(e, false) } -func (ctx *traceContext) emitFooter(e *ViewerEvent) { +func (ctx *traceContext) emitFooter(e *viewerEvent) { ctx.consumer.consumeViewerEvent(e, true) } func (ctx *traceContext) emitSectionFooter(sectionID uint64, name string, priority int) { - ctx.emitFooter(&ViewerEvent{Name: "process_name", Phase: "M", Pid: sectionID, Arg: &NameArg{name}}) - ctx.emitFooter(&ViewerEvent{Name: "process_sort_index", Phase: "M", Pid: sectionID, Arg: &SortIndexArg{priority}}) + ctx.emitFooter(&viewerEvent{Name: "process_name", Phase: "M", Pid: sectionID, Arg: &nameArg{name}}) + ctx.emitFooter(&viewerEvent{Name: "process_sort_index", Phase: "M", Pid: sectionID, Arg: &sortIndexArg{priority}}) } func (ctx *traceContext) time(ev *trace.Event) float64 { @@ -842,31 +844,30 @@ func tsWithinRange(ts, s, e int64) bool { func (ctx *traceContext) proc(ev *trace.Event) uint64 { if ctx.mode&modeGoroutineOriented != 0 && ev.P < trace.FakeP { return ev.G - } else { - return uint64(ev.P) } + return uint64(ev.P) } func (ctx *traceContext) emitSlice(ev *trace.Event, name string) { ctx.emit(ctx.makeSlice(ev, name)) } -func (ctx *traceContext) makeSlice(ev *trace.Event, name string) *ViewerEvent { - // If ViewerEvent.Dur is not a positive value, +func (ctx *traceContext) makeSlice(ev *trace.Event, name string) *viewerEvent { + // If viewerEvent.Dur is not a positive value, // trace viewer handles it as a non-terminating time interval. // Avoid it by setting the field with a small value. durationUsec := ctx.time(ev.Link) - ctx.time(ev) - if ev.Link.Ts-ev.Ts <= 0 { + if ev.Link == nil || ev.Link.Ts-ev.Ts <= 0 { durationUsec = 0.0001 // 0.1 nanoseconds } - sl := &ViewerEvent{ + sl := &viewerEvent{ Name: name, Phase: "X", Time: ctx.time(ev), Dur: durationUsec, Tid: ctx.proc(ev), - Stack: ctx.stack(ev.Stk), - EndStack: ctx.stack(ev.Link.Stk), + Stack: ctx.stack(ctx.res.Stacks[ev.StkID]), + EndStack: ctx.stack(ctx.res.Stacks[ev.Link.StkID]), } // grey out non-overlapping events if the event is not a global event (ev.G == 0) @@ -876,7 +877,7 @@ func (ctx *traceContext) makeSlice(ev *trace.Event, name string) *ViewerEvent { type Arg struct { P int } - sl.Arg = &Arg{P: ev.P} + sl.Arg = &Arg{P: int(ev.P)} } // grey out non-overlapping events. overlapping := false @@ -898,10 +899,10 @@ func (ctx *traceContext) emitTask(task *taskDesc, sortIndex int) { taskName := task.name durationUsec := float64(task.lastTimestamp()-task.firstTimestamp()) / 1e3 - ctx.emitFooter(&ViewerEvent{Name: "thread_name", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &NameArg{fmt.Sprintf("T%d %s", task.id, taskName)}}) - ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &SortIndexArg{sortIndex}}) + ctx.emitFooter(&viewerEvent{Name: "thread_name", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &nameArg{fmt.Sprintf("T%d %s", task.id, taskName)}}) + ctx.emit(&viewerEvent{Name: "thread_sort_index", Phase: "M", Pid: tasksSection, Tid: taskRow, Arg: &sortIndexArg{sortIndex}}) ts := float64(task.firstTimestamp()) / 1e3 - sl := &ViewerEvent{ + sl := &viewerEvent{ Name: taskName, Phase: "X", Time: ts, @@ -910,13 +911,13 @@ func (ctx *traceContext) emitTask(task *taskDesc, sortIndex int) { Tid: taskRow, Cname: pickTaskColor(task.id), } - targ := TaskArg{ID: task.id} + targ := taskArg{ID: task.id} if task.create != nil { - sl.Stack = ctx.stack(task.create.Stk) + sl.Stack = ctx.stack(ctx.res.Stacks[task.create.StkID]) targ.StartG = task.create.G } if task.end != nil { - sl.EndStack = ctx.stack(task.end.Stk) + sl.EndStack = ctx.stack(ctx.res.Stacks[task.end.StkID]) targ.EndG = task.end.G } sl.Arg = targ @@ -924,8 +925,8 @@ func (ctx *traceContext) emitTask(task *taskDesc, sortIndex int) { if task.create != nil && task.create.Type == trace.EvUserTaskCreate && task.create.Args[1] != 0 { ctx.arrowSeq++ - ctx.emit(&ViewerEvent{Name: "newTask", Phase: "s", Tid: task.create.Args[1], ID: ctx.arrowSeq, Time: ts, Pid: tasksSection}) - ctx.emit(&ViewerEvent{Name: "newTask", Phase: "t", Tid: taskRow, ID: ctx.arrowSeq, Time: ts, Pid: tasksSection}) + ctx.emit(&viewerEvent{Name: "newTask", Phase: "s", Tid: task.create.Args[1], ID: ctx.arrowSeq, Time: ts, Pid: tasksSection}) + ctx.emit(&viewerEvent{Name: "newTask", Phase: "t", Tid: taskRow, ID: ctx.arrowSeq, Time: ts, Pid: tasksSection}) } } @@ -946,7 +947,7 @@ func (ctx *traceContext) emitRegion(s regionDesc) { scopeID := fmt.Sprintf("%x", id) name := s.Name - sl0 := &ViewerEvent{ + sl0 := &viewerEvent{ Category: "Region", Name: name, Phase: "b", @@ -957,11 +958,11 @@ func (ctx *traceContext) emitRegion(s regionDesc) { Cname: pickTaskColor(s.TaskID), } if s.Start != nil { - sl0.Stack = ctx.stack(s.Start.Stk) + sl0.Stack = ctx.stack(ctx.res.Stacks[s.Start.StkID]) } ctx.emit(sl0) - sl1 := &ViewerEvent{ + sl1 := &viewerEvent{ Category: "Region", Name: name, Phase: "e", @@ -970,10 +971,10 @@ func (ctx *traceContext) emitRegion(s regionDesc) { ID: uint64(regionID), Scope: scopeID, Cname: pickTaskColor(s.TaskID), - Arg: RegionArg{TaskID: s.TaskID}, + Arg: regionArg{TaskID: s.TaskID}, } if s.End != nil { - sl1.Stack = ctx.stack(s.End.Stk) + sl1.Stack = ctx.stack(ctx.res.Stacks[s.End.StkID]) } ctx.emit(sl1) } @@ -992,7 +993,7 @@ func (ctx *traceContext) emitHeapCounters(ev *trace.Event) { diff = ctx.heapStats.nextGC - ctx.heapStats.heapAlloc } if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) { - ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &heapCountersArg{ctx.heapStats.heapAlloc, diff}}) + ctx.emit(&viewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &heapCountersArg{ctx.heapStats.heapAlloc, diff}}) } ctx.prevHeapStats = ctx.heapStats } @@ -1008,7 +1009,7 @@ func (ctx *traceContext) emitGoroutineCounters(ev *trace.Event) { return } if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) { - ctx.emit(&ViewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &goroutineCountersArg{uint64(ctx.gstates[gRunning]), uint64(ctx.gstates[gRunnable]), uint64(ctx.gstates[gWaitingGC])}}) + ctx.emit(&viewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &goroutineCountersArg{uint64(ctx.gstates[gRunning]), uint64(ctx.gstates[gRunnable]), uint64(ctx.gstates[gWaitingGC])}}) } ctx.prevGstates = ctx.gstates } @@ -1023,7 +1024,7 @@ func (ctx *traceContext) emitThreadCounters(ev *trace.Event) { return } if tsWithinRange(ev.Ts, ctx.startTime, ctx.endTime) { - ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &threadCountersArg{ + ctx.emit(&viewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &threadCountersArg{ Running: ctx.threadStats.prunning, InSyscall: ctx.threadStats.insyscall}}) } @@ -1061,14 +1062,14 @@ func (ctx *traceContext) emitInstant(ev *trace.Event, name, category string) { } arg = &Arg{ev.Args[0]} } - ctx.emit(&ViewerEvent{ + ctx.emit(&viewerEvent{ Name: name, Category: category, Phase: "I", Scope: "t", Time: ctx.time(ev), Tid: ctx.proc(ev), - Stack: ctx.stack(ev.Stk), + Stack: ctx.stack(ctx.res.Stacks[ev.StkID]), Cname: cname, Arg: arg}) } @@ -1105,8 +1106,11 @@ func (ctx *traceContext) emitArrow(ev *trace.Event, name string) { } ctx.arrowSeq++ - ctx.emit(&ViewerEvent{Name: name, Phase: "s", Tid: ctx.proc(ev), ID: ctx.arrowSeq, Time: ctx.time(ev), Stack: ctx.stack(ev.Stk), Cname: color}) - ctx.emit(&ViewerEvent{Name: name, Phase: "t", Tid: ctx.proc(ev.Link), ID: ctx.arrowSeq, Time: ctx.time(ev.Link), Cname: color}) + ctx.emit(&viewerEvent{Name: name, Phase: "s", Tid: ctx.proc(ev), + ID: ctx.arrowSeq, Time: ctx.time(ev), + Stack: ctx.stack(ctx.res.Stacks[ev.StkID]), Cname: color}) + ctx.emit(&viewerEvent{Name: name, Phase: "t", Tid: ctx.proc(ev.Link), + ID: ctx.arrowSeq, Time: ctx.time(ev.Link), Cname: color}) } func (ctx *traceContext) stack(stk []*trace.Frame) int { @@ -1128,7 +1132,7 @@ func (ctx *traceContext) buildBranch(parent frameNode, stk []*trace.Frame) int { node.id = ctx.frameSeq node.children = make(map[uint64]frameNode) parent.children[frame.PC] = node - ctx.consumer.consumeViewerFrame(strconv.Itoa(node.id), ViewerFrame{fmt.Sprintf("%v:%v", frame.Fn, frame.Line), parent.id}) + ctx.consumer.consumeViewerFrame(strconv.Itoa(node.id), viewerFrame{fmt.Sprintf("%v:%v", frame.Fn, frame.Line), parent.id}) } return ctx.buildBranch(node, stk) } @@ -1163,7 +1167,7 @@ type jsonWriter struct { } func viewerDataTraceConsumer(w io.Writer, start, end int64) traceConsumer { - frames := make(map[string]ViewerFrame) + frames := make(map[string]viewerFrame) enc := json.NewEncoder(w) written := 0 index := int64(-1) @@ -1175,7 +1179,7 @@ func viewerDataTraceConsumer(w io.Writer, start, end int64) traceConsumer { enc.Encode(unit) io.WriteString(w, ",") }, - consumeViewerEvent: func(v *ViewerEvent, required bool) { + consumeViewerEvent: func(v *viewerEvent, required bool) { index++ if !required && (index < start || index > end) { // not in the range. Skip! @@ -1192,7 +1196,7 @@ func viewerDataTraceConsumer(w io.Writer, start, end int64) traceConsumer { // Same should be applied to splittingTraceConsumer. written++ }, - consumeViewerFrame: func(k string, v ViewerFrame) { + consumeViewerFrame: func(k string, v viewerFrame) { frames[k] = v }, flush: func() { diff --git a/src/cmd/trace/trace_test.go b/src/cmd/trace/trace_test.go index 9e90f50d4b..abeb330924 100644 --- a/src/cmd/trace/trace_test.go +++ b/src/cmd/trace/trace_test.go @@ -8,26 +8,27 @@ package main import ( "context" - "internal/trace" "io/ioutil" rtrace "runtime/trace" "strings" "testing" + + trace "internal/traceparser" ) // stacks is a fake stack map populated for test. -type stacks map[uint64][]*trace.Frame +type stacks map[uint32][]*trace.Frame // add adds a stack with a single frame whose Fn field is // set to the provided fname and returns a unique stack id. func (s *stacks) add(fname string) uint64 { if *s == nil { - *s = make(map[uint64][]*trace.Frame) + *s = make(map[uint32][]*trace.Frame) } - id := uint64(len(*s)) + id := uint32(len(*s)) (*s)[id] = []*trace.Frame{{Fn: fname}} - return id + return uint64(id) } // TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace @@ -36,8 +37,7 @@ func (s *stacks) add(fname string) uint64 { // - the counts must not include goroutines blocked waiting on channels or in syscall. func TestGoroutineCount(t *testing.T) { w := trace.NewWriter() - w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] - w.Emit(trace.EvFrequency, 1) // [ticks per second] + w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] var s stacks @@ -61,8 +61,9 @@ func TestGoroutineCount(t *testing.T) { w.Emit(trace.EvGoCreate, 1, 40, s.add("pkg.f4"), s.add("main.f4")) w.Emit(trace.EvGoStartLocal, 1, 40) // [timestamp, goroutine id] w.Emit(trace.EvGoSched, 1, s.add("main.f4")) // [timestamp, stack] + w.Emit(trace.EvFrequency, 1) // [ticks per second] - res, err := trace.Parse(w, "") + res, err := trace.ParseBuffer(w) if err != nil { t.Fatalf("failed to parse test trace: %v", err) } @@ -74,9 +75,9 @@ func TestGoroutineCount(t *testing.T) { } // Use the default viewerDataTraceConsumer but replace - // consumeViewerEvent to intercept the ViewerEvents for testing. + // consumeViewerEvent to intercept the viewerEvents for testing. c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) - c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { + c.consumeViewerEvent = func(ev *viewerEvent, _ bool) { if ev.Name == "Goroutines" { cnt := ev.Arg.(*goroutineCountersArg) if cnt.Runnable+cnt.Running > 2 { @@ -87,7 +88,7 @@ func TestGoroutineCount(t *testing.T) { } // If the counts drop below 0, generateTrace will return an error. - if err := generateTrace(params, c); err != nil { + if err := generateTrace(res, params, c); err != nil { t.Fatalf("generateTrace failed: %v", err) } } @@ -99,8 +100,7 @@ func TestGoroutineFilter(t *testing.T) { var s stacks w := trace.NewWriter() - w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] - w.Emit(trace.EvFrequency, 1) // [ticks per second] + w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] // goroutine 10: blocked w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id] @@ -115,8 +115,9 @@ func TestGoroutineFilter(t *testing.T) { // goroutine 10: runnable->running->block w.Emit(trace.EvGoStartLocal, 1, 10) // [timestamp, goroutine id] w.Emit(trace.EvGoBlock, 1, s.add("pkg.f3")) // [timestamp, stack] + w.Emit(trace.EvFrequency, 1) // [ticks per second] - res, err := trace.Parse(w, "") + res, err := trace.ParseBuffer(w) if err != nil { t.Fatalf("failed to parse test trace: %v", err) } @@ -129,15 +130,14 @@ func TestGoroutineFilter(t *testing.T) { } c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) - if err := generateTrace(params, c); err != nil { + if err := generateTrace(res, params, c); err != nil { t.Fatalf("generateTrace failed: %v", err) } } func TestPreemptedMarkAssist(t *testing.T) { w := trace.NewWriter() - w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] - w.Emit(trace.EvFrequency, 1) // [ticks per second] + w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] var s stacks // goroutine 9999: running -> mark assisting -> preempted -> assisting -> running -> block @@ -148,11 +148,13 @@ func TestPreemptedMarkAssist(t *testing.T) { w.Emit(trace.EvGoStartLocal, 1, 9999) // [timestamp, goroutine id] w.Emit(trace.EvGCMarkAssistDone, 1) // [timestamp] w.Emit(trace.EvGoBlock, 1, s.add("main.f2")) // [timestamp, stack] + w.Emit(trace.EvFrequency, 1) // [ticks per second] - res, err := trace.Parse(w, "") + res, err := trace.ParseBuffer(w) if err != nil { t.Fatalf("failed to parse test trace: %v", err) } + t.Logf("%+v", *res) res.Stacks = s // use fake stacks params := &traceParams{ @@ -163,12 +165,12 @@ func TestPreemptedMarkAssist(t *testing.T) { c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) marks := 0 - c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { + c.consumeViewerEvent = func(ev *viewerEvent, _ bool) { if strings.Contains(ev.Name, "MARK ASSIST") { marks++ } } - if err := generateTrace(params, c); err != nil { + if err := generateTrace(res, params, c); err != nil { t.Fatalf("generateTrace failed: %v", err) } @@ -214,7 +216,7 @@ func TestFoo(t *testing.T) { c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) var logBeforeTaskEnd, logAfterTaskEnd bool - c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { + c.consumeViewerEvent = func(ev *viewerEvent, _ bool) { if ev.Name == "log before task ends" { logBeforeTaskEnd = true } @@ -222,7 +224,7 @@ func TestFoo(t *testing.T) { logAfterTaskEnd = true } } - if err := generateTrace(params, c); err != nil { + if err := generateTrace(res, params, c); err != nil { t.Fatalf("generateTrace failed: %v", err) } if !logBeforeTaskEnd { diff --git a/src/cmd/trace/trace_unix_test.go b/src/cmd/trace/trace_unix_test.go index fec060e121..144642ad9e 100644 --- a/src/cmd/trace/trace_unix_test.go +++ b/src/cmd/trace/trace_unix_test.go @@ -8,7 +8,7 @@ package main import ( "bytes" - traceparser "internal/trace" + "internal/traceparser" "io/ioutil" "runtime" "runtime/trace" @@ -73,17 +73,15 @@ func TestGoroutineInSyscall(t *testing.T) { } trace.Stop() - res, err := traceparser.Parse(buf, "") - if err == traceparser.ErrTimeOrder { - t.Skipf("skipping due to golang.org/issue/16755 (timestamps are unreliable): %v", err) - } else if err != nil { + res, err := traceparser.ParseBuffer(buf) + if err != nil { t.Fatalf("failed to parse trace: %v", err) } // Check only one thread for the pipe read goroutine is // considered in-syscall. c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) - c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { + c.consumeViewerEvent = func(ev *viewerEvent, _ bool) { if ev.Name == "Threads" { arg := ev.Arg.(*threadCountersArg) if arg.InSyscall > 1 { @@ -96,7 +94,7 @@ func TestGoroutineInSyscall(t *testing.T) { parsed: res, endTime: int64(1<<63 - 1), } - if err := generateTrace(param, c); err != nil { + if err := generateTrace(res, param, c); err != nil { t.Fatalf("failed to generate ViewerData: %v", err) } } From 0e0f7986241ad035ff5469d424a8c87bcb5b3873 Mon Sep 17 00:00:00 2001 From: Katie Hockman Date: Tue, 30 Oct 2018 16:42:55 +0000 Subject: [PATCH 15/76] Revert "os: add support for long path names on unix RemoveAll" This reverts commit 85143d355493c6bba994d49ed154b4df8b78874b. Reason for revert: Breaking all Darwin and FreeBSD builds. Trybots did not pass for this. Change-Id: I5494e14ad5ab9cf6e1e225a25b2e8b38f3359d13 Reviewed-on: https://go-review.googlesource.com/c/145897 Reviewed-by: Katie Hockman Run-TryBot: Katie Hockman TryBot-Result: Gobot Gobot --- src/internal/syscall/unix/at.go | 58 ----- src/internal/syscall/unix/at_sysnum_darwin.go | 12 - .../syscall/unix/at_sysnum_dragonfly.go | 14 -- .../syscall/unix/at_sysnum_freebsd.go | 14 -- .../syscall/unix/at_sysnum_fstatat64_linux.go | 11 - .../syscall/unix/at_sysnum_fstatat_linux.go | 11 - src/internal/syscall/unix/at_sysnum_linux.go | 13 - src/internal/syscall/unix/at_sysnum_netbsd.go | 14 -- .../unix/at_sysnum_newfstatat_linux.go | 11 - .../syscall/unix/at_sysnum_openbsd.go | 14 -- src/os/path.go | 99 ++++++++ src/os/path_test.go | 125 +++++++++ src/os/path_unix.go | 28 +-- src/os/removeall_at.go | 139 ---------- src/os/removeall_noat.go | 110 -------- src/os/removeall_test.go | 237 ------------------ 16 files changed, 225 insertions(+), 685 deletions(-) delete mode 100644 src/internal/syscall/unix/at.go delete mode 100644 src/internal/syscall/unix/at_sysnum_darwin.go delete mode 100644 src/internal/syscall/unix/at_sysnum_dragonfly.go delete mode 100644 src/internal/syscall/unix/at_sysnum_freebsd.go delete mode 100644 src/internal/syscall/unix/at_sysnum_fstatat64_linux.go delete mode 100644 src/internal/syscall/unix/at_sysnum_fstatat_linux.go delete mode 100644 src/internal/syscall/unix/at_sysnum_linux.go delete mode 100644 src/internal/syscall/unix/at_sysnum_netbsd.go delete mode 100644 src/internal/syscall/unix/at_sysnum_newfstatat_linux.go delete mode 100644 src/internal/syscall/unix/at_sysnum_openbsd.go delete mode 100644 src/os/removeall_at.go delete mode 100644 src/os/removeall_noat.go delete mode 100644 src/os/removeall_test.go diff --git a/src/internal/syscall/unix/at.go b/src/internal/syscall/unix/at.go deleted file mode 100644 index 1c05d2abe3..0000000000 --- a/src/internal/syscall/unix/at.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 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 linux darwin freebsd openbsd netbsd dragonfly - -package unix - -import ( - "syscall" - "unsafe" -) - -func Unlinkat(dirfd int, path string, flags int) error { - var p *byte - p, err := syscall.BytePtrFromString(path) - if err != nil { - return err - } - - _, _, errno := syscall.Syscall(unlinkatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags)) - if errno != 0 { - return errno - } - - return nil -} - -func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { - var p *byte - p, err := syscall.BytePtrFromString(path) - if err != nil { - return 0, err - } - - fd, _, errno := syscall.Syscall6(openatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) - if errno != 0 { - return 0, errno - } - - return int(fd), nil -} - -func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { - var p *byte - p, err := syscall.BytePtrFromString(path) - if err != nil { - return err - } - - _, _, errno := syscall.Syscall6(fstatatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) - if errno != 0 { - return errno - } - - return nil - -} diff --git a/src/internal/syscall/unix/at_sysnum_darwin.go b/src/internal/syscall/unix/at_sysnum_darwin.go deleted file mode 100644 index 6aa08b4284..0000000000 --- a/src/internal/syscall/unix/at_sysnum_darwin.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018 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 unix - -const unlinkatTrap = uintptr(472) -const openatTrap = uintptr(463) -const fstatatTrap = uintptr(469) - -const AT_REMOVEDIR = 0x80 -const AT_SYMLINK_NOFOLLOW = 0x0020 diff --git a/src/internal/syscall/unix/at_sysnum_dragonfly.go b/src/internal/syscall/unix/at_sysnum_dragonfly.go deleted file mode 100644 index cec9abce6a..0000000000 --- a/src/internal/syscall/unix/at_sysnum_dragonfly.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const unlinkatTrap uintptr = syscall.SYS_UNLINKAT -const openatTrap uintptr = syscall.SYS_OPENAT -const fstatatTrap uintptr = syscall.SYS_FSTATAT - -const AT_REMOVEDIR = 0x2 -const AT_SYMLINK_NOFOLLOW = 0x1 diff --git a/src/internal/syscall/unix/at_sysnum_freebsd.go b/src/internal/syscall/unix/at_sysnum_freebsd.go deleted file mode 100644 index fe45e296d7..0000000000 --- a/src/internal/syscall/unix/at_sysnum_freebsd.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const unlinkatTrap uintptr = syscall.SYS_UNLINKAT -const openatTrap uintptr = syscall.SYS_OPENAT -const fstatatTrap uintptr = syscall.SYS_FSTATAT - -const AT_REMOVEDIR = 0x800 -const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go b/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go deleted file mode 100644 index c6ea206c12..0000000000 --- a/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 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 arm mips mipsle 386 - -package unix - -import "syscall" - -const fstatatTrap uintptr = syscall.SYS_FSTATAT64 diff --git a/src/internal/syscall/unix/at_sysnum_fstatat_linux.go b/src/internal/syscall/unix/at_sysnum_fstatat_linux.go deleted file mode 100644 index 580e7997f8..0000000000 --- a/src/internal/syscall/unix/at_sysnum_fstatat_linux.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const fstatatTrap uintptr = syscall.SYS_FSTATAT diff --git a/src/internal/syscall/unix/at_sysnum_linux.go b/src/internal/syscall/unix/at_sysnum_linux.go deleted file mode 100644 index fa7cd75d42..0000000000 --- a/src/internal/syscall/unix/at_sysnum_linux.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const unlinkatTrap uintptr = syscall.SYS_UNLINKAT -const openatTrap uintptr = syscall.SYS_OPENAT - -const AT_REMOVEDIR = 0x200 -const AT_SYMLINK_NOFOLLOW = 0x100 diff --git a/src/internal/syscall/unix/at_sysnum_netbsd.go b/src/internal/syscall/unix/at_sysnum_netbsd.go deleted file mode 100644 index fe45e296d7..0000000000 --- a/src/internal/syscall/unix/at_sysnum_netbsd.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const unlinkatTrap uintptr = syscall.SYS_UNLINKAT -const openatTrap uintptr = syscall.SYS_OPENAT -const fstatatTrap uintptr = syscall.SYS_FSTATAT - -const AT_REMOVEDIR = 0x800 -const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go b/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go deleted file mode 100644 index e76c1cbdce..0000000000 --- a/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 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 amd64 mips64 mips64le ppc64 ppc64le s390x - -package unix - -import "syscall" - -const fstatatTrap uintptr = syscall.SYS_NEWFSTATAT diff --git a/src/internal/syscall/unix/at_sysnum_openbsd.go b/src/internal/syscall/unix/at_sysnum_openbsd.go deleted file mode 100644 index c2d48b9914..0000000000 --- a/src/internal/syscall/unix/at_sysnum_openbsd.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const unlinkatTrap uintptr = syscall.SYS_UNLINKAT -const openatTrap uintptr = syscall.SYS_OPENAT -const fstatatTrap uintptr = syscall.SYS_FSTATAT - -const AT_REMOVEDIR = 0x08 -const AT_SYMLINK_NOFOLLOW = 0x02 diff --git a/src/os/path.go b/src/os/path.go index e31f64c750..cdfbc18921 100644 --- a/src/os/path.go +++ b/src/os/path.go @@ -5,6 +5,7 @@ package os import ( + "io" "syscall" ) @@ -57,3 +58,101 @@ func MkdirAll(path string, perm FileMode) error { } return nil } + +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +func RemoveAll(path string) error { + // Simple case: if Remove works, we're done. + err := Remove(path) + if err == nil || IsNotExist(err) { + return nil + } + + // Otherwise, is this a directory we need to recurse into? + dir, serr := Lstat(path) + if serr != nil { + if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { + return nil + } + return serr + } + if !dir.IsDir() { + // Not a directory; return the error from Remove. + return err + } + + // Remove contents & return first error. + err = nil + for { + fd, err := Open(path) + if err != nil { + if IsNotExist(err) { + // Already deleted by someone else. + return nil + } + return err + } + + const request = 1024 + names, err1 := fd.Readdirnames(request) + + // Removing files from the directory may have caused + // the OS to reshuffle it. Simply calling Readdirnames + // again may skip some entries. The only reliable way + // to avoid this is to close and re-open the + // directory. See issue 20841. + fd.Close() + + for _, name := range names { + err1 := RemoveAll(path + string(PathSeparator) + name) + if err == nil { + err = err1 + } + } + + if err1 == io.EOF { + break + } + // If Readdirnames returned an error, use it. + if err == nil { + err = err1 + } + if len(names) == 0 { + break + } + + // We don't want to re-open unnecessarily, so if we + // got fewer than request names from Readdirnames, try + // simply removing the directory now. If that + // succeeds, we are done. + if len(names) < request { + err1 := Remove(path) + if err1 == nil || IsNotExist(err1) { + return nil + } + + if err != nil { + // We got some error removing the + // directory contents, and since we + // read fewer names than we requested + // there probably aren't more files to + // remove. Don't loop around to read + // the directory again. We'll probably + // just get the same error. + return err + } + } + } + + // Remove directory. + err1 := Remove(path) + if err1 == nil || IsNotExist(err1) { + return nil + } + if err == nil { + err = err1 + } + return err +} diff --git a/src/os/path_test.go b/src/os/path_test.go index 6cb25bcaa7..f58c7e746d 100644 --- a/src/os/path_test.go +++ b/src/os/path_test.go @@ -5,6 +5,7 @@ package os_test import ( + "fmt" "internal/testenv" "io/ioutil" . "os" @@ -75,6 +76,130 @@ func TestMkdirAll(t *testing.T) { } } +func TestRemoveAll(t *testing.T) { + tmpDir := TempDir() + // Work directory. + path := tmpDir + "/_TestRemoveAll_" + fpath := path + "/file" + dpath := path + "/dir" + + // Make directory with 1 file and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + fd, err := Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q (first): %s", path, err) + } + if _, err = Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path) + } + + // Make directory with file and subdirectory and remove. + if err = MkdirAll(dpath, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", dpath, err) + } + fd, err = Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + fd, err = Create(dpath + "/file") + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q (second): %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) + } + + // Determine if we should run the following test. + testit := true + if runtime.GOOS == "windows" { + // Chmod is not supported under windows. + testit = false + } else { + // Test fails as root. + testit = Getuid() != 0 + } + if testit { + // Make directory with file and subdirectory and trigger error. + if err = MkdirAll(dpath, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", dpath, err) + } + + for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { + fd, err = Create(s) + if err != nil { + t.Fatalf("create %q: %s", s, err) + } + fd.Close() + } + if err = Chmod(dpath, 0); err != nil { + t.Fatalf("Chmod %q 0: %s", dpath, err) + } + + // No error checking here: either RemoveAll + // will or won't be able to remove dpath; + // either way we want to see if it removes fpath + // and path/zzz. Reasons why RemoveAll might + // succeed in removing dpath as well include: + // * running as root + // * running on a file system without permissions (FAT) + RemoveAll(path) + Chmod(dpath, 0777) + + for _, s := range []string{fpath, path + "/zzz"} { + if _, err = Lstat(s); err == nil { + t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) + } + } + } + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) + } + if _, err = Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) + } +} + +// Test RemoveAll on a large directory. +func TestRemoveAllLarge(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + tmpDir := TempDir() + // Work directory. + path := tmpDir + "/_TestRemoveAllLarge_" + + // Make directory with 1000 files and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + for i := 0; i < 1000; i++ { + fpath := fmt.Sprintf("%s/file%d", path, i) + fd, err := Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + } + if err := RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q: %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll", path) + } +} + func TestMkdirAllWithSymlink(t *testing.T) { testenv.MustHaveSymlink(t) diff --git a/src/os/path_unix.go b/src/os/path_unix.go index be373a50a9..3cb0e3acc4 100644 --- a/src/os/path_unix.go +++ b/src/os/path_unix.go @@ -16,7 +16,7 @@ func IsPathSeparator(c uint8) bool { return PathSeparator == c } -// basename removes trailing slashes and the leading directory name from path name. +// basename removes trailing slashes and the leading directory name from path name func basename(name string) string { i := len(name) - 1 // Remove trailing slashes @@ -34,32 +34,6 @@ func basename(name string) string { return name } -// splitPath returns the base name and parent directory. -func splitPath(path string) (string, string) { - // if no better parent is found, the path is relative from "here" - dirname := "." - // if no slashes in path, base is path - basename := path - - i := len(path) - 1 - - // Remove trailing slashes - for ; i > 0 && path[i] == '/'; i-- { - path = path[:i] - } - - // Remove leading directory path - for i--; i >= 0; i-- { - if path[i] == '/' { - dirname = path[:i+1] - basename = path[i+1:] - break - } - } - - return dirname, basename -} - func fixRootDirectory(p string) string { return p } diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go deleted file mode 100644 index ec69d40f29..0000000000 --- a/src/os/removeall_at.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2018 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 linux darwin freebsd openbsd netbsd dragonfly - -package os - -import ( - "internal/syscall/unix" - "io" - "syscall" -) - -func RemoveAll(path string) error { - // Not allowed in unix - if path == "" || endsWithDot(path) { - return syscall.EINVAL - } - - // RemoveAll recurses by deleting the path base from - // its parent directory - parentDir, base := splitPath(path) - - parent, err := Open(parentDir) - if IsNotExist(err) { - // If parent does not exist, base cannot exist. Fail silently - return nil - } - if err != nil { - return err - } - defer parent.Close() - - return removeAllFrom(parent, base) -} - -func removeAllFrom(parent *File, path string) error { - parentFd := int(parent.Fd()) - // Simple case: if Unlink (aka remove) works, we're done. - err := unix.Unlinkat(parentFd, path, 0) - if err == nil || IsNotExist(err) { - return nil - } - - // If not a "is directory" error, we have a problem - if err != syscall.EISDIR && err != syscall.EPERM { - return err - } - - // Is this a directory we need to recurse into? - var statInfo syscall.Stat_t - statErr := unix.Fstatat(parentFd, path, &statInfo, unix.AT_SYMLINK_NOFOLLOW) - if statErr != nil { - return statErr - } - if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { - // Not a directory; return the error from the Remove - return err - } - - // Remove the directory's entries - var recurseErr error - for { - const request = 1024 - - // Open the directory to recurse into - file, err := openFdAt(parentFd, path) - if err != nil { - if IsNotExist(err) { - return nil - } - return err - } - - names, readErr := file.Readdirnames(request) - // Errors other than EOF should stop us from continuing - if readErr != nil && readErr != io.EOF { - file.Close() - if IsNotExist(readErr) { - return nil - } - return readErr - } - - for _, name := range names { - err := removeAllFrom(file, name) - if err != nil { - recurseErr = err - } - } - - // Removing files from the directory may have caused - // the OS to reshuffle it. Simply calling Readdirnames - // again may skip some entries. The only reliable way - // to avoid this is to close and re-open the - // directory. See issue 20841. - file.Close() - - // Finish when the end of the directory is reached - if len(names) < request { - break - } - } - - // Remove the directory itself - unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR) - if unlinkError == nil || IsNotExist(unlinkError) { - return nil - } - - if recurseErr != nil { - return recurseErr - } - return unlinkError -} - -func openFdAt(fd int, path string) (*File, error) { - fd, err := unix.Openat(fd, path, O_RDONLY, 0) - if err != nil { - return nil, err - } - - return NewFile(uintptr(fd), path), nil -} - -func endsWithDot(path string) bool { - if path == "." || path == ".." { - return true - } - if len(path) >= 2 && path[len(path)-2:] == "/." { - return true - } - if len(path) >= 3 && path[len(path)-3:] == "/.." { - return true - } - - return false -} diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go deleted file mode 100644 index 7cfc33c025..0000000000 --- a/src/os/removeall_noat.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 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 !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly - -package os - -import ( - "io" - "syscall" -) - -// RemoveAll removes path and any children it contains. -// It removes everything it can but returns the first error -// it encounters. If the path does not exist, RemoveAll -// returns nil (no error). -func RemoveAll(path string) error { - // Simple case: if Remove works, we're done. - err := Remove(path) - if err == nil || IsNotExist(err) { - return nil - } - - // Otherwise, is this a directory we need to recurse into? - dir, serr := Lstat(path) - if serr != nil { - if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { - return nil - } - return serr - } - if !dir.IsDir() { - // Not a directory; return the error from Remove. - return err - } - - // Remove contents & return first error. - err = nil - for { - fd, err := Open(path) - if err != nil { - if IsNotExist(err) { - // Already deleted by someone else. - return nil - } - return err - } - - const request = 1024 - names, err1 := fd.Readdirnames(request) - - // Removing files from the directory may have caused - // the OS to reshuffle it. Simply calling Readdirnames - // again may skip some entries. The only reliable way - // to avoid this is to close and re-open the - // directory. See issue 20841. - fd.Close() - - for _, name := range names { - err1 := RemoveAll(path + string(PathSeparator) + name) - if err == nil { - err = err1 - } - } - - if err1 == io.EOF { - break - } - // If Readdirnames returned an error, use it. - if err == nil { - err = err1 - } - if len(names) == 0 { - break - } - - // We don't want to re-open unnecessarily, so if we - // got fewer than request names from Readdirnames, try - // simply removing the directory now. If that - // succeeds, we are done. - if len(names) < request { - err1 := Remove(path) - if err1 == nil || IsNotExist(err1) { - return nil - } - - if err != nil { - // We got some error removing the - // directory contents, and since we - // read fewer names than we requested - // there probably aren't more files to - // remove. Don't loop around to read - // the directory again. We'll probably - // just get the same error. - return err - } - } - } - - // Remove directory. - err1 := Remove(path) - if err1 == nil || IsNotExist(err1) { - return nil - } - if err == nil { - err = err1 - } - return err -} diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go deleted file mode 100644 index 0da5d772d1..0000000000 --- a/src/os/removeall_test.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2018 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 os_test - -import ( - "fmt" - "io/ioutil" - . "os" - "runtime" - "strings" - "testing" -) - -func TestRemoveAll(t *testing.T) { - tmpDir := TempDir() - // Work directory. - file := "file" - path := tmpDir + "/_TestRemoveAll_" - fpath := path + "/file" - dpath := path + "/dir" - - // Make a regular file and remove - fd, err := Create(file) - if err != nil { - t.Fatalf("create %q: %s", file, err) - } - fd.Close() - if err = RemoveAll(file); err != nil { - t.Fatalf("RemoveAll %q (first): %s", file, err) - } - if _, err = Lstat(file); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file) - } - - // Make directory with 1 file and remove. - if err := MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", path, err) - } - fd, err = Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q (second): %s", path, err) - } - if _, err = Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) - } - - // Make directory with file and subdirectory and remove. - if err = MkdirAll(dpath, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", dpath, err) - } - fd, err = Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - fd, err = Create(dpath + "/file") - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q (third): %s", path, err) - } - if _, err := Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path) - } - - // Determine if we should run the following test. - testit := true - if runtime.GOOS == "windows" { - // Chmod is not supported under windows. - testit = false - } else { - // Test fails as root. - testit = Getuid() != 0 - } - if testit { - // Make directory with file and subdirectory and trigger error. - if err = MkdirAll(dpath, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", dpath, err) - } - - for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { - fd, err = Create(s) - if err != nil { - t.Fatalf("create %q: %s", s, err) - } - fd.Close() - } - if err = Chmod(dpath, 0); err != nil { - t.Fatalf("Chmod %q 0: %s", dpath, err) - } - - // No error checking here: either RemoveAll - // will or won't be able to remove dpath; - // either way we want to see if it removes fpath - // and path/zzz. Reasons why RemoveAll might - // succeed in removing dpath as well include: - // * running as root - // * running on a file system without permissions (FAT) - RemoveAll(path) - Chmod(dpath, 0777) - - for _, s := range []string{fpath, path + "/zzz"} { - if _, err = Lstat(s); err == nil { - t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) - } - } - } - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) - } - if _, err = Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) - } -} - -// Test RemoveAll on a large directory. -func TestRemoveAllLarge(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - - tmpDir := TempDir() - // Work directory. - path := tmpDir + "/_TestRemoveAllLarge_" - - // Make directory with 1000 files and remove. - if err := MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", path, err) - } - for i := 0; i < 1000; i++ { - fpath := fmt.Sprintf("%s/file%d", path, i) - fd, err := Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - } - if err := RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q: %s", path, err) - } - if _, err := Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll", path) - } -} - -func TestRemoveAllLongPath(t *testing.T) { - switch runtime.GOOS { - case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly": - break - default: - t.Skip("skipping for not implemented platforms") - } - - prevDir, err := Getwd() - if err != nil { - t.Fatalf("Could not get wd: %s", err) - } - - startPath, err := ioutil.TempDir("", "TestRemoveAllLongPath-") - if err != nil { - t.Fatalf("Could not create TempDir: %s", err) - } - err = Chdir(startPath) - if err != nil { - t.Fatalf("Could not chdir %s: %s", startPath, err) - } - - // Removing paths with over 4096 chars commonly fails - for i := 0; i < 41; i++ { - name := strings.Repeat("a", 100) - - err = Mkdir(name, 0755) - if err != nil { - t.Fatalf("Could not mkdir %s: %s", name, err) - } - - err = Chdir(name) - if err != nil { - t.Fatalf("Could not chdir %s: %s", name, err) - } - } - - err = Chdir(prevDir) - if err != nil { - t.Fatalf("Could not chdir %s: %s", prevDir, err) - } - - err = RemoveAll(startPath) - if err != nil { - t.Errorf("RemoveAll could not remove long file path %s: %s", startPath, err) - } -} - -func TestRemoveAllDot(t *testing.T) { - switch runtime.GOOS { - case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly": - break - default: - t.Skip("skipping for not implemented platforms") - } - - prevDir, err := Getwd() - if err != nil { - t.Fatalf("Could not get wd: %s", err) - } - tempDir, err := ioutil.TempDir("", "TestRemoveAllDot-") - if err != nil { - t.Fatalf("Could not create TempDir: %s", err) - } - err = Chdir(tempDir) - if err != nil { - t.Fatalf("Could not chdir to tempdir: %s", err) - } - - err = RemoveAll(".") - if err == nil { - t.Errorf("RemoveAll succeed to remove .") - } - - err = RemoveAll("..") - if err == nil { - t.Errorf("RemoveAll succeed to remove ..") - } - - err = Chdir(prevDir) - if err != nil { - t.Fatalf("Could not chdir %s: %s", prevDir, err) - } -} From 5168fcf63f5001b38f9ac64ce5c5e3c2d397363d Mon Sep 17 00:00:00 2001 From: templexxx Date: Sat, 21 Jul 2018 04:18:08 +0800 Subject: [PATCH 16/76] crypto/cipher: use SIMD for xor on amd64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz Benchmark: xor name old time/op new time/op delta XORBytes/8Bytes-8 8.21ns ± 1% 6.35ns ± 3% -22.66% (p=0.008 n=5+5) XORBytes/128Bytes-8 17.9ns ± 1% 10.4ns ± 1% -41.68% (p=0.008 n=5+5) XORBytes/2048Bytes-8 187ns ± 1% 78ns ± 0% -58.44% (p=0.008 n=5+5) XORBytes/32768Bytes-8 2.87µs ± 1% 1.38µs ± 0% -52.05% (p=0.008 n=5+5) name old speed new speed delta XORBytes/8Bytes-8 974MB/s ± 1% 1260MB/s ± 2% +29.33% (p=0.008 n=5+5) XORBytes/128Bytes-8 7.15GB/s ± 0% 12.25GB/s ± 1% +71.17% (p=0.008 n=5+5) XORBytes/2048Bytes-8 10.9GB/s ± 1% 26.4GB/s ± 0% +140.99% (p=0.008 n=5+5) XORBytes/32768Bytes-8 11.4GB/s ± 1% 23.8GB/s ± 0% +108.52% (p=0.008 n=5+5) Benchmark: cipher name old time/op new time/op delta AESGCMSeal1K-8 269ns ± 6% 261ns ± 2% ~ (p=0.246 n=5+5) AESGCMOpen1K-8 242ns ± 1% 240ns ± 2% ~ (p=0.190 n=5+5) AESGCMSign8K-8 869ns ± 0% 870ns ± 1% ~ (p=0.683 n=5+5) AESGCMSeal8K-8 1.64µs ± 6% 1.59µs ± 7% ~ (p=0.151 n=5+5) AESGCMOpen8K-8 1.48µs ± 2% 1.46µs ± 0% -1.39% (p=0.008 n=5+5) AESCFBEncrypt1K-8 1.88µs ± 5% 1.62µs ± 1% -13.52% (p=0.008 n=5+5) AESCFBDecrypt1K-8 1.76µs ± 1% 1.58µs ± 1% -10.24% (p=0.016 n=4+5) AESOFB1K-8 1.10µs ± 4% 1.03µs ± 2% -6.36% (p=0.008 n=5+5) AESCTR1K-8 1.24µs ± 1% 1.17µs ± 0% -5.96% (p=0.008 n=5+5) AESCBCEncrypt1K-8 1.74µs ± 0% 1.14µs ± 1% -34.36% (p=0.008 n=5+5) AESCBCDecrypt1K-8 1.28µs ± 1% 1.10µs ± 1% -14.04% (p=0.008 n=5+5) name old speed new speed delta AESGCMSeal1K-8 3.81GB/s ± 6% 3.91GB/s ± 2% ~ (p=0.310 n=5+5) AESGCMOpen1K-8 4.23GB/s ± 1% 4.27GB/s ± 2% ~ (p=0.222 n=5+5) AESGCMSign8K-8 9.43GB/s ± 0% 9.41GB/s ± 1% ~ (p=0.841 n=5+5) AESGCMSeal8K-8 5.01GB/s ± 6% 5.16GB/s ± 6% ~ (p=0.151 n=5+5) AESGCMOpen8K-8 5.54GB/s ± 2% 5.62GB/s ± 0% +1.41% (p=0.008 n=5+5) AESCFBEncrypt1K-8 543MB/s ± 5% 627MB/s ± 1% +15.55% (p=0.008 n=5+5) AESCFBDecrypt1K-8 580MB/s ± 1% 646MB/s ± 1% +11.40% (p=0.016 n=4+5) AESOFB1K-8 925MB/s ± 4% 988MB/s ± 2% +6.73% (p=0.008 n=5+5) AESCTR1K-8 821MB/s ± 1% 873MB/s ± 1% +6.34% (p=0.008 n=5+5) AESCBCEncrypt1K-8 588MB/s ± 1% 897MB/s ± 1% +52.36% (p=0.008 n=5+5) AESCBCDecrypt1K-8 799MB/s ± 1% 929MB/s ± 1% +16.32% (p=0.008 n=5+5) Change-Id: I42e6ba66c23dad853d33c924fca7b0ed805cefdd Reviewed-on: https://go-review.googlesource.com/c/125316 Reviewed-by: Ilya Tocar Run-TryBot: Ilya Tocar TryBot-Result: Gobot Gobot --- src/crypto/cipher/export_test.go | 8 +++ src/crypto/cipher/xor_amd64.go | 27 ++++++++ src/crypto/cipher/xor_amd64.s | 54 ++++++++++++++++ src/crypto/cipher/{xor.go => xor_generic.go} | 62 +++++++++--------- src/crypto/cipher/xor_test.go | 68 ++++++++++++++++---- 5 files changed, 177 insertions(+), 42 deletions(-) create mode 100644 src/crypto/cipher/export_test.go create mode 100644 src/crypto/cipher/xor_amd64.go create mode 100644 src/crypto/cipher/xor_amd64.s rename src/crypto/cipher/{xor.go => xor_generic.go} (75%) diff --git a/src/crypto/cipher/export_test.go b/src/crypto/cipher/export_test.go new file mode 100644 index 0000000000..cf8007ab49 --- /dev/null +++ b/src/crypto/cipher/export_test.go @@ -0,0 +1,8 @@ +// Copyright 2018 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 cipher + +// Export internal functions for testing. +var XorBytes = xorBytes diff --git a/src/crypto/cipher/xor_amd64.go b/src/crypto/cipher/xor_amd64.go new file mode 100644 index 0000000000..a595acc017 --- /dev/null +++ b/src/crypto/cipher/xor_amd64.go @@ -0,0 +1,27 @@ +// Copyright 2018 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 cipher + +// xorBytes xors the bytes in a and b. The destination should have enough +// space, otherwise xorBytes will panic. Returns the number of bytes xor'd. +func xorBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + if n == 0 { + return 0 + } + _ = dst[n-1] + xorBytesSSE2(&dst[0], &a[0], &b[0], n) // amd64 must have SSE2 + return n +} + +func xorWords(dst, a, b []byte) { + xorBytes(dst, a, b) +} + +//go:noescape +func xorBytesSSE2(dst, a, b *byte, n int) diff --git a/src/crypto/cipher/xor_amd64.s b/src/crypto/cipher/xor_amd64.s new file mode 100644 index 0000000000..780d37a06e --- /dev/null +++ b/src/crypto/cipher/xor_amd64.s @@ -0,0 +1,54 @@ +// Copyright 2018 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. + +#include "textflag.h" + +// func xorBytesSSE2(dst, a, b *byte, n int) +TEXT ·xorBytesSSE2(SB), NOSPLIT, $0 + MOVQ dst+0(FP), BX + MOVQ a+8(FP), SI + MOVQ b+16(FP), CX + MOVQ n+24(FP), DX + TESTQ $15, DX // AND 15 & len, if not zero jump to not_aligned. + JNZ not_aligned + +aligned: + MOVQ $0, AX // position in slices + +loop16b: + MOVOU (SI)(AX*1), X0 // XOR 16byte forwards. + MOVOU (CX)(AX*1), X1 + PXOR X1, X0 + MOVOU X0, (BX)(AX*1) + ADDQ $16, AX + CMPQ DX, AX + JNE loop16b + RET + +loop_1b: + SUBQ $1, DX // XOR 1byte backwards. + MOVB (SI)(DX*1), DI + MOVB (CX)(DX*1), AX + XORB AX, DI + MOVB DI, (BX)(DX*1) + TESTQ $7, DX // AND 7 & len, if not zero jump to loop_1b. + JNZ loop_1b + CMPQ DX, $0 // if len is 0, ret. + JE ret + TESTQ $15, DX // AND 15 & len, if zero jump to aligned. + JZ aligned + +not_aligned: + TESTQ $7, DX // AND $7 & len, if not zero jump to loop_1b. + JNE loop_1b + SUBQ $8, DX // XOR 8bytes backwards. + MOVQ (SI)(DX*1), DI + MOVQ (CX)(DX*1), AX + XORQ AX, DI + MOVQ DI, (BX)(DX*1) + CMPQ DX, $16 // if len is greater or equal 16 here, it must be aligned. + JGE aligned + +ret: + RET diff --git a/src/crypto/cipher/xor.go b/src/crypto/cipher/xor_generic.go similarity index 75% rename from src/crypto/cipher/xor.go rename to src/crypto/cipher/xor_generic.go index 5b26eace09..4d660b0a75 100644 --- a/src/crypto/cipher/xor.go +++ b/src/crypto/cipher/xor_generic.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build !amd64 + package cipher import ( @@ -9,12 +11,9 @@ import ( "unsafe" ) -const wordSize = int(unsafe.Sizeof(uintptr(0))) -const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" - -// fastXORBytes xors in bulk. It only works on architectures that -// support unaligned read/writes. -func fastXORBytes(dst, a, b []byte) int { +// xorBytes xors the bytes in a and b. The destination should have enough +// space, otherwise xorBytes will panic. Returns the number of bytes xor'd. +func xorBytes(dst, a, b []byte) int { n := len(a) if len(b) < n { n = len(b) @@ -22,6 +21,28 @@ func fastXORBytes(dst, a, b []byte) int { if n == 0 { return 0 } + + switch { + case supportsUnaligned: + fastXORBytes(dst, a, b, n) + default: + // TODO(hanwen): if (dst, a, b) have common alignment + // we could still try fastXORBytes. It is not clear + // how often this happens, and it's only worth it if + // the block encryption itself is hardware + // accelerated. + safeXORBytes(dst, a, b, n) + } + return n +} + +const wordSize = int(unsafe.Sizeof(uintptr(0))) +const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" + +// fastXORBytes xors in bulk. It only works on architectures that +// support unaligned read/writes. +// n needs to be smaller or equal than the length of a and b. +func fastXORBytes(dst, a, b []byte, n int) { // Assert dst has enough space _ = dst[n-1] @@ -38,34 +59,13 @@ func fastXORBytes(dst, a, b []byte) int { for i := (n - n%wordSize); i < n; i++ { dst[i] = a[i] ^ b[i] } - - return n } -func safeXORBytes(dst, a, b []byte) int { - n := len(a) - if len(b) < n { - n = len(b) - } +// n needs to be smaller or equal than the length of a and b. +func safeXORBytes(dst, a, b []byte, n int) { for i := 0; i < n; i++ { dst[i] = a[i] ^ b[i] } - return n -} - -// xorBytes xors the bytes in a and b. The destination should have enough -// space, otherwise xorBytes will panic. Returns the number of bytes xor'd. -func xorBytes(dst, a, b []byte) int { - if supportsUnaligned { - return fastXORBytes(dst, a, b) - } else { - // TODO(hanwen): if (dst, a, b) have common alignment - // we could still try fastXORBytes. It is not clear - // how often this happens, and it's only worth it if - // the block encryption itself is hardware - // accelerated. - return safeXORBytes(dst, a, b) - } } // fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.) @@ -80,10 +80,12 @@ func fastXORWords(dst, a, b []byte) { } } +// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.) +// The slice arguments a and b are assumed to be of equal length. func xorWords(dst, a, b []byte) { if supportsUnaligned { fastXORWords(dst, a, b) } else { - safeXORBytes(dst, a, b) + safeXORBytes(dst, a, b, len(b)) } } diff --git a/src/crypto/cipher/xor_test.go b/src/crypto/cipher/xor_test.go index d9187eb726..24877efc36 100644 --- a/src/crypto/cipher/xor_test.go +++ b/src/crypto/cipher/xor_test.go @@ -2,27 +2,71 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package cipher +package cipher_test import ( "bytes" + "crypto/cipher" + "crypto/rand" + "fmt" + "io" "testing" ) func TestXOR(t *testing.T) { - for alignP := 0; alignP < 2; alignP++ { - for alignQ := 0; alignQ < 2; alignQ++ { - for alignD := 0; alignD < 2; alignD++ { - p := make([]byte, 1024)[alignP:] - q := make([]byte, 1024)[alignQ:] - d1 := make([]byte, 1024+alignD)[alignD:] - d2 := make([]byte, 1024+alignD)[alignD:] - xorBytes(d1, p, q) - safeXORBytes(d2, p, q) - if !bytes.Equal(d1, d2) { - t.Error("not equal") + for j := 1; j <= 1024; j++ { + for alignP := 0; alignP < 2; alignP++ { + for alignQ := 0; alignQ < 2; alignQ++ { + for alignD := 0; alignD < 2; alignD++ { + p := make([]byte, j)[alignP:] + q := make([]byte, j)[alignQ:] + d1 := make([]byte, j+alignD)[alignD:] + d2 := make([]byte, j+alignD)[alignD:] + if _, err := io.ReadFull(rand.Reader, p); err != nil { + t.Fatal(err) + } + if _, err := io.ReadFull(rand.Reader, q); err != nil { + t.Fatal(err) + } + cipher.XorBytes(d1, p, q) + n := min(p, q) + for i := 0; i < n; i++ { + d2[i] = p[i] ^ q[i] + } + if !bytes.Equal(d1, d2) { + t.Logf("p: %#v", p) + t.Logf("q: %#v", q) + t.Logf("expect: %#v", d2) + t.Logf("result: %#v", d1) + t.Fatal("not equal") + } } } } } } + +func min(a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + return n +} + +func BenchmarkXORBytes(b *testing.B) { + dst := make([]byte, 1<<15) + data0 := make([]byte, 1<<15) + data1 := make([]byte, 1<<15) + sizes := []int64{1 << 3, 1 << 7, 1 << 11, 1 << 15} + for _, size := range sizes { + b.Run(fmt.Sprintf("%dBytes", size), func(b *testing.B) { + s0 := data0[:size] + s1 := data1[:size] + b.SetBytes(int64(size)) + for i := 0; i < b.N; i++ { + cipher.XorBytes(dst, s0, s1) + } + }) + } +} From f14067f3c10e15343f29aed439ff60af856eb323 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 29 Oct 2018 17:02:42 -0700 Subject: [PATCH 17/76] cmd/compile: when comparing 0-size types, make sure expr side-effects survive Fixes #23837 Change-Id: I53f524d87946a0065f28a4ddbe47b40f2b43c459 Reviewed-on: https://go-review.googlesource.com/c/145757 Run-TryBot: Keith Randall Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/walk.go | 8 ++++ test/fixedbugs/issue23837.go | 70 +++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 test/fixedbugs/issue23837.go diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index c0fb5bfd28..0e07efa0d9 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -3297,6 +3297,14 @@ func walkcompare(n *Node, init *Nodes) *Node { } if expr == nil { expr = nodbool(n.Op == OEQ) + // We still need to use cmpl and cmpr, in case they contain + // an expression which might panic. See issue 23837. + t := temp(cmpl.Type) + a1 := nod(OAS, t, cmpl) + a1 = typecheck(a1, Etop) + a2 := nod(OAS, t, cmpr) + a2 = typecheck(a2, Etop) + init.Append(a1, a2) } n = finishcompare(n, expr, init) return n diff --git a/test/fixedbugs/issue23837.go b/test/fixedbugs/issue23837.go new file mode 100644 index 0000000000..7ad50837f4 --- /dev/null +++ b/test/fixedbugs/issue23837.go @@ -0,0 +1,70 @@ +// run + +// Copyright 2018 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 main + +//go:noinline +func f(p, q *struct{}) bool { + return *p == *q +} + +type T struct { + x struct{} + y int +} + +//go:noinline +func g(p, q *T) bool { + return p.x == q.x +} + +//go:noinline +func h(p, q func() struct{}) bool { + return p() == q() +} + +func fi(p, q *struct{}) bool { + return *p == *q +} + +func gi(p, q *T) bool { + return p.x == q.x +} + +func hi(p, q func() struct{}) bool { + return p() == q() +} + +func main() { + shouldPanic(func() { f(nil, nil) }) + shouldPanic(func() { g(nil, nil) }) + shouldPanic(func() { h(nil, nil) }) + shouldPanic(func() { fi(nil, nil) }) + shouldPanic(func() { gi(nil, nil) }) + shouldPanic(func() { hi(nil, nil) }) + n := 0 + inc := func() struct{} { + n++ + return struct{}{} + } + h(inc, inc) + if n != 2 { + panic("inc not called") + } + hi(inc, inc) + if n != 4 { + panic("inc not called") + } +} + +func shouldPanic(x func()) { + defer func() { + if recover() == nil { + panic("did not panic") + } + }() + x() +} From 56b7c61c5e88e684c1bdb3b9ae61dadbeda96fd0 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 30 Oct 2018 10:56:02 -0700 Subject: [PATCH 18/76] strings: declare IndexByte as noescape This lets []byte->string conversions which are used as arguments to strings.IndexByte and friends have their backing store allocated on the stack. It only prevents allocation when the string is small enough (32 bytes), so it isn't perfect. But reusing the []byte backing store directly requires a bunch more compiler analysis (see #2205 and related issues). Fixes #25864. Change-Id: Ie52430422196e3c91e5529d6e56a8435ced1fc4c Reviewed-on: https://go-review.googlesource.com/c/146018 Reviewed-by: Brad Fitzpatrick --- src/runtime/string_test.go | 28 ++++++++++++++++++++++++++++ src/strings/strings_decl.go | 2 ++ 2 files changed, 30 insertions(+) diff --git a/src/runtime/string_test.go b/src/runtime/string_test.go index 678ff00363..a1716fa32f 100644 --- a/src/runtime/string_test.go +++ b/src/runtime/string_test.go @@ -240,6 +240,34 @@ func TestCompareTempString(t *testing.T) { } } +func TestStringIndexHaystack(t *testing.T) { + // See issue 25864. + haystack := []byte("hello") + needle := "ll" + n := testing.AllocsPerRun(1000, func() { + if strings.Index(string(haystack), needle) != 2 { + t.Fatalf("needle not found") + } + }) + if n != 0 { + t.Fatalf("want 0 allocs, got %v", n) + } +} + +func TestStringIndexNeedle(t *testing.T) { + // See issue 25864. + haystack := "hello" + needle := []byte("ll") + n := testing.AllocsPerRun(1000, func() { + if strings.Index(haystack, string(needle)) != 2 { + t.Fatalf("needle not found") + } + }) + if n != 0 { + t.Fatalf("want 0 allocs, got %v", n) + } +} + func TestStringOnStack(t *testing.T) { s := "" for i := 0; i < 3; i++ { diff --git a/src/strings/strings_decl.go b/src/strings/strings_decl.go index 98194445e1..6718c3ace4 100644 --- a/src/strings/strings_decl.go +++ b/src/strings/strings_decl.go @@ -4,5 +4,7 @@ package strings +//go:noescape + // IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s. func IndexByte(s string, c byte) int // in internal/bytealg From 0ad332d80cf3e2bbeef5f1e5f5eb50272bbde92e Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 30 Oct 2018 12:58:50 -0700 Subject: [PATCH 19/76] cmd/compile: implement some moves using non-overlapping reads&writes For moves >8,<16 bytes, do a move using non-overlapping loads/stores if it would require no more instructions. This helps a bit with the case when the move is from a static constant, because then the code to materialize the value being moved is smaller. Change-Id: Ie47a5a7c654afeb4973142b0a9922faea13c9b54 Reviewed-on: https://go-review.googlesource.com/c/146019 Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot Reviewed-by: Cherry Zhang --- src/cmd/compile/internal/ssa/gen/AMD64.rules | 11 +- src/cmd/compile/internal/ssa/rewriteAMD64.go | 102 ++++++++++++++++++- test/codegen/strings.go | 12 +++ 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 3a40d98495..6b2d3f77cf 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -286,7 +286,16 @@ (Move [7] dst src mem) -> (MOVLstore [3] dst (MOVLload [3] src mem) (MOVLstore dst (MOVLload src mem) mem)) -(Move [s] dst src mem) && s > 8 && s < 16 -> +(Move [9] dst src mem) -> + (MOVBstore [8] dst (MOVBload [8] src mem) + (MOVQstore dst (MOVQload src mem) mem)) +(Move [10] dst src mem) -> + (MOVWstore [8] dst (MOVWload [8] src mem) + (MOVQstore dst (MOVQload src mem) mem)) +(Move [12] dst src mem) -> + (MOVLstore [8] dst (MOVLload [8] src mem) + (MOVQstore dst (MOVQload src mem) mem)) +(Move [s] dst src mem) && s == 11 || s >= 13 && s <= 15 -> (MOVQstore [s-8] dst (MOVQload [s-8] src mem) (MOVQstore dst (MOVQload src mem) mem)) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 3ac860c1a2..43d77c97a4 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -932,7 +932,7 @@ func rewriteValueAMD64(v *Value) bool { case OpMod8u: return rewriteValueAMD64_OpMod8u_0(v) case OpMove: - return rewriteValueAMD64_OpMove_0(v) || rewriteValueAMD64_OpMove_10(v) + return rewriteValueAMD64_OpMove_0(v) || rewriteValueAMD64_OpMove_10(v) || rewriteValueAMD64_OpMove_20(v) case OpMul16: return rewriteValueAMD64_OpMul16_0(v) case OpMul32: @@ -62736,8 +62736,95 @@ func rewriteValueAMD64_OpMove_10(v *Value) bool { v.AddArg(v1) return true } + // match: (Move [9] dst src mem) + // cond: + // result: (MOVBstore [8] dst (MOVBload [8] src mem) (MOVQstore dst (MOVQload src mem) mem)) + for { + if v.AuxInt != 9 { + break + } + _ = v.Args[2] + dst := v.Args[0] + src := v.Args[1] + mem := v.Args[2] + v.reset(OpAMD64MOVBstore) + v.AuxInt = 8 + v.AddArg(dst) + v0 := b.NewValue0(v.Pos, OpAMD64MOVBload, typ.UInt8) + v0.AuxInt = 8 + v0.AddArg(src) + v0.AddArg(mem) + v.AddArg(v0) + v1 := b.NewValue0(v.Pos, OpAMD64MOVQstore, types.TypeMem) + v1.AddArg(dst) + v2 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v2.AddArg(src) + v2.AddArg(mem) + v1.AddArg(v2) + v1.AddArg(mem) + v.AddArg(v1) + return true + } + // match: (Move [10] dst src mem) + // cond: + // result: (MOVWstore [8] dst (MOVWload [8] src mem) (MOVQstore dst (MOVQload src mem) mem)) + for { + if v.AuxInt != 10 { + break + } + _ = v.Args[2] + dst := v.Args[0] + src := v.Args[1] + mem := v.Args[2] + v.reset(OpAMD64MOVWstore) + v.AuxInt = 8 + v.AddArg(dst) + v0 := b.NewValue0(v.Pos, OpAMD64MOVWload, typ.UInt16) + v0.AuxInt = 8 + v0.AddArg(src) + v0.AddArg(mem) + v.AddArg(v0) + v1 := b.NewValue0(v.Pos, OpAMD64MOVQstore, types.TypeMem) + v1.AddArg(dst) + v2 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v2.AddArg(src) + v2.AddArg(mem) + v1.AddArg(v2) + v1.AddArg(mem) + v.AddArg(v1) + return true + } + // match: (Move [12] dst src mem) + // cond: + // result: (MOVLstore [8] dst (MOVLload [8] src mem) (MOVQstore dst (MOVQload src mem) mem)) + for { + if v.AuxInt != 12 { + break + } + _ = v.Args[2] + dst := v.Args[0] + src := v.Args[1] + mem := v.Args[2] + v.reset(OpAMD64MOVLstore) + v.AuxInt = 8 + v.AddArg(dst) + v0 := b.NewValue0(v.Pos, OpAMD64MOVLload, typ.UInt32) + v0.AuxInt = 8 + v0.AddArg(src) + v0.AddArg(mem) + v.AddArg(v0) + v1 := b.NewValue0(v.Pos, OpAMD64MOVQstore, types.TypeMem) + v1.AddArg(dst) + v2 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v2.AddArg(src) + v2.AddArg(mem) + v1.AddArg(v2) + v1.AddArg(mem) + v.AddArg(v1) + return true + } // match: (Move [s] dst src mem) - // cond: s > 8 && s < 16 + // cond: s == 11 || s >= 13 && s <= 15 // result: (MOVQstore [s-8] dst (MOVQload [s-8] src mem) (MOVQstore dst (MOVQload src mem) mem)) for { s := v.AuxInt @@ -62745,7 +62832,7 @@ func rewriteValueAMD64_OpMove_10(v *Value) bool { dst := v.Args[0] src := v.Args[1] mem := v.Args[2] - if !(s > 8 && s < 16) { + if !(s == 11 || s >= 13 && s <= 15) { break } v.reset(OpAMD64MOVQstore) @@ -62830,6 +62917,15 @@ func rewriteValueAMD64_OpMove_10(v *Value) bool { v.AddArg(v2) return true } + return false +} +func rewriteValueAMD64_OpMove_20(v *Value) bool { + b := v.Block + _ = b + config := b.Func.Config + _ = config + typ := &b.Func.Config.Types + _ = typ // match: (Move [s] dst src mem) // cond: s > 16 && s%16 != 0 && s%16 > 8 && !config.useSSE // result: (Move [s-s%16] (OffPtr dst [s%16]) (OffPtr src [s%16]) (MOVQstore [8] dst (MOVQload [8] src mem) (MOVQstore dst (MOVQload src mem) mem))) diff --git a/test/codegen/strings.go b/test/codegen/strings.go index 39ee2e8b9f..d688b6cbf9 100644 --- a/test/codegen/strings.go +++ b/test/codegen/strings.go @@ -44,6 +44,18 @@ func ConstantLoad() { // 386:`MOVL\t\$858927408, \(`,`DUFFCOPY` // arm64:`MOVD\t\$3978425819141910832`,`MOVD\t\$1650538808`,`MOVD\t\$25699`,`MOVD\t\$101` bsink = []byte("0123456789abcde") + + // 56 = 0x38 + // amd64:`MOVQ\t\$3978425819141910832`,`MOVB\t\$56` + bsink = []byte("012345678") + + // 14648 = 0x3938 + // amd64:`MOVQ\t\$3978425819141910832`,`MOVW\t\$14648` + bsink = []byte("0123456789") + + // 1650538808 = 0x62613938 + // amd64:`MOVQ\t\$3978425819141910832`,`MOVL\t\$1650538808` + bsink = []byte("0123456789ab") } var bsink []byte From faafcc606e9b2e19bd8d9829aa55603cb291b1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Mon, 29 Oct 2018 13:39:53 +0100 Subject: [PATCH 20/76] cmd: allow build with gccgo on AIX This commit adapts cmd/internal/buildid and cmd/go to allow the use of gccgo on AIX. Buildid is supported only for AIX archives. Change-Id: I14c790a8994ae8d2ee629d8751e04189c30ffd94 Reviewed-on: https://go-review.googlesource.com/c/145417 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/cmd/go/internal/work/buildid.go | 13 +++-- src/cmd/go/internal/work/exec.go | 8 ++- src/cmd/go/internal/work/gccgo.go | 38 ++++++++++--- src/cmd/internal/buildid/buildid.go | 83 +++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 14 deletions(-) diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go index af3183ae9a..a6cfb50558 100644 --- a/src/cmd/go/internal/work/buildid.go +++ b/src/cmd/go/internal/work/buildid.go @@ -322,13 +322,16 @@ func assemblerIsGas() bool { } } -// gccgoBuildIDELFFile creates an assembler file that records the -// action's build ID in an SHF_EXCLUDE section. -func (b *Builder) gccgoBuildIDELFFile(a *Action) (string, error) { +// gccgoBuildIDFile creates an assembler file that records the +// action's build ID in an SHF_EXCLUDE section for ELF files or +// in a CSECT in XCOFF files. +func (b *Builder) gccgoBuildIDFile(a *Action) (string, error) { sfile := a.Objdir + "_buildid.s" var buf bytes.Buffer - if cfg.Goos != "solaris" || assemblerIsGas() { + if cfg.Goos == "aix" { + fmt.Fprintf(&buf, "\t.csect .go.buildid[XO]\n") + } else if cfg.Goos != "solaris" || assemblerIsGas() { fmt.Fprintf(&buf, "\t"+`.section .go.buildid,"e"`+"\n") } else if cfg.Goarch == "sparc" || cfg.Goarch == "sparc64" { fmt.Fprintf(&buf, "\t"+`.section ".go.buildid",#exclude`+"\n") @@ -347,7 +350,7 @@ func (b *Builder) gccgoBuildIDELFFile(a *Action) (string, error) { fmt.Fprintf(&buf, "%#02x", a.buildID[i]) } fmt.Fprintf(&buf, "\n") - if cfg.Goos != "solaris" { + if cfg.Goos != "solaris" && cfg.Goos != "aix" { secType := "@progbits" if cfg.Goarch == "arm" { secType = "%progbits" diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 99a500f11f..d9c59aab80 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -699,8 +699,8 @@ func (b *Builder) build(a *Action) (err error) { // This is read by readGccgoArchive in cmd/internal/buildid/buildid.go. if a.buildID != "" && cfg.BuildToolchainName == "gccgo" { switch cfg.Goos { - case "android", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": - asmfile, err := b.gccgoBuildIDELFFile(a) + case "aix", "android", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": + asmfile, err := b.gccgoBuildIDFile(a) if err != nil { return err } @@ -2297,6 +2297,10 @@ func (b *Builder) gccArchArgs() []string { return []string{"-mabi=64"} case "mips", "mipsle": return []string{"-mabi=32", "-march=mips32"} + case "ppc64": + if cfg.Goos == "aix" { + return []string{"-maix64"} + } } return nil } diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go index 91daf529d4..ca3be4fd36 100644 --- a/src/cmd/go/internal/work/gccgo.go +++ b/src/cmd/go/internal/work/gccgo.go @@ -186,7 +186,15 @@ func (gccgoToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) for _, f := range ofiles { absOfiles = append(absOfiles, mkAbs(objdir, f)) } - return b.run(a, p.Dir, p.ImportPath, nil, "ar", "rc", mkAbs(objdir, afile), absOfiles) + var arArgs string + if cfg.Goos == "aix" && cfg.Goarch == "ppc64" { + // AIX puts both 32-bit and 64-bit objects in the same archive. + // Tell the AIX "ar" command to only care about 64-bit objects. + // AIX "ar" command does not know D option. + arArgs = "-X64" + } + + return b.run(a, p.Dir, p.ImportPath, nil, "ar", arArgs, "rc", mkAbs(objdir, afile), absOfiles) } func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string, allactions []*Action, buildmode, desc string) error { @@ -342,17 +350,24 @@ func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string } } - ldflags = append(ldflags, "-Wl,--whole-archive") + wholeArchive := []string{"-Wl,--whole-archive"} + noWholeArchive := []string{"-Wl,--no-whole-archive"} + if cfg.Goos == "aix" { + wholeArchive = nil + noWholeArchive = nil + } + ldflags = append(ldflags, wholeArchive...) ldflags = append(ldflags, afiles...) - ldflags = append(ldflags, "-Wl,--no-whole-archive") + ldflags = append(ldflags, noWholeArchive...) ldflags = append(ldflags, cgoldflags...) ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...) if root.Package != nil { ldflags = append(ldflags, root.Package.CgoLDFLAGS...) } - - ldflags = str.StringList("-Wl,-(", ldflags, "-Wl,-)") + if cfg.Goos != "aix" { + ldflags = str.StringList("-Wl,-(", ldflags, "-Wl,-)") + } if root.buildID != "" { // On systems that normally use gold or the GNU linker, @@ -363,11 +378,17 @@ func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string } } + var rLibPath string + if cfg.Goos == "aix" { + rLibPath = "-Wl,-blibpath=" + } else { + rLibPath = "-Wl,-rpath=" + } for _, shlib := range shlibs { ldflags = append( ldflags, "-L"+filepath.Dir(shlib), - "-Wl,-rpath="+filepath.Dir(shlib), + rLibPath+filepath.Dir(shlib), "-l"+strings.TrimSuffix( strings.TrimPrefix(filepath.Base(shlib), "lib"), ".so")) @@ -412,7 +433,10 @@ func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string case "c-shared": ldflags = append(ldflags, "-shared", "-nostdlib", "-Wl,--whole-archive", "-lgolibbegin", "-Wl,--no-whole-archive", "-lgo", "-lgcc_s", "-lgcc", "-lc", "-lgcc") case "shared": - ldflags = append(ldflags, "-zdefs", "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc") + if cfg.Goos != "aix" { + ldflags = append(ldflags, "-zdefs") + } + ldflags = append(ldflags, "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc") default: base.Fatalf("-buildmode=%s not supported for gccgo", buildmode) diff --git a/src/cmd/internal/buildid/buildid.go b/src/cmd/internal/buildid/buildid.go index fa3d7f37ec..8205f696eb 100644 --- a/src/cmd/internal/buildid/buildid.go +++ b/src/cmd/internal/buildid/buildid.go @@ -6,6 +6,7 @@ package buildid import ( "bytes" + "cmd/internal/xcoff" "debug/elf" "fmt" "io" @@ -40,6 +41,9 @@ func ReadFile(name string) (id string, err error) { return "", err } if string(buf) != "!\n" { + if string(buf) == "\n" { + return readGccgoBigArchive(name, f) + } return readBinary(name, f) } @@ -157,6 +161,85 @@ func readGccgoArchive(name string, f *os.File) (string, error) { } } +// readGccgoBigArchive tries to parse the archive as an AIX big +// archive file, and fetch the build ID from the _buildid.o entry. +// The _buildid.o entry is written by (*Builder).gccgoBuildIDXCOFFFile +// in cmd/go/internal/work/exec.go. +func readGccgoBigArchive(name string, f *os.File) (string, error) { + bad := func() (string, error) { + return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} + } + + // Read fixed-length header. + if _, err := f.Seek(0, io.SeekStart); err != nil { + return "", err + } + var flhdr [128]byte + if _, err := io.ReadFull(f, flhdr[:]); err != nil { + return "", err + } + // Read first member offset. + offStr := strings.TrimSpace(string(flhdr[68:88])) + off, err := strconv.ParseInt(offStr, 10, 64) + if err != nil { + return bad() + } + for { + if off == 0 { + // No more entries, no build ID. + return "", nil + } + if _, err := f.Seek(off, io.SeekStart); err != nil { + return "", err + } + // Read member header. + var hdr [112]byte + if _, err := io.ReadFull(f, hdr[:]); err != nil { + return "", err + } + // Read member name length. + namLenStr := strings.TrimSpace(string(hdr[108:112])) + namLen, err := strconv.ParseInt(namLenStr, 10, 32) + if err != nil { + return bad() + } + if namLen == 10 { + var nam [10]byte + if _, err := io.ReadFull(f, nam[:]); err != nil { + return "", err + } + if string(nam[:]) == "_buildid.o" { + sizeStr := strings.TrimSpace(string(hdr[0:20])) + size, err := strconv.ParseInt(sizeStr, 10, 64) + if err != nil { + return bad() + } + off += int64(len(hdr)) + namLen + 2 + if off&1 != 0 { + off++ + } + sr := io.NewSectionReader(f, off, size) + x, err := xcoff.NewFile(sr) + if err != nil { + return bad() + } + data := x.CSect(".go.buildid") + if data == nil { + return bad() + } + return string(data), nil + } + } + + // Read next member offset. + offStr = strings.TrimSpace(string(hdr[20:40])) + off, err = strconv.ParseInt(offStr, 10, 64) + if err != nil { + return bad() + } + } +} + var ( goBuildPrefix = []byte("\xff Go build ID: \"") goBuildEnd = []byte("\"\n \xff") From 84b96c21bf226e80e46d5c22c5e42815bb392af3 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Wed, 24 Oct 2018 13:33:14 +1100 Subject: [PATCH 21/76] cmd/doc: allow -all to apply to individual items It really only matters for types, and the code already worked but was blocked by a usage check. Fixes #25595 Change-Id: I823f313b682b37616ea555aee079e2fe39f914c2 Reviewed-on: https://go-review.googlesource.com/c/144357 Reviewed-by: Ian Lance Taylor --- src/cmd/doc/doc_test.go | 20 ++++++++++++++++++++ src/cmd/doc/main.go | 5 +---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go index f8c52b1988..0761c6ddb3 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/doc/doc_test.go @@ -481,6 +481,26 @@ var tests = []test{ `unexportedTypedConstant`, // No unexported constant. }, }, + // Type -all. + { + "type", + []string{"-all", p, `ExportedType`}, + []string{ + `type ExportedType struct {`, // Type definition as source. + `Comment about exported type`, // Include comment afterwards. + `const ConstGroup4 ExportedType = ExportedType\{\}`, // Related constants. + `ExportedTypedConstant ExportedType = iota`, + `Constants tied to ExportedType`, + `func ExportedTypeConstructor\(\) \*ExportedType`, + `Comment about constructor for exported type.`, + `func ReturnExported\(\) ExportedType`, + `func \(ExportedType\) ExportedMethod\(a int\) bool`, + `Comment about exported method.`, + }, + []string{ + `unexportedType`, + }, + }, // Type T1 dump (alias). { "type T1", diff --git a/src/cmd/doc/main.go b/src/cmd/doc/main.go index 614f19438c..ec15ec5826 100644 --- a/src/cmd/doc/main.go +++ b/src/cmd/doc/main.go @@ -133,10 +133,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { } // We have a package. - if showAll { - if symbol != "" { - return fmt.Errorf("-all valid only for package, not symbol: %s", symbol) - } + if showAll && symbol == "" { pkg.allDoc() return } From fde4b9ed14e339b5064373c1d4a73e211ec32ac4 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 29 Oct 2018 13:44:44 -0700 Subject: [PATCH 22/76] cmd/compile: better documentation around checkwidth Change-Id: I5c7ec9676b5573c883c196459acea85aa9ff8130 Reviewed-on: https://go-review.googlesource.com/c/146021 Run-TryBot: Robert Griesemer Reviewed-by: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/align.go | 16 ++++++++-------- src/cmd/compile/internal/types/type.go | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/cmd/compile/internal/gc/align.go b/src/cmd/compile/internal/gc/align.go index fb761d2339..87a7de547a 100644 --- a/src/cmd/compile/internal/gc/align.go +++ b/src/cmd/compile/internal/gc/align.go @@ -208,7 +208,7 @@ func dowidth(t *types.Type) { } t.Width = -2 - t.Align = 0 + t.Align = 0 // 0 means use t.Width, below et := t.Etype switch et { @@ -222,7 +222,7 @@ func dowidth(t *types.Type) { } } - w := int64(0) + var w int64 switch et { default: Fatalf("dowidth: unknown type: %v", t) @@ -366,7 +366,7 @@ func dowidth(t *types.Type) { t.Width = w if t.Align == 0 { - if w > 8 || w&(w-1) != 0 || w == 0 { + if w == 0 || w > 8 || w&(w-1) != 0 { Fatalf("invalid alignment for %v", t) } t.Align = uint8(w) @@ -423,12 +423,11 @@ func checkwidth(t *types.Type) { return } - if t.Deferwidth() { - return + // if type has not yet been pushed on deferredTypeStack yet, do it now + if !t.Deferwidth() { + t.SetDeferwidth(true) + deferredTypeStack = append(deferredTypeStack, t) } - t.SetDeferwidth(true) - - deferredTypeStack = append(deferredTypeStack, t) } func defercheckwidth() { @@ -443,6 +442,7 @@ func resumecheckwidth() { if defercalc == 0 { Fatalf("resumecheckwidth") } + for len(deferredTypeStack) > 0 { t := deferredTypeStack[len(deferredTypeStack)-1] deferredTypeStack = deferredTypeStack[:len(deferredTypeStack)-1] diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index e6e6127405..39f4d2aa7b 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -141,7 +141,7 @@ type Type struct { Extra interface{} // Width is the width of this Type in bytes. - Width int64 + Width int64 // valid if Align > 0 methods Fields allMethods Fields @@ -156,16 +156,16 @@ type Type struct { Vargen int32 // unique name for OTYPE/ONAME Etype EType // kind of type - Align uint8 // the required alignment of this type, in bytes + Align uint8 // the required alignment of this type, in bytes (0 means Width and Align have not yet been computed) flags bitset8 } const ( - typeNotInHeap = 1 << iota // type cannot be heap allocated - typeBroke // broken type definition - typeNoalg // suppress hash and eq algorithm generation - typeDeferwidth + typeNotInHeap = 1 << iota // type cannot be heap allocated + typeBroke // broken type definition + typeNoalg // suppress hash and eq algorithm generation + typeDeferwidth // width computation has been deferred and type is on deferredTypeStack typeRecur ) From 5cc80899486027db5f0de00870f1e022e1cfb9c5 Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Fri, 26 Oct 2018 18:44:17 +1100 Subject: [PATCH 23/76] os: use Stat instead of Lstat in Symlink Windows implementation of Symlink uses CreateSymbolicLink Windows API. The API requires to identify the target type: file or directory. Current Symlink implementation uses Lstat to determine symlink type, but Lstat will not be able to determine correct result if destination is symlink. Replace Lstat call with Stat. Fixes #28432 Change-Id: Ibee6d8ac21e2246bf8d0a019c4c66d38b09887d4 Reviewed-on: https://go-review.googlesource.com/c/145217 Run-TryBot: Alex Brainman TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/os/file_windows.go | 2 +- src/os/stat_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 223698c130..7ed4fe2f38 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -362,7 +362,7 @@ func Symlink(oldname, newname string) error { destpath = dirname(newname) + `\` + oldname } - fi, err := Lstat(destpath) + fi, err := Stat(destpath) isdir := err == nil && fi.IsDir() n, err := syscall.UTF16PtrFromString(fixLongPath(newname)) diff --git a/src/os/stat_test.go b/src/os/stat_test.go index d59edeb547..da20a4fdbf 100644 --- a/src/os/stat_test.go +++ b/src/os/stat_test.go @@ -205,6 +205,14 @@ func TestDirAndSymlinkStats(t *testing.T) { } testSymlinkStats(t, dirlink, true) testSymlinkSameFile(t, dir, dirlink) + + linklink := filepath.Join(tmpdir, "linklink") + err = os.Symlink(dirlink, linklink) + if err != nil { + t.Fatal(err) + } + testSymlinkStats(t, linklink, true) + testSymlinkSameFile(t, dir, linklink) } func TestFileAndSymlinkStats(t *testing.T) { @@ -230,6 +238,14 @@ func TestFileAndSymlinkStats(t *testing.T) { } testSymlinkStats(t, filelink, false) testSymlinkSameFile(t, file, filelink) + + linklink := filepath.Join(tmpdir, "linklink") + err = os.Symlink(filelink, linklink) + if err != nil { + t.Fatal(err) + } + testSymlinkStats(t, linklink, false) + testSymlinkSameFile(t, file, linklink) } // see issue 27225 for details From 49dafc70c8558271fc2205061d07eed490f2bc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Mon, 22 Oct 2018 17:00:37 +0200 Subject: [PATCH 24/76] cmd: add XCOFF objfile and adapt cmd/nm tests This commit adds a new file format in cmd/internal/objfile for XCOFF. It also adapts tests inside cmd/nm for AIX. Updates: #25893 Change-Id: I1e55ea0b7f7d08a871343bee27d11e2d3baad254 Reviewed-on: https://go-review.googlesource.com/c/145397 Run-TryBot: Tobias Klauser TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/cmd/internal/objfile/objfile.go | 1 + src/cmd/internal/objfile/xcoff.go | 133 ++++++++++++++++++++++++++++ src/cmd/nm/nm_test.go | 37 +++++--- 3 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 src/cmd/internal/objfile/xcoff.go diff --git a/src/cmd/internal/objfile/objfile.go b/src/cmd/internal/objfile/objfile.go index 10307be072..41c5d9b9f5 100644 --- a/src/cmd/internal/objfile/objfile.go +++ b/src/cmd/internal/objfile/objfile.go @@ -61,6 +61,7 @@ var openers = []func(io.ReaderAt) (rawFile, error){ openMacho, openPE, openPlan9, + openXcoff, } // Open opens the named file. diff --git a/src/cmd/internal/objfile/xcoff.go b/src/cmd/internal/objfile/xcoff.go new file mode 100644 index 0000000000..c36b4362ba --- /dev/null +++ b/src/cmd/internal/objfile/xcoff.go @@ -0,0 +1,133 @@ +// Copyright 2018 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. + +// Parsing of XCOFF executable (AIX) + +package objfile + +import ( + "cmd/internal/xcoff" + "debug/dwarf" + "fmt" + "io" + "unicode" +) + +type xcoffFile struct { + xcoff *xcoff.File +} + +func openXcoff(r io.ReaderAt) (rawFile, error) { + f, err := xcoff.NewFile(r) + if err != nil { + return nil, err + } + return &xcoffFile{f}, nil +} + +func (f *xcoffFile) symbols() ([]Sym, error) { + var syms []Sym + for _, s := range f.xcoff.Symbols { + const ( + N_UNDEF = 0 // An undefined (extern) symbol + N_ABS = -1 // An absolute symbol (e_value is a constant, not an address) + N_DEBUG = -2 // A debugging symbol + ) + sym := Sym{Name: s.Name, Addr: s.Value, Code: '?'} + + switch s.SectionNumber { + case N_UNDEF: + sym.Code = 'U' + case N_ABS: + sym.Code = 'C' + case N_DEBUG: + sym.Code = '?' + default: + if s.SectionNumber < 0 || len(f.xcoff.Sections) < int(s.SectionNumber) { + return nil, fmt.Errorf("invalid section number in symbol table") + } + sect := f.xcoff.Sections[s.SectionNumber-1] + + // debug/xcoff returns an offset in the section not the actual address + sym.Addr += sect.VirtualAddress + + if s.AuxCSect.SymbolType&0x3 == xcoff.XTY_LD { + // The size of a function is contained in the + // AUX_FCN entry + sym.Size = s.AuxFcn.Size + } else { + sym.Size = s.AuxCSect.Length + } + + sym.Size = s.AuxCSect.Length + + switch sect.Type { + case xcoff.STYP_TEXT: + if s.AuxCSect.StorageMappingClass == xcoff.XMC_RO { + sym.Code = 'R' + } else { + sym.Code = 'T' + } + case xcoff.STYP_DATA: + sym.Code = 'D' + case xcoff.STYP_BSS: + sym.Code = 'B' + } + + if s.StorageClass == xcoff.C_HIDEXT { + // Local symbol + sym.Code = unicode.ToLower(sym.Code) + } + + } + syms = append(syms, sym) + } + + return syms, nil +} + +func (f *xcoffFile) pcln() (textStart uint64, symtab, pclntab []byte, err error) { + if sect := f.xcoff.Section(".text"); sect != nil { + textStart = sect.VirtualAddress + } + if sect := f.xcoff.Section(".gosymtab"); sect != nil { + if symtab, err = sect.Data(); err != nil { + return 0, nil, nil, err + } + } + if sect := f.xcoff.Section(".gopclntab"); sect != nil { + if pclntab, err = sect.Data(); err != nil { + return 0, nil, nil, err + } + } + return textStart, symtab, pclntab, nil +} + +func (f *xcoffFile) text() (textStart uint64, text []byte, err error) { + sect := f.xcoff.Section(".text") + if sect == nil { + return 0, nil, fmt.Errorf("text section not found") + } + textStart = sect.VirtualAddress + text, err = sect.Data() + return +} + +func (f *xcoffFile) goarch() string { + switch f.xcoff.TargetMachine { + case xcoff.U802TOCMAGIC: + return "ppc" + case xcoff.U64_TOCMAGIC: + return "ppc64" + } + return "" +} + +func (f *xcoffFile) loadAddress() (uint64, error) { + return 0, fmt.Errorf("unknown load address") +} + +func (f *xcoffFile) dwarf() (*dwarf.Data, error) { + return f.xcoff.DWARF() +} diff --git a/src/cmd/nm/nm_test.go b/src/cmd/nm/nm_test.go index ccf5682d69..87baa09d38 100644 --- a/src/cmd/nm/nm_test.go +++ b/src/cmd/nm/nm_test.go @@ -56,17 +56,18 @@ func testMain(m *testing.M) int { func TestNonGoExecs(t *testing.T) { testfiles := []string{ - "elf/testdata/gcc-386-freebsd-exec", - "elf/testdata/gcc-amd64-linux-exec", - "macho/testdata/gcc-386-darwin-exec", - "macho/testdata/gcc-amd64-darwin-exec", - // "pe/testdata/gcc-amd64-mingw-exec", // no symbols! - "pe/testdata/gcc-386-mingw-exec", - "plan9obj/testdata/amd64-plan9-exec", - "plan9obj/testdata/386-plan9-exec", + "debug/elf/testdata/gcc-386-freebsd-exec", + "debug/elf/testdata/gcc-amd64-linux-exec", + "debug/macho/testdata/gcc-386-darwin-exec", + "debug/macho/testdata/gcc-amd64-darwin-exec", + // "debug/pe/testdata/gcc-amd64-mingw-exec", // no symbols! + "debug/pe/testdata/gcc-386-mingw-exec", + "debug/plan9obj/testdata/amd64-plan9-exec", + "debug/plan9obj/testdata/386-plan9-exec", + "cmd/internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec", } for _, f := range testfiles { - exepath := filepath.Join(runtime.GOROOT(), "src", "debug", f) + exepath := filepath.Join(runtime.GOROOT(), "src", f) cmd := exec.Command(testnmpath, exepath) out, err := cmd.CombinedOutput() if err != nil { @@ -139,6 +140,20 @@ func testGoExec(t *testing.T, iscgo, isexternallinker bool) { if err != nil { t.Fatalf("go tool nm: %v\n%s", err, string(out)) } + + relocated := func(code string) bool { + if runtime.GOOS == "aix" { + // On AIX, .data and .bss addresses are changed by the loader. + // Therefore, the values returned by the exec aren't the same + // than the ones inside the symbol table. + switch code { + case "D", "d", "B", "b": + return true + } + } + return false + } + scanner := bufio.NewScanner(bytes.NewBuffer(out)) dups := make(map[string]bool) for scanner.Scan() { @@ -149,7 +164,9 @@ func testGoExec(t *testing.T, iscgo, isexternallinker bool) { name := f[2] if addr, found := names[name]; found { if want, have := addr, "0x"+f[0]; have != want { - t.Errorf("want %s address for %s symbol, but have %s", want, name, have) + if !relocated(f[1]) { + t.Errorf("want %s address for %s symbol, but have %s", want, name, have) + } } delete(names, name) } From e8ffb8a74c90e3723dd6777af2c894f39d656795 Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Wed, 31 Oct 2018 10:55:24 +0000 Subject: [PATCH 25/76] os: add support for long path names on unix RemoveAll On unix systems, long enough path names will fail when performing syscalls like `Lstat`. The current RemoveAll uses several of these syscalls, and so will fail for long paths. This can be risky, as it can let users "hide" files from the system or otherwise make long enough paths for programs to fail. By using `Unlinkat` and `Openat` syscalls instead, RemoveAll is safer on unix systems. Initially implemented for linux, darwin, dragonfly, netbsd and openbsd. Not yet implemented on freebsd due to fstatat 64-bit inode compatibility issues. Fixes #27029 Co-authored-by: Giuseppe Capizzi Co-authored-by: Julia Nedialkova Change-Id: I978a6a4986878fe076d3c7af86e7927675624a96 GitHub-Last-Rev: 9235489c81b90c228210144b7c25b28a46bb80b7 GitHub-Pull-Request: golang/go#28494 Reviewed-on: https://go-review.googlesource.com/c/146020 Run-TryBot: Tobias Klauser TryBot-Result: Gobot Gobot Reviewed-by: Tobias Klauser Reviewed-by: Ian Lance Taylor --- src/internal/syscall/unix/at.go | 58 +++++ src/internal/syscall/unix/at_sysnum_darwin.go | 12 + .../syscall/unix/at_sysnum_dragonfly.go | 14 ++ .../syscall/unix/at_sysnum_freebsd.go | 14 ++ .../syscall/unix/at_sysnum_fstatat64_linux.go | 11 + .../syscall/unix/at_sysnum_fstatat_linux.go | 11 + src/internal/syscall/unix/at_sysnum_linux.go | 13 + src/internal/syscall/unix/at_sysnum_netbsd.go | 14 ++ .../unix/at_sysnum_newfstatat_linux.go | 11 + .../syscall/unix/at_sysnum_openbsd.go | 14 ++ src/os/path.go | 99 -------- src/os/path_test.go | 125 --------- src/os/path_unix.go | 28 ++- src/os/removeall_at.go | 139 ++++++++++ src/os/removeall_noat.go | 110 ++++++++ src/os/removeall_test.go | 237 ++++++++++++++++++ 16 files changed, 685 insertions(+), 225 deletions(-) create mode 100644 src/internal/syscall/unix/at.go create mode 100644 src/internal/syscall/unix/at_sysnum_darwin.go create mode 100644 src/internal/syscall/unix/at_sysnum_dragonfly.go create mode 100644 src/internal/syscall/unix/at_sysnum_freebsd.go create mode 100644 src/internal/syscall/unix/at_sysnum_fstatat64_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_fstatat_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_netbsd.go create mode 100644 src/internal/syscall/unix/at_sysnum_newfstatat_linux.go create mode 100644 src/internal/syscall/unix/at_sysnum_openbsd.go create mode 100644 src/os/removeall_at.go create mode 100644 src/os/removeall_noat.go create mode 100644 src/os/removeall_test.go diff --git a/src/internal/syscall/unix/at.go b/src/internal/syscall/unix/at.go new file mode 100644 index 0000000000..1c05d2abe3 --- /dev/null +++ b/src/internal/syscall/unix/at.go @@ -0,0 +1,58 @@ +// Copyright 2018 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 linux darwin freebsd openbsd netbsd dragonfly + +package unix + +import ( + "syscall" + "unsafe" +) + +func Unlinkat(dirfd int, path string, flags int) error { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall(unlinkatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags)) + if errno != 0 { + return errno + } + + return nil +} + +func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + fd, _, errno := syscall.Syscall6(openatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) + if errno != 0 { + return 0, errno + } + + return int(fd), nil +} + +func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall6(fstatatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if errno != 0 { + return errno + } + + return nil + +} diff --git a/src/internal/syscall/unix/at_sysnum_darwin.go b/src/internal/syscall/unix/at_sysnum_darwin.go new file mode 100644 index 0000000000..12b7d79882 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_darwin.go @@ -0,0 +1,12 @@ +// Copyright 2018 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 unix + +const unlinkatTrap uintptr = 472 +const openatTrap uintptr = 463 +const fstatatTrap uintptr = 470 + +const AT_REMOVEDIR = 0x80 +const AT_SYMLINK_NOFOLLOW = 0x0020 diff --git a/src/internal/syscall/unix/at_sysnum_dragonfly.go b/src/internal/syscall/unix/at_sysnum_dragonfly.go new file mode 100644 index 0000000000..cec9abce6a --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_dragonfly.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x2 +const AT_SYMLINK_NOFOLLOW = 0x1 diff --git a/src/internal/syscall/unix/at_sysnum_freebsd.go b/src/internal/syscall/unix/at_sysnum_freebsd.go new file mode 100644 index 0000000000..fe45e296d7 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_freebsd.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x800 +const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go b/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go new file mode 100644 index 0000000000..c6ea206c12 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_fstatat64_linux.go @@ -0,0 +1,11 @@ +// Copyright 2018 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 arm mips mipsle 386 + +package unix + +import "syscall" + +const fstatatTrap uintptr = syscall.SYS_FSTATAT64 diff --git a/src/internal/syscall/unix/at_sysnum_fstatat_linux.go b/src/internal/syscall/unix/at_sysnum_fstatat_linux.go new file mode 100644 index 0000000000..580e7997f8 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_fstatat_linux.go @@ -0,0 +1,11 @@ +// Copyright 2018 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 unix + +import "syscall" + +const fstatatTrap uintptr = syscall.SYS_FSTATAT diff --git a/src/internal/syscall/unix/at_sysnum_linux.go b/src/internal/syscall/unix/at_sysnum_linux.go new file mode 100644 index 0000000000..fa7cd75d42 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_linux.go @@ -0,0 +1,13 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT + +const AT_REMOVEDIR = 0x200 +const AT_SYMLINK_NOFOLLOW = 0x100 diff --git a/src/internal/syscall/unix/at_sysnum_netbsd.go b/src/internal/syscall/unix/at_sysnum_netbsd.go new file mode 100644 index 0000000000..fe45e296d7 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_netbsd.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x800 +const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go b/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go new file mode 100644 index 0000000000..e76c1cbdce --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_newfstatat_linux.go @@ -0,0 +1,11 @@ +// Copyright 2018 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 amd64 mips64 mips64le ppc64 ppc64le s390x + +package unix + +import "syscall" + +const fstatatTrap uintptr = syscall.SYS_NEWFSTATAT diff --git a/src/internal/syscall/unix/at_sysnum_openbsd.go b/src/internal/syscall/unix/at_sysnum_openbsd.go new file mode 100644 index 0000000000..c2d48b9914 --- /dev/null +++ b/src/internal/syscall/unix/at_sysnum_openbsd.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +import "syscall" + +const unlinkatTrap uintptr = syscall.SYS_UNLINKAT +const openatTrap uintptr = syscall.SYS_OPENAT +const fstatatTrap uintptr = syscall.SYS_FSTATAT + +const AT_REMOVEDIR = 0x08 +const AT_SYMLINK_NOFOLLOW = 0x02 diff --git a/src/os/path.go b/src/os/path.go index cdfbc18921..e31f64c750 100644 --- a/src/os/path.go +++ b/src/os/path.go @@ -5,7 +5,6 @@ package os import ( - "io" "syscall" ) @@ -58,101 +57,3 @@ func MkdirAll(path string, perm FileMode) error { } return nil } - -// RemoveAll removes path and any children it contains. -// It removes everything it can but returns the first error -// it encounters. If the path does not exist, RemoveAll -// returns nil (no error). -func RemoveAll(path string) error { - // Simple case: if Remove works, we're done. - err := Remove(path) - if err == nil || IsNotExist(err) { - return nil - } - - // Otherwise, is this a directory we need to recurse into? - dir, serr := Lstat(path) - if serr != nil { - if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { - return nil - } - return serr - } - if !dir.IsDir() { - // Not a directory; return the error from Remove. - return err - } - - // Remove contents & return first error. - err = nil - for { - fd, err := Open(path) - if err != nil { - if IsNotExist(err) { - // Already deleted by someone else. - return nil - } - return err - } - - const request = 1024 - names, err1 := fd.Readdirnames(request) - - // Removing files from the directory may have caused - // the OS to reshuffle it. Simply calling Readdirnames - // again may skip some entries. The only reliable way - // to avoid this is to close and re-open the - // directory. See issue 20841. - fd.Close() - - for _, name := range names { - err1 := RemoveAll(path + string(PathSeparator) + name) - if err == nil { - err = err1 - } - } - - if err1 == io.EOF { - break - } - // If Readdirnames returned an error, use it. - if err == nil { - err = err1 - } - if len(names) == 0 { - break - } - - // We don't want to re-open unnecessarily, so if we - // got fewer than request names from Readdirnames, try - // simply removing the directory now. If that - // succeeds, we are done. - if len(names) < request { - err1 := Remove(path) - if err1 == nil || IsNotExist(err1) { - return nil - } - - if err != nil { - // We got some error removing the - // directory contents, and since we - // read fewer names than we requested - // there probably aren't more files to - // remove. Don't loop around to read - // the directory again. We'll probably - // just get the same error. - return err - } - } - } - - // Remove directory. - err1 := Remove(path) - if err1 == nil || IsNotExist(err1) { - return nil - } - if err == nil { - err = err1 - } - return err -} diff --git a/src/os/path_test.go b/src/os/path_test.go index f58c7e746d..6cb25bcaa7 100644 --- a/src/os/path_test.go +++ b/src/os/path_test.go @@ -5,7 +5,6 @@ package os_test import ( - "fmt" "internal/testenv" "io/ioutil" . "os" @@ -76,130 +75,6 @@ func TestMkdirAll(t *testing.T) { } } -func TestRemoveAll(t *testing.T) { - tmpDir := TempDir() - // Work directory. - path := tmpDir + "/_TestRemoveAll_" - fpath := path + "/file" - dpath := path + "/dir" - - // Make directory with 1 file and remove. - if err := MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", path, err) - } - fd, err := Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q (first): %s", path, err) - } - if _, err = Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path) - } - - // Make directory with file and subdirectory and remove. - if err = MkdirAll(dpath, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", dpath, err) - } - fd, err = Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - fd, err = Create(dpath + "/file") - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q (second): %s", path, err) - } - if _, err := Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) - } - - // Determine if we should run the following test. - testit := true - if runtime.GOOS == "windows" { - // Chmod is not supported under windows. - testit = false - } else { - // Test fails as root. - testit = Getuid() != 0 - } - if testit { - // Make directory with file and subdirectory and trigger error. - if err = MkdirAll(dpath, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", dpath, err) - } - - for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { - fd, err = Create(s) - if err != nil { - t.Fatalf("create %q: %s", s, err) - } - fd.Close() - } - if err = Chmod(dpath, 0); err != nil { - t.Fatalf("Chmod %q 0: %s", dpath, err) - } - - // No error checking here: either RemoveAll - // will or won't be able to remove dpath; - // either way we want to see if it removes fpath - // and path/zzz. Reasons why RemoveAll might - // succeed in removing dpath as well include: - // * running as root - // * running on a file system without permissions (FAT) - RemoveAll(path) - Chmod(dpath, 0777) - - for _, s := range []string{fpath, path + "/zzz"} { - if _, err = Lstat(s); err == nil { - t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) - } - } - } - if err = RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) - } - if _, err = Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) - } -} - -// Test RemoveAll on a large directory. -func TestRemoveAllLarge(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - - tmpDir := TempDir() - // Work directory. - path := tmpDir + "/_TestRemoveAllLarge_" - - // Make directory with 1000 files and remove. - if err := MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll %q: %s", path, err) - } - for i := 0; i < 1000; i++ { - fpath := fmt.Sprintf("%s/file%d", path, i) - fd, err := Create(fpath) - if err != nil { - t.Fatalf("create %q: %s", fpath, err) - } - fd.Close() - } - if err := RemoveAll(path); err != nil { - t.Fatalf("RemoveAll %q: %s", path, err) - } - if _, err := Lstat(path); err == nil { - t.Fatalf("Lstat %q succeeded after RemoveAll", path) - } -} - func TestMkdirAllWithSymlink(t *testing.T) { testenv.MustHaveSymlink(t) diff --git a/src/os/path_unix.go b/src/os/path_unix.go index 3cb0e3acc4..be373a50a9 100644 --- a/src/os/path_unix.go +++ b/src/os/path_unix.go @@ -16,7 +16,7 @@ func IsPathSeparator(c uint8) bool { return PathSeparator == c } -// basename removes trailing slashes and the leading directory name from path name +// basename removes trailing slashes and the leading directory name from path name. func basename(name string) string { i := len(name) - 1 // Remove trailing slashes @@ -34,6 +34,32 @@ func basename(name string) string { return name } +// splitPath returns the base name and parent directory. +func splitPath(path string) (string, string) { + // if no better parent is found, the path is relative from "here" + dirname := "." + // if no slashes in path, base is path + basename := path + + i := len(path) - 1 + + // Remove trailing slashes + for ; i > 0 && path[i] == '/'; i-- { + path = path[:i] + } + + // Remove leading directory path + for i--; i >= 0; i-- { + if path[i] == '/' { + dirname = path[:i+1] + basename = path[i+1:] + break + } + } + + return dirname, basename +} + func fixRootDirectory(p string) string { return p } diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go new file mode 100644 index 0000000000..062b81e577 --- /dev/null +++ b/src/os/removeall_at.go @@ -0,0 +1,139 @@ +// Copyright 2018 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 linux darwin openbsd netbsd dragonfly + +package os + +import ( + "internal/syscall/unix" + "io" + "syscall" +) + +func RemoveAll(path string) error { + // Not allowed in unix + if path == "" || endsWithDot(path) { + return syscall.EINVAL + } + + // RemoveAll recurses by deleting the path base from + // its parent directory + parentDir, base := splitPath(path) + + parent, err := Open(parentDir) + if IsNotExist(err) { + // If parent does not exist, base cannot exist. Fail silently + return nil + } + if err != nil { + return err + } + defer parent.Close() + + return removeAllFrom(parent, base) +} + +func removeAllFrom(parent *File, path string) error { + parentFd := int(parent.Fd()) + // Simple case: if Unlink (aka remove) works, we're done. + err := unix.Unlinkat(parentFd, path, 0) + if err == nil || IsNotExist(err) { + return nil + } + + // If not a "is directory" error, we have a problem + if err != syscall.EISDIR && err != syscall.EPERM { + return err + } + + // Is this a directory we need to recurse into? + var statInfo syscall.Stat_t + statErr := unix.Fstatat(parentFd, path, &statInfo, unix.AT_SYMLINK_NOFOLLOW) + if statErr != nil { + return statErr + } + if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { + // Not a directory; return the error from the Remove + return err + } + + // Remove the directory's entries + var recurseErr error + for { + const request = 1024 + + // Open the directory to recurse into + file, err := openFdAt(parentFd, path) + if err != nil { + if IsNotExist(err) { + return nil + } + return err + } + + names, readErr := file.Readdirnames(request) + // Errors other than EOF should stop us from continuing + if readErr != nil && readErr != io.EOF { + file.Close() + if IsNotExist(readErr) { + return nil + } + return readErr + } + + for _, name := range names { + err := removeAllFrom(file, name) + if err != nil { + recurseErr = err + } + } + + // Removing files from the directory may have caused + // the OS to reshuffle it. Simply calling Readdirnames + // again may skip some entries. The only reliable way + // to avoid this is to close and re-open the + // directory. See issue 20841. + file.Close() + + // Finish when the end of the directory is reached + if len(names) < request { + break + } + } + + // Remove the directory itself + unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR) + if unlinkError == nil || IsNotExist(unlinkError) { + return nil + } + + if recurseErr != nil { + return recurseErr + } + return unlinkError +} + +func openFdAt(fd int, path string) (*File, error) { + fd, err := unix.Openat(fd, path, O_RDONLY, 0) + if err != nil { + return nil, err + } + + return NewFile(uintptr(fd), path), nil +} + +func endsWithDot(path string) bool { + if path == "." || path == ".." { + return true + } + if len(path) >= 2 && path[len(path)-2:] == "/." { + return true + } + if len(path) >= 3 && path[len(path)-3:] == "/.." { + return true + } + + return false +} diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go new file mode 100644 index 0000000000..1b85a6afa5 --- /dev/null +++ b/src/os/removeall_noat.go @@ -0,0 +1,110 @@ +// Copyright 2018 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 !linux,!darwin,!openbsd,!netbsd,!dragonfly + +package os + +import ( + "io" + "syscall" +) + +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +func RemoveAll(path string) error { + // Simple case: if Remove works, we're done. + err := Remove(path) + if err == nil || IsNotExist(err) { + return nil + } + + // Otherwise, is this a directory we need to recurse into? + dir, serr := Lstat(path) + if serr != nil { + if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { + return nil + } + return serr + } + if !dir.IsDir() { + // Not a directory; return the error from Remove. + return err + } + + // Remove contents & return first error. + err = nil + for { + fd, err := Open(path) + if err != nil { + if IsNotExist(err) { + // Already deleted by someone else. + return nil + } + return err + } + + const request = 1024 + names, err1 := fd.Readdirnames(request) + + // Removing files from the directory may have caused + // the OS to reshuffle it. Simply calling Readdirnames + // again may skip some entries. The only reliable way + // to avoid this is to close and re-open the + // directory. See issue 20841. + fd.Close() + + for _, name := range names { + err1 := RemoveAll(path + string(PathSeparator) + name) + if err == nil { + err = err1 + } + } + + if err1 == io.EOF { + break + } + // If Readdirnames returned an error, use it. + if err == nil { + err = err1 + } + if len(names) == 0 { + break + } + + // We don't want to re-open unnecessarily, so if we + // got fewer than request names from Readdirnames, try + // simply removing the directory now. If that + // succeeds, we are done. + if len(names) < request { + err1 := Remove(path) + if err1 == nil || IsNotExist(err1) { + return nil + } + + if err != nil { + // We got some error removing the + // directory contents, and since we + // read fewer names than we requested + // there probably aren't more files to + // remove. Don't loop around to read + // the directory again. We'll probably + // just get the same error. + return err + } + } + } + + // Remove directory. + err1 := Remove(path) + if err1 == nil || IsNotExist(err1) { + return nil + } + if err == nil { + err = err1 + } + return err +} diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go new file mode 100644 index 0000000000..a1006230da --- /dev/null +++ b/src/os/removeall_test.go @@ -0,0 +1,237 @@ +// Copyright 2018 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 os_test + +import ( + "fmt" + "io/ioutil" + . "os" + "runtime" + "strings" + "testing" +) + +func TestRemoveAll(t *testing.T) { + tmpDir := TempDir() + // Work directory. + file := "file" + path := tmpDir + "/_TestRemoveAll_" + fpath := path + "/file" + dpath := path + "/dir" + + // Make a regular file and remove + fd, err := Create(file) + if err != nil { + t.Fatalf("create %q: %s", file, err) + } + fd.Close() + if err = RemoveAll(file); err != nil { + t.Fatalf("RemoveAll %q (first): %s", file, err) + } + if _, err = Lstat(file); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file) + } + + // Make directory with 1 file and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + fd, err = Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q (second): %s", path, err) + } + if _, err = Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) + } + + // Make directory with file and subdirectory and remove. + if err = MkdirAll(dpath, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", dpath, err) + } + fd, err = Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + fd, err = Create(dpath + "/file") + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q (third): %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path) + } + + // Determine if we should run the following test. + testit := true + if runtime.GOOS == "windows" { + // Chmod is not supported under windows. + testit = false + } else { + // Test fails as root. + testit = Getuid() != 0 + } + if testit { + // Make directory with file and subdirectory and trigger error. + if err = MkdirAll(dpath, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", dpath, err) + } + + for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { + fd, err = Create(s) + if err != nil { + t.Fatalf("create %q: %s", s, err) + } + fd.Close() + } + if err = Chmod(dpath, 0); err != nil { + t.Fatalf("Chmod %q 0: %s", dpath, err) + } + + // No error checking here: either RemoveAll + // will or won't be able to remove dpath; + // either way we want to see if it removes fpath + // and path/zzz. Reasons why RemoveAll might + // succeed in removing dpath as well include: + // * running as root + // * running on a file system without permissions (FAT) + RemoveAll(path) + Chmod(dpath, 0777) + + for _, s := range []string{fpath, path + "/zzz"} { + if _, err = Lstat(s); err == nil { + t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) + } + } + } + if err = RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) + } + if _, err = Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) + } +} + +// Test RemoveAll on a large directory. +func TestRemoveAllLarge(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + tmpDir := TempDir() + // Work directory. + path := tmpDir + "/_TestRemoveAllLarge_" + + // Make directory with 1000 files and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + for i := 0; i < 1000; i++ { + fpath := fmt.Sprintf("%s/file%d", path, i) + fd, err := Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + } + if err := RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q: %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll", path) + } +} + +func TestRemoveAllLongPath(t *testing.T) { + switch runtime.GOOS { + case "linux", "darwin", "openbsd", "netbsd", "dragonfly": + break + default: + t.Skip("skipping for not implemented platforms") + } + + prevDir, err := Getwd() + if err != nil { + t.Fatalf("Could not get wd: %s", err) + } + + startPath, err := ioutil.TempDir("", "TestRemoveAllLongPath-") + if err != nil { + t.Fatalf("Could not create TempDir: %s", err) + } + err = Chdir(startPath) + if err != nil { + t.Fatalf("Could not chdir %s: %s", startPath, err) + } + + // Removing paths with over 4096 chars commonly fails + for i := 0; i < 41; i++ { + name := strings.Repeat("a", 100) + + err = Mkdir(name, 0755) + if err != nil { + t.Fatalf("Could not mkdir %s: %s", name, err) + } + + err = Chdir(name) + if err != nil { + t.Fatalf("Could not chdir %s: %s", name, err) + } + } + + err = Chdir(prevDir) + if err != nil { + t.Fatalf("Could not chdir %s: %s", prevDir, err) + } + + err = RemoveAll(startPath) + if err != nil { + t.Errorf("RemoveAll could not remove long file path %s: %s", startPath, err) + } +} + +func TestRemoveAllDot(t *testing.T) { + switch runtime.GOOS { + case "linux", "darwin", "openbsd", "netbsd", "dragonfly": + break + default: + t.Skip("skipping for not implemented platforms") + } + + prevDir, err := Getwd() + if err != nil { + t.Fatalf("Could not get wd: %s", err) + } + tempDir, err := ioutil.TempDir("", "TestRemoveAllDot-") + if err != nil { + t.Fatalf("Could not create TempDir: %s", err) + } + err = Chdir(tempDir) + if err != nil { + t.Fatalf("Could not chdir to tempdir: %s", err) + } + + err = RemoveAll(".") + if err == nil { + t.Errorf("RemoveAll succeed to remove .") + } + + err = RemoveAll("..") + if err == nil { + t.Errorf("RemoveAll succeed to remove ..") + } + + err = Chdir(prevDir) + if err != nil { + t.Fatalf("Could not chdir %s: %s", prevDir, err) + } +} From 1350214e4134a31117ae71f80adf678aa5c33f08 Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Wed, 31 Oct 2018 14:37:26 +0000 Subject: [PATCH 26/76] crypto/tls: cache Leaf certificate during BuildNameToCertificate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I am working on a TLS server program, which issues new TLS certificates on demand. The new certificates will be added into tls.Config.Certificates. BuildNameToCertificate will be called to refresh the name table afterwards. This change will reduce some workload on existing certificates. Note that you can’t modify the Certificates field (or call BuildNameToCertificate) on a Config in use by a Server. You can however modify an unused Config that gets cloned in GetConfigForClient with appropriate locking. Change-Id: I7bdb7d23fc5d68df83c73f3bfa3ba9181d38fbde GitHub-Last-Rev: c3788f4116be47f2fdb777935c421e7dd694f5c8 GitHub-Pull-Request: golang/go#24920 Reviewed-on: https://go-review.googlesource.com/c/107627 Reviewed-by: Filippo Valsorda --- src/crypto/tls/common.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index ba47d565a0..9d9137bc68 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -765,10 +765,14 @@ func (c *Config) BuildNameToCertificate() { c.NameToCertificate = make(map[string]*Certificate) for i := range c.Certificates { cert := &c.Certificates[i] - x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - continue + if cert.Leaf == nil { + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + continue + } + cert.Leaf = x509Cert } + x509Cert := cert.Leaf if len(x509Cert.Subject.CommonName) > 0 { c.NameToCertificate[x509Cert.Subject.CommonName] = cert } From 53856c4765b1a0ce5494174a92516853aab87d49 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 31 Oct 2018 07:36:42 -0700 Subject: [PATCH 27/76] go/build, cmd/go: add "hurd" as a GOOS value RELNOTES=yes Change-Id: Ie7090b5a6edd548a49bdc4295e01cb686dfe6522 Reviewed-on: https://go-review.googlesource.com/c/146023 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/cmd/go/internal/imports/build.go | 2 +- src/go/build/syslist.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/go/internal/imports/build.go b/src/cmd/go/internal/imports/build.go index a67d2ebaae..ddf425b020 100644 --- a/src/cmd/go/internal/imports/build.go +++ b/src/cmd/go/internal/imports/build.go @@ -207,5 +207,5 @@ func init() { } } -const goosList = "aix android darwin dragonfly freebsd js linux nacl netbsd openbsd plan9 solaris windows zos " +const goosList = "aix android darwin dragonfly freebsd hurd js linux nacl netbsd openbsd plan9 solaris windows zos " const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc riscv riscv64 s390 s390x sparc sparc64 wasm " diff --git a/src/go/build/syslist.go b/src/go/build/syslist.go index 597212b6d0..d13fe9c4f9 100644 --- a/src/go/build/syslist.go +++ b/src/go/build/syslist.go @@ -4,5 +4,5 @@ package build -const goosList = "aix android darwin dragonfly freebsd js linux nacl netbsd openbsd plan9 solaris windows zos " +const goosList = "aix android darwin dragonfly freebsd hurd js linux nacl netbsd openbsd plan9 solaris windows zos " const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc riscv riscv64 s390 s390x sparc sparc64 wasm " From cb07f492db5eae374901f94d8e55d8aeda1fca58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Thu, 25 Oct 2018 11:23:53 +0200 Subject: [PATCH 28/76] internal/cpu, runtime: add CPU feature detection support for AIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AIX doesn't have HWCAP/HWCAP2 variables like Linux. Therefore, it relies on getsystemcfg syscall which can provide some information about the CPU. Change-Id: Ic0dc927e80890d4bf8f0bdfb43fad1e2b890d7a0 Reviewed-on: https://go-review.googlesource.com/c/144959 Run-TryBot: Tobias Klauser TryBot-Result: Gobot Gobot Reviewed-by: Martin Möhrmann --- src/internal/cpu/cpu_ppc64x.go | 17 +++++++++-------- src/runtime/os2_aix.go | 9 +++++++++ src/runtime/os_aix.go | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/internal/cpu/cpu_ppc64x.go b/src/internal/cpu/cpu_ppc64x.go index 1e7959b306..880c4e1d01 100644 --- a/src/internal/cpu/cpu_ppc64x.go +++ b/src/internal/cpu/cpu_ppc64x.go @@ -11,18 +11,19 @@ const CacheLinePadSize = 128 // ppc64x doesn't have a 'cpuid' equivalent, so we rely on HWCAP/HWCAP2. // These are initialized by archauxv in runtime/os_linux_ppc64x.go. // These should not be changed after they are initialized. +// On aix/ppc64, these values are initialized early in the runtime in runtime/os_aix.go. var HWCap uint var HWCap2 uint // HWCAP/HWCAP2 bits. These are exposed by the kernel. const ( // ISA Level - _PPC_FEATURE2_ARCH_2_07 = 0x80000000 - _PPC_FEATURE2_ARCH_3_00 = 0x00800000 + PPC_FEATURE2_ARCH_2_07 = 0x80000000 + PPC_FEATURE2_ARCH_3_00 = 0x00800000 // CPU features - _PPC_FEATURE2_DARN = 0x00200000 - _PPC_FEATURE2_SCV = 0x00100000 + PPC_FEATURE2_DARN = 0x00200000 + PPC_FEATURE2_SCV = 0x00100000 ) func doinit() { @@ -36,10 +37,10 @@ func doinit() { } // HWCAP2 feature bits - PPC64.IsPOWER8 = isSet(HWCap2, _PPC_FEATURE2_ARCH_2_07) - PPC64.IsPOWER9 = isSet(HWCap2, _PPC_FEATURE2_ARCH_3_00) - PPC64.HasDARN = isSet(HWCap2, _PPC_FEATURE2_DARN) - PPC64.HasSCV = isSet(HWCap2, _PPC_FEATURE2_SCV) + PPC64.IsPOWER8 = isSet(HWCap2, PPC_FEATURE2_ARCH_2_07) + PPC64.IsPOWER9 = isSet(HWCap2, PPC_FEATURE2_ARCH_3_00) + PPC64.HasDARN = isSet(HWCap2, PPC_FEATURE2_DARN) + PPC64.HasSCV = isSet(HWCap2, PPC_FEATURE2_SCV) } func isSet(hwc uint, value uint) bool { diff --git a/src/runtime/os2_aix.go b/src/runtime/os2_aix.go index 9e26ce23fc..c478d4b0d8 100644 --- a/src/runtime/os2_aix.go +++ b/src/runtime/os2_aix.go @@ -33,6 +33,7 @@ var ( //go:cgo_import_dynamic libc_close close "libc.a/shr_64.o" //go:cgo_import_dynamic libc_exit exit "libc.a/shr_64.o" //go:cgo_import_dynamic libc_getpid getpid "libc.a/shr_64.o" +//go:cgo_import_dynamic libc_getsystemcfg getsystemcfg "libc.a/shr_64.o" //go:cgo_import_dynamic libc_kill kill "libc.a/shr_64.o" //go:cgo_import_dynamic libc_madvise madvise "libc.a/shr_64.o" //go:cgo_import_dynamic libc_malloc malloc "libc.a/shr_64.o" @@ -69,6 +70,7 @@ var ( //go:linkname libc_close libc_close //go:linkname libc_exit libc_exit //go:linkname libc_getpid libc_getpid +//go:linkname libc_getsystemcfg libc_getsystemcfg //go:linkname libc_kill libc_kill //go:linkname libc_madvise libc_madvise //go:linkname libc_malloc libc_malloc @@ -107,6 +109,7 @@ var ( libc_close, libc_exit, libc_getpid, + libc_getsystemcfg, libc_kill, libc_madvise, libc_malloc, @@ -319,6 +322,12 @@ func sigaltstack(new, old *stackt) { } } +//go:nosplit +func getsystemcfg(label uint) uintptr { + r, _ := syscall1(&libc_getsystemcfg, uintptr(label)) + return r +} + //go:nosplit func usleep(us uint32) { r, err := syscall1(&libc_usleep, uintptr(us)) diff --git a/src/runtime/os_aix.go b/src/runtime/os_aix.go index 31590f22d8..141ce3bb11 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -7,6 +7,7 @@ package runtime import ( + "internal/cpu" "unsafe" ) @@ -93,6 +94,7 @@ func semawakeup(mp *m) { func osinit() { ncpu = int32(sysconf(__SC_NPROCESSORS_ONLN)) physPageSize = sysconf(__SC_PAGE_SIZE) + setupSystemConf() } // Ms related functions @@ -260,3 +262,22 @@ func walltime() (sec int64, nsec int32) { } return ts.tv_sec, int32(ts.tv_nsec) } + +const ( + // getsystemcfg constants + _SC_IMPL = 2 + _IMPL_POWER8 = 0x10000 + _IMPL_POWER9 = 0x20000 +) + +// setupSystemConf retrieves information about the CPU and updates +// cpu.HWCap variables. +func setupSystemConf() { + impl := getsystemcfg(_SC_IMPL) + if impl&_IMPL_POWER8 != 0 { + cpu.HWCap2 |= cpu.PPC_FEATURE2_ARCH_2_07 + } + if impl&_IMPL_POWER9 != 0 { + cpu.HWCap2 |= cpu.PPC_FEATURE2_ARCH_3_00 + } +} From 86ad85ce98264f79d7f5fc60c38f81275c40d7a6 Mon Sep 17 00:00:00 2001 From: Yury Smolsky Date: Wed, 31 Oct 2018 17:15:32 +0200 Subject: [PATCH 29/76] time: display results in examples rather soon We have fixed the playground to display results of the program when it was timed out. This CL fixes how soon results will be displayed to the user. Change-Id: Ifb75828e0de12c726c8ca6e2d04947e01913dc73 Reviewed-on: https://go-review.googlesource.com/c/146237 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/time/example_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/time/example_test.go b/src/time/example_test.go index 7e303ac5a0..0fd325f2e4 100644 --- a/src/time/example_test.go +++ b/src/time/example_test.go @@ -132,7 +132,7 @@ func ExampleAfter() { select { case m := <-c: handle(m) - case <-time.After(5 * time.Minute): + case <-time.After(10 * time.Second): fmt.Println("timed out") } } @@ -144,7 +144,7 @@ func ExampleSleep() { func statusUpdate() string { return "" } func ExampleTick() { - c := time.Tick(1 * time.Minute) + c := time.Tick(5 * time.Second) for now := range c { fmt.Printf("%v %s\n", now, statusUpdate()) } From 4d1e95bb63c4d43369ea335680aa65000e3f3866 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Tue, 30 Oct 2018 00:40:24 +0000 Subject: [PATCH 30/76] os: add support for long path names on solaris RemoveAll Follow CL 146020 and enable RemoveAll based on Unlinkat and Openat on solaris. Updates #27029 Change-Id: I0b0e92f4422fa960a13dcd3e9adb57cd23f09ed4 Reviewed-on: https://go-review.googlesource.com/c/145839 Run-TryBot: Tobias Klauser Reviewed-by: Brad Fitzpatrick Reviewed-by: Ian Lance Taylor TryBot-Result: Gobot Gobot --- src/internal/syscall/unix/asm_solaris.s | 10 ++++ src/internal/syscall/unix/at_solaris.go | 75 +++++++++++++++++++++++++ src/os/removeall_at.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_test.go | 4 +- 5 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/internal/syscall/unix/asm_solaris.s create mode 100644 src/internal/syscall/unix/at_solaris.go diff --git a/src/internal/syscall/unix/asm_solaris.s b/src/internal/syscall/unix/asm_solaris.s new file mode 100644 index 0000000000..a7ad26df9b --- /dev/null +++ b/src/internal/syscall/unix/asm_solaris.s @@ -0,0 +1,10 @@ +// Copyright 2018 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. + +#include "textflag.h" + +// System calls for Solaris are implemented in runtime/syscall_solaris.go + +TEXT ·sysvicall6(SB),NOSPLIT,$0-88 + JMP syscall·sysvicall6(SB) diff --git a/src/internal/syscall/unix/at_solaris.go b/src/internal/syscall/unix/at_solaris.go new file mode 100644 index 0000000000..d63ee990fd --- /dev/null +++ b/src/internal/syscall/unix/at_solaris.go @@ -0,0 +1,75 @@ +// Copyright 2018 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 unix + +import ( + "syscall" + "unsafe" +) + +// Implemented in runtime/syscall_solaris.go. +func sysvicall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) + +//go:cgo_import_dynamic libc_fstatat fstatat "libc.so" +//go:cgo_import_dynamic libc_openat openat "libc.so" +//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so" + +//go:linkname procFstatat libc_fstatat +//go:linkname procOpenat libc_openat +//go:linkname procUnlinkat libc_unlinkat + +var ( + procFstatat, + procOpenat, + procUnlinkat uintptr +) + +const AT_REMOVEDIR = 0x1 +const AT_SYMLINK_NOFOLLOW = 0x1000 + +func Unlinkat(dirfd int, path string, flags int) error { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procUnlinkat)), 3, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), 0, 0, 0) + if errno != 0 { + return errno + } + + return nil +} + +func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + fd, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procOpenat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) + if errno != 0 { + return 0, errno + } + + return int(fd), nil +} + +func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { + var p *byte + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procFstatat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if errno != 0 { + return errno + } + + return nil +} diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go index 062b81e577..b7ed2aa6d4 100644 --- a/src/os/removeall_at.go +++ b/src/os/removeall_at.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux darwin openbsd netbsd dragonfly +// +build linux darwin openbsd netbsd dragonfly solaris package os diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index 1b85a6afa5..02af047d6e 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!darwin,!openbsd,!netbsd,!dragonfly +// +build !linux,!darwin,!openbsd,!netbsd,!dragonfly,!solaris package os diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go index a1006230da..4b6f3e9256 100644 --- a/src/os/removeall_test.go +++ b/src/os/removeall_test.go @@ -153,7 +153,7 @@ func TestRemoveAllLarge(t *testing.T) { func TestRemoveAllLongPath(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "openbsd", "netbsd", "dragonfly": + case "linux", "darwin", "openbsd", "netbsd", "dragonfly", "solaris": break default: t.Skip("skipping for not implemented platforms") @@ -201,7 +201,7 @@ func TestRemoveAllLongPath(t *testing.T) { func TestRemoveAllDot(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "openbsd", "netbsd", "dragonfly": + case "linux", "darwin", "openbsd", "netbsd", "dragonfly", "solaris": break default: t.Skip("skipping for not implemented platforms") From f6f27bfb97b8dcaa2350829dd043d7c5a5f341ab Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 31 Oct 2018 11:38:37 -0700 Subject: [PATCH 31/76] cmd/compile/internal/gc: more direct noder.nod implementation (cleanup) Also, renamed - noder.lineno -> noder.setlineno (because that's what it does) - noder.setlineno -> noder.pos (and return the src.XPos) Change-Id: I5d3442cf2af97028afcab028290152ce8d062927 Reviewed-on: https://go-review.googlesource.com/c/146317 Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/closure.go | 2 +- src/cmd/compile/internal/gc/dcl.go | 8 +-- src/cmd/compile/internal/gc/noder.go | 68 +++++++++++++------------- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/cmd/compile/internal/gc/closure.go b/src/cmd/compile/internal/gc/closure.go index 0736c5be4f..ec19f5c112 100644 --- a/src/cmd/compile/internal/gc/closure.go +++ b/src/cmd/compile/internal/gc/closure.go @@ -16,7 +16,7 @@ func (p *noder) funcLit(expr *syntax.FuncLit) *Node { xfunc := p.nod(expr, ODCLFUNC, nil, nil) xfunc.Func.SetIsHiddenClosure(Curfn != nil) - xfunc.Func.Nname = p.setlineno(expr, newfuncname(nblank.Sym)) // filled in by typecheckclosure + xfunc.Func.Nname = newfuncnamel(p.pos(expr), nblank.Sym) // filled in by typecheckclosure xfunc.Func.Nname.Name.Param.Ntype = xtype xfunc.Func.Nname.Name.Defn = xfunc diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index 22201e5044..645ba7558c 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -208,12 +208,6 @@ func newnoname(s *types.Sym) *Node { return n } -// newfuncname generates a new name node for a function or method. -// TODO(rsc): Use an ODCLFUNC node instead. See comment in CL 7360. -func newfuncname(s *types.Sym) *Node { - return newfuncnamel(lineno, s) -} - // newfuncnamel generates a new name node for a function or method. // TODO(rsc): Use an ODCLFUNC node instead. See comment in CL 7360. func newfuncnamel(pos src.XPos, s *types.Sym) *Node { @@ -1013,7 +1007,7 @@ func dclfunc(sym *types.Sym, tfn *Node) *Node { } fn := nod(ODCLFUNC, nil, nil) - fn.Func.Nname = newfuncname(sym) + fn.Func.Nname = newfuncnamel(lineno, sym) fn.Func.Nname.Name.Defn = fn fn.Func.Nname.Name.Param.Ntype = tfn declare(fn.Func.Nname, PFUNC) diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 8964536ff0..f13d2cdbb5 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -237,7 +237,7 @@ func (p *noder) node() { types.Block = 1 imported_unsafe = false - p.lineno(p.file.PkgName) + p.setlineno(p.file.PkgName) mkpackage(p.file.PkgName.Value) xtop = append(xtop, p.decls(p.file.DeclList)...) @@ -259,7 +259,7 @@ func (p *noder) decls(decls []syntax.Decl) (l []*Node) { var cs constState for _, decl := range decls { - p.lineno(decl) + p.setlineno(decl) switch decl := decl.(type) { case *syntax.ImportDecl: p.importDecl(decl) @@ -335,7 +335,7 @@ func (p *noder) varDecl(decl *syntax.VarDecl) []*Node { exprs = p.exprList(decl.Values) } - p.lineno(decl) + p.setlineno(decl) return variter(names, typ, exprs) } @@ -433,7 +433,9 @@ func (p *noder) declNames(names []*syntax.Name) []*Node { } func (p *noder) declName(name *syntax.Name) *Node { - return p.setlineno(name, dclname(p.name(name))) + n := dclname(p.name(name)) + n.Pos = p.pos(name) + return n } func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { @@ -459,7 +461,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { name = nblank.Sym // filled in by typecheckfunc } - f.Func.Nname = p.setlineno(fun.Name, newfuncname(name)) + f.Func.Nname = newfuncnamel(p.pos(fun.Name), name) f.Func.Nname.Name.Defn = f f.Func.Nname.Name.Param.Ntype = t @@ -502,7 +504,7 @@ func (p *noder) signature(recv *syntax.Field, typ *syntax.FuncType) *Node { func (p *noder) params(params []*syntax.Field, dddOk bool) []*Node { var nodes []*Node for i, param := range params { - p.lineno(param) + p.setlineno(param) nodes = append(nodes, p.param(param, dddOk, i+1 == len(params))) } return nodes @@ -552,15 +554,14 @@ func (p *noder) exprs(exprs []syntax.Expr) []*Node { } func (p *noder) expr(expr syntax.Expr) *Node { - p.lineno(expr) + p.setlineno(expr) switch expr := expr.(type) { case nil, *syntax.BadExpr: return nil case *syntax.Name: return p.mkname(expr) case *syntax.BasicLit: - return p.setlineno(expr, nodlit(p.basicLit(expr))) - + return nodlit(p.basicLit(expr)) case *syntax.CompositeLit: n := p.nod(expr, OCOMPLIT, nil, nil) if expr.Type != nil { @@ -587,7 +588,9 @@ func (p *noder) expr(expr syntax.Expr) *Node { obj.Name.SetUsed(true) return oldname(restrictlookup(expr.Sel.Value, obj.Name.Pkg)) } - return p.setlineno(expr, nodSym(OXDOT, obj, p.name(expr.Sel))) + n := nodSym(OXDOT, obj, p.name(expr.Sel)) + n.Pos = p.pos(expr) // lineno may have been changed by p.expr(expr.X) + return n case *syntax.IndexExpr: return p.nod(expr, OINDEX, p.expr(expr.X), p.expr(expr.Index)) case *syntax.SliceExpr: @@ -771,7 +774,7 @@ func (p *noder) chanDir(dir syntax.ChanDir) types.ChanDir { func (p *noder) structType(expr *syntax.StructType) *Node { var l []*Node for i, field := range expr.FieldList { - p.lineno(field) + p.setlineno(field) var n *Node if field.Name == nil { n = p.embedded(field.Type) @@ -784,7 +787,7 @@ func (p *noder) structType(expr *syntax.StructType) *Node { l = append(l, n) } - p.lineno(expr) + p.setlineno(expr) n := p.nod(expr, OTSTRUCT, nil, nil) n.List.Set(l) return n @@ -793,7 +796,7 @@ func (p *noder) structType(expr *syntax.StructType) *Node { func (p *noder) interfaceType(expr *syntax.InterfaceType) *Node { var l []*Node for _, method := range expr.MethodList { - p.lineno(method) + p.setlineno(method) var n *Node if method.Name == nil { n = p.nodSym(method, ODCLFIELD, oldname(p.packname(method.Type)), nil) @@ -882,7 +885,7 @@ func (p *noder) stmt(stmt syntax.Stmt) *Node { } func (p *noder) stmtFall(stmt syntax.Stmt, fallOK bool) *Node { - p.lineno(stmt) + p.setlineno(stmt) switch stmt := stmt.(type) { case *syntax.EmptyStmt: return nil @@ -1010,7 +1013,7 @@ func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node { newOrErr := false for i, expr := range exprs { - p.lineno(expr) + p.setlineno(expr) res[i] = nblank name, ok := expr.(*syntax.Name) @@ -1132,7 +1135,7 @@ func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node, rbrace syntax.Pos) []*Node { var nodes []*Node for i, clause := range clauses { - p.lineno(clause) + p.setlineno(clause) if i > 0 { p.closeScope(clause.Pos()) } @@ -1188,7 +1191,7 @@ func (p *noder) selectStmt(stmt *syntax.SelectStmt) *Node { func (p *noder) commClauses(clauses []*syntax.CommClause, rbrace syntax.Pos) []*Node { var nodes []*Node for i, clause := range clauses { - p.lineno(clause) + p.setlineno(clause) if i > 0 { p.closeScope(clause.Pos()) } @@ -1361,33 +1364,28 @@ func (p *noder) wrapname(n syntax.Node, x *Node) *Node { } func (p *noder) nod(orig syntax.Node, op Op, left, right *Node) *Node { - return p.setlineno(orig, nod(op, left, right)) + return nodl(p.pos(orig), op, left, right) } func (p *noder) nodSym(orig syntax.Node, op Op, left *Node, sym *types.Sym) *Node { - return p.setlineno(orig, nodSym(op, left, sym)) + n := nodSym(op, left, sym) + n.Pos = p.pos(orig) + return n } -func (p *noder) setlineno(src_ syntax.Node, dst *Node) *Node { - pos := src_.Pos() - if !pos.IsKnown() { - // TODO(mdempsky): Shouldn't happen. Fix package syntax. - return dst +func (p *noder) pos(n syntax.Node) src.XPos { + // TODO(gri): orig.Pos() should always be known - fix package syntax + xpos := lineno + if pos := n.Pos(); pos.IsKnown() { + xpos = p.makeXPos(pos) } - dst.Pos = p.makeXPos(pos) - return dst + return xpos } -func (p *noder) lineno(n syntax.Node) { - if n == nil { - return +func (p *noder) setlineno(n syntax.Node) { + if n != nil { + lineno = p.pos(n) } - pos := n.Pos() - if !pos.IsKnown() { - // TODO(mdempsky): Shouldn't happen. Fix package syntax. - return - } - lineno = p.makeXPos(pos) } // error is called concurrently if files are parsed concurrently. From 3553eca27c31e9709e9831f3d26ec555b904dcd4 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Mon, 29 Oct 2018 12:06:58 +0530 Subject: [PATCH 32/76] doc: replace command line usages of godoc with go doc Effective Go and the FAQ still had some instances which showed the command line usage of godoc. Changed them to use go doc. Updates #25443 Change-Id: If550963322034e6848bc466f79e968e7220e4a88 Reviewed-on: https://go-review.googlesource.com/c/145222 Reviewed-by: Brad Fitzpatrick --- doc/effective_go.html | 10 ++++++---- doc/go_faq.html | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/effective_go.html b/doc/effective_go.html index 5d184b76a9..1743d0fa11 100644 --- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -246,14 +246,16 @@ func Compile(str string) (*Regexp, error) {

If every doc comment begins with the name of the item it describes, -the output of godoc can usefully be run through grep. +you can use the doc +subcommand of the go tool +and run the output through grep. Imagine you couldn't remember the name "Compile" but were looking for the parsing function for regular expressions, so you ran the command,

-$ godoc regexp | grep -i parse
+$ go doc -all regexp | grep -i parse
 

@@ -264,10 +266,10 @@ which recalls the word you're looking for.

-$ godoc regexp | grep parse
+$ go doc -all regexp | grep -i parse
     Compile parses a regular expression and returns, if successful, a Regexp
+    MustCompile is like Compile but panics if the expression cannot be parsed.
     parsed. It simplifies safe initialization of global variables holding
-    cannot be parsed. It simplifies safe initialization of global variables
 $
 
diff --git a/doc/go_faq.html b/doc/go_faq.html index 6bc9d6ef15..c61dd0fc5f 100644 --- a/doc/go_faq.html +++ b/doc/go_faq.html @@ -804,7 +804,7 @@ type Fooer interface {

A type must then implement the ImplementsFooer method to be a Fooer, clearly documenting the fact and announcing it in -godoc's output. +go doc's output.


From d086c5c81f6e19e1dce0a17d901fec071b9394ff Mon Sep 17 00:00:00 2001
From: Ian Lance Taylor 
Date: Wed, 31 Oct 2018 09:32:51 -0700
Subject: [PATCH 33/76] cmd/go: don't pass empty string to ar when using
 -compiler=gccgo

Correct error introduced in CL 145417 on non-AIX systems.

Fixes #28511

Change-Id: I6624939061425af19faccedd271f465d1fe6b975
Reviewed-on: https://go-review.googlesource.com/c/146277
Run-TryBot: Ian Lance Taylor 
TryBot-Result: Gobot Gobot 
Reviewed-by: Brad Fitzpatrick 
---
 src/cmd/go/internal/work/gccgo.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go
index ca3be4fd36..b89d07ead0 100644
--- a/src/cmd/go/internal/work/gccgo.go
+++ b/src/cmd/go/internal/work/gccgo.go
@@ -186,12 +186,12 @@ func (gccgoToolchain) pack(b *Builder, a *Action, afile string, ofiles []string)
 	for _, f := range ofiles {
 		absOfiles = append(absOfiles, mkAbs(objdir, f))
 	}
-	var arArgs string
+	var arArgs []string
 	if cfg.Goos == "aix" && cfg.Goarch == "ppc64" {
 		// AIX puts both 32-bit and 64-bit objects in the same archive.
 		// Tell the AIX "ar" command to only care about 64-bit objects.
 		// AIX "ar" command does not know D option.
-		arArgs = "-X64"
+		arArgs = []string{"-X64"}
 	}
 
 	return b.run(a, p.Dir, p.ImportPath, nil, "ar", arArgs, "rc", mkAbs(objdir, afile), absOfiles)

From 4b05c01f9ce4740e801937af9062ee0a9a96b772 Mon Sep 17 00:00:00 2001
From: Keith Randall 
Date: Mon, 15 Oct 2018 15:14:48 -0700
Subject: [PATCH 34/76] runtime: exit early when scanning map buckets
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Divide the "empty" slot state into two, "emptyOne" and "emptyRest".
emptyOne means just that slot is empty. emptyRest means all subsequent
slots in that bucket are empty and the overflow pointer is nil.

When scanning a bucket, we can often stop at emptyRest, reducing
the total work we have to do. (This is similar to how tombstones
work in open addressing.)

Ideally on delete we have to figure out whether to zero the slot
with an emptyOne or emptyRest marker. For now, we choose the safe
but non-optimal choice. (Fix in subsequent CL?)

This is a simpler CL than some others we've tried, including my
CL sequence 11835[5-8] and Ilya's CL 115616.

Update #19495

name                           old time/op    new time/op    delta
MegMap                           8.96ns ± 2%    8.74ns ± 6%   -2.44%  (p=0.020 n=10+10)
MegOneMap                        8.91ns ± 2%    5.53ns ± 2%  -37.99%  (p=0.000 n=10+10)
MegEqMap                         46.0µs ± 1%    45.8µs ± 3%     ~     (p=0.315 n=9+10)
MegEmptyMap                      2.50ns ± 0%    2.50ns ± 2%     ~     (p=0.957 n=8+10)
SmallStrMap                      8.54ns ± 1%    8.71ns ± 2%   +2.01%  (p=0.000 n=10+10)
MapStringKeysEight_16            8.61ns ± 3%    8.71ns ± 3%   +1.20%  (p=0.026 n=9+9)
MapStringKeysEight_32            8.54ns ± 2%    8.97ns ± 1%   +5.05%  (p=0.000 n=10+9)
MapStringKeysEight_64            8.66ns ± 2%    8.99ns ± 2%   +3.87%  (p=0.000 n=10+10)
MapStringKeysEight_1M            8.57ns ± 2%    8.95ns ± 2%   +4.51%  (p=0.000 n=10+9)
IntMap                           6.69ns ± 1%    7.46ns ± 1%  +11.60%  (p=0.000 n=9+9)
MapFirst/1                       3.69ns ± 1%    3.63ns ± 3%   -1.52%  (p=0.040 n=10+10)
MapFirst/2                       3.70ns ± 2%    3.63ns ± 2%   -1.95%  (p=0.001 n=9+9)
MapFirst/3                       3.74ns ± 2%    3.66ns ± 2%   -2.12%  (p=0.000 n=8+10)
MapFirst/4                       3.71ns ± 2%    3.66ns ± 4%     ~     (p=0.073 n=9+10)
MapFirst/5                       3.69ns ± 1%    3.62ns ± 2%   -1.88%  (p=0.000 n=9+10)
MapFirst/6                       3.68ns ± 2%    3.62ns ± 1%   -1.83%  (p=0.001 n=10+9)
MapFirst/7                       3.67ns ± 1%    3.60ns ± 1%   -1.98%  (p=0.000 n=10+8)
MapFirst/8                       3.68ns ± 2%    3.61ns ± 2%   -1.87%  (p=0.000 n=10+10)
MapFirst/9                       8.03ns ± 4%    7.89ns ± 2%   -1.76%  (p=0.007 n=10+10)
MapFirst/10                      7.99ns ± 2%    7.86ns ± 3%   -1.64%  (p=0.009 n=9+10)
MapFirst/11                      7.96ns ± 1%    7.80ns ± 2%   -2.01%  (p=0.000 n=10+10)
MapFirst/12                      7.96ns ± 1%    7.82ns ± 1%   -1.67%  (p=0.000 n=10+10)
MapFirst/13                      8.06ns ± 3%    7.92ns ± 3%     ~     (p=0.055 n=10+10)
MapFirst/14                      7.95ns ± 1%    7.80ns ± 1%   -1.88%  (p=0.000 n=10+9)
MapFirst/15                      8.01ns ± 2%    7.80ns ± 2%   -2.57%  (p=0.000 n=10+10)
MapFirst/16                      8.05ns ± 2%    7.90ns ± 2%   -1.84%  (p=0.005 n=9+10)
MapMid/1                         4.00ns ± 1%    3.94ns ± 2%   -1.30%  (p=0.021 n=8+9)
MapMid/2                         4.39ns ± 2%    4.32ns ± 4%     ~     (p=0.128 n=10+10)
MapMid/3                         4.40ns ± 2%    4.27ns ± 2%   -2.93%  (p=0.000 n=10+9)
MapMid/4                         4.76ns ± 2%    4.65ns ± 1%   -2.26%  (p=0.000 n=10+9)
MapMid/5                         4.76ns ± 1%    4.65ns ± 1%   -2.27%  (p=0.000 n=10+10)
MapMid/6                         5.11ns ± 2%    4.98ns ± 2%   -2.55%  (p=0.000 n=10+10)
MapMid/7                         5.12ns ± 1%    5.01ns ± 3%   -2.02%  (p=0.003 n=9+9)
MapMid/8                         5.71ns ± 3%    5.97ns ± 1%   +4.51%  (p=0.000 n=10+9)
MapMid/9                         8.72ns ±10%    8.89ns ±10%     ~     (p=0.458 n=9+10)
MapMid/10                        10.1ns ±15%     9.6ns ± 7%     ~     (p=0.080 n=9+10)
MapMid/11                        9.88ns ±10%    9.44ns ±11%     ~     (p=0.065 n=10+10)
MapMid/12                        9.90ns ±13%   10.04ns ± 9%     ~     (p=1.000 n=10+8)
MapMid/13                        9.67ns ±14%   10.23ns ±10%     ~     (p=0.209 n=10+9)
MapMid/14                        9.12ns ±14%    9.14ns ±13%     ~     (p=0.927 n=10+10)
MapMid/15                        9.16ns ±12%    9.15ns ±16%     ~     (p=0.955 n=10+10)
MapMid/16                        9.37ns ±11%    9.60ns ±23%     ~     (p=0.825 n=9+10)
MapLast/1                        4.08ns ± 1%    3.92ns ± 0%   -3.91%  (p=0.000 n=10+9)
MapLast/2                        4.37ns ± 1%    4.28ns ± 1%   -1.95%  (p=0.000 n=10+10)
MapLast/3                        4.94ns ± 2%    4.65ns ± 1%   -5.79%  (p=0.000 n=9+8)
MapLast/4                        5.40ns ± 3%    5.02ns ± 2%   -7.13%  (p=0.000 n=9+9)
MapLast/5                        5.88ns ± 2%    5.67ns ± 2%   -3.57%  (p=0.000 n=10+10)
MapLast/6                        6.48ns ± 3%    5.90ns ± 2%   -8.89%  (p=0.000 n=10+10)
MapLast/7                        7.01ns ± 2%    6.27ns ± 5%  -10.56%  (p=0.000 n=10+10)
MapLast/8                        7.60ns ± 2%    6.62ns ± 2%  -12.93%  (p=0.000 n=9+10)
MapLast/9                        10.6ns ± 9%    10.9ns ±15%     ~     (p=0.344 n=9+10)
MapLast/10                       11.0ns ±12%    10.9ns ±14%     ~     (p=0.985 n=10+10)
MapLast/11                       11.4ns ±12%    11.8ns ±22%     ~     (p=0.671 n=10+10)
MapLast/12                       11.6ns ±10%    12.1ns ±19%     ~     (p=0.617 n=10+10)
MapLast/13                       12.5ns ±23%    11.8ns ±13%     ~     (p=0.827 n=10+9)
MapLast/14                       10.5ns ±22%    10.4ns ± 5%     ~     (p=0.797 n=10+9)
MapLast/15                       10.0ns ±15%    10.3ns ±16%     ~     (p=0.565 n=10+10)
MapLast/16                       10.4ns ±12%    10.5ns ±13%     ~     (p=0.889 n=10+9)
MapCycle                         22.3ns ± 1%    22.0ns ± 2%   -1.43%  (p=0.002 n=9+10)
RepeatedLookupStrMapKey32        16.4ns ± 1%    16.6ns ± 1%   +1.24%  (p=0.000 n=10+9)
RepeatedLookupStrMapKey1M        35.6µs ± 0%    35.4µs ± 1%   -0.62%  (p=0.002 n=10+10)
NewEmptyMap                      5.36ns ± 1%    9.05ns ± 1%  +69.02%  (p=0.000 n=10+8)
NewSmallMap                      51.2ns ± 2%    33.7ns ± 1%  -34.22%  (p=0.000 n=10+9)
MapIter                          83.8ns ± 1%    88.4ns ± 1%   +5.55%  (p=0.000 n=10+10)
MapIterEmpty                     4.32ns ± 3%    5.54ns ± 3%  +28.12%  (p=0.000 n=10+10)
SameLengthMap                    4.31ns ± 1%    4.59ns ± 2%   +6.41%  (p=0.000 n=9+10)
BigKeyMap                        24.2ns ± 2%    24.3ns ± 1%     ~     (p=0.432 n=10+10)
BigValMap                        24.3ns ± 1%    24.4ns ± 2%     ~     (p=0.200 n=10+9)
SmallKeyMap                      17.5ns ± 1%    18.5ns ± 2%   +5.81%  (p=0.000 n=9+10)
MapPopulate/1                    29.0ns ± 4%    18.8ns ± 1%  -35.27%  (p=0.000 n=10+9)
MapPopulate/10                    736ns ± 5%     693ns ± 4%   -5.92%  (p=0.000 n=10+10)
MapPopulate/100                  11.3µs ± 2%    10.8µs ± 3%   -4.38%  (p=0.000 n=10+10)
MapPopulate/1000                  139µs ± 8%     132µs ± 4%   -5.10%  (p=0.002 n=10+10)
MapPopulate/10000                1.21ms ± 5%    1.16ms ± 5%   -4.56%  (p=0.002 n=10+10)
MapPopulate/100000               12.2ms ± 3%    11.8ms ± 5%     ~     (p=0.052 n=10+10)
ComplexAlgMap                    73.9ns ± 1%    74.4ns ± 2%     ~     (p=0.161 n=9+10)
GoMapClear/Reflexive/1           36.0ns ± 1%    26.9ns ± 2%  -25.31%  (p=0.000 n=10+10)
GoMapClear/Reflexive/10          35.2ns ± 1%    24.4ns ± 1%  -30.62%  (p=0.000 n=10+10)
GoMapClear/Reflexive/100         69.6ns ± 2%    59.2ns ± 1%  -14.92%  (p=0.000 n=10+10)
GoMapClear/Reflexive/1000        1.06µs ± 2%    1.05µs ± 1%   -1.16%  (p=0.013 n=10+9)
GoMapClear/Reflexive/10000       11.7µs ± 1%    11.7µs ± 1%     ~     (p=0.542 n=10+10)
GoMapClear/NonReflexive/1        96.3ns ± 1%    90.0ns ± 1%   -6.52%  (p=0.000 n=10+10)
GoMapClear/NonReflexive/10        110ns ± 2%     101ns ± 0%   -8.10%  (p=0.000 n=10+7)
GoMapClear/NonReflexive/100       270ns ± 2%     235ns ± 2%  -12.94%  (p=0.000 n=10+10)
GoMapClear/NonReflexive/1000     3.02µs ± 2%    2.48µs ± 1%  -17.92%  (p=0.000 n=10+10)
GoMapClear/NonReflexive/10000    23.7µs ± 1%    19.6µs ± 1%  -17.30%  (p=0.000 n=10+9)
MapPop100                        9.65µs ± 6%    9.18µs ± 8%   -4.82%  (p=0.008 n=9+10)
MapPop1000                        162µs ± 6%     148µs ± 4%   -8.67%  (p=0.000 n=9+9)
MapPop10000                      3.05ms ± 8%    2.82ms ±15%   -7.66%  (p=0.023 n=10+10)
MapAssign/Int32/256              15.7ns ± 4%    14.6ns ± 2%   -7.08%  (p=0.000 n=10+10)
MapAssign/Int32/65536            29.8ns ± 1%    30.4ns ± 0%   +2.04%  (p=0.000 n=10+8)
MapAssign/Int64/256              14.9ns ± 5%    14.8ns ± 4%     ~     (p=0.611 n=10+10)
MapAssign/Int64/65536            30.3ns ± 2%    30.4ns ± 1%   +0.54%  (p=0.046 n=10+9)
MapAssign/Str/256                17.8ns ± 3%    19.8ns ± 4%  +11.08%  (p=0.000 n=10+10)
MapAssign/Str/65536              35.7ns ± 1%    36.4ns ± 1%   +1.82%  (p=0.000 n=10+10)
MapOperatorAssign/Int32/256      18.8ns ± 5%    14.6ns ± 3%  -22.57%  (p=0.000 n=10+10)
MapOperatorAssign/Int32/65536    29.8ns ± 1%    30.5ns ± 1%   +2.39%  (p=0.000 n=10+10)
MapOperatorAssign/Int64/256      16.6ns ± 4%    15.0ns ± 6%   -9.34%  (p=0.000 n=10+10)
MapOperatorAssign/Int64/65536    30.1ns ± 1%    31.7ns ± 2%   +5.21%  (p=0.000 n=10+10)
MapOperatorAssign/Str/256        1.70µs ± 1%    1.61µs ± 2%   -5.55%  (p=0.000 n=10+8)
MapOperatorAssign/Str/65536       289ns ± 7%     294ns ± 4%     ~     (p=0.425 n=10+10)
MapAppendAssign/Int32/256        34.3ns ± 2%    31.0ns ± 3%   -9.59%  (p=0.000 n=9+9)
MapAppendAssign/Int32/65536      51.8ns ± 3%    47.1ns ±13%   -9.17%  (p=0.002 n=9+10)
MapAppendAssign/Int64/256        32.5ns ± 8%    31.2ns ± 6%     ~     (p=0.065 n=10+10)
MapAppendAssign/Int64/65536      51.4ns ± 4%    47.2ns ±10%   -8.07%  (p=0.005 n=9+10)
MapAppendAssign/Str/256           105ns ±12%     109ns ± 4%     ~     (p=0.138 n=10+8)
MapAppendAssign/Str/65536         101ns ±14%      81ns ± 8%  -19.82%  (p=0.000 n=10+9)
MapDelete/Int32/100              32.0ns ± 1%    35.0ns ± 2%   +9.59%  (p=0.000 n=9+10)
MapDelete/Int32/1000             27.0ns ± 3%    30.3ns ± 1%  +12.10%  (p=0.000 n=10+9)
MapDelete/Int32/10000            29.2ns ± 1%    32.9ns ± 2%  +12.80%  (p=0.000 n=10+10)
MapDelete/Int64/100              31.5ns ± 1%    35.7ns ± 2%  +13.16%  (p=0.000 n=10+10)
MapDelete/Int64/1000             27.0ns ± 2%    30.6ns ± 1%  +13.21%  (p=0.000 n=10+10)
MapDelete/Int64/10000            30.3ns ± 1%    34.4ns ± 3%  +13.47%  (p=0.000 n=10+10)
MapDelete/Str/100                23.4ns ± 8%    26.7ns ± 6%  +14.10%  (p=0.000 n=10+9)
MapDelete/Str/1000               31.0ns ± 2%    35.1ns ± 3%  +13.19%  (p=0.000 n=10+9)
MapDelete/Str/10000              38.8ns ± 1%    43.4ns ± 2%  +12.02%  (p=0.000 n=9+10)

Change-Id: I564ce0f40936589f0f9b837f7f2bbcca4c4a1070
Reviewed-on: https://go-review.googlesource.com/c/142437
Reviewed-by: Giovanni Bajo 
Reviewed-by: Martin Möhrmann 
Run-TryBot: Martin Möhrmann 
TryBot-Result: Gobot Gobot 
---
 src/runtime/map.go                | 46 ++++++++++++++++++-----
 src/runtime/map_benchmark_test.go | 62 +++++++++++++++++++++++++++++++
 src/runtime/map_fast32.go         | 23 ++++++++----
 src/runtime/map_fast64.go         | 23 ++++++++----
 src/runtime/map_faststr.go        | 31 ++++++++++++----
 5 files changed, 155 insertions(+), 30 deletions(-)

diff --git a/src/runtime/map.go b/src/runtime/map.go
index 3e368f929f..617e88faa4 100644
--- a/src/runtime/map.go
+++ b/src/runtime/map.go
@@ -89,11 +89,12 @@ const (
 	// Each bucket (including its overflow buckets, if any) will have either all or none of its
 	// entries in the evacuated* states (except during the evacuate() method, which only happens
 	// during map writes and thus no one else can observe the map during that time).
-	empty          = 0 // cell is empty
-	evacuatedEmpty = 1 // cell is empty, bucket is evacuated.
+	emptyRest      = 0 // this cell is empty, and there are no more non-empty cells at higher indexes or overflows.
+	emptyOne       = 1 // this cell is empty
 	evacuatedX     = 2 // key/value is valid.  Entry has been evacuated to first half of larger table.
 	evacuatedY     = 3 // same as above, but evacuated to second half of larger table.
-	minTopHash     = 4 // minimum tophash for a normal filled cell.
+	evacuatedEmpty = 4 // cell is empty, bucket is evacuated.
+	minTopHash     = 5 // minimum tophash for a normal filled cell.
 
 	// flags
 	iterator     = 1 // there may be an iterator using buckets
@@ -105,6 +106,11 @@ const (
 	noCheck = 1<<(8*sys.PtrSize) - 1
 )
 
+// isEmpty reports whether the given tophash array entry represents an empty bucket entry.
+func isEmpty(x uint8) bool {
+	return x <= emptyOne
+}
+
 // A header for a Go map.
 type hmap struct {
 	// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
@@ -197,7 +203,7 @@ func tophash(hash uintptr) uint8 {
 
 func evacuated(b *bmap) bool {
 	h := b.tophash[0]
-	return h > empty && h < minTopHash
+	return h > emptyOne && h < minTopHash
 }
 
 func (b *bmap) overflow(t *maptype) *bmap {
@@ -418,9 +424,13 @@ func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
 		}
 	}
 	top := tophash(hash)
+bucketloop:
 	for ; b != nil; b = b.overflow(t) {
 		for i := uintptr(0); i < bucketCnt; i++ {
 			if b.tophash[i] != top {
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
@@ -470,9 +480,13 @@ func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)
 		}
 	}
 	top := tophash(hash)
+bucketloop:
 	for ; b != nil; b = b.overflow(t) {
 		for i := uintptr(0); i < bucketCnt; i++ {
 			if b.tophash[i] != top {
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
@@ -511,9 +525,13 @@ func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe
 		}
 	}
 	top := tophash(hash)
+bucketloop:
 	for ; b != nil; b = b.overflow(t) {
 		for i := uintptr(0); i < bucketCnt; i++ {
 			if b.tophash[i] != top {
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
@@ -587,14 +605,18 @@ again:
 	var inserti *uint8
 	var insertk unsafe.Pointer
 	var val unsafe.Pointer
+bucketloop:
 	for {
 		for i := uintptr(0); i < bucketCnt; i++ {
 			if b.tophash[i] != top {
-				if b.tophash[i] == empty && inserti == nil {
+				if isEmpty(b.tophash[i]) && inserti == nil {
 					inserti = &b.tophash[i]
 					insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
 					val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
 				}
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
@@ -694,6 +716,9 @@ search:
 	for ; b != nil; b = b.overflow(t) {
 		for i := uintptr(0); i < bucketCnt; i++ {
 			if b.tophash[i] != top {
+				if b.tophash[i] == emptyRest {
+					break search
+				}
 				continue
 			}
 			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
@@ -718,7 +743,8 @@ search:
 			} else {
 				memclrNoHeapPointers(v, t.elem.size)
 			}
-			b.tophash[i] = empty
+			b.tophash[i] = emptyOne
+			// TODO: set up emptyRest here.
 			h.count--
 			break search
 		}
@@ -833,7 +859,9 @@ next:
 	}
 	for ; i < bucketCnt; i++ {
 		offi := (i + it.offset) & (bucketCnt - 1)
-		if b.tophash[offi] == empty || b.tophash[offi] == evacuatedEmpty {
+		if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {
+			// TODO: emptyRest is hard to use here, as we start iterating
+			// in the middle of a bucket. It's feasible, just tricky.
 			continue
 		}
 		k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize))
@@ -1092,7 +1120,7 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
 			v := add(k, bucketCnt*uintptr(t.keysize))
 			for i := 0; i < bucketCnt; i, k, v = i+1, add(k, uintptr(t.keysize)), add(v, uintptr(t.valuesize)) {
 				top := b.tophash[i]
-				if top == empty {
+				if isEmpty(top) {
 					b.tophash[i] = evacuatedEmpty
 					continue
 				}
@@ -1129,7 +1157,7 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
 					}
 				}
 
-				if evacuatedX+1 != evacuatedY {
+				if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
 					throw("bad evacuatedN")
 				}
 
diff --git a/src/runtime/map_benchmark_test.go b/src/runtime/map_benchmark_test.go
index 5681d5eeb8..d37dadcb56 100644
--- a/src/runtime/map_benchmark_test.go
+++ b/src/runtime/map_benchmark_test.go
@@ -5,6 +5,7 @@ package runtime_test
 
 import (
 	"fmt"
+	"math/rand"
 	"strconv"
 	"strings"
 	"testing"
@@ -206,6 +207,67 @@ func BenchmarkIntMap(b *testing.B) {
 	}
 }
 
+func BenchmarkMapFirst(b *testing.B) {
+	for n := 1; n <= 16; n++ {
+		b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
+			m := make(map[int]bool)
+			for i := 0; i < n; i++ {
+				m[i] = true
+			}
+			b.ResetTimer()
+			for i := 0; i < b.N; i++ {
+				_ = m[0]
+			}
+		})
+	}
+}
+func BenchmarkMapMid(b *testing.B) {
+	for n := 1; n <= 16; n++ {
+		b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
+			m := make(map[int]bool)
+			for i := 0; i < n; i++ {
+				m[i] = true
+			}
+			b.ResetTimer()
+			for i := 0; i < b.N; i++ {
+				_ = m[n>>1]
+			}
+		})
+	}
+}
+func BenchmarkMapLast(b *testing.B) {
+	for n := 1; n <= 16; n++ {
+		b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
+			m := make(map[int]bool)
+			for i := 0; i < n; i++ {
+				m[i] = true
+			}
+			b.ResetTimer()
+			for i := 0; i < b.N; i++ {
+				_ = m[n-1]
+			}
+		})
+	}
+}
+
+func BenchmarkMapCycle(b *testing.B) {
+	// Arrange map entries to be a permuation, so that
+	// we hit all entries, and one lookup is data dependent
+	// on the previous lookup.
+	const N = 3127
+	p := rand.New(rand.NewSource(1)).Perm(N)
+	m := map[int]int{}
+	for i := 0; i < N; i++ {
+		m[i] = p[i]
+	}
+	b.ResetTimer()
+	j := 0
+	for i := 0; i < b.N; i++ {
+		j = m[j]
+	}
+	sink = uint64(j)
+}
+
 // Accessing the same keys in a row.
 func benchmarkRepeatedLookup(b *testing.B, lookupKeySize int) {
 	m := make(map[string]bool)
diff --git a/src/runtime/map_fast32.go b/src/runtime/map_fast32.go
index 671558545a..063a5cbe3a 100644
--- a/src/runtime/map_fast32.go
+++ b/src/runtime/map_fast32.go
@@ -41,7 +41,7 @@ func mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
 	}
 	for ; b != nil; b = b.overflow(t) {
 		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 4) {
-			if *(*uint32)(k) == key && b.tophash[i] != empty {
+			if *(*uint32)(k) == key && !isEmpty(b.tophash[i]) {
 				return add(unsafe.Pointer(b), dataOffset+bucketCnt*4+i*uintptr(t.valuesize))
 			}
 		}
@@ -81,7 +81,7 @@ func mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool) {
 	}
 	for ; b != nil; b = b.overflow(t) {
 		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 4) {
-			if *(*uint32)(k) == key && b.tophash[i] != empty {
+			if *(*uint32)(k) == key && !isEmpty(b.tophash[i]) {
 				return add(unsafe.Pointer(b), dataOffset+bucketCnt*4+i*uintptr(t.valuesize)), true
 			}
 		}
@@ -120,13 +120,17 @@ again:
 	var inserti uintptr
 	var insertk unsafe.Pointer
 
+bucketloop:
 	for {
 		for i := uintptr(0); i < bucketCnt; i++ {
-			if b.tophash[i] == empty {
+			if isEmpty(b.tophash[i]) {
 				if insertb == nil {
 					inserti = i
 					insertb = b
 				}
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := *((*uint32)(add(unsafe.Pointer(b), dataOffset+i*4)))
@@ -206,13 +210,17 @@ again:
 	var inserti uintptr
 	var insertk unsafe.Pointer
 
+bucketloop:
 	for {
 		for i := uintptr(0); i < bucketCnt; i++ {
-			if b.tophash[i] == empty {
+			if isEmpty(b.tophash[i]) {
 				if insertb == nil {
 					inserti = i
 					insertb = b
 				}
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := *((*unsafe.Pointer)(add(unsafe.Pointer(b), dataOffset+i*4)))
@@ -286,7 +294,7 @@ func mapdelete_fast32(t *maptype, h *hmap, key uint32) {
 search:
 	for ; b != nil; b = b.overflow(t) {
 		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 4) {
-			if key != *(*uint32)(k) || b.tophash[i] == empty {
+			if key != *(*uint32)(k) || isEmpty(b.tophash[i]) {
 				continue
 			}
 			// Only clear key if there are pointers in it.
@@ -299,7 +307,8 @@ search:
 			} else {
 				memclrNoHeapPointers(v, t.elem.size)
 			}
-			b.tophash[i] = empty
+			b.tophash[i] = emptyOne
+			// TODO: emptyRest?
 			h.count--
 			break search
 		}
@@ -350,7 +359,7 @@ func evacuate_fast32(t *maptype, h *hmap, oldbucket uintptr) {
 			v := add(k, bucketCnt*4)
 			for i := 0; i < bucketCnt; i, k, v = i+1, add(k, 4), add(v, uintptr(t.valuesize)) {
 				top := b.tophash[i]
-				if top == empty {
+				if isEmpty(top) {
 					b.tophash[i] = evacuatedEmpty
 					continue
 				}
diff --git a/src/runtime/map_fast64.go b/src/runtime/map_fast64.go
index 164a4dd1ce..8270cf7b7d 100644
--- a/src/runtime/map_fast64.go
+++ b/src/runtime/map_fast64.go
@@ -41,7 +41,7 @@ func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
 	}
 	for ; b != nil; b = b.overflow(t) {
 		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
-			if *(*uint64)(k) == key && b.tophash[i] != empty {
+			if *(*uint64)(k) == key && !isEmpty(b.tophash[i]) {
 				return add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.valuesize))
 			}
 		}
@@ -81,7 +81,7 @@ func mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool) {
 	}
 	for ; b != nil; b = b.overflow(t) {
 		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
-			if *(*uint64)(k) == key && b.tophash[i] != empty {
+			if *(*uint64)(k) == key && !isEmpty(b.tophash[i]) {
 				return add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.valuesize)), true
 			}
 		}
@@ -120,13 +120,17 @@ again:
 	var inserti uintptr
 	var insertk unsafe.Pointer
 
+bucketloop:
 	for {
 		for i := uintptr(0); i < bucketCnt; i++ {
-			if b.tophash[i] == empty {
+			if isEmpty(b.tophash[i]) {
 				if insertb == nil {
 					insertb = b
 					inserti = i
 				}
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := *((*uint64)(add(unsafe.Pointer(b), dataOffset+i*8)))
@@ -206,13 +210,17 @@ again:
 	var inserti uintptr
 	var insertk unsafe.Pointer
 
+bucketloop:
 	for {
 		for i := uintptr(0); i < bucketCnt; i++ {
-			if b.tophash[i] == empty {
+			if isEmpty(b.tophash[i]) {
 				if insertb == nil {
 					insertb = b
 					inserti = i
 				}
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := *((*unsafe.Pointer)(add(unsafe.Pointer(b), dataOffset+i*8)))
@@ -286,7 +294,7 @@ func mapdelete_fast64(t *maptype, h *hmap, key uint64) {
 search:
 	for ; b != nil; b = b.overflow(t) {
 		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
-			if key != *(*uint64)(k) || b.tophash[i] == empty {
+			if key != *(*uint64)(k) || isEmpty(b.tophash[i]) {
 				continue
 			}
 			// Only clear key if there are pointers in it.
@@ -299,7 +307,8 @@ search:
 			} else {
 				memclrNoHeapPointers(v, t.elem.size)
 			}
-			b.tophash[i] = empty
+			b.tophash[i] = emptyOne
+			//TODO: emptyRest
 			h.count--
 			break search
 		}
@@ -350,7 +359,7 @@ func evacuate_fast64(t *maptype, h *hmap, oldbucket uintptr) {
 			v := add(k, bucketCnt*8)
 			for i := 0; i < bucketCnt; i, k, v = i+1, add(k, 8), add(v, uintptr(t.valuesize)) {
 				top := b.tophash[i]
-				if top == empty {
+				if isEmpty(top) {
 					b.tophash[i] = evacuatedEmpty
 					continue
 				}
diff --git a/src/runtime/map_faststr.go b/src/runtime/map_faststr.go
index bee62dfb03..8f505f90a6 100644
--- a/src/runtime/map_faststr.go
+++ b/src/runtime/map_faststr.go
@@ -28,7 +28,10 @@ func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
 			// short key, doing lots of comparisons is ok
 			for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
 				k := (*stringStruct)(kptr)
-				if k.len != key.len || b.tophash[i] == empty {
+				if k.len != key.len || isEmpty(b.tophash[i]) {
+					if b.tophash[i] == emptyRest {
+						break
+					}
 					continue
 				}
 				if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
@@ -41,7 +44,10 @@ func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
 		keymaybe := uintptr(bucketCnt)
 		for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
 			k := (*stringStruct)(kptr)
-			if k.len != key.len || b.tophash[i] == empty {
+			if k.len != key.len || isEmpty(b.tophash[i]) {
+				if b.tophash[i] == emptyRest {
+					break
+				}
 				continue
 			}
 			if k.str == key.str {
@@ -117,7 +123,10 @@ func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
 			// short key, doing lots of comparisons is ok
 			for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
 				k := (*stringStruct)(kptr)
-				if k.len != key.len || b.tophash[i] == empty {
+				if k.len != key.len || isEmpty(b.tophash[i]) {
+					if b.tophash[i] == emptyRest {
+						break
+					}
 					continue
 				}
 				if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
@@ -130,7 +139,10 @@ func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
 		keymaybe := uintptr(bucketCnt)
 		for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
 			k := (*stringStruct)(kptr)
-			if k.len != key.len || b.tophash[i] == empty {
+			if k.len != key.len || isEmpty(b.tophash[i]) {
+				if b.tophash[i] == emptyRest {
+					break
+				}
 				continue
 			}
 			if k.str == key.str {
@@ -220,13 +232,17 @@ again:
 	var inserti uintptr
 	var insertk unsafe.Pointer
 
+bucketloop:
 	for {
 		for i := uintptr(0); i < bucketCnt; i++ {
 			if b.tophash[i] != top {
-				if b.tophash[i] == empty && insertb == nil {
+				if isEmpty(b.tophash[i]) && insertb == nil {
 					insertb = b
 					inserti = i
 				}
+				if b.tophash[i] == emptyRest {
+					break bucketloop
+				}
 				continue
 			}
 			k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+i*2*sys.PtrSize))
@@ -320,7 +336,8 @@ search:
 			} else {
 				memclrNoHeapPointers(v, t.elem.size)
 			}
-			b.tophash[i] = empty
+			b.tophash[i] = emptyOne
+			// TODO: emptyRest
 			h.count--
 			break search
 		}
@@ -371,7 +388,7 @@ func evacuate_faststr(t *maptype, h *hmap, oldbucket uintptr) {
 			v := add(k, bucketCnt*2*sys.PtrSize)
 			for i := 0; i < bucketCnt; i, k, v = i+1, add(k, 2*sys.PtrSize), add(v, uintptr(t.valuesize)) {
 				top := b.tophash[i]
-				if top == empty {
+				if isEmpty(top) {
 					b.tophash[i] = evacuatedEmpty
 					continue
 				}

From 01f0dbbafce1b4f7cd3771268041d3caa40fc73b Mon Sep 17 00:00:00 2001
From: Austin Clements 
Date: Wed, 31 Oct 2018 20:46:57 -0400
Subject: [PATCH 35/76] cmd/compile: gofmt

I don't know how this file wasn't gofmted.

Change-Id: I9b3765ae63970b7bc4dc87107f546e64a78e2830
Reviewed-on: https://go-review.googlesource.com/c/146497
Reviewed-by: Ian Lance Taylor 
---
 src/cmd/compile/internal/gc/obj.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go
index 5976cffd06..a9dd092b67 100644
--- a/src/cmd/compile/internal/gc/obj.go
+++ b/src/cmd/compile/internal/gc/obj.go
@@ -81,7 +81,7 @@ func printObjHeader(bout *bio.Writer) {
 	if localpkg.Name == "main" {
 		fmt.Fprintf(bout, "main\n")
 	}
-	fmt.Fprintf(bout, "\n")     // header ends with blank line
+	fmt.Fprintf(bout, "\n") // header ends with blank line
 }
 
 func startArchiveEntry(bout *bio.Writer) int64 {

From 6c9b655ac1ab99eb2d3f944deb09b0339717f8cc Mon Sep 17 00:00:00 2001
From: Ian Lance Taylor 
Date: Wed, 31 Oct 2018 15:06:59 -0700
Subject: [PATCH 36/76] os: don't create files in local directory

Also, use a random temporary directory rather than os.TempDir.  Defer
removal of existing random temporary directories.

Change-Id: Id7549031cdf78a2bab28c07b6eeff621bdf6e49c
Reviewed-on: https://go-review.googlesource.com/c/146457
Run-TryBot: Ian Lance Taylor 
TryBot-Result: Gobot Gobot 
Reviewed-by: Tobias Klauser 
---
 src/os/removeall_test.go | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go
index 4b6f3e9256..93a6733d6a 100644
--- a/src/os/removeall_test.go
+++ b/src/os/removeall_test.go
@@ -8,18 +8,23 @@ import (
 	"fmt"
 	"io/ioutil"
 	. "os"
+	"path/filepath"
 	"runtime"
 	"strings"
 	"testing"
 )
 
 func TestRemoveAll(t *testing.T) {
-	tmpDir := TempDir()
-	// Work directory.
-	file := "file"
-	path := tmpDir + "/_TestRemoveAll_"
-	fpath := path + "/file"
-	dpath := path + "/dir"
+	tmpDir, err := ioutil.TempDir("", "TestRemoveAll-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer RemoveAll(tmpDir)
+
+	file := filepath.Join(tmpDir, "file")
+	path := filepath.Join(tmpDir, "_TestRemoveAll_")
+	fpath := filepath.Join(path, "file")
+	dpath := filepath.Join(path, "dir")
 
 	// Make a regular file and remove
 	fd, err := Create(file)
@@ -127,9 +132,13 @@ func TestRemoveAllLarge(t *testing.T) {
 		t.Skip("skipping in short mode")
 	}
 
-	tmpDir := TempDir()
-	// Work directory.
-	path := tmpDir + "/_TestRemoveAllLarge_"
+	tmpDir, err := ioutil.TempDir("", "TestRemoveAll-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer RemoveAll(tmpDir)
+
+	path := filepath.Join(tmpDir, "_TestRemoveAllLarge_")
 
 	// Make directory with 1000 files and remove.
 	if err := MkdirAll(path, 0777); err != nil {
@@ -168,6 +177,8 @@ func TestRemoveAllLongPath(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Could not create TempDir: %s", err)
 	}
+	defer RemoveAll(startPath)
+
 	err = Chdir(startPath)
 	if err != nil {
 		t.Fatalf("Could not chdir %s: %s", startPath, err)
@@ -215,6 +226,8 @@ func TestRemoveAllDot(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Could not create TempDir: %s", err)
 	}
+	defer RemoveAll(tempDir)
+
 	err = Chdir(tempDir)
 	if err != nil {
 		t.Fatalf("Could not chdir to tempdir: %s", err)

From 3f485e14b2ca9d6e74471f2c3c9aa0b732971d51 Mon Sep 17 00:00:00 2001
From: Austin Clements 
Date: Wed, 24 Oct 2018 17:59:25 -0400
Subject: [PATCH 37/76] cmd/compile: document Sym flags

Change-Id: Id14b417095628c7a1dc7a8e47bc28cfa392b5262
Reviewed-on: https://go-review.googlesource.com/c/146498
Reviewed-by: Robert Griesemer 
---
 src/cmd/compile/internal/types/sym.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/cmd/compile/internal/types/sym.go b/src/cmd/compile/internal/types/sym.go
index 49233ad386..b7fd7ae9fb 100644
--- a/src/cmd/compile/internal/types/sym.go
+++ b/src/cmd/compile/internal/types/sym.go
@@ -39,9 +39,9 @@ type Sym struct {
 const (
 	symOnExportList = 1 << iota // added to exportlist (no need to add again)
 	symUniq
-	symSiggen
-	symAsm
-	symAlgGen
+	symSiggen // type symbol has been generated
+	symAsm    // on asmlist, for writing to -asmhdr
+	symAlgGen // algorithm table has been generated
 )
 
 func (sym *Sym) OnExportList() bool { return sym.flags&symOnExportList != 0 }

From 21d2e15ee1bed44a7a1b8f775aff4a57cae9533a Mon Sep 17 00:00:00 2001
From: tkivisik 
Date: Wed, 17 Oct 2018 12:42:54 +0000
Subject: [PATCH 38/76] doc: improve language in contribute
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Previous wording was incorrect.

Change-Id: I91406c775161d9724ec60824c156e8e9a925bcd7
GitHub-Last-Rev: 2b6b4e136a78a431cc847a2717cbeae4341ed7bc
GitHub-Pull-Request: golang/go#28248
Reviewed-on: https://go-review.googlesource.com/c/142879
Reviewed-by: Daniel Martí 
---
 doc/contribute.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/contribute.html b/doc/contribute.html
index 2068ab8a3e..53088c99c6 100644
--- a/doc/contribute.html
+++ b/doc/contribute.html
@@ -393,8 +393,8 @@ Remember you can always visit Gerrit to see the fine-grained review.
 

It is not possible to fully sync Gerrit and GitHub, at least at the moment, so we recommend learning Gerrit. -It's different but powerful and familiarity -with help you understand the flow. +It's different but powerful and familiarity with it will help you understand +the flow.

Overview

From f5b695030b857b079a4cbcfb79564ff933c0c8f2 Mon Sep 17 00:00:00 2001 From: Travis Bischel Date: Sun, 9 Sep 2018 15:00:38 -0700 Subject: [PATCH 39/76] net/textproto: do not buffer a line if we know the next line is empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readContinuedLineSlice intends to buffer a continued line of text, where a continued line can continue through newlines so long as the next line begins with a space or tab. The current optimization is to not try to buffer and build a line if we immediately see that the next line begins with an ASCII character. This adds avoiding copying the line if we see that the next line is \n or \r\n as well. Notably, headers always end in \r\n\r\n. In the general, well formatted header case, we can now avoid ever allocating textproto.Reader's internal reusable buf. This can mildly be seen in net/http's BenchmarkClientServer: name old time/op new time/op delta ClientServer-4 66.4µs ± 0% 66.2µs ± 0% -0.35% (p=0.004 n=10+10) name old alloc/op new alloc/op delta ClientServer-4 4.87kB ± 0% 4.82kB ± 0% -1.01% (p=0.000 n=6+10) name old allocs/op new allocs/op delta ClientServer-4 64.0 ± 0% 63.0 ± 0% -1.56% (p=0.000 n=10+10) Change-Id: Id8c2ab69086ac481b90abda289396dcb7bfe8851 Reviewed-on: https://go-review.googlesource.com/c/134227 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/net/textproto/reader.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go index feb464b2f2..2c4f25d5ae 100644 --- a/src/net/textproto/reader.go +++ b/src/net/textproto/reader.go @@ -129,12 +129,13 @@ func (r *Reader) readContinuedLineSlice() ([]byte, error) { } // Optimistically assume that we have started to buffer the next line - // and it starts with an ASCII letter (the next header key), so we can - // avoid copying that buffered data around in memory and skipping over - // non-existent whitespace. + // and it starts with an ASCII letter (the next header key), or a blank + // line, so we can avoid copying that buffered data around in memory + // and skipping over non-existent whitespace. if r.R.Buffered() > 1 { - peek, err := r.R.Peek(1) - if err == nil && isASCIILetter(peek[0]) { + peek, _ := r.R.Peek(2) + if len(peek) > 0 && (isASCIILetter(peek[0]) || peek[0] == '\n') || + len(peek) == 2 && peek[0] == '\r' && peek[1] == '\n' { return trim(line), nil } } From 0ff6e5f1b48dfdff9ebf77796eeca1a5ec097def Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Seo Date: Wed, 19 Sep 2018 18:37:08 -0300 Subject: [PATCH 40/76] crypto/cipher: add VSX implementation of xorBytes for ppc64x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds asm implementations of xorBytes for ppc64x that takes advantage of VSX registers and instructions. name old time/op new time/op delta XORBytes/8Bytes-8 16.4ns ± 0% 11.1ns ± 0% -32.32% (p=0.000 n=5+4) XORBytes/128Bytes-8 45.6ns ± 0% 16.2ns ± 0% -64.50% (p=0.008 n=5+5) XORBytes/2048Bytes-8 433ns ±13% 129ns ± 1% -70.29% (p=0.000 n=5+4) XORBytes/32768Bytes-8 7.16µs ± 0% 1.83µs ± 0% -74.39% (p=0.008 n=5+5) name old speed new speed delta XORBytes/8Bytes-8 488MB/s ± 0% 721MB/s ± 0% +47.75% (p=0.016 n=5+4) XORBytes/128Bytes-8 2.80GB/s ± 0% 7.89GB/s ± 0% +181.33% (p=0.008 n=5+5) XORBytes/2048Bytes-8 4.77GB/s ±13% 15.87GB/s ± 0% +232.68% (p=0.016 n=5+4) XORBytes/32768Bytes-8 4.58GB/s ± 0% 17.88GB/s ± 0% +290.47% (p=0.008 n=5+5) Change-Id: Ic27d9b858f8ec2d597fdabc68a288d6844eba701 Reviewed-on: https://go-review.googlesource.com/c/145997 Run-TryBot: Carlos Eduardo Seo TryBot-Result: Gobot Gobot Reviewed-by: Lynn Boger --- src/crypto/cipher/xor_generic.go | 2 +- src/crypto/cipher/xor_ppc64x.go | 29 ++++++++++++++ src/crypto/cipher/xor_ppc64x.s | 66 ++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/crypto/cipher/xor_ppc64x.go create mode 100644 src/crypto/cipher/xor_ppc64x.s diff --git a/src/crypto/cipher/xor_generic.go b/src/crypto/cipher/xor_generic.go index 4d660b0a75..b7de60873c 100644 --- a/src/crypto/cipher/xor_generic.go +++ b/src/crypto/cipher/xor_generic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !amd64 +// +build !amd64,!ppc64,!ppc64le package cipher diff --git a/src/crypto/cipher/xor_ppc64x.go b/src/crypto/cipher/xor_ppc64x.go new file mode 100644 index 0000000000..8d2e43d327 --- /dev/null +++ b/src/crypto/cipher/xor_ppc64x.go @@ -0,0 +1,29 @@ +// Copyright 2018 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 ppc64 ppc64le + +package cipher + +// xorBytes xors the bytes in a and b. The destination should have enough +// space, otherwise xorBytes will panic. Returns the number of bytes xor'd. +func xorBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + if n == 0 { + return 0 + } + _ = dst[n-1] + xorBytesVSX(&dst[0], &a[0], &b[0], n) + return n +} + +func xorWords(dst, a, b []byte) { + xorBytes(dst, a, b) +} + +//go:noescape +func xorBytesVSX(dst, a, b *byte, n int) diff --git a/src/crypto/cipher/xor_ppc64x.s b/src/crypto/cipher/xor_ppc64x.s new file mode 100644 index 0000000000..af4d08bda3 --- /dev/null +++ b/src/crypto/cipher/xor_ppc64x.s @@ -0,0 +1,66 @@ +// Copyright 2018 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 ppc64 ppc64le + +#include "textflag.h" + +// func xorBytesVSX(dst, a, b *byte, n int) +TEXT ·xorBytesVSX(SB), NOSPLIT, $0 + MOVD dst+0(FP), R3 // R3 = dst + MOVD a+8(FP), R4 // R4 = a + MOVD b+16(FP), R5 // R5 = b + MOVD n+24(FP), R6 // R6 = n + + CMPU R6, $16, CR7 // Check if n ≥ 16 bytes + MOVD R0, R8 // R8 = index + CMPU R6, $8, CR6 // Check if 8 ≤ n < 16 bytes + BGE CR7, preloop16 + BLT CR6, small + + // Case for 8 ≤ n < 16 bytes + MOVD (R4)(R8), R14 // R14 = a[i,...,i+7] + MOVD (R5)(R8), R15 // R15 = b[i,...,i+7] + XOR R14, R15, R16 // R16 = a[] ^ b[] + SUB $8, R6 // n = n - 8 + MOVD R16, (R3)(R8) // Store to dst + ADD $8, R8 + + // Check if we're finished + CMP R6, R0 + BGT small + JMP done + + // Case for n ≥ 16 bytes +preloop16: + SRD $4, R6, R7 // Setup loop counter + MOVD R7, CTR + ANDCC $15, R6, R9 // Check for tailing bytes for later +loop16: + LXVD2X (R4)(R8), VS32 // VS32 = a[i,...,i+15] + LXVD2X (R5)(R8), VS33 // VS33 = b[i,...,i+15] + XXLXOR VS32, VS33, VS34 // VS34 = a[] ^ b[] + STXVD2X VS34, (R3)(R8) // Store to dst + ADD $16, R8 // Update index + BC 16, 0, loop16 // bdnz loop16 + + BEQ CR0, done + SLD $4, R7 + SUB R7, R6 // R6 = n - (R7 * 16) + + // Case for n < 8 bytes and tailing bytes from the + // previous cases. +small: + MOVD R6, CTR // Setup loop counter + +loop: + MOVBZ (R4)(R8), R14 // R14 = a[i] + MOVBZ (R5)(R8), R15 // R15 = b[i] + XOR R14, R15, R16 // R16 = a[i] ^ b[i] + MOVB R16, (R3)(R8) // Store to dst + ADD $1, R8 + BC 16, 0, loop // bdnz loop + +done: + RET From efc185029bf770894defe63cec2c72a4c84b2ee9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 1 Nov 2018 16:29:42 +0000 Subject: [PATCH 41/76] net: enable RFC 6555 Fast Fallback by default The Dialer.DualStack field is now meaningless and documented as deprecated. To disable fallback, set FallbackDelay to a negative value. Fixes #22225 Change-Id: Icc212fe07bb69d7651ab81e539b8b3e3d3372fa9 Reviewed-on: https://go-review.googlesource.com/c/146659 Reviewed-by: Ian Lance Taylor --- src/net/dial.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/net/dial.go b/src/net/dial.go index b1a5ca7cd5..76dcdc164c 100644 --- a/src/net/dial.go +++ b/src/net/dial.go @@ -44,16 +44,23 @@ type Dialer struct { // If nil, a local address is automatically chosen. LocalAddr Addr - // DualStack enables RFC 6555-compliant "Happy Eyeballs" - // dialing when the network is "tcp" and the host in the - // address parameter resolves to both IPv4 and IPv6 addresses. - // This allows a client to tolerate networks where one address - // family is silently broken. + // DualStack previously enabled RFC 6555 Fast Fallback + // support, also known as "Happy Eyeballs", in which IPv4 is + // tried soon if IPv6 appears to be misconfigured and + // hanging. + // + // Deprecated: Fast Fallback is enabled by default. To + // disable, set FallbackDelay to a negative value. DualStack bool // FallbackDelay specifies the length of time to wait before - // spawning a fallback connection, when DualStack is enabled. + // spawning a RFC 6555 Fast Fallback connection. That is, this + // is the amount of time to wait for IPv6 to succeed before + // assuming that IPv6 is misconfigured and falling back to + // IPv4. + // // If zero, a default delay of 300ms is used. + // A negative value disables Fast Fallback support. FallbackDelay time.Duration // KeepAlive specifies the keep-alive period for an active @@ -81,6 +88,8 @@ type Dialer struct { Control func(network, address string, c syscall.RawConn) error } +func (d *Dialer) dualStack() bool { return d.FallbackDelay >= 0 } + func minNonzeroTime(a, b time.Time) time.Time { if a.IsZero() { return b @@ -393,7 +402,7 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn } var primaries, fallbacks addrList - if d.DualStack && network == "tcp" { + if d.dualStack() && network == "tcp" { primaries, fallbacks = addrs.partition(isIPv4) } else { primaries = addrs From fa466a4f5a836fa5ac971e709d1e2d9df4a2e74d Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 1 Nov 2018 08:02:46 +0000 Subject: [PATCH 42/76] os: add support for long path names on freebsd RemoveAll Follow CL 146020 and enable RemoveAll based on Unlinkat and Openat on freebsd. Since the layout of syscall.Stat_t changes in FreeBSD 12, Fstatat needs a compatibility wrapper akin to Fstatat in x/sys/unix. See CL 138595 and CL 136816 for details. Updates #27029 Change-Id: I8851a5b7fa658eaa6e69a1693150b16d9a68f36a Reviewed-on: https://go-review.googlesource.com/c/146597 Run-TryBot: Tobias Klauser TryBot-Result: Gobot Gobot Reviewed-by: Yuval Pavel Zholkover Reviewed-by: Brad Fitzpatrick Reviewed-by: Ian Lance Taylor --- src/internal/syscall/unix/at.go | 2 +- src/internal/syscall/unix/at_freebsd.go | 47 +++++++++++++++++++ .../syscall/unix/at_sysnum_freebsd.go | 14 ------ src/os/removeall_at.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_test.go | 4 +- src/syscall/syscall_freebsd.go | 15 ++++++ src/syscall/zsyscall_freebsd_386.go | 15 ++++++ src/syscall/zsyscall_freebsd_amd64.go | 15 ++++++ src/syscall/zsyscall_freebsd_arm.go | 15 ++++++ 10 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 src/internal/syscall/unix/at_freebsd.go delete mode 100644 src/internal/syscall/unix/at_sysnum_freebsd.go diff --git a/src/internal/syscall/unix/at.go b/src/internal/syscall/unix/at.go index 1c05d2abe3..c007c87b20 100644 --- a/src/internal/syscall/unix/at.go +++ b/src/internal/syscall/unix/at.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux darwin freebsd openbsd netbsd dragonfly +// +build linux darwin openbsd netbsd dragonfly package unix diff --git a/src/internal/syscall/unix/at_freebsd.go b/src/internal/syscall/unix/at_freebsd.go new file mode 100644 index 0000000000..e171f4dbb5 --- /dev/null +++ b/src/internal/syscall/unix/at_freebsd.go @@ -0,0 +1,47 @@ +// Copyright 2018 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 unix + +import ( + "syscall" + "unsafe" +) + +const ( + AT_REMOVEDIR = 0x800 + AT_SYMLINK_NOFOLLOW = 0x200 +) + +func Unlinkat(dirfd int, path string, flags int) error { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall(syscall.SYS_UNLINKAT, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags)) + if errno != 0 { + return errno + } + + return nil +} + +func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + fd, _, errno := syscall.Syscall6(syscall.SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) + if errno != 0 { + return 0, errno + } + + return int(fd), nil +} + +func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { + return syscall.Fstatat(dirfd, path, stat, flags) +} diff --git a/src/internal/syscall/unix/at_sysnum_freebsd.go b/src/internal/syscall/unix/at_sysnum_freebsd.go deleted file mode 100644 index fe45e296d7..0000000000 --- a/src/internal/syscall/unix/at_sysnum_freebsd.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 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 unix - -import "syscall" - -const unlinkatTrap uintptr = syscall.SYS_UNLINKAT -const openatTrap uintptr = syscall.SYS_OPENAT -const fstatatTrap uintptr = syscall.SYS_FSTATAT - -const AT_REMOVEDIR = 0x800 -const AT_SYMLINK_NOFOLLOW = 0x200 diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go index b7ed2aa6d4..12d8152bec 100644 --- a/src/os/removeall_at.go +++ b/src/os/removeall_at.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux darwin openbsd netbsd dragonfly solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package os diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index 02af047d6e..f8af0da87f 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!darwin,!openbsd,!netbsd,!dragonfly,!solaris +// +build !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly,!solaris package os diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go index 93a6733d6a..4daf8c298b 100644 --- a/src/os/removeall_test.go +++ b/src/os/removeall_test.go @@ -162,7 +162,7 @@ func TestRemoveAllLarge(t *testing.T) { func TestRemoveAllLongPath(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "openbsd", "netbsd", "dragonfly", "solaris": + case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris": break default: t.Skip("skipping for not implemented platforms") @@ -212,7 +212,7 @@ func TestRemoveAllLongPath(t *testing.T) { func TestRemoveAllDot(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "openbsd", "netbsd", "dragonfly", "solaris": + case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris": break default: t.Skip("skipping for not implemented platforms") diff --git a/src/syscall/syscall_freebsd.go b/src/syscall/syscall_freebsd.go index d6f75098c0..19ace227d5 100644 --- a/src/syscall/syscall_freebsd.go +++ b/src/syscall/syscall_freebsd.go @@ -223,6 +223,20 @@ func Fstat(fd int, st *Stat_t) (err error) { return nil } +func Fstatat(fd int, path string, st *Stat_t, flags int) (err error) { + var oldStat stat_freebsd11_t + if supportsABI(_ino64First) { + return fstatat_freebsd12(fd, path, st, flags) + } + err = fstatat(fd, path, &oldStat, flags) + if err != nil { + return err + } + + st.convertFrom(&oldStat) + return nil +} + func Statfs(path string, st *Statfs_t) (err error) { var oldStatfs statfs_freebsd11_t if supportsABI(_ino64First) { @@ -403,6 +417,7 @@ func convertFromDirents11(buf []byte, old []byte) int { //sys Fpathconf(fd int, name int) (val int, err error) //sys fstat(fd int, stat *stat_freebsd11_t) (err error) //sys fstat_freebsd12(fd int, stat *Stat_t) (err error) = _SYS_FSTAT_FREEBSD12 +//sys fstatat(fd int, path string, stat *stat_freebsd11_t, flags int) (err error) //sys fstatat_freebsd12(fd int, path string, stat *Stat_t, flags int) (err error) = _SYS_FSTATAT_FREEBSD12 //sys fstatfs(fd int, stat *statfs_freebsd11_t) (err error) //sys fstatfs_freebsd12(fd int, stat *Statfs_t) (err error) = _SYS_FSTATFS_FREEBSD12 diff --git a/src/syscall/zsyscall_freebsd_386.go b/src/syscall/zsyscall_freebsd_386.go index ba7ea27f8d..8f4234c7e9 100644 --- a/src/syscall/zsyscall_freebsd_386.go +++ b/src/syscall/zsyscall_freebsd_386.go @@ -483,6 +483,21 @@ func fstat_freebsd12(fd int, stat *Stat_t) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func fstatat(fd int, path string, stat *stat_freebsd11_t, flags int) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall6(SYS_FSTATAT, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func fstatat_freebsd12(fd int, path string, stat *Stat_t, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/src/syscall/zsyscall_freebsd_amd64.go b/src/syscall/zsyscall_freebsd_amd64.go index 4b519a7f7f..baa7d68a7d 100644 --- a/src/syscall/zsyscall_freebsd_amd64.go +++ b/src/syscall/zsyscall_freebsd_amd64.go @@ -483,6 +483,21 @@ func fstat_freebsd12(fd int, stat *Stat_t) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func fstatat(fd int, path string, stat *stat_freebsd11_t, flags int) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall6(SYS_FSTATAT, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func fstatat_freebsd12(fd int, path string, stat *Stat_t, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/src/syscall/zsyscall_freebsd_arm.go b/src/syscall/zsyscall_freebsd_arm.go index e89707654b..16e4bc5414 100644 --- a/src/syscall/zsyscall_freebsd_arm.go +++ b/src/syscall/zsyscall_freebsd_arm.go @@ -483,6 +483,21 @@ func fstat_freebsd12(fd int, stat *Stat_t) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func fstatat(fd int, path string, stat *stat_freebsd11_t, flags int) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall6(SYS_FSTATAT, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func fstatat_freebsd12(fd int, path string, stat *Stat_t, flags int) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) From 02aa1aeeb1baf9bcfb8b9eeff9c92e93426ae512 Mon Sep 17 00:00:00 2001 From: Andrew Bonventre Date: Wed, 31 Oct 2018 12:03:15 -0400 Subject: [PATCH 43/76] doc/go1.12: initial add of release notes for Go 1.12 Change-Id: I9cb3c80ea397d964fe745b74d595df3fd8982a47 Reviewed-on: https://go-review.googlesource.com/c/146257 Reviewed-by: Brad Fitzpatrick --- doc/go1.12.html | 282 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 doc/go1.12.html diff --git a/doc/go1.12.html b/doc/go1.12.html new file mode 100644 index 0000000000..f4920f4670 --- /dev/null +++ b/doc/go1.12.html @@ -0,0 +1,282 @@ + + + + + + +

DRAFT RELEASE NOTES - Introduction to Go 1.12

+ +

+ + Go 1.12 is not yet released. These are work-in-progress + release notes. Go 1.12 is expected to be released in February 2019. + +

+ +

+ The latest Go release, version 1.12, arrives six months after Go 1.11. + Most of its changes are in TODO. + As always, the release maintains the Go 1 promise of compatibility. + We expect almost all Go programs to continue to compile and run as before. +

+ +

Changes to the language

+ +

+ There are no changes to the language specification. +

+ +

Tools

+ +

Build cache requirement

+ +

+ The build cache is now required as a step toward eliminating + $GOPATH/pkg. Setting the environment variable + GOCACHE=off to disable the + build cache + has no effect in Go 1.12. +

+ +

Godoc

+ +

+ In Go 1.12, godoc no longer has a command-line interface and + is only a web server. Users should use go doc + for command-line help output instead. +

+ +

Core library

+ +

+ All of the changes to the standard library are minor. +

+ +

Minor changes to the library

+ +

+ As always, there are various minor changes and updates to the library, + made with the Go 1 promise of compatibility + in mind. +

+ + + + + + +
build
+
+

+ TODO: https://golang.org/cl/61511: support frame-pointer for arm64 +

+ +
+ +
bytes, strings
+
+

+ TODO: https://golang.org/cl/137855: add ReplaceAll +

+ +

+ TODO: https://golang.org/cl/145098: fix Reader.UnreadRune returning without error on a zero Reader +

+ +
+ +
crypto/tls, net/http
+
+

+ TODO: https://golang.org/cl/143177: reject HTTP requests to HTTPS server +

+ +
+ +
expvar
+
+

+ TODO: https://golang.org/cl/139537: add Map.Delete +

+ +
+ +
fmt
+
+

+ TODO: https://golang.org/cl/142737: print maps in key-sorted order +

+ +
+ +
go/build, cmd/go
+
+

+ TODO: https://golang.org/cl/146023: add "hurd" as a GOOS value +

+ +
+ +
go/doc
+
+

+ TODO: https://golang.org/cl/140958: add new mode bit PreserveAST to control clearing of data in AST +

+ +
+ +
godoc, cmd/godoc
+
+

+ TODO: https://golang.org/cl/141397: remove CLI support +

+ +
+ +
image
+
+

+ TODO: https://golang.org/cl/118755: make RegisterFormat safe for concurrent use +

+ +
+ +
image/png
+
+

+ TODO: https://golang.org/cl/134235: pack image data for small bitdepth paletted images +

+ +
+ +
internal/poll
+
+

+ TODO: https://golang.org/cl/130676: use F_FULLFSYNC fcntl for FD.Fsync on OS X +

+ +
+ +
io
+
+

+ TODO: https://golang.org/cl/139457: export StringWriter +

+ +
+ +
math/bits
+
+

+ TODO: https://golang.org/cl/123157: add extended precision Add, Sub, Mul, Div +

+ +
+ +
net
+
+

+ TODO: https://golang.org/cl/113997: use splice(2) on Linux when reading from UnixConn, rework splice tests +

+ +
+ +
net/http
+
+

+ TODO: https://golang.org/cl/130115: add Client.CloseIdleConnections +

+ +

+ TODO: https://golang.org/cl/145398: in Transport, don't error on non-chunked response with Trailer header +

+ +
+ +
os
+
+

+ TODO: https://golang.org/cl/125443: add ExitCode method to ProcessState +

+ +

+ TODO: https://golang.org/cl/135075: add ModeCharDevice to ModeType +

+ +

+ TODO: https://golang.org/cl/139418: add UserHomeDir +

+ +
+ +
reflect
+
+

+ TODO: https://golang.org/cl/33572: add Value.MapRange method and MapIter type +

+ +
+ +
runtime
+
+

+ TODO: https://golang.org/cl/135395: use MADV_FREE on Linux if available +

+ +
+ +
strings
+
+

+ TODO: https://golang.org/cl/122835: add Builder.Cap +

+ +
+ +
syscall
+
+

+ TODO: https://golang.org/cl/125456: implement Unix Socket for Windows +

+ +

+ TODO: https://golang.org/cl/138595: FreeBSD 12 ino64 support +

+ +
+ +
syscall/js
+
+

+ TODO: https://golang.org/cl/141644: add Wrapper interface to support external Value wrapper types +

+ +

+ TODO: https://golang.org/cl/143137: make zero js.Value represent "undefined" +

+ +

+ TODO: https://golang.org/cl/144384: add the Value.Truthy method +

+ +
+ +
testing
+
+

+ TODO: https://golang.org/cl/139258: implement -benchtime=100x +

+ +
+ From 90df37769d6441ce550443fa7e0c008d38e53455 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 17 Oct 2018 08:04:01 -0700 Subject: [PATCH 44/76] cmd/cgo: rewrite pointer checking to use more function literals Fixes #14210 Fixes #25941 Change-Id: Idde2d032290da3edb742b5b4f6ffeb625f05b494 Reviewed-on: https://go-review.googlesource.com/c/142884 Reviewed-by: Brad Fitzpatrick --- misc/cgo/errors/ptr_test.go | 49 ++++++ src/cmd/cgo/gcc.go | 300 ++++++++++++++++++++++++------------ src/cmd/cgo/main.go | 1 + 3 files changed, 250 insertions(+), 100 deletions(-) diff --git a/misc/cgo/errors/ptr_test.go b/misc/cgo/errors/ptr_test.go index fe8dfff1d8..165c2d407c 100644 --- a/misc/cgo/errors/ptr_test.go +++ b/misc/cgo/errors/ptr_test.go @@ -357,6 +357,55 @@ var ptrTests = []ptrTest{ body: `r, _, _ := os.Pipe(); r.SetDeadline(time.Now().Add(C.US * time.Microsecond))`, fail: false, }, + { + // Test for double evaluation of channel receive. + name: "chan-recv", + c: `void f(char** p) {}`, + imports: []string{"time"}, + body: `c := make(chan []*C.char, 2); c <- make([]*C.char, 1); go func() { time.Sleep(10 * time.Second); panic("received twice from chan") }(); C.f(&(<-c)[0]);`, + fail: false, + }, + { + // Test that converting the address of a struct field + // to unsafe.Pointer still just checks that field. + // Issue #25941. + name: "struct-field", + c: `void f(void* p) {}`, + imports: []string{"unsafe"}, + support: `type S struct { p *int; a [8]byte; u uintptr }`, + body: `s := &S{p: new(int)}; C.f(unsafe.Pointer(&s.a))`, + fail: false, + }, + { + // Test that converting multiple struct field + // addresses to unsafe.Pointer still just checks those + // fields. Issue #25941. + name: "struct-field-2", + c: `void f(void* p, int r, void* s) {}`, + imports: []string{"unsafe"}, + support: `type S struct { a [8]byte; p *int; b int64; }`, + body: `s := &S{p: new(int)}; C.f(unsafe.Pointer(&s.a), 32, unsafe.Pointer(&s.b))`, + fail: false, + }, + { + // Test that second argument to cgoCheckPointer is + // evaluated when a deferred function is deferred, not + // when it is run. + name: "defer2", + c: `void f(char **pc) {}`, + support: `type S1 struct { s []*C.char }; type S2 struct { ps *S1 }`, + body: `p := &S2{&S1{[]*C.char{nil}}}; defer C.f(&p.ps.s[0]); p.ps = nil`, + fail: false, + }, + { + // Test that indexing into a function call still + // examines only the slice being indexed. + name: "buffer", + c: `void f(void *p) {}`, + imports: []string{"bytes", "unsafe"}, + body: `var b bytes.Buffer; b.WriteString("a"); C.f(unsafe.Pointer(&b.Bytes()[0]))`, + fail: false, + }, } func TestPointerChecks(t *testing.T) { diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 45bf90ffc2..1e746ce577 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -721,7 +721,9 @@ func (p *Package) mangleName(n *Name) { // This returns whether the package needs to import unsafe as _cgo_unsafe. func (p *Package) rewriteCalls(f *File) bool { needsUnsafe := false - for _, call := range f.Calls { + // Walk backward so that in C.f1(C.f2()) we rewrite C.f2 first. + for i := len(f.Calls) - 1; i >= 0; i-- { + call := f.Calls[i] // This is a call to C.xxx; set goname to "xxx". goname := call.Call.Fun.(*ast.SelectorExpr).Sel.Name if goname == "malloc" { @@ -768,103 +770,132 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { // We need to rewrite this call. // - // We are going to rewrite C.f(p) to - // func (_cgo0 ptype) { + // Rewrite C.f(p) to + // func() { + // _cgo0 := p // _cgoCheckPointer(_cgo0) // C.f(_cgo0) - // }(p) - // Using a function literal like this lets us do correct - // argument type checking, and works correctly if the call is - // deferred. + // }() + // Using a function literal like this lets us evaluate the + // function arguments only once while doing pointer checks. + // This is particularly useful when passing additional arguments + // to _cgoCheckPointer, as done in checkIndex and checkAddr. + // + // When the function argument is a conversion to unsafe.Pointer, + // we unwrap the conversion before checking the pointer, + // and then wrap again when calling C.f. This lets us check + // the real type of the pointer in some cases. See issue #25941. + // + // When the call to C.f is deferred, we use an additional function + // literal to evaluate the arguments at the right time. + // defer func() func() { + // _cgo0 := p + // return func() { + // _cgoCheckPointer(_cgo0) + // C.f(_cgo0) + // } + // }()() + // This works because the defer statement evaluates the first + // function literal in order to get the function to call. + var sb bytes.Buffer - sb.WriteString("func(") + sb.WriteString("func() ") + if call.Deferred { + sb.WriteString("func() ") + } needsUnsafe := false - - for i, param := range params { - if i > 0 { - sb.WriteString(", ") - } - - fmt.Fprintf(&sb, "_cgo%d ", i) - - ptype := p.rewriteUnsafe(param.Go) - if ptype != param.Go { - needsUnsafe = true - } - sb.WriteString(gofmtLine(ptype)) - } - - sb.WriteString(")") - result := false twoResults := false - - // Check whether this call expects two results. - for _, ref := range f.Ref { - if ref.Expr != &call.Call.Fun { - continue + if !call.Deferred { + // Check whether this call expects two results. + for _, ref := range f.Ref { + if ref.Expr != &call.Call.Fun { + continue + } + if ref.Context == ctxCall2 { + sb.WriteString("(") + result = true + twoResults = true + } + break } - if ref.Context == ctxCall2 { - sb.WriteString(" (") + + // Add the result type, if any. + if name.FuncType.Result != nil { + rtype := p.rewriteUnsafe(name.FuncType.Result.Go) + if rtype != name.FuncType.Result.Go { + needsUnsafe = true + } + sb.WriteString(gofmtLine(rtype)) result = true - twoResults = true } - break + + // Add the second result type, if any. + if twoResults { + if name.FuncType.Result == nil { + // An explicit void result looks odd but it + // seems to be how cgo has worked historically. + sb.WriteString("_Ctype_void") + } + sb.WriteString(", error)") + } } - // Add the result type, if any. - if name.FuncType.Result != nil { - rtype := p.rewriteUnsafe(name.FuncType.Result.Go) - if rtype != name.FuncType.Result.Go { - needsUnsafe = true - } - if !twoResults { - sb.WriteString(" ") - } - sb.WriteString(gofmtLine(rtype)) - result = true - } - - // Add the second result type, if any. - if twoResults { - if name.FuncType.Result == nil { - // An explicit void result looks odd but it - // seems to be how cgo has worked historically. - sb.WriteString("_Ctype_void") - } - sb.WriteString(", error)") - } - - sb.WriteString(" { ") + sb.WriteString("{ ") + // Define _cgoN for each argument value. + // Write _cgoCheckPointer calls to sbCheck. + var sbCheck bytes.Buffer for i, param := range params { - arg := args[i] - if !p.needsPointerCheck(f, param.Go, arg) { + arg := p.mangle(f, &args[i]) + + // Explicitly convert untyped constants to the + // parameter type, to avoid a type mismatch. + if p.isConst(f, arg) { + ptype := p.rewriteUnsafe(param.Go) + if ptype != param.Go { + needsUnsafe = true + } + arg = &ast.CallExpr{ + Fun: ptype, + Args: []ast.Expr{arg}, + } + } + + if !p.needsPointerCheck(f, param.Go, args[i]) { + fmt.Fprintf(&sb, "_cgo%d := %s; ", i, gofmtLine(arg)) continue } // Check for &a[i]. - if p.checkIndex(&sb, f, arg, i) { + if p.checkIndex(&sb, &sbCheck, arg, i) { continue } // Check for &x. - if p.checkAddr(&sb, arg, i) { + if p.checkAddr(&sb, &sbCheck, arg, i) { continue } - fmt.Fprintf(&sb, "_cgoCheckPointer(_cgo%d); ", i) + fmt.Fprintf(&sb, "_cgo%d := %s; ", i, gofmtLine(arg)) + fmt.Fprintf(&sbCheck, "_cgoCheckPointer(_cgo%d); ", i) } + if call.Deferred { + sb.WriteString("return func() { ") + } + + // Write out the calls to _cgoCheckPointer. + sb.WriteString(sbCheck.String()) + if result { sb.WriteString("return ") } // Now we are ready to call the C function. // To work smoothly with rewriteRef we leave the call in place - // and just insert our new arguments between the function - // and the old arguments. + // and just replace the old arguments with our new ones. f.Edit.Insert(f.offset(call.Call.Fun.Pos()), sb.String()) sb.Reset() @@ -875,9 +906,17 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { } fmt.Fprintf(&sb, "_cgo%d", i) } - sb.WriteString("); }") + sb.WriteString("); ") + if call.Deferred { + sb.WriteString("}") + } + sb.WriteString("}") + if call.Deferred { + sb.WriteString("()") + } + sb.WriteString("()") - f.Edit.Insert(f.offset(call.Call.Lparen), sb.String()) + f.Edit.Replace(f.offset(call.Call.Lparen), f.offset(call.Call.Rparen)+1, sb.String()) return needsUnsafe } @@ -986,11 +1025,44 @@ func (p *Package) hasPointer(f *File, t ast.Expr, top bool) bool { } } +// mangle replaces references to C names in arg with the mangled names. +// It removes the corresponding references in f.Ref, so that we don't +// try to do the replacement again in rewriteRef. +func (p *Package) mangle(f *File, arg *ast.Expr) ast.Expr { + f.walk(arg, ctxExpr, func(f *File, arg interface{}, context astContext) { + px, ok := arg.(*ast.Expr) + if !ok { + return + } + sel, ok := (*px).(*ast.SelectorExpr) + if !ok { + return + } + if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" { + return + } + + for _, r := range f.Ref { + if r.Expr == px { + *px = p.rewriteName(f, r) + r.Done = true + break + } + } + }) + return *arg +} + // checkIndex checks whether arg the form &a[i], possibly inside type -// conversions. If so, and if a has no side effects, it writes -// _cgoCheckPointer(_cgoNN, a) to sb and returns true. This tells -// _cgoCheckPointer to check the complete contents of the slice. -func (p *Package) checkIndex(sb *bytes.Buffer, f *File, arg ast.Expr, i int) bool { +// conversions. If so, it writes +// _cgoIndexNN := a +// _cgoNN := &cgoIndexNN[i] // with type conversions, if any +// to sb, and writes +// _cgoCheckPointer(_cgoNN, _cgoIndexNN) +// to sbCheck, and returns true. This tells _cgoCheckPointer to check +// the complete contents of the slice or array being indexed, but no +// other part of the memory allocation. +func (p *Package) checkIndex(sb, sbCheck *bytes.Buffer, arg ast.Expr, i int) bool { // Strip type conversions. x := arg for { @@ -1008,22 +1080,29 @@ func (p *Package) checkIndex(sb *bytes.Buffer, f *File, arg ast.Expr, i int) boo if !ok { return false } - if p.hasSideEffects(f, index.X) { - return false - } - fmt.Fprintf(sb, "_cgoCheckPointer(_cgo%d, %s); ", i, gofmtLine(index.X)) + fmt.Fprintf(sb, "_cgoIndex%d := %s; ", i, gofmtLine(index.X)) + origX := index.X + index.X = ast.NewIdent(fmt.Sprintf("_cgoIndex%d", i)) + fmt.Fprintf(sb, "_cgo%d := %s; ", i, gofmtLine(arg)) + index.X = origX + + fmt.Fprintf(sbCheck, "_cgoCheckPointer(_cgo%d, _cgoIndex%d); ", i, i) return true } // checkAddr checks whether arg has the form &x, possibly inside type -// conversions. If so it writes _cgoCheckPointer(_cgoNN, true) to sb -// and returns true. This tells _cgoCheckPointer to check just the -// contents of the pointer being passed, not any other part of the -// memory allocation. This is run after checkIndex, which looks for -// the special case of &a[i], which requires different checks. -func (p *Package) checkAddr(sb *bytes.Buffer, arg ast.Expr, i int) bool { +// conversions. If so it writes +// _cgoBaseNN := &x +// _cgoNN := _cgoBaseNN // with type conversions, if any +// to sb, and writes +// _cgoCheckPointer(_cgoBaseNN, true) +// to sbCheck, and returns true. This tells _cgoCheckPointer to check +// just the contents of the pointer being passed, not any other part +// of the memory allocation. This is run after checkIndex, which looks +// for the special case of &a[i], which requires different checks. +func (p *Package) checkAddr(sb, sbCheck *bytes.Buffer, arg ast.Expr, i int) bool { // Strip type conversions. px := &arg for { @@ -1037,27 +1116,20 @@ func (p *Package) checkAddr(sb *bytes.Buffer, arg ast.Expr, i int) bool { return false } + fmt.Fprintf(sb, "_cgoBase%d := %s; ", i, gofmtLine(*px)) + + origX := *px + *px = ast.NewIdent(fmt.Sprintf("_cgoBase%d", i)) + fmt.Fprintf(sb, "_cgo%d := %s; ", i, gofmtLine(arg)) + *px = origX + // Use "0 == 0" to do the right thing in the unlikely event // that "true" is shadowed. - fmt.Fprintf(sb, "_cgoCheckPointer(_cgo%d, 0 == 0); ", i) + fmt.Fprintf(sbCheck, "_cgoCheckPointer(_cgoBase%d, 0 == 0); ", i) return true } -// hasSideEffects returns whether the expression x has any side -// effects. x is an expression, not a statement, so the only side -// effect is a function call. -func (p *Package) hasSideEffects(f *File, x ast.Expr) bool { - found := false - f.walk(x, ctxExpr, - func(f *File, x interface{}, context astContext) { - if _, ok := x.(*ast.CallExpr); ok { - found = true - } - }) - return found -} - // isType returns whether the expression is definitely a type. // This is conservative--it returns false for an unknown identifier. func (p *Package) isType(t ast.Expr) bool { @@ -1087,6 +1159,9 @@ func (p *Package) isType(t ast.Expr) bool { return true } + if strings.HasPrefix(t.Name, "_Ctype_") { + return true + } case *ast.StarExpr: return p.isType(t.X) case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, @@ -1097,6 +1172,29 @@ func (p *Package) isType(t ast.Expr) bool { return false } +// isConst returns whether x is an untyped constant. +func (p *Package) isConst(f *File, x ast.Expr) bool { + switch x := x.(type) { + case *ast.BasicLit: + return true + case *ast.SelectorExpr: + id, ok := x.X.(*ast.Ident) + if !ok || id.Name != "C" { + return false + } + name := f.Name[x.Sel.Name] + if name != nil { + return name.IsConst() + } + case *ast.Ident: + return x.Name == "nil" || + strings.HasPrefix(x.Name, "_Ciconst_") || + strings.HasPrefix(x.Name, "_Cfconst_") || + strings.HasPrefix(x.Name, "_Csconst_") + } + return false +} + // rewriteUnsafe returns a version of t with references to unsafe.Pointer // rewritten to use _cgo_unsafe.Pointer instead. func (p *Package) rewriteUnsafe(t ast.Expr) ast.Expr { @@ -1205,11 +1303,13 @@ func (p *Package) rewriteRef(f *File) { *r.Expr = expr // Record source-level edit for cgo output. - repl := gofmt(expr) - if r.Name.Kind != "type" { - repl = "(" + repl + ")" + if !r.Done { + repl := gofmt(expr) + if r.Name.Kind != "type" { + repl = "(" + repl + ")" + } + f.Edit.Replace(f.offset(old.Pos()), f.offset(old.End()), repl) } - f.Edit.Replace(f.offset(old.Pos()), f.offset(old.End()), repl) } // Remove functions only used as expressions, so their respective diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index 5bcb9754d7..626ffe2390 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -88,6 +88,7 @@ type Ref struct { Name *Name Expr *ast.Expr Context astContext + Done bool } func (r *Ref) Pos() token.Pos { From 484fc06849fa54d168af1d513c37d1b22bf38e7c Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 31 Oct 2018 13:18:17 -0400 Subject: [PATCH 45/76] cmd/link: don't link sections not named .o For many build systems, modular static analysis is most conveniently implemented by saving analysis facts (which are analogous to export data) in an additional section in the archive file, similar to __PKGDEF. See golang.org/x/tools/go/analysis for an overview. Because such sections are not object files, the linker must not attempt to link them. This change causes the linker to skip special sections whose name does not end with .o (and is short enough not to be truncated). Fixes #28429 Change-Id: I830852decf868cb017263308b114f72838032993 Reviewed-on: https://go-review.googlesource.com/c/146297 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/cmd/link/internal/ld/lib.go | 7 +++++ src/cmd/link/link_test.go | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 2075066e31..4b23ecc483 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -856,6 +856,13 @@ func loadobjfile(ctxt *Link, lib *sym.Library) { continue } + // Skip other special (non-object-file) sections that + // build tools may have added. Such sections must have + // short names so that the suffix is not truncated. + if len(arhdr.name) < 16 && !strings.HasSuffix(arhdr.name, ".o") { + continue + } + pname := fmt.Sprintf("%s(%s)", lib.File, arhdr.name) l = atolwhex(arhdr.size) ldobj(ctxt, f, lib, l, pname, lib.File) diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 4ec03abc85..6ed751abb5 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" ) @@ -70,3 +71,48 @@ func main() {} t.Fatalf("failed to link main.o: %v, output: %s\n", err, out) } } + +// TestIssue28429 ensures that the linker does not attempt to link +// sections not named *.o. Such sections may be used by a build system +// to, for example, save facts produced by a modular static analysis +// such as golang.org/x/tools/go/analysis. +func TestIssue28429(t *testing.T) { + testenv.MustHaveGoBuild(t) + + tmpdir, err := ioutil.TempDir("", "issue28429-") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpdir) + + write := func(name, content string) { + err := ioutil.WriteFile(filepath.Join(tmpdir, name), []byte(content), 0666) + if err != nil { + t.Fatal(err) + } + } + + runGo := func(args ...string) { + cmd := exec.Command(testenv.GoToolPath(t), args...) + cmd.Dir = tmpdir + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("'go %s' failed: %v, output: %s", + strings.Join(args, " "), err, out) + } + } + + // Compile a main package. + write("main.go", "package main; func main() {}") + runGo("tool", "compile", "-p", "main", "main.go") + runGo("tool", "pack", "c", "main.a", "main.o") + + // Add an extra section with a short, non-.o name. + // This simulates an alternative build system. + write(".facts", "this is not an object file") + runGo("tool", "pack", "r", "main.a", ".facts") + + // Verify that the linker does not attempt + // to compile the extra section. + runGo("tool", "link", "main.a") +} From 914a25c0eb8aacdbb5cddd3e7d5e6ee7b019f88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20M=C3=B6hrmann?= Date: Thu, 1 Nov 2018 11:20:11 +0100 Subject: [PATCH 46/76] runtime: only check the existence of variables in gdb info locals test As discussed in golang.org/cl/28499: Only test that all expected variables are listed in 'info locals' since different versions of gdb print variables in different order and with differing amount of information and formats. Fixes #28499 Change-Id: I76627351170b5fdf2bf8cbf143e54f628b45dc4e Reviewed-on: https://go-review.googlesource.com/c/146598 Reviewed-by: Heschi Kreinick --- src/runtime/runtime-gdb_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index ee63285ec5..7672e45b03 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -262,15 +262,13 @@ func testGdbPython(t *testing.T, cgo bool) { // However, the newer dwarf location list code reconstituted // aggregates from their fields and reverted their printing // back to its original form. + // Only test that all variables are listed in 'info locals' since + // different versions of gdb print variables in different + // order and with differing amount of information and formats. - infoLocalsRe1 := regexp.MustCompile(`slicevar *= *\[\]string *= *{"def"}`) - // Format output from gdb v8.2 - infoLocalsRe2 := regexp.MustCompile(`^slicevar = .*\nmapvar = .*\nstrvar = 0x[0-9a-f]+ "abc"`) - // Format output from gdb v7.7 - infoLocalsRe3 := regexp.MustCompile(`^mapvar = .*\nstrvar = "abc"\nslicevar *= *\[\]string`) - if bl := blocks["info locals"]; !infoLocalsRe1.MatchString(bl) && - !infoLocalsRe2.MatchString(bl) && - !infoLocalsRe3.MatchString(bl) { + if bl := blocks["info locals"]; !strings.Contains(bl, "slicevar") || + !strings.Contains(bl, "mapvar") || + !strings.Contains(bl, "strvar") { t.Fatalf("info locals failed: %s", bl) } From 2182bb097dc5cacf556f89e4e7d2cfdd96882237 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Thu, 27 Sep 2018 19:29:39 -0400 Subject: [PATCH 47/76] .github: don't render author-facing text in ISSUE_TEMPLATE Fixes #27914 Change-Id: Ic359a099661e959eb25d0f763ea16a6f48a3d4c7 Reviewed-on: https://go-review.googlesource.com/c/138295 Reviewed-by: Andrew Bonventre --- .github/ISSUE_TEMPLATE | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index d3c3a2d262..9d5156b2e2 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -1,24 +1,36 @@ -Please answer these questions before submitting your issue. Thanks! - + ### What version of Go are you using (`go version`)? +
+$ go version
+
+
### Does this issue reproduce with the latest release? + ### What operating system and processor architecture are you using (`go env`)? +
go env Output
+$ go env
+
+
### What did you do? + + ### What did you expect to see? + ### What did you see instead? From a70a2a8ad69f481d5fcaf9e006e224fbab7df754 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 1 Nov 2018 22:06:51 -0700 Subject: [PATCH 48/76] cmd/cgo: don't update each call in place Updating each call in place broke when there were multiple cgo calls used as arguments to another cgo call where some required rewriting. Instead, rewrite calls to strings via the existing mangling mechanism, and only substitute the top level call in place. Fixes #28540 Change-Id: Ifd66f04c205adc4ad6dd5ee8e79e57dce17e86bb Reviewed-on: https://go-review.googlesource.com/c/146860 Reviewed-by: Brad Fitzpatrick Reviewed-by: Dmitri Shuralyov --- misc/cgo/test/twoargs.go | 22 +++++++ src/cmd/cgo/gcc.go | 120 ++++++++++++++++++++++++++------------- src/cmd/cgo/main.go | 1 + 3 files changed, 104 insertions(+), 39 deletions(-) create mode 100644 misc/cgo/test/twoargs.go diff --git a/misc/cgo/test/twoargs.go b/misc/cgo/test/twoargs.go new file mode 100644 index 0000000000..ca0534ca31 --- /dev/null +++ b/misc/cgo/test/twoargs.go @@ -0,0 +1,22 @@ +// Copyright 2018 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. + +// Crash from call with two arguments that need pointer checking. +// No runtime test; just make sure it compiles. + +package cgotest + +/* +static void twoargs1(void *p, int n) {} +static void *twoargs2() { return 0; } +static int twoargs3(void * p) { return 0; } +*/ +import "C" + +import "unsafe" + +func twoargsF() { + v := []string{} + C.twoargs1(C.twoargs2(), C.twoargs3(unsafe.Pointer(&v))) +} diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 1e746ce577..e8be785bf6 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -722,20 +722,18 @@ func (p *Package) mangleName(n *Name) { func (p *Package) rewriteCalls(f *File) bool { needsUnsafe := false // Walk backward so that in C.f1(C.f2()) we rewrite C.f2 first. - for i := len(f.Calls) - 1; i >= 0; i-- { - call := f.Calls[i] - // This is a call to C.xxx; set goname to "xxx". - goname := call.Call.Fun.(*ast.SelectorExpr).Sel.Name - if goname == "malloc" { + for _, call := range f.Calls { + if call.Done { continue } - name := f.Name[goname] - if name.Kind != "func" { - // Probably a type conversion. - continue - } - if p.rewriteCall(f, call, name) { - needsUnsafe = true + start := f.offset(call.Call.Pos()) + end := f.offset(call.Call.End()) + str, nu := p.rewriteCall(f, call) + if str != "" { + f.Edit.Replace(start, end, str) + if nu { + needsUnsafe = true + } } } return needsUnsafe @@ -745,8 +743,29 @@ func (p *Package) rewriteCalls(f *File) bool { // If any pointer checks are required, we rewrite the call into a // function literal that calls _cgoCheckPointer for each pointer // argument and then calls the original function. -// This returns whether the package needs to import unsafe as _cgo_unsafe. -func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { +// This returns the rewritten call and whether the package needs to +// import unsafe as _cgo_unsafe. +// If it returns the empty string, the call did not need to be rewritten. +func (p *Package) rewriteCall(f *File, call *Call) (string, bool) { + // This is a call to C.xxx; set goname to "xxx". + // It may have already been mangled by rewriteName. + var goname string + switch fun := call.Call.Fun.(type) { + case *ast.SelectorExpr: + goname = fun.Sel.Name + case *ast.Ident: + goname = strings.TrimPrefix(fun.Name, "_C2func_") + goname = strings.TrimPrefix(goname, "_Cfunc_") + } + if goname == "" || goname == "malloc" { + return "", false + } + name := f.Name[goname] + if name == nil || name.Kind != "func" { + // Probably a type conversion. + return "", false + } + params := name.FuncType.Params args := call.Call.Args @@ -754,7 +773,7 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { // less than the number of parameters. // This will be caught when the generated file is compiled. if len(args) < len(params) { - return false + return "", false } any := false @@ -765,7 +784,7 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { } } if !any { - return false + return "", false } // We need to rewrite this call. @@ -848,7 +867,10 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { // Write _cgoCheckPointer calls to sbCheck. var sbCheck bytes.Buffer for i, param := range params { - arg := p.mangle(f, &args[i]) + arg, nu := p.mangle(f, &args[i]) + if nu { + needsUnsafe = true + } // Explicitly convert untyped constants to the // parameter type, to avoid a type mismatch. @@ -893,12 +915,12 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { sb.WriteString("return ") } - // Now we are ready to call the C function. - // To work smoothly with rewriteRef we leave the call in place - // and just replace the old arguments with our new ones. - f.Edit.Insert(f.offset(call.Call.Fun.Pos()), sb.String()) + m, nu := p.mangle(f, &call.Call.Fun) + if nu { + needsUnsafe = true + } + sb.WriteString(gofmtLine(m)) - sb.Reset() sb.WriteString("(") for i := range params { if i > 0 { @@ -916,9 +938,7 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool { } sb.WriteString("()") - f.Edit.Replace(f.offset(call.Call.Lparen), f.offset(call.Call.Rparen)+1, sb.String()) - - return needsUnsafe + return sb.String(), needsUnsafe } // needsPointerCheck returns whether the type t needs a pointer check. @@ -1025,32 +1045,54 @@ func (p *Package) hasPointer(f *File, t ast.Expr, top bool) bool { } } -// mangle replaces references to C names in arg with the mangled names. -// It removes the corresponding references in f.Ref, so that we don't -// try to do the replacement again in rewriteRef. -func (p *Package) mangle(f *File, arg *ast.Expr) ast.Expr { +// mangle replaces references to C names in arg with the mangled names, +// rewriting calls when it finds them. +// It removes the corresponding references in f.Ref and f.Calls, so that we +// don't try to do the replacement again in rewriteRef or rewriteCall. +func (p *Package) mangle(f *File, arg *ast.Expr) (ast.Expr, bool) { + needsUnsafe := false f.walk(arg, ctxExpr, func(f *File, arg interface{}, context astContext) { px, ok := arg.(*ast.Expr) if !ok { return } sel, ok := (*px).(*ast.SelectorExpr) - if !ok { - return - } - if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" { + if ok { + if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" { + return + } + + for _, r := range f.Ref { + if r.Expr == px { + *px = p.rewriteName(f, r) + r.Done = true + break + } + } + return } - for _, r := range f.Ref { - if r.Expr == px { - *px = p.rewriteName(f, r) - r.Done = true - break + call, ok := (*px).(*ast.CallExpr) + if !ok { + return + } + + for _, c := range f.Calls { + if !c.Done && c.Call.Lparen == call.Lparen { + cstr, nu := p.rewriteCall(f, c) + if cstr != "" { + // Smuggle the rewritten call through an ident. + *px = ast.NewIdent(cstr) + if nu { + needsUnsafe = true + } + c.Done = true + } } } }) - return *arg + return *arg, needsUnsafe } // checkIndex checks whether arg the form &a[i], possibly inside type diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index 626ffe2390..3098a4a63d 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -81,6 +81,7 @@ func nameKeys(m map[string]*Name) []string { type Call struct { Call *ast.CallExpr Deferred bool + Done bool } // A Ref refers to an expression of the form C.xxx in the AST. From d154ef60a0c88be98c70bbe1c5735fb7b1f45250 Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Sun, 21 Oct 2018 14:57:58 +1100 Subject: [PATCH 49/76] path/filepath: change IsAbs("NUL") to return true This CL changes IsAbs to return true for "NUL" and other Windows reserved filenames (search https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file for NUL for details). os.Open("NUL") and os.Stat("NUL") work regardless of what current directory is, and it is mistake to join "NUL" with current directory when building full path. Changing IsAbs("NUL") to return true fixes that mistake. Fixes #28035 Change-Id: Ife8f8aee48400702613ede8fc6834fd43e6e0f03 Reviewed-on: https://go-review.googlesource.com/c/145220 Run-TryBot: Alex Brainman TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/cmd/go/testdata/script/test_devnull.txt | 13 +++++++++++ src/path/filepath/path_test.go | 5 ++++ src/path/filepath/path_windows.go | 26 +++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/cmd/go/testdata/script/test_devnull.txt diff --git a/src/cmd/go/testdata/script/test_devnull.txt b/src/cmd/go/testdata/script/test_devnull.txt new file mode 100644 index 0000000000..c414e59ba3 --- /dev/null +++ b/src/cmd/go/testdata/script/test_devnull.txt @@ -0,0 +1,13 @@ +# go test -c -o NUL +# should work (see golang.org/issue/28035). +cd x +go test -o=$devnull -c +! exists x.test$exe + +-- x/x_test.go -- +package x_test +import ( + "testing" +) +func TestNUL(t *testing.T) { +} diff --git a/src/path/filepath/path_test.go b/src/path/filepath/path_test.go index eddae4755b..3434ea2e6e 100644 --- a/src/path/filepath/path_test.go +++ b/src/path/filepath/path_test.go @@ -751,6 +751,11 @@ func TestIsAbs(t *testing.T) { for _, test := range isabstests { tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) } + // Test reserved names. + tests = append(tests, IsAbsTest{os.DevNull, true}) + tests = append(tests, IsAbsTest{"NUL", true}) + tests = append(tests, IsAbsTest{"nul", true}) + tests = append(tests, IsAbsTest{"CON", true}) } else { tests = isabstests } diff --git a/src/path/filepath/path_windows.go b/src/path/filepath/path_windows.go index 6a144d9e0b..445c868e41 100644 --- a/src/path/filepath/path_windows.go +++ b/src/path/filepath/path_windows.go @@ -13,8 +13,34 @@ func isSlash(c uint8) bool { return c == '\\' || c == '/' } +// reservedNames lists reserved Windows names. Search for PRN in +// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file +// for details. +var reservedNames = []string{ + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", +} + +// isReservedName returns true, if path is Windows reserved name. +// See reservedNames for the full list. +func isReservedName(path string) bool { + if len(path) == 0 { + return false + } + for _, reserved := range reservedNames { + if strings.EqualFold(path, reserved) { + return true + } + } + return false +} + // IsAbs reports whether the path is absolute. func IsAbs(path string) (b bool) { + if isReservedName(path) { + return true + } l := volumeNameLen(path) if l == 0 { return false From f10815898c0732e2e6cdb697d6f95f33f8650b4e Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Sat, 20 Oct 2018 16:30:57 +1100 Subject: [PATCH 50/76] os: use CreateFile for Stat of symlinks Stat uses Windows FindFirstFile + CreateFile to gather symlink information - FindFirstFile determines if file is a symlink, and then CreateFile follows symlink to capture target details. Lstat only uses FindFirstFile. This CL replaces current approach with just a call to CreateFile. Lstat uses FILE_FLAG_OPEN_REPARSE_POINT flag, that instructs CreateFile not to follow symlink. Other than that both Stat and Lstat look the same now. New code is simpler. CreateFile + GetFileInformationByHandle (unlike FindFirstFile) does not report reparse tag of a file. I tried to ignore reparse tag altogether. And it works for symlinks and mount points. Unfortunately (see https://github.com/moby/moby/issues/37026), files on deduped disk volumes are reported with FILE_ATTRIBUTE_REPARSE_POINT attribute set and reparse tag set to IO_REPARSE_TAG_DEDUP. So, if we ignore reparse tag, Lstat interprets deduped volume files as symlinks. That is incorrect. So I had to add GetFileInformationByHandleEx call to gather reparse tag after calling CreateFile and GetFileInformationByHandle. Fixes #27225 Fixes #27515 Change-Id: If60233bcf18836c147597cc17450d82f3f88c623 Reviewed-on: https://go-review.googlesource.com/c/143578 Run-TryBot: Alex Brainman TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor Reviewed-by: Kirill Kolyshkin --- src/internal/syscall/windows/mksyscall.go | 2 +- .../syscall/windows/symlink_windows.go | 25 ++++++ .../syscall/windows/zsyscall_windows.go | 57 +++++++----- src/os/stat_test.go | 10 +-- src/os/stat_windows.go | 87 +++++++++++-------- src/os/types_windows.go | 69 ++------------- 6 files changed, 127 insertions(+), 123 deletions(-) diff --git a/src/internal/syscall/windows/mksyscall.go b/src/internal/syscall/windows/mksyscall.go index 23efb6a01a..a8edafb3c3 100644 --- a/src/internal/syscall/windows/mksyscall.go +++ b/src/internal/syscall/windows/mksyscall.go @@ -4,4 +4,4 @@ package windows -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go security_windows.go psapi_windows.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go security_windows.go psapi_windows.go symlink_windows.go diff --git a/src/internal/syscall/windows/symlink_windows.go b/src/internal/syscall/windows/symlink_windows.go index cc2163e933..b64d058d13 100644 --- a/src/internal/syscall/windows/symlink_windows.go +++ b/src/internal/syscall/windows/symlink_windows.go @@ -11,4 +11,29 @@ const ( // symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972) SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 + + // FileInformationClass values + FileBasicInfo = 0 // FILE_BASIC_INFO + FileStandardInfo = 1 // FILE_STANDARD_INFO + FileNameInfo = 2 // FILE_NAME_INFO + FileStreamInfo = 7 // FILE_STREAM_INFO + FileCompressionInfo = 8 // FILE_COMPRESSION_INFO + FileAttributeTagInfo = 9 // FILE_ATTRIBUTE_TAG_INFO + FileIdBothDirectoryInfo = 0xa // FILE_ID_BOTH_DIR_INFO + FileIdBothDirectoryRestartInfo = 0xb // FILE_ID_BOTH_DIR_INFO + FileRemoteProtocolInfo = 0xd // FILE_REMOTE_PROTOCOL_INFO + FileFullDirectoryInfo = 0xe // FILE_FULL_DIR_INFO + FileFullDirectoryRestartInfo = 0xf // FILE_FULL_DIR_INFO + FileStorageInfo = 0x10 // FILE_STORAGE_INFO + FileAlignmentInfo = 0x11 // FILE_ALIGNMENT_INFO + FileIdInfo = 0x12 // FILE_ID_INFO + FileIdExtdDirectoryInfo = 0x13 // FILE_ID_EXTD_DIR_INFO + FileIdExtdDirectoryRestartInfo = 0x14 // FILE_ID_EXTD_DIR_INFO ) + +type FILE_ATTRIBUTE_TAG_INFO struct { + FileAttributes uint32 + ReparseTag uint32 +} + +//sys GetFileInformationByHandleEx(handle syscall.Handle, class uint32, info *byte, bufsize uint32) (err error) diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 550a8a5bd4..2212697b1b 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -44,28 +44,29 @@ var ( moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll")) modpsapi = syscall.NewLazyDLL(sysdll.Add("psapi.dll")) - procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") - procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") - procMoveFileExW = modkernel32.NewProc("MoveFileExW") - procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW") - procWSASocketW = modws2_32.NewProc("WSASocketW") - procGetACP = modkernel32.NewProc("GetACP") - procGetConsoleCP = modkernel32.NewProc("GetConsoleCP") - procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar") - procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") - procNetShareAdd = modnetapi32.NewProc("NetShareAdd") - procNetShareDel = modnetapi32.NewProc("NetShareDel") - procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW") - procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") - procRevertToSelf = modadvapi32.NewProc("RevertToSelf") - procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") - procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") - procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") - procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx") - procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation") - procGetProfilesDirectoryW = moduserenv.NewProc("GetProfilesDirectoryW") - procNetUserGetLocalGroups = modnetapi32.NewProc("NetUserGetLocalGroups") - procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") + procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") + procMoveFileExW = modkernel32.NewProc("MoveFileExW") + procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW") + procWSASocketW = modws2_32.NewProc("WSASocketW") + procGetACP = modkernel32.NewProc("GetACP") + procGetConsoleCP = modkernel32.NewProc("GetConsoleCP") + procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar") + procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") + procNetShareAdd = modnetapi32.NewProc("NetShareAdd") + procNetShareDel = modnetapi32.NewProc("NetShareDel") + procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW") + procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") + procRevertToSelf = modadvapi32.NewProc("RevertToSelf") + procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") + procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") + procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") + procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx") + procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation") + procGetProfilesDirectoryW = moduserenv.NewProc("GetProfilesDirectoryW") + procNetUserGetLocalGroups = modnetapi32.NewProc("NetUserGetLocalGroups") + procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx") ) func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { @@ -321,3 +322,15 @@ func GetProcessMemoryInfo(handle syscall.Handle, memCounters *PROCESS_MEMORY_COU } return } + +func GetFileInformationByHandleEx(handle syscall.Handle, class uint32, info *byte, bufsize uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(handle), uintptr(class), uintptr(unsafe.Pointer(info)), uintptr(bufsize), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/src/os/stat_test.go b/src/os/stat_test.go index da20a4fdbf..60f3b4c587 100644 --- a/src/os/stat_test.go +++ b/src/os/stat_test.go @@ -250,10 +250,6 @@ func TestFileAndSymlinkStats(t *testing.T) { // see issue 27225 for details func TestSymlinkWithTrailingSlash(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("skipping on windows; issue 27225") - } - testenv.MustHaveSymlink(t) tmpdir, err := ioutil.TempDir("", "TestSymlinkWithTrailingSlash") @@ -274,7 +270,11 @@ func TestSymlinkWithTrailingSlash(t *testing.T) { } dirlinkWithSlash := dirlink + string(os.PathSeparator) - testDirStats(t, dirlinkWithSlash) + if runtime.GOOS == "windows" { + testSymlinkStats(t, dirlinkWithSlash, true) + } else { + testDirStats(t, dirlinkWithSlash) + } fi1, err := os.Stat(dir) if err != nil { diff --git a/src/os/stat_windows.go b/src/os/stat_windows.go index 19cc0cf6b7..f4700f5818 100644 --- a/src/os/stat_windows.go +++ b/src/os/stat_windows.go @@ -5,7 +5,9 @@ package os import ( + "internal/syscall/windows" "syscall" + "unsafe" ) // isNulName returns true if name is NUL file name. @@ -58,33 +60,59 @@ func (file *File) Stat() (FileInfo, error) { return fs, err } -// statNolog implements Stat for Windows. -func statNolog(name string) (FileInfo, error) { +// stat implements both Stat and Lstat of a file. +func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) { if len(name) == 0 { - return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} + return nil, &PathError{funcname, name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} } if isNulName(name) { return &devNullStat, nil } namep, err := syscall.UTF16PtrFromString(fixLongPath(name)) if err != nil { - return nil, &PathError{"Stat", name, err} + return nil, &PathError{funcname, name, err} } - fs, err := newFileStatFromGetFileAttributesExOrFindFirstFile(name, namep) - if err != nil { - return nil, err - } - if !fs.isSymlink() { - err = fs.updatePathAndName(name) - if err != nil { - return nil, err + + // Try GetFileAttributesEx first, because it is faster than CreateFile. + // See https://golang.org/issues/19922#issuecomment-300031421 for details. + var fa syscall.Win32FileAttributeData + err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) + if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + // Not a symlink. + fs := &fileStat{ + path: name, + FileAttributes: fa.FileAttributes, + CreationTime: fa.CreationTime, + LastAccessTime: fa.LastAccessTime, + LastWriteTime: fa.LastWriteTime, + FileSizeHigh: fa.FileSizeHigh, + FileSizeLow: fa.FileSizeLow, } + // Gather full path to be used by os.SameFile later. + if !isAbs(fs.path) { + fs.path, err = syscall.FullPath(fs.path) + if err != nil { + return nil, &PathError{"FullPath", name, err} + } + } + fs.name = basename(name) return fs, nil } - // Use Windows I/O manager to dereference the symbolic link, as per - // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ + // GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for + // files, like c:\pagefile.sys. Use FindFirstFile for such files. + if err == windows.ERROR_SHARING_VIOLATION { + var fd syscall.Win32finddata + sh, err := syscall.FindFirstFile(namep, &fd) + if err != nil { + return nil, &PathError{"FindFirstFile", name, err} + } + syscall.FindClose(sh) + return newFileStatFromWin32finddata(&fd), nil + } + + // Finally use CreateFile. h, err := syscall.CreateFile(namep, 0, 0, nil, - syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + syscall.OPEN_EXISTING, createFileAttrs, 0) if err != nil { return nil, &PathError{"CreateFile", name, err} } @@ -93,25 +121,16 @@ func statNolog(name string) (FileInfo, error) { return newFileStatFromGetFileInformationByHandle(name, h) } +// statNolog implements Stat for Windows. +func statNolog(name string) (FileInfo, error) { + return stat("Stat", name, syscall.FILE_FLAG_BACKUP_SEMANTICS) +} + // lstatNolog implements Lstat for Windows. func lstatNolog(name string) (FileInfo, error) { - if len(name) == 0 { - return nil, &PathError{"Lstat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} - } - if isNulName(name) { - return &devNullStat, nil - } - namep, err := syscall.UTF16PtrFromString(fixLongPath(name)) - if err != nil { - return nil, &PathError{"Lstat", name, err} - } - fs, err := newFileStatFromGetFileAttributesExOrFindFirstFile(name, namep) - if err != nil { - return nil, err - } - err = fs.updatePathAndName(name) - if err != nil { - return nil, err - } - return fs, nil + attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) + // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. + // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted + attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT + return stat("Lstat", name, attrs) } diff --git a/src/os/types_windows.go b/src/os/types_windows.go index 7ebeec50ef..8636dc7f05 100644 --- a/src/os/types_windows.go +++ b/src/os/types_windows.go @@ -47,6 +47,13 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f if err != nil { return nil, &PathError{"GetFileInformationByHandle", path, err} } + + var ti windows.FILE_ATTRIBUTE_TAG_INFO + err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti))) + if err != nil { + return nil, &PathError{"GetFileInformationByHandleEx", path, err} + } + return &fileStat{ name: basename(path), FileAttributes: d.FileAttributes, @@ -58,6 +65,7 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f vol: d.VolumeSerialNumber, idxhi: d.FileIndexHigh, idxlo: d.FileIndexLow, + Reserved0: ti.ReparseTag, // fileStat.path is used by os.SameFile to decide if it needs // to fetch vol, idxhi and idxlo. But these are already set, // so set fileStat.path to "" to prevent os.SameFile doing it again. @@ -78,67 +86,6 @@ func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { } } -// newFileStatFromGetFileAttributesExOrFindFirstFile calls GetFileAttributesEx -// and FindFirstFile to gather all required information about the provided file path pathp. -func newFileStatFromGetFileAttributesExOrFindFirstFile(path string, pathp *uint16) (*fileStat, error) { - // As suggested by Microsoft, use GetFileAttributes() to acquire the file information, - // and if it's a reparse point use FindFirstFile() to get the tag: - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363940(v=vs.85).aspx - // Notice that always calling FindFirstFile can create performance problems - // (https://golang.org/issues/19922#issuecomment-300031421) - var fa syscall.Win32FileAttributeData - err := syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) - if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { - // Not a symlink. - return &fileStat{ - FileAttributes: fa.FileAttributes, - CreationTime: fa.CreationTime, - LastAccessTime: fa.LastAccessTime, - LastWriteTime: fa.LastWriteTime, - FileSizeHigh: fa.FileSizeHigh, - FileSizeLow: fa.FileSizeLow, - }, nil - } - // GetFileAttributesEx returns ERROR_INVALID_NAME if called - // for invalid file name like "*.txt". Do not attempt to call - // FindFirstFile with "*.txt", because FindFirstFile will - // succeed. So just return ERROR_INVALID_NAME instead. - // see https://golang.org/issue/24999 for details. - if errno, _ := err.(syscall.Errno); errno == windows.ERROR_INVALID_NAME { - return nil, &PathError{"GetFileAttributesEx", path, err} - } - // We might have symlink here. But some directories also have - // FileAttributes FILE_ATTRIBUTE_REPARSE_POINT bit set. - // For example, OneDrive directory is like that - // (see golang.org/issue/22579 for details). - // So use FindFirstFile instead to distinguish directories like - // OneDrive from real symlinks (see instructions described at - // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ - // and in particular bits about using both FileAttributes and - // Reserved0 fields). - var fd syscall.Win32finddata - sh, err := syscall.FindFirstFile(pathp, &fd) - if err != nil { - return nil, &PathError{"FindFirstFile", path, err} - } - syscall.FindClose(sh) - - return newFileStatFromWin32finddata(&fd), nil -} - -func (fs *fileStat) updatePathAndName(name string) error { - fs.path = name - if !isAbs(fs.path) { - var err error - fs.path, err = syscall.FullPath(fs.path) - if err != nil { - return &PathError{"FullPath", name, err} - } - } - fs.name = basename(name) - return nil -} - func (fs *fileStat) isSymlink() bool { // Use instructions described at // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ From 7f9984535995fc90556bd18d73b43b4bad744c99 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Tue, 29 May 2018 07:26:17 +0100 Subject: [PATCH 51/76] crypto/md5: simplify generic implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change uses library functions such as bits.RotateLeft32 to reduce the amount of code needed in the generic implementation. Since the code is now shorter I've also removed the option to generate a non-unrolled version of the code. I've also tried to remove bounds checks where possible to make the new version performant, however that is not the primary goal of this change since most architectures have assembly implementations already. Assembly performance: name old speed new speed delta Hash8Bytes 50.3MB/s ± 1% 59.1MB/s ± 0% +17.63% (p=0.000 n=9+8) Hash1K 590MB/s ± 0% 597MB/s ± 0% +1.25% (p=0.000 n=9+9) Hash8K 636MB/s ± 1% 638MB/s ± 1% ~ (p=0.072 n=10+10) Hash8BytesUnaligned 50.5MB/s ± 0% 59.1MB/s ± 1% +17.09% (p=0.000 n=10+10) Hash1KUnaligned 589MB/s ± 1% 596MB/s ± 1% +1.23% (p=0.000 n=9+10) Hash8KUnaligned 638MB/s ± 1% 640MB/s ± 0% +0.35% (p=0.002 n=10+10) Pure Go performance: name old speed new speed delta Hash8Bytes 30.3MB/s ± 1% 42.8MB/s ± 0% +41.20% (p=0.000 n=9+9) Hash1K 364MB/s ± 4% 394MB/s ± 1% +8.27% (p=0.000 n=10+10) Hash8K 404MB/s ± 1% 420MB/s ± 0% +4.17% (p=0.000 n=10+9) Hash8BytesUnaligned 30.3MB/s ± 1% 42.8MB/s ± 1% +40.92% (p=0.000 n=9+10) Hash1KUnaligned 368MB/s ± 0% 394MB/s ± 0% +7.07% (p=0.000 n=9+9) Hash8KUnaligned 404MB/s ± 1% 411MB/s ± 3% +1.91% (p=0.026 n=9+10) Change-Id: I9a91fb52ea8d62964d5351bdf121e9fbc9282852 Reviewed-on: https://go-review.googlesource.com/c/137355 Run-TryBot: Michael Munday TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/crypto/md5/gen.go | 205 ++++++----------- src/crypto/md5/md5.go | 98 ++++---- src/crypto/md5/md5block.go | 349 +++++++++-------------------- src/crypto/md5/md5block_decl.go | 2 + src/crypto/md5/md5block_generic.go | 2 + 5 files changed, 218 insertions(+), 438 deletions(-) diff --git a/src/crypto/md5/gen.go b/src/crypto/md5/gen.go index a815dc29f6..a11f22059f 100644 --- a/src/crypto/md5/gen.go +++ b/src/crypto/md5/gen.go @@ -7,10 +7,7 @@ // This program generates md5block.go // Invoke as // -// go run gen.go [-full] -output md5block.go -// -// The -full flag causes the generated code to do a full -// (16x) unrolling instead of a 4x unrolling. +// go run gen.go -output md5block.go package main @@ -56,13 +53,14 @@ type Data struct { Table2 []uint32 Table3 []uint32 Table4 []uint32 - Full bool } var funcs = template.FuncMap{ "dup": dup, "relabel": relabel, "rotate": rotate, + "idx": idx, + "seq": seq, } func dup(count int, x []int) []int { @@ -74,7 +72,7 @@ func dup(count int, x []int) []int { } func relabel(s string) string { - return strings.NewReplacer("a", data.a, "b", data.b, "c", data.c, "d", data.d).Replace(s) + return strings.NewReplacer("arg0", data.a, "arg1", data.b, "arg2", data.c, "arg3", data.d).Replace(s) } func rotate() string { @@ -82,8 +80,27 @@ func rotate() string { return "" // no output } -func init() { - flag.BoolVar(&data.Full, "full", false, "complete unrolling") +func idx(round, index int) int { + v := 0 + switch round { + case 1: + v = index + case 2: + v = (1 + 5*index) & 15 + case 3: + v = (5 + 3*index) & 15 + case 4: + v = (7 * index) & 15 + } + return v +} + +func seq(i int) []int { + s := make([]int, i) + for i := range s { + s[i] = i + } + return s } var data = Data{ @@ -179,152 +196,64 @@ var program = `// 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. -// Code generated by go run gen.go{{if .Full}} -full{{end}} -output md5block.go; DO NOT EDIT. +// Code generated by go run gen.go -output md5block.go; DO NOT EDIT. package md5 import ( - "unsafe" - "runtime" + "encoding/binary" + "math/bits" ) -{{if not .Full}} - var t1 = [...]uint32{ - {{range .Table1}}{{printf "\t%#x,\n" .}}{{end}} - } - - var t2 = [...]uint32{ - {{range .Table2}}{{printf "\t%#x,\n" .}}{{end}} - } - - var t3 = [...]uint32{ - {{range .Table3}}{{printf "\t%#x,\n" .}}{{end}} - } - - var t4 = [...]uint32{ - {{range .Table4}}{{printf "\t%#x,\n" .}}{{end}} - } -{{end}} - -const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386" - -var littleEndian bool - -func init() { - x := uint32(0x04030201) - y := [4]byte{0x1, 0x2, 0x3, 0x4} - littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y -} - func blockGeneric(dig *digest, p []byte) { - a := dig.s[0] - b := dig.s[1] - c := dig.s[2] - d := dig.s[3] - var X *[16]uint32 - var xbuf [16]uint32 - for len(p) >= chunk { + // load state + a, b, c, d := dig.s[0], dig.s[1], dig.s[2], dig.s[3] + + for i := 0; i <= len(p)-BlockSize; i += BlockSize { + // eliminate bounds checks on p + q := p[i:] + q = q[:BlockSize:BlockSize] + + // save current state aa, bb, cc, dd := a, b, c, d - // This is a constant condition - it is not evaluated on each iteration. - if x86 { - // MD5 was designed so that x86 processors can just iterate - // over the block data directly as uint32s, and we generate - // less code and run 1.3x faster if we take advantage of that. - // My apologies. - X = (*[16]uint32)(unsafe.Pointer(&p[0])) - } else if littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 { - X = (*[16]uint32)(unsafe.Pointer(&p[0])) - } else { - X = &xbuf - j := 0 - for i := 0; i < 16; i++ { - X[i&15] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 - j += 4 - } - } - - {{if .Full}} - // Round 1. - {{range $i, $s := dup 4 .Shift1}} - {{index $.Table1 $i | printf "a += (((c^d)&b)^d) + X[%d] + %d" $i | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - {{rotate}} - {{end}} - - // Round 2. - {{range $i, $s := dup 4 .Shift2}} - {{index $.Table2 $i | printf "a += (((b^c)&d)^c) + X[(1+5*%d)&15] + %d" $i | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - {{rotate}} - {{end}} - - // Round 3. - {{range $i, $s := dup 4 .Shift3}} - {{index $.Table3 $i | printf "a += (b^c^d) + X[(5+3*%d)&15] + %d" $i | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - {{rotate}} - {{end}} - - // Round 4. - {{range $i, $s := dup 4 .Shift4}} - {{index $.Table4 $i | printf "a += (c^(b|^d)) + X[(7*%d)&15] + %d" $i | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - {{rotate}} - {{end}} - {{else}} - // Round 1. - for i := uint(0); i < 16; { - {{range $s := .Shift1}} - {{printf "a += (((c^d)&b)^d) + X[i&15] + t1[i&15]" | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - i++ - {{rotate}} - {{end}} - } - - // Round 2. - for i := uint(0); i < 16; { - {{range $s := .Shift2}} - {{printf "a += (((b^c)&d)^c) + X[(1+5*i)&15] + t2[i&15]" | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - i++ - {{rotate}} - {{end}} - } - - // Round 3. - for i := uint(0); i < 16; { - {{range $s := .Shift3}} - {{printf "a += (b^c^d) + X[(5+3*i)&15] + t3[i&15]" | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - i++ - {{rotate}} - {{end}} - } - - // Round 4. - for i := uint(0); i < 16; { - {{range $s := .Shift4}} - {{printf "a += (c^(b|^d)) + X[(7*i)&15] + t4[i&15]" | relabel}} - {{printf "a = a<<%d | a>>(32-%d) + b" $s $s | relabel}} - i++ - {{rotate}} - {{end}} - } + // load input block + {{range $i := seq 16 -}} + {{printf "x%x := binary.LittleEndian.Uint32(q[4*%#x:])" $i $i}} {{end}} + // round 1 + {{range $i, $s := dup 4 .Shift1 -}} + {{printf "arg0 = arg1 + bits.RotateLeft32((((arg2^arg3)&arg1)^arg3)+arg0+x%x+%#08x, %d)" (idx 1 $i) (index $.Table1 $i) $s | relabel}} + {{rotate -}} + {{end}} + + // round 2 + {{range $i, $s := dup 4 .Shift2 -}} + {{printf "arg0 = arg1 + bits.RotateLeft32((((arg1^arg2)&arg3)^arg2)+arg0+x%x+%#08x, %d)" (idx 2 $i) (index $.Table2 $i) $s | relabel}} + {{rotate -}} + {{end}} + + // round 3 + {{range $i, $s := dup 4 .Shift3 -}} + {{printf "arg0 = arg1 + bits.RotateLeft32((arg1^arg2^arg3)+arg0+x%x+%#08x, %d)" (idx 3 $i) (index $.Table3 $i) $s | relabel}} + {{rotate -}} + {{end}} + + // round 4 + {{range $i, $s := dup 4 .Shift4 -}} + {{printf "arg0 = arg1 + bits.RotateLeft32((arg2^(arg1|^arg3))+arg0+x%x+%#08x, %d)" (idx 4 $i) (index $.Table4 $i) $s | relabel}} + {{rotate -}} + {{end}} + + // add saved state a += aa b += bb c += cc d += dd - - p = p[chunk:] } - dig.s[0] = a - dig.s[1] = b - dig.s[2] = c - dig.s[3] = d + // save state + dig.s[0], dig.s[1], dig.s[2], dig.s[3] = a, b, c, d } ` diff --git a/src/crypto/md5/md5.go b/src/crypto/md5/md5.go index 88d914d22c..3e66db6d0d 100644 --- a/src/crypto/md5/md5.go +++ b/src/crypto/md5/md5.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:generate go run gen.go -full -output md5block.go +//go:generate go run gen.go -output md5block.go // Package md5 implements the MD5 hash algorithm as defined in RFC 1321. // @@ -12,6 +12,7 @@ package md5 import ( "crypto" + "encoding/binary" "errors" "hash" ) @@ -27,7 +28,6 @@ const Size = 16 const BlockSize = 64 const ( - chunk = 64 init0 = 0x67452301 init1 = 0xEFCDAB89 init2 = 0x98BADCFE @@ -37,7 +37,7 @@ const ( // digest represents the partial evaluation of a checksum. type digest struct { s [4]uint32 - x [chunk]byte + x [BlockSize]byte nx int len uint64 } @@ -53,7 +53,7 @@ func (d *digest) Reset() { const ( magic = "md5\x01" - marshaledSize = len(magic) + 4*4 + chunk + 8 + marshaledSize = len(magic) + 4*4 + BlockSize + 8 ) func (d *digest) MarshalBinary() ([]byte, error) { @@ -83,45 +83,28 @@ func (d *digest) UnmarshalBinary(b []byte) error { b, d.s[3] = consumeUint32(b) b = b[copy(d.x[:], b):] b, d.len = consumeUint64(b) - d.nx = int(d.len) % chunk + d.nx = int(d.len) % BlockSize return nil } func appendUint64(b []byte, x uint64) []byte { - a := [8]byte{ - byte(x >> 56), - byte(x >> 48), - byte(x >> 40), - byte(x >> 32), - byte(x >> 24), - byte(x >> 16), - byte(x >> 8), - byte(x), - } + var a [8]byte + binary.BigEndian.PutUint64(a[:], x) return append(b, a[:]...) } func appendUint32(b []byte, x uint32) []byte { - a := [4]byte{ - byte(x >> 24), - byte(x >> 16), - byte(x >> 8), - byte(x), - } + var a [4]byte + binary.BigEndian.PutUint32(a[:], x) return append(b, a[:]...) } func consumeUint64(b []byte) ([]byte, uint64) { - _ = b[7] - x := uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | - uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 - return b[8:], x + return b[8:], binary.BigEndian.Uint64(b[0:8]) } func consumeUint32(b []byte) ([]byte, uint32) { - _ = b[3] - x := uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 - return b[4:], x + return b[4:], binary.BigEndian.Uint32(b[0:4]) } // New returns a new hash.Hash computing the MD5 checksum. The Hash also @@ -138,20 +121,31 @@ func (d *digest) Size() int { return Size } func (d *digest) BlockSize() int { return BlockSize } func (d *digest) Write(p []byte) (nn int, err error) { + // Note that we currently call block or blockGeneric + // directly (guarded using haveAsm) because this allows + // escape analysis to see that p and d don't escape. nn = len(p) d.len += uint64(nn) if d.nx > 0 { n := copy(d.x[d.nx:], p) d.nx += n - if d.nx == chunk { - block(d, d.x[:]) + if d.nx == BlockSize { + if haveAsm { + block(d, d.x[:]) + } else { + blockGeneric(d, d.x[:]) + } d.nx = 0 } p = p[n:] } - if len(p) >= chunk { - n := len(p) &^ (chunk - 1) - block(d, p[:n]) + if len(p) >= BlockSize { + n := len(p) &^ (BlockSize - 1) + if haveAsm { + block(d, p[:n]) + } else { + blockGeneric(d, p[:n]) + } p = p[n:] } if len(p) > 0 { @@ -168,35 +162,27 @@ func (d *digest) Sum(in []byte) []byte { } func (d *digest) checkSum() [Size]byte { - // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. - len := d.len - var tmp [64]byte - tmp[0] = 0x80 - if len%64 < 56 { - d.Write(tmp[0 : 56-len%64]) - } else { - d.Write(tmp[0 : 64+56-len%64]) - } - - // Length in bits. - len <<= 3 - for i := uint(0); i < 8; i++ { - tmp[i] = byte(len >> (8 * i)) - } - d.Write(tmp[0:8]) + // Append 0x80 to the end of the message and then append zeros + // until the length is a multiple of 56 bytes. Finally append + // 8 bytes representing the message length in bits. + // + // 1 byte end marker :: 0-63 padding bytes :: 8 byte length + tmp := [1 + 63 + 8]byte{0x80} + pad := (55 - d.len) % 64 // calculate number of padding bytes + binary.LittleEndian.PutUint64(tmp[1+pad:], d.len<<3) // append length in bits + d.Write(tmp[:1+pad+8]) + // The previous write ensures that a whole number of + // blocks (i.e. a multiple of 64 bytes) have been hashed. if d.nx != 0 { panic("d.nx != 0") } var digest [Size]byte - for i, s := range d.s { - digest[i*4] = byte(s) - digest[i*4+1] = byte(s >> 8) - digest[i*4+2] = byte(s >> 16) - digest[i*4+3] = byte(s >> 24) - } - + binary.LittleEndian.PutUint32(digest[0:], d.s[0]) + binary.LittleEndian.PutUint32(digest[4:], d.s[1]) + binary.LittleEndian.PutUint32(digest[8:], d.s[2]) + binary.LittleEndian.PutUint32(digest[12:], d.s[3]) return digest } diff --git a/src/crypto/md5/md5block.go b/src/crypto/md5/md5block.go index 8ac32ffeb7..4ff289e860 100644 --- a/src/crypto/md5/md5block.go +++ b/src/crypto/md5/md5block.go @@ -2,263 +2,124 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Code generated by go run gen.go -full -output md5block.go; DO NOT EDIT. +// Code generated by go run gen.go -output md5block.go; DO NOT EDIT. package md5 import ( - "runtime" - "unsafe" + "encoding/binary" + "math/bits" ) -const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386" - -var littleEndian bool - -func init() { - x := uint32(0x04030201) - y := [4]byte{0x1, 0x2, 0x3, 0x4} - littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y -} - func blockGeneric(dig *digest, p []byte) { - a := dig.s[0] - b := dig.s[1] - c := dig.s[2] - d := dig.s[3] - var X *[16]uint32 - var xbuf [16]uint32 - for len(p) >= chunk { + // load state + a, b, c, d := dig.s[0], dig.s[1], dig.s[2], dig.s[3] + + for i := 0; i <= len(p)-BlockSize; i += BlockSize { + // eliminate bounds checks on p + q := p[i:] + q = q[:BlockSize:BlockSize] + + // save current state aa, bb, cc, dd := a, b, c, d - // This is a constant condition - it is not evaluated on each iteration. - if x86 { - // MD5 was designed so that x86 processors can just iterate - // over the block data directly as uint32s, and we generate - // less code and run 1.3x faster if we take advantage of that. - // My apologies. - X = (*[16]uint32)(unsafe.Pointer(&p[0])) - } else if littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 { - X = (*[16]uint32)(unsafe.Pointer(&p[0])) - } else { - X = &xbuf - j := 0 - for i := 0; i < 16; i++ { - X[i&15] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 - j += 4 - } - } - - // Round 1. - - a += (((c ^ d) & b) ^ d) + X[0] + 3614090360 - a = a<<7 | a>>(32-7) + b - - d += (((b ^ c) & a) ^ c) + X[1] + 3905402710 - d = d<<12 | d>>(32-12) + a - - c += (((a ^ b) & d) ^ b) + X[2] + 606105819 - c = c<<17 | c>>(32-17) + d - - b += (((d ^ a) & c) ^ a) + X[3] + 3250441966 - b = b<<22 | b>>(32-22) + c - - a += (((c ^ d) & b) ^ d) + X[4] + 4118548399 - a = a<<7 | a>>(32-7) + b - - d += (((b ^ c) & a) ^ c) + X[5] + 1200080426 - d = d<<12 | d>>(32-12) + a - - c += (((a ^ b) & d) ^ b) + X[6] + 2821735955 - c = c<<17 | c>>(32-17) + d - - b += (((d ^ a) & c) ^ a) + X[7] + 4249261313 - b = b<<22 | b>>(32-22) + c - - a += (((c ^ d) & b) ^ d) + X[8] + 1770035416 - a = a<<7 | a>>(32-7) + b - - d += (((b ^ c) & a) ^ c) + X[9] + 2336552879 - d = d<<12 | d>>(32-12) + a - - c += (((a ^ b) & d) ^ b) + X[10] + 4294925233 - c = c<<17 | c>>(32-17) + d - - b += (((d ^ a) & c) ^ a) + X[11] + 2304563134 - b = b<<22 | b>>(32-22) + c - - a += (((c ^ d) & b) ^ d) + X[12] + 1804603682 - a = a<<7 | a>>(32-7) + b - - d += (((b ^ c) & a) ^ c) + X[13] + 4254626195 - d = d<<12 | d>>(32-12) + a - - c += (((a ^ b) & d) ^ b) + X[14] + 2792965006 - c = c<<17 | c>>(32-17) + d - - b += (((d ^ a) & c) ^ a) + X[15] + 1236535329 - b = b<<22 | b>>(32-22) + c - - // Round 2. - - a += (((b ^ c) & d) ^ c) + X[(1+5*0)&15] + 4129170786 - a = a<<5 | a>>(32-5) + b - - d += (((a ^ b) & c) ^ b) + X[(1+5*1)&15] + 3225465664 - d = d<<9 | d>>(32-9) + a - - c += (((d ^ a) & b) ^ a) + X[(1+5*2)&15] + 643717713 - c = c<<14 | c>>(32-14) + d - - b += (((c ^ d) & a) ^ d) + X[(1+5*3)&15] + 3921069994 - b = b<<20 | b>>(32-20) + c - - a += (((b ^ c) & d) ^ c) + X[(1+5*4)&15] + 3593408605 - a = a<<5 | a>>(32-5) + b - - d += (((a ^ b) & c) ^ b) + X[(1+5*5)&15] + 38016083 - d = d<<9 | d>>(32-9) + a - - c += (((d ^ a) & b) ^ a) + X[(1+5*6)&15] + 3634488961 - c = c<<14 | c>>(32-14) + d - - b += (((c ^ d) & a) ^ d) + X[(1+5*7)&15] + 3889429448 - b = b<<20 | b>>(32-20) + c - - a += (((b ^ c) & d) ^ c) + X[(1+5*8)&15] + 568446438 - a = a<<5 | a>>(32-5) + b - - d += (((a ^ b) & c) ^ b) + X[(1+5*9)&15] + 3275163606 - d = d<<9 | d>>(32-9) + a - - c += (((d ^ a) & b) ^ a) + X[(1+5*10)&15] + 4107603335 - c = c<<14 | c>>(32-14) + d - - b += (((c ^ d) & a) ^ d) + X[(1+5*11)&15] + 1163531501 - b = b<<20 | b>>(32-20) + c - - a += (((b ^ c) & d) ^ c) + X[(1+5*12)&15] + 2850285829 - a = a<<5 | a>>(32-5) + b - - d += (((a ^ b) & c) ^ b) + X[(1+5*13)&15] + 4243563512 - d = d<<9 | d>>(32-9) + a - - c += (((d ^ a) & b) ^ a) + X[(1+5*14)&15] + 1735328473 - c = c<<14 | c>>(32-14) + d - - b += (((c ^ d) & a) ^ d) + X[(1+5*15)&15] + 2368359562 - b = b<<20 | b>>(32-20) + c - - // Round 3. - - a += (b ^ c ^ d) + X[(5+3*0)&15] + 4294588738 - a = a<<4 | a>>(32-4) + b - - d += (a ^ b ^ c) + X[(5+3*1)&15] + 2272392833 - d = d<<11 | d>>(32-11) + a - - c += (d ^ a ^ b) + X[(5+3*2)&15] + 1839030562 - c = c<<16 | c>>(32-16) + d - - b += (c ^ d ^ a) + X[(5+3*3)&15] + 4259657740 - b = b<<23 | b>>(32-23) + c - - a += (b ^ c ^ d) + X[(5+3*4)&15] + 2763975236 - a = a<<4 | a>>(32-4) + b - - d += (a ^ b ^ c) + X[(5+3*5)&15] + 1272893353 - d = d<<11 | d>>(32-11) + a - - c += (d ^ a ^ b) + X[(5+3*6)&15] + 4139469664 - c = c<<16 | c>>(32-16) + d - - b += (c ^ d ^ a) + X[(5+3*7)&15] + 3200236656 - b = b<<23 | b>>(32-23) + c - - a += (b ^ c ^ d) + X[(5+3*8)&15] + 681279174 - a = a<<4 | a>>(32-4) + b - - d += (a ^ b ^ c) + X[(5+3*9)&15] + 3936430074 - d = d<<11 | d>>(32-11) + a - - c += (d ^ a ^ b) + X[(5+3*10)&15] + 3572445317 - c = c<<16 | c>>(32-16) + d - - b += (c ^ d ^ a) + X[(5+3*11)&15] + 76029189 - b = b<<23 | b>>(32-23) + c - - a += (b ^ c ^ d) + X[(5+3*12)&15] + 3654602809 - a = a<<4 | a>>(32-4) + b - - d += (a ^ b ^ c) + X[(5+3*13)&15] + 3873151461 - d = d<<11 | d>>(32-11) + a - - c += (d ^ a ^ b) + X[(5+3*14)&15] + 530742520 - c = c<<16 | c>>(32-16) + d - - b += (c ^ d ^ a) + X[(5+3*15)&15] + 3299628645 - b = b<<23 | b>>(32-23) + c - - // Round 4. - - a += (c ^ (b | ^d)) + X[(7*0)&15] + 4096336452 - a = a<<6 | a>>(32-6) + b - - d += (b ^ (a | ^c)) + X[(7*1)&15] + 1126891415 - d = d<<10 | d>>(32-10) + a - - c += (a ^ (d | ^b)) + X[(7*2)&15] + 2878612391 - c = c<<15 | c>>(32-15) + d - - b += (d ^ (c | ^a)) + X[(7*3)&15] + 4237533241 - b = b<<21 | b>>(32-21) + c - - a += (c ^ (b | ^d)) + X[(7*4)&15] + 1700485571 - a = a<<6 | a>>(32-6) + b - - d += (b ^ (a | ^c)) + X[(7*5)&15] + 2399980690 - d = d<<10 | d>>(32-10) + a - - c += (a ^ (d | ^b)) + X[(7*6)&15] + 4293915773 - c = c<<15 | c>>(32-15) + d - - b += (d ^ (c | ^a)) + X[(7*7)&15] + 2240044497 - b = b<<21 | b>>(32-21) + c - - a += (c ^ (b | ^d)) + X[(7*8)&15] + 1873313359 - a = a<<6 | a>>(32-6) + b - - d += (b ^ (a | ^c)) + X[(7*9)&15] + 4264355552 - d = d<<10 | d>>(32-10) + a - - c += (a ^ (d | ^b)) + X[(7*10)&15] + 2734768916 - c = c<<15 | c>>(32-15) + d - - b += (d ^ (c | ^a)) + X[(7*11)&15] + 1309151649 - b = b<<21 | b>>(32-21) + c - - a += (c ^ (b | ^d)) + X[(7*12)&15] + 4149444226 - a = a<<6 | a>>(32-6) + b - - d += (b ^ (a | ^c)) + X[(7*13)&15] + 3174756917 - d = d<<10 | d>>(32-10) + a - - c += (a ^ (d | ^b)) + X[(7*14)&15] + 718787259 - c = c<<15 | c>>(32-15) + d - - b += (d ^ (c | ^a)) + X[(7*15)&15] + 3951481745 - b = b<<21 | b>>(32-21) + c - + // load input block + x0 := binary.LittleEndian.Uint32(q[4*0x0:]) + x1 := binary.LittleEndian.Uint32(q[4*0x1:]) + x2 := binary.LittleEndian.Uint32(q[4*0x2:]) + x3 := binary.LittleEndian.Uint32(q[4*0x3:]) + x4 := binary.LittleEndian.Uint32(q[4*0x4:]) + x5 := binary.LittleEndian.Uint32(q[4*0x5:]) + x6 := binary.LittleEndian.Uint32(q[4*0x6:]) + x7 := binary.LittleEndian.Uint32(q[4*0x7:]) + x8 := binary.LittleEndian.Uint32(q[4*0x8:]) + x9 := binary.LittleEndian.Uint32(q[4*0x9:]) + xa := binary.LittleEndian.Uint32(q[4*0xa:]) + xb := binary.LittleEndian.Uint32(q[4*0xb:]) + xc := binary.LittleEndian.Uint32(q[4*0xc:]) + xd := binary.LittleEndian.Uint32(q[4*0xd:]) + xe := binary.LittleEndian.Uint32(q[4*0xe:]) + xf := binary.LittleEndian.Uint32(q[4*0xf:]) + + // round 1 + a = b + bits.RotateLeft32((((c^d)&b)^d)+a+x0+0xd76aa478, 7) + d = a + bits.RotateLeft32((((b^c)&a)^c)+d+x1+0xe8c7b756, 12) + c = d + bits.RotateLeft32((((a^b)&d)^b)+c+x2+0x242070db, 17) + b = c + bits.RotateLeft32((((d^a)&c)^a)+b+x3+0xc1bdceee, 22) + a = b + bits.RotateLeft32((((c^d)&b)^d)+a+x4+0xf57c0faf, 7) + d = a + bits.RotateLeft32((((b^c)&a)^c)+d+x5+0x4787c62a, 12) + c = d + bits.RotateLeft32((((a^b)&d)^b)+c+x6+0xa8304613, 17) + b = c + bits.RotateLeft32((((d^a)&c)^a)+b+x7+0xfd469501, 22) + a = b + bits.RotateLeft32((((c^d)&b)^d)+a+x8+0x698098d8, 7) + d = a + bits.RotateLeft32((((b^c)&a)^c)+d+x9+0x8b44f7af, 12) + c = d + bits.RotateLeft32((((a^b)&d)^b)+c+xa+0xffff5bb1, 17) + b = c + bits.RotateLeft32((((d^a)&c)^a)+b+xb+0x895cd7be, 22) + a = b + bits.RotateLeft32((((c^d)&b)^d)+a+xc+0x6b901122, 7) + d = a + bits.RotateLeft32((((b^c)&a)^c)+d+xd+0xfd987193, 12) + c = d + bits.RotateLeft32((((a^b)&d)^b)+c+xe+0xa679438e, 17) + b = c + bits.RotateLeft32((((d^a)&c)^a)+b+xf+0x49b40821, 22) + + // round 2 + a = b + bits.RotateLeft32((((b^c)&d)^c)+a+x1+0xf61e2562, 5) + d = a + bits.RotateLeft32((((a^b)&c)^b)+d+x6+0xc040b340, 9) + c = d + bits.RotateLeft32((((d^a)&b)^a)+c+xb+0x265e5a51, 14) + b = c + bits.RotateLeft32((((c^d)&a)^d)+b+x0+0xe9b6c7aa, 20) + a = b + bits.RotateLeft32((((b^c)&d)^c)+a+x5+0xd62f105d, 5) + d = a + bits.RotateLeft32((((a^b)&c)^b)+d+xa+0x02441453, 9) + c = d + bits.RotateLeft32((((d^a)&b)^a)+c+xf+0xd8a1e681, 14) + b = c + bits.RotateLeft32((((c^d)&a)^d)+b+x4+0xe7d3fbc8, 20) + a = b + bits.RotateLeft32((((b^c)&d)^c)+a+x9+0x21e1cde6, 5) + d = a + bits.RotateLeft32((((a^b)&c)^b)+d+xe+0xc33707d6, 9) + c = d + bits.RotateLeft32((((d^a)&b)^a)+c+x3+0xf4d50d87, 14) + b = c + bits.RotateLeft32((((c^d)&a)^d)+b+x8+0x455a14ed, 20) + a = b + bits.RotateLeft32((((b^c)&d)^c)+a+xd+0xa9e3e905, 5) + d = a + bits.RotateLeft32((((a^b)&c)^b)+d+x2+0xfcefa3f8, 9) + c = d + bits.RotateLeft32((((d^a)&b)^a)+c+x7+0x676f02d9, 14) + b = c + bits.RotateLeft32((((c^d)&a)^d)+b+xc+0x8d2a4c8a, 20) + + // round 3 + a = b + bits.RotateLeft32((b^c^d)+a+x5+0xfffa3942, 4) + d = a + bits.RotateLeft32((a^b^c)+d+x8+0x8771f681, 11) + c = d + bits.RotateLeft32((d^a^b)+c+xb+0x6d9d6122, 16) + b = c + bits.RotateLeft32((c^d^a)+b+xe+0xfde5380c, 23) + a = b + bits.RotateLeft32((b^c^d)+a+x1+0xa4beea44, 4) + d = a + bits.RotateLeft32((a^b^c)+d+x4+0x4bdecfa9, 11) + c = d + bits.RotateLeft32((d^a^b)+c+x7+0xf6bb4b60, 16) + b = c + bits.RotateLeft32((c^d^a)+b+xa+0xbebfbc70, 23) + a = b + bits.RotateLeft32((b^c^d)+a+xd+0x289b7ec6, 4) + d = a + bits.RotateLeft32((a^b^c)+d+x0+0xeaa127fa, 11) + c = d + bits.RotateLeft32((d^a^b)+c+x3+0xd4ef3085, 16) + b = c + bits.RotateLeft32((c^d^a)+b+x6+0x04881d05, 23) + a = b + bits.RotateLeft32((b^c^d)+a+x9+0xd9d4d039, 4) + d = a + bits.RotateLeft32((a^b^c)+d+xc+0xe6db99e5, 11) + c = d + bits.RotateLeft32((d^a^b)+c+xf+0x1fa27cf8, 16) + b = c + bits.RotateLeft32((c^d^a)+b+x2+0xc4ac5665, 23) + + // round 4 + a = b + bits.RotateLeft32((c^(b|^d))+a+x0+0xf4292244, 6) + d = a + bits.RotateLeft32((b^(a|^c))+d+x7+0x432aff97, 10) + c = d + bits.RotateLeft32((a^(d|^b))+c+xe+0xab9423a7, 15) + b = c + bits.RotateLeft32((d^(c|^a))+b+x5+0xfc93a039, 21) + a = b + bits.RotateLeft32((c^(b|^d))+a+xc+0x655b59c3, 6) + d = a + bits.RotateLeft32((b^(a|^c))+d+x3+0x8f0ccc92, 10) + c = d + bits.RotateLeft32((a^(d|^b))+c+xa+0xffeff47d, 15) + b = c + bits.RotateLeft32((d^(c|^a))+b+x1+0x85845dd1, 21) + a = b + bits.RotateLeft32((c^(b|^d))+a+x8+0x6fa87e4f, 6) + d = a + bits.RotateLeft32((b^(a|^c))+d+xf+0xfe2ce6e0, 10) + c = d + bits.RotateLeft32((a^(d|^b))+c+x6+0xa3014314, 15) + b = c + bits.RotateLeft32((d^(c|^a))+b+xd+0x4e0811a1, 21) + a = b + bits.RotateLeft32((c^(b|^d))+a+x4+0xf7537e82, 6) + d = a + bits.RotateLeft32((b^(a|^c))+d+xb+0xbd3af235, 10) + c = d + bits.RotateLeft32((a^(d|^b))+c+x2+0x2ad7d2bb, 15) + b = c + bits.RotateLeft32((d^(c|^a))+b+x9+0xeb86d391, 21) + + // add saved state a += aa b += bb c += cc d += dd - - p = p[chunk:] } - dig.s[0] = a - dig.s[1] = b - dig.s[2] = c - dig.s[3] = d + // save state + dig.s[0], dig.s[1], dig.s[2], dig.s[3] = a, b, c, d } diff --git a/src/crypto/md5/md5block_decl.go b/src/crypto/md5/md5block_decl.go index 2fd1cb9795..40bca49a0e 100644 --- a/src/crypto/md5/md5block_decl.go +++ b/src/crypto/md5/md5block_decl.go @@ -6,6 +6,8 @@ package md5 +const haveAsm = true + //go:noescape func block(dig *digest, p []byte) diff --git a/src/crypto/md5/md5block_generic.go b/src/crypto/md5/md5block_generic.go index a5f7882038..c744cf72e7 100644 --- a/src/crypto/md5/md5block_generic.go +++ b/src/crypto/md5/md5block_generic.go @@ -6,4 +6,6 @@ package md5 +const haveAsm = false + var block = blockGeneric From a9280fa2de5b9dc13f75dd3aebdf4a218fca451e Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 11:31:31 +0100 Subject: [PATCH 52/76] runtime: don't wake timeproc needlessly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's not always necessary to wake timerproc even if we add a new timer to the top of the heap. Since we don't wake and reset timerproc when we remove timers, it still can be sleeping with shorter timeout. It such case it's more profitable to let it sleep and then update timeout when it wakes on its own rather than proactively wake it, let it update timeout and go to sleep again. name old time/op new time/op delta TCP4OneShotTimeout-6 18.6µs ± 1% 17.2µs ± 0% -7.66% (p=0.008 n=5+5) SetReadDeadline-6 562ns ± 5% 319ns ± 1% -43.27% (p=0.008 n=5+5) Update #25729 Change-Id: Iec8eacb8563dbc574a82358b3bac7ac479c16826 Reviewed-on: https://go-review.googlesource.com/c/146337 Reviewed-by: Ian Lance Taylor --- src/net/tcpsock_test.go | 31 +++++++++++++++++++++++++++++++ src/runtime/time.go | 10 +++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/net/tcpsock_test.go b/src/net/tcpsock_test.go index c2f26b1770..36d2ccb09a 100644 --- a/src/net/tcpsock_test.go +++ b/src/net/tcpsock_test.go @@ -796,3 +796,34 @@ func TestCopyPipeIntoTCP(t *testing.T) { t.Fatal(err) } } + +func BenchmarkSetReadDeadline(b *testing.B) { + ln, err := newLocalListener("tcp") + if err != nil { + b.Fatal(err) + } + defer ln.Close() + var serv Conn + done := make(chan error) + go func() { + var err error + serv, err = ln.Accept() + done <- err + }() + c, err := Dial("tcp", ln.Addr().String()) + if err != nil { + b.Fatal(err) + } + defer c.Close() + if err := <-done; err != nil { + b.Fatal(err) + } + defer serv.Close() + c.SetWriteDeadline(time.Now().Add(2 * time.Hour)) + deadline := time.Now().Add(time.Hour) + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.SetReadDeadline(deadline) + deadline = deadline.Add(1) + } +} diff --git a/src/runtime/time.go b/src/runtime/time.go index 790819f259..5e1a925dee 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -156,7 +156,7 @@ func (tb *timersBucket) addtimerLocked(t *timer) bool { } if t.i == 0 { // siftup moved to top: new earliest deadline. - if tb.sleeping { + if tb.sleeping && tb.sleepUntil > t.when { tb.sleeping = false notewakeup(&tb.waitnote) } @@ -164,10 +164,10 @@ func (tb *timersBucket) addtimerLocked(t *timer) bool { tb.rescheduling = false goready(tb.gp, 0) } - } - if !tb.created { - tb.created = true - go timerproc(tb) + if !tb.created { + tb.created = true + go timerproc(tb) + } } return true } From 86d375498fa377c7d81c5b93750e8dce2389500e Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 16:18:36 +0100 Subject: [PATCH 53/76] runtime: don't recreate netpoll timers if they don't change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we always delete both read and write timers and then add them again. However, if user setups read and write deadline separately, then we don't need to touch the other one. name old time/op new time/op delta TCP4OneShotTimeout-6 17.2µs ± 0% 17.2µs ± 0% ~ (p=0.310 n=5+5) SetReadDeadline-6 319ns ± 1% 274ns ± 2% -13.94% (p=0.008 n=5+5) Update #25729 Change-Id: I4c869c3083521de6d0cd6ca99a7609d4dd84b4e4 Reviewed-on: https://go-review.googlesource.com/c/146338 Reviewed-by: Ian Lance Taylor --- src/runtime/netpoll.go | 71 +++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index da822a7308..00701665f1 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -56,14 +56,15 @@ type pollDesc struct { lock mutex // protects the following fields fd uintptr closing bool - seq uintptr // protects from stale timers and ready notifications + user uint32 // user settable cookie + rseq uintptr // protects from stale read timers rg uintptr // pdReady, pdWait, G waiting for read or nil rt timer // read deadline timer (set if rt.f != nil) rd int64 // read deadline + wseq uintptr // protects from stale write timers wg uintptr // pdReady, pdWait, G waiting for write or nil wt timer // write deadline timer wd int64 // write deadline - user uint32 // user settable cookie } type pollCache struct { @@ -112,9 +113,10 @@ func poll_runtime_pollOpen(fd uintptr) (*pollDesc, int) { } pd.fd = fd pd.closing = false - pd.seq++ + pd.rseq++ pd.rg = 0 pd.rd = 0 + pd.wseq++ pd.wg = 0 pd.wd = 0 unlock(&pd.lock) @@ -197,17 +199,8 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { unlock(&pd.lock) return } - pd.seq++ // invalidate current timers - // Reset current timers. - if pd.rt.f != nil { - deltimer(&pd.rt) - pd.rt.f = nil - } - if pd.wt.f != nil { - deltimer(&pd.wt) - pd.wt.f = nil - } - // Setup new timers. + rd0, wd0 := pd.rd, pd.wd + combo0 := rd0 > 0 && rd0 == wd0 if d != 0 && d <= nanotime() { d = -1 } @@ -217,28 +210,43 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { if mode == 'w' || mode == 'r'+'w' { pd.wd = d } - if pd.rd > 0 && pd.rd == pd.wd { - pd.rt.f = netpollDeadline - pd.rt.when = pd.rd - // Copy current seq into the timer arg. - // Timer func will check the seq against current descriptor seq, - // if they differ the descriptor was reused or timers were reset. - pd.rt.arg = pd - pd.rt.seq = pd.seq - addtimer(&pd.rt) + combo := pd.rd > 0 && pd.rd == pd.wd + // Reset current timers if necessary. + if pd.rt.f != nil && (pd.rd != rd0 || combo != combo0) { + pd.rseq++ // invalidate current timers + deltimer(&pd.rt) + pd.rt.f = nil + } + if pd.wt.f != nil && (pd.wd != wd0 || combo != combo0) { + pd.wseq++ // invalidate current timers + deltimer(&pd.wt) + pd.wt.f = nil + } + // Setup new timers. + if combo { + if pd.rt.f == nil { + pd.rt.f = netpollDeadline + pd.rt.when = pd.rd + // Copy current seq into the timer arg. + // Timer func will check the seq against current descriptor seq, + // if they differ the descriptor was reused or timers were reset. + pd.rt.arg = pd + pd.rt.seq = pd.rseq + addtimer(&pd.rt) + } } else { - if pd.rd > 0 { + if pd.rd > 0 && pd.rt.f == nil { pd.rt.f = netpollReadDeadline pd.rt.when = pd.rd pd.rt.arg = pd - pd.rt.seq = pd.seq + pd.rt.seq = pd.rseq addtimer(&pd.rt) } - if pd.wd > 0 { + if pd.wd > 0 && pd.wt.f == nil { pd.wt.f = netpollWriteDeadline pd.wt.when = pd.wd pd.wt.arg = pd - pd.wt.seq = pd.seq + pd.wt.seq = pd.wseq addtimer(&pd.wt) } } @@ -267,7 +275,8 @@ func poll_runtime_pollUnblock(pd *pollDesc) { throw("runtime: unblock on closing polldesc") } pd.closing = true - pd.seq++ + pd.rseq++ + pd.wseq++ var rg, wg *g atomicstorep(unsafe.Pointer(&rg), nil) // full memory barrier between store to closing and read of rg/wg in netpollunblock rg = netpollunblock(pd, 'r', false) @@ -404,7 +413,11 @@ func netpolldeadlineimpl(pd *pollDesc, seq uintptr, read, write bool) { lock(&pd.lock) // Seq arg is seq when the timer was set. // If it's stale, ignore the timer event. - if seq != pd.seq { + currentSeq := pd.rseq + if !read { + currentSeq = pd.wseq + } + if seq != currentSeq { // The descriptor was reused or timers were reset. unlock(&pd.lock) return From a86f549703c107c8b4d83d8e7527521c9e215d9a Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 17:03:35 +0100 Subject: [PATCH 54/76] runtime: add and use modtimer in netpoll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently when netpoll deadline is incrementally prolonged, we delete and re-add timer each time. Add modtimer function that does both and use it when we need to modify an existing netpoll timer to avoid unnecessary lock/unlock. TCP4OneShotTimeout-6 17.2µs ± 0% 17.0µs ± 0% -0.82% (p=0.008 n=5+5) SetReadDeadline-6 274ns ± 2% 261ns ± 0% -4.89% (p=0.008 n=5+5) Update #25729 Change-Id: I08b89dbbc1785dd180e967a37b0aa23b0c4613a8 Reviewed-on: https://go-review.googlesource.com/c/146339 Reviewed-by: Ian Lance Taylor --- src/runtime/netpoll.go | 46 ++++++++++++++++++++++-------------------- src/runtime/time.go | 31 ++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index 00701665f1..34e7c902eb 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -211,21 +211,13 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { pd.wd = d } combo := pd.rd > 0 && pd.rd == pd.wd - // Reset current timers if necessary. - if pd.rt.f != nil && (pd.rd != rd0 || combo != combo0) { - pd.rseq++ // invalidate current timers - deltimer(&pd.rt) - pd.rt.f = nil - } - if pd.wt.f != nil && (pd.wd != wd0 || combo != combo0) { - pd.wseq++ // invalidate current timers - deltimer(&pd.wt) - pd.wt.f = nil - } - // Setup new timers. + rtf := netpollReadDeadline if combo { - if pd.rt.f == nil { - pd.rt.f = netpollDeadline + rtf = netpollDeadline + } + if pd.rt.f == nil { + if pd.rd > 0 { + pd.rt.f = rtf pd.rt.when = pd.rd // Copy current seq into the timer arg. // Timer func will check the seq against current descriptor seq, @@ -234,21 +226,31 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { pd.rt.seq = pd.rseq addtimer(&pd.rt) } - } else { - if pd.rd > 0 && pd.rt.f == nil { - pd.rt.f = netpollReadDeadline - pd.rt.when = pd.rd - pd.rt.arg = pd - pd.rt.seq = pd.rseq - addtimer(&pd.rt) + } else if pd.rd != rd0 || combo != combo0 { + pd.rseq++ // invalidate current timers + if pd.rd > 0 { + modtimer(&pd.rt, pd.rd, 0, rtf, pd, pd.rseq) + } else { + deltimer(&pd.rt) + pd.rt.f = nil } - if pd.wd > 0 && pd.wt.f == nil { + } + if pd.wt.f == nil { + if pd.wd > 0 && !combo { pd.wt.f = netpollWriteDeadline pd.wt.when = pd.wd pd.wt.arg = pd pd.wt.seq = pd.wseq addtimer(&pd.wt) } + } else if pd.wd != wd0 || combo != combo0 { + pd.wseq++ // invalidate current timers + if pd.wd > 0 && !combo { + modtimer(&pd.wt, pd.wd, 0, netpollWriteDeadline, pd, pd.wseq) + } else { + deltimer(&pd.wt) + pd.wt.f = nil + } } // If we set the new deadline in the past, unblock currently pending IO if any. var rg, wg *g diff --git a/src/runtime/time.go b/src/runtime/time.go index 5e1a925dee..88fd319a90 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -187,14 +187,22 @@ func deltimer(t *timer) bool { tb := t.tb lock(&tb.lock) + removed, ok := tb.deltimerLocked(t) + unlock(&tb.lock) + if !ok { + badTimer() + } + return removed +} + +func (tb *timersBucket) deltimerLocked(t *timer) (removed, ok bool) { // t may not be registered anymore and may have // a bogus i (typically 0, if generated by Go). // Verify it before proceeding. i := t.i last := len(tb.t) - 1 if i < 0 || i > last || tb.t[i] != t { - unlock(&tb.lock) - return false + return false, true } if i != last { tb.t[i] = tb.t[last] @@ -202,7 +210,7 @@ func deltimer(t *timer) bool { } tb.t[last] = nil tb.t = tb.t[:last] - ok := true + ok = true if i != last { if !siftupTimer(tb.t, i) { ok = false @@ -211,11 +219,26 @@ func deltimer(t *timer) bool { ok = false } } + return true, ok +} + +func modtimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) { + tb := t.tb + + lock(&tb.lock) + _, ok := tb.deltimerLocked(t) + if ok { + t.when = when + t.period = period + t.f = f + t.arg = arg + t.seq = seq + ok = tb.addtimerLocked(t) + } unlock(&tb.lock) if !ok { badTimer() } - return true } // Timerproc runs the time-driven events. From a1ee0a21cfd3d44f4585c1eb57698460abbfc7f1 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 17:27:16 +0100 Subject: [PATCH 55/76] runtime, time: refactor startNano handling Move startNano from runtime to time package. In preparation for a subsequent change that speeds up Since and Until. This also makes code simpler as we have less assembly as the result, monotonic time handling is better localized in time package. This changes values returned from nanotime on windows (it does not account for startNano anymore), current comments state that it's important, but it's unclear how it can be important since no other OS does this. Update #25729 Change-Id: I2275d57b7b5ed8fd0d53eb0f19d55a86136cc555 Reviewed-on: https://go-review.googlesource.com/c/146340 Reviewed-by: Ian Lance Taylor --- src/runtime/proc.go | 3 +-- src/runtime/sys_windows_386.s | 7 +------ src/runtime/sys_windows_amd64.s | 2 -- src/runtime/sys_windows_arm.s | 12 ++---------- src/runtime/time.go | 8 -------- src/runtime/timeasm.go | 2 -- src/runtime/timestub.go | 2 +- src/time/sleep.go | 3 --- src/time/time.go | 12 ++++++++++++ 9 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 365e516ec8..542cf1ed70 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -157,8 +157,7 @@ func main() { } }() - // Record when the world started. Must be after runtime_init - // because nanotime on some platforms depends on startNano. + // Record when the world started. runtimeInitTime = nanotime() gcenable() diff --git a/src/runtime/sys_windows_386.s b/src/runtime/sys_windows_386.s index babd91c936..e6d774e66f 100644 --- a/src/runtime/sys_windows_386.s +++ b/src/runtime/sys_windows_386.s @@ -455,9 +455,7 @@ loop: MULL CX IMULL $100, DI ADDL DI, DX - // wintime*100 = DX:AX, subtract startNano and return - SUBL runtime·startNano+0(SB), AX - SBBL runtime·startNano+4(SB), DX + // wintime*100 = DX:AX MOVL AX, ret_lo+0(FP) MOVL DX, ret_hi+4(FP) RET @@ -482,9 +480,6 @@ loop: IMULL $100, DI ADDL DI, DX // w*100 = DX:AX - // subtract startNano and save for return - SUBL runtime·startNano+0(SB), AX - SBBL runtime·startNano+4(SB), DX MOVL AX, mono+12(FP) MOVL DX, mono+16(FP) diff --git a/src/runtime/sys_windows_amd64.s b/src/runtime/sys_windows_amd64.s index ec49caa43e..612f0a474d 100644 --- a/src/runtime/sys_windows_amd64.s +++ b/src/runtime/sys_windows_amd64.s @@ -486,7 +486,6 @@ loop: SHLQ $32, CX ORQ BX, CX IMULQ $100, CX - SUBQ runtime·startNano(SB), CX MOVQ CX, ret+0(FP) RET useQPC: @@ -506,7 +505,6 @@ loop: SHLQ $32, AX ORQ BX, AX IMULQ $100, AX - SUBQ runtime·startNano(SB), AX MOVQ AX, mono+16(FP) MOVQ $_SYSTEM_TIME, DI diff --git a/src/runtime/sys_windows_arm.s b/src/runtime/sys_windows_arm.s index 409c72c554..60a85b8ffb 100644 --- a/src/runtime/sys_windows_arm.s +++ b/src/runtime/sys_windows_arm.s @@ -510,11 +510,7 @@ loop: MULLU R0, R2, (R4, R3) // R4:R3 = R1:R0 * R2 MULA R1, R2, R4, R4 - // wintime*100 = R4:R3, subtract startNano and return - MOVW runtime·startNano+0(SB), R0 - MOVW runtime·startNano+4(SB), R1 - SUB.S R0, R3 - SBC R1, R4 + // wintime*100 = R4:R3 MOVW R3, ret_lo+0(FP) MOVW R4, ret_hi+4(FP) RET @@ -540,11 +536,7 @@ loop: MULLU R0, R2, (R4, R3) // R4:R3 = R1:R0 * R2 MULA R1, R2, R4, R4 - // wintime*100 = R4:R3, subtract startNano and return - MOVW runtime·startNano+0(SB), R0 - MOVW runtime·startNano+4(SB), R1 - SUB.S R0, R3 - SBC R1, R4 + // wintime*100 = R4:R3 MOVW R3, mono+12(FP) MOVW R4, mono+16(FP) diff --git a/src/runtime/time.go b/src/runtime/time.go index 88fd319a90..b345ed4e02 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -470,11 +470,3 @@ func poll_runtimeNano() int64 { func time_runtimeNano() int64 { return nanotime() } - -// Monotonic times are reported as offsets from startNano. -// We initialize startNano to nanotime() - 1 so that on systems where -// monotonic time resolution is fairly low (e.g. Windows 2008 -// which appears to have a default resolution of 15ms), -// we avoid ever reporting a nanotime of 0. -// (Callers may want to use 0 as "time not set".) -var startNano int64 = nanotime() - 1 diff --git a/src/runtime/timeasm.go b/src/runtime/timeasm.go index 5af920c18c..82cf63edff 100644 --- a/src/runtime/timeasm.go +++ b/src/runtime/timeasm.go @@ -3,8 +3,6 @@ // license that can be found in the LICENSE file. // Declarations for operating systems implementing time.now directly in assembly. -// Those systems are also expected to have nanotime subtract startNano, -// so that time.now and nanotime return the same monotonic clock readings. // +build windows diff --git a/src/runtime/timestub.go b/src/runtime/timestub.go index f9230da69f..459bf8e543 100644 --- a/src/runtime/timestub.go +++ b/src/runtime/timestub.go @@ -14,5 +14,5 @@ import _ "unsafe" // for go:linkname //go:linkname time_now time.now func time_now() (sec int64, nsec int32, mono int64) { sec, nsec = walltime() - return sec, nsec, nanotime() - startNano + return sec, nsec, nanotime() } diff --git a/src/time/sleep.go b/src/time/sleep.go index b8c81b437c..10edf6fe0e 100644 --- a/src/time/sleep.go +++ b/src/time/sleep.go @@ -8,9 +8,6 @@ package time // A negative or zero duration causes Sleep to return immediately. func Sleep(d Duration) -// runtimeNano returns the current value of the runtime clock in nanoseconds. -func runtimeNano() int64 - // Interface to timers implemented in package runtime. // Must be in sync with ../runtime/time.go:/^type timer type runtimeTimer struct { diff --git a/src/time/time.go b/src/time/time.go index f2da32dbad..144f2fe73d 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -1050,9 +1050,21 @@ func daysIn(m Month, year int) int { // Provided by package runtime. func now() (sec int64, nsec int32, mono int64) +// runtimeNano returns the current value of the runtime clock in nanoseconds. +func runtimeNano() int64 + +// Monotonic times are reported as offsets from startNano. +// We initialize startNano to runtimeNano() - 1 so that on systems where +// monotonic time resolution is fairly low (e.g. Windows 2008 +// which appears to have a default resolution of 15ms), +// we avoid ever reporting a monotonic time of 0. +// (Callers may want to use 0 as "time not set".) +var startNano int64 = runtimeNano() - 1 + // Now returns the current local time. func Now() Time { sec, nsec, mono := now() + mono -= startNano sec += unixToInternal - minWall if uint64(sec)>>33 != 0 { return Time{uint64(nsec), sec + minWall, Local} From fc3f8d43f1b7da3ee3fb9a5181f2a86841620273 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 17:36:51 +0100 Subject: [PATCH 56/76] time: speed up Since and Until MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit time.now is somewhat expensive (much more expensive than nanotime), in the common case when Time has monotonic time we don't actually need to call time.now in Since/Until as we can do calculation based purely on monotonic times. name old time/op new time/op delta TCP4OneShotTimeout-6 17.0µs ± 0% 17.1µs ± 1% ~ (p=0.151 n=5+5) SetReadDeadline-6 261ns ± 0% 234ns ± 1% -10.35% (p=0.008 n=5+5) Benchmark that only calls Until: benchmark old ns/op new ns/op delta BenchmarkUntil 54.0 29.5 -45.37% Update #25729 Change-Id: I5ac5af3eb1fe9f583cf79299f10b84501b1a0d7d Reviewed-on: https://go-review.googlesource.com/c/146341 Run-TryBot: Dmitry Vyukov Reviewed-by: Ian Lance Taylor TryBot-Result: Gobot Gobot --- src/time/time.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/time/time.go b/src/time/time.go index 144f2fe73d..4241a6241b 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -908,13 +908,27 @@ func (t Time) Sub(u Time) Duration { // Since returns the time elapsed since t. // It is shorthand for time.Now().Sub(t). func Since(t Time) Duration { - return Now().Sub(t) + var now Time + if t.wall&hasMonotonic != 0 { + // Common case optimization: if t has monotomic time, then Sub will use only it. + now = Time{hasMonotonic, runtimeNano() - startNano, nil} + } else { + now = Now() + } + return now.Sub(t) } // Until returns the duration until t. // It is shorthand for t.Sub(time.Now()). func Until(t Time) Duration { - return t.Sub(Now()) + var now Time + if t.wall&hasMonotonic != 0 { + // Common case optimization: if t has monotomic time, then Sub will use only it. + now = Time{hasMonotonic, runtimeNano() - startNano, nil} + } else { + now = Now() + } + return t.Sub(now) } // AddDate returns the time corresponding to adding the From ff51353c3887b9d83130d958fb503ff1f2291fde Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 17:42:15 +0100 Subject: [PATCH 57/76] runtime: move nanotime wrappers to time and poll packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nanotime wrappers in runtime introduce a bunch of unnecessary code onto hot paths, e.g.: 0000000000449d70 : 449d70: 64 48 8b 0c 25 f8 ff mov %fs:0xfffffffffffffff8,%rcx 449d77: ff ff 449d79: 48 3b 61 10 cmp 0x10(%rcx),%rsp 449d7d: 76 26 jbe 449da5 449d7f: 48 83 ec 10 sub $0x10,%rsp 449d83: 48 89 6c 24 08 mov %rbp,0x8(%rsp) 449d88: 48 8d 6c 24 08 lea 0x8(%rsp),%rbp 449d8d: e8 ae 18 01 00 callq 45b640 449d92: 48 8b 04 24 mov (%rsp),%rax 449d96: 48 89 44 24 18 mov %rax,0x18(%rsp) 449d9b: 48 8b 6c 24 08 mov 0x8(%rsp),%rbp 449da0: 48 83 c4 10 add $0x10,%rsp 449da4: c3 retq 449da5: e8 56 e0 00 00 callq 457e00 449daa: eb c4 jmp 449d70 Move them to the corresponding packages which eliminates all of this. name old time/op new time/op delta TCP4OneShotTimeout-6 17.1µs ± 1% 17.0µs ± 0% -0.66% (p=0.032 n=5+5) SetReadDeadline-6 234ns ± 1% 232ns ± 0% -0.77% (p=0.016 n=5+4) Update #25729 Change-Id: Iee05027adcdc289ba895c5f5a37f154e451bc862 Reviewed-on: https://go-review.googlesource.com/c/146342 Run-TryBot: Dmitry Vyukov TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/internal/poll/fd_poll_runtime.go | 2 ++ src/runtime/time.go | 12 ------------ src/time/time.go | 6 +++++- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/internal/poll/fd_poll_runtime.go b/src/internal/poll/fd_poll_runtime.go index b91cbe40e4..a48e62eefa 100644 --- a/src/internal/poll/fd_poll_runtime.go +++ b/src/internal/poll/fd_poll_runtime.go @@ -11,9 +11,11 @@ import ( "sync" "syscall" "time" + _ "unsafe" // for go:linkname ) // runtimeNano returns the current value of the runtime clock in nanoseconds. +//go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 func runtime_pollServerInit() diff --git a/src/runtime/time.go b/src/runtime/time.go index b345ed4e02..28a4722866 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -458,15 +458,3 @@ func siftdownTimer(t []*timer, i int) bool { func badTimer() { panic(errorString("racy use of timers")) } - -// Entry points for net, time to call nanotime. - -//go:linkname poll_runtimeNano internal/poll.runtimeNano -func poll_runtimeNano() int64 { - return nanotime() -} - -//go:linkname time_runtimeNano time.runtimeNano -func time_runtimeNano() int64 { - return nanotime() -} diff --git a/src/time/time.go b/src/time/time.go index 4241a6241b..5dc0d8a973 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -75,7 +75,10 @@ // package time -import "errors" +import ( + "errors" + _ "unsafe" // for go:linkname +) // A Time represents an instant in time with nanosecond precision. // @@ -1065,6 +1068,7 @@ func daysIn(m Month, year int) int { func now() (sec int64, nsec int32, mono int64) // runtimeNano returns the current value of the runtime clock in nanoseconds. +//go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 // Monotonic times are reported as offsets from startNano. From 31e7842f3d3f85f7aec5ca72f59befce9f58a3b6 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 17:56:14 +0100 Subject: [PATCH 58/76] runtime: execute memory barrier conditionally when changing netpoll timers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We only need the memory barrier in poll_runtime_pollSetDeadline only when one of the timers has fired, which is not the expected case. Memory barrier can be somewhat expensive on some archs, so execute it only if one of the timers has in fact fired. name old time/op new time/op delta TCP4OneShotTimeout-6 17.0µs ± 0% 17.1µs ± 0% +0.35% (p=0.032 n=5+5) SetReadDeadline-6 232ns ± 0% 230ns ± 0% -1.03% (p=0.000 n=4+5) Update #25729 Change-Id: Ifce6f505b9e7ba3717bad8f454077a2e94ea6e75 Reviewed-on: https://go-review.googlesource.com/c/146343 Reviewed-by: Ian Lance Taylor --- src/runtime/netpoll.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index 34e7c902eb..f914844cdf 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -254,12 +254,14 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { } // If we set the new deadline in the past, unblock currently pending IO if any. var rg, wg *g - atomicstorep(unsafe.Pointer(&wg), nil) // full memory barrier between stores to rd/wd and load of rg/wg in netpollunblock - if pd.rd < 0 { - rg = netpollunblock(pd, 'r', false) - } - if pd.wd < 0 { - wg = netpollunblock(pd, 'w', false) + if pd.rd < 0 || pd.wd < 0 { + atomicstorep(unsafe.Pointer(&wg), nil) // full memory barrier between stores to rd/wd and load of rg/wg in netpollunblock + if pd.rd < 0 { + rg = netpollunblock(pd, 'r', false) + } + if pd.wd < 0 { + wg = netpollunblock(pd, 'w', false) + } } unlock(&pd.lock) if rg != nil { From 21f7f01289577698eb70a4558d588bd00b6fed01 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 15:07:57 +0100 Subject: [PATCH 59/76] runtime: avoid runtimeNano call on a common netpoll path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runtimeNano is slower than nanotime, so pass the duration to runtime_pollSetDeadline as is. netpoll can add nanotime itself. Arguably a bit simpler because, say, a negative duration clearly represents already expired timer, no need to compare to nanotime again. This may also fix an obscure corner case when a deadline in past which happens to be nanotime 0 is confused with no deadline at all, which are radically different things. Also don't compute any durations and times if Time is zero (currently we first compute everything and then reset d back to 0, which is wasteful). name old time/op new time/op delta TCP4OneShotTimeout-6 17.1µs ± 0% 17.0µs ± 0% ~ (p=0.421 n=5+5) SetReadDeadline-6 230ns ± 0% 205ns ± 1% -10.63% (p=0.008 n=5+5) Change-Id: I2aad699270289a5b9ead68f5e44ec4ec6d96baa0 Reviewed-on: https://go-review.googlesource.com/c/146344 Reviewed-by: Ian Lance Taylor Run-TryBot: Dmitry Vyukov --- src/internal/poll/fd_poll_runtime.go | 15 ++++++--------- src/runtime/netpoll.go | 9 +++++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/internal/poll/fd_poll_runtime.go b/src/internal/poll/fd_poll_runtime.go index a48e62eefa..f4540a60f6 100644 --- a/src/internal/poll/fd_poll_runtime.go +++ b/src/internal/poll/fd_poll_runtime.go @@ -136,15 +136,12 @@ func (fd *FD) SetWriteDeadline(t time.Time) error { } func setDeadlineImpl(fd *FD, t time.Time, mode int) error { - diff := int64(time.Until(t)) - d := runtimeNano() + diff - if d <= 0 && diff > 0 { - // If the user has a deadline in the future, but the delay calculation - // overflows, then set the deadline to the maximum possible value. - d = 1<<63 - 1 - } - if t.IsZero() { - d = 0 + var d int64 + if !t.IsZero() { + d = int64(time.Until(t)) + if d == 0 { + d = -1 // don't confuse deadline right now with no deadline + } } if err := fd.incref(); err != nil { return err diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index f914844cdf..7e6e93d6c3 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -201,8 +201,13 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { } rd0, wd0 := pd.rd, pd.wd combo0 := rd0 > 0 && rd0 == wd0 - if d != 0 && d <= nanotime() { - d = -1 + if d > 0 { + d += nanotime() + if d <= 0 { + // If the user has a deadline in the future, but the delay calculation + // overflows, then set the deadline to the maximum possible value. + d = 1<<63 - 1 + } } if mode == 'r' || mode == 'r'+'w' { pd.rd = d From cdad4080695f09d79c403fd3827ef0ba50c399a8 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 2 Nov 2018 09:12:08 +0100 Subject: [PATCH 60/76] os: add support for long path names on aix RemoveAll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow CL 146020 and enable RemoveAll based on Unlinkat and Openat on aix. Updates #27029 Change-Id: I78b34ed671166ee6fa651d5f2025b88548ee6c68 Reviewed-on: https://go-review.googlesource.com/c/146937 Run-TryBot: Tobias Klauser Reviewed-by: Clément Chigot Reviewed-by: Ian Lance Taylor --- src/internal/syscall/unix/asm_solaris.s | 2 +- src/internal/syscall/unix/at_aix.go | 14 +++++ src/internal/syscall/unix/at_libc.go | 64 +++++++++++++++++++++++ src/internal/syscall/unix/at_solaris.go | 68 +++---------------------- src/os/removeall_at.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_test.go | 4 +- 7 files changed, 89 insertions(+), 67 deletions(-) create mode 100644 src/internal/syscall/unix/at_aix.go create mode 100644 src/internal/syscall/unix/at_libc.go diff --git a/src/internal/syscall/unix/asm_solaris.s b/src/internal/syscall/unix/asm_solaris.s index a7ad26df9b..2057338315 100644 --- a/src/internal/syscall/unix/asm_solaris.s +++ b/src/internal/syscall/unix/asm_solaris.s @@ -6,5 +6,5 @@ // System calls for Solaris are implemented in runtime/syscall_solaris.go -TEXT ·sysvicall6(SB),NOSPLIT,$0-88 +TEXT ·syscall6(SB),NOSPLIT,$0-88 JMP syscall·sysvicall6(SB) diff --git a/src/internal/syscall/unix/at_aix.go b/src/internal/syscall/unix/at_aix.go new file mode 100644 index 0000000000..425df98211 --- /dev/null +++ b/src/internal/syscall/unix/at_aix.go @@ -0,0 +1,14 @@ +// Copyright 2018 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 unix + +//go:cgo_import_dynamic libc_fstatat fstatat "libc.a/shr_64.o" +//go:cgo_import_dynamic libc_openat openat "libc.a/shr_64.o" +//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o" + +const ( + AT_REMOVEDIR = 0x1 + AT_SYMLINK_NOFOLLOW = 0x1 +) diff --git a/src/internal/syscall/unix/at_libc.go b/src/internal/syscall/unix/at_libc.go new file mode 100644 index 0000000000..6c3a8c9160 --- /dev/null +++ b/src/internal/syscall/unix/at_libc.go @@ -0,0 +1,64 @@ +// Copyright 2018 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 aix solaris + +package unix + +import ( + "syscall" + "unsafe" +) + +//go:linkname procFstatat libc_fstatat +//go:linkname procOpenat libc_openat +//go:linkname procUnlinkat libc_unlinkat + +var ( + procFstatat, + procOpenat, + procUnlinkat uintptr +) + +func Unlinkat(dirfd int, path string, flags int) error { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall6(uintptr(unsafe.Pointer(&procUnlinkat)), 3, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), 0, 0, 0) + if errno != 0 { + return errno + } + + return nil +} + +func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + fd, _, errno := syscall6(uintptr(unsafe.Pointer(&procOpenat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) + if errno != 0 { + return 0, errno + } + + return int(fd), nil +} + +func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + _, _, errno := syscall6(uintptr(unsafe.Pointer(&procFstatat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) + if errno != 0 { + return errno + } + + return nil +} diff --git a/src/internal/syscall/unix/at_solaris.go b/src/internal/syscall/unix/at_solaris.go index d63ee990fd..e917c4fc9b 100644 --- a/src/internal/syscall/unix/at_solaris.go +++ b/src/internal/syscall/unix/at_solaris.go @@ -4,72 +4,16 @@ package unix -import ( - "syscall" - "unsafe" -) +import "syscall" -// Implemented in runtime/syscall_solaris.go. -func sysvicall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) +// Implemented as sysvicall6 in runtime/syscall_solaris.go. +func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) //go:cgo_import_dynamic libc_fstatat fstatat "libc.so" //go:cgo_import_dynamic libc_openat openat "libc.so" //go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so" -//go:linkname procFstatat libc_fstatat -//go:linkname procOpenat libc_openat -//go:linkname procUnlinkat libc_unlinkat - -var ( - procFstatat, - procOpenat, - procUnlinkat uintptr +const ( + AT_REMOVEDIR = 0x1 + AT_SYMLINK_NOFOLLOW = 0x1000 ) - -const AT_REMOVEDIR = 0x1 -const AT_SYMLINK_NOFOLLOW = 0x1000 - -func Unlinkat(dirfd int, path string, flags int) error { - var p *byte - p, err := syscall.BytePtrFromString(path) - if err != nil { - return err - } - - _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procUnlinkat)), 3, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), 0, 0, 0) - if errno != 0 { - return errno - } - - return nil -} - -func Openat(dirfd int, path string, flags int, perm uint32) (int, error) { - var p *byte - p, err := syscall.BytePtrFromString(path) - if err != nil { - return 0, err - } - - fd, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procOpenat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0) - if errno != 0 { - return 0, errno - } - - return int(fd), nil -} - -func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error { - var p *byte - p, err := syscall.BytePtrFromString(path) - if err != nil { - return err - } - - _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procFstatat)), 4, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0) - if errno != 0 { - return errno - } - - return nil -} diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go index 12d8152bec..eb220bd103 100644 --- a/src/os/removeall_at.go +++ b/src/os/removeall_at.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris package os diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index f8af0da87f..d1dd43ff6a 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly,!solaris +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris package os diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go index 4daf8c298b..5eec8cd154 100644 --- a/src/os/removeall_test.go +++ b/src/os/removeall_test.go @@ -162,7 +162,7 @@ func TestRemoveAllLarge(t *testing.T) { func TestRemoveAllLongPath(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris": + case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": break default: t.Skip("skipping for not implemented platforms") @@ -212,7 +212,7 @@ func TestRemoveAllLongPath(t *testing.T) { func TestRemoveAllDot(t *testing.T) { switch runtime.GOOS { - case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris": + case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": break default: t.Skip("skipping for not implemented platforms") From 56421f26ef8526427e034639510a162f3b40d825 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 31 Oct 2018 19:36:21 +0100 Subject: [PATCH 61/76] runtime: use StorepNoWB instead of atomicstorep in netpoll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We only need the memory barrier from these stores, and we only store nil over nil or over a static function value. The write barrier is unnecessary. name old time/op new time/op delta TCP4OneShotTimeout-6 17.0µs ± 0% 17.0µs ± 0% -0.43% (p=0.032 n=5+5) SetReadDeadline-6 205ns ± 1% 205ns ± 1% ~ (p=0.683 n=5+5) Update #25729 Change-Id: I66c097a1db7188697ddfc381f31acec053dfed2c Reviewed-on: https://go-review.googlesource.com/c/146345 Run-TryBot: Dmitry Vyukov TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/runtime/netpoll.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index 7e6e93d6c3..4f4abfcd2c 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -260,7 +260,7 @@ func poll_runtime_pollSetDeadline(pd *pollDesc, d int64, mode int) { // If we set the new deadline in the past, unblock currently pending IO if any. var rg, wg *g if pd.rd < 0 || pd.wd < 0 { - atomicstorep(unsafe.Pointer(&wg), nil) // full memory barrier between stores to rd/wd and load of rg/wg in netpollunblock + atomic.StorepNoWB(noescape(unsafe.Pointer(&wg)), nil) // full memory barrier between stores to rd/wd and load of rg/wg in netpollunblock if pd.rd < 0 { rg = netpollunblock(pd, 'r', false) } @@ -287,7 +287,7 @@ func poll_runtime_pollUnblock(pd *pollDesc) { pd.rseq++ pd.wseq++ var rg, wg *g - atomicstorep(unsafe.Pointer(&rg), nil) // full memory barrier between store to closing and read of rg/wg in netpollunblock + atomic.StorepNoWB(noescape(unsafe.Pointer(&rg)), nil) // full memory barrier between store to closing and read of rg/wg in netpollunblock rg = netpollunblock(pd, 'r', false) wg = netpollunblock(pd, 'w', false) if pd.rt.f != nil { @@ -437,7 +437,7 @@ func netpolldeadlineimpl(pd *pollDesc, seq uintptr, read, write bool) { throw("runtime: inconsistent read deadline") } pd.rd = -1 - atomicstorep(unsafe.Pointer(&pd.rt.f), nil) // full memory barrier between store to rd and load of rg in netpollunblock + atomic.StorepNoWB(unsafe.Pointer(&pd.rt.f), nil) // full memory barrier between store to rd and load of rg in netpollunblock rg = netpollunblock(pd, 'r', false) } var wg *g @@ -446,7 +446,7 @@ func netpolldeadlineimpl(pd *pollDesc, seq uintptr, read, write bool) { throw("runtime: inconsistent write deadline") } pd.wd = -1 - atomicstorep(unsafe.Pointer(&pd.wt.f), nil) // full memory barrier between store to wd and load of wg in netpollunblock + atomic.StorepNoWB(unsafe.Pointer(&pd.wt.f), nil) // full memory barrier between store to wd and load of wg in netpollunblock wg = netpollunblock(pd, 'w', false) } unlock(&pd.lock) From 5ee06f547122b7417b70ada55bb0d633e2094d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Fri, 2 Nov 2018 10:02:38 +0100 Subject: [PATCH 62/76] internal/poll, os/exec, runtime: replace PollDescriptor by IsPollDescriptor This commit changes poll.PollDescriptor by poll.IsPollDescriptor. This is needed for OS like AIX which have more than one FD using inside their netpoll implementation. Change-Id: I49e12a8d74045c501e19fdd8527cf166a3c64850 Reviewed-on: https://go-review.googlesource.com/c/146938 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/internal/poll/fd_plan9.go | 8 ++++---- src/internal/poll/fd_poll_nacljs.go | 8 ++++---- src/internal/poll/fd_poll_runtime.go | 10 +++++----- src/os/exec/exec_test.go | 10 +++++++--- src/runtime/netpoll.go | 17 ++++++++++++----- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/internal/poll/fd_plan9.go b/src/internal/poll/fd_plan9.go index 107f454523..fce2285931 100644 --- a/src/internal/poll/fd_plan9.go +++ b/src/internal/poll/fd_plan9.go @@ -193,10 +193,10 @@ func isInterrupted(err error) bool { return err != nil && stringsHasSuffix(err.Error(), "interrupted") } -// PollDescriptor returns the descriptor being used by the poller, -// or ^uintptr(0) if there isn't one. This is only used for testing. -func PollDescriptor() uintptr { - return ^uintptr(0) +// IsPollDescriptor returns true if fd is the descriptor being used by the poller. +// This is only used for testing. +func IsPollDescriptor(fd uintptr) bool { + return false } // RawControl invokes the user-defined function f for a non-IO diff --git a/src/internal/poll/fd_poll_nacljs.go b/src/internal/poll/fd_poll_nacljs.go index 832dddb4aa..e0d3f976f1 100644 --- a/src/internal/poll/fd_poll_nacljs.go +++ b/src/internal/poll/fd_poll_nacljs.go @@ -92,8 +92,8 @@ func setDeadlineImpl(fd *FD, t time.Time, mode int) error { return nil } -// PollDescriptor returns the descriptor being used by the poller, -// or ^uintptr(0) if there isn't one. This is only used for testing. -func PollDescriptor() uintptr { - return ^uintptr(0) +// IsPollDescriptor returns true if fd is the descriptor being used by the poller. +// This is only used for testing. +func IsPollDescriptor(fd uintptr) bool { + return false } diff --git a/src/internal/poll/fd_poll_runtime.go b/src/internal/poll/fd_poll_runtime.go index f4540a60f6..2ee8e7c2c9 100644 --- a/src/internal/poll/fd_poll_runtime.go +++ b/src/internal/poll/fd_poll_runtime.go @@ -19,7 +19,6 @@ import ( func runtimeNano() int64 func runtime_pollServerInit() -func runtime_pollServerDescriptor() uintptr func runtime_pollOpen(fd uintptr) (uintptr, int) func runtime_pollClose(ctx uintptr) func runtime_pollWait(ctx uintptr, mode int) int @@ -27,6 +26,7 @@ func runtime_pollWaitCanceled(ctx uintptr, mode int) int func runtime_pollReset(ctx uintptr, mode int) int func runtime_pollSetDeadline(ctx uintptr, d int64, mode int) func runtime_pollUnblock(ctx uintptr) +func runtime_isPollServerDescriptor(fd uintptr) bool type pollDesc struct { runtimeCtx uintptr @@ -154,8 +154,8 @@ func setDeadlineImpl(fd *FD, t time.Time, mode int) error { return nil } -// PollDescriptor returns the descriptor being used by the poller, -// or ^uintptr(0) if there isn't one. This is only used for testing. -func PollDescriptor() uintptr { - return runtime_pollServerDescriptor() +// IsPollDescriptor returns true if fd is the descriptor being used by the poller. +// This is only used for testing. +func IsPollDescriptor(fd uintptr) bool { + return runtime_isPollServerDescriptor(fd) } diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go index 558345ff63..3e6b7bb95e 100644 --- a/src/os/exec/exec_test.go +++ b/src/os/exec/exec_test.go @@ -459,7 +459,7 @@ func basefds() uintptr { // The poll (epoll/kqueue) descriptor can be numerically // either between stderr and the testlog-fd, or after // testlog-fd. - if poll.PollDescriptor() == n { + if poll.IsPollDescriptor(n) { n++ } for _, arg := range os.Args { @@ -472,7 +472,7 @@ func basefds() uintptr { func closeUnexpectedFds(t *testing.T, m string) { for fd := basefds(); fd <= 101; fd++ { - if fd == poll.PollDescriptor() { + if poll.IsPollDescriptor(fd) { continue } err := os.NewFile(fd, "").Close() @@ -734,6 +734,8 @@ func TestHelperProcess(*testing.T) { ofcmd = "fstat" case "plan9": ofcmd = "/bin/cat" + case "aix": + ofcmd = "procfiles" } args := os.Args @@ -837,7 +839,7 @@ func TestHelperProcess(*testing.T) { // Now verify that there are no other open fds. var files []*os.File for wantfd := basefds() + 1; wantfd <= 100; wantfd++ { - if wantfd == poll.PollDescriptor() { + if poll.IsPollDescriptor(wantfd) { continue } f, err := os.Open(os.Args[0]) @@ -851,6 +853,8 @@ func TestHelperProcess(*testing.T) { switch runtime.GOOS { case "plan9": args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} + case "aix": + args = []string{fmt.Sprint(os.Getpid())} default: args = []string{"-p", fmt.Sprint(os.Getpid())} } diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index 4f4abfcd2c..75db8c6c2f 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -93,12 +93,19 @@ func netpollinited() bool { return atomic.Load(&netpollInited) != 0 } -//go:linkname poll_runtime_pollServerDescriptor internal/poll.runtime_pollServerDescriptor +//go:linkname poll_runtime_isPollServerDescriptor internal/poll.runtime_isPollServerDescriptor -// poll_runtime_pollServerDescriptor returns the descriptor being used, -// or ^uintptr(0) if the system does not use a poll descriptor. -func poll_runtime_pollServerDescriptor() uintptr { - return netpolldescriptor() +// poll_runtime_isPollServerDescriptor returns true if fd is a +// descriptor being used by netpoll. +func poll_runtime_isPollServerDescriptor(fd uintptr) bool { + fds := netpolldescriptor() + if GOOS != "aix" { + return fd == fds + } else { + // AIX have a pipe in its netpoll implementation. + // Therefore, two fd are returned by netpolldescriptor using a mask. + return fd == fds&0xFFFF || fd == (fds>>16)&0xFFFF + } } //go:linkname poll_runtime_pollOpen internal/poll.runtime_pollOpen From bd4b6ca781e7e82580620011f8af2a7f11c5c239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Fri, 2 Nov 2018 10:55:12 +0100 Subject: [PATCH 63/76] all: fix tests for older versions of AIX 7.2 This commit fixes tests which fail on some versions of AIX 7.2 due to internal bugs. getsockname isn't working properly with unix networks. Timezone files aren't returning a correct output. Change-Id: I4ff15683912be62ab86dfbeeb63b73513404d086 Reviewed-on: https://go-review.googlesource.com/c/146940 Run-TryBot: Ian Lance Taylor Reviewed-by: Ian Lance Taylor --- src/net/platform_test.go | 12 ++++++++++++ src/time/zoneinfo_read.go | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/src/net/platform_test.go b/src/net/platform_test.go index 01c579bd8e..7e9ad70d19 100644 --- a/src/net/platform_test.go +++ b/src/net/platform_test.go @@ -7,7 +7,9 @@ package net import ( "internal/testenv" "os" + "os/exec" "runtime" + "strconv" "strings" "testing" ) @@ -35,6 +37,16 @@ func testableNetwork(network string) bool { switch runtime.GOOS { case "android", "nacl", "plan9", "windows": return false + case "aix": + // Unix network isn't properly working on AIX 7.2 with Technical Level < 2 + out, err := exec.Command("oslevel", "-s").Output() + if err != nil { + return false + } + if tl, err := strconv.Atoi(string(out[5:7])); err != nil || tl < 2 { + return false + } + return true } // iOS does not support unix, unixgram. if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { diff --git a/src/time/zoneinfo_read.go b/src/time/zoneinfo_read.go index 15d6aab1de..b495217c06 100644 --- a/src/time/zoneinfo_read.go +++ b/src/time/zoneinfo_read.go @@ -11,6 +11,7 @@ package time import ( "errors" + "runtime" "syscall" ) @@ -172,6 +173,14 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) { return nil, badData } zone[i].name = byteString(abbrev[b:]) + if runtime.GOOS == "aix" && len(name) > 8 && (name[:8] == "Etc/GMT+" || name[:8] == "Etc/GMT-") { + // There is a bug with AIX 7.2 TL 0 with files in Etc, + // GMT+1 will return GMT-1 instead of GMT+1 or -01. + if name != "Etc/GMT+0" { + // GMT+0 is OK + zone[i].name = name[4:] + } + } } // Now the transition time info. From 3a0d6091d64f0204d0942764d3356ac0572be0e3 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Tue, 16 Oct 2018 16:05:51 -0400 Subject: [PATCH 64/76] internal/syscall/windows: add LockFileEx and UnlockFileEx for use in cmd/go Updates #26794 Change-Id: Ic1d3078176721f3d2e5d8188c234383037babbaf Reviewed-on: https://go-review.googlesource.com/c/145177 Run-TryBot: Bryan C. Mills TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick Reviewed-by: Alex Brainman --- .../syscall/windows/syscall_windows.go | 12 +++++++++ .../syscall/windows/zsyscall_windows.go | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index 66fe9324c0..121132f6f7 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -12,7 +12,11 @@ import ( const ( ERROR_SHARING_VIOLATION syscall.Errno = 32 + ERROR_LOCK_VIOLATION syscall.Errno = 33 + ERROR_NOT_SUPPORTED syscall.Errno = 50 + ERROR_CALL_NOT_IMPLEMENTED syscall.Errno = 120 ERROR_INVALID_NAME syscall.Errno = 123 + ERROR_LOCK_FAILED syscall.Errno = 167 ERROR_NO_UNICODE_TRANSLATION syscall.Errno = 1113 ) @@ -255,6 +259,14 @@ func Rename(oldpath, newpath string) error { return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING) } +//sys LockFileEx(file syscall.Handle, flags uint32, reserved uint32, bytesLow uint32, bytesHigh uint32, overlapped *syscall.Overlapped) (err error) = kernel32.LockFileEx +//sys UnlockFileEx(file syscall.Handle, reserved uint32, bytesLow uint32, bytesHigh uint32, overlapped *syscall.Overlapped) (err error) = kernel32.UnlockFileEx + +const ( + LOCKFILE_FAIL_IMMEDIATELY = 0x00000001 + LOCKFILE_EXCLUSIVE_LOCK = 0x00000002 +) + const MB_ERR_INVALID_CHARS = 8 //sys GetACP() (acp uint32) = kernel32.GetACP diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 2212697b1b..9527a370a4 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -49,6 +49,8 @@ var ( procMoveFileExW = modkernel32.NewProc("MoveFileExW") procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW") procWSASocketW = modws2_32.NewProc("WSASocketW") + procLockFileEx = modkernel32.NewProc("LockFileEx") + procUnlockFileEx = modkernel32.NewProc("UnlockFileEx") procGetACP = modkernel32.NewProc("GetACP") procGetConsoleCP = modkernel32.NewProc("GetConsoleCP") procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar") @@ -127,6 +129,30 @@ func WSASocket(af int32, typ int32, protocol int32, protinfo *syscall.WSAProtoco return } +func LockFileEx(file syscall.Handle, flags uint32, reserved uint32, bytesLow uint32, bytesHigh uint32, overlapped *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(file), uintptr(flags), uintptr(reserved), uintptr(bytesLow), uintptr(bytesHigh), uintptr(unsafe.Pointer(overlapped))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func UnlockFileEx(file syscall.Handle, reserved uint32, bytesLow uint32, bytesHigh uint32, overlapped *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall6(procUnlockFileEx.Addr(), 5, uintptr(file), uintptr(reserved), uintptr(bytesLow), uintptr(bytesHigh), uintptr(unsafe.Pointer(overlapped)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + func GetACP() (acp uint32) { r0, _, _ := syscall.Syscall(procGetACP.Addr(), 0, 0, 0, 0) acp = uint32(r0) From 85525c56ab5fdb214fee70b4b4cce8700344258b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= Date: Fri, 2 Nov 2018 10:09:30 +0100 Subject: [PATCH 65/76] all: skip unsupported tests on AIX This commit skips tests which aren't yet supported on AIX. nosplit.go is disabled because stackGuardMultiplier is increased for syscalls. Change-Id: Ib5ff9a4539c7646bcb6caee159f105ff8a160ad7 Reviewed-on: https://go-review.googlesource.com/c/146939 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick --- src/cmd/internal/obj/x86/issue19518_test.go | 10 ++-------- src/cmd/internal/obj/x86/obj6_test.go | 8 +------- src/debug/elf/file_test.go | 2 +- src/runtime/crash_test.go | 3 +++ src/runtime/pprof/pprof_test.go | 2 +- src/runtime/runtime-gdb_test.go | 2 ++ test/nosplit.go | 2 +- 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/cmd/internal/obj/x86/issue19518_test.go b/src/cmd/internal/obj/x86/issue19518_test.go index 2fe227ee3f..fa2beb8aad 100644 --- a/src/cmd/internal/obj/x86/issue19518_test.go +++ b/src/cmd/internal/obj/x86/issue19518_test.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "testing" ) @@ -68,13 +67,8 @@ func objdumpOutput(t *testing.T) []byte { testenv.GoToolPath(t), "build", "-o", filepath.Join(tmpdir, "output")) - var env []string - for _, v := range os.Environ() { - if !strings.HasPrefix(v, "GOARCH=") { - env = append(env, v) - } - } - cmd.Env = append(env, "GOARCH=amd64") + cmd.Env = append(os.Environ(), "GOARCH=amd64", "GOOS=linux") + out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("error %s output %s", err, out) diff --git a/src/cmd/internal/obj/x86/obj6_test.go b/src/cmd/internal/obj/x86/obj6_test.go index 2f6296ce8b..c5399744f2 100644 --- a/src/cmd/internal/obj/x86/obj6_test.go +++ b/src/cmd/internal/obj/x86/obj6_test.go @@ -99,13 +99,7 @@ func asmOutput(t *testing.T, s string) []byte { testenv.GoToolPath(t), "tool", "asm", "-S", "-dynlink", "-o", filepath.Join(tmpdir, "output.6"), tmpfile.Name()) - var env []string - for _, v := range os.Environ() { - if !strings.HasPrefix(v, "GOARCH=") { - env = append(env, v) - } - } - cmd.Env = append(env, "GOARCH=amd64") + cmd.Env = append(os.Environ(), "GOARCH=amd64", "GOOS=linux") asmout, err := cmd.CombinedOutput() if err != nil { t.Fatalf("error %s output %s", err, asmout) diff --git a/src/debug/elf/file_test.go b/src/debug/elf/file_test.go index 11d8992b71..d7c1e9f800 100644 --- a/src/debug/elf/file_test.go +++ b/src/debug/elf/file_test.go @@ -784,7 +784,7 @@ func TestCompressedSection(t *testing.T) { func TestNoSectionOverlaps(t *testing.T) { // Ensure cmd/link outputs sections without overlaps. switch runtime.GOOS { - case "android", "darwin", "js", "nacl", "plan9", "windows": + case "aix", "android", "darwin", "js", "nacl", "plan9", "windows": t.Skipf("cmd/link doesn't produce ELF binaries on %s", runtime.GOOS) } _ = net.ResolveIPAddr // force dynamic linkage diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index 6835cacb3f..6fba4dd91a 100644 --- a/src/runtime/crash_test.go +++ b/src/runtime/crash_test.go @@ -623,6 +623,9 @@ func TestBadTraceback(t *testing.T) { } func TestTimePprof(t *testing.T) { + if runtime.GOOS == "aix" { + t.Skip("pprof not yet available on AIX (see golang.org/issue/28555)") + } fn := runTestProg(t, "testprog", "TimeProf") fn = strings.TrimSpace(fn) defer os.Remove(fn) diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index 593924183f..a1089c8fdf 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !nacl,!js +// +build !aix,!nacl,!js package pprof diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 7672e45b03..5d35813708 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -36,6 +36,8 @@ func checkGdbEnvironment(t *testing.T) { if runtime.GOARCH == "mips" { t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939") } + case "aix": + t.Skip("gdb does not work on AIX; see golang.org/issue/28558") } if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { t.Skip("gdb test can fail with GOROOT_FINAL pending") diff --git a/test/nosplit.go b/test/nosplit.go index 1855c010ae..734f456cc9 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -1,4 +1,4 @@ -// +build !nacl,!js,!gcflags_noopt +// +build !nacl,!js,!aix,!gcflags_noopt // run // Copyright 2014 The Go Authors. All rights reserved. From f08352bd16e03555112154781afe84c3b5d6e0c8 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 2 Nov 2018 15:47:40 +0000 Subject: [PATCH 66/76] runtime: look up runtime env variables case insensitively on Windows Fixes #28557 Change-Id: Ifca958b78e8c62fbc66515e693f528d799e8e84b Reviewed-on: https://go-review.googlesource.com/c/147039 Reviewed-by: Ian Lance Taylor --- src/runtime/env_posix.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/runtime/env_posix.go b/src/runtime/env_posix.go index a2daeb7f27..03208c7c10 100644 --- a/src/runtime/env_posix.go +++ b/src/runtime/env_posix.go @@ -14,13 +14,36 @@ func gogetenv(key string) string { throw("getenv before env init") } for _, s := range env { - if len(s) > len(key) && s[len(key)] == '=' && s[:len(key)] == key { + if len(s) > len(key) && s[len(key)] == '=' && envKeyEqual(s[:len(key)], key) { return s[len(key)+1:] } } return "" } +// envKeyEqual reports whether a == b, with ASCII-only case insensitivity +// on Windows. The two strings must have the same length. +func envKeyEqual(a, b string) bool { + if GOOS == "windows" { // case insensitive + for i := 0; i < len(a); i++ { + ca, cb := a[i], b[i] + if ca == cb || lowerASCII(ca) == lowerASCII(cb) { + continue + } + return false + } + return true + } + return a == b +} + +func lowerASCII(c byte) byte { + if 'A' <= c && c <= 'Z' { + return c + ('a' - 'A') + } + return c +} + var _cgo_setenv unsafe.Pointer // pointer to C function var _cgo_unsetenv unsafe.Pointer // pointer to C function From d6e204e71ff68b69af30b8ff0a0026bca1cbee10 Mon Sep 17 00:00:00 2001 From: Vladimir Kovpak Date: Fri, 2 Nov 2018 16:56:42 +0000 Subject: [PATCH 67/76] database/sql: add description to String method of IsolationLevel struct. Add simple description to String method of IsolationLevel struct. Change-Id: I8bdf829c81d4895b8542a3f21437bed61c6e925d GitHub-Last-Rev: 7e16d6a93a9560cf3a09413dfc47d0cb04bb2fde GitHub-Pull-Request: golang/go#28560 Reviewed-on: https://go-review.googlesource.com/c/147040 Reviewed-by: Brad Fitzpatrick --- src/database/sql/sql.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go index 31db7a47d6..16f1c9fce8 100644 --- a/src/database/sql/sql.go +++ b/src/database/sql/sql.go @@ -133,6 +133,7 @@ const ( LevelLinearizable ) +// String returns the name of the transaction isolation level. func (i IsolationLevel) String() string { switch i { case LevelDefault: From e8df7df68f8d3b182570049879e780fec57a1878 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 3 Nov 2018 02:19:47 +0900 Subject: [PATCH 68/76] syscall: add Syscall18 on Windows There are some OpenGL functions that take more than 15 arguments. This CL adds Syscall18 to enable to call such functions on Windows via syscall functions. Fixes #28434 Change-Id: Ic7e37dda9cadf4516183e98166bfc52844ad2bbe Reviewed-on: https://go-review.googlesource.com/c/147117 Reviewed-by: Brad Fitzpatrick Reviewed-by: Ian Lance Taylor Run-TryBot: Brad Fitzpatrick --- src/runtime/syscall_windows.go | 13 +++++++++++++ src/syscall/dll_windows.go | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go index 0858efaf61..8cfc71124a 100644 --- a/src/runtime/syscall_windows.go +++ b/src/runtime/syscall_windows.go @@ -238,3 +238,16 @@ func syscall_Syscall15(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, cgocall(asmstdcallAddr, unsafe.Pointer(c)) return c.r1, c.r2, c.err } + +//go:linkname syscall_Syscall18 syscall.Syscall18 +//go:nosplit +func syscall_Syscall18(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18 uintptr) (r1, r2, err uintptr) { + lockOSThread() + defer unlockOSThread() + c := &getg().m.syscall + c.fn = fn + c.n = nargs + c.args = uintptr(noescape(unsafe.Pointer(&a1))) + cgocall(asmstdcallAddr, unsafe.Pointer(c)) + return c.r1, c.r2, c.err +} diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go index 2ee85a0d77..816334226f 100644 --- a/src/syscall/dll_windows.go +++ b/src/syscall/dll_windows.go @@ -26,6 +26,7 @@ func Syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err func Syscall9(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) func Syscall12(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2 uintptr, err Errno) func Syscall15(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2 uintptr, err Errno) +func Syscall18(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18 uintptr) (r1, r2 uintptr, err Errno) func loadlibrary(filename *uint16) (handle uintptr, err Errno) func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno) func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno) @@ -172,6 +173,12 @@ func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { return Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0) case 15: return Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14]) + case 16: + return Syscall18(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], 0, 0) + case 17: + return Syscall18(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], 0) + case 18: + return Syscall18(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17]) default: panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".") } From 745ec8b922d236016dac99ae270084447dd4758f Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 2 Nov 2018 07:34:52 -0700 Subject: [PATCH 69/76] cmd/go: don't let "go test -c -o /dev/null" overwrite /dev/null Fixes #28549 Change-Id: Iba71bb2edd0759004e0c7df92b2b8f1197bd62d3 Reviewed-on: https://go-review.googlesource.com/c/146901 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/cmd/go/go_test.go | 30 ++++++++++++++++++++++++++++++ src/cmd/go/internal/test/test.go | 20 ++++++++++++-------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index aa6ce27ffc..f956ecb916 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -1462,8 +1462,38 @@ func TestInstallIntoGOPATH(t *testing.T) { func TestBuildOutputToDevNull(t *testing.T) { tg := testgo(t) defer tg.cleanup() + fi1, err1 := os.Lstat(os.DevNull) tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata")) tg.run("build", "-o", os.DevNull, "go-cmd-test") + fi2, err2 := os.Lstat(os.DevNull) + if err1 == nil { + if err2 != nil { + t.Errorf("second stat of /dev/null failed: %v", err2) + } else if !os.SameFile(fi1, fi2) { + t.Errorf("/dev/null changed: now %v was %v", fi1, fi2) + } + } +} + +// Issue 28549. +func TestTestOutputToDevNull(t *testing.T) { + tg := testgo(t) + defer tg.cleanup() + fi1, err1 := os.Lstat(os.DevNull) + tg.makeTempdir() + tg.setenv("GOPATH", tg.path(".")) + tg.tempFile("src/p/p.go", "package p\n") + tg.tempFile("src/p/p_test.go", "package p\nimport \"testing\"\nfunc TestX(t *testing.T) {}\n") + tg.run("test", "-o", os.DevNull, "-c", "p") + tg.mustNotExist("p.test") + fi2, err2 := os.Lstat(os.DevNull) + if err1 == nil { + if err2 != nil { + t.Errorf("second stat of /dev/null failed: %v", err2) + } else if !os.SameFile(fi1, fi2) { + t.Errorf("/dev/null changed: now %v was %v", fi1, fi2) + } + } } func TestPackageMainTestImportsArchiveNotBinary(t *testing.T) { diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 70deea3643..750b515e41 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -887,15 +887,19 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin target = filepath.Join(base.Cwd, target) } } - pmain.Target = target - installAction = &work.Action{ - Mode: "test build", - Func: work.BuildInstallFunc, - Deps: []*work.Action{buildAction}, - Package: pmain, - Target: target, + if target == os.DevNull { + runAction = buildAction + } else { + pmain.Target = target + installAction = &work.Action{ + Mode: "test build", + Func: work.BuildInstallFunc, + Deps: []*work.Action{buildAction}, + Package: pmain, + Target: target, + } + runAction = installAction // make sure runAction != nil even if not running test } - runAction = installAction // make sure runAction != nil even if not running test } var vetRunAction *work.Action if testC { From 4d567310d24032e887576c798b65ffcb4d6d8498 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 2 Nov 2018 06:55:48 -0700 Subject: [PATCH 70/76] cmd/cgo: accept expressions as untyped constants Fixes #28545 Change-Id: I31c57ce11aca651cacc72235c7753e0c0fd170ef Reviewed-on: https://go-review.googlesource.com/c/146900 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Dmitri Shuralyov --- misc/cgo/test/issue28545.go | 20 ++++++++++++++++++++ src/cmd/cgo/gcc.go | 19 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 misc/cgo/test/issue28545.go diff --git a/misc/cgo/test/issue28545.go b/misc/cgo/test/issue28545.go new file mode 100644 index 0000000000..802a20b779 --- /dev/null +++ b/misc/cgo/test/issue28545.go @@ -0,0 +1,20 @@ +// Copyright 2018 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. + +// Failed to add type conversion for negative constant. +// No runtime test; just make sure it compiles. + +package cgotest + +/* +#include + +static void issue28545F(char **p, int n, complex double a) {} +*/ +import "C" + +func issue28545G(p **C.char) { + C.issue28545F(p, -1, (0)) + C.issue28545F(p, 2+3, complex(1, 1)) +} diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index e8be785bf6..9b615db5db 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -1214,7 +1214,7 @@ func (p *Package) isType(t ast.Expr) bool { return false } -// isConst returns whether x is an untyped constant. +// isConst returns whether x is an untyped constant expression. func (p *Package) isConst(f *File, x ast.Expr) bool { switch x := x.(type) { case *ast.BasicLit: @@ -1233,6 +1233,23 @@ func (p *Package) isConst(f *File, x ast.Expr) bool { strings.HasPrefix(x.Name, "_Ciconst_") || strings.HasPrefix(x.Name, "_Cfconst_") || strings.HasPrefix(x.Name, "_Csconst_") + case *ast.UnaryExpr: + return p.isConst(f, x.X) + case *ast.BinaryExpr: + return p.isConst(f, x.X) && p.isConst(f, x.Y) + case *ast.ParenExpr: + return p.isConst(f, x.X) + case *ast.CallExpr: + // Calling the builtin function complex on two untyped + // constants returns an untyped constant. + // TODO: It's possible to construct a case that will + // erroneously succeed if there is a local function + // named "complex", shadowing the builtin, that returns + // a numeric type. I can't think of any cases that will + // erroneously fail. + if id, ok := x.Fun.(*ast.Ident); ok && id.Name == "complex" && len(x.Args) == 2 { + return p.isConst(f, x.Args[0]) && p.isConst(f, x.Args[1]) + } } return false } From c5d78f512ae6e3867266cfd1cf4cf2194388cbfb Mon Sep 17 00:00:00 2001 From: Andrew Bonventre Date: Fri, 2 Nov 2018 15:28:32 -0400 Subject: [PATCH 71/76] doc: document Go 1.11.2 Change-Id: Iaff03911f1807d462f1966590626bd486807f53d Reviewed-on: https://go-review.googlesource.com/c/147178 Reviewed-by: Brad Fitzpatrick --- doc/devel/release.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/devel/release.html b/doc/devel/release.html index 0448c0d43e..c8dd215220 100644 --- a/doc/devel/release.html +++ b/doc/devel/release.html @@ -41,6 +41,14 @@ See the Go 1.11.1 milestone on our issue tracker for details.

+

+go1.11.2 (released 2018/11/02) includes fixes to the compiler, linker, +documentation, go command, and the database/sql and +go/types packages. +See the Go +1.11.2 milestone on our issue tracker for details. +

+

go1.10 (released 2018/02/16)

From 2764d5ee7b23ae1caf2a4cd4506116a1b9efbf66 Mon Sep 17 00:00:00 2001 From: Andrew Bonventre Date: Fri, 2 Nov 2018 15:31:37 -0400 Subject: [PATCH 72/76] doc: document Go 1.10.5 Change-Id: I11adca150ab795607b832fb354a3e065655e1020 Reviewed-on: https://go-review.googlesource.com/c/147179 Reviewed-by: Brad Fitzpatrick --- doc/devel/release.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/devel/release.html b/doc/devel/release.html index c8dd215220..b02efed501 100644 --- a/doc/devel/release.html +++ b/doc/devel/release.html @@ -91,6 +91,13 @@ See the Go 1.10.4 milestone on our issue tracker for details.

+

+go1.10.5 (released 2018/11/02) includes fixes to the go command, linker, runtime +and the database/sql package. +See the Go +1.10.5 milestone on our issue tracker for details. +

+

go1.9 (released 2017/08/24)

From b2c397e53798fad7395fa8c67f66d9200d663ae0 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Tue, 16 Oct 2018 15:31:07 -0700 Subject: [PATCH 73/76] cmd/compile: disallow converting string to notinheap slice Unlikely to happen in practice, but easy enough to prevent and might as well do so for completeness. Fixes #28243. Change-Id: I848c3af49cb923f088e9490c6a79373e182fad08 Reviewed-on: https://go-review.googlesource.com/c/142719 Run-TryBot: Matthew Dempsky Reviewed-by: Austin Clements --- src/cmd/compile/internal/gc/subr.go | 12 ++++++++++-- test/notinheap.go | 8 ++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go index df3bde86ea..97f7e4880d 100644 --- a/src/cmd/compile/internal/gc/subr.go +++ b/src/cmd/compile/internal/gc/subr.go @@ -696,14 +696,22 @@ func convertop(src *types.Type, dst *types.Type, why *string) Op { } // Conversions from regular to go:notinheap are not allowed - // (unless it's unsafe.Pointer). This is a runtime-specific - // rule. + // (unless it's unsafe.Pointer). These are runtime-specific + // rules. + // (a) Disallow (*T) to (*U) where T is go:notinheap but U isn't. if src.IsPtr() && dst.IsPtr() && dst.Elem().NotInHeap() && !src.Elem().NotInHeap() { if why != nil { *why = fmt.Sprintf(":\n\t%v is go:notinheap, but %v is not", dst.Elem(), src.Elem()) } return 0 } + // (b) Disallow string to []T where T is go:notinheap. + if src.IsString() && dst.IsSlice() && dst.Elem().NotInHeap() && (dst.Elem().Etype == types.Bytetype.Etype || dst.Elem().Etype == types.Runetype.Etype) { + if why != nil { + *why = fmt.Sprintf(":\n\t%v is go:notinheap", dst.Elem()) + } + return 0 + } // 1. src can be assigned to dst. op := assignop(src, dst, why) diff --git a/test/notinheap.go b/test/notinheap.go index 44b79646ef..16c3f8faf0 100644 --- a/test/notinheap.go +++ b/test/notinheap.go @@ -46,10 +46,18 @@ type t1 struct{ x int } //go:notinheap type t2 t1 +//go:notinheap +type t3 byte + +//go:notinheap +type t4 rune + var sink interface{} func i() { sink = new(t1) // no error sink = (*t2)(new(t1)) // ERROR "cannot convert(.|\n)*t2 is go:notinheap" sink = (*t2)(new(struct{ x int })) // ERROR "cannot convert(.|\n)*t2 is go:notinheap" + sink = []t3("foo") // ERROR "cannot convert(.|\n)*t3 is go:notinheap" + sink = []t4("bar") // ERROR "cannot convert(.|\n)*t4 is go:notinheap" } From 61e2b75a2f9856e06e05e64603c95809be21ba29 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 2 Nov 2018 16:56:09 -0400 Subject: [PATCH 74/76] go/build: add go1.12 release tag Change-Id: I82e3f9140e5d14f02beef64c474a3ae88fe256e1 Reviewed-on: https://go-review.googlesource.com/c/147219 Run-TryBot: Alan Donovan Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/go/build/build.go | 2 +- src/go/build/doc.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/go/build/build.go b/src/go/build/build.go index 015551d008..91fe4cfc74 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -298,7 +298,7 @@ func defaultContext() Context { // (perhaps it is the stub to use in that case) should say "+build !go1.x". // NOTE: If you add to this list, also update the doc comment in doc.go. // NOTE: The last element in ReleaseTags should be the current release. - const version = 11 // go1.11 + const version = 12 // go1.12 for i := 1; i <= version; i++ { c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i)) } diff --git a/src/go/build/doc.go b/src/go/build/doc.go index d803b8967b..682315cbd6 100644 --- a/src/go/build/doc.go +++ b/src/go/build/doc.go @@ -108,6 +108,7 @@ // - "go1.9", from Go version 1.9 onward // - "go1.10", from Go version 1.10 onward // - "go1.11", from Go version 1.11 onward +// - "go1.12", from Go version 1.12 onward // - any additional words listed in ctxt.BuildTags // // There are no build tags for beta or minor releases. Programs that need the From 7aee7990ab82371af2558dd2a94ca9ed95227592 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 25 Oct 2018 15:11:13 -0400 Subject: [PATCH 75/76] crypto/tls: implement TLS 1.3 extensions for ClientHello and ServerHello Updates #9671 Change-Id: Ia68224aca866dc3c98af1fccbe56bfb3f22da9f6 Reviewed-on: https://go-review.googlesource.com/c/144578 Run-TryBot: Filippo Valsorda TryBot-Result: Gobot Gobot Reviewed-by: Adam Langley --- src/crypto/tls/common.go | 50 +++- src/crypto/tls/handshake_messages.go | 278 ++++++++++++++++++++-- src/crypto/tls/handshake_messages_test.go | 51 +++- 3 files changed, 344 insertions(+), 35 deletions(-) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 9d9137bc68..717c5f0b0e 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -73,16 +73,22 @@ const ( // TLS extension numbers const ( - extensionServerName uint16 = 0 - extensionStatusRequest uint16 = 5 - extensionSupportedCurves uint16 = 10 - extensionSupportedPoints uint16 = 11 - extensionSignatureAlgorithms uint16 = 13 - extensionALPN uint16 = 16 - extensionSCT uint16 = 18 // RFC 6962, Section 6 - extensionSessionTicket uint16 = 35 - extensionNextProtoNeg uint16 = 13172 // not IANA assigned - extensionRenegotiationInfo uint16 = 0xff01 + extensionServerName uint16 = 0 + extensionStatusRequest uint16 = 5 + extensionSupportedCurves uint16 = 10 // supported_groups in TLS 1.3, see RFC 8446, Section 4.2.7 + extensionSupportedPoints uint16 = 11 + extensionSignatureAlgorithms uint16 = 13 + extensionALPN uint16 = 16 + extensionSCT uint16 = 18 + extensionSessionTicket uint16 = 35 + extensionPreSharedKey uint16 = 41 + extensionSupportedVersions uint16 = 43 + extensionCookie uint16 = 44 + extensionPSKModes uint16 = 45 + extensionSignatureAlgorithmsCert uint16 = 50 + extensionKeyShare uint16 = 51 + extensionNextProtoNeg uint16 = 13172 // not IANA assigned + extensionRenegotiationInfo uint16 = 0xff01 ) // TLS signaling cipher suite values @@ -91,7 +97,10 @@ const ( ) // CurveID is the type of a TLS identifier for an elliptic curve. See -// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8 +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8. +// +// In TLS 1.3, this type is called NamedGroup, but at this time this library +// only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7. type CurveID uint16 const ( @@ -101,6 +110,25 @@ const ( X25519 CurveID = 29 ) +// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. +type keyShare struct { + group CurveID + data []byte +} + +// TLS 1.3 PSK Key Exchange Modes. See RFC 8446, Section 4.2.9. +const ( + pskModePlain uint8 = 0 + pskModeDHE uint8 = 1 +) + +// TLS 1.3 PSK Identity. Can be a Session Ticket, or a reference to a saved +// session. See RFC 8446, Section 4.2.11. +type pskIdentity struct { + label []byte + obfuscatedTicketAge uint32 +} + // TLS Elliptic Curve Point Formats // https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-9 const ( diff --git a/src/crypto/tls/handshake_messages.go b/src/crypto/tls/handshake_messages.go index d6785550a2..d04efc98f6 100644 --- a/src/crypto/tls/handshake_messages.go +++ b/src/crypto/tls/handshake_messages.go @@ -49,24 +49,31 @@ func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool { } type clientHelloMsg struct { - raw []byte - vers uint16 - random []byte - sessionId []byte - cipherSuites []uint16 - compressionMethods []uint8 - nextProtoNeg bool - serverName string - ocspStapling bool - scts bool - supportedCurves []CurveID - supportedPoints []uint8 - ticketSupported bool - sessionTicket []uint8 - supportedSignatureAlgorithms []SignatureScheme - secureRenegotiation []byte - secureRenegotiationSupported bool - alpnProtocols []string + raw []byte + vers uint16 + random []byte + sessionId []byte + cipherSuites []uint16 + compressionMethods []uint8 + nextProtoNeg bool + serverName string + ocspStapling bool + supportedCurves []CurveID + supportedPoints []uint8 + ticketSupported bool + sessionTicket []uint8 + supportedSignatureAlgorithms []SignatureScheme + supportedSignatureAlgorithmsCert []SignatureScheme + secureRenegotiationSupported bool + secureRenegotiation []byte + alpnProtocols []string + scts bool + supportedVersions []uint16 + cookie []byte + keyShares []keyShare + pskModes []uint8 + pskIdentities []pskIdentity + pskBinders [][]byte } func (m *clientHelloMsg) marshal() []byte { @@ -123,7 +130,7 @@ func (m *clientHelloMsg) marshal() []byte { }) } if len(m.supportedCurves) > 0 { - // RFC 4492, Section 5.1.1 + // RFC 4492, Section 5.1.1 and RFC 8446, Section 4.2.7 b.AddUint16(extensionSupportedCurves) b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { @@ -160,6 +167,17 @@ func (m *clientHelloMsg) marshal() []byte { }) }) } + if len(m.supportedSignatureAlgorithmsCert) > 0 { + // RFC 8446, Section 4.2.3 + b.AddUint16(extensionSignatureAlgorithmsCert) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, sigAlgo := range m.supportedSignatureAlgorithmsCert { + b.AddUint16(uint16(sigAlgo)) + } + }) + }) + } if m.secureRenegotiationSupported { // RFC 5746, Section 3.2 b.AddUint16(extensionRenegotiationInfo) @@ -187,6 +205,70 @@ func (m *clientHelloMsg) marshal() []byte { b.AddUint16(extensionSCT) b.AddUint16(0) // empty extension_data } + if len(m.supportedVersions) > 0 { + // RFC 8446, Section 4.2.1 + b.AddUint16(extensionSupportedVersions) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + for _, vers := range m.supportedVersions { + b.AddUint16(vers) + } + }) + }) + } + if len(m.cookie) > 0 { + // RFC 8446, Section 4.2.2 + b.AddUint16(extensionCookie) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.cookie) + }) + }) + } + if len(m.keyShares) > 0 { + // RFC 8446, Section 4.2.8 + b.AddUint16(extensionKeyShare) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, ks := range m.keyShares { + b.AddUint16(uint16(ks.group)) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(ks.data) + }) + } + }) + }) + } + if len(m.pskModes) > 0 { + // RFC 8446, Section 4.2.9 + b.AddUint16(extensionPSKModes) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.pskModes) + }) + }) + } + if len(m.pskIdentities) > 0 { // pre_shared_key must be the last extension + // RFC 8446, Section 4.2.11 + b.AddUint16(extensionPreSharedKey) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, psk := range m.pskIdentities { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(psk.label) + }) + b.AddUint32(psk.obfuscatedTicketAge) + } + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, binder := range m.pskBinders { + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(binder) + }) + } + }) + }) + } extensionsPresent = len(b.BytesOrPanic()) > 2 }) @@ -291,7 +373,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { } m.ocspStapling = statusType == statusTypeOCSP case extensionSupportedCurves: - // RFC 4492, Section 5.1.1 + // RFC 4492, Section 5.1.1 and RFC 8446, Section 4.2.7 var curves cryptobyte.String if !extData.ReadUint16LengthPrefixed(&curves) || curves.Empty() { return false @@ -327,6 +409,20 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { m.supportedSignatureAlgorithms = append( m.supportedSignatureAlgorithms, SignatureScheme(sigAndAlg)) } + case extensionSignatureAlgorithmsCert: + // RFC 8446, Section 4.2.3 + var sigAndAlgs cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { + return false + } + for !sigAndAlgs.Empty() { + var sigAndAlg uint16 + if !sigAndAlgs.ReadUint16(&sigAndAlg) { + return false + } + m.supportedSignatureAlgorithmsCert = append( + m.supportedSignatureAlgorithmsCert, SignatureScheme(sigAndAlg)) + } case extensionRenegotiationInfo: // RFC 5746, Section 3.2 if !readUint8LengthPrefixed(&extData, &m.secureRenegotiation) { @@ -349,6 +445,74 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { case extensionSCT: // RFC 6962, Section 3.3.1 m.scts = true + case extensionSupportedVersions: + // RFC 8446, Section 4.2.1 + var versList cryptobyte.String + if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() { + return false + } + for !versList.Empty() { + var vers uint16 + if !versList.ReadUint16(&vers) { + return false + } + m.supportedVersions = append(m.supportedVersions, vers) + } + case extensionCookie: + // RFC 8446, Section 4.2.2 + if !readUint16LengthPrefixed(&extData, &m.cookie) { + return false + } + case extensionKeyShare: + // RFC 8446, Section 4.2.8 + var clientShares cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&clientShares) { + return false + } + for !clientShares.Empty() { + var ks keyShare + if !clientShares.ReadUint16((*uint16)(&ks.group)) || + !readUint16LengthPrefixed(&clientShares, &ks.data) || + len(ks.data) == 0 { + return false + } + m.keyShares = append(m.keyShares, ks) + } + case extensionPSKModes: + // RFC 8446, Section 4.2.9 + if !readUint8LengthPrefixed(&extData, &m.pskModes) { + return false + } + case extensionPreSharedKey: + // RFC 8446, Section 4.2.11 + if !extensions.Empty() { + return false // pre_shared_key must be the last extension + } + var identities cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&identities) || identities.Empty() { + return false + } + for !identities.Empty() { + var psk pskIdentity + if !readUint16LengthPrefixed(&identities, &psk.label) || + !identities.ReadUint32(&psk.obfuscatedTicketAge) || + len(psk.label) == 0 { + return false + } + m.pskIdentities = append(m.pskIdentities, psk) + } + var binders cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&binders) || binders.Empty() { + return false + } + for !binders.Empty() { + var binder []byte + if !readUint8LengthPrefixed(&binders, &binder) || + len(binder) == 0 { + return false + } + m.pskBinders = append(m.pskBinders, binder) + } default: // Ignore unknown extensions. continue @@ -372,11 +536,19 @@ type serverHelloMsg struct { nextProtoNeg bool nextProtos []string ocspStapling bool - scts [][]byte ticketSupported bool - secureRenegotiation []byte secureRenegotiationSupported bool + secureRenegotiation []byte alpnProtocol string + scts [][]byte + supportedVersion uint16 + serverShare keyShare + selectedIdentityPresent bool + selectedIdentity uint16 + + // HelloRetryRequest extensions + cookie []byte + selectedGroup CurveID } func (m *serverHelloMsg) marshal() []byte { @@ -448,6 +620,42 @@ func (m *serverHelloMsg) marshal() []byte { }) }) } + if m.supportedVersion != 0 { + b.AddUint16(extensionSupportedVersions) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16(m.supportedVersion) + }) + } + if m.serverShare.group != 0 { + b.AddUint16(extensionKeyShare) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16(uint16(m.serverShare.group)) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.serverShare.data) + }) + }) + } + if m.selectedIdentityPresent { + b.AddUint16(extensionPreSharedKey) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16(m.selectedIdentity) + }) + } + + if len(m.cookie) > 0 { + b.AddUint16(extensionCookie) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.cookie) + }) + }) + } + if m.selectedGroup != 0 { + b.AddUint16(extensionKeyShare) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16(uint16(m.selectedGroup)) + }) + } extensionsPresent = len(b.BytesOrPanic()) > 2 }) @@ -535,6 +743,32 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { } m.scts = append(m.scts, sct) } + case extensionSupportedVersions: + if !extData.ReadUint16(&m.supportedVersion) { + return false + } + case extensionCookie: + if !readUint16LengthPrefixed(&extData, &m.cookie) { + return false + } + case extensionKeyShare: + // This extension has different formats in SH and HRR, accept either + // and let the handshake logic decide. See RFC 8446, Section 4.2.8. + if len(extData) == 2 { + if !extData.ReadUint16((*uint16)(&m.selectedGroup)) { + return false + } + } else { + if !extData.ReadUint16((*uint16)(&m.serverShare.group)) || + !readUint16LengthPrefixed(&extData, &m.serverShare.data) { + return false + } + } + case extensionPreSharedKey: + m.selectedIdentityPresent = true + if !extData.ReadUint16(&m.selectedIdentity) { + return false + } default: // Ignore unknown extensions. continue diff --git a/src/crypto/tls/handshake_messages_test.go b/src/crypto/tls/handshake_messages_test.go index fbc294b64e..d32f33f378 100644 --- a/src/crypto/tls/handshake_messages_test.go +++ b/src/crypto/tls/handshake_messages_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" "testing/quick" + "time" ) var tests = []interface{}{ @@ -31,7 +32,7 @@ var tests = []interface{}{ } func TestMarshalUnmarshal(t *testing.T) { - rand := rand.New(rand.NewSource(0)) + rand := rand.New(rand.NewSource(time.Now().UnixNano())) for i, iface := range tests { ty := reflect.ValueOf(iface).Type() @@ -132,7 +133,7 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { m.supportedPoints = randomBytes(rand.Intn(5)+1, rand) m.supportedCurves = make([]CurveID, rand.Intn(5)+1) for i := range m.supportedCurves { - m.supportedCurves[i] = CurveID(rand.Intn(30000)) + m.supportedCurves[i] = CurveID(rand.Intn(30000) + 1) } if rand.Intn(10) > 5 { m.ticketSupported = true @@ -145,6 +146,9 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { if rand.Intn(10) > 5 { m.supportedSignatureAlgorithms = supportedSignatureAlgorithms } + if rand.Intn(10) > 5 { + m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms + } for i := 0; i < rand.Intn(5); i++ { m.alpnProtocols = append(m.alpnProtocols, randomString(rand.Intn(20)+1, rand)) } @@ -155,6 +159,31 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { m.secureRenegotiationSupported = true m.secureRenegotiation = randomBytes(rand.Intn(50)+1, rand) } + for i := 0; i < rand.Intn(5); i++ { + m.supportedVersions = append(m.supportedVersions, uint16(rand.Intn(0xffff)+1)) + } + if rand.Intn(10) > 5 { + m.cookie = randomBytes(rand.Intn(500)+1, rand) + } + for i := 0; i < rand.Intn(5); i++ { + var ks keyShare + ks.group = CurveID(rand.Intn(30000) + 1) + ks.data = randomBytes(rand.Intn(200)+1, rand) + m.keyShares = append(m.keyShares, ks) + } + switch rand.Intn(3) { + case 1: + m.pskModes = []uint8{pskModeDHE} + case 2: + m.pskModes = []uint8{pskModeDHE, pskModePlain} + } + for i := 0; i < rand.Intn(5); i++ { + var psk pskIdentity + psk.obfuscatedTicketAge = uint32(rand.Intn(500000)) + psk.label = randomBytes(rand.Intn(500)+1, rand) + m.pskIdentities = append(m.pskIdentities, psk) + m.pskBinders = append(m.pskBinders, randomBytes(rand.Intn(50)+32, rand)) + } return reflect.ValueOf(m) } @@ -190,6 +219,24 @@ func (*serverHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { m.secureRenegotiationSupported = true m.secureRenegotiation = randomBytes(rand.Intn(50)+1, rand) } + if rand.Intn(10) > 5 { + m.supportedVersion = uint16(rand.Intn(0xffff) + 1) + } + if rand.Intn(10) > 5 { + m.cookie = randomBytes(rand.Intn(500)+1, rand) + } + if rand.Intn(10) > 5 { + for i := 0; i < rand.Intn(5); i++ { + m.serverShare.group = CurveID(rand.Intn(30000) + 1) + m.serverShare.data = randomBytes(rand.Intn(200)+1, rand) + } + } else if rand.Intn(10) > 5 { + m.selectedGroup = CurveID(rand.Intn(30000) + 1) + } + if rand.Intn(10) > 5 { + m.selectedIdentityPresent = true + m.selectedIdentity = uint16(rand.Intn(0xffff)) + } return reflect.ValueOf(m) } From ef21689a68c64cdd57a06722152848e63eeac6fe Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 27 Oct 2018 12:50:25 -0400 Subject: [PATCH 76/76] crypto/tls: implement TLS 1.3 record layer and cipher suites Updates #9671 Change-Id: I1ea7b724975c0841d01f4536eebb23956b30d5ea Reviewed-on: https://go-review.googlesource.com/c/145297 Reviewed-by: Adam Langley --- src/crypto/tls/cipher_suites.go | 92 +++++++++++++----- src/crypto/tls/common.go | 51 +++++++--- src/crypto/tls/conn.go | 162 +++++++++++++++++++++++++------- src/crypto/tls/tls_test.go | 4 +- 4 files changed, 238 insertions(+), 71 deletions(-) diff --git a/src/crypto/tls/cipher_suites.go b/src/crypto/tls/cipher_suites.go index e937235876..d948fac8cd 100644 --- a/src/crypto/tls/cipher_suites.go +++ b/src/crypto/tls/cipher_suites.go @@ -5,6 +5,7 @@ package tls import ( + "crypto" "crypto/aes" "crypto/cipher" "crypto/des" @@ -58,8 +59,7 @@ const ( suiteDefaultOff ) -// A cipherSuite is a specific combination of key agreement, cipher and MAC -// function. All cipher suites currently assume RSA key agreement. +// A cipherSuite is a specific combination of key agreement, cipher and MAC function. type cipherSuite struct { id uint16 // the lengths, in bytes, of the key material needed for each component. @@ -71,7 +71,7 @@ type cipherSuite struct { flags int cipher func(key, iv []byte, isRead bool) interface{} mac func(version uint16, macKey []byte) macFunction - aead func(key, fixedNonce []byte) cipher.AEAD + aead func(key, fixedNonce []byte) aead } var cipherSuites = []*cipherSuite{ @@ -103,6 +103,21 @@ var cipherSuites = []*cipherSuite{ {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteDefaultOff, cipherRC4, macSHA1, nil}, } +// A cipherSuiteTLS13 defines only the pair of the AEAD algorithm and hash +// algorithm to be used with HKDF. See RFC 8446, Appendix B.4. +type cipherSuiteTLS13 struct { + id uint16 + keyLen int + aead func(key, fixedNonce []byte) aead + hash crypto.Hash +} + +var cipherSuitesTLS13 = []*cipherSuiteTLS13{ + {TLS_AES_128_GCM_SHA256, 16, aeadAESGCMTLS13, crypto.SHA256}, + {TLS_CHACHA20_POLY1305_SHA256, 32, aeadChaCha20Poly1305, crypto.SHA256}, + {TLS_AES_256_GCM_SHA384, 32, aeadAESGCMTLS13, crypto.SHA384}, +} + func cipherRC4(key, iv []byte, isRead bool) interface{} { cipher, _ := rc4.NewCipher(key) return cipher @@ -161,36 +176,41 @@ type aead interface { explicitNonceLen() int } -// fixedNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to +const ( + aeadNonceLength = 12 + noncePrefixLength = 4 +) + +// prefixNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to // each call. -type fixedNonceAEAD struct { +type prefixNonceAEAD struct { // nonce contains the fixed part of the nonce in the first four bytes. - nonce [12]byte + nonce [aeadNonceLength]byte aead cipher.AEAD } -func (f *fixedNonceAEAD) NonceSize() int { return 8 } -func (f *fixedNonceAEAD) Overhead() int { return f.aead.Overhead() } -func (f *fixedNonceAEAD) explicitNonceLen() int { return 8 } +func (f *prefixNonceAEAD) NonceSize() int { return aeadNonceLength - noncePrefixLength } +func (f *prefixNonceAEAD) Overhead() int { return f.aead.Overhead() } +func (f *prefixNonceAEAD) explicitNonceLen() int { return f.NonceSize() } -func (f *fixedNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte { +func (f *prefixNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte { copy(f.nonce[4:], nonce) return f.aead.Seal(out, f.nonce[:], plaintext, additionalData) } -func (f *fixedNonceAEAD) Open(out, nonce, plaintext, additionalData []byte) ([]byte, error) { +func (f *prefixNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) { copy(f.nonce[4:], nonce) - return f.aead.Open(out, f.nonce[:], plaintext, additionalData) + return f.aead.Open(out, f.nonce[:], ciphertext, additionalData) } // xoredNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce // before each call. type xorNonceAEAD struct { - nonceMask [12]byte + nonceMask [aeadNonceLength]byte aead cipher.AEAD } -func (f *xorNonceAEAD) NonceSize() int { return 8 } +func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() } func (f *xorNonceAEAD) explicitNonceLen() int { return 0 } @@ -206,11 +226,11 @@ func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte return result } -func (f *xorNonceAEAD) Open(out, nonce, plaintext, additionalData []byte) ([]byte, error) { +func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) { for i, b := range nonce { f.nonceMask[4+i] ^= b } - result, err := f.aead.Open(out, f.nonceMask[:], plaintext, additionalData) + result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData) for i, b := range nonce { f.nonceMask[4+i] ^= b } @@ -218,7 +238,10 @@ func (f *xorNonceAEAD) Open(out, nonce, plaintext, additionalData []byte) ([]byt return result, err } -func aeadAESGCM(key, fixedNonce []byte) cipher.AEAD { +func aeadAESGCM(key, noncePrefix []byte) aead { + if len(noncePrefix) != noncePrefixLength { + panic("tls: internal error: wrong nonce length") + } aes, err := aes.NewCipher(key) if err != nil { panic(err) @@ -228,19 +251,40 @@ func aeadAESGCM(key, fixedNonce []byte) cipher.AEAD { panic(err) } - ret := &fixedNonceAEAD{aead: aead} - copy(ret.nonce[:], fixedNonce) + ret := &prefixNonceAEAD{aead: aead} + copy(ret.nonce[:], noncePrefix) return ret } -func aeadChaCha20Poly1305(key, fixedNonce []byte) cipher.AEAD { +func aeadAESGCMTLS13(key, nonceMask []byte) aead { + if len(nonceMask) != aeadNonceLength { + panic("tls: internal error: wrong nonce length") + } + aes, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + aead, err := cipher.NewGCM(aes) + if err != nil { + panic(err) + } + + ret := &xorNonceAEAD{aead: aead} + copy(ret.nonceMask[:], nonceMask) + return ret +} + +func aeadChaCha20Poly1305(key, nonceMask []byte) aead { + if len(nonceMask) != aeadNonceLength { + panic("tls: internal error: wrong nonce length") + } aead, err := chacha20poly1305.New(key) if err != nil { panic(err) } ret := &xorNonceAEAD{aead: aead} - copy(ret.nonceMask[:], fixedNonce) + copy(ret.nonceMask[:], nonceMask) return ret } @@ -371,6 +415,7 @@ func mutualCipherSuite(have []uint16, want uint16) *cipherSuite { // // Taken from https://www.iana.org/assignments/tls-parameters/tls-parameters.xml const ( + // TLS 1.0 - 1.2 cipher suites. TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005 TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f @@ -394,6 +439,11 @@ const ( TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 uint16 = 0xcca8 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 uint16 = 0xcca9 + // TLS 1.3 cipher suites. + TLS_AES_128_GCM_SHA256 uint16 = 0x1301 + TLS_AES_256_GCM_SHA384 uint16 = 0x1302 + TLS_CHACHA20_POLY1305_SHA256 uint16 = 0x1303 + // TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator // that the client is doing version fallback. See RFC 7507. TLS_FALLBACK_SCSV uint16 = 0x5600 diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 717c5f0b0e..4808c01f9c 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -26,14 +26,19 @@ const ( VersionTLS10 = 0x0301 VersionTLS11 = 0x0302 VersionTLS12 = 0x0303 + + // VersionTLS13 is under development in this library and can't be selected + // nor negotiated yet on either side. + VersionTLS13 = 0x0304 ) const ( - maxPlaintext = 16384 // maximum plaintext payload length - maxCiphertext = 16384 + 2048 // maximum ciphertext payload length - recordHeaderLen = 5 // record header length - maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) - maxWarnAlertCount = 5 // maximum number of consecutive warning alerts + maxPlaintext = 16384 // maximum plaintext payload length + maxCiphertext = 16384 + 2048 // maximum ciphertext payload length + maxCiphertextTLS13 = 16384 + 256 // maximum ciphertext length in TLS 1.3 + recordHeaderLen = 5 // record header length + maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) + maxUselessRecords = 5 // maximum number of consecutive non-advancing records minVersion = VersionTLS10 maxVersion = VersionTLS12 @@ -942,8 +947,9 @@ func defaultConfig() *Config { } var ( - once sync.Once - varDefaultCipherSuites []uint16 + once sync.Once + varDefaultCipherSuites []uint16 + varDefaultCipherSuitesTLS13 []uint16 ) func defaultCipherSuites() []uint16 { @@ -951,19 +957,24 @@ func defaultCipherSuites() []uint16 { return varDefaultCipherSuites } +func defaultCipherSuitesTLS13() []uint16 { + once.Do(initDefaultCipherSuites) + return varDefaultCipherSuitesTLS13 +} + func initDefaultCipherSuites() { var topCipherSuites []uint16 // Check the cpu flags for each platform that has optimized GCM implementations. - // Worst case, these variables will just all be false - hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + // Worst case, these variables will just all be false. + var ( + hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + // Keep in sync with crypto/aes/cipher_s390x.go. + hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) - hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) - - hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X + hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X + ) if hasGCMAsm { // If AES-GCM hardware is provided then prioritise AES-GCM @@ -976,6 +987,11 @@ func initDefaultCipherSuites() { TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, } + varDefaultCipherSuitesTLS13 = []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_CHACHA20_POLY1305_SHA256, + TLS_AES_256_GCM_SHA384, + } } else { // Without AES-GCM hardware, we put the ChaCha20-Poly1305 // cipher suites first. @@ -987,6 +1003,11 @@ func initDefaultCipherSuites() { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, } + varDefaultCipherSuitesTLS13 = []uint16{ + TLS_CHACHA20_POLY1305_SHA256, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + } } varDefaultCipherSuites = make([]uint16, 0, len(cipherSuites)) diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index dae5fd103a..5af1413935 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -94,9 +94,9 @@ type Conn struct { bytesSent int64 packetsSent int64 - // warnCount counts the number of consecutive warning alerts received + // retryCount counts the number of consecutive warning alerts received // by Conn.readRecord. Protected by in.Mutex. - warnCount int + retryCount int // activeCall is an atomic int32; the low bit is whether Close has // been called. the rest of the bits are the number of goroutines @@ -291,9 +291,17 @@ type cbcMode interface { // decrypt authenticates and decrypts the record if protection is active at // this stage. The returned plaintext might overlap with the input. -func (hc *halfConn) decrypt(record []byte) (plaintext []byte, err error) { +func (hc *halfConn) decrypt(record []byte) ([]byte, recordType, error) { + var plaintext []byte + typ := recordType(record[0]) payload := record[recordHeaderLen:] + // In TLS 1.3, change_cipher_spec messages are to be ignored without being + // decrypted. See RFC 8446, Appendix D.4. + if hc.version == VersionTLS13 && typ == recordTypeChangeCipherSpec { + return payload, typ, nil + } + paddingGood := byte(255) paddingLen := 0 @@ -305,7 +313,7 @@ func (hc *halfConn) decrypt(record []byte) (plaintext []byte, err error) { c.XORKeyStream(payload, payload) case aead: if len(payload) < explicitNonceLen { - return nil, alertBadRecordMAC + return nil, 0, alertBadRecordMAC } nonce := payload[:explicitNonceLen] if len(nonce) == 0 { @@ -313,22 +321,27 @@ func (hc *halfConn) decrypt(record []byte) (plaintext []byte, err error) { } payload = payload[explicitNonceLen:] - copy(hc.additionalData[:], hc.seq[:]) - copy(hc.additionalData[8:], record[:3]) - n := len(payload) - c.Overhead() - hc.additionalData[11] = byte(n >> 8) - hc.additionalData[12] = byte(n) + additionalData := hc.additionalData[:] + if hc.version == VersionTLS13 { + additionalData = record[:recordHeaderLen] + } else { + copy(additionalData, hc.seq[:]) + copy(additionalData[8:], record[:3]) + n := len(payload) - c.Overhead() + additionalData[11] = byte(n >> 8) + additionalData[12] = byte(n) + } var err error - plaintext, err = c.Open(payload[:0], nonce, payload, hc.additionalData[:]) + plaintext, err = c.Open(payload[:0], nonce, payload, additionalData) if err != nil { - return nil, alertBadRecordMAC + return nil, 0, alertBadRecordMAC } case cbcMode: blockSize := c.BlockSize() minPayload := explicitNonceLen + roundUp(hc.mac.Size()+1, blockSize) // TODO: vuln? if len(payload)%blockSize != 0 || len(payload) < minPayload { - return nil, alertBadRecordMAC + return nil, 0, alertBadRecordMAC } if explicitNonceLen > 0 { @@ -351,6 +364,26 @@ func (hc *halfConn) decrypt(record []byte) (plaintext []byte, err error) { default: panic("unknown cipher type") } + + if hc.version == VersionTLS13 { + if typ != recordTypeApplicationData { + return nil, 0, alertUnexpectedMessage + } + if len(plaintext) > maxPlaintext+1 { + return nil, 0, alertRecordOverflow + } + // Remove padding and find the ContentType scanning from the end. + for i := len(plaintext) - 1; i >= 0; i-- { + if plaintext[i] != 0 { + typ = recordType(plaintext[i]) + plaintext = plaintext[:i] + break + } + if i == 0 { + return nil, 0, alertUnexpectedMessage + } + } + } } else { plaintext = payload } @@ -358,7 +391,7 @@ func (hc *halfConn) decrypt(record []byte) (plaintext []byte, err error) { if hc.mac != nil { macSize := hc.mac.Size() if len(payload) < macSize { - return nil, alertBadRecordMAC + return nil, 0, alertBadRecordMAC } n := len(payload) - macSize - paddingLen @@ -369,14 +402,14 @@ func (hc *halfConn) decrypt(record []byte) (plaintext []byte, err error) { localMAC := hc.mac.MAC(hc.seq[0:], record[:recordHeaderLen], payload[:n], payload[n+macSize:]) if subtle.ConstantTimeCompare(localMAC, remoteMAC) != 1 || paddingGood != 255 { - return nil, alertBadRecordMAC + return nil, 0, alertBadRecordMAC } plaintext = payload[:n] } hc.incSeq() - return plaintext, nil + return plaintext, typ, nil } // sliceForAppend extends the input slice by n bytes. head is the full extended @@ -438,12 +471,24 @@ func (hc *halfConn) encrypt(record, payload []byte, rand io.Reader) ([]byte, err nonce = hc.seq[:] } - copy(hc.additionalData[:], hc.seq[:]) - copy(hc.additionalData[8:], record[:3]) - hc.additionalData[11] = byte(len(payload) >> 8) - hc.additionalData[12] = byte(len(payload)) + if hc.version == VersionTLS13 { + record = append(record, payload...) - record = c.Seal(record, nonce, payload, hc.additionalData[:]) + // Encrypt the actual ContentType and replace the plaintext one. + record = append(record, record[0]) + record[0] = byte(recordTypeApplicationData) + + n := len(payload) + 1 + c.Overhead() + record[3] = byte(n >> 8) + record[4] = byte(n) + + record = c.Seal(record[:recordHeaderLen], + nonce, record[recordHeaderLen:], record[:recordHeaderLen]) + } else { + copy(hc.additionalData[:], hc.seq[:]) + copy(hc.additionalData[8:], record) + record = c.Seal(record, nonce, payload, hc.additionalData[:]) + } case cbcMode: blockSize := c.BlockSize() plaintextLen := len(payload) + len(mac) @@ -546,7 +591,7 @@ func (c *Conn) readRecord(want recordType) error { vers := uint16(hdr[1])<<8 | uint16(hdr[2]) n := int(hdr[3])<<8 | int(hdr[4]) - if c.haveVers && vers != c.vers { + if c.haveVers && c.vers != VersionTLS13 && vers != c.vers { c.sendAlert(alertProtocolVersion) msg := fmt.Sprintf("received record with version %x when expecting version %x", vers, c.vers) return c.in.setErrorLocked(c.newRecordHeaderError(nil, msg)) @@ -560,7 +605,7 @@ func (c *Conn) readRecord(want recordType) error { return c.in.setErrorLocked(c.newRecordHeaderError(c.conn, "first record does not look like a TLS handshake")) } } - if n > maxCiphertext { + if c.vers == VersionTLS13 && n > maxCiphertextTLS13 || n > maxCiphertext { c.sendAlert(alertRecordOverflow) msg := fmt.Sprintf("oversized record received with length %d", n) return c.in.setErrorLocked(c.newRecordHeaderError(nil, msg)) @@ -574,7 +619,7 @@ func (c *Conn) readRecord(want recordType) error { // Process message. record := c.rawInput.Next(recordHeaderLen + n) - data, err := c.in.decrypt(record) + data, typ, err := c.in.decrypt(record) if err != nil { return c.in.setErrorLocked(c.sendAlert(err.(alert))) } @@ -582,9 +627,31 @@ func (c *Conn) readRecord(want recordType) error { return c.in.setErrorLocked(c.sendAlert(alertRecordOverflow)) } - if typ != recordTypeAlert && len(data) > 0 { - // this is a valid non-alert message: reset the count of alerts - c.warnCount = 0 + // Application Data messages are always protected. + if c.in.cipher == nil && typ == recordTypeApplicationData { + return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 { + // This is a state-advancing message: reset the retry count. + c.retryCount = 0 + } + + // Handshake messages MUST NOT be interleaved with other record types in TLS 1.3. + if c.vers == VersionTLS13 && typ != recordTypeHandshake && c.hand.Len() > 0 { + return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + // In TLS 1.3, change_cipher_spec records are ignored until the Finished. + // See RFC 8446, Appendix D.4. Note that according to Section 5, a server + // can send a ChangeCipherSpec before its ServerHello, when c.vers is still + // unset. That's not useful though and suspicious if the server then selects + // a lower protocol version, so don't allow that. + if c.vers == VersionTLS13 && typ == recordTypeChangeCipherSpec { + if len(data) != 1 || data[0] != 1 || c.handshakeComplete() { + return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + return c.retryReadRecord(want) } switch typ { @@ -598,14 +665,12 @@ func (c *Conn) readRecord(want recordType) error { if alert(data[1]) == alertCloseNotify { return c.in.setErrorLocked(io.EOF) } + if c.vers == VersionTLS13 { + return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])}) + } switch data[0] { case alertLevelWarning: - c.warnCount++ - if c.warnCount > maxWarnAlertCount { - c.sendAlert(alertUnexpectedMessage) - return c.in.setErrorLocked(errors.New("tls: too many warn alerts")) - } - return c.readRecord(want) // Drop the record on the floor and retry. + return c.retryReadRecord(want) // Drop the record on the floor and retry. case alertLevelError: return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])}) default: @@ -629,6 +694,9 @@ func (c *Conn) readRecord(want recordType) error { if typ != want { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } + if len(data) == 0 { + return c.retryReadRecord(want) + } // Note that data is owned by c.rawInput, following the Next call above, // to avoid copying the plaintext. This is safe because c.rawInput is // not read from or written to until c.input is drained. @@ -636,14 +704,35 @@ func (c *Conn) readRecord(want recordType) error { return nil case recordTypeHandshake: - if typ != want && !(c.isClient && c.config.Renegotiation != RenegotiateNever) { + if typ != want && !c.isRenegotiationAcceptable() { return c.in.setErrorLocked(c.sendAlert(alertNoRenegotiation)) } + if len(data) == 0 { + return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } c.hand.Write(data) return nil } } +// retryReadRecord recurses into readRecord to drop a non-advancing record, like +// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3. +func (c *Conn) retryReadRecord(want recordType) error { + c.retryCount++ + if c.retryCount > maxUselessRecords { + c.sendAlert(alertUnexpectedMessage) + return c.in.setErrorLocked(errors.New("tls: too many ignored records")) + } + return c.readRecord(want) +} + +func (c *Conn) isRenegotiationAcceptable() bool { + return c.isClient && + c.vers != VersionTLS13 && + c.handshakeComplete() && + c.config.Renegotiation != RenegotiateNever +} + // atLeastReader reads from R, stopping with EOF once at least N bytes have been // read. It is different from an io.LimitedReader in that it doesn't cut short // the last Read call, and in that it considers an early EOF an error. @@ -767,6 +856,9 @@ func (c *Conn) maxPayloadSizeForWrite(typ recordType) int { panic("unknown cipher type") } } + if c.vers == VersionTLS13 { + payloadBytes-- // encrypted ContentType + } // Allow packet growth in arithmetic progression up to max. pkt := c.packetsSent @@ -822,6 +914,10 @@ func (c *Conn) writeRecordLocked(typ recordType, data []byte) (int, error) { // Some TLS servers fail if the record version is // greater than TLS 1.0 for the initial ClientHello. vers = VersionTLS10 + } else if vers == VersionTLS13 { + // TLS 1.3 froze the record layer version to 1.2. + // See RFC 8446, Section 5.1. + vers = VersionTLS12 } c.outBuf[1] = byte(vers >> 8) c.outBuf[2] = byte(vers) diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 7542699bdc..e9abe01280 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -589,7 +589,7 @@ func TestWarningAlertFlood(t *testing.T) { if err == nil { return errors.New("unexpected lack of error from server") } - const expected = "too many warn" + const expected = "too many ignored" if str := err.Error(); !strings.Contains(str, expected) { return fmt.Errorf("expected error containing %q, but saw: %s", expected, str) } @@ -610,7 +610,7 @@ func TestWarningAlertFlood(t *testing.T) { t.Fatal(err) } - for i := 0; i < maxWarnAlertCount+1; i++ { + for i := 0; i < maxUselessRecords+1; i++ { conn.sendAlert(alertNoRenegotiation) }