1
0
mirror of https://github.com/golang/go synced 2024-09-23 13:30:13 -06:00

[dev.boringcrypto.go1.14] all: merge go1.14.12 into dev.boringcrypto.go1.14

Change-Id: I2b2d6d288a5e68a3bc12b13ac9567d0139969eec
This commit is contained in:
Filippo Valsorda 2020-11-13 03:04:29 +01:00
commit 21ba30ad40
22 changed files with 1006 additions and 41 deletions

View File

@ -0,0 +1,224 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package errorstest
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"unicode"
)
// A manually modified object file could pass unexpected characters
// into the files generated by cgo.
const magicInput = "abcdefghijklmnopqrstuvwxyz0123"
const magicReplace = "\n//go:cgo_ldflag \"-badflag\"\n//"
const cSymbol = "BadSymbol" + magicInput + "Name"
const cDefSource = "int " + cSymbol + " = 1;"
const cRefSource = "extern int " + cSymbol + "; int F() { return " + cSymbol + "; }"
// goSource is the source code for the trivial Go file we use.
// We will replace TMPDIR with the temporary directory name.
const goSource = `
package main
// #cgo LDFLAGS: TMPDIR/cbad.o TMPDIR/cbad.so
// extern int F();
import "C"
func main() {
println(C.F())
}
`
func TestBadSymbol(t *testing.T) {
dir, err := ioutil.TempDir("", "testbadsymbol")
if err != nil {
t.Fatalf("TempDir: %v", err)
}
t.Cleanup(func() {
if err := os.RemoveAll(dir); err != nil {
t.Errorf("TempDir RemoveAll cleanup: %v", err)
}
})
mkdir := func(base string) string {
ret := filepath.Join(dir, base)
if err := os.Mkdir(ret, 0755); err != nil {
t.Fatal(err)
}
return ret
}
cdir := mkdir("c")
godir := mkdir("go")
makeFile := func(mdir, base, source string) string {
ret := filepath.Join(mdir, base)
if err := ioutil.WriteFile(ret, []byte(source), 0644); err != nil {
t.Fatal(err)
}
return ret
}
cDefFile := makeFile(cdir, "cdef.c", cDefSource)
cRefFile := makeFile(cdir, "cref.c", cRefSource)
ccCmd := cCompilerCmd(t)
cCompile := func(arg, base, src string) string {
out := filepath.Join(cdir, base)
run := append(ccCmd, arg, "-o", out, src)
output, err := exec.Command(run[0], run[1:]...).CombinedOutput()
if err != nil {
t.Log(run)
t.Logf("%s", output)
t.Fatal(err)
}
if err := os.Remove(src); err != nil {
t.Fatal(err)
}
return out
}
// Build a shared library that defines a symbol whose name
// contains magicInput.
cShared := cCompile("-shared", "c.so", cDefFile)
// Build an object file that refers to the symbol whose name
// contains magicInput.
cObj := cCompile("-c", "c.o", cRefFile)
// Rewrite the shared library and the object file, replacing
// magicInput with magicReplace. This will have the effect of
// introducing a symbol whose name looks like a cgo command.
// The cgo tool will use that name when it generates the
// _cgo_import.go file, thus smuggling a magic //go:cgo_ldflag
// pragma into a Go file. We used to not check the pragmas in
// _cgo_import.go.
rewrite := func(from, to string) {
obj, err := ioutil.ReadFile(from)
if err != nil {
t.Fatal(err)
}
if bytes.Count(obj, []byte(magicInput)) == 0 {
t.Fatalf("%s: did not find magic string", from)
}
if len(magicInput) != len(magicReplace) {
t.Fatalf("internal test error: different magic lengths: %d != %d", len(magicInput), len(magicReplace))
}
obj = bytes.ReplaceAll(obj, []byte(magicInput), []byte(magicReplace))
if err := ioutil.WriteFile(to, obj, 0644); err != nil {
t.Fatal(err)
}
}
cBadShared := filepath.Join(godir, "cbad.so")
rewrite(cShared, cBadShared)
cBadObj := filepath.Join(godir, "cbad.o")
rewrite(cObj, cBadObj)
goSourceBadObject := strings.ReplaceAll(goSource, "TMPDIR", godir)
makeFile(godir, "go.go", goSourceBadObject)
makeFile(godir, "go.mod", "module badsym")
// Try to build our little package.
cmd := exec.Command("go", "build", "-ldflags=-v")
cmd.Dir = godir
output, err := cmd.CombinedOutput()
// The build should fail, but we want it to fail because we
// detected the error, not because we passed a bad flag to the
// C linker.
if err == nil {
t.Errorf("go build succeeded unexpectedly")
}
t.Logf("%s", output)
for _, line := range bytes.Split(output, []byte("\n")) {
if bytes.Contains(line, []byte("dynamic symbol")) && bytes.Contains(line, []byte("contains unsupported character")) {
// This is the error from cgo.
continue
}
// We passed -ldflags=-v to see the external linker invocation,
// which should not include -badflag.
if bytes.Contains(line, []byte("-badflag")) {
t.Error("output should not mention -badflag")
}
// Also check for compiler errors, just in case.
// GCC says "unrecognized command line option".
// clang says "unknown argument".
if bytes.Contains(line, []byte("unrecognized")) || bytes.Contains(output, []byte("unknown")) {
t.Error("problem should have been caught before invoking C linker")
}
}
}
func cCompilerCmd(t *testing.T) []string {
cc := []string{goEnv(t, "CC")}
out := goEnv(t, "GOGCCFLAGS")
quote := '\000'
start := 0
lastSpace := true
backslash := false
s := string(out)
for i, c := range s {
if quote == '\000' && unicode.IsSpace(c) {
if !lastSpace {
cc = append(cc, s[start:i])
lastSpace = true
}
} else {
if lastSpace {
start = i
lastSpace = false
}
if quote == '\000' && !backslash && (c == '"' || c == '\'') {
quote = c
backslash = false
} else if !backslash && quote == c {
quote = '\000'
} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
backslash = true
} else {
backslash = false
}
}
}
if !lastSpace {
cc = append(cc, s[start:])
}
return cc
}
func goEnv(t *testing.T, key string) string {
out, err := exec.Command("go", "env", key).CombinedOutput()
if err != nil {
t.Logf("go env %s\n", key)
t.Logf("%s", out)
t.Fatal(err)
}
return strings.TrimSpace(string(out))
}

View File

@ -22,6 +22,7 @@ import (
"regexp"
"sort"
"strings"
"unicode"
)
var (
@ -325,6 +326,8 @@ func dynimport(obj string) {
if s.Version != "" {
targ += "#" + s.Version
}
checkImportSymName(s.Name)
checkImportSymName(targ)
fmt.Fprintf(stdout, "//go:cgo_import_dynamic %s %s %q\n", s.Name, targ, s.Library)
}
lib, _ := f.ImportedLibraries()
@ -340,6 +343,7 @@ func dynimport(obj string) {
if len(s) > 0 && s[0] == '_' {
s = s[1:]
}
checkImportSymName(s)
fmt.Fprintf(stdout, "//go:cgo_import_dynamic %s %s %q\n", s, s, "")
}
lib, _ := f.ImportedLibraries()
@ -354,6 +358,8 @@ func dynimport(obj string) {
for _, s := range sym {
ss := strings.Split(s, ":")
name := strings.Split(ss[0], "@")[0]
checkImportSymName(name)
checkImportSymName(ss[0])
fmt.Fprintf(stdout, "//go:cgo_import_dynamic %s %s %q\n", name, ss[0], strings.ToLower(ss[1]))
}
return
@ -371,6 +377,7 @@ func dynimport(obj string) {
// Go symbols.
continue
}
checkImportSymName(s.Name)
fmt.Fprintf(stdout, "//go:cgo_import_dynamic %s %s %q\n", s.Name, s.Name, s.Library)
}
lib, err := f.ImportedLibraries()
@ -386,6 +393,23 @@ func dynimport(obj string) {
fatalf("cannot parse %s as ELF, Mach-O, PE or XCOFF", obj)
}
// checkImportSymName checks a symbol name we are going to emit as part
// of a //go:cgo_import_dynamic pragma. These names come from object
// files, so they may be corrupt. We are going to emit them unquoted,
// so while they don't need to be valid symbol names (and in some cases,
// involving symbol versions, they won't be) they must contain only
// graphic characters and must not contain Go comments.
func checkImportSymName(s string) {
for _, c := range s {
if !unicode.IsGraphic(c) || unicode.IsSpace(c) {
fatalf("dynamic symbol %q contains unsupported character", s)
}
}
if strings.Index(s, "//") >= 0 || strings.Index(s, "/*") >= 0 {
fatalf("dynamic symbol %q contains Go comment")
}
}
// Construct a gcc struct matching the gc argument frame.
// Assumes that in gcc, char is 1 byte, short 2 bytes, int 4 bytes, long long 8 bytes.
// These assumptions are checked by the gccProlog.

View File

@ -655,11 +655,6 @@ var codeRepoVersionsTests = []struct {
path: "swtch.com/testmod",
versions: []string{"v1.0.0", "v1.1.1"},
},
{
vcs: "git",
path: "gopkg.in/russross/blackfriday.v2",
versions: []string{"v2.0.0", "v2.0.1"},
},
{
vcs: "git",
path: "gopkg.in/natefinch/lumberjack.v2",

View File

@ -2632,6 +2632,66 @@ func (b *Builder) cgo(a *Action, cgoExe, objdir string, pcCFLAGS, pcLDFLAGS, cgo
noCompiler()
}
// Double check the //go:cgo_ldflag comments in the generated files.
// The compiler only permits such comments in files whose base name
// starts with "_cgo_". Make sure that the comments in those files
// are safe. This is a backstop against people somehow smuggling
// such a comment into a file generated by cgo.
if cfg.BuildToolchainName == "gc" && !cfg.BuildN {
var flags []string
for _, f := range outGo {
if !strings.HasPrefix(filepath.Base(f), "_cgo_") {
continue
}
src, err := ioutil.ReadFile(f)
if err != nil {
return nil, nil, err
}
const cgoLdflag = "//go:cgo_ldflag"
idx := bytes.Index(src, []byte(cgoLdflag))
for idx >= 0 {
// We are looking at //go:cgo_ldflag.
// Find start of line.
start := bytes.LastIndex(src[:idx], []byte("\n"))
if start == -1 {
start = 0
}
// Find end of line.
end := bytes.Index(src[idx:], []byte("\n"))
if end == -1 {
end = len(src)
} else {
end += idx
}
// Check for first line comment in line.
// We don't worry about /* */ comments,
// which normally won't appear in files
// generated by cgo.
commentStart := bytes.Index(src[start:], []byte("//"))
commentStart += start
// If that line comment is //go:cgo_ldflag,
// it's a match.
if bytes.HasPrefix(src[commentStart:], []byte(cgoLdflag)) {
// Pull out the flag, and unquote it.
// This is what the compiler does.
flag := string(src[idx+len(cgoLdflag) : end])
flag = strings.TrimSpace(flag)
flag = strings.Trim(flag, `"`)
flags = append(flags, flag)
}
src = src[end:]
idx = bytes.Index(src, []byte(cgoLdflag))
}
}
if err := checkLinkerFlags("LDFLAGS", "go:cgo_ldflag", flags); err != nil {
return nil, nil, err
}
}
return outGo, outObj, nil
}

View File

@ -42,8 +42,8 @@ import (
var re = lazyregexp.New
var validCompilerFlags = []*lazyregexp.Regexp{
re(`-D([A-Za-z_].*)`),
re(`-U([A-Za-z_]*)`),
re(`-D([A-Za-z_][A-Za-z0-9_]*)(=[^@\-]*)?`),
re(`-U([A-Za-z_][A-Za-z0-9_]*)`),
re(`-F([^@\-].*)`),
re(`-I([^@\-].*)`),
re(`-O`),
@ -51,8 +51,8 @@ var validCompilerFlags = []*lazyregexp.Regexp{
re(`-W`),
re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
re(`-Wa,-mbig-obj`),
re(`-Wp,-D([A-Za-z_].*)`),
re(`-Wp,-U([A-Za-z_]*)`),
re(`-Wp,-D([A-Za-z_][A-Za-z0-9_]*)(=[^@,\-]*)?`),
re(`-Wp,-U([A-Za-z_][A-Za-z0-9_]*)`),
re(`-ansi`),
re(`-f(no-)?asynchronous-unwind-tables`),
re(`-f(no-)?blocks`),

View File

@ -13,6 +13,7 @@ var goodCompilerFlags = [][]string{
{"-DFOO"},
{"-Dfoo=bar"},
{"-Ufoo"},
{"-Ufoo1"},
{"-F/Qt"},
{"-I/"},
{"-I/etc/passwd"},
@ -24,6 +25,8 @@ var goodCompilerFlags = [][]string{
{"-Wall"},
{"-Wp,-Dfoo=bar"},
{"-Wp,-Ufoo"},
{"-Wp,-Dfoo1"},
{"-Wp,-Ufoo1"},
{"-fobjc-arc"},
{"-fno-objc-arc"},
{"-fomit-frame-pointer"},
@ -78,6 +81,8 @@ var badCompilerFlags = [][]string{
{"-O@1"},
{"-Wa,-foo"},
{"-W@foo"},
{"-Wp,-DX,-D@X"},
{"-Wp,-UX,-U@X"},
{"-g@gdb"},
{"-g-gdb"},
{"-march=@dawn"},

View File

@ -4,7 +4,7 @@ go 1.14
require (
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933
golang.org/x/net v0.0.0-20201029202311-70d85028bad3
golang.org/x/sys v0.0.0-20200201011859-915c9c3d4ccf // indirect
golang.org/x/text v0.3.3-0.20191031172631-4b67af870c6f // indirect
)

View File

@ -2,8 +2,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201029202311-70d85028bad3 h1:zhmuDiUI8/S25xjIBeUn8XwfxP7SKYR41pTuOoa1O+w=
golang.org/x/net v0.0.0-20201029202311-70d85028bad3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200201011859-915c9c3d4ccf h1:+4j7oujXP478CVb/AFvHJmVX5+Pczx2NGts5yirA0oY=
@ -11,5 +11,4 @@ golang.org/x/sys v0.0.0-20200201011859-915c9c3d4ccf/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3-0.20191031172631-4b67af870c6f h1:wYBuYA3M/ZC3iBpL1jKHNRNEK7d8D3JoJmM+zx6rLVQ=
golang.org/x/text v0.3.3-0.20191031172631-4b67af870c6f/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -928,7 +928,7 @@ func (z nat) divRecursiveStep(u, v nat, depth int, tmp *nat, temps []*nat) {
// Now u < (v<<B), compute lower bits in the same way.
// Choose shift = B-1 again.
s := B
s := B - 1
qhat := *temps[depth]
qhat.clear()
qhat.divRecursiveStep(u[s:].norm(), v[s:], depth+1, tmp, temps)

View File

@ -5267,6 +5267,7 @@ func (sc *http2serverConn) processData(f *http2DataFrame) error {
if len(data) > 0 {
wrote, err := st.body.Write(data)
if err != nil {
sc.sendWindowUpdate(nil, int(f.Length)-wrote)
return http2streamError(id, http2ErrCodeStreamClosed)
}
if wrote != len(data) {
@ -7168,6 +7169,7 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client
cc.inflow.add(http2transportDefaultConnFlow + http2initialWindowSize)
cc.bw.Flush()
if cc.werr != nil {
cc.Close()
return nil, cc.werr
}
@ -7533,6 +7535,15 @@ func (cc *http2ClientConn) roundTrip(req *Request) (res *Response, gotErrAfterRe
bodyWriter := cc.t.getBodyWriterState(cs, body)
cs.on100 = bodyWriter.on100
defer func() {
cc.wmu.Lock()
werr := cc.werr
cc.wmu.Unlock()
if werr != nil {
cc.Close()
}
}()
cc.wmu.Lock()
endStream := !hasBody && !hasTrailers
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)

View File

@ -387,7 +387,7 @@ func (r *Request) Clone(ctx context.Context) *Request {
if s := r.TransferEncoding; s != nil {
s2 := make([]string, len(s))
copy(s2, s)
r2.TransferEncoding = s
r2.TransferEncoding = s2
}
r2.Form = cloneURLValues(r.Form)
r2.PostForm = cloneURLValues(r.PostForm)

View File

@ -828,6 +828,27 @@ func TestWithContextDeepCopiesURL(t *testing.T) {
}
}
// Ensure that Request.Clone creates a deep copy of TransferEncoding.
// See issue 41907.
func TestRequestCloneTransferEncoding(t *testing.T) {
body := strings.NewReader("body")
req, _ := NewRequest("POST", "https://example.org/", body)
req.TransferEncoding = []string{
"encoding1",
}
clonedReq := req.Clone(context.Background())
// modify original after deep copy
req.TransferEncoding[0] = "encoding2"
if req.TransferEncoding[0] != "encoding2" {
t.Error("expected req.TransferEncoding to be changed")
}
if clonedReq.TransferEncoding[0] != "encoding1" {
t.Error("expected clonedReq.TransferEncoding to be unchanged")
}
}
func TestNoPanicOnRoundTripWithBasicAuth_h1(t *testing.T) {
testNoPanicWithBasicAuth(t, h1Mode)
}

View File

@ -1221,6 +1221,14 @@ found:
checkdead()
unlock(&sched.lock)
if GOOS == "darwin" {
// Make sure pendingPreemptSignals is correct when an M exits.
// For #41702.
if atomic.Load(&m.signalPending) != 0 {
atomic.Xadd(&pendingPreemptSignals, -1)
}
}
if osStack {
// Return from mstart and let the system thread
// library free the g0 stack and terminate the thread.
@ -3375,11 +3383,24 @@ func syscall_runtime_AfterForkInChild() {
inForkedChild = false
}
// pendingPreemptSignals is the number of preemption signals
// that have been sent but not received. This is only used on Darwin.
// For #41702.
var pendingPreemptSignals uint32
// Called from syscall package before Exec.
//go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec
func syscall_runtime_BeforeExec() {
// Prevent thread creation during exec.
execLock.lock()
// On Darwin, wait for all pending preemption signals to
// be received. See issue #41702.
if GOOS == "darwin" {
for int32(atomic.Load(&pendingPreemptSignals)) > 0 {
osyield()
}
}
}
// Called from syscall package after Exec.

View File

@ -334,6 +334,10 @@ func doSigPreempt(gp *g, ctxt *sigctxt) {
// Acknowledge the preemption.
atomic.Xadd(&gp.m.preemptGen, 1)
atomic.Store(&gp.m.signalPending, 0)
if GOOS == "darwin" {
atomic.Xadd(&pendingPreemptSignals, -1)
}
}
const preemptMSupported = pushCallSupported
@ -360,7 +364,18 @@ func preemptM(mp *m) {
// required).
return
}
// On Darwin, don't try to preempt threads during exec.
// Issue #41702.
if GOOS == "darwin" {
execLock.rlock()
}
if atomic.Cas(&mp.signalPending, 0, 1) {
if GOOS == "darwin" {
atomic.Xadd(&pendingPreemptSignals, 1)
}
// If multiple threads are preempting the same M, it may send many
// signals to the same M such that it hardly make progress, causing
// live-lock problem. Apparently this could happen on darwin. See
@ -368,6 +383,10 @@ func preemptM(mp *m) {
// Only send a signal if there isn't already one pending.
signalM(mp, sigPreempt)
}
if GOOS == "darwin" {
execLock.runlock()
}
}
// sigFetchG fetches the value of G safely when running in a signal handler.
@ -428,6 +447,9 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
// no non-Go signal handler for sigPreempt.
// The default behavior for sigPreempt is to ignore
// the signal, so badsignal will be a no-op anyway.
if GOOS == "darwin" {
atomic.Xadd(&pendingPreemptSignals, -1)
}
return
}
c.fixsigcode(sig)
@ -435,6 +457,8 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
return
}
setg(g.m.gsignal)
// If some non-Go code called sigaltstack, adjust.
var gsignalStack gsignalStack
setStack := adjustSignalStack(sig, g.m, &gsignalStack)
@ -442,8 +466,6 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
g.m.gsignal.stktopsp = getcallersp()
}
setg(g.m.gsignal)
if g.stackguard0 == stackFork {
signalDuringFork(sig)
}

View File

@ -9,11 +9,14 @@ package syscall_test
import (
"internal/testenv"
"io"
"math/rand"
"os"
"os/exec"
"os/signal"
"runtime"
"syscall"
"testing"
"time"
"unsafe"
)
@ -213,3 +216,46 @@ func TestForeground(t *testing.T) {
signal.Reset()
}
// TestExec is for issue #41702.
func TestExec(t *testing.T) {
testenv.MustHaveExec(t)
cmd := exec.Command(os.Args[0], "-test.run=TestExecHelper")
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=2")
o, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("%s\n%v", o, err)
}
}
// TestExecHelper is used by TestExec. It does nothing by itself.
// In testing on macOS 10.14, this used to fail with
// "signal: illegal instruction" more than half the time.
func TestExecHelper(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "2" {
return
}
// We don't have to worry about restoring these values.
// We are in a child process that only runs this test,
// and we are going to call syscall.Exec anyhow.
runtime.GOMAXPROCS(50)
os.Setenv("GO_WANT_HELPER_PROCESS", "3")
stop := time.Now().Add(time.Second)
for i := 0; i < 100; i++ {
go func(i int) {
r := rand.New(rand.NewSource(int64(i)))
for time.Now().Before(stop) {
r.Uint64()
}
}(i)
}
time.Sleep(10 * time.Millisecond)
argv := []string{os.Args[0], "-test.run=TestExecHelper"}
syscall.Exec(os.Args[0], argv, os.Environ())
t.Error("syscall.Exec returned")
}

View File

@ -36,8 +36,39 @@ var (
ReadFile = readFile
LoadTzinfo = loadTzinfo
NextStdChunk = nextStdChunk
Tzset = tzset
TzsetName = tzsetName
TzsetOffset = tzsetOffset
)
type RuleKind int
const (
RuleJulian = RuleKind(ruleJulian)
RuleDOY = RuleKind(ruleDOY)
RuleMonthWeekDay = RuleKind(ruleMonthWeekDay)
)
type Rule struct {
Kind RuleKind
Day int
Week int
Mon int
Time int
}
func TzsetRule(s string) (Rule, string, bool) {
r, rs, ok := tzsetRule(s)
rr := Rule{
Kind: RuleKind(r.kind),
Day: r.day,
Week: r.week,
Mon: r.mon,
Time: r.time,
}
return rr, rs, ok
}
// StdChunkNames maps from nextStdChunk results to the matched strings.
var StdChunkNames = map[int]string{
0: "",

View File

@ -1074,6 +1074,34 @@ func daysIn(m Month, year int) int {
return int(daysBefore[m] - daysBefore[m-1])
}
// daysSinceEpoch takes a year and returns the number of days from
// the absolute epoch to the start of that year.
// This is basically (year - zeroYear) * 365, but accounting for leap days.
func daysSinceEpoch(year int) uint64 {
y := uint64(int64(year) - absoluteZeroYear)
// Add in days from 400-year cycles.
n := y / 400
y -= 400 * n
d := daysPer400Years * n
// Add in 100-year cycles.
n = y / 100
y -= 100 * n
d += daysPer100Years * n
// Add in 4-year cycles.
n = y / 4
y -= 4 * n
d += daysPer4Years * n
// Add in non-leap years.
n = y
d += 365 * n
return d
}
// Provided by package runtime.
func now() (sec int64, nsec int32, mono int64)
@ -1382,28 +1410,8 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T
hour, min = norm(hour, min, 60)
day, hour = norm(day, hour, 24)
y := uint64(int64(year) - absoluteZeroYear)
// Compute days since the absolute epoch.
// Add in days from 400-year cycles.
n := y / 400
y -= 400 * n
d := daysPer400Years * n
// Add in 100-year cycles.
n = y / 100
y -= 100 * n
d += daysPer100Years * n
// Add in 4-year cycles.
n = y / 4
y -= 4 * n
d += daysPer4Years * n
// Add in non-leap years.
n = y
d += 365 * n
d := daysSinceEpoch(year)
// Add in days before this month.
d += uint64(daysBefore[month-1])

View File

@ -66,6 +66,13 @@ var nanoutctests = []TimeTest{
var localtests = []TimeTest{
{0, parsedTime{1969, December, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST"}},
{1221681866, parsedTime{2008, September, 17, 13, 4, 26, 0, Wednesday, -7 * 60 * 60, "PDT"}},
{2159200800, parsedTime{2038, June, 3, 11, 0, 0, 0, Thursday, -7 * 60 * 60, "PDT"}},
{2152173599, parsedTime{2038, March, 14, 1, 59, 59, 0, Sunday, -8 * 60 * 60, "PST"}},
{2152173600, parsedTime{2038, March, 14, 3, 0, 0, 0, Sunday, -7 * 60 * 60, "PDT"}},
{2152173601, parsedTime{2038, March, 14, 3, 0, 1, 0, Sunday, -7 * 60 * 60, "PDT"}},
{2172733199, parsedTime{2038, November, 7, 1, 59, 59, 0, Sunday, -7 * 60 * 60, "PDT"}},
{2172733200, parsedTime{2038, November, 7, 1, 0, 0, 0, Sunday, -8 * 60 * 60, "PST"}},
{2172733201, parsedTime{2038, November, 7, 1, 0, 1, 0, Sunday, -8 * 60 * 60, "PST"}},
}
var nanolocaltests = []TimeTest{

View File

@ -21,6 +21,13 @@ type Location struct {
zone []zone
tx []zoneTrans
// The tzdata information can be followed by a string that describes
// how to handle DST transitions not recorded in zoneTrans.
// The format is the TZ environment variable without a colon; see
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html.
// Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0
extend string
// Most lookups will be for the current time.
// To avoid the binary search through tx, keep a
// static one-element cache that gives the correct
@ -167,6 +174,15 @@ func (l *Location) lookup(sec int64) (name string, offset int, start, end int64)
offset = zone.offset
start = tx[lo].when
// end = maintained during the search
// If we're at the end of the known zone transitions,
// try the extend string.
if lo == len(tx)-1 && l.extend != "" {
if ename, eoffset, estart, eend, ok := tzset(l.extend, end, sec); ok {
return ename, eoffset, estart, eend
}
}
return
}
@ -222,6 +238,338 @@ func (l *Location) firstZoneUsed() bool {
return false
}
// tzset takes a timezone string like the one found in the TZ environment
// variable, the end of the last time zone transition expressed as seconds
// since January 1, 1970 00:00:00 UTC, and a time expressed the same way.
// We call this a tzset string since in C the function tzset reads TZ.
// The return values are as for lookup, plus ok which reports whether the
// parse succeeded.
func tzset(s string, initEnd, sec int64) (name string, offset int, start, end int64, ok bool) {
var (
stdName, dstName string
stdOffset, dstOffset int
)
stdName, s, ok = tzsetName(s)
if ok {
stdOffset, s, ok = tzsetOffset(s)
}
if !ok {
return "", 0, 0, 0, false
}
// The numbers in the tzset string are added to local time to get UTC,
// but our offsets are added to UTC to get local time,
// so we negate the number we see here.
stdOffset = -stdOffset
if len(s) == 0 || s[0] == ',' {
// No daylight savings time.
return stdName, stdOffset, initEnd, omega, true
}
dstName, s, ok = tzsetName(s)
if ok {
if len(s) == 0 || s[0] == ',' {
dstOffset = stdOffset + secondsPerHour
} else {
dstOffset, s, ok = tzsetOffset(s)
dstOffset = -dstOffset // as with stdOffset, above
}
}
if !ok {
return "", 0, 0, 0, false
}
if len(s) == 0 {
// Default DST rules per tzcode.
s = ",M3.2.0,M11.1.0"
}
// The TZ definition does not mention ';' here but tzcode accepts it.
if s[0] != ',' && s[0] != ';' {
return "", 0, 0, 0, false
}
s = s[1:]
var startRule, endRule rule
startRule, s, ok = tzsetRule(s)
if !ok || len(s) == 0 || s[0] != ',' {
return "", 0, 0, 0, false
}
s = s[1:]
endRule, s, ok = tzsetRule(s)
if !ok || len(s) > 0 {
return "", 0, 0, 0, false
}
year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
ysec := int64(yday*secondsPerDay) + sec%secondsPerDay
// Compute start of year in seconds since Unix epoch.
d := daysSinceEpoch(year)
abs := int64(d * secondsPerDay)
abs += absoluteToInternal + internalToUnix
startSec := int64(tzruleTime(year, startRule, stdOffset))
endSec := int64(tzruleTime(year, endRule, dstOffset))
if endSec < startSec {
startSec, endSec = endSec, startSec
stdName, dstName = dstName, stdName
stdOffset, dstOffset = dstOffset, stdOffset
}
// The start and end values that we return are accurate
// close to a daylight savings transition, but are otherwise
// just the start and end of the year. That suffices for
// the only caller that cares, which is Date.
if ysec < startSec {
return stdName, stdOffset, abs, startSec + abs, true
} else if ysec >= endSec {
return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, true
} else {
return dstName, dstOffset, startSec + abs, endSec + abs, true
}
}
// tzsetName returns the timezone name at the start of the tzset string s,
// and the remainder of s, and reports whether the parsing is OK.
func tzsetName(s string) (string, string, bool) {
if len(s) == 0 {
return "", "", false
}
if s[0] != '<' {
for i, r := range s {
switch r {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
if i < 3 {
return "", "", false
}
return s[:i], s[i:], true
}
}
if len(s) < 3 {
return "", "", false
}
return s, "", true
} else {
for i, r := range s {
if r == '>' {
return s[1:i], s[i+1:], true
}
}
return "", "", false
}
}
// tzsetOffset returns the timezone offset at the start of the tzset string s,
// and the remainder of s, and reports whether the parsing is OK.
// The timezone offset is returned as a number of seconds.
func tzsetOffset(s string) (offset int, rest string, ok bool) {
if len(s) == 0 {
return 0, "", false
}
neg := false
if s[0] == '+' {
s = s[1:]
} else if s[0] == '-' {
s = s[1:]
neg = true
}
var hours int
hours, s, ok = tzsetNum(s, 0, 24)
if !ok {
return 0, "", false
}
off := hours * secondsPerHour
if len(s) == 0 || s[0] != ':' {
if neg {
off = -off
}
return off, s, true
}
var mins int
mins, s, ok = tzsetNum(s[1:], 0, 59)
if !ok {
return 0, "", false
}
off += mins * secondsPerMinute
if len(s) == 0 || s[0] != ':' {
if neg {
off = -off
}
return off, s, true
}
var secs int
secs, s, ok = tzsetNum(s[1:], 0, 59)
if !ok {
return 0, "", false
}
off += secs
if neg {
off = -off
}
return off, s, true
}
// ruleKind is the kinds of rules that can be seen in a tzset string.
type ruleKind int
const (
ruleJulian ruleKind = iota
ruleDOY
ruleMonthWeekDay
)
// rule is a rule read from a tzset string.
type rule struct {
kind ruleKind
day int
week int
mon int
time int // transition time
}
// tzsetRule parses a rule from a tzset string.
// It returns the rule, and the remainder of the string, and reports success.
func tzsetRule(s string) (rule, string, bool) {
var r rule
if len(s) == 0 {
return rule{}, "", false
}
ok := false
if s[0] == 'J' {
var jday int
jday, s, ok = tzsetNum(s[1:], 1, 365)
if !ok {
return rule{}, "", false
}
r.kind = ruleJulian
r.day = jday
} else if s[0] == 'M' {
var mon int
mon, s, ok = tzsetNum(s[1:], 1, 12)
if !ok || len(s) == 0 || s[0] != '.' {
return rule{}, "", false
}
var week int
week, s, ok = tzsetNum(s[1:], 1, 5)
if !ok || len(s) == 0 || s[0] != '.' {
return rule{}, "", false
}
var day int
day, s, ok = tzsetNum(s[1:], 0, 6)
if !ok {
return rule{}, "", false
}
r.kind = ruleMonthWeekDay
r.day = day
r.week = week
r.mon = mon
} else {
var day int
day, s, ok = tzsetNum(s, 0, 365)
if !ok {
return rule{}, "", false
}
r.kind = ruleDOY
r.day = day
}
if len(s) == 0 || s[0] != '/' {
r.time = 2 * secondsPerHour // 2am is the default
return r, s, true
}
offset, s, ok := tzsetOffset(s[1:])
if !ok || offset < 0 {
return rule{}, "", false
}
r.time = offset
return r, s, true
}
// tzsetNum parses a number from a tzset string.
// It returns the number, and the remainder of the string, and reports success.
// The number must be between min and max.
func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
if len(s) == 0 {
return 0, "", false
}
num = 0
for i, r := range s {
if r < '0' || r > '9' {
if i == 0 || num < min {
return 0, "", false
}
return num, s[i:], true
}
num *= 10
num += int(r) - '0'
if num > max {
return 0, "", false
}
}
if num < min {
return 0, "", false
}
return num, "", true
}
// tzruleTime takes a year, a rule, and a timezone offset,
// and returns the number of seconds since the start of the year
// that the rule takes effect.
func tzruleTime(year int, r rule, off int) int {
var s int
switch r.kind {
case ruleJulian:
s = (r.day - 1) * secondsPerDay
if isLeap(year) && r.day >= 60 {
s += secondsPerDay
}
case ruleDOY:
s = r.day * secondsPerDay
case ruleMonthWeekDay:
// Zeller's Congruence.
m1 := (r.mon+9)%12 + 1
yy0 := year
if r.mon <= 2 {
yy0--
}
yy1 := yy0 / 100
yy2 := yy0 % 100
dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
if dow < 0 {
dow += 7
}
// Now dow is the day-of-week of the first day of r.mon.
// Get the day-of-month of the first "dow" day.
d := r.day - dow
if d < 0 {
d += 7
}
for i := 1; i < r.week; i++ {
if d+7 >= daysIn(Month(r.mon), year) {
break
}
d += 7
}
d += int(daysBefore[r.mon-1])
if isLeap(year) && r.mon > 2 {
d++
}
s = d * secondsPerDay
}
return s + r.time - off
}
// lookupName returns information about the time zone with
// the given name (such as "EST") at the given pseudo-Unix time
// (what the given time of day would be in UTC).

View File

@ -78,6 +78,13 @@ func (d *dataIO) byte() (n byte, ok bool) {
return p[0], true
}
// read returns the read of the data in the buffer.
func (d *dataIO) rest() []byte {
r := d.p
d.p = nil
return r
}
// Make a string by stopping at the first NUL
func byteString(p []byte) string {
for i := 0; i < len(p); i++ {
@ -213,6 +220,12 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
return nil, badData
}
var extend string
rest := d.rest()
if len(rest) > 2 && rest[0] == '\n' && rest[len(rest)-1] == '\n' {
extend = string(rest[1 : len(rest)-1])
}
// Now we can build up a useful data structure.
// First the zone information.
// utcoff[4] isdst[1] nameindex[1]
@ -289,7 +302,7 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
}
// Committed to succeed.
l := &Location{zone: zone, tx: tx, name: name}
l := &Location{zone: zone, tx: tx, name: name, extend: extend}
// Fill in the cache with information about right now,
// since that will be the most common lookup.
@ -298,10 +311,27 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
l.cacheStart = tx[i].when
l.cacheEnd = omega
zoneIdx := tx[i].index
if i+1 < len(tx) {
l.cacheEnd = tx[i+1].when
} else if l.extend != "" {
// If we're at the end of the known zone transitions,
// try the extend string.
if name, _, estart, eend, ok := tzset(l.extend, l.cacheEnd, sec); ok {
l.cacheStart = estart
l.cacheEnd = eend
// Find the zone that is returned by tzset,
// the last transition is not always the correct zone.
for i, z := range l.zone {
if z.name == name {
zoneIdx = uint8(i)
break
}
}
}
}
l.cacheZone = &l.zone[tx[i].index]
l.cacheZone = &l.zone[zoneIdx]
break
}
}

View File

@ -182,3 +182,116 @@ func TestMalformedTZData(t *testing.T) {
t.Error("expected error, got none")
}
}
func TestLoadLocationFromTZDataSlim(t *testing.T) {
// A 2020b slim tzdata for Europe/Berlin
tzData := "TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x04\x00\x00\x00\x12\xff\xff\xff\xffo\xa2a\xf8\xff\xff\xff\xff\x9b\f\x17`\xff\xff\xff\xff\x9b\xd5\xda\xf0\xff\xff\xff\xff\x9cٮ\x90\xff\xff\xff\xff\x9d\xa4\xb5\x90\xff\xff\xff\xff\x9e\xb9\x90\x90\xff\xff\xff\xff\x9f\x84\x97\x90\xff\xff\xff\xff\xc8\tq\x90\xff\xff\xff\xff\xcc\xe7K\x10\xff\xff\xff\xffͩ\x17\x90\xff\xff\xff\xff\u03a2C\x10\xff\xff\xff\xffϒ4\x10\xff\xff\xff\xffЂ%\x10\xff\xff\xff\xff\xd1r\x16\x10\xff\xff\xff\xffѶ\x96\x00\xff\xff\xff\xff\xd2X\xbe\x80\xff\xff\xff\xffҡO\x10\xff\xff\xff\xff\xd3c\x1b\x90\xff\xff\xff\xff\xd4K#\x90\xff\xff\xff\xff\xd59\xd1 \xff\xff\xff\xff\xd5g\xe7\x90\xff\xff\xff\xffըs\x00\xff\xff\xff\xff\xd6)\xb4\x10\xff\xff\xff\xff\xd7,\x1a\x10\xff\xff\xff\xff\xd8\t\x96\x10\xff\xff\xff\xff\xd9\x02\xc1\x90\xff\xff\xff\xff\xd9\xe9x\x10\x00\x00\x00\x00\x13MD\x10\x00\x00\x00\x00\x143\xfa\x90\x00\x00\x00\x00\x15#\xeb\x90\x00\x00\x00\x00\x16\x13ܐ\x00\x00\x00\x00\x17\x03͐\x00\x00\x00\x00\x17\xf3\xbe\x90\x00\x00\x00\x00\x18㯐\x00\x00\x00\x00\x19Ӡ\x90\x00\x00\x00\x00\x1aÑ\x90\x00\x00\x00\x00\x1b\xbc\xbd\x10\x00\x00\x00\x00\x1c\xac\xae\x10\x00\x00\x00\x00\x1d\x9c\x9f\x10\x00\x00\x00\x00\x1e\x8c\x90\x10\x00\x00\x00\x00\x1f|\x81\x10\x00\x00\x00\x00 lr\x10\x00\x00\x00\x00!\\c\x10\x00\x00\x00\x00\"LT\x10\x00\x00\x00\x00#<E\x10\x00\x00\x00\x00$,6\x10\x00\x00\x00\x00%\x1c'\x10\x00\x00\x00\x00&\f\x18\x10\x00\x00\x00\x00'\x05C\x90\x00\x00\x00\x00'\xf54\x90\x00\x00\x00\x00(\xe5%\x90\x00\x00\x00\x00)\xd5\x16\x90\x00\x00\x00\x00*\xc5\a\x90\x00\x00\x00\x00+\xb4\xf8\x90\x00\x00\x00\x00,\xa4\xe9\x90\x00\x00\x00\x00-\x94ڐ\x00\x00\x00\x00.\x84ː\x00\x00\x00\x00/t\xbc\x90\x00\x00\x00\x000d\xad\x90\x00\x00\x00\x001]\xd9\x10\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x03\x01\x02\x01\x02\x01\x03\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x00\x00\f\x88\x00\x00\x00\x00\x1c \x01\x04\x00\x00\x0e\x10\x00\t\x00\x00*0\x01\rLMT\x00CEST\x00CET\x00CEMT\x00\nCET-1CEST,M3.5.0,M10.5.0/3\n"
reference, err := time.LoadLocationFromTZData("Europe/Berlin", []byte(tzData))
if err != nil {
t.Fatal(err)
}
d := time.Date(2020, time.October, 29, 15, 30, 0, 0, reference)
tzName, tzOffset := d.Zone()
if want := "CET"; tzName != want {
t.Errorf("Zone name == %s, want %s", tzName, want)
}
if want := 3600; tzOffset != want {
t.Errorf("Zone offset == %d, want %d", tzOffset, want)
}
}
func TestTzset(t *testing.T) {
for _, test := range []struct {
inStr string
inEnd int64
inSec int64
name string
off int
start int64
end int64
ok bool
}{
{"", 0, 0, "", 0, 0, 0, false},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2159200800, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2152173599, "PST", -8 * 60 * 60, 2145916800, 2152173600, true},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2152173600, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2152173601, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2172733199, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2172733200, "PST", -8 * 60 * 60, 2172733200, 2177452800, true},
{"PST8PDT,M3.2.0,M11.1.0", 0, 2172733201, "PST", -8 * 60 * 60, 2172733200, 2177452800, true},
} {
name, off, start, end, ok := time.Tzset(test.inStr, test.inEnd, test.inSec)
if name != test.name || off != test.off || start != test.start || end != test.end || ok != test.ok {
t.Errorf("tzset(%q, %d, %d) = %q, %d, %d, %d, %t, want %q, %d, %d, %d, %t", test.inStr, test.inEnd, test.inSec, name, off, start, end, ok, test.name, test.off, test.start, test.end, test.ok)
}
}
}
func TestTzsetName(t *testing.T) {
for _, test := range []struct {
in string
name string
out string
ok bool
}{
{"", "", "", false},
{"X", "", "", false},
{"PST", "PST", "", true},
{"PST8PDT", "PST", "8PDT", true},
{"PST-08", "PST", "-08", true},
{"<A+B>+08", "A+B", "+08", true},
} {
name, out, ok := time.TzsetName(test.in)
if name != test.name || out != test.out || ok != test.ok {
t.Errorf("tzsetName(%q) = %q, %q, %t, want %q, %q, %t", test.in, name, out, ok, test.name, test.out, test.ok)
}
}
}
func TestTzsetOffset(t *testing.T) {
for _, test := range []struct {
in string
off int
out string
ok bool
}{
{"", 0, "", false},
{"X", 0, "", false},
{"+", 0, "", false},
{"+08", 8 * 60 * 60, "", true},
{"-01:02:03", -1*60*60 - 2*60 - 3, "", true},
{"01", 1 * 60 * 60, "", true},
{"100", 0, "", false},
{"8PDT", 8 * 60 * 60, "PDT", true},
} {
off, out, ok := time.TzsetOffset(test.in)
if off != test.off || out != test.out || ok != test.ok {
t.Errorf("tzsetName(%q) = %d, %q, %t, want %d, %q, %t", test.in, off, out, ok, test.off, test.out, test.ok)
}
}
}
func TestTzsetRule(t *testing.T) {
for _, test := range []struct {
in string
r time.Rule
out string
ok bool
}{
{"", time.Rule{}, "", false},
{"X", time.Rule{}, "", false},
{"J10", time.Rule{Kind: time.RuleJulian, Day: 10, Time: 2 * 60 * 60}, "", true},
{"20", time.Rule{Kind: time.RuleDOY, Day: 20, Time: 2 * 60 * 60}, "", true},
{"M1.2.3", time.Rule{Kind: time.RuleMonthWeekDay, Mon: 1, Week: 2, Day: 3, Time: 2 * 60 * 60}, "", true},
{"30/03:00:00", time.Rule{Kind: time.RuleDOY, Day: 30, Time: 3 * 60 * 60}, "", true},
{"M4.5.6/03:00:00", time.Rule{Kind: time.RuleMonthWeekDay, Mon: 4, Week: 5, Day: 6, Time: 3 * 60 * 60}, "", true},
{"M4.5.7/03:00:00", time.Rule{}, "", false},
} {
r, out, ok := time.TzsetRule(test.in)
if r != test.r || out != test.out || ok != test.ok {
t.Errorf("tzsetName(%q) = %#v, %q, %t, want %#v, %q, %t", test.in, r, out, ok, test.r, test.out, test.ok)
}
}
}

View File

@ -8,7 +8,7 @@ golang.org/x/crypto/curve25519
golang.org/x/crypto/hkdf
golang.org/x/crypto/internal/subtle
golang.org/x/crypto/poly1305
# golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933
# golang.org/x/net v0.0.0-20201029202311-70d85028bad3
## explicit
golang.org/x/net/dns/dnsmessage
golang.org/x/net/http/httpguts