diff options
Diffstat (limited to 'libgo/go')
226 files changed, 6486 insertions, 4471 deletions
diff --git a/libgo/go/archive/tar/common.go b/libgo/go/archive/tar/common.go index 6735508..fc7a409 100644 --- a/libgo/go/archive/tar/common.go +++ b/libgo/go/archive/tar/common.go @@ -11,41 +11,42 @@ // http://www.gnu.org/software/tar/manual/html_node/Standard.html package tar +import "time" + const ( blockSize = 512 // Types - TypeReg = '0' // regular file. - TypeRegA = '\x00' // regular file. - TypeLink = '1' // hard link. - TypeSymlink = '2' // symbolic link. - TypeChar = '3' // character device node. - TypeBlock = '4' // block device node. - TypeDir = '5' // directory. - TypeFifo = '6' // fifo node. - TypeCont = '7' // reserved. - TypeXHeader = 'x' // extended header. - TypeXGlobalHeader = 'g' // global extended header. + TypeReg = '0' // regular file + TypeRegA = '\x00' // regular file + TypeLink = '1' // hard link + TypeSymlink = '2' // symbolic link + TypeChar = '3' // character device node + TypeBlock = '4' // block device node + TypeDir = '5' // directory + TypeFifo = '6' // fifo node + TypeCont = '7' // reserved + TypeXHeader = 'x' // extended header + TypeXGlobalHeader = 'g' // global extended header ) // A Header represents a single header in a tar archive. // Some fields may not be populated. type Header struct { - Name string // name of header file entry. - Mode int64 // permission and mode bits. - Uid int // user id of owner. - Gid int // group id of owner. - Size int64 // length in bytes. - Mtime int64 // modified time; seconds since epoch. - Typeflag byte // type of header entry. - Linkname string // target name of link. - Uname string // user name of owner. - Gname string // group name of owner. - Devmajor int64 // major number of character or block device. - Devminor int64 // minor number of character or block device. - Atime int64 // access time; seconds since epoch. - Ctime int64 // status change time; seconds since epoch. - + Name string // name of header file entry + Mode int64 // permission and mode bits + Uid int // user id of owner + Gid int // group id of owner + Size int64 // length in bytes + ModTime time.Time // modified time + Typeflag byte // type of header entry + Linkname string // target name of link + Uname string // user name of owner + Gname string // group name of owner + Devmajor int64 // major number of character or block device + Devminor int64 // minor number of character or block device + AccessTime time.Time // access time + ChangeTime time.Time // status change time } var zeroBlock = make([]byte, blockSize) diff --git a/libgo/go/archive/tar/reader.go b/libgo/go/archive/tar/reader.go index facba2c..76955e2 100644 --- a/libgo/go/archive/tar/reader.go +++ b/libgo/go/archive/tar/reader.go @@ -14,6 +14,7 @@ import ( "io/ioutil" "os" "strconv" + "time" ) var ( @@ -141,7 +142,7 @@ func (tr *Reader) readHeader() *Header { hdr.Uid = int(tr.octal(s.next(8))) hdr.Gid = int(tr.octal(s.next(8))) hdr.Size = tr.octal(s.next(12)) - hdr.Mtime = tr.octal(s.next(12)) + hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0) s.next(8) // chksum hdr.Typeflag = s.next(1)[0] hdr.Linkname = cString(s.next(100)) @@ -178,8 +179,8 @@ func (tr *Reader) readHeader() *Header { prefix = cString(s.next(155)) case "star": prefix = cString(s.next(131)) - hdr.Atime = tr.octal(s.next(12)) - hdr.Ctime = tr.octal(s.next(12)) + hdr.AccessTime = time.Unix(tr.octal(s.next(12)), 0) + hdr.ChangeTime = time.Unix(tr.octal(s.next(12)), 0) } if len(prefix) > 0 { hdr.Name = prefix + "/" + hdr.Name diff --git a/libgo/go/archive/tar/reader_test.go b/libgo/go/archive/tar/reader_test.go index 00eea6b..5ca4212 100644 --- a/libgo/go/archive/tar/reader_test.go +++ b/libgo/go/archive/tar/reader_test.go @@ -12,6 +12,7 @@ import ( "os" "reflect" "testing" + "time" ) type untarTest struct { @@ -29,7 +30,7 @@ var gnuTarTest = &untarTest{ Uid: 73025, Gid: 5000, Size: 5, - Mtime: 1244428340, + ModTime: time.Unix(1244428340, 0), Typeflag: '0', Uname: "dsymonds", Gname: "eng", @@ -40,7 +41,7 @@ var gnuTarTest = &untarTest{ Uid: 73025, Gid: 5000, Size: 11, - Mtime: 1244436044, + ModTime: time.Unix(1244436044, 0), Typeflag: '0', Uname: "dsymonds", Gname: "eng", @@ -58,30 +59,30 @@ var untarTests = []*untarTest{ file: "testdata/star.tar", headers: []*Header{ &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244592783, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - Atime: 1244592783, - Ctime: 1244592783, + Name: "small.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 5, + ModTime: time.Unix(1244592783, 0), + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + AccessTime: time.Unix(1244592783, 0), + ChangeTime: time.Unix(1244592783, 0), }, &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244592783, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - Atime: 1244592783, - Ctime: 1244592783, + Name: "small2.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 11, + ModTime: time.Unix(1244592783, 0), + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + AccessTime: time.Unix(1244592783, 0), + ChangeTime: time.Unix(1244592783, 0), }, }, }, @@ -94,7 +95,7 @@ var untarTests = []*untarTest{ Uid: 73025, Gid: 5000, Size: 5, - Mtime: 1244593104, + ModTime: time.Unix(1244593104, 0), Typeflag: '\x00', }, &Header{ @@ -103,7 +104,7 @@ var untarTests = []*untarTest{ Uid: 73025, Gid: 5000, Size: 11, - Mtime: 1244593104, + ModTime: time.Unix(1244593104, 0), Typeflag: '\x00', }, }, @@ -221,7 +222,7 @@ func TestIncrementalRead(t *testing.T) { h.Write(rdbuf[0:nr]) } // verify checksum - have := fmt.Sprintf("%x", h.Sum()) + have := fmt.Sprintf("%x", h.Sum(nil)) want := cksums[nread] if want != have { t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want) diff --git a/libgo/go/archive/tar/writer.go b/libgo/go/archive/tar/writer.go index 222df90..b9310b3 100644 --- a/libgo/go/archive/tar/writer.go +++ b/libgo/go/archive/tar/writer.go @@ -127,19 +127,19 @@ func (tw *Writer) WriteHeader(hdr *Header) error { // TODO(dsymonds): handle names longer than 100 chars copy(s.next(100), []byte(hdr.Name)) - tw.octal(s.next(8), hdr.Mode) // 100:108 - tw.numeric(s.next(8), int64(hdr.Uid)) // 108:116 - tw.numeric(s.next(8), int64(hdr.Gid)) // 116:124 - tw.numeric(s.next(12), hdr.Size) // 124:136 - tw.numeric(s.next(12), hdr.Mtime) // 136:148 - s.next(8) // chksum (148:156) - s.next(1)[0] = hdr.Typeflag // 156:157 - tw.cString(s.next(100), hdr.Linkname) // linkname (157:257) - copy(s.next(8), []byte("ustar\x0000")) // 257:265 - tw.cString(s.next(32), hdr.Uname) // 265:297 - tw.cString(s.next(32), hdr.Gname) // 297:329 - tw.numeric(s.next(8), hdr.Devmajor) // 329:337 - tw.numeric(s.next(8), hdr.Devminor) // 337:345 + tw.octal(s.next(8), hdr.Mode) // 100:108 + tw.numeric(s.next(8), int64(hdr.Uid)) // 108:116 + tw.numeric(s.next(8), int64(hdr.Gid)) // 116:124 + tw.numeric(s.next(12), hdr.Size) // 124:136 + tw.numeric(s.next(12), hdr.ModTime.Unix()) // 136:148 + s.next(8) // chksum (148:156) + s.next(1)[0] = hdr.Typeflag // 156:157 + tw.cString(s.next(100), hdr.Linkname) // linkname (157:257) + copy(s.next(8), []byte("ustar\x0000")) // 257:265 + tw.cString(s.next(32), hdr.Uname) // 265:297 + tw.cString(s.next(32), hdr.Gname) // 297:329 + tw.numeric(s.next(8), hdr.Devmajor) // 329:337 + tw.numeric(s.next(8), hdr.Devminor) // 337:345 // Use the GNU magic instead of POSIX magic if we used any GNU extensions. if tw.usedBinary { diff --git a/libgo/go/archive/tar/writer_test.go b/libgo/go/archive/tar/writer_test.go index 6cc9386..8d7ed32 100644 --- a/libgo/go/archive/tar/writer_test.go +++ b/libgo/go/archive/tar/writer_test.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "testing" "testing/iotest" + "time" ) type writerTestEntry struct { @@ -38,7 +39,7 @@ var writerTests = []*writerTest{ Uid: 73025, Gid: 5000, Size: 5, - Mtime: 1246508266, + ModTime: time.Unix(1246508266, 0), Typeflag: '0', Uname: "dsymonds", Gname: "eng", @@ -52,7 +53,7 @@ var writerTests = []*writerTest{ Uid: 73025, Gid: 5000, Size: 11, - Mtime: 1245217492, + ModTime: time.Unix(1245217492, 0), Typeflag: '0', Uname: "dsymonds", Gname: "eng", @@ -66,7 +67,7 @@ var writerTests = []*writerTest{ Uid: 1000, Gid: 1000, Size: 0, - Mtime: 1314603082, + ModTime: time.Unix(1314603082, 0), Typeflag: '2', Linkname: "small.txt", Uname: "strings", @@ -89,7 +90,7 @@ var writerTests = []*writerTest{ Uid: 73025, Gid: 5000, Size: 16 << 30, - Mtime: 1254699560, + ModTime: time.Unix(1254699560, 0), Typeflag: '0', Uname: "dsymonds", Gname: "eng", diff --git a/libgo/go/archive/zip/reader.go b/libgo/go/archive/zip/reader.go index cfbe549..4365009 100644 --- a/libgo/go/archive/zip/reader.go +++ b/libgo/go/archive/zip/reader.go @@ -56,7 +56,7 @@ func OpenReader(name string) (*ReadCloser, error) { return nil, err } r := new(ReadCloser) - if err := r.init(f, fi.Size); err != nil { + if err := r.init(f, fi.Size()); err != nil { f.Close() return nil, err } diff --git a/libgo/go/archive/zip/reader_test.go b/libgo/go/archive/zip/reader_test.go index ca0b04e..8c0ecaa 100644 --- a/libgo/go/archive/zip/reader_test.go +++ b/libgo/go/archive/zip/reader_test.go @@ -164,8 +164,8 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) { t.Error(err) return } - if got, want := f.Mtime_ns()/1e9, mtime.Seconds(); got != want { - t.Errorf("%s: mtime=%s (%d); want %s (%d)", f.Name, time.SecondsToUTC(got), got, mtime, want) + if ft := f.ModTime(); !ft.Equal(mtime) { + t.Errorf("%s: mtime=%s, want %s", f.Name, ft, mtime) } testFileMode(t, f, ft.Mode) diff --git a/libgo/go/archive/zip/struct.go b/libgo/go/archive/zip/struct.go index b862b5a..43c04bb 100644 --- a/libgo/go/archive/zip/struct.go +++ b/libgo/go/archive/zip/struct.go @@ -11,8 +11,10 @@ This package does not support ZIP64 or disk spanning. */ package zip -import "errors" -import "time" +import ( + "errors" + "time" +) // Compression methods. const ( @@ -74,24 +76,26 @@ func recoverError(errp *error) { // The resolution is 2s. // See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx func msDosTimeToTime(dosDate, dosTime uint16) time.Time { - return time.Time{ + return time.Date( // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980 - Year: int64(dosDate>>9 + 1980), - Month: int(dosDate >> 5 & 0xf), - Day: int(dosDate & 0x1f), + int(dosDate>>9+1980), + time.Month(dosDate>>5&0xf), + int(dosDate&0x1f), // time bits 0-4: second/2; 5-10: minute; 11-15: hour - Hour: int(dosTime >> 11), - Minute: int(dosTime >> 5 & 0x3f), - Second: int(dosTime & 0x1f * 2), - } + int(dosTime>>11), + int(dosTime>>5&0x3f), + int(dosTime&0x1f*2), + 0, // nanoseconds + + time.UTC, + ) } -// Mtime_ns returns the modified time in ns since epoch. +// ModTime returns the modification time. // The resolution is 2s. -func (h *FileHeader) Mtime_ns() int64 { - t := msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) - return t.Seconds() * 1e9 +func (h *FileHeader) ModTime() time.Time { + return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) } // Mode returns the permission and mode bits for the FileHeader. diff --git a/libgo/go/bytes/bytes_test.go b/libgo/go/bytes/bytes_test.go index 21a1a4f..829ef05 100644 --- a/libgo/go/bytes/bytes_test.go +++ b/libgo/go/bytes/bytes_test.go @@ -702,7 +702,7 @@ func TestTrim(t *testing.T) { case "TrimRight": f = TrimRight default: - t.Error("Undefined trim function %s", name) + t.Errorf("Undefined trim function %s", name) } actual := string(f([]byte(tc.in), tc.cutset)) if actual != tc.out { diff --git a/libgo/go/compress/gzip/gunzip.go b/libgo/go/compress/gzip/gunzip.go index a23e515..7c78b9e 100644 --- a/libgo/go/compress/gzip/gunzip.go +++ b/libgo/go/compress/gzip/gunzip.go @@ -13,6 +13,7 @@ import ( "hash" "hash/crc32" "io" + "time" ) // BUG(nigeltao): Comments and Names don't properly map UTF-8 character codes outside of @@ -42,11 +43,11 @@ var ChecksumError = errors.New("gzip checksum error") // The gzip file stores a header giving metadata about the compressed file. // That header is exposed as the fields of the Compressor and Decompressor structs. type Header struct { - Comment string // comment - Extra []byte // "extra data" - Mtime uint32 // modification time (seconds since January 1, 1970) - Name string // file name - OS byte // operating system type + Comment string // comment + Extra []byte // "extra data" + ModTime time.Time // modification time + Name string // file name + OS byte // operating system type } // An Decompressor is an io.Reader that can be read to retrieve @@ -130,7 +131,7 @@ func (z *Decompressor) readHeader(save bool) error { } z.flg = z.buf[3] if save { - z.Mtime = get4(z.buf[4:8]) + z.ModTime = time.Unix(int64(get4(z.buf[4:8])), 0) // z.buf[8] is xfl, ignored z.OS = z.buf[9] } diff --git a/libgo/go/compress/gzip/gzip.go b/libgo/go/compress/gzip/gzip.go index 94b0f1f..07b91b6 100644 --- a/libgo/go/compress/gzip/gzip.go +++ b/libgo/go/compress/gzip/gzip.go @@ -122,7 +122,7 @@ func (z *Compressor) Write(p []byte) (int, error) { if z.Comment != "" { z.buf[3] |= 0x10 } - put4(z.buf[4:8], z.Mtime) + put4(z.buf[4:8], uint32(z.ModTime.Unix())) if z.level == BestCompression { z.buf[8] = 2 } else if z.level == BestSpeed { diff --git a/libgo/go/compress/gzip/gzip_test.go b/libgo/go/compress/gzip/gzip_test.go index 121e627..815825b 100644 --- a/libgo/go/compress/gzip/gzip_test.go +++ b/libgo/go/compress/gzip/gzip_test.go @@ -8,6 +8,7 @@ import ( "io" "io/ioutil" "testing" + "time" ) // pipe creates two ends of a pipe that gzip and gunzip, and runs dfunc at the @@ -53,7 +54,7 @@ func TestWriter(t *testing.T) { func(compressor *Compressor) { compressor.Comment = "comment" compressor.Extra = []byte("extra") - compressor.Mtime = 1e8 + compressor.ModTime = time.Unix(1e8, 0) compressor.Name = "name" _, err := compressor.Write([]byte("payload")) if err != nil { @@ -74,8 +75,8 @@ func TestWriter(t *testing.T) { if string(decompressor.Extra) != "extra" { t.Fatalf("extra is %q, want %q", decompressor.Extra, "extra") } - if decompressor.Mtime != 1e8 { - t.Fatalf("mtime is %d, want %d", decompressor.Mtime, uint32(1e8)) + if decompressor.ModTime.Unix() != 1e8 { + t.Fatalf("mtime is %d, want %d", decompressor.ModTime.Unix(), uint32(1e8)) } if decompressor.Name != "name" { t.Fatalf("name is %q, want %q", decompressor.Name, "name") diff --git a/libgo/go/crypto/bcrypt/bcrypt.go b/libgo/go/crypto/bcrypt/bcrypt.go index 9740135..362b2eb 100644 --- a/libgo/go/crypto/bcrypt/bcrypt.go +++ b/libgo/go/crypto/bcrypt/bcrypt.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. -// Package bcrypt implements Provos and Mazières's bcrypt adapative hashing +// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf package bcrypt diff --git a/libgo/go/crypto/ecdsa/ecdsa_test.go b/libgo/go/crypto/ecdsa/ecdsa_test.go index 22360b5..45433e1 100644 --- a/libgo/go/crypto/ecdsa/ecdsa_test.go +++ b/libgo/go/crypto/ecdsa/ecdsa_test.go @@ -214,7 +214,7 @@ func TestVectors(t *testing.T) { msg, _ := hex.DecodeString(test.msg) sha.Reset() sha.Write(msg) - hashed := sha.Sum() + hashed := sha.Sum(nil) r := fromHex(test.r) s := fromHex(test.s) if Verify(&pub, hashed, r, s) != test.ok { diff --git a/libgo/go/crypto/hmac/hmac.go b/libgo/go/crypto/hmac/hmac.go index 6a17bbd..deaceaf 100644 --- a/libgo/go/crypto/hmac/hmac.go +++ b/libgo/go/crypto/hmac/hmac.go @@ -48,15 +48,15 @@ func (h *hmac) tmpPad(xor byte) { } } -func (h *hmac) Sum() []byte { - sum := h.inner.Sum() +func (h *hmac) Sum(in []byte) []byte { + sum := h.inner.Sum(nil) h.tmpPad(0x5c) for i, b := range sum { h.tmp[padSize+i] = b } h.outer.Reset() h.outer.Write(h.tmp) - return h.outer.Sum() + return h.outer.Sum(in) } func (h *hmac) Write(p []byte) (n int, err error) { @@ -81,7 +81,7 @@ func New(h func() hash.Hash, key []byte) hash.Hash { if len(key) > padSize { // If key is too big, hash it. hm.outer.Write(key) - key = hm.outer.Sum() + key = hm.outer.Sum(nil) } hm.key = make([]byte, len(key)) copy(hm.key, key) diff --git a/libgo/go/crypto/hmac/hmac_test.go b/libgo/go/crypto/hmac/hmac_test.go index 03431c9..eac254b 100644 --- a/libgo/go/crypto/hmac/hmac_test.go +++ b/libgo/go/crypto/hmac/hmac_test.go @@ -192,7 +192,7 @@ func TestHMAC(t *testing.T) { // Repetitive Sum() calls should return the same value for k := 0; k < 2; k++ { - sum := fmt.Sprintf("%x", h.Sum()) + sum := fmt.Sprintf("%x", h.Sum(nil)) if sum != tt.out { t.Errorf("test %d.%d.%d: have %s want %s\n", i, j, k, sum, tt.out) } diff --git a/libgo/go/crypto/md4/md4.go b/libgo/go/crypto/md4/md4.go index f21cc51..e51e8be 100644 --- a/libgo/go/crypto/md4/md4.go +++ b/libgo/go/crypto/md4/md4.go @@ -77,7 +77,7 @@ func (d *digest) Write(p []byte) (nn int, err error) { return } -func (d0 *digest) Sum() []byte { +func (d0 *digest) Sum(in []byte) []byte { // Make a copy of d0, so that caller can keep writing and summing. d := new(digest) *d = *d0 @@ -103,14 +103,11 @@ func (d0 *digest) Sum() []byte { panic("d.nx != 0") } - p := make([]byte, 16) - j := 0 for _, s := range d.s { - p[j+0] = byte(s >> 0) - p[j+1] = byte(s >> 8) - p[j+2] = byte(s >> 16) - p[j+3] = byte(s >> 24) - j += 4 + in = append(in, byte(s>>0)) + in = append(in, byte(s>>8)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>24)) } - return p + return in } diff --git a/libgo/go/crypto/md4/md4_test.go b/libgo/go/crypto/md4/md4_test.go index 721bd4c..b56edd7 100644 --- a/libgo/go/crypto/md4/md4_test.go +++ b/libgo/go/crypto/md4/md4_test.go @@ -58,10 +58,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out) } diff --git a/libgo/go/crypto/md5/md5.go b/libgo/go/crypto/md5/md5.go index 20f3a1b..182cfb8 100644 --- a/libgo/go/crypto/md5/md5.go +++ b/libgo/go/crypto/md5/md5.go @@ -77,7 +77,7 @@ func (d *digest) Write(p []byte) (nn int, err error) { return } -func (d0 *digest) Sum() []byte { +func (d0 *digest) Sum(in []byte) []byte { // Make a copy of d0 so that caller can keep writing and summing. d := new(digest) *d = *d0 @@ -103,14 +103,11 @@ func (d0 *digest) Sum() []byte { panic("d.nx != 0") } - p := make([]byte, 16) - j := 0 for _, s := range d.s { - p[j+0] = byte(s >> 0) - p[j+1] = byte(s >> 8) - p[j+2] = byte(s >> 16) - p[j+3] = byte(s >> 24) - j += 4 + in = append(in, byte(s>>0)) + in = append(in, byte(s>>8)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>24)) } - return p + return in } diff --git a/libgo/go/crypto/md5/md5_test.go b/libgo/go/crypto/md5/md5_test.go index 857002b..b15e466 100644 --- a/libgo/go/crypto/md5/md5_test.go +++ b/libgo/go/crypto/md5/md5_test.go @@ -58,10 +58,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("md5[%d](%s) = %s want %s", j, g.in, s, g.out) } diff --git a/libgo/go/crypto/ocsp/ocsp.go b/libgo/go/crypto/ocsp/ocsp.go index a04b5bd..b9dfdf9 100644 --- a/libgo/go/crypto/ocsp/ocsp.go +++ b/libgo/go/crypto/ocsp/ocsp.go @@ -61,7 +61,7 @@ type responseData struct { Version int `asn1:"optional,default:1,explicit,tag:0"` RequestorName pkix.RDNSequence `asn1:"optional,explicit,tag:1"` KeyHash []byte `asn1:"optional,explicit,tag:2"` - ProducedAt *time.Time + ProducedAt time.Time Responses []singleResponse } @@ -70,12 +70,12 @@ type singleResponse struct { Good asn1.Flag `asn1:"explicit,tag:0,optional"` Revoked revokedInfo `asn1:"explicit,tag:1,optional"` Unknown asn1.Flag `asn1:"explicit,tag:2,optional"` - ThisUpdate *time.Time - NextUpdate *time.Time `asn1:"explicit,tag:0,optional"` + ThisUpdate time.Time + NextUpdate time.Time `asn1:"explicit,tag:0,optional"` } type revokedInfo struct { - RevocationTime *time.Time + RevocationTime time.Time Reason int `asn1:"explicit,tag:0,optional"` } @@ -97,7 +97,7 @@ type Response struct { // Status is one of {Good, Revoked, Unknown, ServerFailed} Status int SerialNumber []byte - ProducedAt, ThisUpdate, NextUpdate, RevokedAt *time.Time + ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time RevocationReason int Certificate *x509.Certificate } @@ -161,7 +161,7 @@ func ParseResponse(bytes []byte) (*Response, error) { pub := ret.Certificate.PublicKey.(*rsa.PublicKey) h.Write(basicResp.TBSResponseData.Raw) - digest := h.Sum() + digest := h.Sum(nil) signature := basicResp.Signature.RightAlign() if rsa.VerifyPKCS1v15(pub, hashType, digest, signature) != nil { diff --git a/libgo/go/crypto/ocsp/ocsp_test.go b/libgo/go/crypto/ocsp/ocsp_test.go index 7be3721..bacca55 100644 --- a/libgo/go/crypto/ocsp/ocsp_test.go +++ b/libgo/go/crypto/ocsp/ocsp_test.go @@ -15,7 +15,13 @@ func TestOCSPDecode(t *testing.T) { t.Error(err) } - expected := Response{Status: 0, SerialNumber: []byte{0x1, 0xd0, 0xfa}, RevocationReason: 0, ThisUpdate: &time.Time{Year: 2010, Month: 7, Day: 7, Hour: 15, Minute: 1, Second: 5, ZoneOffset: 0, Zone: "UTC"}, NextUpdate: &time.Time{Year: 2010, Month: 7, Day: 7, Hour: 18, Minute: 35, Second: 17, ZoneOffset: 0, Zone: "UTC"}} + expected := Response{ + Status: 0, + SerialNumber: []byte{0x1, 0xd0, 0xfa}, + RevocationReason: 0, + ThisUpdate: time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC), + NextUpdate: time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC), + } if !reflect.DeepEqual(resp.ThisUpdate, resp.ThisUpdate) { t.Errorf("resp.ThisUpdate: got %d, want %d", resp.ThisUpdate, expected.ThisUpdate) diff --git a/libgo/go/crypto/openpgp/canonical_text.go b/libgo/go/crypto/openpgp/canonical_text.go index fe4557a..98cee5e 100644 --- a/libgo/go/crypto/openpgp/canonical_text.go +++ b/libgo/go/crypto/openpgp/canonical_text.go @@ -41,8 +41,8 @@ func (cth *canonicalTextHash) Write(buf []byte) (int, error) { return len(buf), nil } -func (cth *canonicalTextHash) Sum() []byte { - return cth.h.Sum() +func (cth *canonicalTextHash) Sum(in []byte) []byte { + return cth.h.Sum(in) } func (cth *canonicalTextHash) Reset() { diff --git a/libgo/go/crypto/openpgp/canonical_text_test.go b/libgo/go/crypto/openpgp/canonical_text_test.go index ae54f8c8..841475f 100644 --- a/libgo/go/crypto/openpgp/canonical_text_test.go +++ b/libgo/go/crypto/openpgp/canonical_text_test.go @@ -17,8 +17,8 @@ func (r recordingHash) Write(b []byte) (n int, err error) { return r.buf.Write(b) } -func (r recordingHash) Sum() []byte { - return r.buf.Bytes() +func (r recordingHash) Sum(in []byte) []byte { + return append(in, r.buf.Bytes()...) } func (r recordingHash) Reset() { @@ -33,7 +33,7 @@ func testCanonicalText(t *testing.T, input, expected string) { r := recordingHash{bytes.NewBuffer(nil)} c := NewCanonicalTextHash(r) c.Write([]byte(input)) - result := c.Sum() + result := c.Sum(nil) if expected != string(result) { t.Errorf("input: %x got: %x want: %x", input, result, expected) } diff --git a/libgo/go/crypto/openpgp/keys.go b/libgo/go/crypto/openpgp/keys.go index b705d22..df39970 100644 --- a/libgo/go/crypto/openpgp/keys.go +++ b/libgo/go/crypto/openpgp/keys.go @@ -381,7 +381,7 @@ const defaultRSAKeyBits = 2048 // NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a // single identity composed of the given full name, comment and email, any of // which may be empty but must not contain any of "()<>\x00". -func NewEntity(rand io.Reader, currentTimeSecs int64, name, comment, email string) (*Entity, error) { +func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email string) (*Entity, error) { uid := packet.NewUserId(name, comment, email) if uid == nil { return nil, error_.InvalidArgumentError("user id field contained invalid characters") @@ -395,11 +395,9 @@ func NewEntity(rand io.Reader, currentTimeSecs int64, name, comment, email strin return nil, err } - t := uint32(currentTimeSecs) - e := &Entity{ - PrimaryKey: packet.NewRSAPublicKey(t, &signingPriv.PublicKey, false /* not a subkey */ ), - PrivateKey: packet.NewRSAPrivateKey(t, signingPriv, false /* not a subkey */ ), + PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey, false /* not a subkey */ ), + PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv, false /* not a subkey */ ), Identities: make(map[string]*Identity), } isPrimaryId := true @@ -407,7 +405,7 @@ func NewEntity(rand io.Reader, currentTimeSecs int64, name, comment, email strin Name: uid.Name, UserId: uid, SelfSignature: &packet.Signature{ - CreationTime: t, + CreationTime: currentTime, SigType: packet.SigTypePositiveCert, PubKeyAlgo: packet.PubKeyAlgoRSA, Hash: crypto.SHA256, @@ -421,10 +419,10 @@ func NewEntity(rand io.Reader, currentTimeSecs int64, name, comment, email strin e.Subkeys = make([]Subkey, 1) e.Subkeys[0] = Subkey{ - PublicKey: packet.NewRSAPublicKey(t, &encryptingPriv.PublicKey, true /* is a subkey */ ), - PrivateKey: packet.NewRSAPrivateKey(t, encryptingPriv, true /* is a subkey */ ), + PublicKey: packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey, true /* is a subkey */ ), + PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv, true /* is a subkey */ ), Sig: &packet.Signature{ - CreationTime: t, + CreationTime: currentTime, SigType: packet.SigTypeSubkeyBinding, PubKeyAlgo: packet.PubKeyAlgoRSA, Hash: crypto.SHA256, @@ -533,7 +531,7 @@ func (e *Entity) SignIdentity(identity string, signer *Entity) error { SigType: packet.SigTypeGenericCert, PubKeyAlgo: signer.PrivateKey.PubKeyAlgo, Hash: crypto.SHA256, - CreationTime: uint32(time.Seconds()), + CreationTime: time.Now(), IssuerKeyId: &signer.PrivateKey.KeyId, } if err := sig.SignKey(e.PrimaryKey, signer.PrivateKey); err != nil { diff --git a/libgo/go/crypto/openpgp/packet/private_key.go b/libgo/go/crypto/openpgp/packet/private_key.go index c0ff82b4..d67e968 100644 --- a/libgo/go/crypto/openpgp/packet/private_key.go +++ b/libgo/go/crypto/openpgp/packet/private_key.go @@ -17,6 +17,7 @@ import ( "io/ioutil" "math/big" "strconv" + "time" ) // PrivateKey represents a possibly encrypted private key. See RFC 4880, @@ -32,9 +33,9 @@ type PrivateKey struct { iv []byte } -func NewRSAPrivateKey(currentTimeSecs uint32, priv *rsa.PrivateKey, isSubkey bool) *PrivateKey { +func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey, isSubkey bool) *PrivateKey { pk := new(PrivateKey) - pk.PublicKey = *NewRSAPublicKey(currentTimeSecs, &priv.PublicKey, isSubkey) + pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey, isSubkey) pk.PrivateKey = priv return pk } @@ -99,13 +100,9 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { } func mod64kHash(d []byte) uint16 { - h := uint16(0) - for i := 0; i < len(d); i += 2 { - v := uint16(d[i]) << 8 - if i+1 < len(d) { - v += uint16(d[i+1]) - } - h += v + var h uint16 + for _, b := range d { + h += uint16(b) } return h } @@ -195,7 +192,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error { } h := sha1.New() h.Write(data[:len(data)-sha1.Size]) - sum := h.Sum() + sum := h.Sum(nil) if !bytes.Equal(sum, data[len(data)-sha1.Size:]) { return error_.StructuralError("private key checksum failure") } diff --git a/libgo/go/crypto/openpgp/packet/private_key_test.go b/libgo/go/crypto/openpgp/packet/private_key_test.go index 60eebaa..35d8951 100644 --- a/libgo/go/crypto/openpgp/packet/private_key_test.go +++ b/libgo/go/crypto/openpgp/packet/private_key_test.go @@ -6,19 +6,20 @@ package packet import ( "testing" + "time" ) var privateKeyTests = []struct { privateKeyHex string - creationTime uint32 + creationTime time.Time }{ { privKeyRSAHex, - 0x4cc349a8, + time.Unix(0x4cc349a8, 0), }, { privKeyElGamalHex, - 0x4df9ee1a, + time.Unix(0x4df9ee1a, 0), }, } @@ -43,7 +44,7 @@ func TestPrivateKeyRead(t *testing.T) { continue } - if privKey.CreationTime != test.creationTime || privKey.Encrypted { + if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted { t.Errorf("#%d: bad result, got: %#v", i, privKey) } } diff --git a/libgo/go/crypto/openpgp/packet/public_key.go b/libgo/go/crypto/openpgp/packet/public_key.go index 7d71dc4..9aa30e0 100644 --- a/libgo/go/crypto/openpgp/packet/public_key.go +++ b/libgo/go/crypto/openpgp/packet/public_key.go @@ -16,11 +16,12 @@ import ( "io" "math/big" "strconv" + "time" ) // PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2. type PublicKey struct { - CreationTime uint32 // seconds since the epoch + CreationTime time.Time PubKeyAlgo PublicKeyAlgorithm PublicKey interface{} // Either a *rsa.PublicKey or *dsa.PublicKey Fingerprint [20]byte @@ -38,9 +39,9 @@ func fromBig(n *big.Int) parsedMPI { } // NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey. -func NewRSAPublicKey(creationTimeSecs uint32, pub *rsa.PublicKey, isSubkey bool) *PublicKey { +func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey, isSubkey bool) *PublicKey { pk := &PublicKey{ - CreationTime: creationTimeSecs, + CreationTime: creationTime, PubKeyAlgo: PubKeyAlgoRSA, PublicKey: pub, IsSubkey: isSubkey, @@ -62,7 +63,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { if buf[0] != 4 { return error_.UnsupportedError("public key version") } - pk.CreationTime = uint32(buf[1])<<24 | uint32(buf[2])<<16 | uint32(buf[3])<<8 | uint32(buf[4]) + pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0) pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5]) switch pk.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: @@ -87,7 +88,7 @@ func (pk *PublicKey) setFingerPrintAndKeyId() { fingerPrint := sha1.New() pk.SerializeSignaturePrefix(fingerPrint) pk.serializeWithoutHeaders(fingerPrint) - copy(pk.Fingerprint[:], fingerPrint.Sum()) + copy(pk.Fingerprint[:], fingerPrint.Sum(nil)) pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20]) } @@ -234,10 +235,11 @@ func (pk *PublicKey) Serialize(w io.Writer) (err error) { func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) { var buf [6]byte buf[0] = 4 - buf[1] = byte(pk.CreationTime >> 24) - buf[2] = byte(pk.CreationTime >> 16) - buf[3] = byte(pk.CreationTime >> 8) - buf[4] = byte(pk.CreationTime) + t := uint32(pk.CreationTime.Unix()) + buf[1] = byte(t >> 24) + buf[2] = byte(t >> 16) + buf[3] = byte(t >> 8) + buf[4] = byte(t) buf[5] = byte(pk.PubKeyAlgo) _, err = w.Write(buf[:]) @@ -269,7 +271,7 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro } signed.Write(sig.HashSuffix) - hashBytes := signed.Sum() + hashBytes := signed.Sum(nil) if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { return error_.SignatureError("hash tag doesn't match") diff --git a/libgo/go/crypto/openpgp/packet/public_key_test.go b/libgo/go/crypto/openpgp/packet/public_key_test.go index 6e8bfbc..72f459f 100644 --- a/libgo/go/crypto/openpgp/packet/public_key_test.go +++ b/libgo/go/crypto/openpgp/packet/public_key_test.go @@ -8,19 +8,20 @@ import ( "bytes" "encoding/hex" "testing" + "time" ) var pubKeyTests = []struct { hexData string hexFingerprint string - creationTime uint32 + creationTime time.Time pubKeyAlgo PublicKeyAlgorithm keyId uint64 keyIdString string keyIdShort string }{ - {rsaPkDataHex, rsaFingerprintHex, 0x4d3c5c10, PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"}, - {dsaPkDataHex, dsaFingerprintHex, 0x4d432f89, PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"}, + {rsaPkDataHex, rsaFingerprintHex, time.Unix(0x4d3c5c10, 0), PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"}, + {dsaPkDataHex, dsaFingerprintHex, time.Unix(0x4d432f89, 0), PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"}, } func TestPublicKeyRead(t *testing.T) { @@ -38,8 +39,8 @@ func TestPublicKeyRead(t *testing.T) { if pk.PubKeyAlgo != test.pubKeyAlgo { t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo) } - if pk.CreationTime != test.creationTime { - t.Errorf("#%d: bad creation time got:%x want:%x", i, pk.CreationTime, test.creationTime) + if !pk.CreationTime.Equal(test.creationTime) { + t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime) } expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint) if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) { diff --git a/libgo/go/crypto/openpgp/packet/signature.go b/libgo/go/crypto/openpgp/packet/signature.go index 4ebb906..1cdc1ee 100644 --- a/libgo/go/crypto/openpgp/packet/signature.go +++ b/libgo/go/crypto/openpgp/packet/signature.go @@ -15,6 +15,7 @@ import ( "hash" "io" "strconv" + "time" ) // Signature represents a signature. See RFC 4880, section 5.2. @@ -28,7 +29,7 @@ type Signature struct { // HashTag contains the first two bytes of the hash for fast rejection // of bad signed data. HashTag [2]byte - CreationTime uint32 // Unix epoch time + CreationTime time.Time RSASignature parsedMPI DSASigR, DSASigS parsedMPI @@ -151,7 +152,7 @@ func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) } } - if sig.CreationTime == 0 { + if sig.CreationTime.IsZero() { err = error_.StructuralError("no creation time in signature") } @@ -223,7 +224,12 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r err = error_.StructuralError("signature creation time not four bytes") return } - sig.CreationTime = binary.BigEndian.Uint32(subpacket) + t := binary.BigEndian.Uint32(subpacket) + if t == 0 { + sig.CreationTime = time.Time{} + } else { + sig.CreationTime = time.Unix(int64(t), 0) + } case signatureExpirationSubpacket: // Signature expiration time, section 5.2.3.10 if !isHashed { @@ -417,7 +423,7 @@ func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { } h.Write(sig.HashSuffix) - digest = h.Sum() + digest = h.Sum(nil) copy(sig.HashTag[:], digest) return } @@ -541,10 +547,7 @@ type outputSubpacket struct { func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) { creationTime := make([]byte, 4) - creationTime[0] = byte(sig.CreationTime >> 24) - creationTime[1] = byte(sig.CreationTime >> 16) - creationTime[2] = byte(sig.CreationTime >> 8) - creationTime[3] = byte(sig.CreationTime) + binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix())) subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime}) if sig.IssuerKeyId != nil { diff --git a/libgo/go/crypto/openpgp/packet/symmetrically_encrypted.go b/libgo/go/crypto/openpgp/packet/symmetrically_encrypted.go index 8225db6..dff776e 100644 --- a/libgo/go/crypto/openpgp/packet/symmetrically_encrypted.go +++ b/libgo/go/crypto/openpgp/packet/symmetrically_encrypted.go @@ -201,7 +201,7 @@ func (ser *seMDCReader) Close() error { } ser.h.Write(ser.trailer[:2]) - final := ser.h.Sum() + final := ser.h.Sum(nil) if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 { return error_.SignatureError("hash mismatch") } @@ -227,7 +227,7 @@ func (w *seMDCWriter) Close() (err error) { buf[0] = mdcPacketTagByte buf[1] = sha1.Size w.h.Write(buf[:2]) - digest := w.h.Sum() + digest := w.h.Sum(nil) copy(buf[2:], digest) _, err = w.w.Write(buf[:]) diff --git a/libgo/go/crypto/openpgp/s2k/s2k.go b/libgo/go/crypto/openpgp/s2k/s2k.go index 2a753db..83673e1 100644 --- a/libgo/go/crypto/openpgp/s2k/s2k.go +++ b/libgo/go/crypto/openpgp/s2k/s2k.go @@ -34,7 +34,7 @@ func Salted(out []byte, h hash.Hash, in []byte, salt []byte) { } h.Write(salt) h.Write(in) - n := copy(out[done:], h.Sum()) + n := copy(out[done:], h.Sum(nil)) done += n } } @@ -68,7 +68,7 @@ func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) { written += len(combined) } } - n := copy(out[done:], h.Sum()) + n := copy(out[done:], h.Sum(nil)) done += n } } diff --git a/libgo/go/crypto/openpgp/write.go b/libgo/go/crypto/openpgp/write.go index 6f3450c..60dae01 100644 --- a/libgo/go/crypto/openpgp/write.go +++ b/libgo/go/crypto/openpgp/write.go @@ -68,7 +68,7 @@ func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.S sig.SigType = sigType sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo sig.Hash = crypto.SHA256 - sig.CreationTime = uint32(time.Seconds()) + sig.CreationTime = time.Now() sig.IssuerKeyId = &signer.PrivateKey.KeyId h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) @@ -95,8 +95,8 @@ type FileHints struct { // file should not be written to disk. It may be equal to "_CONSOLE" to // suggest the data should not be written to disk. FileName string - // EpochSeconds contains the modification time of the file, or 0 if not applicable. - EpochSeconds uint32 + // ModTime contains the modification time of the file, or the zero time if not applicable. + ModTime time.Time } // SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. @@ -115,7 +115,11 @@ func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHi if err != nil { return } - return packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, hints.EpochSeconds) + var epochSeconds uint32 + if !hints.ModTime.IsZero() { + epochSeconds = uint32(hints.ModTime.Unix()) + } + return packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) } // intersectPreferences mutates and returns a prefix of a that contains only @@ -243,7 +247,11 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint w = noOpCloser{encryptedData} } - literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, hints.EpochSeconds) + var epochSeconds uint32 + if !hints.ModTime.IsZero() { + epochSeconds = uint32(hints.ModTime.Unix()) + } + literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) if err != nil { return nil, err } @@ -275,7 +283,7 @@ func (s signatureWriter) Close() error { SigType: packet.SigTypeBinary, PubKeyAlgo: s.signer.PubKeyAlgo, Hash: s.hashType, - CreationTime: uint32(time.Seconds()), + CreationTime: time.Now(), IssuerKeyId: &s.signer.KeyId, } diff --git a/libgo/go/crypto/openpgp/write_test.go b/libgo/go/crypto/openpgp/write_test.go index 3cadf4c..02fa5b7 100644 --- a/libgo/go/crypto/openpgp/write_test.go +++ b/libgo/go/crypto/openpgp/write_test.go @@ -54,7 +54,7 @@ func TestNewEntity(t *testing.T) { return } - e, err := NewEntity(rand.Reader, time.Seconds(), "Test User", "test", "test@example.com") + e, err := NewEntity(rand.Reader, time.Now(), "Test User", "test", "test@example.com") if err != nil { t.Errorf("failed to create entity: %s", err) return diff --git a/libgo/go/crypto/rand/rand_unix.go b/libgo/go/crypto/rand/rand_unix.go index 09442ad..d9cddf6 100644 --- a/libgo/go/crypto/rand/rand_unix.go +++ b/libgo/go/crypto/rand/rand_unix.go @@ -100,7 +100,7 @@ func (r *reader) Read(b []byte) (n int, err error) { // t = encrypt(time) // dst = encrypt(t^seed) // seed = encrypt(t^dst) - ns := time.Nanoseconds() + ns := time.Now().UnixNano() r.time[0] = byte(ns >> 56) r.time[1] = byte(ns >> 48) r.time[2] = byte(ns >> 40) diff --git a/libgo/go/crypto/ripemd160/ripemd160.go b/libgo/go/crypto/ripemd160/ripemd160.go index 6ccfe87..c128ee4 100644 --- a/libgo/go/crypto/ripemd160/ripemd160.go +++ b/libgo/go/crypto/ripemd160/ripemd160.go @@ -81,7 +81,7 @@ func (d *digest) Write(p []byte) (nn int, err error) { return } -func (d0 *digest) Sum() []byte { +func (d0 *digest) Sum(in []byte) []byte { // Make a copy of d0 so that caller can keep writing and summing. d := new(digest) *d = *d0 @@ -107,11 +107,11 @@ func (d0 *digest) Sum() []byte { panic("d.nx != 0") } - p := make([]byte, 20) - j := 0 for _, s := range d.s { - p[j], p[j+1], p[j+2], p[j+3] = byte(s), byte(s>>8), byte(s>>16), byte(s>>24) - j += 4 + in = append(in, byte(s)) + in = append(in, byte(s>>8)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>24)) } - return p + return in } diff --git a/libgo/go/crypto/ripemd160/ripemd160_test.go b/libgo/go/crypto/ripemd160/ripemd160_test.go index f4135f5..5df1b25 100644 --- a/libgo/go/crypto/ripemd160/ripemd160_test.go +++ b/libgo/go/crypto/ripemd160/ripemd160_test.go @@ -38,10 +38,10 @@ func TestVectors(t *testing.T) { io.WriteString(md, tv.in) } else { io.WriteString(md, tv.in[0:len(tv.in)/2]) - md.Sum() + md.Sum(nil) io.WriteString(md, tv.in[len(tv.in)/2:]) } - s := fmt.Sprintf("%x", md.Sum()) + s := fmt.Sprintf("%x", md.Sum(nil)) if s != tv.out { t.Fatalf("RIPEMD-160[%d](%s) = %s, expected %s", j, tv.in, s, tv.out) } @@ -56,7 +56,7 @@ func TestMillionA(t *testing.T) { io.WriteString(md, "aaaaaaaaaa") } out := "52783243c1697bdbe16d37f97f68f08325dc1528" - s := fmt.Sprintf("%x", md.Sum()) + s := fmt.Sprintf("%x", md.Sum(nil)) if s != out { t.Fatalf("RIPEMD-160 (1 million 'a') = %s, expected %s", s, out) } diff --git a/libgo/go/crypto/rsa/pkcs1v15_test.go b/libgo/go/crypto/rsa/pkcs1v15_test.go index 66188ac..58d5fda 100644 --- a/libgo/go/crypto/rsa/pkcs1v15_test.go +++ b/libgo/go/crypto/rsa/pkcs1v15_test.go @@ -168,7 +168,7 @@ func TestSignPKCS1v15(t *testing.T) { for i, test := range signPKCS1v15Tests { h := sha1.New() h.Write([]byte(test.in)) - digest := h.Sum() + digest := h.Sum(nil) s, err := SignPKCS1v15(nil, rsaPrivateKey, crypto.SHA1, digest) if err != nil { @@ -186,7 +186,7 @@ func TestVerifyPKCS1v15(t *testing.T) { for i, test := range signPKCS1v15Tests { h := sha1.New() h.Write([]byte(test.in)) - digest := h.Sum() + digest := h.Sum(nil) sig, _ := hex.DecodeString(test.out) diff --git a/libgo/go/crypto/rsa/rsa.go b/libgo/go/crypto/rsa/rsa.go index 27ccf61..f74525c 100644 --- a/libgo/go/crypto/rsa/rsa.go +++ b/libgo/go/crypto/rsa/rsa.go @@ -194,7 +194,7 @@ func mgf1XOR(out []byte, hash hash.Hash, seed []byte) { for done < len(out) { hash.Write(seed) hash.Write(counter[0:4]) - digest := hash.Sum() + digest := hash.Sum(nil) hash.Reset() for i := 0; i < len(digest) && done < len(out); i++ { @@ -231,7 +231,7 @@ func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, l } hash.Write(label) - lHash := hash.Sum() + lHash := hash.Sum(nil) hash.Reset() em := make([]byte, k) @@ -428,7 +428,7 @@ func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext } hash.Write(label) - lHash := hash.Sum() + lHash := hash.Sum(nil) hash.Reset() // Converting the plaintext number to bytes will strip any diff --git a/libgo/go/crypto/sha1/sha1.go b/libgo/go/crypto/sha1/sha1.go index 4cdf5b2..f41cdb5 100644 --- a/libgo/go/crypto/sha1/sha1.go +++ b/libgo/go/crypto/sha1/sha1.go @@ -79,7 +79,7 @@ func (d *digest) Write(p []byte) (nn int, err error) { return } -func (d0 *digest) Sum() []byte { +func (d0 *digest) Sum(in []byte) []byte { // Make a copy of d0 so that caller can keep writing and summing. d := new(digest) *d = *d0 @@ -105,14 +105,11 @@ func (d0 *digest) Sum() []byte { panic("d.nx != 0") } - p := make([]byte, 20) - j := 0 for _, s := range d.h { - p[j+0] = byte(s >> 24) - p[j+1] = byte(s >> 16) - p[j+2] = byte(s >> 8) - p[j+3] = byte(s >> 0) - j += 4 + in = append(in, byte(s>>24)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>8)) + in = append(in, byte(s)) } - return p + return in } diff --git a/libgo/go/crypto/sha1/sha1_test.go b/libgo/go/crypto/sha1/sha1_test.go index 2712fe35..c23df6c 100644 --- a/libgo/go/crypto/sha1/sha1_test.go +++ b/libgo/go/crypto/sha1/sha1_test.go @@ -60,10 +60,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("sha1[%d](%s) = %s want %s", j, g.in, s, g.out) } diff --git a/libgo/go/crypto/sha256/sha256.go b/libgo/go/crypto/sha256/sha256.go index 14b8cfc..34861f6 100644 --- a/libgo/go/crypto/sha256/sha256.go +++ b/libgo/go/crypto/sha256/sha256.go @@ -123,7 +123,7 @@ func (d *digest) Write(p []byte) (nn int, err error) { return } -func (d0 *digest) Sum() []byte { +func (d0 *digest) Sum(in []byte) []byte { // Make a copy of d0 so that caller can keep writing and summing. d := new(digest) *d = *d0 @@ -149,17 +149,15 @@ func (d0 *digest) Sum() []byte { panic("d.nx != 0") } - p := make([]byte, 32) - j := 0 - for _, s := range d.h { - p[j+0] = byte(s >> 24) - p[j+1] = byte(s >> 16) - p[j+2] = byte(s >> 8) - p[j+3] = byte(s >> 0) - j += 4 - } + h := d.h[:] if d.is224 { - return p[0:28] + h = d.h[:7] + } + for _, s := range h { + in = append(in, byte(s>>24)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>8)) + in = append(in, byte(s)) } - return p + return in } diff --git a/libgo/go/crypto/sha256/sha256_test.go b/libgo/go/crypto/sha256/sha256_test.go index 42a3fa7..a6efb37 100644 --- a/libgo/go/crypto/sha256/sha256_test.go +++ b/libgo/go/crypto/sha256/sha256_test.go @@ -94,10 +94,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("sha256[%d](%s) = %s want %s", j, g.in, s, g.out) } @@ -112,10 +112,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("sha224[%d](%s) = %s want %s", j, g.in, s, g.out) } diff --git a/libgo/go/crypto/sha512/sha512.go b/libgo/go/crypto/sha512/sha512.go index 1bd2798..3cf65cb 100644 --- a/libgo/go/crypto/sha512/sha512.go +++ b/libgo/go/crypto/sha512/sha512.go @@ -123,7 +123,7 @@ func (d *digest) Write(p []byte) (nn int, err error) { return } -func (d0 *digest) Sum() []byte { +func (d0 *digest) Sum(in []byte) []byte { // Make a copy of d0 so that caller can keep writing and summing. d := new(digest) *d = *d0 @@ -149,21 +149,19 @@ func (d0 *digest) Sum() []byte { panic("d.nx != 0") } - p := make([]byte, 64) - j := 0 - for _, s := range d.h { - p[j+0] = byte(s >> 56) - p[j+1] = byte(s >> 48) - p[j+2] = byte(s >> 40) - p[j+3] = byte(s >> 32) - p[j+4] = byte(s >> 24) - p[j+5] = byte(s >> 16) - p[j+6] = byte(s >> 8) - p[j+7] = byte(s >> 0) - j += 8 - } + h := d.h[:] if d.is384 { - return p[0:48] + h = d.h[:6] + } + for _, s := range h { + in = append(in, byte(s>>56)) + in = append(in, byte(s>>48)) + in = append(in, byte(s>>40)) + in = append(in, byte(s>>32)) + in = append(in, byte(s>>24)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>8)) + in = append(in, byte(s)) } - return p + return in } diff --git a/libgo/go/crypto/sha512/sha512_test.go b/libgo/go/crypto/sha512/sha512_test.go index dd116dc..a70f7c5 100644 --- a/libgo/go/crypto/sha512/sha512_test.go +++ b/libgo/go/crypto/sha512/sha512_test.go @@ -94,10 +94,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("sha512[%d](%s) = %s want %s", j, g.in, s, g.out) } @@ -112,10 +112,10 @@ func TestGolden(t *testing.T) { io.WriteString(c, g.in) } else { io.WriteString(c, g.in[0:len(g.in)/2]) - c.Sum() + c.Sum(nil) io.WriteString(c, g.in[len(g.in)/2:]) } - s := fmt.Sprintf("%x", c.Sum()) + s := fmt.Sprintf("%x", c.Sum(nil)) if s != g.out { t.Fatalf("sha384[%d](%s) = %s want %s", j, g.in, s, g.out) } diff --git a/libgo/go/crypto/tls/cipher_suites.go b/libgo/go/crypto/tls/cipher_suites.go index 1134f36..c0e8656 100644 --- a/libgo/go/crypto/tls/cipher_suites.go +++ b/libgo/go/crypto/tls/cipher_suites.go @@ -37,6 +37,7 @@ type keyAgreement interface { // A cipherSuite is a specific combination of key agreement, cipher and MAC // function. All cipher suites currently assume RSA key agreement. type cipherSuite struct { + id uint16 // the lengths, in bytes, of the key material needed for each component. keyLen int macLen int @@ -50,13 +51,13 @@ type cipherSuite struct { mac func(version uint16, macKey []byte) macFunction } -var cipherSuites = map[uint16]*cipherSuite{ - TLS_RSA_WITH_RC4_128_SHA: &cipherSuite{16, 20, 0, rsaKA, false, cipherRC4, macSHA1}, - TLS_RSA_WITH_3DES_EDE_CBC_SHA: &cipherSuite{24, 20, 8, rsaKA, false, cipher3DES, macSHA1}, - TLS_RSA_WITH_AES_128_CBC_SHA: &cipherSuite{16, 20, 16, rsaKA, false, cipherAES, macSHA1}, - TLS_ECDHE_RSA_WITH_RC4_128_SHA: &cipherSuite{16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1}, - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: &cipherSuite{24, 20, 8, ecdheRSAKA, true, cipher3DES, macSHA1}, - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: &cipherSuite{16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1}, +var cipherSuites = []*cipherSuite{ + &cipherSuite{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, false, cipherRC4, macSHA1}, + &cipherSuite{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, false, cipher3DES, macSHA1}, + &cipherSuite{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, false, cipherAES, macSHA1}, + &cipherSuite{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1}, + &cipherSuite{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, true, cipher3DES, macSHA1}, + &cipherSuite{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1}, } func cipherRC4(key, iv []byte, isRead bool) interface{} { @@ -126,13 +127,13 @@ func (s ssl30MAC) MAC(seq, record []byte) []byte { s.h.Write(record[:1]) s.h.Write(record[3:5]) s.h.Write(record[recordHeaderLen:]) - digest := s.h.Sum() + digest := s.h.Sum(nil) s.h.Reset() s.h.Write(s.key) s.h.Write(ssl30Pad2[:padLength]) s.h.Write(digest) - return s.h.Sum() + return s.h.Sum(nil) } // tls10MAC implements the TLS 1.0 MAC function. RFC 2246, section 6.2.3. @@ -148,7 +149,7 @@ func (s tls10MAC) MAC(seq, record []byte) []byte { s.h.Reset() s.h.Write(seq) s.h.Write(record) - return s.h.Sum() + return s.h.Sum(nil) } func rsaKA() keyAgreement { @@ -159,15 +160,20 @@ func ecdheRSAKA() keyAgreement { return new(ecdheRSAKeyAgreement) } -// mutualCipherSuite returns a cipherSuite and its id given a list of supported +// mutualCipherSuite returns a cipherSuite given a list of supported // ciphersuites and the id requested by the peer. -func mutualCipherSuite(have []uint16, want uint16) (suite *cipherSuite, id uint16) { +func mutualCipherSuite(have []uint16, want uint16) *cipherSuite { for _, id := range have { if id == want { - return cipherSuites[id], id + for _, suite := range cipherSuites { + if suite.id == want { + return suite + } + } + return nil } } - return + return nil } // A list of the possible cipher suite ids. Taken from diff --git a/libgo/go/crypto/tls/common.go b/libgo/go/crypto/tls/common.go index ea52085..f57d932 100644 --- a/libgo/go/crypto/tls/common.go +++ b/libgo/go/crypto/tls/common.go @@ -121,7 +121,7 @@ type Config struct { // Time returns the current time as the number of seconds since the epoch. // If Time is nil, TLS uses the system time.Seconds. - Time func() int64 + Time func() time.Time // Certificates contains one or more certificate chains // to present to the other side of the connection. @@ -175,10 +175,10 @@ func (c *Config) rand() io.Reader { return r } -func (c *Config) time() int64 { +func (c *Config) time() time.Time { t := c.Time if t == nil { - t = time.Seconds + t = time.Now } return t() } @@ -315,9 +315,7 @@ var ( func initDefaultCipherSuites() { varDefaultCipherSuites = make([]uint16, len(cipherSuites)) - i := 0 - for id := range cipherSuites { - varDefaultCipherSuites[i] = id - i++ + for i, suite := range cipherSuites { + varDefaultCipherSuites[i] = suite.id } } diff --git a/libgo/go/crypto/tls/handshake_client.go b/libgo/go/crypto/tls/handshake_client.go index aed991cc..b4337f2 100644 --- a/libgo/go/crypto/tls/handshake_client.go +++ b/libgo/go/crypto/tls/handshake_client.go @@ -32,7 +32,7 @@ func (c *Conn) clientHandshake() error { nextProtoNeg: len(c.config.NextProtos) > 0, } - t := uint32(c.config.time()) + t := uint32(c.config.time().Unix()) hello.random[0] = byte(t >> 24) hello.random[1] = byte(t >> 16) hello.random[2] = byte(t >> 8) @@ -72,7 +72,7 @@ func (c *Conn) clientHandshake() error { return errors.New("server advertised unrequested NPN") } - suite, suiteId := mutualCipherSuite(c.config.cipherSuites(), serverHello.cipherSuite) + suite := mutualCipherSuite(c.config.cipherSuites(), serverHello.cipherSuite) if suite == nil { return c.sendAlert(alertHandshakeFailure) } @@ -232,8 +232,8 @@ func (c *Conn) clientHandshake() error { if cert != nil { certVerify := new(certificateVerifyMsg) var digest [36]byte - copy(digest[0:16], finishedHash.serverMD5.Sum()) - copy(digest[16:36], finishedHash.serverSHA1.Sum()) + copy(digest[0:16], finishedHash.serverMD5.Sum(nil)) + copy(digest[16:36], finishedHash.serverSHA1.Sum(nil)) signed, err := rsa.SignPKCS1v15(c.config.rand(), c.config.Certificates[0].PrivateKey, crypto.MD5SHA1, digest[0:]) if err != nil { return c.sendAlert(alertInternalError) @@ -292,7 +292,7 @@ func (c *Conn) clientHandshake() error { } c.handshakeComplete = true - c.cipherSuite = suiteId + c.cipherSuite = suite.id return nil } diff --git a/libgo/go/crypto/tls/handshake_server.go b/libgo/go/crypto/tls/handshake_server.go index d5af084..bbb23c0 100644 --- a/libgo/go/crypto/tls/handshake_server.go +++ b/libgo/go/crypto/tls/handshake_server.go @@ -56,18 +56,25 @@ Curves: ellipticOk := supportedCurve && supportedPointFormat var suite *cipherSuite - var suiteId uint16 FindCipherSuite: for _, id := range clientHello.cipherSuites { for _, supported := range config.cipherSuites() { if id == supported { - suite = cipherSuites[id] + suite = nil + for _, s := range cipherSuites { + if s.id == id { + suite = s + break + } + } + if suite == nil { + continue + } // Don't select a ciphersuite which we can't // support for this client. if suite.elliptic && !ellipticOk { continue } - suiteId = id break FindCipherSuite } } @@ -87,8 +94,8 @@ FindCipherSuite: } hello.vers = vers - hello.cipherSuite = suiteId - t := uint32(config.time()) + hello.cipherSuite = suite.id + t := uint32(config.time().Unix()) hello.random = make([]byte, 32) hello.random[0] = byte(t >> 24) hello.random[1] = byte(t >> 16) @@ -228,8 +235,8 @@ FindCipherSuite: } digest := make([]byte, 36) - copy(digest[0:16], finishedHash.serverMD5.Sum()) - copy(digest[16:36], finishedHash.serverSHA1.Sum()) + copy(digest[0:16], finishedHash.serverMD5.Sum(nil)) + copy(digest[16:36], finishedHash.serverSHA1.Sum(nil)) err = rsa.VerifyPKCS1v15(pub, crypto.MD5SHA1, digest, certVerify.signature) if err != nil { c.sendAlert(alertBadCertificate) @@ -296,7 +303,7 @@ FindCipherSuite: c.writeRecord(recordTypeHandshake, finished.marshal()) c.handshakeComplete = true - c.cipherSuite = suiteId + c.cipherSuite = suite.id return nil } diff --git a/libgo/go/crypto/tls/handshake_server_test.go b/libgo/go/crypto/tls/handshake_server_test.go index bc37979..e00c32c 100644 --- a/libgo/go/crypto/tls/handshake_server_test.go +++ b/libgo/go/crypto/tls/handshake_server_test.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "testing" + "time" ) type zeroSource struct{} @@ -31,7 +32,7 @@ var testConfig *Config func init() { testConfig = new(Config) - testConfig.Time = func() int64 { return 0 } + testConfig.Time = func() time.Time { return time.Unix(0, 0) } testConfig.Rand = zeroSource{} testConfig.Certificates = make([]Certificate, 1) testConfig.Certificates[0].Certificate = [][]byte{testCertificate} diff --git a/libgo/go/crypto/tls/key_agreement.go b/libgo/go/crypto/tls/key_agreement.go index 08fb852..b531717 100644 --- a/libgo/go/crypto/tls/key_agreement.go +++ b/libgo/go/crypto/tls/key_agreement.go @@ -90,13 +90,13 @@ func md5SHA1Hash(slices ...[]byte) []byte { for _, slice := range slices { hmd5.Write(slice) } - copy(md5sha1, hmd5.Sum()) + copy(md5sha1, hmd5.Sum(nil)) hsha1 := sha1.New() for _, slice := range slices { hsha1.Write(slice) } - copy(md5sha1[md5.Size:], hsha1.Sum()) + copy(md5sha1[md5.Size:], hsha1.Sum(nil)) return md5sha1 } diff --git a/libgo/go/crypto/tls/prf.go b/libgo/go/crypto/tls/prf.go index d758f21..637ef03e 100644 --- a/libgo/go/crypto/tls/prf.go +++ b/libgo/go/crypto/tls/prf.go @@ -22,14 +22,14 @@ func splitPreMasterSecret(secret []byte) (s1, s2 []byte) { func pHash(result, secret, seed []byte, hash func() hash.Hash) { h := hmac.New(hash, secret) h.Write(seed) - a := h.Sum() + a := h.Sum(nil) j := 0 for j < len(result) { h.Reset() h.Write(a) h.Write(seed) - b := h.Sum() + b := h.Sum(nil) todo := len(b) if j+todo > len(result) { todo = len(result) - j @@ -39,7 +39,7 @@ func pHash(result, secret, seed []byte, hash func() hash.Hash) { h.Reset() h.Write(a) - a = h.Sum() + a = h.Sum(nil) } } @@ -84,13 +84,13 @@ func pRF30(result, secret, label, seed []byte) { hashSHA1.Write(b[:i+1]) hashSHA1.Write(secret) hashSHA1.Write(seed) - digest := hashSHA1.Sum() + digest := hashSHA1.Sum(nil) hashMD5.Reset() hashMD5.Write(secret) hashMD5.Write(digest) - done += copy(result[done:], hashMD5.Sum()) + done += copy(result[done:], hashMD5.Sum(nil)) i++ } } @@ -182,24 +182,24 @@ func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []by md5.Write(magic[:]) md5.Write(masterSecret) md5.Write(ssl30Pad1[:]) - md5Digest := md5.Sum() + md5Digest := md5.Sum(nil) md5.Reset() md5.Write(masterSecret) md5.Write(ssl30Pad2[:]) md5.Write(md5Digest) - md5Digest = md5.Sum() + md5Digest = md5.Sum(nil) sha1.Write(magic[:]) sha1.Write(masterSecret) sha1.Write(ssl30Pad1[:40]) - sha1Digest := sha1.Sum() + sha1Digest := sha1.Sum(nil) sha1.Reset() sha1.Write(masterSecret) sha1.Write(ssl30Pad2[:40]) sha1.Write(sha1Digest) - sha1Digest = sha1.Sum() + sha1Digest = sha1.Sum(nil) ret := make([]byte, len(md5Digest)+len(sha1Digest)) copy(ret, md5Digest) @@ -217,8 +217,8 @@ func (h finishedHash) clientSum(masterSecret []byte) []byte { return finishedSum30(h.clientMD5, h.clientSHA1, masterSecret, ssl3ClientFinishedMagic) } - md5 := h.clientMD5.Sum() - sha1 := h.clientSHA1.Sum() + md5 := h.clientMD5.Sum(nil) + sha1 := h.clientSHA1.Sum(nil) return finishedSum10(md5, sha1, clientFinishedLabel, masterSecret) } @@ -229,7 +229,7 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte { return finishedSum30(h.serverMD5, h.serverSHA1, masterSecret, ssl3ServerFinishedMagic) } - md5 := h.serverMD5.Sum() - sha1 := h.serverSHA1.Sum() + md5 := h.serverMD5.Sum(nil) + sha1 := h.serverSHA1.Sum(nil) return finishedSum10(md5, sha1, serverFinishedLabel, masterSecret) } diff --git a/libgo/go/crypto/tls/root_unix.go b/libgo/go/crypto/tls/root_unix.go index 095beec..1b9aeb0 100644 --- a/libgo/go/crypto/tls/root_unix.go +++ b/libgo/go/crypto/tls/root_unix.go @@ -14,6 +14,7 @@ var certFiles = []string{ "/etc/ssl/certs/ca-certificates.crt", // Linux etc "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/ssl/cert.pem", // OpenBSD } func initDefaultRoots() { diff --git a/libgo/go/crypto/tls/root_windows.go b/libgo/go/crypto/tls/root_windows.go index 13073dc..319309a 100644 --- a/libgo/go/crypto/tls/root_windows.go +++ b/libgo/go/crypto/tls/root_windows.go @@ -6,7 +6,6 @@ package tls import ( "crypto/x509" - "reflect" "syscall" "unsafe" ) @@ -16,29 +15,23 @@ func loadStore(roots *x509.CertPool, name string) { if err != nil { return } + defer syscall.CertCloseStore(store, 0) var cert *syscall.CertContext for { - cert = syscall.CertEnumCertificatesInStore(store, cert) - if cert == nil { - break + cert, err = syscall.CertEnumCertificatesInStore(store, cert) + if err != nil { + return } - var asn1Slice []byte - hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&asn1Slice)) - hdrp.Data = cert.EncodedCert - hdrp.Len = int(cert.Length) - hdrp.Cap = int(cert.Length) - - buf := make([]byte, len(asn1Slice)) - copy(buf, asn1Slice) - - if cert, err := x509.ParseCertificate(buf); err == nil { - roots.AddCert(cert) + buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:] + // ParseCertificate requires its own copy of certificate data to keep. + buf2 := make([]byte, cert.Length) + copy(buf2, buf) + if c, err := x509.ParseCertificate(buf2); err == nil { + roots.AddCert(c) } } - - syscall.CertCloseStore(store, 0) } func initDefaultRoots() { diff --git a/libgo/go/crypto/tls/tls.go b/libgo/go/crypto/tls/tls.go index 3ca6240..79ab502 100644 --- a/libgo/go/crypto/tls/tls.go +++ b/libgo/go/crypto/tls/tls.go @@ -157,10 +157,21 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (cert Certificate, err error) return } - key, err := x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes) - if err != nil { - err = errors.New("crypto/tls: failed to parse key: " + err.Error()) - return + // OpenSSL 0.9.8 generates PKCS#1 private keys by default, while + // OpenSSL 1.0.0 generates PKCS#8 keys. We try both. + var key *rsa.PrivateKey + if key, err = x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes); err != nil { + var privKey interface{} + if privKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes); err != nil { + err = errors.New("crypto/tls: failed to parse key: " + err.Error()) + return + } + + var ok bool + if key, ok = privKey.(*rsa.PrivateKey); !ok { + err = errors.New("crypto/tls: found non-RSA private key in PKCS#8 wrapping") + return + } } cert.PrivateKey = key diff --git a/libgo/go/crypto/x509/cert_pool.go b/libgo/go/crypto/x509/cert_pool.go index b9196ed..adc7f9b 100644 --- a/libgo/go/crypto/x509/cert_pool.go +++ b/libgo/go/crypto/x509/cert_pool.go @@ -8,7 +8,7 @@ import ( "encoding/pem" ) -// Roots is a set of certificates. +// CertPool is a set of certificates. type CertPool struct { bySubjectKeyId map[string][]int byName map[string][]int @@ -70,11 +70,11 @@ func (s *CertPool) AddCert(cert *Certificate) { s.byName[name] = append(s.byName[name], n) } -// AppendCertsFromPEM attempts to parse a series of PEM encoded root -// certificates. It appends any certificates found to s and returns true if any -// certificates were successfully parsed. +// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates. +// It appends any certificates found to s and returns true if any certificates +// were successfully parsed. // -// On many Linux systems, /etc/ssl/cert.pem will contains the system wide set +// On many Linux systems, /etc/ssl/cert.pem will contain the system wide set // of root CAs in a format suitable for this function. func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) { for len(pemCerts) > 0 { diff --git a/libgo/go/crypto/x509/pkcs8.go b/libgo/go/crypto/x509/pkcs8.go new file mode 100644 index 0000000..4d8e051 --- /dev/null +++ b/libgo/go/crypto/x509/pkcs8.go @@ -0,0 +1,42 @@ +// Copyright 2011 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 x509 + +import ( + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" +) + +// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See +// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn. +type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte + // optional attributes omitted. +} + +// ParsePKCS8PrivateKey parses an unencrypted, PKCS#8 private key. See +// http://www.rsa.com/rsalabs/node.asp?id=2130 +func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) { + var privKey pkcs8 + if _, err := asn1.Unmarshal(der, &privKey); err != nil { + return nil, err + } + switch { + case privKey.Algo.Algorithm.Equal(oidRSA): + key, err = ParsePKCS1PrivateKey(privKey.PrivateKey) + if err != nil { + return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error()) + } + return key, nil + default: + return nil, fmt.Errorf("crypto/x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm) + } + + panic("unreachable") +} diff --git a/libgo/go/crypto/x509/pkcs8_test.go b/libgo/go/crypto/x509/pkcs8_test.go new file mode 100644 index 0000000..372005f --- /dev/null +++ b/libgo/go/crypto/x509/pkcs8_test.go @@ -0,0 +1,20 @@ +// Copyright 2011 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 x509 + +import ( + "encoding/hex" + "testing" +) + +var pkcs8PrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031` + +func TestPKCS8(t *testing.T) { + derBytes, _ := hex.DecodeString(pkcs8PrivateKeyHex) + _, err := ParsePKCS8PrivateKey(derBytes) + if err != nil { + t.Errorf("failed to decode PKCS8 key: %s", err) + } +} diff --git a/libgo/go/crypto/x509/pkix/pkix.go b/libgo/go/crypto/x509/pkix/pkix.go index b35274c..8eced55 100644 --- a/libgo/go/crypto/x509/pkix/pkix.go +++ b/libgo/go/crypto/x509/pkix/pkix.go @@ -142,10 +142,9 @@ type CertificateList struct { SignatureValue asn1.BitString } -// HasExpired returns true iff currentTimeSeconds is past the expiry time of -// certList. -func (certList *CertificateList) HasExpired(currentTimeSeconds int64) bool { - return certList.TBSCertList.NextUpdate.Seconds() <= currentTimeSeconds +// HasExpired returns true iff now is past the expiry time of certList. +func (certList *CertificateList) HasExpired(now time.Time) bool { + return now.After(certList.TBSCertList.NextUpdate) } // TBSCertificateList represents the ASN.1 structure of the same name. See RFC @@ -155,8 +154,8 @@ type TBSCertificateList struct { Version int `asn1:"optional,default:2"` Signature AlgorithmIdentifier Issuer RDNSequence - ThisUpdate *time.Time - NextUpdate *time.Time + ThisUpdate time.Time + NextUpdate time.Time RevokedCertificates []RevokedCertificate `asn1:"optional"` Extensions []Extension `asn1:"tag:0,optional,explicit"` } @@ -165,6 +164,6 @@ type TBSCertificateList struct { // 5280, section 5.1. type RevokedCertificate struct { SerialNumber *big.Int - RevocationTime *time.Time + RevocationTime time.Time Extensions []Extension `asn1:"optional"` } diff --git a/libgo/go/crypto/x509/verify.go b/libgo/go/crypto/x509/verify.go index 3021d20..50a3b66 100644 --- a/libgo/go/crypto/x509/verify.go +++ b/libgo/go/crypto/x509/verify.go @@ -76,7 +76,7 @@ type VerifyOptions struct { DNSName string Intermediates *CertPool Roots *CertPool - CurrentTime int64 // if 0, the current system time is used. + CurrentTime time.Time // if zero, the current time is used } const ( @@ -87,8 +87,11 @@ const ( // isValid performs validity checks on the c. func (c *Certificate) isValid(certType int, opts *VerifyOptions) error { - if opts.CurrentTime < c.NotBefore.Seconds() || - opts.CurrentTime > c.NotAfter.Seconds() { + now := opts.CurrentTime + if now.IsZero() { + now = time.Now() + } + if now.Before(c.NotBefore) || now.After(c.NotAfter) { return CertificateInvalidError{c, Expired} } @@ -136,9 +139,6 @@ func (c *Certificate) isValid(certType int, opts *VerifyOptions) error { // // WARNING: this doesn't do any revocation checking. func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) { - if opts.CurrentTime == 0 { - opts.CurrentTime = time.Seconds() - } err = c.isValid(leafCertificate, &opts) if err != nil { return diff --git a/libgo/go/crypto/x509/verify_test.go b/libgo/go/crypto/x509/verify_test.go index 2194d15..df54430 100644 --- a/libgo/go/crypto/x509/verify_test.go +++ b/libgo/go/crypto/x509/verify_test.go @@ -10,6 +10,7 @@ import ( "errors" "strings" "testing" + "time" ) type verifyTest struct { @@ -133,7 +134,7 @@ func TestVerify(t *testing.T) { Roots: NewCertPool(), Intermediates: NewCertPool(), DNSName: test.dnsName, - CurrentTime: test.currentTime, + CurrentTime: time.Unix(test.currentTime, 0), } for j, root := range test.roots { diff --git a/libgo/go/crypto/x509/x509.go b/libgo/go/crypto/x509/x509.go index 9ff7db9..7e6b5c9 100644 --- a/libgo/go/crypto/x509/x509.go +++ b/libgo/go/crypto/x509/x509.go @@ -107,7 +107,7 @@ type dsaSignature struct { } type validity struct { - NotBefore, NotAfter *time.Time + NotBefore, NotAfter time.Time } type publicKeyInfo struct { @@ -303,7 +303,7 @@ type Certificate struct { SerialNumber *big.Int Issuer pkix.Name Subject pkix.Name - NotBefore, NotAfter *time.Time // Validity bounds. + NotBefore, NotAfter time.Time // Validity bounds. KeyUsage KeyUsage ExtKeyUsage []ExtKeyUsage // Sequence of extended key usages. @@ -398,7 +398,7 @@ func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature } h.Write(signed) - digest := h.Sum() + digest := h.Sum(nil) switch pub := c.PublicKey.(type) { case *rsa.PublicKey: @@ -899,11 +899,10 @@ var ( oidRSA = []int{1, 2, 840, 113549, 1, 1, 1} ) -// CreateSelfSignedCertificate creates a new certificate based on -// a template. The following members of template are used: SerialNumber, -// Subject, NotBefore, NotAfter, KeyUsage, BasicConstraintsValid, IsCA, -// MaxPathLen, SubjectKeyId, DNSNames, PermittedDNSDomainsCritical, -// PermittedDNSDomains. +// CreateCertificate creates a new certificate based on a template. The +// following members of template are used: SerialNumber, Subject, NotBefore, +// NotAfter, KeyUsage, BasicConstraintsValid, IsCA, MaxPathLen, SubjectKeyId, +// DNSNames, PermittedDNSDomainsCritical, PermittedDNSDomains. // // The certificate is signed by parent. If parent is equal to template then the // certificate is self-signed. The parameter pub is the public key of the @@ -958,7 +957,7 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub *rsa.P h := sha1.New() h.Write(tbsCertContents) - digest := h.Sum() + digest := h.Sum(nil) signature, err := rsa.SignPKCS1v15(rand, priv, crypto.SHA1, digest) if err != nil { @@ -1006,7 +1005,7 @@ func ParseDERCRL(derBytes []byte) (certList *pkix.CertificateList, err error) { // CreateCRL returns a DER encoded CRL, signed by this Certificate, that // contains the given list of revoked certificates. -func (c *Certificate) CreateCRL(rand io.Reader, priv *rsa.PrivateKey, revokedCerts []pkix.RevokedCertificate, now, expiry *time.Time) (crlBytes []byte, err error) { +func (c *Certificate) CreateCRL(rand io.Reader, priv *rsa.PrivateKey, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) { tbsCertList := pkix.TBSCertificateList{ Version: 2, Signature: pkix.AlgorithmIdentifier{ @@ -1025,7 +1024,7 @@ func (c *Certificate) CreateCRL(rand io.Reader, priv *rsa.PrivateKey, revokedCer h := sha1.New() h.Write(tbsCertListContents) - digest := h.Sum() + digest := h.Sum(nil) signature, err := rsa.SignPKCS1v15(rand, priv, crypto.SHA1, digest) if err != nil { diff --git a/libgo/go/crypto/x509/x509_test.go b/libgo/go/crypto/x509/x509_test.go index c424715..f0327b0 100644 --- a/libgo/go/crypto/x509/x509_test.go +++ b/libgo/go/crypto/x509/x509_test.go @@ -250,8 +250,8 @@ func TestCreateSelfSignedCertificate(t *testing.T) { CommonName: commonName, Organization: []string{"Acme Co"}, }, - NotBefore: time.SecondsToUTC(1000), - NotAfter: time.SecondsToUTC(100000), + NotBefore: time.Unix(1000, 0), + NotAfter: time.Unix(100000, 0), SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: KeyUsageCertSign, @@ -396,8 +396,8 @@ func TestCRLCreation(t *testing.T) { block, _ = pem.Decode([]byte(pemCertificate)) cert, _ := ParseCertificate(block.Bytes) - now := time.SecondsToUTC(1000) - expiry := time.SecondsToUTC(10000) + now := time.Unix(1000, 0) + expiry := time.Unix(10000, 0) revokedCerts := []pkix.RevokedCertificate{ { @@ -443,7 +443,7 @@ func TestParseDERCRL(t *testing.T) { t.Errorf("bad number of revoked certificates. got: %d want: %d", numCerts, expected) } - if certList.HasExpired(1302517272) { + if certList.HasExpired(time.Unix(1302517272, 0)) { t.Errorf("CRL has expired (but shouldn't have)") } @@ -463,7 +463,7 @@ func TestParsePEMCRL(t *testing.T) { t.Errorf("bad number of revoked certificates. got: %d want: %d", numCerts, expected) } - if certList.HasExpired(1302517272) { + if certList.HasExpired(time.Unix(1302517272, 0)) { t.Errorf("CRL has expired (but shouldn't have)") } diff --git a/libgo/go/encoding/asn1/asn1.go b/libgo/go/encoding/asn1/asn1.go index a006665..22a0dde 100644 --- a/libgo/go/encoding/asn1/asn1.go +++ b/libgo/go/encoding/asn1/asn1.go @@ -247,7 +247,7 @@ func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) // UTCTime -func parseUTCTime(bytes []byte) (ret *time.Time, err error) { +func parseUTCTime(bytes []byte) (ret time.Time, err error) { s := string(bytes) ret, err = time.Parse("0601021504Z0700", s) if err == nil { @@ -259,7 +259,7 @@ func parseUTCTime(bytes []byte) (ret *time.Time, err error) { // parseGeneralizedTime parses the GeneralizedTime from the given byte slice // and returns the resulting time. -func parseGeneralizedTime(bytes []byte) (ret *time.Time, err error) { +func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) { return time.Parse("20060102150405Z0700", string(bytes)) } @@ -450,7 +450,7 @@ var ( objectIdentifierType = reflect.TypeOf(ObjectIdentifier{}) enumeratedType = reflect.TypeOf(Enumerated(0)) flagType = reflect.TypeOf(Flag(false)) - timeType = reflect.TypeOf(&time.Time{}) + timeType = reflect.TypeOf(time.Time{}) rawValueType = reflect.TypeOf(RawValue{}) rawContentsType = reflect.TypeOf(RawContent(nil)) bigIntType = reflect.TypeOf(new(big.Int)) @@ -647,7 +647,7 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam err = err1 return case timeType: - var time *time.Time + var time time.Time var err1 error if universalTag == tagUTCTime { time, err1 = parseUTCTime(innerBytes) @@ -799,7 +799,7 @@ func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { // // An ASN.1 ENUMERATED can be written to an Enumerated. // -// An ASN.1 UTCTIME or GENERALIZEDTIME can be written to a *time.Time. +// An ASN.1 UTCTIME or GENERALIZEDTIME can be written to a time.Time. // // An ASN.1 PrintableString or IA5String can be written to a string. // diff --git a/libgo/go/encoding/asn1/asn1_test.go b/libgo/go/encoding/asn1/asn1_test.go index 1c529bd..2e6fccf 100644 --- a/libgo/go/encoding/asn1/asn1_test.go +++ b/libgo/go/encoding/asn1/asn1_test.go @@ -202,43 +202,51 @@ func TestObjectIdentifier(t *testing.T) { type timeTest struct { in string ok bool - out *time.Time + out time.Time } var utcTestData = []timeTest{ - {"910506164540-0700", true, &time.Time{1991, 05, 06, 16, 45, 40, 0, -7 * 60 * 60, ""}}, - {"910506164540+0730", true, &time.Time{1991, 05, 06, 16, 45, 40, 0, 7*60*60 + 30*60, ""}}, - {"910506234540Z", true, &time.Time{1991, 05, 06, 23, 45, 40, 0, 0, "UTC"}}, - {"9105062345Z", true, &time.Time{1991, 05, 06, 23, 45, 0, 0, 0, "UTC"}}, - {"a10506234540Z", false, nil}, - {"91a506234540Z", false, nil}, - {"9105a6234540Z", false, nil}, - {"910506a34540Z", false, nil}, - {"910506334a40Z", false, nil}, - {"91050633444aZ", false, nil}, - {"910506334461Z", false, nil}, - {"910506334400Za", false, nil}, + {"910506164540-0700", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", -7*60*60))}, + {"910506164540+0730", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", 7*60*60+30*60))}, + {"910506234540Z", true, time.Date(1991, 05, 06, 23, 45, 40, 0, time.UTC)}, + {"9105062345Z", true, time.Date(1991, 05, 06, 23, 45, 0, 0, time.UTC)}, + {"a10506234540Z", false, time.Time{}}, + {"91a506234540Z", false, time.Time{}}, + {"9105a6234540Z", false, time.Time{}}, + {"910506a34540Z", false, time.Time{}}, + {"910506334a40Z", false, time.Time{}}, + {"91050633444aZ", false, time.Time{}}, + {"910506334461Z", false, time.Time{}}, + {"910506334400Za", false, time.Time{}}, } func TestUTCTime(t *testing.T) { for i, test := range utcTestData { ret, err := parseUTCTime([]byte(test.in)) - if (err == nil) != test.ok { - t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok) - } - if err == nil { - if !reflect.DeepEqual(test.out, ret) { - t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out) + if err != nil { + if test.ok { + t.Errorf("#%d: parseUTCTime(%q) = error %v", i, err) } + continue + } + if !test.ok { + t.Errorf("#%d: parseUTCTime(%q) succeeded, should have failed", i) + continue + } + const format = "Jan _2 15:04:05 -0700 2006" // ignore zone name, just offset + have := ret.Format(format) + want := test.out.Format(format) + if have != want { + t.Errorf("#%d: parseUTCTime(%q) = %s, want %s", test.in, have, want) } } } var generalizedTimeTestData = []timeTest{ - {"20100102030405Z", true, &time.Time{2010, 01, 02, 03, 04, 05, 0, 0, "UTC"}}, - {"20100102030405", false, nil}, - {"20100102030405+0607", true, &time.Time{2010, 01, 02, 03, 04, 05, 0, 6*60*60 + 7*60, ""}}, - {"20100102030405-0607", true, &time.Time{2010, 01, 02, 03, 04, 05, 0, -6*60*60 - 7*60, ""}}, + {"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC)}, + {"20100102030405", false, time.Time{}}, + {"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))}, + {"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60))}, } func TestGeneralizedTime(t *testing.T) { @@ -407,7 +415,7 @@ type AttributeTypeAndValue struct { } type Validity struct { - NotBefore, NotAfter *time.Time + NotBefore, NotAfter time.Time } type PublicKeyInfo struct { @@ -475,7 +483,10 @@ var derEncodedSelfSignedCert = Certificate{ RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 3}, Value: "false.example.com"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "false@example.com"}}, }, - Validity: Validity{NotBefore: &time.Time{Year: 2009, Month: 10, Day: 8, Hour: 0, Minute: 25, Second: 53, ZoneOffset: 0, Zone: "UTC"}, NotAfter: &time.Time{Year: 2010, Month: 10, Day: 8, Hour: 0, Minute: 25, Second: 53, ZoneOffset: 0, Zone: "UTC"}}, + Validity: Validity{ + NotBefore: time.Date(2009, 10, 8, 00, 25, 53, 0, time.UTC), + NotAfter: time.Date(2010, 10, 8, 00, 25, 53, 0, time.UTC), + }, Subject: RDNSequence{ RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 6}, Value: "XX"}}, RelativeDistinguishedNameSET{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 8}, Value: "Some-State"}}, diff --git a/libgo/go/encoding/asn1/marshal.go b/libgo/go/encoding/asn1/marshal.go index 89c50a7..c181e43 100644 --- a/libgo/go/encoding/asn1/marshal.go +++ b/libgo/go/encoding/asn1/marshal.go @@ -288,52 +288,58 @@ func marshalTwoDigits(out *forkableWriter, v int) (err error) { return out.WriteByte(byte('0' + v%10)) } -func marshalUTCTime(out *forkableWriter, t *time.Time) (err error) { +func marshalUTCTime(out *forkableWriter, t time.Time) (err error) { + utc := t.UTC() + year, month, day := utc.Date() + switch { - case 1950 <= t.Year && t.Year < 2000: - err = marshalTwoDigits(out, int(t.Year-1900)) - case 2000 <= t.Year && t.Year < 2050: - err = marshalTwoDigits(out, int(t.Year-2000)) + case 1950 <= year && year < 2000: + err = marshalTwoDigits(out, int(year-1900)) + case 2000 <= year && year < 2050: + err = marshalTwoDigits(out, int(year-2000)) default: return StructuralError{"Cannot represent time as UTCTime"} } - if err != nil { return } - err = marshalTwoDigits(out, t.Month) + err = marshalTwoDigits(out, int(month)) if err != nil { return } - err = marshalTwoDigits(out, t.Day) + err = marshalTwoDigits(out, day) if err != nil { return } - err = marshalTwoDigits(out, t.Hour) + hour, min, sec := utc.Clock() + + err = marshalTwoDigits(out, hour) if err != nil { return } - err = marshalTwoDigits(out, t.Minute) + err = marshalTwoDigits(out, min) if err != nil { return } - err = marshalTwoDigits(out, t.Second) + err = marshalTwoDigits(out, sec) if err != nil { return } + _, offset := t.Zone() + switch { - case t.ZoneOffset/60 == 0: + case offset/60 == 0: err = out.WriteByte('Z') return - case t.ZoneOffset > 0: + case offset > 0: err = out.WriteByte('+') - case t.ZoneOffset < 0: + case offset < 0: err = out.WriteByte('-') } @@ -341,7 +347,7 @@ func marshalUTCTime(out *forkableWriter, t *time.Time) (err error) { return } - offsetMinutes := t.ZoneOffset / 60 + offsetMinutes := offset / 60 if offsetMinutes < 0 { offsetMinutes = -offsetMinutes } @@ -366,7 +372,7 @@ func stripTagAndLength(in []byte) []byte { func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameters) (err error) { switch value.Type() { case timeType: - return marshalUTCTime(out, value.Interface().(*time.Time)) + return marshalUTCTime(out, value.Interface().(time.Time)) case bitStringType: return marshalBitString(out, value.Interface().(BitString)) case objectIdentifierType: diff --git a/libgo/go/encoding/asn1/marshal_test.go b/libgo/go/encoding/asn1/marshal_test.go index 03df5f1..d05b5d8 100644 --- a/libgo/go/encoding/asn1/marshal_test.go +++ b/libgo/go/encoding/asn1/marshal_test.go @@ -51,10 +51,7 @@ type optionalRawValueTest struct { type testSET []int -func setPST(t *time.Time) *time.Time { - t.ZoneOffset = -28800 - return t -} +var PST = time.FixedZone("PST", -8*60*60) type marshalTest struct { in interface{} @@ -73,9 +70,9 @@ var marshalTests = []marshalTest{ {[]byte{1, 2, 3}, "0403010203"}, {implicitTagTest{64}, "3003850140"}, {explicitTagTest{64}, "3005a503020140"}, - {time.SecondsToUTC(0), "170d3730303130313030303030305a"}, - {time.SecondsToUTC(1258325776), "170d3039313131353232353631365a"}, - {setPST(time.SecondsToUTC(1258325776)), "17113039313131353232353631362d30383030"}, + {time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"}, + {time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"}, + {time.Unix(1258325776, 0).In(PST), "17113039313131353232353631362d30383030"}, {BitString{[]byte{0x80}, 1}, "03020780"}, {BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"}, {ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"}, @@ -123,7 +120,8 @@ func TestMarshal(t *testing.T) { } out, _ := hex.DecodeString(test.out) if bytes.Compare(out, data) != 0 { - t.Errorf("#%d got: %x want %x", i, data, out) + t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) + } } } diff --git a/libgo/go/encoding/json/encode.go b/libgo/go/encoding/json/encode.go index 35964c5..14284f5 100644 --- a/libgo/go/encoding/json/encode.go +++ b/libgo/go/encoding/json/encode.go @@ -16,6 +16,7 @@ import ( "runtime" "sort" "strconv" + "sync" "unicode" "unicode/utf8" ) @@ -295,28 +296,10 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) { case reflect.Struct: e.WriteByte('{') - t := v.Type() - n := v.NumField() first := true - for i := 0; i < n; i++ { - f := t.Field(i) - if f.PkgPath != "" { - continue - } - tag, omitEmpty, quoted := f.Name, false, false - if tv := f.Tag.Get("json"); tv != "" { - if tv == "-" { - continue - } - name, opts := parseTag(tv) - if isValidTag(name) { - tag = name - } - omitEmpty = opts.Contains("omitempty") - quoted = opts.Contains("string") - } - fieldValue := v.Field(i) - if omitEmpty && isEmptyValue(fieldValue) { + for _, ef := range encodeFields(v.Type()) { + fieldValue := v.Field(ef.i) + if ef.omitEmpty && isEmptyValue(fieldValue) { continue } if first { @@ -324,9 +307,9 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) { } else { e.WriteByte(',') } - e.string(tag) + e.string(ef.tag) e.WriteByte(':') - e.reflectValueQuoted(fieldValue, quoted) + e.reflectValueQuoted(fieldValue, ef.quoted) } e.WriteByte('}') @@ -470,3 +453,63 @@ func (e *encodeState) string(s string) (int, error) { e.WriteByte('"') return e.Len() - len0, nil } + +// encodeField contains information about how to encode a field of a +// struct. +type encodeField struct { + i int // field index in struct + tag string + quoted bool + omitEmpty bool +} + +var ( + typeCacheLock sync.RWMutex + encodeFieldsCache = make(map[reflect.Type][]encodeField) +) + +// encodeFields returns a slice of encodeField for a given +// struct type. +func encodeFields(t reflect.Type) []encodeField { + typeCacheLock.RLock() + fs, ok := encodeFieldsCache[t] + typeCacheLock.RUnlock() + if ok { + return fs + } + + typeCacheLock.Lock() + defer typeCacheLock.Unlock() + fs, ok = encodeFieldsCache[t] + if ok { + return fs + } + + v := reflect.Zero(t) + n := v.NumField() + for i := 0; i < n; i++ { + f := t.Field(i) + if f.PkgPath != "" { + continue + } + var ef encodeField + ef.i = i + ef.tag = f.Name + + tv := f.Tag.Get("json") + if tv != "" { + if tv == "-" { + continue + } + name, opts := parseTag(tv) + if isValidTag(name) { + ef.tag = name + } + ef.omitEmpty = opts.Contains("omitempty") + ef.quoted = opts.Contains("string") + } + fs = append(fs, ef) + } + encodeFieldsCache[t] = fs + return fs +} diff --git a/libgo/go/encoding/xml/xml.go b/libgo/go/encoding/xml/xml.go index 216d888..d67a299 100644 --- a/libgo/go/encoding/xml/xml.go +++ b/libgo/go/encoding/xml/xml.go @@ -61,7 +61,7 @@ type StartElement struct { func (e StartElement) Copy() StartElement { attrs := make([]Attr, len(e.Attr)) - copy(e.Attr, attrs) + copy(attrs, e.Attr) e.Attr = attrs return e } diff --git a/libgo/go/encoding/xml/xml_test.go b/libgo/go/encoding/xml/xml_test.go index bcb22af..25ffc91 100644 --- a/libgo/go/encoding/xml/xml_test.go +++ b/libgo/go/encoding/xml/xml_test.go @@ -29,71 +29,69 @@ const testInput = ` </body><!-- missing final newline -->` var rawTokens = []Token{ - CharData([]byte("\n")), + CharData("\n"), ProcInst{"xml", []byte(`version="1.0" encoding="UTF-8"`)}, - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + CharData("\n"), + Directive(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"`), - ), - CharData([]byte("\n")), + CharData("\n"), StartElement{Name{"", "body"}, []Attr{{Name{"xmlns", "foo"}, "ns1"}, {Name{"", "xmlns"}, "ns2"}, {Name{"xmlns", "tag"}, "ns3"}}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"", "hello"}, []Attr{{Name{"", "lang"}, "en"}}}, - CharData([]byte("World <>'\" 白鵬翔")), + CharData("World <>'\" 白鵬翔"), EndElement{Name{"", "hello"}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"", "goodbye"}, []Attr{}}, EndElement{Name{"", "goodbye"}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"", "outer"}, []Attr{{Name{"foo", "attr"}, "value"}, {Name{"xmlns", "tag"}, "ns4"}}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"", "inner"}, []Attr{}}, EndElement{Name{"", "inner"}}, - CharData([]byte("\n ")), + CharData("\n "), EndElement{Name{"", "outer"}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"tag", "name"}, []Attr{}}, - CharData([]byte("\n ")), - CharData([]byte("Some text here.")), - CharData([]byte("\n ")), + CharData("\n "), + CharData("Some text here."), + CharData("\n "), EndElement{Name{"tag", "name"}}, - CharData([]byte("\n")), + CharData("\n"), EndElement{Name{"", "body"}}, - Comment([]byte(" missing final newline ")), + Comment(" missing final newline "), } var cookedTokens = []Token{ - CharData([]byte("\n")), + CharData("\n"), ProcInst{"xml", []byte(`version="1.0" encoding="UTF-8"`)}, - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + CharData("\n"), + Directive(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"`), - ), - CharData([]byte("\n")), + CharData("\n"), StartElement{Name{"ns2", "body"}, []Attr{{Name{"xmlns", "foo"}, "ns1"}, {Name{"", "xmlns"}, "ns2"}, {Name{"xmlns", "tag"}, "ns3"}}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"ns2", "hello"}, []Attr{{Name{"", "lang"}, "en"}}}, - CharData([]byte("World <>'\" 白鵬翔")), + CharData("World <>'\" 白鵬翔"), EndElement{Name{"ns2", "hello"}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"ns2", "goodbye"}, []Attr{}}, EndElement{Name{"ns2", "goodbye"}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"ns2", "outer"}, []Attr{{Name{"ns1", "attr"}, "value"}, {Name{"xmlns", "tag"}, "ns4"}}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"ns2", "inner"}, []Attr{}}, EndElement{Name{"ns2", "inner"}}, - CharData([]byte("\n ")), + CharData("\n "), EndElement{Name{"ns2", "outer"}}, - CharData([]byte("\n ")), + CharData("\n "), StartElement{Name{"ns3", "name"}, []Attr{}}, - CharData([]byte("\n ")), - CharData([]byte("Some text here.")), - CharData([]byte("\n ")), + CharData("\n "), + CharData("Some text here."), + CharData("\n "), EndElement{Name{"ns3", "name"}}, - CharData([]byte("\n")), + CharData("\n"), EndElement{Name{"ns2", "body"}}, - Comment([]byte(" missing final newline ")), + Comment(" missing final newline "), } const testInputAltEncoding = ` @@ -101,11 +99,11 @@ const testInputAltEncoding = ` <TAG>VALUE</TAG>` var rawTokensAltEncoding = []Token{ - CharData([]byte("\n")), + CharData("\n"), ProcInst{"xml", []byte(`version="1.0" encoding="x-testing-uppercase"`)}, - CharData([]byte("\n")), + CharData("\n"), StartElement{Name{"", "tag"}, []Attr{}}, - CharData([]byte("value")), + CharData("value"), EndElement{Name{"", "tag"}}, } @@ -270,21 +268,21 @@ var nestedDirectivesInput = ` ` var nestedDirectivesTokens = []Token{ - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">]`)), - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY xlt ">">]`)), - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY xlt "<">]`)), - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY xlt '>'>]`)), - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY xlt '<'>]`)), - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY xlt '">'>]`)), - CharData([]byte("\n")), - Directive([]byte(`DOCTYPE [<!ENTITY xlt "'<">]`)), - CharData([]byte("\n")), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">]`), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY xlt ">">]`), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY xlt "<">]`), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY xlt '>'>]`), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY xlt '<'>]`), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY xlt '">'>]`), + CharData("\n"), + Directive(`DOCTYPE [<!ENTITY xlt "'<">]`), + CharData("\n"), } func TestNestedDirectives(t *testing.T) { @@ -488,10 +486,13 @@ func TestCopyTokenStartElement(t *testing.T) { elt := StartElement{Name{"", "hello"}, []Attr{{Name{"", "lang"}, "en"}}} var tok1 Token = elt tok2 := CopyToken(tok1) + if tok1.(StartElement).Attr[0].Value != "en" { + t.Error("CopyToken overwrote Attr[0]") + } if !reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(StartElement) != StartElement") } - elt.Attr[0] = Attr{Name{"", "lang"}, "de"} + tok1.(StartElement).Attr[0] = Attr{Name{"", "lang"}, "de"} if reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(CharData) uses same buffer.") } diff --git a/libgo/go/exp/gotype/gotype.go b/libgo/go/exp/gotype/gotype.go index bc4a112..a2a9361 100644 --- a/libgo/go/exp/gotype/gotype.go +++ b/libgo/go/exp/gotype/gotype.go @@ -150,15 +150,15 @@ func processFiles(filenames []string, allFiles bool) { switch info, err := os.Stat(filename); { case err != nil: report(err) - case info.IsRegular(): - if allFiles || isGoFilename(info.Name) { - filenames[i] = filename - i++ - } - case info.IsDirectory(): + case info.IsDir(): if allFiles || *recursive { processDirectory(filename) } + default: + if allFiles || isGoFilename(info.Name()) { + filenames[i] = filename + i++ + } } } fset := token.NewFileSet() diff --git a/libgo/go/exp/gui/gui.go b/libgo/go/exp/gui/gui.go deleted file mode 100644 index a69f83a..0000000 --- a/libgo/go/exp/gui/gui.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2009 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 gui defines a basic graphical user interface programming model. -package gui - -import ( - "image" - "image/draw" -) - -// A Window represents a single graphics window. -type Window interface { - // Screen returns an editable Image for the window. - Screen() draw.Image - // FlushImage flushes changes made to Screen() back to screen. - FlushImage() - // EventChan returns a channel carrying UI events such as key presses, - // mouse movements and window resizes. - EventChan() <-chan interface{} - // Close closes the window. - Close() error -} - -// A KeyEvent is sent for a key press or release. -type KeyEvent struct { - // The value k represents key k being pressed. - // The value -k represents key k being released. - // The specific set of key values is not specified, - // but ordinary characters represent themselves. - Key int -} - -// A MouseEvent is sent for a button press or release or for a mouse movement. -type MouseEvent struct { - // Buttons is a bit mask of buttons: 1<<0 is left, 1<<1 middle, 1<<2 right. - // It represents button state and not necessarily the state delta: bit 0 - // being on means that the left mouse button is down, but does not imply - // that the same button was up in the previous MouseEvent. - Buttons int - // Loc is the location of the cursor. - Loc image.Point - // Nsec is the event's timestamp. - Nsec int64 -} - -// A ConfigEvent is sent each time the window's color model or size changes. -// The client should respond by calling Window.Screen to obtain a new image. -type ConfigEvent struct { - Config image.Config -} - -// An ErrEvent is sent when an error occurs. -type ErrEvent struct { - Err error -} diff --git a/libgo/go/exp/gui/x11/auth.go b/libgo/go/exp/gui/x11/auth.go deleted file mode 100644 index 24e941c..0000000 --- a/libgo/go/exp/gui/x11/auth.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2009 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 x11 - -import ( - "bufio" - "errors" - "io" - "os" -) - -// readU16BE reads a big-endian uint16 from r, using b as a scratch buffer. -func readU16BE(r io.Reader, b []byte) (uint16, error) { - _, err := io.ReadFull(r, b[0:2]) - if err != nil { - return 0, err - } - return uint16(b[0])<<8 + uint16(b[1]), nil -} - -// readStr reads a length-prefixed string from r, using b as a scratch buffer. -func readStr(r io.Reader, b []byte) (string, error) { - n, err := readU16BE(r, b) - if err != nil { - return "", err - } - if int(n) > len(b) { - return "", errors.New("Xauthority entry too long for buffer") - } - _, err = io.ReadFull(r, b[0:n]) - if err != nil { - return "", err - } - return string(b[0:n]), nil -} - -// readAuth reads the X authority file and returns the name/data pair for the display. -// displayStr is the "12" out of a $DISPLAY like ":12.0". -func readAuth(displayStr string) (name, data string, err error) { - // b is a scratch buffer to use and should be at least 256 bytes long - // (i.e. it should be able to hold a hostname). - var b [256]byte - // As per /usr/include/X11/Xauth.h. - const familyLocal = 256 - - fn := os.Getenv("XAUTHORITY") - if fn == "" { - home := os.Getenv("HOME") - if home == "" { - err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set") - return - } - fn = home + "/.Xauthority" - } - r, err := os.Open(fn) - if err != nil { - return - } - defer r.Close() - br := bufio.NewReader(r) - - hostname, err := os.Hostname() - if err != nil { - return - } - for { - var family uint16 - var addr, disp, name0, data0 string - family, err = readU16BE(br, b[0:2]) - if err != nil { - return - } - addr, err = readStr(br, b[0:]) - if err != nil { - return - } - disp, err = readStr(br, b[0:]) - if err != nil { - return - } - name0, err = readStr(br, b[0:]) - if err != nil { - return - } - data0, err = readStr(br, b[0:]) - if err != nil { - return - } - if family == familyLocal && addr == hostname && disp == displayStr { - return name0, data0, nil - } - } - panic("unreachable") -} diff --git a/libgo/go/exp/gui/x11/conn.go b/libgo/go/exp/gui/x11/conn.go deleted file mode 100644 index 15afc65..0000000 --- a/libgo/go/exp/gui/x11/conn.go +++ /dev/null @@ -1,631 +0,0 @@ -// Copyright 2009 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 x11 implements an X11 backend for the exp/gui package. -// -// The X protocol specification is at ftp://ftp.x.org/pub/X11R7.0/doc/PDF/proto.pdf. -// A summary of the wire format can be found in XCB's xproto.xml. -package x11 - -import ( - "bufio" - "errors" - "exp/gui" - "image" - "image/draw" - "io" - "log" - "net" - "os" - "strconv" - "strings" - "time" -) - -type resID uint32 // X resource IDs. - -// TODO(nigeltao): Handle window resizes. -const ( - windowHeight = 600 - windowWidth = 800 -) - -const ( - keymapLo = 8 - keymapHi = 255 -) - -type conn struct { - c io.Closer - r *bufio.Reader - w *bufio.Writer - - gc, window, root, visual resID - - img *image.RGBA - eventc chan interface{} - mouseState gui.MouseEvent - - buf [256]byte // General purpose scratch buffer. - - flush chan bool - flushBuf0 [24]byte - flushBuf1 [4 * 1024]byte -} - -// writeSocket runs in its own goroutine, serving both FlushImage calls -// directly from the exp/gui client and indirectly from X expose events. -// It paints c.img to the X server via PutImage requests. -func (c *conn) writeSocket() { - defer c.c.Close() - for _ = range c.flush { - b := c.img.Bounds() - if b.Empty() { - continue - } - // Each X request has a 16-bit length (in terms of 4-byte units). To avoid going over - // this limit, we send PutImage for each row of the image, rather than trying to paint - // the entire image in one X request. This approach could easily be optimized (or the - // X protocol may have an escape sequence to delimit very large requests). - // TODO(nigeltao): See what XCB's xcb_put_image does in this situation. - units := 6 + b.Dx() - if units > 0xffff || b.Dy() > 0xffff { - log.Print("x11: window is too large for PutImage") - return - } - - c.flushBuf0[0] = 0x48 // PutImage opcode. - c.flushBuf0[1] = 0x02 // XCB_IMAGE_FORMAT_Z_PIXMAP. - c.flushBuf0[2] = uint8(units) - c.flushBuf0[3] = uint8(units >> 8) - setU32LE(c.flushBuf0[4:8], uint32(c.window)) - setU32LE(c.flushBuf0[8:12], uint32(c.gc)) - setU32LE(c.flushBuf0[12:16], 1<<16|uint32(b.Dx())) - c.flushBuf0[21] = 0x18 // depth = 24 bits. - - for y := b.Min.Y; y < b.Max.Y; y++ { - setU32LE(c.flushBuf0[16:20], uint32(y<<16)) - if _, err := c.w.Write(c.flushBuf0[:24]); err != nil { - if err != io.EOF { - log.Println("x11:", err) - } - return - } - p := c.img.Pix[(y-b.Min.Y)*c.img.Stride:] - for x, dx := 0, 4*b.Dx(); x < dx; { - nx := dx - x - if nx > len(c.flushBuf1) { - nx = len(c.flushBuf1) &^ 3 - } - for i := 0; i < nx; i += 4 { - // X11's order is BGRX, not RGBA. - c.flushBuf1[i+0] = p[x+i+2] - c.flushBuf1[i+1] = p[x+i+1] - c.flushBuf1[i+2] = p[x+i+0] - } - x += nx - if _, err := c.w.Write(c.flushBuf1[:nx]); err != nil { - if err != io.EOF { - log.Println("x11:", err) - } - return - } - } - } - if err := c.w.Flush(); err != nil { - if err != io.EOF { - log.Println("x11:", err) - } - return - } - } -} - -func (c *conn) Screen() draw.Image { return c.img } - -func (c *conn) FlushImage() { - select { - case c.flush <- false: - // Flush notification sent. - default: - // Could not send. - // Flush notification must be pending already. - } -} - -func (c *conn) Close() error { - // Shut down the writeSocket goroutine. This will close the socket to the - // X11 server, which will cause c.eventc to close. - close(c.flush) - for _ = range c.eventc { - // Drain the channel to allow the readSocket goroutine to shut down. - } - return nil -} - -func (c *conn) EventChan() <-chan interface{} { return c.eventc } - -// readSocket runs in its own goroutine, reading X events and sending gui -// events on c's EventChan. -func (c *conn) readSocket() { - var ( - keymap [256][]int - keysymsPerKeycode int - ) - defer close(c.eventc) - for { - // X events are always 32 bytes long. - if _, err := io.ReadFull(c.r, c.buf[:32]); err != nil { - if err != io.EOF { - c.eventc <- gui.ErrEvent{err} - } - return - } - switch c.buf[0] { - case 0x01: // Reply from a request (e.g. GetKeyboardMapping). - cookie := int(c.buf[3])<<8 | int(c.buf[2]) - if cookie != 1 { - // We issued only one request (GetKeyboardMapping) with a cookie of 1, - // so we shouldn't get any other reply from the X server. - c.eventc <- gui.ErrEvent{errors.New("x11: unexpected cookie")} - return - } - keysymsPerKeycode = int(c.buf[1]) - b := make([]int, 256*keysymsPerKeycode) - for i := range keymap { - keymap[i] = b[i*keysymsPerKeycode : (i+1)*keysymsPerKeycode] - } - for i := keymapLo; i <= keymapHi; i++ { - m := keymap[i] - for j := range m { - u, err := readU32LE(c.r, c.buf[:4]) - if err != nil { - if err != io.EOF { - c.eventc <- gui.ErrEvent{err} - } - return - } - m[j] = int(u) - } - } - case 0x02, 0x03: // Key press, key release. - // X Keyboard Encoding is documented at http://tronche.com/gui/x/xlib/input/keyboard-encoding.html - // TODO(nigeltao): Do we need to implement the "MODE SWITCH / group modifier" feature - // or is that some no-longer-used X construct? - if keysymsPerKeycode < 2 { - // Either we haven't yet received the GetKeyboardMapping reply or - // the X server has sent one that's too short. - continue - } - keycode := int(c.buf[1]) - shift := int(c.buf[28]) & 0x01 - keysym := keymap[keycode][shift] - if keysym == 0 { - keysym = keymap[keycode][0] - } - // TODO(nigeltao): Should we send KeyEvents for Shift/Ctrl/Alt? Should Shift-A send - // the same int down the channel as the sent on just the A key? - // TODO(nigeltao): How should IME events (e.g. key presses that should generate CJK text) work? Or - // is that outside the scope of the gui.Window interface? - if c.buf[0] == 0x03 { - keysym = -keysym - } - c.eventc <- gui.KeyEvent{keysym} - case 0x04, 0x05: // Button press, button release. - mask := 1 << (c.buf[1] - 1) - if c.buf[0] == 0x04 { - c.mouseState.Buttons |= mask - } else { - c.mouseState.Buttons &^= mask - } - c.mouseState.Nsec = time.Nanoseconds() - c.eventc <- c.mouseState - case 0x06: // Motion notify. - c.mouseState.Loc.X = int(int16(c.buf[25])<<8 | int16(c.buf[24])) - c.mouseState.Loc.Y = int(int16(c.buf[27])<<8 | int16(c.buf[26])) - c.mouseState.Nsec = time.Nanoseconds() - c.eventc <- c.mouseState - case 0x0c: // Expose. - // A single user action could trigger multiple expose events (e.g. if moving another - // window with XShape'd rounded corners over our window). In that case, the X server will - // send a uint16 count (in bytes 16-17) of the number of additional expose events coming. - // We could parse each event for the (x, y, width, height) and maintain a minimal dirty - // rectangle, but for now, the simplest approach is to paint the entire window, when - // receiving the final event in the series. - if c.buf[17] == 0 && c.buf[16] == 0 { - // TODO(nigeltao): Should we ignore the very first expose event? A freshly mapped window - // will trigger expose, but until the first c.FlushImage call, there's probably nothing to - // paint but black. For an 800x600 window, at 4 bytes per pixel, each repaint writes about - // 2MB over the socket. - c.FlushImage() - } - // TODO(nigeltao): Should we listen to DestroyNotify (0x11) and ResizeRequest (0x19) events? - // What about EnterNotify (0x07) and LeaveNotify (0x08)? - } - } -} - -// connect connects to the X server given by the full X11 display name (e.g. -// ":12.0") and returns the connection as well as the portion of the full name -// that is the display number (e.g. "12"). -// Examples: -// connect(":1") // calls net.Dial("unix", "", "/tmp/.X11-unix/X1"), displayStr="1" -// connect("/tmp/launch-123/:0") // calls net.Dial("unix", "", "/tmp/launch-123/:0"), displayStr="0" -// connect("hostname:2.1") // calls net.Dial("tcp", "", "hostname:6002"), displayStr="2" -// connect("tcp/hostname:1.0") // calls net.Dial("tcp", "", "hostname:6001"), displayStr="1" -func connect(display string) (conn net.Conn, displayStr string, err error) { - colonIdx := strings.LastIndex(display, ":") - if colonIdx < 0 { - return nil, "", errors.New("bad display: " + display) - } - // Parse the section before the colon. - var protocol, host, socket string - if display[0] == '/' { - socket = display[:colonIdx] - } else { - if i := strings.LastIndex(display, "/"); i < 0 { - // The default protocol is TCP. - protocol = "tcp" - host = display[:colonIdx] - } else { - protocol = display[:i] - host = display[i+1 : colonIdx] - } - } - // Parse the section after the colon. - after := display[colonIdx+1:] - if after == "" { - return nil, "", errors.New("bad display: " + display) - } - if i := strings.LastIndex(after, "."); i < 0 { - displayStr = after - } else { - displayStr = after[:i] - } - displayInt, err := strconv.Atoi(displayStr) - if err != nil || displayInt < 0 { - return nil, "", errors.New("bad display: " + display) - } - // Make the connection. - if socket != "" { - conn, err = net.Dial("unix", socket+":"+displayStr) - } else if host != "" { - conn, err = net.Dial(protocol, host+":"+strconv.Itoa(6000+displayInt)) - } else { - conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+displayStr) - } - if err != nil { - return nil, "", errors.New("cannot connect to " + display + ": " + err.Error()) - } - return -} - -// authenticate authenticates ourselves with the X server. -// displayStr is the "12" out of ":12.0". -func authenticate(w *bufio.Writer, displayStr string) error { - key, value, err := readAuth(displayStr) - if err != nil { - return err - } - // Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1". - if len(key) != 18 || len(value) != 16 { - return errors.New("unsupported Xauth") - } - // 0x006c means little-endian. 0x000b, 0x0000 means X major version 11, minor version 0. - // 0x0012 and 0x0010 means the auth key and value have lengths 18 and 16. - // The final 0x0000 is padding, so that the string length is a multiple of 4. - _, err = io.WriteString(w, "\x6c\x00\x0b\x00\x00\x00\x12\x00\x10\x00\x00\x00") - if err != nil { - return err - } - _, err = io.WriteString(w, key) - if err != nil { - return err - } - // Again, the 0x0000 is padding. - _, err = io.WriteString(w, "\x00\x00") - if err != nil { - return err - } - _, err = io.WriteString(w, value) - if err != nil { - return err - } - err = w.Flush() - if err != nil { - return err - } - return nil -} - -// readU8 reads a uint8 from r, using b as a scratch buffer. -func readU8(r io.Reader, b []byte) (uint8, error) { - _, err := io.ReadFull(r, b[:1]) - if err != nil { - return 0, err - } - return uint8(b[0]), nil -} - -// readU16LE reads a little-endian uint16 from r, using b as a scratch buffer. -func readU16LE(r io.Reader, b []byte) (uint16, error) { - _, err := io.ReadFull(r, b[:2]) - if err != nil { - return 0, err - } - return uint16(b[0]) | uint16(b[1])<<8, nil -} - -// readU32LE reads a little-endian uint32 from r, using b as a scratch buffer. -func readU32LE(r io.Reader, b []byte) (uint32, error) { - _, err := io.ReadFull(r, b[:4]) - if err != nil { - return 0, err - } - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, nil -} - -// setU32LE sets b[:4] to be the little-endian representation of u. -func setU32LE(b []byte, u uint32) { - b[0] = byte((u >> 0) & 0xff) - b[1] = byte((u >> 8) & 0xff) - b[2] = byte((u >> 16) & 0xff) - b[3] = byte((u >> 24) & 0xff) -} - -// checkPixmapFormats checks that we have an agreeable X pixmap Format. -func checkPixmapFormats(r io.Reader, b []byte, n int) (agree bool, err error) { - for i := 0; i < n; i++ { - _, err = io.ReadFull(r, b[:8]) - if err != nil { - return - } - // Byte 0 is depth, byte 1 is bits-per-pixel, byte 2 is scanline-pad, the rest (5) is padding. - if b[0] == 24 && b[1] == 32 { - agree = true - } - } - return -} - -// checkDepths checks that we have an agreeable X Depth (i.e. one that has an agreeable X VisualType). -func checkDepths(r io.Reader, b []byte, n int, visual uint32) (agree bool, err error) { - for i := 0; i < n; i++ { - var depth, visualsLen uint16 - depth, err = readU16LE(r, b) - if err != nil { - return - } - depth &= 0xff - visualsLen, err = readU16LE(r, b) - if err != nil { - return - } - // Ignore 4 bytes of padding. - _, err = io.ReadFull(r, b[:4]) - if err != nil { - return - } - for j := 0; j < int(visualsLen); j++ { - // Read 24 bytes: visual(4), class(1), bits per rgb value(1), colormap entries(2), - // red mask(4), green mask(4), blue mask(4), padding(4). - v, _ := readU32LE(r, b) - _, _ = readU32LE(r, b) - rm, _ := readU32LE(r, b) - gm, _ := readU32LE(r, b) - bm, _ := readU32LE(r, b) - _, err = readU32LE(r, b) - if err != nil { - return - } - if v == visual && rm == 0xff0000 && gm == 0xff00 && bm == 0xff && depth == 24 { - agree = true - } - } - } - return -} - -// checkScreens checks that we have an agreeable X Screen. -func checkScreens(r io.Reader, b []byte, n int) (root, visual uint32, err error) { - for i := 0; i < n; i++ { - var root0, visual0, x uint32 - root0, err = readU32LE(r, b) - if err != nil { - return - } - // Ignore the next 7x4 bytes, which is: colormap, whitepixel, blackpixel, current input masks, - // width and height (pixels), width and height (mm), min and max installed maps. - _, err = io.ReadFull(r, b[:28]) - if err != nil { - return - } - visual0, err = readU32LE(r, b) - if err != nil { - return - } - // Next 4 bytes: backing stores, save unders, root depth, allowed depths length. - x, err = readU32LE(r, b) - if err != nil { - return - } - nDepths := int(x >> 24) - var agree bool - agree, err = checkDepths(r, b, nDepths, visual0) - if err != nil { - return - } - if agree && root == 0 { - root = root0 - visual = visual0 - } - } - return -} - -// handshake performs the protocol handshake with the X server, and ensures -// that the server provides a compatible Screen, Depth, etc. -func (c *conn) handshake() error { - _, err := io.ReadFull(c.r, c.buf[:8]) - if err != nil { - return err - } - // Byte 0 should be 1 (success), bytes 2:6 should be 0xb0000000 (major/minor version 11.0). - if c.buf[0] != 1 || c.buf[2] != 11 || c.buf[3] != 0 || c.buf[4] != 0 || c.buf[5] != 0 { - return errors.New("unsupported X version") - } - // Ignore the release number. - _, err = io.ReadFull(c.r, c.buf[:4]) - if err != nil { - return err - } - // Read the resource ID base. - resourceIdBase, err := readU32LE(c.r, c.buf[:4]) - if err != nil { - return err - } - // Read the resource ID mask. - resourceIdMask, err := readU32LE(c.r, c.buf[:4]) - if err != nil { - return err - } - if resourceIdMask < 256 { - return errors.New("X resource ID mask is too small") - } - // Ignore the motion buffer size. - _, err = io.ReadFull(c.r, c.buf[:4]) - if err != nil { - return err - } - // Read the vendor length and round it up to a multiple of 4, - // for X11 protocol alignment reasons. - vendorLen, err := readU16LE(c.r, c.buf[:2]) - if err != nil { - return err - } - vendorLen = (vendorLen + 3) &^ 3 - // Read the maximum request length. - maxReqLen, err := readU16LE(c.r, c.buf[:2]) - if err != nil { - return err - } - if maxReqLen != 0xffff { - return errors.New("unsupported X maximum request length") - } - // Read the roots length. - rootsLen, err := readU8(c.r, c.buf[:1]) - if err != nil { - return err - } - // Read the pixmap formats length. - pixmapFormatsLen, err := readU8(c.r, c.buf[:1]) - if err != nil { - return err - } - // Ignore some things that we don't care about (totaling 10 + vendorLen bytes): - // imageByteOrder(1), bitmapFormatBitOrder(1), bitmapFormatScanlineUnit(1) bitmapFormatScanlinePad(1), - // minKeycode(1), maxKeycode(1), padding(4), vendor (vendorLen). - if 10+int(vendorLen) > cap(c.buf) { - return errors.New("unsupported X vendor") - } - _, err = io.ReadFull(c.r, c.buf[:10+int(vendorLen)]) - if err != nil { - return err - } - // Check that we have an agreeable pixmap format. - agree, err := checkPixmapFormats(c.r, c.buf[:8], int(pixmapFormatsLen)) - if err != nil { - return err - } - if !agree { - return errors.New("unsupported X pixmap formats") - } - // Check that we have an agreeable screen. - root, visual, err := checkScreens(c.r, c.buf[:24], int(rootsLen)) - if err != nil { - return err - } - if root == 0 || visual == 0 { - return errors.New("unsupported X screen") - } - c.gc = resID(resourceIdBase) - c.window = resID(resourceIdBase + 1) - c.root = resID(root) - c.visual = resID(visual) - return nil -} - -// NewWindow calls NewWindowDisplay with $DISPLAY. -func NewWindow() (gui.Window, error) { - display := os.Getenv("DISPLAY") - if len(display) == 0 { - return nil, errors.New("$DISPLAY not set") - } - return NewWindowDisplay(display) -} - -// NewWindowDisplay returns a new gui.Window, backed by a newly created and -// mapped X11 window. The X server to connect to is specified by the display -// string, such as ":1". -func NewWindowDisplay(display string) (gui.Window, error) { - socket, displayStr, err := connect(display) - if err != nil { - return nil, err - } - c := new(conn) - c.c = socket - c.r = bufio.NewReader(socket) - c.w = bufio.NewWriter(socket) - err = authenticate(c.w, displayStr) - if err != nil { - return nil, err - } - err = c.handshake() - if err != nil { - return nil, err - } - - // Now that we're connected, show a window, via three X protocol messages. - // First, issue a GetKeyboardMapping request. This is the first request, and - // will be associated with a cookie of 1. - setU32LE(c.buf[0:4], 0x00020065) // 0x65 is the GetKeyboardMapping opcode, and the message is 2 x 4 bytes long. - setU32LE(c.buf[4:8], uint32((keymapHi-keymapLo+1)<<8|keymapLo)) - // Second, create a graphics context (GC). - setU32LE(c.buf[8:12], 0x00060037) // 0x37 is the CreateGC opcode, and the message is 6 x 4 bytes long. - setU32LE(c.buf[12:16], uint32(c.gc)) - setU32LE(c.buf[16:20], uint32(c.root)) - setU32LE(c.buf[20:24], 0x00010004) // Bit 2 is XCB_GC_FOREGROUND, bit 16 is XCB_GC_GRAPHICS_EXPOSURES. - setU32LE(c.buf[24:28], 0x00000000) // The Foreground is black. - setU32LE(c.buf[28:32], 0x00000000) // GraphicsExposures' value is unused. - // Third, create the window. - setU32LE(c.buf[32:36], 0x000a0001) // 0x01 is the CreateWindow opcode, and the message is 10 x 4 bytes long. - setU32LE(c.buf[36:40], uint32(c.window)) - setU32LE(c.buf[40:44], uint32(c.root)) - setU32LE(c.buf[44:48], 0x00000000) // Initial (x, y) is (0, 0). - setU32LE(c.buf[48:52], windowHeight<<16|windowWidth) - setU32LE(c.buf[52:56], 0x00010000) // Border width is 0, XCB_WINDOW_CLASS_INPUT_OUTPUT is 1. - setU32LE(c.buf[56:60], uint32(c.visual)) - setU32LE(c.buf[60:64], 0x00000802) // Bit 1 is XCB_CW_BACK_PIXEL, bit 11 is XCB_CW_EVENT_MASK. - setU32LE(c.buf[64:68], 0x00000000) // The Back-Pixel is black. - setU32LE(c.buf[68:72], 0x0000804f) // Key/button press and release, pointer motion, and expose event masks. - // Fourth, map the window. - setU32LE(c.buf[72:76], 0x00020008) // 0x08 is the MapWindow opcode, and the message is 2 x 4 bytes long. - setU32LE(c.buf[76:80], uint32(c.window)) - // Write the bytes. - _, err = c.w.Write(c.buf[:80]) - if err != nil { - return nil, err - } - err = c.w.Flush() - if err != nil { - return nil, err - } - - c.img = image.NewRGBA(image.Rect(0, 0, windowWidth, windowHeight)) - c.eventc = make(chan interface{}, 16) - c.flush = make(chan bool, 1) - go c.readSocket() - go c.writeSocket() - return c, nil -} diff --git a/libgo/go/exp/sql/driver/driver.go b/libgo/go/exp/sql/driver/driver.go index 91a3884..f0bcca2 100644 --- a/libgo/go/exp/sql/driver/driver.go +++ b/libgo/go/exp/sql/driver/driver.go @@ -7,7 +7,7 @@ // // Code simply using databases should use package sql. // -// Drivers only need to be aware of a subset of Go's types. The db package +// Drivers only need to be aware of a subset of Go's types. The sql package // will convert all types into one of the following: // // int64 @@ -94,12 +94,35 @@ type Result interface { // used by multiple goroutines concurrently. type Stmt interface { // Close closes the statement. + // + // Closing a statement should not interrupt any outstanding + // query created from that statement. That is, the following + // order of operations is valid: + // + // * create a driver statement + // * call Query on statement, returning Rows + // * close the statement + // * read from Rows + // + // If closing a statement invalidates currently-running + // queries, the final step above will incorrectly fail. + // + // TODO(bradfitz): possibly remove the restriction above, if + // enough driver authors object and find it complicates their + // code too much. The sql package could be smarter about + // refcounting the statement and closing it at the appropriate + // time. Close() error // NumInput returns the number of placeholder parameters. - // -1 means the driver doesn't know how to count the number of - // placeholders, so we won't sanity check input here and instead let the - // driver deal with errors. + // + // If NumInput returns >= 0, the sql package will sanity check + // argument counts from callers and return errors to the caller + // before the statement's Exec or Query methods are called. + // + // NumInput may also return -1, if the driver doesn't know + // its number of placeholders. In that case, the sql package + // will not sanity check Exec or Query argument counts. NumInput() int // Exec executes a query that doesn't return rows, such diff --git a/libgo/go/exp/sql/fakedb_test.go b/libgo/go/exp/sql/fakedb_test.go index 17028e2..2474a86 100644 --- a/libgo/go/exp/sql/fakedb_test.go +++ b/libgo/go/exp/sql/fakedb_test.go @@ -90,6 +90,8 @@ type fakeStmt struct { cmd string table string + closed bool + colName []string // used by CREATE, INSERT, SELECT (selected columns) colType []string // used by CREATE colValue []interface{} // used by INSERT (mix of strings and "?" for bound params) @@ -232,6 +234,9 @@ func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (driver.Stmt, e stmt.table = parts[0] stmt.colName = strings.Split(parts[1], ",") for n, colspec := range strings.Split(parts[2], ",") { + if colspec == "" { + continue + } nameVal := strings.Split(colspec, "=") if len(nameVal) != 2 { return nil, errf("SELECT on table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) @@ -342,10 +347,16 @@ func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter { } func (s *fakeStmt) Close() error { + s.closed = true return nil } +var errClosed = errors.New("fakedb: statement has been closed") + func (s *fakeStmt) Exec(args []interface{}) (driver.Result, error) { + if s.closed { + return nil, errClosed + } err := checkSubsetTypes(args) if err != nil { return nil, err @@ -405,6 +416,9 @@ func (s *fakeStmt) execInsert(args []interface{}) (driver.Result, error) { } func (s *fakeStmt) Query(args []interface{}) (driver.Rows, error) { + if s.closed { + return nil, errClosed + } err := checkSubsetTypes(args) if err != nil { return nil, err diff --git a/libgo/go/exp/sql/sql.go b/libgo/go/exp/sql/sql.go index c055fdd..f17d12e 100644 --- a/libgo/go/exp/sql/sql.go +++ b/libgo/go/exp/sql/sql.go @@ -344,25 +344,26 @@ func (tx *Tx) Rollback() error { return tx.txi.Rollback() } -// Prepare creates a prepared statement. +// Prepare creates a prepared statement for use within a transaction. // -// The statement is only valid within the scope of this transaction. +// The returned statement operates within the transaction and can no longer +// be used once the transaction has been committed or rolled back. +// +// To use an existing prepared statement on this transaction, see Tx.Stmt. func (tx *Tx) Prepare(query string) (*Stmt, error) { - // TODO(bradfitz): the restriction that the returned statement - // is only valid for this Transaction is lame and negates a - // lot of the benefit of prepared statements. We could be - // more efficient here and either provide a method to take an - // existing Stmt (created on perhaps a different Conn), and - // re-create it on this Conn if necessary. Or, better: keep a - // map in DB of query string to Stmts, and have Stmt.Execute - // do the right thing and re-prepare if the Conn in use - // doesn't have that prepared statement. But we'll want to - // avoid caching the statement in the case where we only call - // conn.Prepare implicitly (such as in db.Exec or tx.Exec), - // but the caller package can't be holding a reference to the - // returned statement. Perhaps just looking at the reference - // count (by noting Stmt.Close) would be enough. We might also - // want a finalizer on Stmt to drop the reference count. + // TODO(bradfitz): We could be more efficient here and either + // provide a method to take an existing Stmt (created on + // perhaps a different Conn), and re-create it on this Conn if + // necessary. Or, better: keep a map in DB of query string to + // Stmts, and have Stmt.Execute do the right thing and + // re-prepare if the Conn in use doesn't have that prepared + // statement. But we'll want to avoid caching the statement + // in the case where we only call conn.Prepare implicitly + // (such as in db.Exec or tx.Exec), but the caller package + // can't be holding a reference to the returned statement. + // Perhaps just looking at the reference count (by noting + // Stmt.Close) would be enough. We might also want a finalizer + // on Stmt to drop the reference count. ci, err := tx.grabConn() if err != nil { return nil, err @@ -383,6 +384,39 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) { return stmt, nil } +// Stmt returns a transaction-specific prepared statement from +// an existing statement. +// +// Example: +// updateMoney, err := db.Prepare("UPDATE balance SET money=money+? WHERE id=?") +// ... +// tx, err := db.Begin() +// ... +// res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203) +func (tx *Tx) Stmt(stmt *Stmt) *Stmt { + // TODO(bradfitz): optimize this. Currently this re-prepares + // each time. This is fine for now to illustrate the API but + // we should really cache already-prepared statements + // per-Conn. See also the big comment in Tx.Prepare. + + if tx.db != stmt.db { + return &Stmt{stickyErr: errors.New("sql: Tx.Stmt: statement from different database used")} + } + ci, err := tx.grabConn() + if err != nil { + return &Stmt{stickyErr: err} + } + defer tx.releaseConn() + si, err := ci.Prepare(stmt.query) + return &Stmt{ + db: tx.db, + tx: tx, + txsi: si, + query: stmt.query, + stickyErr: err, + } +} + // Exec executes a query that doesn't return rows. // For example: an INSERT and UPDATE. func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) { @@ -448,8 +482,9 @@ type connStmt struct { // Stmt is a prepared statement. Stmt is safe for concurrent use by multiple goroutines. type Stmt struct { // Immutable: - db *DB // where we came from - query string // that created the Sttm + db *DB // where we came from + query string // that created the Stmt + stickyErr error // if non-nil, this error is returned for all operations // If in a transaction, else both nil: tx *Tx @@ -513,6 +548,9 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) { // statement, a function to call to release the connection, and a // statement bound to that connection. func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, err error) { + if s.stickyErr != nil { + return nil, nil, nil, s.stickyErr + } s.mu.Lock() if s.closed { s.mu.Unlock() @@ -621,6 +659,9 @@ func (s *Stmt) QueryRow(args ...interface{}) *Row { // Close closes the statement. func (s *Stmt) Close() error { + if s.stickyErr != nil { + return s.stickyErr + } s.mu.Lock() defer s.mu.Unlock() if s.closed { diff --git a/libgo/go/exp/sql/sql_test.go b/libgo/go/exp/sql/sql_test.go index d365f6b..4f8318d 100644 --- a/libgo/go/exp/sql/sql_test.go +++ b/libgo/go/exp/sql/sql_test.go @@ -5,6 +5,7 @@ package sql import ( + "reflect" "strings" "testing" ) @@ -22,7 +23,6 @@ func newTestDB(t *testing.T, name string) *DB { exec(t, db, "INSERT|people|name=Alice,age=?", 1) exec(t, db, "INSERT|people|name=Bob,age=?", 2) exec(t, db, "INSERT|people|name=Chris,age=?", 3) - } return db } @@ -44,6 +44,40 @@ func closeDB(t *testing.T, db *DB) { func TestQuery(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) + rows, err := db.Query("SELECT|people|age,name|") + if err != nil { + t.Fatalf("Query: %v", err) + } + type row struct { + age int + name string + } + got := []row{} + for rows.Next() { + var r row + err = rows.Scan(&r.age, &r.name) + if err != nil { + t.Fatalf("Scan: %v", err) + } + got = append(got, r) + } + err = rows.Err() + if err != nil { + t.Fatalf("Err: %v", err) + } + want := []row{ + {age: 1, name: "Alice"}, + {age: 2, name: "Bob"}, + {age: 3, name: "Chris"}, + } + if !reflect.DeepEqual(got, want) { + t.Logf(" got: %#v\nwant: %#v", got, want) + } +} + +func TestQueryRow(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) var name string var age int @@ -75,6 +109,24 @@ func TestQuery(t *testing.T) { } } +func TestStatementErrorAfterClose(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + stmt, err := db.Prepare("SELECT|people|age|name=?") + if err != nil { + t.Fatalf("Prepare: %v", err) + } + err = stmt.Close() + if err != nil { + t.Fatalf("Close: %v", err) + } + var name string + err = stmt.QueryRow("foo").Scan(&name) + if err == nil { + t.Errorf("expected error from QueryRow.Scan after Stmt.Close") + } +} + func TestStatementQueryRow(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -114,7 +166,7 @@ func TestBogusPreboundParameters(t *testing.T) { } } -func TestDb(t *testing.T) { +func TestExec(t *testing.T) { db := newTestDB(t, "foo") defer closeDB(t, db) exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool") @@ -154,3 +206,25 @@ func TestDb(t *testing.T) { } } } + +func TestTxStmt(t *testing.T) { + db := newTestDB(t, "") + defer closeDB(t, db) + exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool") + stmt, err := db.Prepare("INSERT|t1|name=?,age=?") + if err != nil { + t.Fatalf("Stmt, err = %v, %v", stmt, err) + } + tx, err := db.Begin() + if err != nil { + t.Fatalf("Begin = %v", err) + } + _, err = tx.Stmt(stmt).Exec("Bobby", 7) + if err != nil { + t.Fatalf("Exec = %v", err) + } + err = tx.Commit() + if err != nil { + t.Fatalf("Commit = %v", err) + } +} diff --git a/libgo/go/exp/ssh/channel.go b/libgo/go/exp/ssh/channel.go index 6ff8203..9d75f37 100644 --- a/libgo/go/exp/ssh/channel.go +++ b/libgo/go/exp/ssh/channel.go @@ -244,13 +244,13 @@ func (c *channel) Write(data []byte) (n int, err error) { packet := make([]byte, 1+4+4+len(todo)) packet[0] = msgChannelData - packet[1] = byte(c.theirId) >> 24 - packet[2] = byte(c.theirId) >> 16 - packet[3] = byte(c.theirId) >> 8 + packet[1] = byte(c.theirId >> 24) + packet[2] = byte(c.theirId >> 16) + packet[3] = byte(c.theirId >> 8) packet[4] = byte(c.theirId) - packet[5] = byte(len(todo)) >> 24 - packet[6] = byte(len(todo)) >> 16 - packet[7] = byte(len(todo)) >> 8 + packet[5] = byte(len(todo) >> 24) + packet[6] = byte(len(todo) >> 16) + packet[7] = byte(len(todo) >> 8) packet[8] = byte(len(todo)) copy(packet[9:], todo) diff --git a/libgo/go/exp/ssh/client.go b/libgo/go/exp/ssh/client.go index 24569ad..429dee9 100644 --- a/libgo/go/exp/ssh/client.go +++ b/libgo/go/exp/ssh/client.go @@ -172,40 +172,12 @@ func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha marshalInt(K, kInt) h.Write(K) - H := h.Sum() + H := h.Sum(nil) return H, K, nil } -// openChan opens a new client channel. The most common session type is "session". -// The full set of valid session types are listed in RFC 4250 4.9.1. -func (c *ClientConn) openChan(typ string) (*clientChan, error) { - ch := c.newChan(c.transport) - if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{ - ChanType: typ, - PeersId: ch.id, - PeersWindow: 1 << 14, - MaxPacketSize: 1 << 15, // RFC 4253 6.1 - })); err != nil { - c.chanlist.remove(ch.id) - return nil, err - } - // wait for response - switch msg := (<-ch.msg).(type) { - case *channelOpenConfirmMsg: - ch.peersId = msg.MyId - ch.win <- int(msg.MyWindow) - case *channelOpenFailureMsg: - c.chanlist.remove(ch.id) - return nil, errors.New(msg.Message) - default: - c.chanlist.remove(ch.id) - return nil, errors.New("Unexpected packet") - } - return ch, nil -} - -// mainloop reads incoming messages and routes channel messages +// mainLoop reads incoming messages and routes channel messages // to their respective ClientChans. func (c *ClientConn) mainLoop() { // TODO(dfc) signal the underlying close to all channels @@ -271,7 +243,7 @@ func (c *ClientConn) mainLoop() { case *windowAdjustMsg: c.getChan(msg.PeersId).win <- int(msg.AdditionalBytes) default: - fmt.Printf("mainLoop: unhandled %#v\n", msg) + fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg) } } } @@ -338,27 +310,16 @@ func newClientChan(t *transport, id uint32) *clientChan { // Close closes the channel. This does not close the underlying connection. func (c *clientChan) Close() error { return c.writePacket(marshal(msgChannelClose, channelCloseMsg{ - PeersId: c.id, + PeersId: c.peersId, })) } -func (c *clientChan) sendChanReq(req channelRequestMsg) error { - if err := c.writePacket(marshal(msgChannelRequest, req)); err != nil { - return err - } - msg := <-c.msg - if _, ok := msg.(*channelRequestSuccessMsg); ok { - return nil - } - return fmt.Errorf("failed to complete request: %s, %#v", req.Request, msg) -} - // Thread safe channel list. type chanlist struct { // protects concurrent access to chans sync.Mutex // chans are indexed by the local id of the channel, clientChan.id. - // The PeersId value of messages received by ClientConn.mainloop is + // The PeersId value of messages received by ClientConn.mainLoop is // used to locate the right local clientChan in this slice. chans []*clientChan } @@ -395,7 +356,7 @@ func (c *chanlist) remove(id uint32) { // A chanWriter represents the stdin of a remote process. type chanWriter struct { win chan int // receives window adjustments - id uint32 // this channel's id + peersId uint32 // the peer's id rwin int // current rwin size packetWriter // for sending channelDataMsg } @@ -414,8 +375,8 @@ func (w *chanWriter) Write(data []byte) (n int, err error) { n = len(data) packet := make([]byte, 0, 9+n) packet = append(packet, msgChannelData, - byte(w.id)>>24, byte(w.id)>>16, byte(w.id)>>8, byte(w.id), - byte(n)>>24, byte(n)>>16, byte(n)>>8, byte(n)) + byte(w.peersId>>24), byte(w.peersId>>16), byte(w.peersId>>8), byte(w.peersId), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) err = w.writePacket(append(packet, data...)) w.rwin -= n return @@ -424,7 +385,7 @@ func (w *chanWriter) Write(data []byte) (n int, err error) { } func (w *chanWriter) Close() error { - return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.id})) + return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.peersId})) } // A chanReader represents stdout or stderr of a remote process. @@ -433,8 +394,8 @@ type chanReader struct { // If writes to this channel block, they will block mainLoop, making // it unable to receive new messages from the remote side. data chan []byte // receives data from remote - id uint32 - packetWriter // for sending windowAdjustMsg + peersId uint32 // the peer's id + packetWriter // for sending windowAdjustMsg buf []byte } @@ -446,7 +407,7 @@ func (r *chanReader) Read(data []byte) (int, error) { n := copy(data, r.buf) r.buf = r.buf[n:] msg := windowAdjustMsg{ - PeersId: r.id, + PeersId: r.peersId, AdditionalBytes: uint32(n), } return n, r.writePacket(marshal(msgChannelWindowAdjust, msg)) @@ -458,7 +419,3 @@ func (r *chanReader) Read(data []byte) (int, error) { } panic("unreachable") } - -func (r *chanReader) Close() error { - return r.writePacket(marshal(msgChannelEOF, channelEOFMsg{r.id})) -} diff --git a/libgo/go/exp/ssh/client_auth_test.go b/libgo/go/exp/ssh/client_auth_test.go index 6467f57..4ef9213 100644 --- a/libgo/go/exp/ssh/client_auth_test.go +++ b/libgo/go/exp/ssh/client_auth_test.go @@ -70,7 +70,7 @@ func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err err hashFunc := crypto.SHA1 h := hashFunc.New() h.Write(data) - digest := h.Sum() + digest := h.Sum(nil) return rsa.SignPKCS1v15(rand, k.keys[i], hashFunc, digest) } diff --git a/libgo/go/exp/ssh/common.go b/libgo/go/exp/ssh/common.go index 01c55219..6844fb8 100644 --- a/libgo/go/exp/ssh/common.go +++ b/libgo/go/exp/ssh/common.go @@ -224,3 +224,16 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK r = marshalString(r, pubKey) return ret } + +// safeString sanitises s according to RFC 4251, section 9.2. +// All control characters except tab, carriage return and newline are +// replaced by 0x20. +func safeString(s string) string { + out := []byte(s) + for i, c := range out { + if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 { + out[i] = 0x20 + } + } + return string(out) +} diff --git a/libgo/go/exp/ssh/common_test.go b/libgo/go/exp/ssh/common_test.go new file mode 100644 index 0000000..2f4448a --- /dev/null +++ b/libgo/go/exp/ssh/common_test.go @@ -0,0 +1,26 @@ +// Copyright 2011 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 ssh + +import ( + "testing" +) + +var strings = map[string]string{ + "\x20\x0d\x0a": "\x20\x0d\x0a", + "flibble": "flibble", + "new\x20line": "new\x20line", + "123456\x07789": "123456 789", + "\t\t\x10\r\n": "\t\t \r\n", +} + +func TestSafeString(t *testing.T) { + for s, expected := range strings { + actual := safeString(s) + if expected != actual { + t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual)) + } + } +} diff --git a/libgo/go/exp/ssh/doc.go b/libgo/go/exp/ssh/doc.go index 248b2fe..480f877 100644 --- a/libgo/go/exp/ssh/doc.go +++ b/libgo/go/exp/ssh/doc.go @@ -92,9 +92,9 @@ Each ClientConn can support multiple interactive sessions, represented by a Sess session, err := client.NewSession() Once a Session is created, you can execute a single command on the remote side -using the Exec method. +using the Run method. - if err := session.Exec("/usr/bin/whoami"); err != nil { + if err := session.Run("/usr/bin/whoami"); err != nil { panic("Failed to exec: " + err.String()) } reader := bufio.NewReader(session.Stdin) diff --git a/libgo/go/exp/ssh/server.go b/libgo/go/exp/ssh/server.go index 428a747..1eee9a4 100644 --- a/libgo/go/exp/ssh/server.go +++ b/libgo/go/exp/ssh/server.go @@ -207,11 +207,11 @@ func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handsha marshalInt(K, kInt) h.Write(K) - H = h.Sum() + H = h.Sum(nil) h.Reset() h.Write(H) - hh := h.Sum() + hh := h.Sum(nil) var sig []byte switch hostKeyAlgo { @@ -478,7 +478,7 @@ userAuthLoop: hashFunc := crypto.SHA1 h := hashFunc.New() h.Write(signedData) - digest := h.Sum() + digest := h.Sum(nil) rsaKey, ok := parseRSA(pubKey) if !ok { return ParseError{msgUserAuthRequest} diff --git a/libgo/go/exp/ssh/session.go b/libgo/go/exp/ssh/session.go index 77154f2..5f98a8d 100644 --- a/libgo/go/exp/ssh/session.go +++ b/libgo/go/exp/ssh/session.go @@ -8,125 +8,409 @@ package ssh // "RFC 4254, section 6". import ( - "encoding/binary" + "bytes" "errors" + "fmt" "io" + "io/ioutil" +) + +type Signal string + +// POSIX signals as listed in RFC 4254 Section 6.10. +const ( + SIGABRT Signal = "ABRT" + SIGALRM Signal = "ALRM" + SIGFPE Signal = "FPE" + SIGHUP Signal = "HUP" + SIGILL Signal = "ILL" + SIGINT Signal = "INT" + SIGKILL Signal = "KILL" + SIGPIPE Signal = "PIPE" + SIGQUIT Signal = "QUIT" + SIGSEGV Signal = "SEGV" + SIGTERM Signal = "TERM" + SIGUSR1 Signal = "USR1" + SIGUSR2 Signal = "USR2" ) // A Session represents a connection to a remote command or shell. type Session struct { - // Writes to Stdin are made available to the remote command's standard input. - // Closing Stdin causes the command to observe an EOF on its standard input. - Stdin io.WriteCloser - - // Reads from Stdout and Stderr consume from the remote command's standard - // output and error streams, respectively. - // There is a fixed amount of buffering that is shared for the two streams. - // Failing to read from either may eventually cause the command to block. - // Closing Stdout unblocks such writes and causes them to return errors. - Stdout io.ReadCloser - Stderr io.Reader + // Stdin specifies the remote process's standard input. + // If Stdin is nil, the remote process reads from an empty + // bytes.Buffer. + Stdin io.Reader + + // Stdout and Stderr specify the remote process's standard + // output and error. + // + // If either is nil, Run connects the corresponding file + // descriptor to an instance of ioutil.Discard. There is a + // fixed amount of buffering that is shared for the two streams. + // If either blocks it may eventually cause the remote + // command to block. + Stdout io.Writer + Stderr io.Writer *clientChan // the channel backing this session - started bool // started is set to true once a Shell or Exec is invoked. + started bool // true once Start, Run or Shell is invoked. + closeAfterWait []io.Closer + copyFuncs []func() error + errch chan error // one send per copyFunc +} + +// RFC 4254 Section 6.4. +type setenvRequest struct { + PeersId uint32 + Request string + WantReply bool + Name string + Value string } // Setenv sets an environment variable that will be applied to any -// command executed by Shell or Exec. +// command executed by Shell or Run. func (s *Session) Setenv(name, value string) error { - n, v := []byte(name), []byte(value) - nlen, vlen := stringLength(n), stringLength(v) - payload := make([]byte, nlen+vlen) - marshalString(payload[:nlen], n) - marshalString(payload[nlen:], v) - - return s.sendChanReq(channelRequestMsg{ - PeersId: s.id, - Request: "env", - WantReply: true, - RequestSpecificData: payload, - }) + req := setenvRequest{ + PeersId: s.peersId, + Request: "env", + WantReply: true, + Name: name, + Value: value, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + return s.waitForResponse() } -// An empty mode list (a string of 1 character, opcode 0), see RFC 4254 Section 8. -var emptyModeList = []byte{0, 0, 0, 1, 0} +// An empty mode list, see RFC 4254 Section 8. +var emptyModelist = "\x00" + +// RFC 4254 Section 6.2. +type ptyRequestMsg struct { + PeersId uint32 + Request string + WantReply bool + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} // RequestPty requests the association of a pty with the session on the remote host. func (s *Session) RequestPty(term string, h, w int) error { - buf := make([]byte, 4+len(term)+16+len(emptyModeList)) - b := marshalString(buf, []byte(term)) - binary.BigEndian.PutUint32(b, uint32(h)) - binary.BigEndian.PutUint32(b[4:], uint32(w)) - binary.BigEndian.PutUint32(b[8:], uint32(h*8)) - binary.BigEndian.PutUint32(b[12:], uint32(w*8)) - copy(b[16:], emptyModeList) - - return s.sendChanReq(channelRequestMsg{ - PeersId: s.id, - Request: "pty-req", - WantReply: true, - RequestSpecificData: buf, - }) + req := ptyRequestMsg{ + PeersId: s.peersId, + Request: "pty-req", + WantReply: true, + Term: term, + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + Modelist: emptyModelist, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + return s.waitForResponse() } -// Exec runs cmd on the remote host. Typically, the remote -// server passes cmd to the shell for interpretation. -// A Session only accepts one call to Exec or Shell. -func (s *Session) Exec(cmd string) error { +// RFC 4254 Section 6.9. +type signalMsg struct { + PeersId uint32 + Request string + WantReply bool + Signal string +} + +// Signal sends the given signal to the remote process. +// sig is one of the SIG* constants. +func (s *Session) Signal(sig Signal) error { + req := signalMsg{ + PeersId: s.peersId, + Request: "signal", + WantReply: false, + Signal: string(sig), + } + return s.writePacket(marshal(msgChannelRequest, req)) +} + +// RFC 4254 Section 6.5. +type execMsg struct { + PeersId uint32 + Request string + WantReply bool + Command string +} + +// Start runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start or Shell. +func (s *Session) Start(cmd string) error { if s.started { - return errors.New("session already started") + return errors.New("ssh: session already started") } - cmdLen := stringLength([]byte(cmd)) - payload := make([]byte, cmdLen) - marshalString(payload, []byte(cmd)) - s.started = true + req := execMsg{ + PeersId: s.peersId, + Request: "exec", + WantReply: true, + Command: cmd, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + if err := s.waitForResponse(); err != nil { + return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err) + } + return s.start() +} - return s.sendChanReq(channelRequestMsg{ - PeersId: s.id, - Request: "exec", - WantReply: true, - RequestSpecificData: payload, - }) +// Run runs cmd on the remote host and waits for it to terminate. +// Typically, the remote server passes cmd to the shell for +// interpretation. A Session only accepts one call to Run, +// Start or Shell. +func (s *Session) Run(cmd string) error { + err := s.Start(cmd) + if err != nil { + return err + } + return s.Wait() } -// Shell starts a login shell on the remote host. A Session only -// accepts one call to Exec or Shell. +// Shell starts a login shell on the remote host. A Session only +// accepts one call to Run, Start or Shell. func (s *Session) Shell() error { if s.started { - return errors.New("session already started") + return errors.New("ssh: session already started") } - s.started = true - - return s.sendChanReq(channelRequestMsg{ - PeersId: s.id, + req := channelRequestMsg{ + PeersId: s.peersId, Request: "shell", WantReply: true, + } + if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + if err := s.waitForResponse(); err != nil { + return fmt.Errorf("ssh: cound not execute shell: %v", err) + } + return s.start() +} + +func (s *Session) waitForResponse() error { + msg := <-s.msg + switch msg.(type) { + case *channelRequestSuccessMsg: + return nil + case *channelRequestFailureMsg: + return errors.New("request failed") + } + return fmt.Errorf("unknown packet %T received: %v", msg, msg) +} + +func (s *Session) start() error { + s.started = true + + type F func(*Session) error + for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { + if err := setupFd(s); err != nil { + return err + } + } + + s.errch = make(chan error, len(s.copyFuncs)) + for _, fn := range s.copyFuncs { + go func(fn func() error) { + s.errch <- fn() + }(fn) + } + return nil +} + +// Wait waits for the remote command to exit. +func (s *Session) Wait() error { + if !s.started { + return errors.New("ssh: session not started") + } + waitErr := s.wait() + + var copyError error + for _ = range s.copyFuncs { + if err := <-s.errch; err != nil && copyError == nil { + copyError = err + } + } + for _, fd := range s.closeAfterWait { + fd.Close() + } + if waitErr != nil { + return waitErr + } + return copyError +} + +func (s *Session) wait() error { + for { + switch msg := (<-s.msg).(type) { + case *channelRequestMsg: + // TODO(dfc) improve this behavior to match os.Waitmsg + switch msg.Request { + case "exit-status": + d := msg.RequestSpecificData + status := int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3]) + if status > 0 { + return fmt.Errorf("remote process exited with %d", status) + } + return nil + case "exit-signal": + // TODO(dfc) make a more readable error message + return fmt.Errorf("%v", msg.RequestSpecificData) + default: + return fmt.Errorf("wait: unexpected channel request: %v", msg) + } + default: + return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg) + } + } + panic("unreachable") +} + +func (s *Session) stdin() error { + if s.Stdin == nil { + s.Stdin = new(bytes.Buffer) + } + s.copyFuncs = append(s.copyFuncs, func() error { + w := &chanWriter{ + packetWriter: s, + peersId: s.peersId, + win: s.win, + } + _, err := io.Copy(w, s.Stdin) + if err1 := w.Close(); err == nil { + err = err1 + } + return err }) + return nil } +func (s *Session) stdout() error { + if s.Stdout == nil { + s.Stdout = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + r := &chanReader{ + packetWriter: s, + peersId: s.peersId, + data: s.data, + } + _, err := io.Copy(s.Stdout, r) + return err + }) + return nil +} + +func (s *Session) stderr() error { + if s.Stderr == nil { + s.Stderr = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + r := &chanReader{ + packetWriter: s, + peersId: s.peersId, + data: s.dataExt, + } + _, err := io.Copy(s.Stderr, r) + return err + }) + return nil +} + +// StdinPipe returns a pipe that will be connected to the +// remote command's standard input when the command starts. +func (s *Session) StdinPipe() (io.WriteCloser, error) { + if s.Stdin != nil { + return nil, errors.New("ssh: Stdin already set") + } + if s.started { + return nil, errors.New("ssh: StdinPipe after process started") + } + pr, pw := io.Pipe() + s.Stdin = pr + s.closeAfterWait = append(s.closeAfterWait, pr) + return pw, nil +} + +// StdoutPipe returns a pipe that will be connected to the +// remote command's standard output when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StdoutPipe reader is +// not serviced fast enought it may eventually cause the +// remote command to block. +func (s *Session) StdoutPipe() (io.ReadCloser, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.started { + return nil, errors.New("ssh: StdoutPipe after process started") + } + pr, pw := io.Pipe() + s.Stdout = pw + s.closeAfterWait = append(s.closeAfterWait, pw) + return pr, nil +} + +// StderrPipe returns a pipe that will be connected to the +// remote command's standard error when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StderrPipe reader is +// not serviced fast enought it may eventually cause the +// remote command to block. +func (s *Session) StderrPipe() (io.ReadCloser, error) { + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + if s.started { + return nil, errors.New("ssh: StderrPipe after process started") + } + pr, pw := io.Pipe() + s.Stderr = pw + s.closeAfterWait = append(s.closeAfterWait, pw) + return pr, nil +} + +// TODO(dfc) add Output and CombinedOutput helpers + // NewSession returns a new interactive session on the remote host. func (c *ClientConn) NewSession() (*Session, error) { - ch, err := c.openChan("session") - if err != nil { + ch := c.newChan(c.transport) + if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{ + ChanType: "session", + PeersId: ch.id, + PeersWindow: 1 << 14, + MaxPacketSize: 1 << 15, // RFC 4253 6.1 + })); err != nil { + c.chanlist.remove(ch.id) return nil, err } - return &Session{ - Stdin: &chanWriter{ - packetWriter: ch, - id: ch.id, - win: ch.win, - }, - Stdout: &chanReader{ - packetWriter: ch, - id: ch.id, - data: ch.data, - }, - Stderr: &chanReader{ - packetWriter: ch, - id: ch.id, - data: ch.dataExt, - }, - clientChan: ch, - }, nil + // wait for response + msg := <-ch.msg + switch msg := msg.(type) { + case *channelOpenConfirmMsg: + ch.peersId = msg.MyId + ch.win <- int(msg.MyWindow) + return &Session{ + clientChan: ch, + }, nil + case *channelOpenFailureMsg: + c.chanlist.remove(ch.id) + return nil, fmt.Errorf("ssh: channel open failed: %s", msg.Message) + } + c.chanlist.remove(ch.id) + return nil, fmt.Errorf("ssh: unexpected message %T: %v", msg, msg) } diff --git a/libgo/go/exp/ssh/session_test.go b/libgo/go/exp/ssh/session_test.go new file mode 100644 index 0000000..4be7746 --- /dev/null +++ b/libgo/go/exp/ssh/session_test.go @@ -0,0 +1,149 @@ +// Copyright 2011 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 ssh + +// Session tests. + +import ( + "bytes" + "io" + "testing" +) + +// dial constructs a new test server and returns a *ClientConn. +func dial(t *testing.T) *ClientConn { + pw := password("tiger") + serverConfig.PasswordCallback = func(user, pass string) bool { + return user == "testuser" && pass == string(pw) + } + serverConfig.PubKeyCallback = nil + + l, err := Listen("tcp", "127.0.0.1:0", serverConfig) + if err != nil { + t.Fatalf("unable to listen: %s", err) + } + go func() { + defer l.Close() + conn, err := l.Accept() + if err != nil { + t.Errorf("Unable to accept: %v", err) + return + } + defer conn.Close() + if err := conn.Handshake(); err != nil { + t.Errorf("Unable to handshake: %v", err) + return + } + for { + ch, err := conn.Accept() + if err == io.EOF { + return + } + if err != nil { + t.Errorf("Unable to accept incoming channel request: %v", err) + return + } + if ch.ChannelType() != "session" { + ch.Reject(UnknownChannelType, "unknown channel type") + continue + } + ch.Accept() + go func() { + defer ch.Close() + // this string is returned to stdout + shell := NewServerShell(ch, "golang") + shell.ReadLine() + type exitMsg struct { + PeersId uint32 + Request string + WantReply bool + Status uint32 + } + // TODO(dfc) casting to the concrete type should not be + // necessary to send a packet. + msg := exitMsg{ + PeersId: ch.(*channel).theirId, + Request: "exit-status", + WantReply: false, + Status: 0, + } + ch.(*channel).serverConn.writePacket(marshal(msgChannelRequest, msg)) + }() + } + t.Log("done") + }() + + config := &ClientConfig{ + User: "testuser", + Auth: []ClientAuth{ + ClientAuthPassword(pw), + }, + } + + c, err := Dial("tcp", l.Addr().String(), config) + if err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } + return c +} + +// Test a simple string is returned to session.Stdout. +func TestSessionShell(t *testing.T) { + conn := dial(t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %s", err) + } + defer session.Close() + stdout := new(bytes.Buffer) + session.Stdout = stdout + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %s", err) + } + if err := session.Wait(); err != nil { + t.Fatalf("Remote command did not exit cleanly: %s", err) + } + actual := stdout.String() + if actual != "golang" { + t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) + } +} + +// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it. + +// Test a simple string is returned via StdoutPipe. +func TestSessionStdoutPipe(t *testing.T) { + conn := dial(t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %s", err) + } + defer session.Close() + stdout, err := session.StdoutPipe() + if err != nil { + t.Fatalf("Unable to request StdoutPipe(): %v", err) + } + var buf bytes.Buffer + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %s", err) + } + done := make(chan bool, 1) + go func() { + if _, err := io.Copy(&buf, stdout); err != nil { + t.Errorf("Copy of stdout failed: %v", err) + } + done <- true + }() + if err := session.Wait(); err != nil { + t.Fatalf("Remote command did not exit cleanly: %s", err) + } + <-done + actual := buf.String() + if actual != "golang" { + t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) + } +} diff --git a/libgo/go/exp/ssh/tcpip.go b/libgo/go/exp/ssh/tcpip.go index 859dedc..f3bbac5 100644 --- a/libgo/go/exp/ssh/tcpip.go +++ b/libgo/go/exp/ssh/tcpip.go @@ -86,12 +86,12 @@ func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tc clientChan: ch, Reader: &chanReader{ packetWriter: ch, - id: ch.id, + peersId: ch.peersId, data: ch.data, }, Writer: &chanWriter{ packetWriter: ch, - id: ch.id, + peersId: ch.peersId, win: ch.win, }, }, nil diff --git a/libgo/go/exp/ssh/tcpip_func_test.go b/libgo/go/exp/ssh/tcpip_func_test.go new file mode 100644 index 0000000..2612972 --- /dev/null +++ b/libgo/go/exp/ssh/tcpip_func_test.go @@ -0,0 +1,59 @@ +// Copyright 2011 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 ssh + +// direct-tcpip functional tests + +import ( + "net" + "net/http" + "testing" +) + +func TestTCPIPHTTP(t *testing.T) { + if *sshuser == "" { + t.Log("ssh.user not defined, skipping test") + return + } + // google.com will generate at least one redirect, possibly three + // depending on your location. + doTest(t, "http://google.com") +} + +func TestTCPIPHTTPS(t *testing.T) { + if *sshuser == "" { + t.Log("ssh.user not defined, skipping test") + return + } + doTest(t, "https://encrypted.google.com/") +} + +func doTest(t *testing.T, url string) { + config := &ClientConfig{ + User: *sshuser, + Auth: []ClientAuth{ + ClientAuthPassword(password(*sshpass)), + }, + } + conn, err := Dial("tcp", "localhost:22", config) + if err != nil { + t.Fatalf("Unable to connect: %s", err) + } + defer conn.Close() + tr := &http.Transport{ + Dial: func(n, addr string) (net.Conn, error) { + return conn.Dial(n, addr) + }, + } + client := &http.Client{ + Transport: tr, + } + resp, err := client.Get(url) + if err != nil { + t.Fatalf("unable to proxy: %s", err) + } + // got a body without error + t.Log(resp) +} diff --git a/libgo/go/exp/ssh/transport.go b/libgo/go/exp/ssh/transport.go index b8cb2c3..bcd073e 100644 --- a/libgo/go/exp/ssh/transport.go +++ b/libgo/go/exp/ssh/transport.go @@ -123,7 +123,7 @@ func (r *reader) readOnePacket() ([]byte, error) { if r.mac != nil { r.mac.Write(packet[:length-1]) - if subtle.ConstantTimeCompare(r.mac.Sum(), mac) != 1 { + if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 { return nil, errors.New("ssh: MAC failure") } } @@ -201,7 +201,7 @@ func (w *writer) writePacket(packet []byte) error { } if w.mac != nil { - if _, err := w.Write(w.mac.Sum()); err != nil { + if _, err := w.Write(w.mac.Sum(nil)); err != nil { return err } } @@ -297,7 +297,7 @@ func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) { h.Write(digestsSoFar) } - digest := h.Sum() + digest := h.Sum(nil) n := copy(out, digest) out = out[n:] if len(out) > 0 { @@ -317,9 +317,9 @@ func (t truncatingMAC) Write(data []byte) (int, error) { return t.hmac.Write(data) } -func (t truncatingMAC) Sum() []byte { - digest := t.hmac.Sum() - return digest[:t.length] +func (t truncatingMAC) Sum(in []byte) []byte { + out := t.hmac.Sum(in) + return out[:len(in)+t.length] } func (t truncatingMAC) Reset() { diff --git a/libgo/go/exp/types/check_test.go b/libgo/go/exp/types/check_test.go index 4a30acf..35535ea 100644 --- a/libgo/go/exp/types/check_test.go +++ b/libgo/go/exp/types/check_test.go @@ -202,7 +202,7 @@ func TestCheck(t *testing.T) { // For easy debugging w/o changing the testing code, // if there is a local test file, only test that file. const testfile = "test.go" - if fi, err := os.Stat(testfile); err == nil && fi.IsRegular() { + if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() { fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\n", testfile) check(t, testfile, []string{testfile}) return diff --git a/libgo/go/exp/types/gcimporter.go b/libgo/go/exp/types/gcimporter.go index 4167caf..16a8667 100644 --- a/libgo/go/exp/types/gcimporter.go +++ b/libgo/go/exp/types/gcimporter.go @@ -59,7 +59,7 @@ func findPkg(path string) (filename, id string) { // try extensions for _, ext := range pkgExts { filename = noext + ext - if f, err := os.Stat(filename); err == nil && f.IsRegular() { + if f, err := os.Stat(filename); err == nil && !f.IsDir() { return } } diff --git a/libgo/go/exp/types/gcimporter_test.go b/libgo/go/exp/types/gcimporter_test.go index 3f66d22..7475d35 100644 --- a/libgo/go/exp/types/gcimporter_test.go +++ b/libgo/go/exp/types/gcimporter_test.go @@ -58,32 +58,32 @@ func testPath(t *testing.T, path string) bool { return true } -const maxTime = 3e9 // maximum allotted testing time in ns +const maxTime = 3 * time.Second -func testDir(t *testing.T, dir string, endTime int64) (nimports int) { +func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { dirname := filepath.Join(pkgRoot, dir) list, err := ioutil.ReadDir(dirname) if err != nil { t.Errorf("testDir(%s): %s", dirname, err) } for _, f := range list { - if time.Nanoseconds() >= endTime { + if time.Now().After(endTime) { t.Log("testing time used up") return } switch { - case f.IsRegular(): + case !f.IsDir(): // try extensions for _, ext := range pkgExts { - if strings.HasSuffix(f.Name, ext) { - name := f.Name[0 : len(f.Name)-len(ext)] // remove extension + if strings.HasSuffix(f.Name(), ext) { + name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension if testPath(t, filepath.Join(dir, name)) { nimports++ } } } - case f.IsDirectory(): - nimports += testDir(t, filepath.Join(dir, f.Name), endTime) + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) } } return @@ -96,6 +96,6 @@ func TestGcImport(t *testing.T) { if testPath(t, "./testdata/exports") { nimports++ } - nimports += testDir(t, "", time.Nanoseconds()+maxTime) // installed packages + nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages t.Logf("tested %d imports", nimports) } diff --git a/libgo/go/fmt/fmt_test.go b/libgo/go/fmt/fmt_test.go index 6370560..00aac79 100644 --- a/libgo/go/fmt/fmt_test.go +++ b/libgo/go/fmt/fmt_test.go @@ -47,8 +47,10 @@ func TestFmtInterface(t *testing.T) { const b32 uint32 = 1<<32 - 1 const b64 uint64 = 1<<64 - 1 -var array = []int{1, 2, 3, 4, 5} -var iarray = []interface{}{1, "hello", 2.5, nil} +var array = [5]int{1, 2, 3, 4, 5} +var iarray = [4]interface{}{1, "hello", 2.5, nil} +var slice = array[:] +var islice = iarray[:] type A struct { i int @@ -327,6 +329,12 @@ var fmttests = []struct { {"%v", &array, "&[1 2 3 4 5]"}, {"%v", &iarray, "&[1 hello 2.5 <nil>]"}, + // slices + {"%v", slice, "[1 2 3 4 5]"}, + {"%v", islice, "[1 hello 2.5 <nil>]"}, + {"%v", &slice, "&[1 2 3 4 5]"}, + {"%v", &islice, "&[1 hello 2.5 <nil>]"}, + // complexes with %v {"%v", 1 + 2i, "(1+2i)"}, {"%v", complex64(1 + 2i), "(1+2i)"}, @@ -359,6 +367,10 @@ var fmttests = []struct { {"%#v", SI{}, `fmt_test.SI{I:interface {}(nil)}`}, {"%#v", []int(nil), `[]int(nil)`}, {"%#v", []int{}, `[]int{}`}, + {"%#v", array, `[5]int{1, 2, 3, 4, 5}`}, + {"%#v", &array, `&[5]int{1, 2, 3, 4, 5}`}, + {"%#v", iarray, `[4]interface {}{1, "hello", 2.5, interface {}(nil)}`}, + {"%#v", &iarray, `&[4]interface {}{1, "hello", 2.5, interface {}(nil)}`}, {"%#v", map[int]byte(nil), `map[int] uint8(nil)`}, {"%#v", map[int]byte{}, `map[int] uint8{}`}, diff --git a/libgo/go/fmt/print.go b/libgo/go/fmt/print.go index 7143e07..e5ca117 100644 --- a/libgo/go/fmt/print.go +++ b/libgo/go/fmt/print.go @@ -877,7 +877,7 @@ BigSwitch: } if goSyntax { p.buf.WriteString(value.Type().String()) - if f.IsNil() { + if f.Kind() == reflect.Slice && f.IsNil() { p.buf.WriteString("(nil)") break } diff --git a/libgo/go/go/ast/resolve.go b/libgo/go/go/ast/resolve.go index b24688d..c7c8e7c 100644 --- a/libgo/go/go/ast/resolve.go +++ b/libgo/go/go/ast/resolve.go @@ -113,7 +113,7 @@ func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, importErrors = true continue } - path, _ := strconv.Unquote(string(spec.Path.Value)) + path, _ := strconv.Unquote(spec.Path.Value) pkg, err := importer(imports, path) if err != nil { p.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) diff --git a/libgo/go/go/build/build.go b/libgo/go/go/build/build.go index e3de8d0..5301ab5 100644 --- a/libgo/go/go/build/build.go +++ b/libgo/go/go/build/build.go @@ -15,6 +15,7 @@ import ( "regexp" "runtime" "strings" + "time" ) // Build produces a build Script for the given package. @@ -150,7 +151,7 @@ func (s *Script) Run() error { // Stale returns true if the build's inputs are newer than its outputs. func (s *Script) Stale() bool { - var latest int64 + var latest time.Time // get latest mtime of outputs for _, file := range s.Output { fi, err := os.Stat(file) @@ -158,13 +159,13 @@ func (s *Script) Stale() bool { // any error reading output files means stale return true } - if m := fi.Mtime_ns; m > latest { - latest = m + if mtime := fi.ModTime(); mtime.After(latest) { + latest = mtime } } for _, file := range s.Input { fi, err := os.Stat(file) - if err != nil || fi.Mtime_ns > latest { + if err != nil || fi.ModTime().After(latest) { // any error reading input files means stale // (attempt to rebuild to figure out why) return true diff --git a/libgo/go/go/build/dir.go b/libgo/go/go/build/dir.go index 0d175c7..12dc999 100644 --- a/libgo/go/go/build/dir.go +++ b/libgo/go/go/build/dir.go @@ -38,16 +38,16 @@ type Context struct { // format of the strings dir and file: they can be // slash-separated, backslash-separated, even URLs. - // ReadDir returns a slice of *os.FileInfo, sorted by Name, + // ReadDir returns a slice of os.FileInfo, sorted by Name, // describing the content of the named directory. // The dir argument is the argument to ScanDir. // If ReadDir is nil, ScanDir uses io.ReadDir. - ReadDir func(dir string) (fi []*os.FileInfo, err error) + ReadDir func(dir string) (fi []os.FileInfo, err error) // ReadFile returns the content of the file named file // in the directory named dir. The dir argument is the // argument to ScanDir, and the file argument is the - // Name field from an *os.FileInfo returned by ReadDir. + // Name field from an os.FileInfo returned by ReadDir. // The returned path is the full name of the file, to be // used in error messages. // @@ -56,7 +56,7 @@ type Context struct { ReadFile func(dir, file string) (path string, content []byte, err error) } -func (ctxt *Context) readDir(dir string) ([]*os.FileInfo, error) { +func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) { if f := ctxt.ReadDir; f != nil { return f(dir) } @@ -140,18 +140,19 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { testImported := make(map[string]bool) fset := token.NewFileSet() for _, d := range dirs { - if !d.IsRegular() { + if d.IsDir() { continue } - if strings.HasPrefix(d.Name, "_") || - strings.HasPrefix(d.Name, ".") { + name := d.Name() + if strings.HasPrefix(name, "_") || + strings.HasPrefix(name, ".") { continue } - if !ctxt.goodOSArchFile(d.Name) { + if !ctxt.goodOSArchFile(name) { continue } - ext := path.Ext(d.Name) + ext := path.Ext(name) switch ext { case ".go", ".c", ".s": // tentatively okay @@ -161,7 +162,7 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { } // Look for +build comments to accept or reject the file. - filename, data, err := ctxt.readFile(dir, d.Name) + filename, data, err := ctxt.readFile(dir, name) if err != nil { return nil, err } @@ -172,10 +173,10 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { // Going to save the file. For non-Go files, can stop here. switch ext { case ".c": - di.CFiles = append(di.CFiles, d.Name) + di.CFiles = append(di.CFiles, name) continue case ".s": - di.SFiles = append(di.SFiles, d.Name) + di.SFiles = append(di.SFiles, name) continue } @@ -192,7 +193,7 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { continue } - isTest := strings.HasSuffix(d.Name, "_test.go") + isTest := strings.HasSuffix(name, "_test.go") if isTest && strings.HasSuffix(pkg, "_test") { pkg = pkg[:len(pkg)-len("_test")] } @@ -255,15 +256,15 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { } } if isCgo { - di.CgoFiles = append(di.CgoFiles, d.Name) + di.CgoFiles = append(di.CgoFiles, name) } else if isTest { if pkg == string(pf.Name.Name) { - di.TestGoFiles = append(di.TestGoFiles, d.Name) + di.TestGoFiles = append(di.TestGoFiles, name) } else { - di.XTestGoFiles = append(di.XTestGoFiles, d.Name) + di.XTestGoFiles = append(di.XTestGoFiles, name) } } else { - di.GoFiles = append(di.GoFiles, d.Name) + di.GoFiles = append(di.GoFiles, name) } } if di.Package == "" { diff --git a/libgo/go/go/build/path.go b/libgo/go/go/build/path.go index 7ccb129..91d6c43 100644 --- a/libgo/go/go/build/path.go +++ b/libgo/go/go/build/path.go @@ -70,7 +70,7 @@ func (t *Tree) HasSrc(pkg string) bool { if err != nil { return false } - return fi.IsDirectory() + return fi.IsDir() } // HasPkg returns whether the given package's @@ -80,7 +80,7 @@ func (t *Tree) HasPkg(pkg string) bool { if err != nil { return false } - return fi.IsRegular() + return !fi.IsDir() // TODO(adg): check object version is consistent } diff --git a/libgo/go/go/doc/comment.go b/libgo/go/go/doc/comment.go index 19216f8..d7bb384 100644 --- a/libgo/go/go/doc/comment.go +++ b/libgo/go/go/doc/comment.go @@ -7,11 +7,14 @@ package doc import ( + "bytes" "go/ast" "io" "regexp" "strings" "text/template" // for HTMLEscape + "unicode" + "unicode/utf8" ) func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' } @@ -168,6 +171,8 @@ var ( html_endp = []byte("</p>\n") html_pre = []byte("<pre>") html_endpre = []byte("</pre>\n") + html_h = []byte("<h3>") + html_endh = []byte("</h3>\n") ) // Emphasize and escape a line of text for HTML. URLs are converted into links; @@ -268,6 +273,51 @@ func unindent(block [][]byte) { } } +// heading returns the (possibly trimmed) line if it passes as a valid section +// heading; otherwise it returns nil. +func heading(line []byte) []byte { + line = bytes.TrimSpace(line) + if len(line) == 0 { + return nil + } + + // a heading must start with an uppercase letter + r, _ := utf8.DecodeRune(line) + if !unicode.IsLetter(r) || !unicode.IsUpper(r) { + return nil + } + + // it must end in a letter, digit or ':' + r, _ = utf8.DecodeLastRune(line) + if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != ':' { + return nil + } + + // strip trailing ':', if any + if r == ':' { + line = line[0 : len(line)-1] + } + + // exclude lines with illegal characters + if bytes.IndexAny(line, ",.;:!?+*/=()[]{}_^°&§~%#@<\">\\") >= 0 { + return nil + } + + // allow "'" for possessive "'s" only + for b := line; ; { + i := bytes.IndexRune(b, '\'') + if i < 0 { + break + } + if i+1 >= len(b) || b[i+1] != 's' || (i+2 < len(b) && b[i+2] != ' ') { + return nil // not followed by "s " + } + b = b[i+2:] + } + + return line +} + // Convert comment text to formatted HTML. // The comment was prepared by DocReader, // so it is known not to have leading, trailing blank lines @@ -276,6 +326,7 @@ func unindent(block [][]byte) { // // Turn each run of multiple \n into </p><p>. // Turn each run of indented lines into a <pre> block without indent. +// Enclose headings with header tags. // // URLs in the comment text are converted into links; if the URL also appears // in the words map, the link is taken from the map (if the corresponding map @@ -286,6 +337,8 @@ func unindent(block [][]byte) { // into a link. func ToHTML(w io.Writer, s []byte, words map[string]string) { inpara := false + lastWasBlank := false + lastWasHeading := false close := func() { if inpara { @@ -308,6 +361,7 @@ func ToHTML(w io.Writer, s []byte, words map[string]string) { // close paragraph close() i++ + lastWasBlank = true continue } if indentLen(line) > 0 { @@ -334,10 +388,30 @@ func ToHTML(w io.Writer, s []byte, words map[string]string) { emphasize(w, line, nil, false) // no nice text formatting } w.Write(html_endpre) + lastWasHeading = false continue } + + if lastWasBlank && !lastWasHeading && i+2 < len(lines) && + isBlank(lines[i+1]) && !isBlank(lines[i+2]) && indentLen(lines[i+2]) == 0 { + // current line is non-blank, sourounded by blank lines + // and the next non-blank line is not indented: this + // might be a heading. + if head := heading(line); head != nil { + close() + w.Write(html_h) + template.HTMLEscape(w, head) + w.Write(html_endh) + i += 2 + lastWasHeading = true + continue + } + } + // open paragraph open() + lastWasBlank = false + lastWasHeading = false emphasize(w, lines[i], words, true) // nice text formatting i++ } diff --git a/libgo/go/go/doc/comment_test.go b/libgo/go/go/doc/comment_test.go new file mode 100644 index 0000000..870660a --- /dev/null +++ b/libgo/go/go/doc/comment_test.go @@ -0,0 +1,39 @@ +// Copyright 2011 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 doc + +import ( + "testing" +) + +var headingTests = []struct { + line string + ok bool +}{ + {"Section", true}, + {"A typical usage", true}, + {"ΔΛΞ is Greek", true}, + {"Foo 42", true}, + {"", false}, + {"section", false}, + {"A typical usage:", true}, + {"δ is Greek", false}, + {"Foo §", false}, + {"Fermat's Last Sentence", true}, + {"Fermat's", true}, + {"'sX", false}, + {"Ted 'Too' Bar", false}, + {"Use n+m", false}, + {"Scanning:", true}, + {"N:M", false}, +} + +func TestIsHeading(t *testing.T) { + for _, tt := range headingTests { + if h := heading([]byte(tt.line)); (h != nil) != tt.ok { + t.Errorf("isHeading(%q) = %v, want %v", tt.line, h, tt.ok) + } + } +} diff --git a/libgo/go/go/doc/headscan.go b/libgo/go/go/doc/headscan.go new file mode 100644 index 0000000..83f2462 --- /dev/null +++ b/libgo/go/go/doc/headscan.go @@ -0,0 +1,111 @@ +// Copyright 2011 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. + +/* + The headscan command extracts comment headings from package files; + it is used to detect false positives which may require an adjustment + to the comment formatting heuristics in comment.go. + + Usage: headscan [-root root_directory] + + By default, the $GOROOT/src directory is scanned. +*/ +package main + +import ( + "bytes" + "flag" + "fmt" + "go/doc" + "go/parser" + "go/token" + "os" + "path/filepath" + "runtime" + "strings" +) + +var ( + root = flag.String("root", filepath.Join(runtime.GOROOT(), "src"), "root of filesystem tree to scan") + verbose = flag.Bool("v", false, "verbose mode") +) + +const ( + html_h = "<h3>" + html_endh = "</h3>\n" +) + +func isGoFile(fi os.FileInfo) bool { + return strings.HasSuffix(fi.Name(), ".go") && + !strings.HasSuffix(fi.Name(), "_test.go") +} + +func appendHeadings(list []string, comment string) []string { + var buf bytes.Buffer + doc.ToHTML(&buf, []byte(comment), nil) + for s := buf.String(); ; { + i := strings.Index(s, html_h) + if i < 0 { + break + } + i += len(html_h) + j := strings.Index(s, html_endh) + if j < 0 { + list = append(list, s[i:]) // incorrect HTML + break + } + list = append(list, s[i:j]) + s = s[j+len(html_endh):] + } + return list +} + +func main() { + flag.Parse() + fset := token.NewFileSet() + nheadings := 0 + err := filepath.Walk(*root, func(path string, fi os.FileInfo, err error) error { + if !fi.IsDir() { + return nil + } + pkgs, err := parser.ParseDir(fset, path, isGoFile, parser.ParseComments) + if err != nil { + if *verbose { + fmt.Fprintln(os.Stderr, err) + } + return nil + } + for _, pkg := range pkgs { + d := doc.NewPackageDoc(pkg, path) + list := appendHeadings(nil, d.Doc) + for _, d := range d.Consts { + list = appendHeadings(list, d.Doc) + } + for _, d := range d.Types { + list = appendHeadings(list, d.Doc) + } + for _, d := range d.Vars { + list = appendHeadings(list, d.Doc) + } + for _, d := range d.Funcs { + list = appendHeadings(list, d.Doc) + } + if len(list) > 0 { + // directories may contain multiple packages; + // print path and package name + fmt.Printf("%s (package %s)\n", path, pkg.Name) + for _, h := range list { + fmt.Printf("\t%s\n", h) + } + nheadings += len(list) + } + } + return nil + }) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println(nheadings, "headings found") +} diff --git a/libgo/go/go/parser/interface.go b/libgo/go/go/parser/interface.go index d3bab31..be11f46 100644 --- a/libgo/go/go/parser/interface.go +++ b/libgo/go/go/parser/interface.go @@ -188,7 +188,7 @@ func ParseFiles(fset *token.FileSet, filenames []string, mode uint) (pkgs map[st // returned. If a parse error occurred, a non-nil but incomplete map and the // error are returned. // -func ParseDir(fset *token.FileSet, path string, filter func(*os.FileInfo) bool, mode uint) (map[string]*ast.Package, error) { +func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, mode uint) (map[string]*ast.Package, error) { fd, err := os.Open(path) if err != nil { return nil, err @@ -202,10 +202,9 @@ func ParseDir(fset *token.FileSet, path string, filter func(*os.FileInfo) bool, filenames := make([]string, len(list)) n := 0 - for i := 0; i < len(list); i++ { - d := &list[i] + for _, d := range list { if filter == nil || filter(d) { - filenames[n] = filepath.Join(path, d.Name) + filenames[n] = filepath.Join(path, d.Name()) n++ } } diff --git a/libgo/go/go/parser/parser_test.go b/libgo/go/go/parser/parser_test.go index dee90fbc..f602db8 100644 --- a/libgo/go/go/parser/parser_test.go +++ b/libgo/go/go/parser/parser_test.go @@ -113,7 +113,7 @@ func nameFilter(filename string) bool { return true } -func dirFilter(f *os.FileInfo) bool { return nameFilter(f.Name) } +func dirFilter(f os.FileInfo) bool { return nameFilter(f.Name()) } func TestParse4(t *testing.T) { path := "." diff --git a/libgo/go/go/printer/nodes.go b/libgo/go/go/printer/nodes.go index 248e43d..53f3609 100644 --- a/libgo/go/go/printer/nodes.go +++ b/libgo/go/go/printer/nodes.go @@ -1377,7 +1377,7 @@ func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) { // in RawFormat cfg := Config{Mode: RawFormat} var buf bytes.Buffer - if _, err := cfg.fprint(&buf, p.fset, n, p.nodeSizes); err != nil { + if err := cfg.fprint(&buf, p.fset, n, p.nodeSizes); err != nil { return } if buf.Len() <= maxSize { diff --git a/libgo/go/go/printer/performance_test.go b/libgo/go/go/printer/performance_test.go index 84fb280..dbd9422 100644 --- a/libgo/go/go/printer/performance_test.go +++ b/libgo/go/go/printer/performance_test.go @@ -20,7 +20,7 @@ import ( var testfile *ast.File func testprint(out io.Writer, file *ast.File) { - if _, err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil { + if err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil { log.Fatalf("print error: %s", err) } } diff --git a/libgo/go/go/printer/printer.go b/libgo/go/go/printer/printer.go index 6104c32..f8c22f1 100644 --- a/libgo/go/go/printer/printer.go +++ b/libgo/go/go/printer/printer.go @@ -19,9 +19,9 @@ import ( ) const debug = false // enable for debugging +const infinity = 1 << 30 - -type whiteSpace int +type whiteSpace byte const ( ignore = whiteSpace(0) @@ -33,18 +33,6 @@ const ( unindent = whiteSpace('<') ) -var ( - esc = []byte{tabwriter.Escape} - htab = []byte{'\t'} - htabs = []byte("\t\t\t\t\t\t\t\t") - newlines = []byte("\n\n\n\n\n\n\n\n") // more than the max determined by nlines - formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than the max determined by nlines -) - -// Special positions -var noPos token.Position // use noPos when a position is needed but not known -var infinity = 1 << 30 - // Use ignoreMultiLine if the multiLine information is not important. var ignoreMultiLine = new(bool) @@ -52,31 +40,20 @@ var ignoreMultiLine = new(bool) type pmode int const ( - inLiteral pmode = 1 << iota - noExtraLinebreak + noExtraLinebreak pmode = 1 << iota ) -// local error wrapper so we can distinguish errors we want to return -// as errors from genuine panics (which we don't want to return as errors) -type osError struct { - err error -} - type printer struct { // Configuration (does not change after initialization) - output io.Writer Config fset *token.FileSet // Current state - written int // number of bytes written - indent int // current indentation - mode pmode // current printer mode - lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace) - - // Reused buffers - wsbuf []whiteSpace // delayed white space - litbuf bytes.Buffer // for creation of escaped literals and comments + output bytes.Buffer // raw printer result + indent int // current indentation + mode pmode // current printer mode + lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace) + wsbuf []whiteSpace // delayed white space // The (possibly estimated) position in the generated output; // in AST space (i.e., pos is set whenever a token position is @@ -97,8 +74,7 @@ type printer struct { nodeSizes map[ast.Node]int } -func (p *printer) init(output io.Writer, cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { - p.output = output +func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { p.Config = *cfg p.fset = fset p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short @@ -113,19 +89,6 @@ func (p *printer) internalError(msg ...interface{}) { } } -// escape escapes string s by bracketing it with tabwriter.Escape. -// Escaped strings pass through tabwriter unchanged. (Note that -// valid Go programs cannot contain tabwriter.Escape bytes since -// they do not appear in legal UTF-8 sequences). -// -func (p *printer) escape(s string) string { - p.litbuf.Reset() - p.litbuf.WriteByte(tabwriter.Escape) - p.litbuf.WriteString(s) - p.litbuf.WriteByte(tabwriter.Escape) - return p.litbuf.String() -} - // nlines returns the adjusted number of linebreaks given the desired number // of breaks n such that min <= result <= max. // @@ -140,82 +103,79 @@ func (p *printer) nlines(n, min int) int { return n } -// write0 writes raw (uninterpreted) data to p.output and handles errors. -// write0 does not indent after newlines, and does not HTML-escape or update p.pos. -// -func (p *printer) write0(data []byte) { - if len(data) > 0 { - n, err := p.output.Write(data) - p.written += n - if err != nil { - panic(osError{err}) - } +// writeByte writes a single byte to p.output and updates p.pos. +func (p *printer) writeByte(ch byte) { + p.output.WriteByte(ch) + p.pos.Offset++ + p.pos.Column++ + + if ch == '\n' || ch == '\f' { + // write indentation + // use "hard" htabs - indentation columns + // must not be discarded by the tabwriter + const htabs = "\t\t\t\t\t\t\t\t" + j := p.indent + for j > len(htabs) { + p.output.WriteString(htabs) + j -= len(htabs) + } + p.output.WriteString(htabs[0:j]) + + // update p.pos + p.pos.Line++ + p.pos.Offset += p.indent + p.pos.Column = 1 + p.indent } } -// write interprets data and writes it to p.output. It inserts indentation -// after a line break unless in a tabwriter escape sequence. -// It updates p.pos as a side-effect. +// writeNewlines writes up to n newlines to p.output and updates p.pos. +// The actual number of newlines written is limited by nlines. +// nl must be one of '\n' or '\f'. // -func (p *printer) write(data []byte) { - i0 := 0 - for i, b := range data { - switch b { - case '\n', '\f': - // write segment ending in b - p.write0(data[i0 : i+1]) - - // update p.pos - p.pos.Offset += i + 1 - i0 - p.pos.Line++ - p.pos.Column = 1 - - if p.mode&inLiteral == 0 { - // write indentation - // use "hard" htabs - indentation columns - // must not be discarded by the tabwriter - j := p.indent - for ; j > len(htabs); j -= len(htabs) { - p.write0(htabs) - } - p.write0(htabs[0:j]) - - // update p.pos - p.pos.Offset += p.indent - p.pos.Column += p.indent - } - - // next segment start - i0 = i + 1 - - case tabwriter.Escape: - p.mode ^= inLiteral +func (p *printer) writeNewlines(n int, nl byte) { + for n = p.nlines(n, 0); n > 0; n-- { + p.writeByte(nl) + } +} - // ignore escape chars introduced by printer - they are - // invisible and must not affect p.pos (was issue #1089) - p.pos.Offset-- - p.pos.Column-- - } +// writeString writes the string s to p.output and updates p.pos. +// If isLit is set, s is escaped w/ tabwriter.Escape characters +// to protect s from being interpreted by the tabwriter. +// +// Note: writeString is only used to write Go tokens, literals, and +// comments, all of which must be written literally. Thus, it is correct +// to always set isLit = true. However, setting it explicitly only when +// needed (i.e., when we don't know that s contains no tabs or line breaks) +// avoids processing extra escape characters and reduces run time of the +// printer benchmark by up to 10%. +// +func (p *printer) writeString(s string, isLit bool) { + if isLit { + // Protect s such that is passes through the tabwriter + // unchanged. Note that valid Go programs cannot contain + // tabwriter.Escape bytes since they do not appear in legal + // UTF-8 sequences. + p.output.WriteByte(tabwriter.Escape) } - // write remaining segment - p.write0(data[i0:]) + p.output.WriteString(s) // update p.pos - d := len(data) - i0 - p.pos.Offset += d - p.pos.Column += d -} - -func (p *printer) writeNewlines(n int, useFF bool) { - if n > 0 { - n = p.nlines(n, 0) - if useFF { - p.write(formfeeds[0:n]) - } else { - p.write(newlines[0:n]) + nlines := 0 + column := p.pos.Column + len(s) + for i := 0; i < len(s); i++ { + if s[i] == '\n' { + nlines++ + column = len(s) - i } } + p.pos.Offset += len(s) + p.pos.Line += nlines + p.pos.Column = column + + if isLit { + p.output.WriteByte(tabwriter.Escape) + } } // writeItem writes data at position pos. data is the text corresponding to @@ -224,7 +184,7 @@ func (p *printer) writeNewlines(n int, useFF bool) { // source text. writeItem updates p.last to the position immediately following // the data. // -func (p *printer) writeItem(pos token.Position, data string) { +func (p *printer) writeItem(pos token.Position, data string, isLit bool) { if pos.IsValid() { // continue with previous position if we don't have a valid pos if p.last.IsValid() && p.last.Filename != pos.Filename { @@ -240,9 +200,9 @@ func (p *printer) writeItem(pos token.Position, data string) { if debug { // do not update p.pos - use write0 _, filename := filepath.Split(pos.Filename) - p.write0([]byte(fmt.Sprintf("[%s:%d:%d]", filename, pos.Line, pos.Column))) + fmt.Fprintf(&p.output, "[%s:%d:%d]", filename, pos.Line, pos.Column) } - p.write([]byte(data)) + p.writeString(data, isLit) p.last = p.pos } @@ -257,14 +217,14 @@ const linePrefix = "//line " // next item is a keyword. // func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, isKeyword bool) { - if p.written == 0 { + if p.output.Len() == 0 { // the comment is the first item to be printed - don't write any whitespace return } if pos.IsValid() && pos.Filename != p.last.Filename { // comment in a different file - separate with newlines (writeNewlines will limit the number) - p.writeNewlines(10, true) + p.writeNewlines(10, '\f') return } @@ -297,14 +257,14 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as } // make sure there is at least one separator if !hasSep { + sep := byte('\t') if pos.Line == next.Line { // next item is on the same line as the comment // (which must be a /*-style comment): separate // with a blank instead of a tab - p.write([]byte{' '}) - } else { - p.write(htab) + sep = ' ' } + p.writeByte(sep) } } else { @@ -357,30 +317,31 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as if n <= 0 && prev != nil && prev.Text[1] == '/' { n = 1 } - p.writeNewlines(n, true) + if n > 0 { + p.writeNewlines(n, '\f') + } p.indent = indent } } -// TODO(gri): It should be possible to convert the code below from using -// []byte to string and in the process eliminate some conversions. - // Split comment text into lines -func split(text []byte) [][]byte { +// (using strings.Split(text, "\n") is significantly slower for +// this specific purpose, as measured with: gotest -bench=Print) +func split(text string) []string { // count lines (comment text never ends in a newline) n := 1 - for _, c := range text { - if c == '\n' { + for i := 0; i < len(text); i++ { + if text[i] == '\n' { n++ } } // split - lines := make([][]byte, n) + lines := make([]string, n) n = 0 i := 0 - for j, c := range text { - if c == '\n' { + for j := 0; j < len(text); j++ { + if text[j] == '\n' { lines[n] = text[i:j] // exclude newline i = j + 1 // discard newline n++ @@ -391,16 +352,18 @@ func split(text []byte) [][]byte { return lines } -func isBlank(s []byte) bool { - for _, b := range s { - if b > ' ' { +// Returns true if s contains only white space +// (only tabs and blanks can appear in the printer's context). +func isBlank(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] > ' ' { return false } } return true } -func commonPrefix(a, b []byte) []byte { +func commonPrefix(a, b string) string { i := 0 for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { i++ @@ -408,7 +371,7 @@ func commonPrefix(a, b []byte) []byte { return a[0:i] } -func stripCommonPrefix(lines [][]byte) { +func stripCommonPrefix(lines []string) { if len(lines) < 2 { return // at most one line - nothing to do } @@ -432,19 +395,21 @@ func stripCommonPrefix(lines [][]byte) { // Note that the first and last line are never empty (they // contain the opening /* and closing */ respectively) and // thus they can be ignored by the blank line check. - var prefix []byte + var prefix string if len(lines) > 2 { + first := true for i, line := range lines[1 : len(lines)-1] { switch { case isBlank(line): - lines[1+i] = nil // range starts at line 1 - case prefix == nil: + lines[1+i] = "" // range starts at line 1 + case first: prefix = commonPrefix(line, line) + first = false default: prefix = commonPrefix(prefix, line) } } - } else { // len(lines) == 2 + } else { // len(lines) == 2, lines cannot be blank (contain /* and */) line := lines[1] prefix = commonPrefix(line, line) } @@ -453,7 +418,7 @@ func stripCommonPrefix(lines [][]byte) { * Check for vertical "line of stars" and correct prefix accordingly. */ lineOfStars := false - if i := bytes.Index(prefix, []byte{'*'}); i >= 0 { + if i := strings.Index(prefix, "*"); i >= 0 { // Line of stars present. if i > 0 && prefix[i-1] == ' ' { i-- // remove trailing blank from prefix so stars remain aligned @@ -501,7 +466,7 @@ func stripCommonPrefix(lines [][]byte) { } // Shorten the computed common prefix by the length of // suffix, if it is found as suffix of the prefix. - if bytes.HasSuffix(prefix, suffix) { + if strings.HasSuffix(prefix, string(suffix)) { prefix = prefix[0 : len(prefix)-len(suffix)] } } @@ -511,19 +476,18 @@ func stripCommonPrefix(lines [][]byte) { // with the opening /*, otherwise align the text with the other // lines. last := lines[len(lines)-1] - closing := []byte("*/") - i := bytes.Index(last, closing) + closing := "*/" + i := strings.Index(last, closing) // i >= 0 (closing is always present) if isBlank(last[0:i]) { // last line only contains closing */ - var sep []byte if lineOfStars { - // insert an aligning blank - sep = []byte{' '} + closing = " */" // add blank to align final star } - lines[len(lines)-1] = bytes.Join([][]byte{prefix, closing}, sep) + lines[len(lines)-1] = prefix + closing } else { // last line contains more comment text - assume - // it is aligned like the other lines + // it is aligned like the other lines and include + // in prefix computation prefix = commonPrefix(prefix, last) } @@ -549,9 +513,9 @@ func (p *printer) writeComment(comment *ast.Comment) { // update our own idea of the file and line number // accordingly, after printing the directive. file := pos[:i] - line, _ := strconv.Atoi(string(pos[i+1:])) + line, _ := strconv.Atoi(pos[i+1:]) defer func() { - p.pos.Filename = string(file) + p.pos.Filename = file p.pos.Line = line p.pos.Column = 1 }() @@ -560,26 +524,25 @@ func (p *printer) writeComment(comment *ast.Comment) { // shortcut common case of //-style comments if text[1] == '/' { - p.writeItem(p.fset.Position(comment.Pos()), p.escape(text)) + p.writeItem(p.fset.Position(comment.Pos()), text, true) return } // for /*-style comments, print line by line and let the // write function take care of the proper indentation - lines := split([]byte(text)) + lines := split(text) stripCommonPrefix(lines) // write comment lines, separated by formfeed, // without a line break after the last line - linebreak := formfeeds[0:1] pos := p.fset.Position(comment.Pos()) for i, line := range lines { if i > 0 { - p.write(linebreak) + p.writeByte('\f') pos = p.pos } if len(line) > 0 { - p.writeItem(pos, p.escape(string(line))) + p.writeItem(pos, line, true) } } } @@ -615,7 +578,7 @@ func (p *printer) writeCommentSuffix(needsLinebreak bool) (droppedFF bool) { // make sure we have a line break if needsLinebreak { - p.write([]byte{'\n'}) + p.writeByte('\n') } return @@ -641,7 +604,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (dro if last.Text[1] == '*' && p.fset.Position(last.Pos()).Line == next.Line { // the last comment is a /*-style comment and the next item // follows on the same line: separate with an extra blank - p.write([]byte{' '}) + p.writeByte(' ') } // ensure that there is a line break after a //-style comment, // before a closing '}' unless explicitly disabled, or at eof @@ -661,7 +624,6 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (dro // whiteWhitespace writes the first n whitespace entries. func (p *printer) writeWhitespace(n int) { // write entries - var data [1]byte for i := 0; i < n; i++ { switch ch := p.wsbuf[i]; ch { case ignore: @@ -693,8 +655,7 @@ func (p *printer) writeWhitespace(n int) { } fallthrough default: - data[0] = byte(ch) - p.write(data[0:]) + p.writeByte(byte(ch)) } } @@ -710,7 +671,6 @@ func (p *printer) writeWhitespace(n int) { // ---------------------------------------------------------------------------- // Printing interface - func mayCombine(prev token.Token, next byte) (b bool) { switch prev { case token.INT: @@ -743,7 +703,8 @@ func mayCombine(prev token.Token, next byte) (b bool) { func (p *printer) print(args ...interface{}) { for _, f := range args { next := p.pos // estimated position of next item - var data string + data := "" + isLit := false var tok token.Token switch x := f.(type) { @@ -771,7 +732,8 @@ func (p *printer) print(args ...interface{}) { data = x.Name tok = token.IDENT case *ast.BasicLit: - data = p.escape(x.Value) + data = x.Value + isLit = true tok = x.Kind case token.Token: s := x.String() @@ -803,15 +765,20 @@ func (p *printer) print(args ...interface{}) { p.pos = next if data != "" { - droppedFF := p.flush(next, tok) + nl := byte('\n') + if p.flush(next, tok) { + nl = '\f' // dropped formfeed before + } // intersperse extra newlines if present in the source // (don't do this in flush as it will cause extra newlines // at the end of a file) - use formfeeds if we dropped one // before - p.writeNewlines(next.Line-p.pos.Line, droppedFF) + if n := next.Line - p.pos.Line; n > 0 { + p.writeNewlines(n, nl) + } - p.writeItem(next, data) + p.writeItem(next, data, isLit) } } } @@ -840,6 +807,35 @@ func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) { return } +func (p *printer) printNode(node interface{}) error { + switch n := node.(type) { + case ast.Expr: + p.useNodeComments = true + p.expr(n, ignoreMultiLine) + case ast.Stmt: + p.useNodeComments = true + // A labeled statement will un-indent to position the + // label. Set indent to 1 so we don't get indent "underflow". + if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt { + p.indent = 1 + } + p.stmt(n, false, ignoreMultiLine) + case ast.Decl: + p.useNodeComments = true + p.decl(n, ignoreMultiLine) + case ast.Spec: + p.useNodeComments = true + p.spec(n, 1, false, ignoreMultiLine) + case *ast.File: + p.comments = n.Comments + p.useNodeComments = n.Comments == nil + p.file(n) + default: + return fmt.Errorf("go/printer: unsupported node type %T", n) + } + return nil +} + // ---------------------------------------------------------------------------- // Trimmer @@ -869,6 +865,8 @@ const ( // However, this would mess up any formatting done by // the tabwriter. +var aNewline = []byte("\n") + func (p *trimmer) Write(data []byte) (n int, err error) { // invariants: // p.state == inSpace: @@ -887,8 +885,8 @@ func (p *trimmer) Write(data []byte) (n int, err error) { case '\t', ' ': p.space.WriteByte(b) // WriteByte returns no errors case '\n', '\f': - p.space.Reset() // discard trailing space - _, err = p.output.Write(newlines[0:1]) // write newline + p.space.Reset() // discard trailing space + _, err = p.output.Write(aNewline) case tabwriter.Escape: _, err = p.output.Write(p.space.Bytes()) p.state = inEscape @@ -915,7 +913,7 @@ func (p *trimmer) Write(data []byte) (n int, err error) { _, err = p.output.Write(data[m:n]) p.state = inSpace p.space.Reset() - _, err = p.output.Write(newlines[0:1]) // write newline + _, err = p.output.Write(aNewline) case tabwriter.Escape: _, err = p.output.Write(data[m:n]) p.state = inEscape @@ -957,15 +955,22 @@ type Config struct { } // fprint implements Fprint and takes a nodesSizes map for setting up the printer state. -func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (written int, err error) { +func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) { + // print node + var p printer + p.init(cfg, fset, nodeSizes) + if err = p.printNode(node); err != nil { + return + } + p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) + // redirect output through a trimmer to eliminate trailing whitespace // (Input to a tabwriter must be untrimmed since trailing tabs provide // formatting information. The tabwriter could provide trimming // functionality but no tabwriter is used when RawFormat is set.) output = &trimmer{output: output} - // setup tabwriter if needed and redirect output - var tw *tabwriter.Writer + // redirect output through a tabwriter if necessary if cfg.Mode&RawFormat == 0 { minwidth := cfg.Tabwidth @@ -980,63 +985,28 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{ twmode |= tabwriter.TabIndent } - tw = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) - output = tw + output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) } - // setup printer - var p printer - p.init(output, cfg, fset, nodeSizes) - defer func() { - written = p.written - if e := recover(); e != nil { - err = e.(osError).err // re-panics if it's not a local osError - } - }() - - // print node - switch n := node.(type) { - case ast.Expr: - p.useNodeComments = true - p.expr(n, ignoreMultiLine) - case ast.Stmt: - p.useNodeComments = true - // A labeled statement will un-indent to position the - // label. Set indent to 1 so we don't get indent "underflow". - if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt { - p.indent = 1 - } - p.stmt(n, false, ignoreMultiLine) - case ast.Decl: - p.useNodeComments = true - p.decl(n, ignoreMultiLine) - case ast.Spec: - p.useNodeComments = true - p.spec(n, 1, false, ignoreMultiLine) - case *ast.File: - p.comments = n.Comments - p.useNodeComments = n.Comments == nil - p.file(n) - default: - panic(osError{fmt.Errorf("printer.Fprint: unsupported node type %T", n)}) + // write printer result via tabwriter/trimmer to output + if _, err = output.Write(p.output.Bytes()); err != nil { + return } - p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) // flush tabwriter, if any - if tw != nil { - tw.Flush() // ignore errors + if tw, _ := (output).(*tabwriter.Writer); tw != nil { + err = tw.Flush() } return } -// Fprint "pretty-prints" an AST node to output and returns the number -// of bytes written and an error (if any) for a given configuration cfg. +// Fprint "pretty-prints" an AST node to output for a given configuration cfg. // Position information is interpreted relative to the file set fset. // The node type must be *ast.File, or assignment-compatible to ast.Expr, // ast.Decl, ast.Spec, or ast.Stmt. // -func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, error) { +func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { return cfg.fprint(output, fset, node, make(map[ast.Node]int)) } @@ -1044,6 +1014,5 @@ func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{ // It calls Config.Fprint with default settings. // func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { - _, err := (&Config{Tabwidth: 8}).Fprint(output, fset, node) // don't care about number of bytes written - return err + return (&Config{Tabwidth: 8}).Fprint(output, fset, node) } diff --git a/libgo/go/go/printer/printer_test.go b/libgo/go/go/printer/printer_test.go index a644aa3..924d4df 100644 --- a/libgo/go/go/printer/printer_test.go +++ b/libgo/go/go/printer/printer_test.go @@ -62,7 +62,7 @@ func runcheck(t *testing.T, source, golden string, mode checkMode) { // format source var buf bytes.Buffer - if _, err := cfg.Fprint(&buf, fset, prog); err != nil { + if err := cfg.Fprint(&buf, fset, prog); err != nil { t.Error(err) } res := buf.Bytes() diff --git a/libgo/go/hash/adler32/adler32.go b/libgo/go/hash/adler32/adler32.go index 10bed2f..8103a89 100644 --- a/libgo/go/hash/adler32/adler32.go +++ b/libgo/go/hash/adler32/adler32.go @@ -71,14 +71,13 @@ func (d *digest) Write(p []byte) (nn int, err error) { func (d *digest) Sum32() uint32 { return finish(d.a, d.b) } -func (d *digest) Sum() []byte { - p := make([]byte, 4) +func (d *digest) Sum(in []byte) []byte { s := d.Sum32() - p[0] = byte(s >> 24) - p[1] = byte(s >> 16) - p[2] = byte(s >> 8) - p[3] = byte(s) - return p + in = append(in, byte(s>>24)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>8)) + in = append(in, byte(s)) + return in } // Checksum returns the Adler-32 checksum of data. diff --git a/libgo/go/hash/crc32/crc32.go b/libgo/go/hash/crc32/crc32.go index 5980ec0..557fab8 100644 --- a/libgo/go/hash/crc32/crc32.go +++ b/libgo/go/hash/crc32/crc32.go @@ -119,14 +119,13 @@ func (d *digest) Write(p []byte) (n int, err error) { func (d *digest) Sum32() uint32 { return d.crc } -func (d *digest) Sum() []byte { - p := make([]byte, 4) +func (d *digest) Sum(in []byte) []byte { s := d.Sum32() - p[0] = byte(s >> 24) - p[1] = byte(s >> 16) - p[2] = byte(s >> 8) - p[3] = byte(s) - return p + in = append(in, byte(s>>24)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>8)) + in = append(in, byte(s)) + return in } // Checksum returns the CRC-32 checksum of data diff --git a/libgo/go/hash/crc64/crc64.go b/libgo/go/hash/crc64/crc64.go index 42e53c3..e5c1db4 100644 --- a/libgo/go/hash/crc64/crc64.go +++ b/libgo/go/hash/crc64/crc64.go @@ -75,18 +75,17 @@ func (d *digest) Write(p []byte) (n int, err error) { func (d *digest) Sum64() uint64 { return d.crc } -func (d *digest) Sum() []byte { - p := make([]byte, 8) +func (d *digest) Sum(in []byte) []byte { s := d.Sum64() - p[0] = byte(s >> 56) - p[1] = byte(s >> 48) - p[2] = byte(s >> 40) - p[3] = byte(s >> 32) - p[4] = byte(s >> 24) - p[5] = byte(s >> 16) - p[6] = byte(s >> 8) - p[7] = byte(s) - return p + in = append(in, byte(s>>56)) + in = append(in, byte(s>>48)) + in = append(in, byte(s>>40)) + in = append(in, byte(s>>32)) + in = append(in, byte(s>>24)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>8)) + in = append(in, byte(s)) + return in } // Checksum returns the CRC-64 checksum of data diff --git a/libgo/go/hash/fnv/fnv.go b/libgo/go/hash/fnv/fnv.go index ce3ed0d..2c8a251 100644 --- a/libgo/go/hash/fnv/fnv.go +++ b/libgo/go/hash/fnv/fnv.go @@ -8,7 +8,6 @@ package fnv import ( - "encoding/binary" "hash" ) @@ -105,26 +104,46 @@ func (s *sum32a) Size() int { return 4 } func (s *sum64) Size() int { return 8 } func (s *sum64a) Size() int { return 8 } -func (s *sum32) Sum() []byte { - a := make([]byte, 4) - binary.BigEndian.PutUint32(a, uint32(*s)) - return a +func (s *sum32) Sum(in []byte) []byte { + v := uint32(*s) + in = append(in, byte(v>>24)) + in = append(in, byte(v>>16)) + in = append(in, byte(v>>8)) + in = append(in, byte(v)) + return in } -func (s *sum32a) Sum() []byte { - a := make([]byte, 4) - binary.BigEndian.PutUint32(a, uint32(*s)) - return a +func (s *sum32a) Sum(in []byte) []byte { + v := uint32(*s) + in = append(in, byte(v>>24)) + in = append(in, byte(v>>16)) + in = append(in, byte(v>>8)) + in = append(in, byte(v)) + return in } -func (s *sum64) Sum() []byte { - a := make([]byte, 8) - binary.BigEndian.PutUint64(a, uint64(*s)) - return a +func (s *sum64) Sum(in []byte) []byte { + v := uint64(*s) + in = append(in, byte(v>>56)) + in = append(in, byte(v>>48)) + in = append(in, byte(v>>40)) + in = append(in, byte(v>>32)) + in = append(in, byte(v>>24)) + in = append(in, byte(v>>16)) + in = append(in, byte(v>>8)) + in = append(in, byte(v)) + return in } -func (s *sum64a) Sum() []byte { - a := make([]byte, 8) - binary.BigEndian.PutUint64(a, uint64(*s)) - return a +func (s *sum64a) Sum(in []byte) []byte { + v := uint64(*s) + in = append(in, byte(v>>56)) + in = append(in, byte(v>>48)) + in = append(in, byte(v>>40)) + in = append(in, byte(v>>32)) + in = append(in, byte(v>>24)) + in = append(in, byte(v>>16)) + in = append(in, byte(v>>8)) + in = append(in, byte(v)) + return in } diff --git a/libgo/go/hash/fnv/fnv_test.go b/libgo/go/hash/fnv/fnv_test.go index 429230c..17454de 100644 --- a/libgo/go/hash/fnv/fnv_test.go +++ b/libgo/go/hash/fnv/fnv_test.go @@ -72,7 +72,7 @@ func testGolden(t *testing.T, hash hash.Hash, gold []golden) { if done != len(g.text) { t.Fatalf("wrote only %d out of %d bytes", done, len(g.text)) } - if actual := hash.Sum(); !bytes.Equal(g.sum, actual) { + if actual := hash.Sum(nil); !bytes.Equal(g.sum, actual) { t.Errorf("hash(%q) = 0x%x want 0x%x", g.text, actual, g.sum) } } @@ -97,26 +97,26 @@ func TestIntegrity64a(t *testing.T) { func testIntegrity(t *testing.T, h hash.Hash) { data := []byte{'1', '2', 3, 4, 5} h.Write(data) - sum := h.Sum() + sum := h.Sum(nil) if size := h.Size(); size != len(sum) { t.Fatalf("Size()=%d but len(Sum())=%d", size, len(sum)) } - if a := h.Sum(); !bytes.Equal(sum, a) { + if a := h.Sum(nil); !bytes.Equal(sum, a) { t.Fatalf("first Sum()=0x%x, second Sum()=0x%x", sum, a) } h.Reset() h.Write(data) - if a := h.Sum(); !bytes.Equal(sum, a) { + if a := h.Sum(nil); !bytes.Equal(sum, a) { t.Fatalf("Sum()=0x%x, but after Reset() Sum()=0x%x", sum, a) } h.Reset() h.Write(data[:2]) h.Write(data[2:]) - if a := h.Sum(); !bytes.Equal(sum, a) { + if a := h.Sum(nil); !bytes.Equal(sum, a) { t.Fatalf("Sum()=0x%x, but with partial writes, Sum()=0x%x", sum, a) } @@ -162,6 +162,6 @@ func benchmark(b *testing.B, h hash.Hash) { for todo := b.N; todo != 0; todo-- { h.Reset() h.Write(data) - h.Sum() + h.Sum(nil) } } diff --git a/libgo/go/hash/hash.go b/libgo/go/hash/hash.go index 3536c0b..0d7765d 100644 --- a/libgo/go/hash/hash.go +++ b/libgo/go/hash/hash.go @@ -13,9 +13,9 @@ type Hash interface { // It never returns an error. io.Writer - // Sum returns the current hash, without changing the - // underlying hash state. - Sum() []byte + // Sum appends the current hash in the same manner as append(), without + // changing the underlying hash state. + Sum(in []byte) []byte // Reset resets the hash to one with zero bytes written. Reset() diff --git a/libgo/go/html/doctype.go b/libgo/go/html/doctype.go new file mode 100644 index 0000000..f692061 --- /dev/null +++ b/libgo/go/html/doctype.go @@ -0,0 +1,156 @@ +// Copyright 2011 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 html + +import ( + "strings" +) + +// parseDoctype parses the data from a DoctypeToken into a name, +// public identifier, and system identifier. It returns a Node whose Type +// is DoctypeNode, whose Data is the name, and which has attributes +// named "system" and "public" for the two identifiers if they were present. +// quirks is whether the document should be parsed in "quirks mode". +func parseDoctype(s string) (n *Node, quirks bool) { + n = &Node{Type: DoctypeNode} + + // Find the name. + space := strings.IndexAny(s, whitespace) + if space == -1 { + space = len(s) + } + n.Data = s[:space] + // The comparison to "html" is case-sensitive. + if n.Data != "html" { + quirks = true + } + n.Data = strings.ToLower(n.Data) + s = strings.TrimLeft(s[space:], whitespace) + + if len(s) < 6 { + // It can't start with "PUBLIC" or "SYSTEM". + // Ignore the rest of the string. + return n, quirks || s != "" + } + + key := strings.ToLower(s[:6]) + s = s[6:] + for key == "public" || key == "system" { + s = strings.TrimLeft(s, whitespace) + if s == "" { + break + } + quote := s[0] + if quote != '"' && quote != '\'' { + break + } + s = s[1:] + q := strings.IndexRune(s, rune(quote)) + var id string + if q == -1 { + id = s + s = "" + } else { + id = s[:q] + s = s[q+1:] + } + n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) + if key == "public" { + key = "system" + } else { + key = "" + } + } + + if key != "" || s != "" { + quirks = true + } else if len(n.Attr) > 0 { + if n.Attr[0].Key == "public" { + public := strings.ToLower(n.Attr[0].Val) + switch public { + case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": + quirks = true + default: + for _, q := range quirkyIDs { + if strings.HasPrefix(public, q) { + quirks = true + break + } + } + } + // The following two public IDs only cause quirks mode if there is no system ID. + if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || + strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { + quirks = true + } + } + if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && + strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { + quirks = true + } + } + + return n, quirks +} + +// quirkyIDs is a list of public doctype identifiers that cause a document +// to be interpreted in quirks mode. The identifiers should be in lower case. +var quirkyIDs = []string{ + "+//silmaril//dtd html pro v0r11 19970101//", + "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", + "-//as//dtd html 3.0 aswedit + extensions//", + "-//ietf//dtd html 2.0 level 1//", + "-//ietf//dtd html 2.0 level 2//", + "-//ietf//dtd html 2.0 strict level 1//", + "-//ietf//dtd html 2.0 strict level 2//", + "-//ietf//dtd html 2.0 strict//", + "-//ietf//dtd html 2.0//", + "-//ietf//dtd html 2.1e//", + "-//ietf//dtd html 3.0//", + "-//ietf//dtd html 3.2 final//", + "-//ietf//dtd html 3.2//", + "-//ietf//dtd html 3//", + "-//ietf//dtd html level 0//", + "-//ietf//dtd html level 1//", + "-//ietf//dtd html level 2//", + "-//ietf//dtd html level 3//", + "-//ietf//dtd html strict level 0//", + "-//ietf//dtd html strict level 1//", + "-//ietf//dtd html strict level 2//", + "-//ietf//dtd html strict level 3//", + "-//ietf//dtd html strict//", + "-//ietf//dtd html//", + "-//metrius//dtd metrius presentational//", + "-//microsoft//dtd internet explorer 2.0 html strict//", + "-//microsoft//dtd internet explorer 2.0 html//", + "-//microsoft//dtd internet explorer 2.0 tables//", + "-//microsoft//dtd internet explorer 3.0 html strict//", + "-//microsoft//dtd internet explorer 3.0 html//", + "-//microsoft//dtd internet explorer 3.0 tables//", + "-//netscape comm. corp.//dtd html//", + "-//netscape comm. corp.//dtd strict html//", + "-//o'reilly and associates//dtd html 2.0//", + "-//o'reilly and associates//dtd html extended 1.0//", + "-//o'reilly and associates//dtd html extended relaxed 1.0//", + "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", + "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", + "-//spyglass//dtd html 2.0 extended//", + "-//sq//dtd html 2.0 hotmetal + extensions//", + "-//sun microsystems corp.//dtd hotjava html//", + "-//sun microsystems corp.//dtd hotjava strict html//", + "-//w3c//dtd html 3 1995-03-24//", + "-//w3c//dtd html 3.2 draft//", + "-//w3c//dtd html 3.2 final//", + "-//w3c//dtd html 3.2//", + "-//w3c//dtd html 3.2s draft//", + "-//w3c//dtd html 4.0 frameset//", + "-//w3c//dtd html 4.0 transitional//", + "-//w3c//dtd html experimental 19960712//", + "-//w3c//dtd html experimental 970421//", + "-//w3c//dtd w3 html//", + "-//w3o//dtd w3 html 3.0//", + "-//webtechs//dtd mozilla html 2.0//", + "-//webtechs//dtd mozilla html//", +} diff --git a/libgo/go/html/parse.go b/libgo/go/html/parse.go index 9b7e934..97fbc51 100644 --- a/libgo/go/html/parse.go +++ b/libgo/go/html/parse.go @@ -37,6 +37,11 @@ type parser struct { // fosterParenting is whether new elements should be inserted according to // the foster parenting rules (section 11.2.5.3). fosterParenting bool + // quirks is whether the parser is operating in "quirks mode." + quirks bool + // context is the context element when parsing an HTML fragment + // (section 11.4). + context *Node } func (p *parser) top() *Node { @@ -285,9 +290,10 @@ func (p *parser) setOriginalIM() { func (p *parser) resetInsertionMode() { for i := len(p.oe) - 1; i >= 0; i-- { n := p.oe[i] - if i == 0 { - // TODO: set n to the context element, for HTML fragment parsing. + if i == 0 && p.context != nil { + n = p.context } + switch n.Data { case "select": p.im = inSelectIM @@ -319,9 +325,17 @@ func (p *parser) resetInsertionMode() { p.im = inBodyIM } +const whitespace = " \t\r\n\f" + // Section 11.2.5.4.1. func initialIM(p *parser) bool { switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } case CommentToken: p.doc.Add(&Node{ Type: CommentNode, @@ -329,15 +343,13 @@ func initialIM(p *parser) bool { }) return true case DoctypeToken: - p.doc.Add(&Node{ - Type: DoctypeNode, - Data: p.tok.Data, - }) + n, quirks := parseDoctype(p.tok.Data) + p.doc.Add(n) + p.quirks = quirks p.im = beforeHTMLIM return true } - // TODO: set "quirks mode"? It's defined in the DOM spec instead of HTML5 proper, - // and so switching on "quirks mode" might belong in a different package. + p.quirks = true p.im = beforeHTMLIM return false } @@ -345,6 +357,12 @@ func initialIM(p *parser) bool { // Section 11.2.5.4.2. func beforeHTMLIM(p *parser) bool { switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } case StartTagToken: if p.tok.Data == "html" { p.addElement(p.tok.Data, p.tok.Attr) @@ -383,7 +401,11 @@ func beforeHeadIM(p *parser) bool { case ErrorToken: implied = true case TextToken: - // TODO: distinguish whitespace text from others. + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } implied = true case StartTagToken: switch p.tok.Data { @@ -417,8 +439,6 @@ func beforeHeadIM(p *parser) bool { return !implied } -const whitespace = " \t\r\n\f" - // Section 11.2.5.4.4. func inHeadIM(p *parser) bool { var ( @@ -441,6 +461,8 @@ func inHeadIM(p *parser) bool { implied = true case StartTagToken: switch p.tok.Data { + case "html": + return inBodyIM(p) case "base", "basefont", "bgsound", "command", "link", "meta": p.addElement(p.tok.Data, p.tok.Attr) p.oe.pop() @@ -450,6 +472,9 @@ func inHeadIM(p *parser) bool { p.setOriginalIM() p.im = textIM return true + case "head": + // Ignore the token. + return true default: implied = true } @@ -560,11 +585,30 @@ func copyAttributes(dst *Node, src Token) { func inBodyIM(p *parser) bool { switch p.tok.Type { case TextToken: + switch n := p.oe.top(); n.Data { + case "pre", "listing", "textarea": + if len(n.Child) == 0 { + // Ignore a newline at the start of a <pre> block. + d := p.tok.Data + if d != "" && d[0] == '\r' { + d = d[1:] + } + if d != "" && d[0] == '\n' { + d = d[1:] + } + if d == "" { + return true + } + p.tok.Data = d + } + } p.reconstructActiveFormattingElements() p.addText(p.tok.Data) p.framesetOK = false case StartTagToken: switch p.tok.Data { + case "html": + copyAttributes(p.oe[0], p.tok) case "address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", "p", "section", "summary", "ul": p.popUntil(buttonScopeStopTags, "p") p.addElement(p.tok.Data, p.tok.Attr) @@ -589,6 +633,13 @@ func inBodyIM(p *parser) bool { case "b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u": p.reconstructActiveFormattingElements() p.addFormattingElement(p.tok.Data, p.tok.Attr) + case "nobr": + p.reconstructActiveFormattingElements() + if p.elementInScope(defaultScopeStopTags, "nobr") { + p.inBodyEndTagFormatting("nobr") + p.reconstructActiveFormattingElements() + } + p.addFormattingElement(p.tok.Data, p.tok.Attr) case "applet", "marquee", "object": p.reconstructActiveFormattingElements() p.addElement(p.tok.Data, p.tok.Attr) @@ -601,7 +652,9 @@ func inBodyIM(p *parser) bool { p.acknowledgeSelfClosingTag() p.framesetOK = false case "table": - p.popUntil(buttonScopeStopTags, "p") // TODO: skip this step in quirks mode. + if !p.quirks { + p.popUntil(buttonScopeStopTags, "p") + } p.addElement(p.tok.Data, p.tok.Attr) p.framesetOK = false p.im = inTableIM @@ -721,6 +774,11 @@ func inBodyIM(p *parser) bool { p.oe.pop() p.oe.pop() p.form = nil + case "xmp": + p.popUntil(buttonScopeStopTags, "p") + p.reconstructActiveFormattingElements() + p.framesetOK = false + p.addElement(p.tok.Data, p.tok.Attr) case "caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr": // Ignore the token. default: @@ -1462,18 +1520,7 @@ func afterAfterFramesetIM(p *parser) bool { return true } -// Parse returns the parse tree for the HTML from the given Reader. -// The input is assumed to be UTF-8 encoded. -func Parse(r io.Reader) (*Node, error) { - p := &parser{ - tokenizer: NewTokenizer(r), - doc: &Node{ - Type: DocumentNode, - }, - scripting: true, - framesetOK: true, - im: initialIM, - } +func (p *parser) parse() error { // Iterate until EOF. Any other error will cause an early return. consumed := true for { @@ -1482,7 +1529,7 @@ func Parse(r io.Reader) (*Node, error) { if err == io.EOF { break } - return nil, err + return err } } consumed = p.im(p) @@ -1493,5 +1540,77 @@ func Parse(r io.Reader) (*Node, error) { break } } + return nil +} + +// Parse returns the parse tree for the HTML from the given Reader. +// The input is assumed to be UTF-8 encoded. +func Parse(r io.Reader) (*Node, error) { + p := &parser{ + tokenizer: NewTokenizer(r), + doc: &Node{ + Type: DocumentNode, + }, + scripting: true, + framesetOK: true, + im: initialIM, + } + err := p.parse() + if err != nil { + return nil, err + } return p.doc, nil } + +// ParseFragment parses a fragment of HTML and returns the nodes that were +// found. If the fragment is the InnerHTML for an existing element, pass that +// element in context. +func ParseFragment(r io.Reader, context *Node) ([]*Node, error) { + p := &parser{ + tokenizer: NewTokenizer(r), + doc: &Node{ + Type: DocumentNode, + }, + scripting: true, + context: context, + } + + if context != nil { + switch context.Data { + case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "title", "textarea", "xmp": + p.tokenizer.rawTag = context.Data + } + } + + root := &Node{ + Type: ElementNode, + Data: "html", + } + p.doc.Add(root) + p.oe = nodeStack{root} + p.resetInsertionMode() + + for n := context; n != nil; n = n.Parent { + if n.Type == ElementNode && n.Data == "form" { + p.form = n + break + } + } + + err := p.parse() + if err != nil { + return nil, err + } + + parent := p.doc + if context != nil { + parent = root + } + + result := parent.Child + parent.Child = nil + for _, n := range result { + n.Parent = nil + } + return result, nil +} diff --git a/libgo/go/html/parse_test.go b/libgo/go/html/parse_test.go index 4f15ae1..e0c19cf 100644 --- a/libgo/go/html/parse_test.go +++ b/libgo/go/html/parse_test.go @@ -10,65 +10,77 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "strings" "testing" ) -func pipeErr(err error) io.Reader { - pr, pw := io.Pipe() - pw.CloseWithError(err) - return pr -} - -func readDat(filename string, c chan io.Reader) { - defer close(c) - f, err := os.Open("testdata/webkit/" + filename) +// readParseTest reads a single test case from r. +func readParseTest(r *bufio.Reader) (text, want, context string, err error) { + line, err := r.ReadSlice('\n') if err != nil { - c <- pipeErr(err) - return + return "", "", "", err } - defer f.Close() + var b []byte - // Loop through the lines of the file. Each line beginning with "#" denotes - // a new section, which is returned as a separate io.Reader. - r := bufio.NewReader(f) - var pw *io.PipeWriter + // Read the HTML. + if string(line) != "#data\n" { + return "", "", "", fmt.Errorf(`got %q want "#data\n"`, line) + } for { - line, err := r.ReadSlice('\n') + line, err = r.ReadSlice('\n') if err != nil { - if pw != nil { - pw.CloseWithError(err) - pw = nil - } else { - c <- pipeErr(err) - } - return + return "", "", "", err } - if len(line) == 0 { - continue + if line[0] == '#' { + break + } + b = append(b, line...) + } + text = strings.TrimRight(string(b), "\n") + b = b[:0] + + // Skip the error list. + if string(line) != "#errors\n" { + return "", "", "", fmt.Errorf(`got %q want "#errors\n"`, line) + } + for { + line, err = r.ReadSlice('\n') + if err != nil { + return "", "", "", err } if line[0] == '#' { - if pw != nil { - pw.Close() - } - var pr *io.PipeReader - pr, pw = io.Pipe() - c <- pr - continue + break + } + } + + if string(line) == "#document-fragment\n" { + line, err = r.ReadSlice('\n') + if err != nil { + return "", "", "", err } - if line[0] != '|' { - // Strip the trailing '\n'. - line = line[:len(line)-1] + context = strings.TrimSpace(string(line)) + line, err = r.ReadSlice('\n') + if err != nil { + return "", "", "", err } - if pw != nil { - if _, err := pw.Write(line); err != nil { - pw.CloseWithError(err) - pw = nil - } + } + + // Read the dump of what the parse tree should be. + if string(line) != "#document\n" { + return "", "", "", fmt.Errorf(`got %q want "#document\n"`, line) + } + for { + line, err = r.ReadSlice('\n') + if err != nil && err != io.EOF { + return "", "", "", err + } + if len(line) == 0 || len(line) == 1 && line[0] == '\n' { + break } + b = append(b, line...) } + return text, string(b), context, nil } func dumpIndent(w io.Writer, level int) { @@ -93,11 +105,27 @@ func dumpLevel(w io.Writer, n *Node, level int) error { fmt.Fprintf(w, `%s="%s"`, a.Key, a.Val) } case TextNode: - fmt.Fprintf(w, "%q", n.Data) + fmt.Fprintf(w, `"%s"`, n.Data) case CommentNode: fmt.Fprintf(w, "<!-- %s -->", n.Data) case DoctypeNode: - fmt.Fprintf(w, "<!DOCTYPE %s>", n.Data) + fmt.Fprintf(w, "<!DOCTYPE %s", n.Data) + if n.Attr != nil { + var p, s string + for _, a := range n.Attr { + switch a.Key { + case "public": + p = a.Val + case "system": + s = a.Val + } + } + if p != "" || s != "" { + fmt.Fprintf(w, ` "%s"`, p) + fmt.Fprintf(w, ` "%s"`, s) + } + } + io.WriteString(w, ">") case scopeMarkerNode: return errors.New("unexpected scopeMarkerNode") default: @@ -133,46 +161,62 @@ func TestParser(t *testing.T) { n int }{ // TODO(nigeltao): Process all the test cases from all the .dat files. + {"doctype01.dat", -1}, {"tests1.dat", -1}, - {"tests2.dat", 43}, - {"tests3.dat", 0}, + {"tests2.dat", -1}, + {"tests3.dat", -1}, + {"tests4.dat", -1}, + {"tests5.dat", -1}, } for _, tf := range testFiles { - rc := make(chan io.Reader) - go readDat(tf.filename, rc) + f, err := os.Open("testdata/webkit/" + tf.filename) + if err != nil { + t.Fatal(err) + } + defer f.Close() + r := bufio.NewReader(f) for i := 0; i != tf.n; i++ { - // Parse the #data section. - dataReader := <-rc - if dataReader == nil { + text, want, context, err := readParseTest(r) + if err == io.EOF && tf.n == -1 { break } - b, err := ioutil.ReadAll(dataReader) if err != nil { t.Fatal(err) } - text := string(b) - doc, err := Parse(strings.NewReader(text)) - if err != nil { - t.Fatal(err) + + var doc *Node + if context == "" { + doc, err = Parse(strings.NewReader(text)) + if err != nil { + t.Fatal(err) + } + } else { + contextNode := &Node{ + Type: ElementNode, + Data: context, + } + nodes, err := ParseFragment(strings.NewReader(text), contextNode) + if err != nil { + t.Fatal(err) + } + doc = &Node{ + Type: DocumentNode, + } + for _, n := range nodes { + doc.Add(n) + } } + got, err := dump(doc) if err != nil { t.Fatal(err) } - // Skip the #error section. - if _, err := io.Copy(ioutil.Discard, <-rc); err != nil { - t.Fatal(err) - } // Compare the parsed tree to the #document section. - b, err = ioutil.ReadAll(<-rc) - if err != nil { - t.Fatal(err) - } - if want := string(b); got != want { + if got != want { t.Errorf("%s test #%d %q, got vs want:\n----\n%s----\n%s----", tf.filename, i, text, got, want) continue } - if renderTestBlacklist[text] { + if renderTestBlacklist[text] || context != "" { continue } // Check that rendering and re-parsing results in an identical tree. @@ -193,12 +237,6 @@ func TestParser(t *testing.T) { continue } } - // Drain any untested cases for the test file. - for r := range rc { - if _, err := ioutil.ReadAll(r); err != nil { - t.Fatal(err) - } - } } } diff --git a/libgo/go/html/render.go b/libgo/go/html/render.go index 92c349f..7e1a466 100644 --- a/libgo/go/html/render.go +++ b/libgo/go/html/render.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "strings" ) type writer interface { @@ -98,6 +99,40 @@ func render1(w writer, n *Node) error { if _, err := w.WriteString(n.Data); err != nil { return err } + if n.Attr != nil { + var p, s string + for _, a := range n.Attr { + switch a.Key { + case "public": + p = a.Val + case "system": + s = a.Val + } + } + if p != "" { + if _, err := w.WriteString(" PUBLIC "); err != nil { + return err + } + if err := writeQuoted(w, p); err != nil { + return err + } + if s != "" { + if err := w.WriteByte(' '); err != nil { + return err + } + if err := writeQuoted(w, s); err != nil { + return err + } + } + } else if s != "" { + if _, err := w.WriteString(" SYSTEM "); err != nil { + return err + } + if err := writeQuoted(w, s); err != nil { + return err + } + } + } return w.WriteByte('>') default: return errors.New("html: unknown node type") @@ -138,9 +173,19 @@ func render1(w writer, n *Node) error { return err } + // Add initial newline where there is danger of a newline beging ignored. + if len(n.Child) > 0 && n.Child[0].Type == TextNode && strings.HasPrefix(n.Child[0].Data, "\n") { + switch n.Data { + case "pre", "listing", "textarea": + if err := w.WriteByte('\n'); err != nil { + return err + } + } + } + // Render any child nodes. switch n.Data { - case "noembed", "noframes", "noscript", "plaintext", "script", "style": + case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": for _, c := range n.Child { if c.Type != TextNode { return fmt.Errorf("html: raw text element <%s> has non-text child node", n.Data) @@ -181,6 +226,27 @@ func render1(w writer, n *Node) error { return w.WriteByte('>') } +// writeQuoted writes s to w surrounded by quotes. Normally it will use double +// quotes, but if s contains a double quote, it will use single quotes. +// It is used for writing the identifiers in a doctype declaration. +// In valid HTML, they can't contain both types of quotes. +func writeQuoted(w writer, s string) error { + var q byte = '"' + if strings.Contains(s, `"`) { + q = '\'' + } + if err := w.WriteByte(q); err != nil { + return err + } + if _, err := w.WriteString(s); err != nil { + return err + } + if err := w.WriteByte(q); err != nil { + return err + } + return nil +} + // Section 13.1.2, "Elements", gives this list of void elements. Void elements // are those that can't have any contents. var voidElements = map[string]bool{ diff --git a/libgo/go/html/template/clone_test.go b/libgo/go/html/template/clone_test.go index ed1698a..3978817 100644 --- a/libgo/go/html/template/clone_test.go +++ b/libgo/go/html/template/clone_test.go @@ -7,8 +7,6 @@ package template import ( "bytes" "testing" - "text/template" - "text/template/parse" ) func TestClone(t *testing.T) { @@ -48,15 +46,20 @@ func TestClone(t *testing.T) { } for _, test := range tests { - s := template.Must(template.New("s").Parse(test.input)) - d := template.New("d") - d.Tree = &parse.Tree{Name: d.Name(), Root: cloneList(s.Root)} + s, err := New("s").Parse(test.input) + if err != nil { + t.Errorf("input=%q: unexpected parse error %v", test.input, err) + } + + d, _ := New("d").Parse(test.input) + // Hack: just replace the root of the tree. + d.text.Root = cloneList(s.text.Root) - if want, got := s.Root.String(), d.Root.String(); want != got { + if want, got := s.text.Root.String(), d.text.Root.String(); want != got { t.Errorf("want %q, got %q", want, got) } - err := escape(d) + err = escapeTemplates(d, "d") if err != nil { t.Errorf("%q: failed to escape: %s", test.input, err) continue @@ -73,18 +76,17 @@ func TestClone(t *testing.T) { data := []string{"foo", "<bar>", "baz"} - // Make sure escaping d did not affect s. var b bytes.Buffer - s.Execute(&b, data) - if got := b.String(); got != test.want { - t.Errorf("%q: want %q, got %q", test.input, test.want, got) - continue + d.Execute(&b, data) + if got := b.String(); got != test.wantClone { + t.Errorf("input=%q: want %q, got %q", test.input, test.wantClone, got) } + // Make sure escaping d did not affect s. b.Reset() - d.Execute(&b, data) - if got := b.String(); got != test.wantClone { - t.Errorf("%q: want %q, got %q", test.input, test.wantClone, got) + s.text.Execute(&b, data) + if got := b.String(); got != test.want { + t.Errorf("input=%q: want %q, got %q", test.input, test.want, got) } } } diff --git a/libgo/go/html/template/content.go b/libgo/go/html/template/content.go index 3fb15a6..4de7ccd 100644 --- a/libgo/go/html/template/content.go +++ b/libgo/go/html/template/content.go @@ -12,10 +12,10 @@ import ( // Strings of content from a trusted source. type ( // CSS encapsulates known safe content that matches any of: - // (1) The CSS3 stylesheet production, such as `p { color: purple }`. - // (2) The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. - // (3) CSS3 declaration productions, such as `color: red; margin: 2px`. - // (4) The CSS3 value production, such as `rgba(0, 0, 255, 127)`. + // 1. The CSS3 stylesheet production, such as `p { color: purple }`. + // 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. + // 3. CSS3 declaration productions, such as `color: red; margin: 2px`. + // 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`. // See http://www.w3.org/TR/css3-syntax/#style CSS string @@ -41,8 +41,8 @@ type ( // JSStr encapsulates a sequence of characters meant to be embedded // between quotes in a JavaScript expression. // The string must match a series of StringCharacters: - // StringCharacter :: SourceCharacter but not `\` or LineTerminator - // | EscapeSequence + // StringCharacter :: SourceCharacter but not `\` or LineTerminator + // | EscapeSequence // Note that LineContinuations are not allowed. // JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not. JSStr string diff --git a/libgo/go/html/template/doc.go b/libgo/go/html/template/doc.go index 0324c9c..fc0e382 100644 --- a/libgo/go/html/template/doc.go +++ b/libgo/go/html/template/doc.go @@ -13,9 +13,9 @@ Introduction This package wraps package template so you can use the standard template API to parse and execute templates. - set, err := new(template.Set).Parse(...) - // Error checking elided - err = set.Execute(out, "Foo", data) + set, err := new(template.Set).Parse(...) + // Error checking elided + err = set.Execute(out, "Foo", data) If successful, set will now be injection-safe. Otherwise, err is an error defined in the docs for ErrorCode. @@ -29,25 +29,25 @@ trusted, while Execute's data parameter is not. More details are provided below. Example - import "template" - ... - t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>") + import "text/template" + ... + t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>") produces - Hello, <script>alert('you have been pwned')</script>! + Hello, <script>alert('you have been pwned')</script>! but with contextual autoescaping, - import "html/template" - ... - t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>") + import "html/template" + ... + t, err := (&template.Set{}).Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.Execute(out, "T", "<script>alert('you have been pwned')</script>") produces safe, escaped HTML output - Hello, <script>alert('you have been pwned')</script>! + Hello, <script>alert('you have been pwned')</script>! Contexts @@ -80,36 +80,36 @@ Contexts Assuming {{.}} is `O'Reilly: How are <i>you</i>?`, the table below shows how {{.}} appears when used in the context to the left. -Context {{.}} After -{{.}} O'Reilly: How are <i>you</i>? -<a title='{{.}}'> O'Reilly: How are you? -<a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e? -<a href="?q={{.}}"> O'Reilly%3a%20How%20are%3ci%3e...%3f -<a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? -<a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" -<a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f + Context {{.}} After + {{.}} O'Reilly: How are <i>you</i>? + <a title='{{.}}'> O'Reilly: How are you? + <a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e? + <a href="?q={{.}}"> O'Reilly%3a%20How%20are%3ci%3e...%3f + <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? + <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" + <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f If used in an unsafe context, then the value might be filtered out: -Context {{.}} After -<a href="{{.}}"> #ZgotmplZ + Context {{.}} After + <a href="{{.}}"> #ZgotmplZ since "O'Reilly:" is not an allowed protocol like "http:". If {{.}} is the innocuous word, `left`, then it can appear more widely, -Context {{.}} After -{{.}} left -<a title='{{.}}'> left -<a href='{{.}}'> left -<a href='/{{.}}'> left -<a href='?dir={{.}}'> left -<a style="border-{{.}}: 4px"> left -<a style="align: {{.}}"> left -<a style="background: '{{.}}'> left -<a style="background: url('{{.}}')> left -<style>p.{{.}} {color:red}</style> left + Context {{.}} After + {{.}} left + <a title='{{.}}'> left + <a href='{{.}}'> left + <a href='/{{.}}'> left + <a href='?dir={{.}}'> left + <a style="border-{{.}}: 4px"> left + <a style="align: {{.}}"> left + <a style="background: '{{.}}'> left + <a style="background: url('{{.}}')> left + <style>p.{{.}} {color:red}</style> left Non-string values can be used in JavaScript contexts. If {{.}} is diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go index 8ac07ea..4a7a935 100644 --- a/libgo/go/html/template/escape.go +++ b/libgo/go/html/template/escape.go @@ -12,24 +12,15 @@ import ( "text/template/parse" ) -// escape rewrites each action in the template to guarantee that the output is -// properly escaped. -func escape(t *template.Template) error { - var s template.Set - s.Add(t) - return escapeSet(&s, t.Name()) - // TODO: if s contains cloned dependencies due to self-recursion - // cross-context, error out. -} - -// escapeSet rewrites the template set to guarantee that the output of any of -// the named templates is properly escaped. -// Names should include the names of all templates that might be Executed but -// need not include helper templates. -// If no error is returned, then the named templates have been modified. -// Otherwise the named templates have been rendered unusable. -func escapeSet(s *template.Set, names ...string) error { - e := newEscaper(s) +// escapeTemplates rewrites the named templates, which must be +// associated with t, to guarantee that the output of any of the named +// templates is properly escaped. Names should include the names of +// all templates that might be Executed but need not include helper +// templates. If no error is returned, then the named templates have +// been modified. Otherwise the named templates have been rendered +// unusable. +func escapeTemplates(tmpl *Template, names ...string) error { + e := newEscaper(tmpl) for _, name := range names { c, _ := e.escapeTree(context{}, name, 0) var err error @@ -41,12 +32,13 @@ func escapeSet(s *template.Set, names ...string) error { if err != nil { // Prevent execution of unsafe templates. for _, name := range names { - if t := s.Template(name); t != nil { - t.Tree = nil + if t := tmpl.set[name]; t != nil { + t.text.Tree = nil } } return err } + tmpl.escaped = true } e.commit() return nil @@ -83,8 +75,7 @@ var equivEscapers = map[string]string{ // escaper collects type inferences about templates and changes needed to make // templates injection safe. type escaper struct { - // set is the template set being escaped. - set *template.Set + tmpl *Template // output[templateName] is the output context for a templateName that // has been mangled to include its input context. output map[string]context @@ -102,9 +93,9 @@ type escaper struct { } // newEscaper creates a blank escaper for the given set. -func newEscaper(s *template.Set) *escaper { +func newEscaper(t *Template) *escaper { return &escaper{ - s, + t, map[string]context{}, map[string]*template.Template{}, map[string]bool{}, @@ -442,7 +433,7 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context { // It returns the best guess at an output context, and the result of the filter // which is the same as whether e was updated. func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) { - e1 := newEscaper(e.set) + e1 := newEscaper(e.tmpl) // Make type inferences available to f. for k, v := range e.output { e1.output[k] = v @@ -501,7 +492,7 @@ func (e *escaper) escapeTree(c context, name string, line int) (context, string) }, dname } if dname != name { - // Use any template derived during an earlier call to escapeSet + // Use any template derived during an earlier call to escapeTemplate // with different top level templates, or clone if necessary. dt := e.template(dname) if dt == nil { @@ -529,7 +520,7 @@ func (e *escaper) computeOutCtx(c context, t *template.Template) context { if !ok && c1.state != stateError { return context{ state: stateError, - // TODO: Find the first node with a line in t.Tree.Root + // TODO: Find the first node with a line in t.text.Tree.Root err: errorf(ErrOutputContext, 0, "cannot compute output context for template %s", t.Name()), } } @@ -729,7 +720,9 @@ func (e *escaper) commit() { e.template(name).Funcs(funcMap) } for _, t := range e.derived { - e.set.Add(t) + if _, err := e.tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil { + panic("error adding derived template") + } } for n, s := range e.actionNodeEdits { ensurePipelineContains(n.Pipe, s) @@ -744,7 +737,7 @@ func (e *escaper) commit() { // template returns the named template given a mangled template name. func (e *escaper) template(name string) *template.Template { - t := e.set.Template(name) + t := e.tmpl.text.Lookup(name) if t == nil { t = e.derived[name] } diff --git a/libgo/go/html/template/escape_test.go b/libgo/go/html/template/escape_test.go index 4af58309..b4daca7 100644 --- a/libgo/go/html/template/escape_test.go +++ b/libgo/go/html/template/escape_test.go @@ -806,13 +806,15 @@ func TestEscapeSet(t *testing.T) { for name, body := range test.inputs { source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body) } - s := &Set{} - s.Funcs(fns) - s.Parse(source) + tmpl, err := New("root").Funcs(fns).Parse(source) + if err != nil { + t.Errorf("error parsing %q: %v", source, err) + continue + } var b bytes.Buffer - if err := s.Execute(&b, "main", data); err != nil { - t.Errorf("%q executing %v", err.Error(), s.Template("main")) + if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil { + t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main")) continue } if got := b.String(); test.want != got { @@ -929,13 +931,13 @@ func TestErrors(t *testing.T) { "z:1: no such template foo", }, { - `{{define "z"}}<div{{template "y"}}>{{end}}` + + `<div{{template "y"}}>` + // Illegal starting in stateTag but not in stateText. `{{define "y"}} foo<b{{end}}`, `"<" in attribute name: " foo<b"`, }, { - `{{define "z"}}<script>reverseList = [{{template "t"}}]</script>{{end}}` + + `<script>reverseList = [{{template "t"}}]</script>` + // Missing " after recursive call. `{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`, `: cannot compute output context for template t$htmltemplate_stateJS_elementScript`, @@ -967,21 +969,13 @@ func TestErrors(t *testing.T) { } for _, test := range tests { - var err error buf := new(bytes.Buffer) - if strings.HasPrefix(test.input, "{{define") { - var s *Set - s, err = (&Set{}).Parse(test.input) - if err == nil { - err = s.Execute(buf, "z", nil) - } - } else { - var t *Template - t, err = New("z").Parse(test.input) - if err == nil { - err = t.Execute(buf, nil) - } + tmpl, err := New("z").Parse(test.input) + if err != nil { + t.Errorf("input=%q: unexpected parse error %s\n", test.input, err) + continue } + err = tmpl.Execute(buf, nil) var got string if err != nil { got = err.Error() @@ -1569,11 +1563,11 @@ func TestEscapeErrorsNotIgnorable(t *testing.T) { func TestEscapeSetErrorsNotIgnorable(t *testing.T) { var b bytes.Buffer - s, err := (&Set{}).Parse(`{{define "t"}}<a{{end}}`) + tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`) if err != nil { t.Errorf("failed to parse set: %q", err) } - err = s.Execute(&b, "t", nil) + err = tmpl.ExecuteTemplate(&b, "t", nil) if err == nil { t.Errorf("Expected error") } else if b.Len() != 0 { diff --git a/libgo/go/html/template/template.go b/libgo/go/html/template/template.go index 4733429..f05ca19 100644 --- a/libgo/go/html/template/template.go +++ b/libgo/go/html/template/template.go @@ -7,233 +7,257 @@ package template import ( "fmt" "io" + "io/ioutil" "path/filepath" + "sync" "text/template" + "text/template/parse" ) -// Set is a specialized template.Set that produces a safe HTML document -// fragment. -type Set struct { - escaped map[string]bool - template.Set -} - // Template is a specialized template.Template that produces a safe HTML // document fragment. type Template struct { escaped bool - *template.Template + // We could embed the text/template field, but it's safer not to because + // we need to keep our version of the name space and the underlying + // template's in sync. + text *template.Template + *nameSpace // common to all associated templates } -// Execute applies the named template to the specified data object, writing -// the output to wr. -func (s *Set) Execute(wr io.Writer, name string, data interface{}) error { - if !s.escaped[name] { - if err := escapeSet(&s.Set, name); err != nil { - return err - } - if s.escaped == nil { - s.escaped = make(map[string]bool) +// nameSpace is the data structure shared by all templates in an association. +type nameSpace struct { + mu sync.Mutex + set map[string]*Template +} + +// Execute applies a parsed template to the specified data object, +// writing the output to wr. +func (t *Template) Execute(wr io.Writer, data interface{}) (err error) { + t.nameSpace.mu.Lock() + if !t.escaped { + if err = escapeTemplates(t, t.Name()); err != nil { + t.escaped = true } - s.escaped[name] = true } - return s.Set.Execute(wr, name, data) + t.nameSpace.mu.Unlock() + if err != nil { + return + } + return t.text.Execute(wr, data) } -// Parse parses a string into a set of named templates. Parse may be called -// multiple times for a given set, adding the templates defined in the string -// to the set. If a template is redefined, the element in the set is -// overwritten with the new definition. -func (set *Set) Parse(src string) (*Set, error) { - set.escaped = nil - s, err := set.Set.Parse(src) - if err != nil { - return nil, err +// ExecuteTemplate applies the template associated with t that has the given name +// to the specified data object and writes the output to wr. +func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) (err error) { + t.nameSpace.mu.Lock() + tmpl := t.set[name] + if tmpl == nil { + t.nameSpace.mu.Unlock() + return fmt.Errorf("template: no template %q associated with template %q", name, t.Name()) } - if s != &(set.Set) { - panic("allocated new set") + if !tmpl.escaped { + err = escapeTemplates(tmpl, name) + } + t.nameSpace.mu.Unlock() + if err != nil { + return } - return set, nil + return tmpl.text.ExecuteTemplate(wr, name, data) } -// Parse parses the template definition string to construct an internal -// representation of the template for execution. -func (tmpl *Template) Parse(src string) (*Template, error) { - tmpl.escaped = false - t, err := tmpl.Template.Parse(src) +// Parse parses a string into a template. Nested template definitions +// will be associated with the top-level template t. Parse may be +// called multiple times to parse definitions of templates to associate +// with t. It is an error if a resulting template is non-empty (contains +// content other than template definitions) and would replace a +// non-empty template with the same name. (In multiple calls to Parse +// with the same receiver template, only one call can contain text +// other than space, comments, and template definitions.) +func (t *Template) Parse(src string) (*Template, error) { + t.nameSpace.mu.Lock() + t.escaped = false + t.nameSpace.mu.Unlock() + ret, err := t.text.Parse(src) if err != nil { return nil, err } - tmpl.Template = t - return tmpl, nil -} - -// Execute applies a parsed template to the specified data object, -// writing the output to wr. -func (t *Template) Execute(wr io.Writer, data interface{}) error { - if !t.escaped { - if err := escape(t.Template); err != nil { - return err + // In general, all the named templates might have changed underfoot. + // Regardless, some new ones may have been defined. + // The template.Template set has been updated; update ours. + t.nameSpace.mu.Lock() + defer t.nameSpace.mu.Unlock() + for _, v := range ret.Templates() { + name := v.Name() + tmpl := t.set[name] + if tmpl == nil { + tmpl = t.new(name) } - t.escaped = true + tmpl.escaped = false + tmpl.text = v } - return t.Template.Execute(wr, data) + return t, nil +} + +// AddParseTree is unimplemented. +func (t *Template) AddParseTree(name string, tree *parse.Tree) error { + return fmt.Errorf("html/template: AddParseTree unimplemented") +} + +// Clone is unimplemented. +func (t *Template) Clone(name string) error { + return fmt.Errorf("html/template: Add unimplemented") } // New allocates a new HTML template with the given name. func New(name string) *Template { - return &Template{false, template.New(name)} + tmpl := &Template{ + false, + template.New(name), + &nameSpace{ + set: make(map[string]*Template), + }, + } + tmpl.set[name] = tmpl + return tmpl } -// Must panics if err is non-nil in the same way as template.Must. -func Must(t *Template, err error) *Template { - t.Template = template.Must(t.Template, err) - return t +// New allocates a new HTML template associated with the given one +// and with the same delimiters. The association, which is transitive, +// allows one template to invoke another with a {{template}} action. +func (t *Template) New(name string) *Template { + t.nameSpace.mu.Lock() + defer t.nameSpace.mu.Unlock() + return t.new(name) } -// ParseFile creates a new Template and parses the template definition from -// the named file. The template name is the base name of the file. -func ParseFile(filename string) (*Template, error) { - t, err := template.ParseFile(filename) - if err != nil { - return nil, err +// new is the implementation of New, without the lock. +func (t *Template) new(name string) *Template { + tmpl := &Template{ + false, + t.text.New(name), + t.nameSpace, } - return &Template{false, t}, nil + tmpl.set[name] = tmpl + return tmpl } -// ParseFile reads the template definition from a file and parses it to -// construct an internal representation of the template for execution. -// The returned template will be nil if an error occurs. -func (tmpl *Template) ParseFile(filename string) (*Template, error) { - t, err := tmpl.Template.ParseFile(filename) - if err != nil { - return nil, err - } - tmpl.Template = t - return tmpl, nil +// Name returns the name of the template. +func (t *Template) Name() string { + return t.text.Name() } -// SetMust panics if the error is non-nil just like template.SetMust. -func SetMust(s *Set, err error) *Set { - if err != nil { - template.SetMust(&(s.Set), err) - } - return s +// Funcs adds the elements of the argument map to the template's function map. +// It panics if a value in the map is not a function with appropriate return +// type. However, it is legal to overwrite elements of the map. The return +// value is the template, so calls can be chained. +func (t *Template) Funcs(funcMap template.FuncMap) *Template { + t.text.Funcs(funcMap) + return t } -// ParseFiles parses the named files into a set of named templates. -// Each file must be parseable by itself. -// If an error occurs, parsing stops and the returned set is nil. -func (set *Set) ParseFiles(filenames ...string) (*Set, error) { - s, err := set.Set.ParseFiles(filenames...) - if err != nil { - return nil, err - } - if s != &(set.Set) { - panic("allocated new set") - } - return set, nil +// Delims sets the action delimiters to the specified strings, to be used in +// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template +// definitions will inherit the settings. An empty delimiter stands for the +// corresponding default: {{ or }}. +// The return value is the template, so calls can be chained. +func (t *Template) Delims(left, right string) *Template { + t.text.Delims(left, right) + return t } -// ParseSetFiles creates a new Set and parses the set definition from the -// named files. Each file must be individually parseable. -func ParseSetFiles(filenames ...string) (*Set, error) { - set := new(Set) - s, err := set.Set.ParseFiles(filenames...) - if err != nil { - return nil, err - } - if s != &(set.Set) { - panic("allocated new set") - } - return set, nil +// Lookup returns the template with the given name that is associated with t, +// or nil if there is no such template. +func (t *Template) Lookup(name string) *Template { + t.nameSpace.mu.Lock() + defer t.nameSpace.mu.Unlock() + return t.set[name] } -// ParseGlob parses the set definition from the files identified by the -// pattern. The pattern is processed by filepath.Glob and must match at -// least one file. -// If an error occurs, parsing stops and the returned set is nil. -func (s *Set) ParseGlob(pattern string) (*Set, error) { - filenames, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - if len(filenames) == 0 { - return nil, fmt.Errorf("pattern matches no files: %#q", pattern) - } - return s.ParseFiles(filenames...) +// Must panics if err is non-nil in the same way as template.Must. +func Must(t *Template, err error) *Template { + t.text = template.Must(t.text, err) + return t } -// ParseSetGlob creates a new Set and parses the set definition from the -// files identified by the pattern. The pattern is processed by filepath.Glob -// and must match at least one file. -func ParseSetGlob(pattern string) (*Set, error) { - set, err := new(Set).ParseGlob(pattern) - if err != nil { - return nil, err - } - return set, nil +// ParseFiles creates a new Template and parses the template definitions from +// the named files. The returned template's name will have the (base) name and +// (parsed) contents of the first file. There must be at least one file. +// If an error occurs, parsing stops and the returned *Template is nil. +func ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(nil, filenames...) } -// Functions and methods to parse stand-alone template files into a set. +// ParseFiles parses the named files and associates the resulting templates with +// t. If an error occurs, parsing stops and the returned template is nil; +// otherwise it is t. There must be at least one file. +func (t *Template) ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(t, filenames...) +} -// ParseTemplateFiles parses the named template files and adds them to the set -// in the same way as template.ParseTemplateFiles but ensures that templates -// with upper-case names are contextually-autoescaped. -func (set *Set) ParseTemplateFiles(filenames ...string) (*Set, error) { - s, err := set.Set.ParseTemplateFiles(filenames...) - if err != nil { - return nil, err +// parseFiles is the helper for the method and function. If the argument +// template is nil, it is created from the first file. +func parseFiles(t *Template, filenames ...string) (*Template, error) { + if len(filenames) == 0 { + // Not really a problem, but be consistent. + return nil, fmt.Errorf("template: no files named in call to ParseFiles") } - if s != &(set.Set) { - panic("new set allocated") + for _, filename := range filenames { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + s := string(b) + name := filepath.Base(filename) + // First template becomes return value if not already defined, + // and we use that one for subsequent New calls to associate + // all the templates together. Also, if this file has the same name + // as t, this file becomes the contents of t, so + // t, err := New(name).Funcs(xxx).ParseFiles(name) + // works. Otherwise we create a new template associated with t. + var tmpl *Template + if t == nil { + t = New(name) + } + if name == t.Name() { + tmpl = t + } else { + tmpl = t.New(name) + } + _, err = tmpl.Parse(s) + if err != nil { + return nil, err + } } - return set, nil -} - -// ParseTemplateGlob parses the template files matched by the -// patern and adds them to the set. Each template will be named -// the base name of its file. -// Unlike with ParseGlob, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateGlob is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. -// If an error occurs, parsing stops and the returned set is nil. -func (s *Set) ParseTemplateGlob(pattern string) (*Set, error) { + return t, nil +} + +// ParseGlob creates a new Template and parses the template definitions from the +// files identified by the pattern, which must match at least one file. The +// returned template will have the (base) name and (parsed) contents of the +// first file matched by the pattern. ParseGlob is equivalent to calling +// ParseFiles with the list of files matched by the pattern. +func ParseGlob(pattern string) (*Template, error) { + return parseGlob(nil, pattern) +} + +// ParseGlob parses the template definitions in the files identified by the +// pattern and associates the resulting templates with t. The pattern is +// processed by filepath.Glob and must match at least one file. ParseGlob is +// equivalent to calling t.ParseFiles with the list of files matched by the +// pattern. +func (t *Template) ParseGlob(pattern string) (*Template, error) { + return parseGlob(t, pattern) +} + +// parseGlob is the implementation of the function and method ParseGlob. +func parseGlob(t *Template, pattern string) (*Template, error) { filenames, err := filepath.Glob(pattern) if err != nil { return nil, err } - return s.ParseTemplateFiles(filenames...) -} - -// ParseTemplateFiles creates a set by parsing the named files, -// each of which defines a single template. Each template will be -// named the base name of its file. -// Unlike with ParseFiles, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateFiles is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. Parsing stops if an error is -// encountered. -func ParseTemplateFiles(filenames ...string) (*Set, error) { - return new(Set).ParseTemplateFiles(filenames...) -} - -// ParseTemplateGlob creates a set by parsing the files matched -// by the pattern, each of which defines a single template. The pattern -// is processed by filepath.Glob and must match at least one file. Each -// template will be named the base name of its file. -// Unlike with ParseGlob, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateGlob is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. Parsing stops if an error is -// encountered. -func ParseTemplateGlob(pattern string) (*Set, error) { - return new(Set).ParseTemplateGlob(pattern) + if len(filenames) == 0 { + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) + } + return parseFiles(t, filenames...) } diff --git a/libgo/go/html/token.go b/libgo/go/html/token.go index 9400873..69af968 100644 --- a/libgo/go/html/token.go +++ b/libgo/go/html/token.go @@ -289,7 +289,11 @@ func (z *Tokenizer) readComment() { for dashCount := 2; ; { c := z.readByte() if z.err != nil { - z.data.end = z.raw.end + // Ignore up to two dashes at EOF. + if dashCount > 2 { + dashCount = 2 + } + z.data.end = z.raw.end - dashCount return } switch c { @@ -375,6 +379,28 @@ func (z *Tokenizer) readMarkupDeclaration() TokenType { return DoctypeToken } +// startTagIn returns whether the start tag in z.buf[z.data.start:z.data.end] +// case-insensitively matches any element of ss. +func (z *Tokenizer) startTagIn(ss ...string) bool { +loop: + for _, s := range ss { + if z.data.end-z.data.start != len(s) { + continue loop + } + for i := 0; i < len(s); i++ { + c := z.buf[z.data.start+i] + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + if c != s[i] { + continue loop + } + } + return true + } + return false +} + // readStartTag reads the next start tag token. The opening "<a" has already // been consumed, where 'a' means anything in [A-Za-z]. func (z *Tokenizer) readStartTag() TokenType { @@ -401,17 +427,27 @@ func (z *Tokenizer) readStartTag() TokenType { break } } - // Any "<noembed>", "<noframes>", "<noscript>", "<plaintext", "<script>", "<style>", - // "<textarea>" or "<title>" tag flags the tokenizer's next token as raw. - // The tag name lengths of these special cases ranges in [5, 9]. - if x := z.data.end - z.data.start; 5 <= x && x <= 9 { - switch z.buf[z.data.start] { - case 'n', 'p', 's', 't', 'N', 'P', 'S', 'T': - switch s := strings.ToLower(string(z.buf[z.data.start:z.data.end])); s { - case "noembed", "noframes", "noscript", "plaintext", "script", "style", "textarea", "title": - z.rawTag = s - } - } + // Several tags flag the tokenizer's next token as raw. + c, raw := z.buf[z.data.start], false + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + switch c { + case 'i': + raw = z.startTagIn("iframe") + case 'n': + raw = z.startTagIn("noembed", "noframes", "noscript") + case 'p': + raw = z.startTagIn("plaintext") + case 's': + raw = z.startTagIn("script", "style") + case 't': + raw = z.startTagIn("textarea", "title") + case 'x': + raw = z.startTagIn("xmp") + } + if raw { + z.rawTag = strings.ToLower(string(z.buf[z.data.start:z.data.end])) } // Look for a self-closing token like "<br/>". if z.err == nil && z.buf[z.raw.end-2] == '/' { diff --git a/libgo/go/html/token_test.go b/libgo/go/html/token_test.go index 61d4e67..672d60c 100644 --- a/libgo/go/html/token_test.go +++ b/libgo/go/html/token_test.go @@ -325,6 +325,26 @@ var tokenTests = []tokenTest{ }, { "comment9", + "a<!--z-", + "a$<!--z-->", + }, + { + "comment10", + "a<!--z--", + "a$<!--z-->", + }, + { + "comment11", + "a<!--z---", + "a$<!--z--->", + }, + { + "comment12", + "a<!--z----", + "a$<!--z---->", + }, + { + "comment13", "a<!--x--!>z", "a$<!--x-->$z", }, diff --git a/libgo/go/io/ioutil/ioutil.go b/libgo/go/io/ioutil/ioutil.go index f6c8cd8..be7fa5f 100644 --- a/libgo/go/io/ioutil/ioutil.go +++ b/libgo/go/io/ioutil/ioutil.go @@ -36,8 +36,8 @@ func ReadFile(filename string) ([]byte, error) { // read, so let's try it but be prepared for the answer to be wrong. fi, err := f.Stat() var n int64 - if err == nil && fi.Size < 2e9 { // Don't preallocate a huge buffer, just in case. - n = fi.Size + if size := fi.Size(); err == nil && size < 2e9 { // Don't preallocate a huge buffer, just in case. + n = size } // As initial capacity for readAll, use n + a little extra in case Size is zero, // and to avoid another allocation after Read has filled the buffer. The readAll @@ -63,16 +63,16 @@ func WriteFile(filename string, data []byte, perm uint32) error { return err } -// A fileInfoList implements sort.Interface. -type fileInfoList []*os.FileInfo +// byName implements sort.Interface. +type byName []os.FileInfo -func (f fileInfoList) Len() int { return len(f) } -func (f fileInfoList) Less(i, j int) bool { return f[i].Name < f[j].Name } -func (f fileInfoList) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +func (f byName) Len() int { return len(f) } +func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } +func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } // ReadDir reads the directory named by dirname and returns // a list of sorted directory entries. -func ReadDir(dirname string) ([]*os.FileInfo, error) { +func ReadDir(dirname string) ([]os.FileInfo, error) { f, err := os.Open(dirname) if err != nil { return nil, err @@ -82,12 +82,8 @@ func ReadDir(dirname string) ([]*os.FileInfo, error) { if err != nil { return nil, err } - fi := make(fileInfoList, len(list)) - for i := range list { - fi[i] = &list[i] - } - sort.Sort(fi) - return fi, nil + sort.Sort(byName(list)) + return list, nil } type nopCloser struct { diff --git a/libgo/go/io/ioutil/ioutil_test.go b/libgo/go/io/ioutil/ioutil_test.go index 55e4b2c..89d6815 100644 --- a/libgo/go/io/ioutil/ioutil_test.go +++ b/libgo/go/io/ioutil/ioutil_test.go @@ -15,8 +15,8 @@ func checkSize(t *testing.T, path string, size int64) { if err != nil { t.Fatalf("Stat %q (looking for size %d): %s", path, size, err) } - if dir.Size != size { - t.Errorf("Stat %q: size %d want %d", path, dir.Size, size) + if dir.Size() != size { + t.Errorf("Stat %q: size %d want %d", path, dir.Size(), size) } } @@ -76,9 +76,9 @@ func TestReadDir(t *testing.T) { foundTestDir := false for _, dir := range list { switch { - case dir.IsRegular() && dir.Name == "ioutil_test.go": + case !dir.IsDir() && dir.Name() == "ioutil_test.go": foundTest = true - case dir.IsDirectory() && dir.Name == "_test": + case dir.IsDir() && dir.Name() == "_test": foundTestDir = true } } diff --git a/libgo/go/io/ioutil/tempfile.go b/libgo/go/io/ioutil/tempfile.go index 71028e2..645eed6 100644 --- a/libgo/go/io/ioutil/tempfile.go +++ b/libgo/go/io/ioutil/tempfile.go @@ -18,7 +18,7 @@ import ( var rand uint32 func reseed() uint32 { - return uint32(time.Nanoseconds() + int64(os.Getpid())) + return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextSuffix() string { diff --git a/libgo/go/io/multi_test.go b/libgo/go/io/multi_test.go index 0de5cc3..eb717f7 100644 --- a/libgo/go/io/multi_test.go +++ b/libgo/go/io/multi_test.go @@ -77,7 +77,7 @@ func TestMultiWriter(t *testing.T) { t.Errorf("unexpected error: %v", err) } - sha1hex := fmt.Sprintf("%x", sha1.Sum()) + sha1hex := fmt.Sprintf("%x", sha1.Sum(nil)) if sha1hex != "01cb303fa8c30a64123067c5aa6284ba7ec2d31b" { t.Error("incorrect sha1 value") } diff --git a/libgo/go/log/log.go b/libgo/go/log/log.go index b5368af..a5d88fd 100644 --- a/libgo/go/log/log.go +++ b/libgo/go/log/log.go @@ -83,27 +83,28 @@ func itoa(buf *bytes.Buffer, i int, wid int) { } } -func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, file string, line int) { +func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time, file string, line int) { buf.WriteString(l.prefix) if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { - t := time.SecondsToLocalTime(ns / 1e9) if l.flag&Ldate != 0 { - itoa(buf, int(t.Year), 4) + year, month, day := t.Date() + itoa(buf, year, 4) buf.WriteByte('/') - itoa(buf, int(t.Month), 2) + itoa(buf, int(month), 2) buf.WriteByte('/') - itoa(buf, int(t.Day), 2) + itoa(buf, day, 2) buf.WriteByte(' ') } if l.flag&(Ltime|Lmicroseconds) != 0 { - itoa(buf, int(t.Hour), 2) + hour, min, sec := t.Clock() + itoa(buf, hour, 2) buf.WriteByte(':') - itoa(buf, int(t.Minute), 2) + itoa(buf, min, 2) buf.WriteByte(':') - itoa(buf, int(t.Second), 2) + itoa(buf, sec, 2) if l.flag&Lmicroseconds != 0 { buf.WriteByte('.') - itoa(buf, int(ns%1e9)/1e3, 6) + itoa(buf, t.Nanosecond()/1e3, 6) } buf.WriteByte(' ') } @@ -133,7 +134,7 @@ func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, file string, line int // provided for generality, although at the moment on all pre-defined // paths it will be 2. func (l *Logger) Output(calldepth int, s string) error { - now := time.Nanoseconds() // get this early. + now := time.Now() // get this early. var file string var line int l.mu.Lock() diff --git a/libgo/go/math/abs.go b/libgo/go/math/abs.go index eb3e4c7..4c6297c 100644 --- a/libgo/go/math/abs.go +++ b/libgo/go/math/abs.go @@ -7,8 +7,7 @@ package math // Abs returns the absolute value of x. // // Special cases are: -// Abs(+Inf) = +Inf -// Abs(-Inf) = +Inf +// Abs(±Inf) = +Inf // Abs(NaN) = NaN func Abs(x float64) float64 { switch { diff --git a/libgo/go/math/asinh.go b/libgo/go/math/asinh.go index c1cad56..d697946 100644 --- a/libgo/go/math/asinh.go +++ b/libgo/go/math/asinh.go @@ -33,8 +33,7 @@ package math // Asinh(x) calculates the inverse hyperbolic sine of x. // // Special cases are: -// Asinh(+Inf) = +Inf -// Asinh(-Inf) = -Inf +// Asinh(±Inf) = ±Inf // Asinh(NaN) = NaN func Asinh(x float64) float64 { const ( diff --git a/libgo/go/math/big/calibrate_test.go b/libgo/go/math/big/calibrate_test.go index 1cd93b1..0950eee 100644 --- a/libgo/go/math/big/calibrate_test.go +++ b/libgo/go/math/big/calibrate_test.go @@ -22,14 +22,14 @@ import ( var calibrate = flag.Bool("calibrate", false, "run calibration test") // measure returns the time to run f -func measure(f func()) int64 { +func measure(f func()) time.Duration { const N = 100 - start := time.Nanoseconds() + start := time.Now() for i := N; i > 0; i-- { f() } - stop := time.Nanoseconds() - return (stop - start) / N + stop := time.Now() + return stop.Sub(start) / N } func computeThresholds() { @@ -46,7 +46,7 @@ func computeThresholds() { th1 := -1 th2 := -1 - var deltaOld int64 + var deltaOld time.Duration for count := -1; count != 0; count-- { // determine Tk, the work load execution time using Karatsuba multiplication karatsubaThreshold = n // enable karatsuba diff --git a/libgo/go/math/big/int_test.go b/libgo/go/math/big/int_test.go index 163c662..aa7c194 100644 --- a/libgo/go/math/big/int_test.go +++ b/libgo/go/math/big/int_test.go @@ -1242,10 +1242,14 @@ func TestBitSet(t *testing.T) { x.SetString(test.x, 0) b := x.Bit(test.i) if b != test.b { - - t.Errorf("#%d want %v got %v", i, test.b, b) + t.Errorf("#%d got %v want %v", i, b, test.b) } } + z := NewInt(1) + z.SetBit(NewInt(0), 2, 1) + if z.Cmp(NewInt(4)) != 0 { + t.Errorf("destination leaked into result; got %s want 4", z) + } } func BenchmarkBitset(b *testing.B) { diff --git a/libgo/go/math/big/nat.go b/libgo/go/math/big/nat.go index eee8ee3..680445d 100644 --- a/libgo/go/math/big/nat.go +++ b/libgo/go/math/big/nat.go @@ -21,7 +21,9 @@ package big import ( "errors" "io" + "math" "math/rand" + "sync" ) // An unsigned integer x of the form @@ -719,17 +721,17 @@ func (x nat) string(charset string) string { // special cases switch { - case b < 2 || b > 256: + case b < 2 || MaxBase < b: panic("illegal base") case len(x) == 0: return string(charset[0]) } // allocate buffer for conversion - i := x.bitLen()/log2(b) + 1 // +1: round up + i := int(float64(x.bitLen())/math.Log2(float64(b))) + 1 // off by one at most s := make([]byte, i) - // special case: power of two bases can avoid divisions completely + // convert power of two and non power of two bases separately if b == b&-b { // shift is base-b digit size in bits shift := uint(trailingZeroBits(b)) // shift > 0 because b >= 2 @@ -771,65 +773,209 @@ func (x nat) string(charset string) string { w >>= shift nbits -= shift } + } else { + // determine "big base" as in 10^19 for 19 decimal digits in a 64 bit Word + bb := Word(1) // big base is b**ndigits + ndigits := 0 // number of base b digits + for max := Word(_M / b); bb <= max; bb *= b { + ndigits++ // maximize ndigits where bb = b**ndigits, bb <= _M + } - return string(s[i:]) - } + // construct table of successive squares of bb*leafSize to use in subdivisions + table := divisors(len(x), b, ndigits, bb) - // general case: extract groups of digits by multiprecision division + // preserve x, create local copy for use in divisions + q := nat(nil).set(x) - // maximize ndigits where b**ndigits < 2^_W; bb (big base) is b**ndigits - bb := Word(1) - ndigits := 0 - for max := Word(_M / b); bb <= max; bb *= b { - ndigits++ + // convert q to string s in base b with index of MSD indicated by return value + i = q.convertWords(0, i, s, charset, b, ndigits, bb, table) } - // preserve x, create local copy for use in repeated divisions - q := nat(nil).set(x) - var r Word + return string(s[i:]) +} + +// Convert words of q to base b digits in s directly using iterated nat/Word divison to extract +// low-order Words and indirectly by recursive subdivision and nat/nat division by tabulated +// divisors. +// +// The direct method processes n Words by n divW() calls, each of which visits every Word in the +// incrementally shortened q for a total of n + (n-1) + (n-2) ... + 2 + 1, or n(n+1)/2 divW()'s. +// Indirect conversion divides q by its approximate square root, yielding two parts, each half +// the size of q. Using the direct method on both halves means 2 * (n/2)(n/2 + 1)/2 divW()'s plus +// the expensive long div(). Asymptotically, the ratio is favorable at 1/2 the divW()'s, and is +// made better by splitting the subblocks recursively. Best is to split blocks until one more +// split would take longer (because of the nat/nat div()) than the twice as many divW()'s of the +// direct approach. This threshold is represented by leafSize. Benchmarking of leafSize in the +// range 2..64 shows that values of 8 and 16 work well, with a 4x speedup at medium lengths and +// ~30x for 20000 digits. Use nat_test.go's BenchmarkLeafSize tests to optimize leafSize for +// specfic hardware. +// +// lo and hi index character array s. conversion starts with the LSD at hi and moves down toward +// the MSD, which will be at s[0] or s[1]. lo == 0 signals span includes the most significant word. +// +func (q nat) convertWords(lo, hi int, s []byte, charset string, b Word, ndigits int, bb Word, table []divisor) int { + // indirect conversion: split larger blocks to reduce quadratic expense of iterated nat/W division + if leafSize > 0 && len(q) > leafSize && table != nil { + var r nat + index := len(table) - 1 + for len(q) > leafSize { + // find divisor close to sqrt(q) if possible, but in any case < q + maxLength := q.bitLen() // ~= log2 q, or at of least largest possible q of this bit length + minLength := maxLength >> 1 // ~= log2 sqrt(q) + for index > 0 && table[index-1].nbits > minLength { + index-- // desired + } + if table[index].nbits >= maxLength && table[index].bbb.cmp(q) >= 0 { + index-- + if index < 0 { + panic("internal inconsistency") + } + } - // convert - if b == 10 { // hard-coding for 10 here speeds this up by 1.25x + // split q into the two digit number (q'*bbb + r) to form independent subblocks + q, r = q.div(r, q, table[index].bbb) + + // convert subblocks and collect results in s[lo:partition] and s[partition:hi] + partition := hi - table[index].ndigits + r.convertWords(partition, hi, s, charset, b, ndigits, bb, table[0:index]) + hi = partition // i.e., q.convertWords(lo, partition, s, charset, b, ndigits, bb, table[0:index+1]) + } + } // having split any large blocks now process the remaining small block + + // direct conversion: process smaller blocks monolithically to avoid overhead of nat/nat division + var r Word + if b == 10 { // hard-coding for 10 here speeds this up by 1.25x (allows mod as mul vs div) for len(q) > 0 { // extract least significant, base bb "digit" - q, r = q.divW(q, bb) // N.B. >82% of time is here. Optimize divW - if len(q) == 0 { + q, r = q.divW(q, bb) + if lo == 0 && len(q) == 0 { // skip leading zeros in most-significant group of digits for j := 0; j < ndigits && r != 0; j++ { - i-- - s[i] = charset[r%10] - r /= 10 + hi-- + t := r / 10 + s[hi] = charset[r-(t<<3+t<<1)] // 8*t + 2*t = 10*t; r - 10*int(r/10) = r mod 10 + r = t } } else { - for j := 0; j < ndigits; j++ { - i-- - s[i] = charset[r%10] - r /= 10 + for j := 0; j < ndigits && hi > lo; j++ { + hi-- + t := r / 10 + s[hi] = charset[r-(t<<3+t<<1)] // 8*t + 2*t = 10*t; r - 10*int(r/10) = r mod 10 + r = t } } } } else { for len(q) > 0 { // extract least significant group of digits - q, r = q.divW(q, bb) // N.B. >82% of time is here. Optimize divW - if len(q) == 0 { + q, r = q.divW(q, bb) + if lo == 0 && len(q) == 0 { // skip leading zeros in most-significant group of digits for j := 0; j < ndigits && r != 0; j++ { - i-- - s[i] = charset[r%b] - r /= b + hi-- + s[hi] = charset[r%b] + r = r / b } } else { - for j := 0; j < ndigits; j++ { - i-- - s[i] = charset[r%b] - r /= b + for j := 0; j < ndigits && hi > lo; j++ { + hi-- + s[hi] = charset[r%b] + r = r / b } } } } - return string(s[i:]) + // prepend high-order zeroes when q has been normalized to a short number of Words. + // however, do not prepend zeroes when converting the most dignificant digits. + if lo != 0 { // if not MSD + zero := charset[0] + for hi > lo { // while need more leading zeroes + hi-- + s[hi] = zero + } + } + + // return index of most significant output digit in s[] (stored in lowest index) + return hi +} + +// Split blocks greater than leafSize Words (or set to 0 to disable indirect conversion) +// Benchmark and configure leafSize using: gotest -test.bench="Leaf" +// 8 and 16 effective on 3.0 GHz Xeon "Clovertown" CPU (128 byte cache lines) +// 8 and 16 effective on 2.66 GHz Core 2 Duo "Penryn" CPU +var leafSize int = 8 // number of Word-size binary values treat as a monolithic block + +type divisor struct { + bbb nat // divisor + nbits int // bit length of divisor (discounting leading zeroes) ~= log2(bbb) + ndigits int // digit length of divisor in terms of output base digits +} + +const maxCache = 64 // maximum number of divisors in a single table +var cacheBase10 [maxCache]divisor // cached divisors for base 10 +var cacheLock sync.Mutex // defense against concurrent table extensions + +// construct table of powers of bb*leafSize to use in subdivisions +func divisors(m int, b Word, ndigits int, bb Word) []divisor { + // only build table when indirect conversion is enabled and x is large + if leafSize == 0 || m <= leafSize { + return nil + } + + // determine k where (bb**leafSize)**(2**k) >= sqrt(x) + k := 1 + for words := leafSize; words < m>>1 && k < maxCache; words <<= 1 { + k++ + } + + // create new table of divisors or extend and reuse existing table as appropriate + var cached bool + var table []divisor + switch b { + case 10: + table = cacheBase10[0:k] // reuse old table for this conversion + cached = true + default: + table = make([]divisor, k) // new table for this conversion + } + + // extend table + if table[k-1].ndigits == 0 { + if cached { + cacheLock.Lock() // begin critical section + } + + var i int + var larger nat + for i < k && table[i].ndigits != 0 { // skip existing entries + i++ + } + for ; i < k; i++ { // add new entries + if i == 0 { + table[i].bbb = nat(nil).expWW(bb, Word(leafSize)) + table[i].ndigits = ndigits * leafSize + } else { + table[i].bbb = nat(nil).mul(table[i-1].bbb, table[i-1].bbb) + table[i].ndigits = 2 * table[i-1].ndigits + } + + // optimization: exploit aggregated extra bits in macro blocks + larger = nat(nil).set(table[i].bbb) + for mulAddVWW(larger, larger, b, 0) == 0 { + table[i].bbb = table[i].bbb.set(larger) + table[i].ndigits++ + } + + table[i].nbits = table[i].bbb.bitLen() + } + + if cached { + cacheLock.Unlock() // end critical section + } + } + + return table } const deBruijn32 = 0x077CB531 @@ -919,9 +1065,11 @@ func (z nat) setBit(x nat, i uint, b uint) nat { return z.norm() case 1: if j >= n { - n = j + 1 + z = z.make(j + 1) + z[n:].clear() + } else { + z = z.make(n) } - z = z.make(n) copy(z, x) z[j] |= m // no need to normalize @@ -1140,7 +1288,12 @@ func (z nat) expNN(x, y, m nat) nat { } } - return z + return z.norm() +} + +// calculate x**y for Word arguments y and y +func (z nat) expWW(x, y Word) nat { + return z.expNN(nat(nil).setWord(x), nat(nil).setWord(y), nil) } // probablyPrime performs reps Miller-Rabin tests to check whether n is prime. diff --git a/libgo/go/math/big/nat_test.go b/libgo/go/math/big/nat_test.go index b208646..e3c6552 100644 --- a/libgo/go/math/big/nat_test.go +++ b/libgo/go/math/big/nat_test.go @@ -370,86 +370,34 @@ func BenchmarkScanPi(b *testing.B) { } } -const ( - // 314**271 - // base 2: 2249 digits - // base 8: 751 digits - // base 10: 678 digits - // base 16: 563 digits - shortBase = 314 - shortExponent = 271 - - // 3141**2178 - // base 2: 31577 digits - // base 8: 10527 digits - // base 10: 9507 digits - // base 16: 7895 digits - mediumBase = 3141 - mediumExponent = 2718 - - // 3141**2178 - // base 2: 406078 digits - // base 8: 135360 digits - // base 10: 122243 digits - // base 16: 101521 digits - longBase = 31415 - longExponent = 27182 -) - -func BenchmarkScanShort2(b *testing.B) { - ScanHelper(b, 2, shortBase, shortExponent) -} - -func BenchmarkScanShort8(b *testing.B) { - ScanHelper(b, 8, shortBase, shortExponent) -} - -func BenchmarkScanSort10(b *testing.B) { - ScanHelper(b, 10, shortBase, shortExponent) -} - -func BenchmarkScanShort16(b *testing.B) { - ScanHelper(b, 16, shortBase, shortExponent) -} - -func BenchmarkScanMedium2(b *testing.B) { - ScanHelper(b, 2, mediumBase, mediumExponent) -} - -func BenchmarkScanMedium8(b *testing.B) { - ScanHelper(b, 8, mediumBase, mediumExponent) -} - -func BenchmarkScanMedium10(b *testing.B) { - ScanHelper(b, 10, mediumBase, mediumExponent) -} - -func BenchmarkScanMedium16(b *testing.B) { - ScanHelper(b, 16, mediumBase, mediumExponent) -} - -func BenchmarkScanLong2(b *testing.B) { - ScanHelper(b, 2, longBase, longExponent) -} - -func BenchmarkScanLong8(b *testing.B) { - ScanHelper(b, 8, longBase, longExponent) -} - -func BenchmarkScanLong10(b *testing.B) { - ScanHelper(b, 10, longBase, longExponent) -} - -func BenchmarkScanLong16(b *testing.B) { - ScanHelper(b, 16, longBase, longExponent) -} - -func ScanHelper(b *testing.B, base int, xv, yv Word) { +func BenchmarkScan10Base2(b *testing.B) { ScanHelper(b, 2, 10, 10) } +func BenchmarkScan100Base2(b *testing.B) { ScanHelper(b, 2, 10, 100) } +func BenchmarkScan1000Base2(b *testing.B) { ScanHelper(b, 2, 10, 1000) } +func BenchmarkScan10000Base2(b *testing.B) { ScanHelper(b, 2, 10, 10000) } +func BenchmarkScan100000Base2(b *testing.B) { ScanHelper(b, 2, 10, 100000) } + +func BenchmarkScan10Base8(b *testing.B) { ScanHelper(b, 8, 10, 10) } +func BenchmarkScan100Base8(b *testing.B) { ScanHelper(b, 8, 10, 100) } +func BenchmarkScan1000Base8(b *testing.B) { ScanHelper(b, 8, 10, 1000) } +func BenchmarkScan10000Base8(b *testing.B) { ScanHelper(b, 8, 10, 10000) } +func BenchmarkScan100000Base8(b *testing.B) { ScanHelper(b, 8, 10, 100000) } + +func BenchmarkScan10Base10(b *testing.B) { ScanHelper(b, 10, 10, 10) } +func BenchmarkScan100Base10(b *testing.B) { ScanHelper(b, 10, 10, 100) } +func BenchmarkScan1000Base10(b *testing.B) { ScanHelper(b, 10, 10, 1000) } +func BenchmarkScan10000Base10(b *testing.B) { ScanHelper(b, 10, 10, 10000) } +func BenchmarkScan100000Base10(b *testing.B) { ScanHelper(b, 10, 10, 100000) } + +func BenchmarkScan10Base16(b *testing.B) { ScanHelper(b, 16, 10, 10) } +func BenchmarkScan100Base16(b *testing.B) { ScanHelper(b, 16, 10, 100) } +func BenchmarkScan1000Base16(b *testing.B) { ScanHelper(b, 16, 10, 1000) } +func BenchmarkScan10000Base16(b *testing.B) { ScanHelper(b, 16, 10, 10000) } +func BenchmarkScan100000Base16(b *testing.B) { ScanHelper(b, 16, 10, 100000) } + +func ScanHelper(b *testing.B, base int, x, y Word) { b.StopTimer() - var x, y, z nat - x = x.setWord(xv) - y = y.setWord(yv) - z = z.expNN(x, y, nil) + var z nat + z = z.expWW(x, y) var s string s = z.string(lowercaseDigits[0:base]) @@ -459,68 +407,112 @@ func ScanHelper(b *testing.B, base int, xv, yv Word) { b.StartTimer() for i := 0; i < b.N; i++ { - x.scan(strings.NewReader(s), base) + z.scan(strings.NewReader(s), base) } } -func BenchmarkStringShort2(b *testing.B) { - StringHelper(b, 2, shortBase, shortExponent) -} +func BenchmarkString10Base2(b *testing.B) { StringHelper(b, 2, 10, 10) } +func BenchmarkString100Base2(b *testing.B) { StringHelper(b, 2, 10, 100) } +func BenchmarkString1000Base2(b *testing.B) { StringHelper(b, 2, 10, 1000) } +func BenchmarkString10000Base2(b *testing.B) { StringHelper(b, 2, 10, 10000) } +func BenchmarkString100000Base2(b *testing.B) { StringHelper(b, 2, 10, 100000) } -func BenchmarkStringShort8(b *testing.B) { - StringHelper(b, 8, shortBase, shortExponent) -} +func BenchmarkString10Base8(b *testing.B) { StringHelper(b, 8, 10, 10) } +func BenchmarkString100Base8(b *testing.B) { StringHelper(b, 8, 10, 100) } +func BenchmarkString1000Base8(b *testing.B) { StringHelper(b, 8, 10, 1000) } +func BenchmarkString10000Base8(b *testing.B) { StringHelper(b, 8, 10, 10000) } +func BenchmarkString100000Base8(b *testing.B) { StringHelper(b, 8, 10, 100000) } -func BenchmarkStringShort10(b *testing.B) { - StringHelper(b, 10, shortBase, shortExponent) -} - -func BenchmarkStringShort16(b *testing.B) { - StringHelper(b, 16, shortBase, shortExponent) -} +func BenchmarkString10Base10(b *testing.B) { StringHelper(b, 10, 10, 10) } +func BenchmarkString100Base10(b *testing.B) { StringHelper(b, 10, 10, 100) } +func BenchmarkString1000Base10(b *testing.B) { StringHelper(b, 10, 10, 1000) } +func BenchmarkString10000Base10(b *testing.B) { StringHelper(b, 10, 10, 10000) } +func BenchmarkString100000Base10(b *testing.B) { StringHelper(b, 10, 10, 100000) } -func BenchmarkStringMedium2(b *testing.B) { - StringHelper(b, 2, mediumBase, mediumExponent) -} +func BenchmarkString10Base16(b *testing.B) { StringHelper(b, 16, 10, 10) } +func BenchmarkString100Base16(b *testing.B) { StringHelper(b, 16, 10, 100) } +func BenchmarkString1000Base16(b *testing.B) { StringHelper(b, 16, 10, 1000) } +func BenchmarkString10000Base16(b *testing.B) { StringHelper(b, 16, 10, 10000) } +func BenchmarkString100000Base16(b *testing.B) { StringHelper(b, 16, 10, 100000) } -func BenchmarkStringMedium8(b *testing.B) { - StringHelper(b, 8, mediumBase, mediumExponent) -} +func StringHelper(b *testing.B, base int, x, y Word) { + b.StopTimer() + var z nat + z = z.expWW(x, y) + z.string(lowercaseDigits[0:base]) // warm divisor cache + b.StartTimer() -func BenchmarkStringMedium10(b *testing.B) { - StringHelper(b, 10, mediumBase, mediumExponent) + for i := 0; i < b.N; i++ { + _ = z.string(lowercaseDigits[0:base]) + } } -func BenchmarkStringMedium16(b *testing.B) { - StringHelper(b, 16, mediumBase, mediumExponent) -} +func BenchmarkLeafSize0(b *testing.B) { LeafSizeHelper(b, 10, 0) } // test without splitting +func BenchmarkLeafSize1(b *testing.B) { LeafSizeHelper(b, 10, 1) } +func BenchmarkLeafSize2(b *testing.B) { LeafSizeHelper(b, 10, 2) } +func BenchmarkLeafSize3(b *testing.B) { LeafSizeHelper(b, 10, 3) } +func BenchmarkLeafSize4(b *testing.B) { LeafSizeHelper(b, 10, 4) } +func BenchmarkLeafSize5(b *testing.B) { LeafSizeHelper(b, 10, 5) } +func BenchmarkLeafSize6(b *testing.B) { LeafSizeHelper(b, 10, 6) } +func BenchmarkLeafSize7(b *testing.B) { LeafSizeHelper(b, 10, 7) } +func BenchmarkLeafSize8(b *testing.B) { LeafSizeHelper(b, 10, 8) } +func BenchmarkLeafSize9(b *testing.B) { LeafSizeHelper(b, 10, 9) } +func BenchmarkLeafSize10(b *testing.B) { LeafSizeHelper(b, 10, 10) } +func BenchmarkLeafSize11(b *testing.B) { LeafSizeHelper(b, 10, 11) } +func BenchmarkLeafSize12(b *testing.B) { LeafSizeHelper(b, 10, 12) } +func BenchmarkLeafSize13(b *testing.B) { LeafSizeHelper(b, 10, 13) } +func BenchmarkLeafSize14(b *testing.B) { LeafSizeHelper(b, 10, 14) } +func BenchmarkLeafSize15(b *testing.B) { LeafSizeHelper(b, 10, 15) } +func BenchmarkLeafSize16(b *testing.B) { LeafSizeHelper(b, 10, 16) } +func BenchmarkLeafSize32(b *testing.B) { LeafSizeHelper(b, 10, 32) } // try some large lengths +func BenchmarkLeafSize64(b *testing.B) { LeafSizeHelper(b, 10, 64) } + +func LeafSizeHelper(b *testing.B, base Word, size int) { + b.StopTimer() + originalLeafSize := leafSize + resetTable(cacheBase10[:]) + leafSize = size + b.StartTimer() -func BenchmarkStringLong2(b *testing.B) { - StringHelper(b, 2, longBase, longExponent) -} + for d := 1; d <= 10000; d *= 10 { + b.StopTimer() + var z nat + z = z.expWW(base, Word(d)) // build target number + _ = z.string(lowercaseDigits[0:base]) // warm divisor cache + b.StartTimer() -func BenchmarkStringLong8(b *testing.B) { - StringHelper(b, 8, longBase, longExponent) -} + for i := 0; i < b.N; i++ { + _ = z.string(lowercaseDigits[0:base]) + } + } -func BenchmarkStringLong10(b *testing.B) { - StringHelper(b, 10, longBase, longExponent) + b.StopTimer() + resetTable(cacheBase10[:]) + leafSize = originalLeafSize + b.StartTimer() } -func BenchmarkStringLong16(b *testing.B) { - StringHelper(b, 16, longBase, longExponent) +func resetTable(table []divisor) { + if table != nil && table[0].bbb != nil { + for i := 0; i < len(table); i++ { + table[i].bbb = nil + table[i].nbits = 0 + table[i].ndigits = 0 + } + } } -func StringHelper(b *testing.B, base int, xv, yv Word) { - b.StopTimer() - var x, y, z nat - x = x.setWord(xv) - y = y.setWord(yv) - z = z.expNN(x, y, nil) - b.StartTimer() - - for i := 0; i < b.N; i++ { - z.string(lowercaseDigits[0:base]) +func TestStringPowers(t *testing.T) { + var b, p Word + for b = 2; b <= 16; b++ { + for p = 0; p <= 512; p++ { + x := nat(nil).expWW(b, p) + xs := x.string(lowercaseDigits[0:b]) + xs2 := toString(x, lowercaseDigits[0:b]) + if xs != xs2 { + t.Errorf("failed at %d ** %d in base %d: %s != %s", b, p, b, xs, xs2) + } + } } } diff --git a/libgo/go/math/cbrt.go b/libgo/go/math/cbrt.go index d2b7e91..09edc0e 100644 --- a/libgo/go/math/cbrt.go +++ b/libgo/go/math/cbrt.go @@ -45,22 +45,21 @@ func Cbrt(x float64) float64 { x = -x sign = true } - // Reduce argument - f, e := Frexp(x) + // Reduce argument and estimate cube root + f, e := Frexp(x) // 0.5 <= f < 1.0 m := e % 3 if m > 0 { m -= 3 e -= m // e is multiple of 3 } - f = Ldexp(f, m) // 0.125 <= f < 1.0 - - // Estimate cube root switch m { case 0: // 0.5 <= f < 1.0 f = A1*f + A2 - A3/(A4+f) - case -1: // 0.25 <= f < 0.5 + case -1: + f *= 0.5 // 0.25 <= f < 0.5 f = B1*f + B2 - B3/(B4+f) - default: // 0.125 <= f < 0.25 + default: // m == -2 + f *= 0.25 // 0.125 <= f < 0.25 f = C1*f + C2 - C3/(C4+f) } y := Ldexp(f, e/3) // e/3 = exponent of cube root diff --git a/libgo/go/math/floor.go b/libgo/go/math/floor.go index babbf64..8de4d7e 100644 --- a/libgo/go/math/floor.go +++ b/libgo/go/math/floor.go @@ -7,8 +7,7 @@ package math // Floor returns the greatest integer value less than or equal to x. // // Special cases are: -// Floor(+Inf) = +Inf -// Floor(-Inf) = -Inf +// Floor(±Inf) = ±Inf // Floor(NaN) = NaN func Floor(x float64) float64 { // TODO(rsc): Remove manual inlining of IsNaN, IsInf @@ -30,16 +29,14 @@ func Floor(x float64) float64 { // Ceil returns the least integer value greater than or equal to x. // // Special cases are: -// Ceil(+Inf) = +Inf -// Ceil(-Inf) = -Inf +// Ceil(±Inf) = ±Inf // Ceil(NaN) = NaN func Ceil(x float64) float64 { return -Floor(-x) } // Trunc returns the integer value of x. // // Special cases are: -// Trunc(+Inf) = +Inf -// Trunc(-Inf) = -Inf +// Trunc(±Inf) = ±Inf // Trunc(NaN) = NaN func Trunc(x float64) float64 { // TODO(rsc): Remove manual inlining of IsNaN, IsInf diff --git a/libgo/go/math/gamma.go b/libgo/go/math/gamma.go index ae2c0c4..7365d8e 100644 --- a/libgo/go/math/gamma.go +++ b/libgo/go/math/gamma.go @@ -113,8 +113,7 @@ func stirling(x float64) float64 { // Gamma(x) returns the Gamma function of x. // // Special cases are: -// Gamma(Inf) = Inf -// Gamma(-Inf) = -Inf +// Gamma(±Inf) = ±Inf // Gamma(NaN) = NaN // Large values overflow to +Inf. // Negative integer values equal ±Inf. diff --git a/libgo/go/math/log1p.go b/libgo/go/math/log1p.go index c25d73b..e8914a1 100644 --- a/libgo/go/math/log1p.go +++ b/libgo/go/math/log1p.go @@ -44,7 +44,7 @@ package math // 2 4 6 8 10 12 14 // R(z) ~ Lp1*s +Lp2*s +Lp3*s +Lp4*s +Lp5*s +Lp6*s +Lp7*s // (the values of Lp1 to Lp7 are listed in the program) -// a-0.2929nd +// and // | 2 14 | -58.45 // | Lp1*s +...+Lp7*s - R(z) | <= 2 // | | @@ -88,6 +88,7 @@ package math // // Special cases are: // Log1p(+Inf) = +Inf +// Log1p(±0) = ±0 // Log1p(-1) = -Inf // Log1p(x < -1) = NaN // Log1p(NaN) = NaN diff --git a/libgo/go/math/modf.go b/libgo/go/math/modf.go index 315174b..34889e0 100644 --- a/libgo/go/math/modf.go +++ b/libgo/go/math/modf.go @@ -8,8 +8,7 @@ package math // that sum to f. Both values have the same sign as f. // // Special cases are: -// Modf(+Inf) = +Inf, NaN -// Modf(-Inf) = -Inf, NaN +// Modf(±Inf) = ±Inf, NaN // Modf(NaN) = NaN, NaN func Modf(f float64) (int float64, frac float64) { if f < 1 { diff --git a/libgo/go/math/sincos.go b/libgo/go/math/sincos.go index 4c1576b..f5412fd 100644 --- a/libgo/go/math/sincos.go +++ b/libgo/go/math/sincos.go @@ -4,10 +4,66 @@ package math +// Coefficients _sin[] and _cos[] are found in pkg/math/sin.go. + // Sincos(x) returns Sin(x), Cos(x). // // Special conditions are: -// Sincos(+Inf) = NaN, NaN -// Sincos(-Inf) = NaN, NaN +// Sincos(±0) = ±0, 1 +// Sincos(±Inf) = NaN, NaN // Sincos(NaN) = NaN, NaN -func Sincos(x float64) (sin, cos float64) { return Sin(x), Cos(x) } +func Sincos(x float64) (sin, cos float64) { + const ( + PI4A = 7.85398125648498535156E-1 // 0x3fe921fb40000000, Pi/4 split into three parts + PI4B = 3.77489470793079817668E-8 // 0x3e64442d00000000, + PI4C = 2.69515142907905952645E-15 // 0x3ce8469898cc5170, + M4PI = 1.273239544735162542821171882678754627704620361328125 // 4/pi + ) + // TODO(rsc): Remove manual inlining of IsNaN, IsInf + // when compiler does it for us + // special cases + switch { + case x == 0: + return x, 1 // return ±0.0, 1.0 + case x != x || x < -MaxFloat64 || x > MaxFloat64: // IsNaN(x) || IsInf(x, 0): + return NaN(), NaN() + } + + // make argument positive + sinSign, cosSign := false, false + if x < 0 { + x = -x + sinSign = true + } + + j := int64(x * M4PI) // integer part of x/(Pi/4), as integer for tests on the phase angle + y := float64(j) // integer part of x/(Pi/4), as float + + if j&1 == 1 { // map zeros to origin + j += 1 + y += 1 + } + j &= 7 // octant modulo 2Pi radians (360 degrees) + if j > 3 { // reflect in x axis + j -= 4 + sinSign, cosSign = !sinSign, !cosSign + } + if j > 1 { + cosSign = !cosSign + } + + z := ((x - y*PI4A) - y*PI4B) - y*PI4C // Extended precision modular arithmetic + zz := z * z + cos = 1.0 - 0.5*zz + zz*zz*((((((_cos[0]*zz)+_cos[1])*zz+_cos[2])*zz+_cos[3])*zz+_cos[4])*zz+_cos[5]) + sin = z + z*zz*((((((_sin[0]*zz)+_sin[1])*zz+_sin[2])*zz+_sin[3])*zz+_sin[4])*zz+_sin[5]) + if j == 1 || j == 2 { + sin, cos = cos, sin + } + if cosSign { + cos = -cos + } + if sinSign { + sin = -sin + } + return +} diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go index bab5f2a..79a958e 100644 --- a/libgo/go/net/dnsclient_unix.go +++ b/libgo/go/net/dnsclient_unix.go @@ -29,7 +29,7 @@ func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error return nil, &DNSError{Err: "name too long", Name: name} } out := new(dnsMsg) - out.id = uint16(rand.Int()) ^ uint16(time.Nanoseconds()) + out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano()) out.question = []dnsQuestion{ {name, qtype, dnsClassINET}, } diff --git a/libgo/go/net/fd.go b/libgo/go/net/fd.go index 70e04a2..5318c51 100644 --- a/libgo/go/net/fd.go +++ b/libgo/go/net/fd.go @@ -171,7 +171,7 @@ func (s *pollServer) WakeFD(fd *netFD, mode int) { } func (s *pollServer) Now() int64 { - return time.Nanoseconds() + return time.Now().UnixNano() } func (s *pollServer) CheckDeadlines() { diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go index 7a16023..264b918 100644 --- a/libgo/go/net/fd_windows.go +++ b/libgo/go/net/fd_windows.go @@ -172,11 +172,12 @@ func (s *ioSrv) ExecIO(oi anOpIface, deadline_delta int64) (n int, err error) { return 0, &OpError{oi.Name(), o.fd.net, o.fd.laddr, e} } // Wait for our request to complete. + // TODO(rsc): This should stop the timer. var r ioResult if deadline_delta > 0 { select { case r = <-o.resultc: - case <-time.After(deadline_delta): + case <-time.After(time.Duration(deadline_delta) * time.Nanosecond): s.canchan <- oi <-o.errnoc r = <-o.resultc diff --git a/libgo/go/net/hosts.go b/libgo/go/net/hosts.go index ddfb074..e6674ba 100644 --- a/libgo/go/net/hosts.go +++ b/libgo/go/net/hosts.go @@ -11,7 +11,7 @@ import ( "time" ) -const cacheMaxAge = int64(300) // 5 minutes. +const cacheMaxAge = 5 * time.Minute // hostsPath points to the file with static IP/address entries. var hostsPath = "/etc/hosts" @@ -21,14 +21,14 @@ var hosts struct { sync.Mutex byName map[string][]string byAddr map[string][]string - time int64 + expire time.Time path string } func readHosts() { - now := time.Seconds() + now := time.Now() hp := hostsPath - if len(hosts.byName) == 0 || hosts.time+cacheMaxAge <= now || hosts.path != hp { + if len(hosts.byName) == 0 || now.After(hosts.expire) || hosts.path != hp { hs := make(map[string][]string) is := make(map[string][]string) var file *file @@ -51,7 +51,7 @@ func readHosts() { } } // Update the data cache. - hosts.time = time.Seconds() + hosts.expire = time.Now().Add(cacheMaxAge) hosts.path = hp hosts.byName = hs hosts.byAddr = is diff --git a/libgo/go/net/http/cgi/host_test.go b/libgo/go/net/http/cgi/host_test.go index e6e85e8..849cb00 100644 --- a/libgo/go/net/http/cgi/host_test.go +++ b/libgo/go/net/http/cgi/host_test.go @@ -365,7 +365,7 @@ func TestCopyError(t *testing.T) { tries := 0 for tries < 15 && childRunning() { - time.Sleep(50e6 * int64(tries)) + time.Sleep(50 * time.Millisecond * time.Duration(tries)) tries++ } if childRunning() { diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index 6935014..cad8522 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -115,7 +115,7 @@ func readSetCookies(h Header) []*Cookie { break } } - c.Expires = *exptime + c.Expires = exptime.UTC() continue case "path": c.Path = val @@ -146,8 +146,8 @@ func (c *Cookie) String() string { if len(c.Domain) > 0 { fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) } - if len(c.Expires.Zone) > 0 { - fmt.Fprintf(&b, "; Expires=%s", c.Expires.Format(time.RFC1123)) + if c.Expires.Unix() > 0 { + fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) } if c.MaxAge > 0 { fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index 24adf20..26bff93 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -123,7 +123,7 @@ var readSetCookiesTests = []struct { Path: "/", Domain: ".google.ch", HttpOnly: true, - Expires: time.Time{Year: 2011, Month: 11, Day: 23, Hour: 1, Minute: 5, Second: 3, ZoneOffset: 0, Zone: "GMT"}, + Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC), RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT", Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly", }}, diff --git a/libgo/go/net/http/export_test.go b/libgo/go/net/http/export_test.go index 3fe6586..13640ca8 100644 --- a/libgo/go/net/http/export_test.go +++ b/libgo/go/net/http/export_test.go @@ -7,6 +7,8 @@ package http +import "time" + func (t *Transport) IdleConnKeysForTesting() (keys []string) { keys = make([]string, 0) t.lk.Lock() @@ -33,8 +35,8 @@ func (t *Transport) IdleConnCountForTesting(cacheKey string) int { return len(conns) } -func NewTestTimeoutHandler(handler Handler, ch <-chan int64) Handler { - f := func() <-chan int64 { +func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { + f := func() <-chan time.Time { return ch } return &timeoutHandler{handler, f, ""} diff --git a/libgo/go/net/http/fcgi/child.go b/libgo/go/net/http/fcgi/child.go index 529440c..c94b9a7 100644 --- a/libgo/go/net/http/fcgi/child.go +++ b/libgo/go/net/http/fcgi/child.go @@ -103,7 +103,7 @@ func (r *response) WriteHeader(code int) { } if r.header.Get("Date") == "" { - r.header.Set("Date", time.UTC().Format(http.TimeFormat)) + r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) } fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code)) diff --git a/libgo/go/net/http/fs.go b/libgo/go/net/http/fs.go index 5aadac1..70e7849 100644 --- a/libgo/go/net/http/fs.go +++ b/libgo/go/net/http/fs.go @@ -52,7 +52,7 @@ type FileSystem interface { // served by the FileServer implementation. type File interface { Close() error - Stat() (*os.FileInfo, error) + Stat() (os.FileInfo, error) Readdir(count int) ([]os.FileInfo, error) Read([]byte) (int, error) Seek(offset int64, whence int) (int64, error) @@ -93,8 +93,8 @@ func dirList(w ResponseWriter, f File) { break } for _, d := range dirs { - name := d.Name - if d.IsDirectory() { + name := d.Name() + if d.IsDir() { name += "/" } // TODO htmlescape @@ -135,7 +135,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec // redirect to canonical path: / at end of directory url // r.URL.Path always begins with / url := r.URL.Path - if d.IsDirectory() { + if d.IsDir() { if url[len(url)-1] != '/' { localRedirect(w, r, path.Base(url)+"/") return @@ -148,14 +148,14 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec } } - if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() { + if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && !d.ModTime().After(t) { w.WriteHeader(StatusNotModified) return } - w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat)) + w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat)) // use contents of index.html for directory, if present - if d.IsDirectory() { + if d.IsDir() { index := name + indexPage ff, err := fs.Open(index) if err == nil { @@ -169,13 +169,13 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec } } - if d.IsDirectory() { + if d.IsDir() { dirList(w, f) return } // serve file - size := d.Size + size := d.Size() code := StatusOK // If Content-Type isn't set, use the file's extension to find it. @@ -215,7 +215,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec } size = ra.length code = StatusPartialContent - w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size)) + w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size())) } w.Header().Set("Accept-Ranges", "bytes") diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go index 6697189..976ee75 100644 --- a/libgo/go/net/http/fs_test.go +++ b/libgo/go/net/http/fs_test.go @@ -190,8 +190,8 @@ func TestDirJoin(t *testing.T) { if err != nil { t.Fatalf("stat of %s: %v", name, err) } - if gfi.Ino != wfi.Ino { - t.Errorf("%s got different inode", name) + if !gfi.(*os.FileStat).SameFile(wfi.(*os.FileStat)) { + t.Errorf("%s got different file", name) } } test(Dir("/etc/"), "/hosts") diff --git a/libgo/go/net/http/httptest/server.go b/libgo/go/net/http/httptest/server.go index f09e826..5b02e14 100644 --- a/libgo/go/net/http/httptest/server.go +++ b/libgo/go/net/http/httptest/server.go @@ -7,14 +7,12 @@ package httptest import ( - "crypto/rand" "crypto/tls" "flag" "fmt" "net" "net/http" "os" - "time" ) // A Server is an HTTP server listening on a system-chosen port on the @@ -113,8 +111,6 @@ func (s *Server) StartTLS() { } s.TLS = &tls.Config{ - Rand: rand.Reader, - Time: time.Seconds, NextProtos: []string{"http/1.1"}, Certificates: []tls.Certificate{cert}, } diff --git a/libgo/go/net/http/httputil/reverseproxy.go b/libgo/go/net/http/httputil/reverseproxy.go index bfcb3ca..1dc83e7 100644 --- a/libgo/go/net/http/httputil/reverseproxy.go +++ b/libgo/go/net/http/httputil/reverseproxy.go @@ -31,11 +31,11 @@ type ReverseProxy struct { // If nil, http.DefaultTransport is used. Transport http.RoundTripper - // FlushInterval specifies the flush interval, in - // nanoseconds, to flush to the client while - // coping the response body. + // FlushInterval specifies the flush interval + // to flush to the client while copying the + // response body. // If zero, no periodic flushing is done. - FlushInterval int64 + FlushInterval time.Duration } func singleJoiningSlash(a, b string) string { @@ -135,7 +135,7 @@ type writeFlusher interface { type maxLatencyWriter struct { dst writeFlusher - latency int64 // nanos + latency time.Duration lk sync.Mutex // protects init of done, as well Write + Flush done chan bool diff --git a/libgo/go/net/http/pprof/pprof.go b/libgo/go/net/http/pprof/pprof.go index c0327a9..2de1475 100644 --- a/libgo/go/net/http/pprof/pprof.go +++ b/libgo/go/net/http/pprof/pprof.go @@ -80,7 +80,7 @@ func Profile(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Could not enable CPU profiling: %s\n", err) return } - time.Sleep(sec * 1e9) + time.Sleep(time.Duration(sec) * time.Second) pprof.StopCPUProfile() } diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index 97a0b13..670b541 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -266,19 +266,19 @@ func TestServerTimeouts(t *testing.T) { } // Slow client that should timeout. - t1 := time.Nanoseconds() + t1 := time.Now() conn, err := net.Dial("tcp", addr.String()) if err != nil { t.Fatalf("Dial: %v", err) } buf := make([]byte, 1) n, err := conn.Read(buf) - latency := time.Nanoseconds() - t1 + latency := time.Now().Sub(t1) if n != 0 || err != io.EOF { t.Errorf("Read = %v, %v, wanted %v, %v", n, err, 0, io.EOF) } - if latency < second*0.20 /* fudge from 0.25 above */ { - t.Errorf("got EOF after %d ns, want >= %d", latency, second*0.20) + if latency < 200*time.Millisecond /* fudge from 0.25 above */ { + t.Errorf("got EOF after %s, want >= %s", latency, 200*time.Millisecond) } // Hit the HTTP server successfully again, verifying that the @@ -760,7 +760,7 @@ func TestTimeoutHandler(t *testing.T) { _, werr := w.Write([]byte("hi")) writeErrors <- werr }) - timeout := make(chan int64, 1) // write to this to force timeouts + timeout := make(chan time.Time, 1) // write to this to force timeouts ts := httptest.NewServer(NewTestTimeoutHandler(sayHi, timeout)) defer ts.Close() @@ -782,7 +782,7 @@ func TestTimeoutHandler(t *testing.T) { } // Times out: - timeout <- 1 + timeout <- time.Time{} res, err = Get(ts.URL) if err != nil { t.Error(err) diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index 7221d25..125f3f2 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -347,7 +347,7 @@ func (w *response) WriteHeader(code int) { } if _, ok := w.header["Date"]; !ok { - w.Header().Set("Date", time.UTC().Format(TimeFormat)) + w.Header().Set("Date", time.Now().UTC().Format(TimeFormat)) } te := w.header.Get("Transfer-Encoding") @@ -467,7 +467,7 @@ func (w *response) Write(data []byte) (n int, err error) { // determine the content type. Accumulate the // initial writes in w.conn.body. // Cap m so that append won't allocate. - m := cap(w.conn.body) - len(w.conn.body) + m = cap(w.conn.body) - len(w.conn.body) if m > len(data) { m = len(data) } @@ -1013,8 +1013,8 @@ func (srv *Server) Serve(l net.Listener) error { // package main // // import ( -// "http" // "io" +// "net/http" // "log" // ) // @@ -1044,8 +1044,8 @@ func ListenAndServe(addr string, handler Handler) error { // A trivial example server is: // // import ( -// "http" // "log" +// "net/http" // ) // // func handler(w http.ResponseWriter, req *http.Request) { @@ -1084,7 +1084,6 @@ func (s *Server) ListenAndServeTLS(certFile, keyFile string) error { } config := &tls.Config{ Rand: rand.Reader, - Time: time.Seconds, NextProtos: []string{"http/1.1"}, } @@ -1112,9 +1111,9 @@ func (s *Server) ListenAndServeTLS(certFile, keyFile string) error { // (If msg is empty, a suitable default message will be sent.) // After such a timeout, writes by h to its ResponseWriter will return // ErrHandlerTimeout. -func TimeoutHandler(h Handler, ns int64, msg string) Handler { - f := func() <-chan int64 { - return time.After(ns) +func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler { + f := func() <-chan time.Time { + return time.After(dt) } return &timeoutHandler{h, f, msg} } @@ -1125,7 +1124,7 @@ var ErrHandlerTimeout = errors.New("http: Handler timeout") type timeoutHandler struct { handler Handler - timeout func() <-chan int64 // returns channel producing a timeout + timeout func() <-chan time.Time // returns channel producing a timeout body string } diff --git a/libgo/go/net/http/sniff.go b/libgo/go/net/http/sniff.go index 5707c7f..c1c78e2 100644 --- a/libgo/go/net/http/sniff.go +++ b/libgo/go/net/http/sniff.go @@ -48,23 +48,23 @@ type sniffSig interface { // Data matching the table in section 6. var sniffSignatures = []sniffSig{ - htmlSig([]byte("<!DOCTYPE HTML")), - htmlSig([]byte("<HTML")), - htmlSig([]byte("<HEAD")), - htmlSig([]byte("<SCRIPT")), - htmlSig([]byte("<IFRAME")), - htmlSig([]byte("<H1")), - htmlSig([]byte("<DIV")), - htmlSig([]byte("<FONT")), - htmlSig([]byte("<TABLE")), - htmlSig([]byte("<A")), - htmlSig([]byte("<STYLE")), - htmlSig([]byte("<TITLE")), - htmlSig([]byte("<B")), - htmlSig([]byte("<BODY")), - htmlSig([]byte("<BR")), - htmlSig([]byte("<P")), - htmlSig([]byte("<!--")), + htmlSig("<!DOCTYPE HTML"), + htmlSig("<HTML"), + htmlSig("<HEAD"), + htmlSig("<SCRIPT"), + htmlSig("<IFRAME"), + htmlSig("<H1"), + htmlSig("<DIV"), + htmlSig("<FONT"), + htmlSig("<TABLE"), + htmlSig("<A"), + htmlSig("<STYLE"), + htmlSig("<TITLE"), + htmlSig("<B"), + htmlSig("<BODY"), + htmlSig("<BR"), + htmlSig("<P"), + htmlSig("<!--"), &maskedSig{mask: []byte("\xFF\xFF\xFF\xFF\xFF"), pat: []byte("<?xml"), skipWS: true, ct: "text/xml; charset=utf-8"}, diff --git a/libgo/go/net/http/sniff_test.go b/libgo/go/net/http/sniff_test.go index 86744ee..6efa8ce 100644 --- a/libgo/go/net/http/sniff_test.go +++ b/libgo/go/net/http/sniff_test.go @@ -6,12 +6,14 @@ package http_test import ( "bytes" + "fmt" "io" "io/ioutil" "log" . "net/http" "net/http/httptest" "strconv" + "strings" "testing" ) @@ -112,3 +114,24 @@ func TestContentTypeWithCopy(t *testing.T) { } resp.Body.Close() } + +func TestSniffWriteSize(t *testing.T) { + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + size, _ := strconv.Atoi(r.FormValue("size")) + written, err := io.WriteString(w, strings.Repeat("a", size)) + if err != nil { + t.Errorf("write of %d bytes: %v", size, err) + return + } + if written != size { + t.Errorf("write of %d bytes wrote %d bytes", size, written) + } + })) + defer ts.Close() + for _, size := range []int{0, 1, 200, 600, 999, 1000, 1023, 1024, 512 << 10, 1 << 20} { + _, err := Get(fmt.Sprintf("%s/?size=%d", ts.URL, size)) + if err != nil { + t.Fatalf("size %d: %v", size, err) + } + } +} diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index 7729797..6f50f6f 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -263,7 +263,7 @@ func TestTransportServerClosingUnexpectedly(t *testing.T) { t.Fatalf(format, arg...) } t.Logf("retrying shortly after expected error: "+format, arg...) - time.Sleep(1e9 / int64(retries)) + time.Sleep(time.Second / time.Duration(retries)) } for retries >= 0 { retries-- diff --git a/libgo/go/net/mail/message.go b/libgo/go/net/mail/message.go index 95246b2..e1afa32 100644 --- a/libgo/go/net/mail/message.go +++ b/libgo/go/net/mail/message.go @@ -89,14 +89,14 @@ func init() { } } -func parseDate(date string) (*time.Time, error) { +func parseDate(date string) (time.Time, error) { for _, layout := range dateLayouts { t, err := time.Parse(layout, date) if err == nil { return t, nil } } - return nil, errors.New("mail: header could not be parsed") + return time.Time{}, errors.New("mail: header could not be parsed") } // A Header represents the key-value pairs in a mail message header. @@ -111,10 +111,10 @@ func (h Header) Get(key string) string { var ErrHeaderNotPresent = errors.New("mail: header not in message") // Date parses the Date header field. -func (h Header) Date() (*time.Time, error) { +func (h Header) Date() (time.Time, error) { hdr := h.Get("Date") if hdr == "" { - return nil, ErrHeaderNotPresent + return time.Time{}, ErrHeaderNotPresent } return parseDate(hdr) } @@ -185,7 +185,7 @@ func (a *Address) String() string { type addrParser []byte func newAddrParser(s string) *addrParser { - p := addrParser([]byte(s)) + p := addrParser(s) return &p } diff --git a/libgo/go/net/mail/message_test.go b/libgo/go/net/mail/message_test.go index 5653647..1f71cc4 100644 --- a/libgo/go/net/mail/message_test.go +++ b/libgo/go/net/mail/message_test.go @@ -82,34 +82,18 @@ func headerEq(a, b Header) bool { func TestDateParsing(t *testing.T) { tests := []struct { dateStr string - exp *time.Time + exp time.Time }{ // RFC 5322, Appendix A.1.1 { "Fri, 21 Nov 1997 09:55:06 -0600", - &time.Time{ - Year: 1997, - Month: 11, - Day: 21, - Hour: 9, - Minute: 55, - Second: 6, - ZoneOffset: -6 * 60 * 60, - }, + time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)), }, // RFC5322, Appendix A.6.2 // Obsolete date. { "21 Nov 97 09:55:06 GMT", - &time.Time{ - Year: 1997, - Month: 11, - Day: 21, - Hour: 9, - Minute: 55, - Second: 6, - Zone: "GMT", - }, + time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)), }, } for _, test := range tests { diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go index 3c884ca..f6e5238 100644 --- a/libgo/go/net/timeout_test.go +++ b/libgo/go/net/timeout_test.go @@ -17,7 +17,7 @@ func testTimeout(t *testing.T, network, addr string, readFrom bool) { return } defer fd.Close() - t0 := time.Nanoseconds() + t0 := time.Now() fd.SetReadTimeout(1e8) // 100ms var b [100]byte var n int @@ -27,7 +27,7 @@ func testTimeout(t *testing.T, network, addr string, readFrom bool) { } else { n, err1 = fd.Read(b[0:]) } - t1 := time.Nanoseconds() + t1 := time.Now() what := "Read" if readFrom { what = "ReadFrom" @@ -35,8 +35,8 @@ func testTimeout(t *testing.T, network, addr string, readFrom bool) { if n != 0 || err1 == nil || !err1.(Error).Timeout() { t.Errorf("fd.%s on %s %s did not return 0, timeout: %v, %v", what, network, addr, n, err1) } - if t1-t0 < 0.5e8 || t1-t0 > 1.5e8 { - t.Errorf("fd.%s on %s %s took %f seconds, expected 0.1", what, network, addr, float64(t1-t0)/1e9) + if dt := t1.Sub(t0); dt < 50*time.Millisecond || dt > 150*time.Millisecond { + t.Errorf("fd.%s on %s %s took %s, expected 0.1s", what, network, addr, dt) } } diff --git a/libgo/go/old/netchan/common.go b/libgo/go/old/netchan/common.go index dfd1fd0..03fa8ff 100644 --- a/libgo/go/old/netchan/common.go +++ b/libgo/go/old/netchan/common.go @@ -129,8 +129,8 @@ func (ed *encDec) encode(hdr *header, payloadType int, payload interface{}) erro } // See the comment for Exporter.Drain. -func (cs *clientSet) drain(timeout int64) error { - startTime := time.Nanoseconds() +func (cs *clientSet) drain(timeout time.Duration) error { + deadline := time.Now().Add(timeout) for { pending := false cs.mu.Lock() @@ -152,7 +152,7 @@ func (cs *clientSet) drain(timeout int64) error { if !pending { break } - if timeout > 0 && time.Nanoseconds()-startTime >= timeout { + if timeout > 0 && time.Now().After(deadline) { return errors.New("timeout") } time.Sleep(100 * 1e6) // 100 milliseconds @@ -161,8 +161,8 @@ func (cs *clientSet) drain(timeout int64) error { } // See the comment for Exporter.Sync. -func (cs *clientSet) sync(timeout int64) error { - startTime := time.Nanoseconds() +func (cs *clientSet) sync(timeout time.Duration) error { + deadline := time.Now().Add(timeout) // seq remembers the clients and their seqNum at point of entry. seq := make(map[unackedCounter]int64) for client := range cs.clients { @@ -185,7 +185,7 @@ func (cs *clientSet) sync(timeout int64) error { if !pending { break } - if timeout > 0 && time.Nanoseconds()-startTime >= timeout { + if timeout > 0 && time.Now().After(deadline) { return errors.New("timeout") } time.Sleep(100 * 1e6) // 100 milliseconds diff --git a/libgo/go/old/netchan/export.go b/libgo/go/old/netchan/export.go index d698dd5..d94c4b1 100644 --- a/libgo/go/old/netchan/export.go +++ b/libgo/go/old/netchan/export.go @@ -29,6 +29,7 @@ import ( "reflect" "strconv" "sync" + "time" ) // Export @@ -322,9 +323,9 @@ func (exp *Exporter) delClient(client *expClient) { // those not yet sent to any client and possibly including those sent while // Drain was executing, have been received by the importer. In short, it // waits until all the exporter's messages have been received by a client. -// If the timeout (measured in nanoseconds) is positive and Drain takes -// longer than that to complete, an error is returned. -func (exp *Exporter) Drain(timeout int64) error { +// If the timeout is positive and Drain takes longer than that to complete, +// an error is returned. +func (exp *Exporter) Drain(timeout time.Duration) error { // This wrapper function is here so the method's comment will appear in godoc. return exp.clientSet.drain(timeout) } @@ -332,10 +333,9 @@ func (exp *Exporter) Drain(timeout int64) error { // Sync waits until all clients of the exporter have received the messages // that were sent at the time Sync was invoked. Unlike Drain, it does not // wait for messages sent while it is running or messages that have not been -// dispatched to any client. If the timeout (measured in nanoseconds) is -// positive and Sync takes longer than that to complete, an error is -// returned. -func (exp *Exporter) Sync(timeout int64) error { +// dispatched to any client. If the timeout is positive and Sync takes longer +// than that to complete, an error is returned. +func (exp *Exporter) Sync(timeout time.Duration) error { // This wrapper function is here so the method's comment will appear in godoc. return exp.clientSet.sync(timeout) } diff --git a/libgo/go/old/netchan/import.go b/libgo/go/old/netchan/import.go index 7243672..a6da821 100644 --- a/libgo/go/old/netchan/import.go +++ b/libgo/go/old/netchan/import.go @@ -276,9 +276,9 @@ func (imp *Importer) unackedCount() int64 { // If the timeout (measured in nanoseconds) is positive and Drain takes // longer than that to complete, an error is returned. func (imp *Importer) Drain(timeout int64) error { - startTime := time.Nanoseconds() + deadline := time.Now().Add(time.Duration(timeout)) for imp.unackedCount() > 0 { - if timeout > 0 && time.Nanoseconds()-startTime >= timeout { + if timeout > 0 && time.Now().After(deadline) { return errors.New("timeout") } time.Sleep(100 * 1e6) diff --git a/libgo/go/os/exec/lp_unix.go b/libgo/go/os/exec/lp_unix.go index d234641..9665ea8 100644 --- a/libgo/go/os/exec/lp_unix.go +++ b/libgo/go/os/exec/lp_unix.go @@ -20,7 +20,7 @@ func findExecutable(file string) error { if err != nil { return err } - if d.IsRegular() && d.Permission()&0111 != 0 { + if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } return os.EPERM diff --git a/libgo/go/os/exec/lp_windows.go b/libgo/go/os/exec/lp_windows.go index db32623..ef5bd92 100644 --- a/libgo/go/os/exec/lp_windows.go +++ b/libgo/go/os/exec/lp_windows.go @@ -18,10 +18,10 @@ func chkStat(file string) error { if err != nil { return err } - if d.IsRegular() { - return nil + if d.IsDir() { + return os.EPERM } - return os.EPERM + return nil } func findExecutable(file string, exts []string) (string, error) { diff --git a/libgo/go/os/export_test.go b/libgo/go/os/export_test.go new file mode 100644 index 0000000..9c6ef42 --- /dev/null +++ b/libgo/go/os/export_test.go @@ -0,0 +1,9 @@ +// Copyright 2011 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 + +// Export for testing. + +var Atime = atime diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index 386afb8..71845d3 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -14,7 +14,7 @@ import ( ) // Name returns the name of the file as presented to Open. -func (file *File) Name() string { return file.name } +func (f *File) Name() string { return f.name } // Stdin, Stdout, and Stderr are open Files pointing to the standard input, // standard output, and standard error file descriptors. @@ -51,11 +51,11 @@ const ( // Read reads up to len(b) bytes from the File. // It returns the number of bytes read and an error, if any. // EOF is signaled by a zero count with err set to io.EOF. -func (file *File) Read(b []byte) (n int, err error) { - if file == nil { +func (f *File) Read(b []byte) (n int, err error) { + if f == nil { return 0, EINVAL } - n, e := file.read(b) + n, e := f.read(b) if n < 0 { n = 0 } @@ -63,26 +63,26 @@ func (file *File) Read(b []byte) (n int, err error) { return 0, io.EOF } if e != nil { - err = &PathError{"read", file.name, e} + err = &PathError{"read", f.name, e} } return n, err } // ReadAt reads len(b) bytes from the File starting at byte offset off. // It returns the number of bytes read and the error, if any. -// EOF is signaled by a zero count with err set to io.EOF. -// ReadAt always returns a non-nil error when n != len(b). -func (file *File) ReadAt(b []byte, off int64) (n int, err error) { - if file == nil { +// ReadAt always returns a non-nil error when n < len(b). +// At end of file, that error is io.EOF. +func (f *File) ReadAt(b []byte, off int64) (n int, err error) { + if f == nil { return 0, EINVAL } for len(b) > 0 { - m, e := file.pread(b, off) + m, e := f.pread(b, off) if m == 0 && e == nil { return n, io.EOF } if e != nil { - err = &PathError{"read", file.name, e} + err = &PathError{"read", f.name, e} break } n += m @@ -95,19 +95,19 @@ func (file *File) ReadAt(b []byte, off int64) (n int, err error) { // Write writes len(b) bytes to the File. // It returns the number of bytes written and an error, if any. // Write returns a non-nil error when n != len(b). -func (file *File) Write(b []byte) (n int, err error) { - if file == nil { +func (f *File) Write(b []byte) (n int, err error) { + if f == nil { return 0, EINVAL } - n, e := file.write(b) + n, e := f.write(b) if n < 0 { n = 0 } - epipecheck(file, e) + epipecheck(f, e) if e != nil { - err = &PathError{"write", file.name, e} + err = &PathError{"write", f.name, e} } return n, err } @@ -115,14 +115,14 @@ func (file *File) Write(b []byte) (n int, err error) { // WriteAt writes len(b) bytes to the File starting at byte offset off. // It returns the number of bytes written and an error, if any. // WriteAt returns a non-nil error when n != len(b). -func (file *File) WriteAt(b []byte, off int64) (n int, err error) { - if file == nil { +func (f *File) WriteAt(b []byte, off int64) (n int, err error) { + if f == nil { return 0, EINVAL } for len(b) > 0 { - m, e := file.pwrite(b, off) + m, e := f.pwrite(b, off) if e != nil { - err = &PathError{"write", file.name, e} + err = &PathError{"write", f.name, e} break } n += m @@ -136,24 +136,24 @@ func (file *File) WriteAt(b []byte, off int64) (n int, err error) { // according to whence: 0 means relative to the origin of the file, 1 means // relative to the current offset, and 2 means relative to the end. // It returns the new offset and an error, if any. -func (file *File) Seek(offset int64, whence int) (ret int64, err error) { - r, e := file.seek(offset, whence) - if e == nil && file.dirinfo != nil && r != 0 { +func (f *File) Seek(offset int64, whence int) (ret int64, err error) { + r, e := f.seek(offset, whence) + if e == nil && f.dirinfo != nil && r != 0 { e = syscall.EISDIR } if e != nil { - return 0, &PathError{"seek", file.name, e} + return 0, &PathError{"seek", f.name, e} } return r, nil } // WriteString is like Write, but writes the contents of string s rather than // an array of bytes. -func (file *File) WriteString(s string) (ret int, err error) { - if file == nil { +func (f *File) WriteString(s string) (ret int, err error) { + if f == nil { return 0, EINVAL } - return file.Write([]byte(s)) + return f.Write([]byte(s)) } // Mkdir creates a new directory with the specified name and permission bits. diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go index c80d3df..a4ab5d6 100644 --- a/libgo/go/os/file_posix.go +++ b/libgo/go/os/file_posix.go @@ -8,6 +8,7 @@ package os import ( "syscall" + "time" ) func sigpipe() // implemented in package runtime @@ -168,11 +169,11 @@ func (f *File) Truncate(size int64) error { // Sync commits the current contents of the file to stable storage. // Typically, this means flushing the file system's in-memory copy // of recently written data to disk. -func (file *File) Sync() (err error) { - if file == nil { +func (f *File) Sync() (err error) { + if f == nil { return EINVAL } - if e := syscall.Fsync(file.fd); e != nil { + if e := syscall.Fsync(f.fd); e != nil { return NewSyscallError("fsync", e) } return nil @@ -181,11 +182,12 @@ func (file *File) Sync() (err error) { // Chtimes changes the access and modification times of the named // file, similar to the Unix utime() or utimes() functions. // -// The argument times are in nanoseconds, although the underlying -// filesystem may truncate or round the values to a more -// coarse time unit. -func Chtimes(name string, atime_ns int64, mtime_ns int64) error { +// The underlying filesystem may truncate or round the values to a +// less precise time unit. +func Chtimes(name string, atime time.Time, mtime time.Time) error { var utimes [2]syscall.Timeval + atime_ns := atime.Unix()*1e9 + int64(atime.Nanosecond()) + mtime_ns := mtime.Unix()*1e9 + int64(mtime.Nanosecond()) utimes[0] = syscall.NsecToTimeval(atime_ns) utimes[1] = syscall.NsecToTimeval(mtime_ns) if e := syscall.Utimes(name, utimes[0:]); e != nil { diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go index 0bf31ec..671d1a4 100644 --- a/libgo/go/os/file_unix.go +++ b/libgo/go/os/file_unix.go @@ -28,11 +28,11 @@ type file struct { } // Fd returns the integer Unix file descriptor referencing the open file. -func (file *File) Fd() int { - if file == nil { +func (f *File) Fd() int { + if f == nil { return -1 } - return file.fd + return f.fd } // NewFile returns a new File with the given file descriptor and name. @@ -77,8 +77,8 @@ func OpenFile(name string, flag int, perm uint32) (file *File, err error) { // Close closes the File, rendering it unusable for I/O. // It returns an error, if any. -func (file *File) Close() error { - return file.file.close() +func (f *File) Close() error { + return f.file.close() } func (file *file) close() error { @@ -105,50 +105,43 @@ func (file *file) close() error { // Stat returns the FileInfo structure describing file. // It returns the FileInfo and an error, if any. -func (file *File) Stat() (fi *FileInfo, err error) { +func (f *File) Stat() (fi FileInfo, err error) { var stat syscall.Stat_t - e := syscall.Fstat(file.fd, &stat) - if e != nil { - return nil, &PathError{"stat", file.name, e} + err = syscall.Fstat(f.fd, &stat) + if err != nil { + return nil, &PathError{"stat", f.name, err} } - return fileInfoFromStat(file.name, new(FileInfo), &stat, &stat), nil + return fileInfoFromStat(&stat, f.name), nil } -// Stat returns a FileInfo structure describing the named file and an error, if any. +// Stat returns a FileInfo describing the named file and an error, if any. // If name names a valid symbolic link, the returned FileInfo describes // the file pointed at by the link and has fi.FollowedSymlink set to true. // If name names an invalid symbolic link, the returned FileInfo describes // the link itself and has fi.FollowedSymlink set to false. -func Stat(name string) (fi *FileInfo, err error) { - var lstat, stat syscall.Stat_t - e := syscall.Lstat(name, &lstat) - if e != nil { - return nil, &PathError{"stat", name, e} - } - statp := &lstat - if lstat.Mode&syscall.S_IFMT == syscall.S_IFLNK { - e := syscall.Stat(name, &stat) - if e == nil { - statp = &stat - } +func Stat(name string) (fi FileInfo, err error) { + var stat syscall.Stat_t + err = syscall.Stat(name, &stat) + if err != nil { + return nil, &PathError{"stat", name, err} } - return fileInfoFromStat(name, new(FileInfo), &lstat, statp), nil + return fileInfoFromStat(&stat, name), nil } -// Lstat returns the FileInfo structure describing the named file and an +// Lstat returns a FileInfo describing the named file and an // error, if any. If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. -func Lstat(name string) (fi *FileInfo, err error) { +func Lstat(name string) (fi FileInfo, err error) { var stat syscall.Stat_t - e := syscall.Lstat(name, &stat) - if e != nil { - return nil, &PathError{"lstat", name, e} + err = syscall.Lstat(name, &stat) + if err != nil { + return nil, &PathError{"lstat", name, err} } - return fileInfoFromStat(name, new(FileInfo), &stat, &stat), nil + return fileInfoFromStat(&stat, name), nil } // Readdir reads the contents of the directory associated with file and -// returns an array of up to n FileInfo structures, as would be returned +// returns an array of up to n FileInfo values, as would be returned // by Lstat, in directory order. Subsequent calls on the same file will yield // further FileInfos. // @@ -162,23 +155,23 @@ func Lstat(name string) (fi *FileInfo, err error) { // nil error. If it encounters an error before the end of the // directory, Readdir returns the FileInfo read until that point // and a non-nil error. -func (file *File) Readdir(n int) (fi []FileInfo, err error) { - dirname := file.name +func (f *File) Readdir(n int) (fi []FileInfo, err error) { + dirname := f.name if dirname == "" { dirname = "." } dirname += "/" - names, err := file.Readdirnames(n) + names, err := f.Readdirnames(n) fi = make([]FileInfo, len(names)) for i, filename := range names { fip, err := Lstat(dirname + filename) - if fip == nil || err != nil { - fi[i].Name = filename // rest is already zeroed out + if err == nil { + fi[i] = fip } else { - fi[i] = *fip + fi[i] = &FileStat{name: filename} } } - return + return fi, err } // read reads up to len(b) bytes from the File. diff --git a/libgo/go/os/getwd.go b/libgo/go/os/getwd.go index d5f5ae6..a0d3c99 100644 --- a/libgo/go/os/getwd.go +++ b/libgo/go/os/getwd.go @@ -12,7 +12,7 @@ import ( // current directory. If the current directory can be // reached via multiple paths (due to symbolic links), // Getwd may return any one of them. -func Getwd() (string, error) { +func Getwd() (pwd string, err error) { // If the operating system provides a Getwd call, use it. if syscall.ImplementsGetwd { s, e := syscall.Getwd() @@ -27,10 +27,10 @@ func Getwd() (string, error) { // Clumsy but widespread kludge: // if $PWD is set and matches ".", use it. - pwd := Getenv("PWD") + pwd = Getenv("PWD") if len(pwd) > 0 && pwd[0] == '/' { d, err := Stat(pwd) - if err == nil && d.Dev == dot.Dev && d.Ino == dot.Ino { + if err == nil && dot.(*FileStat).SameFile(d.(*FileStat)) { return pwd, nil } } @@ -42,7 +42,7 @@ func Getwd() (string, error) { // Can't stat root - no hope. return "", err } - if root.Dev == dot.Dev && root.Ino == dot.Ino { + if root.(*FileStat).SameFile(dot.(*FileStat)) { return "/", nil } @@ -67,7 +67,7 @@ func Getwd() (string, error) { } for _, name := range names { d, _ := Lstat(parent + "/" + name) - if d.Dev == dot.Dev && d.Ino == dot.Ino { + if d.(*FileStat).SameFile(dot.(*FileStat)) { pwd = "/" + name + pwd goto Found } @@ -82,7 +82,7 @@ func Getwd() (string, error) { return "", err } fd.Close() - if pd.Dev == root.Dev && pd.Ino == root.Ino { + if pd.(*FileStat).SameFile(root.(*FileStat)) { break } // Set up for next round. diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index 170f733..299d2e8 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -14,6 +14,7 @@ import ( "strings" "syscall" "testing" + "time" ) var dot = []string{ @@ -119,12 +120,12 @@ func TestStat(t *testing.T) { if err != nil { t.Fatal("stat failed:", err) } - if !equal(sfname, dir.Name) { - t.Error("name should be ", sfname, "; is", dir.Name) + if !equal(sfname, dir.Name()) { + t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := size(path, t) - if dir.Size != filesize { - t.Error("size should be", filesize, "; is", dir.Size) + if dir.Size() != filesize { + t.Error("size should be", filesize, "; is", dir.Size()) } } @@ -139,12 +140,12 @@ func TestFstat(t *testing.T) { if err2 != nil { t.Fatal("fstat failed:", err2) } - if !equal(sfname, dir.Name) { - t.Error("name should be ", sfname, "; is", dir.Name) + if !equal(sfname, dir.Name()) { + t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := size(path, t) - if dir.Size != filesize { - t.Error("size should be", filesize, "; is", dir.Size) + if dir.Size() != filesize { + t.Error("size should be", filesize, "; is", dir.Size()) } } @@ -154,12 +155,12 @@ func TestLstat(t *testing.T) { if err != nil { t.Fatal("lstat failed:", err) } - if !equal(sfname, dir.Name) { - t.Error("name should be ", sfname, "; is", dir.Name) + if !equal(sfname, dir.Name()) { + t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := size(path, t) - if dir.Size != filesize { - t.Error("size should be", filesize, "; is", dir.Size) + if dir.Size() != filesize { + t.Error("size should be", filesize, "; is", dir.Size()) } } @@ -226,7 +227,7 @@ func testReaddir(dir string, contents []string, t *testing.T) { for _, m := range contents { found := false for _, n := range s { - if equal(m, n.Name) { + if equal(m, n.Name()) { if found { t.Error("present twice:", m) } @@ -405,7 +406,7 @@ func TestHardLink(t *testing.T) { if err != nil { t.Fatalf("stat %q failed: %v", from, err) } - if tostat.Dev != fromstat.Dev || tostat.Ino != fromstat.Ino { + if !tostat.(*FileStat).SameFile(fromstat.(*FileStat)) { t.Errorf("link %q, %q did not create hard link", to, from) } } @@ -430,32 +431,32 @@ func TestSymLink(t *testing.T) { t.Fatalf("symlink %q, %q failed: %v", to, from, err) } defer Remove(from) - tostat, err := Stat(to) + tostat, err := Lstat(to) if err != nil { t.Fatalf("stat %q failed: %v", to, err) } - if tostat.FollowedSymlink { - t.Fatalf("stat %q claims to have followed a symlink", to) + if tostat.Mode()&ModeSymlink != 0 { + t.Fatalf("stat %q claims to have found a symlink", to) } fromstat, err := Stat(from) if err != nil { t.Fatalf("stat %q failed: %v", from, err) } - if tostat.Dev != fromstat.Dev || tostat.Ino != fromstat.Ino { + if !tostat.(*FileStat).SameFile(fromstat.(*FileStat)) { t.Errorf("symlink %q, %q did not create symlink", to, from) } fromstat, err = Lstat(from) if err != nil { t.Fatalf("lstat %q failed: %v", from, err) } - if !fromstat.IsSymlink() { + if fromstat.Mode()&ModeSymlink == 0 { t.Fatalf("symlink %q, %q did not create symlink", to, from) } fromstat, err = Stat(from) if err != nil { t.Fatalf("stat %q failed: %v", from, err) } - if !fromstat.FollowedSymlink { + if fromstat.Mode()&ModeSymlink != 0 { t.Fatalf("stat %q did not follow symlink", from) } s, err := Readlink(from) @@ -563,13 +564,13 @@ func TestStartProcess(t *testing.T) { exec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le) } -func checkMode(t *testing.T, path string, mode uint32) { +func checkMode(t *testing.T, path string, mode FileMode) { dir, err := Stat(path) if err != nil { t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err) } - if dir.Mode&0777 != mode { - t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode, mode) + if dir.Mode()&0777 != mode { + t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode) } } @@ -593,73 +594,13 @@ func TestChmod(t *testing.T) { checkMode(t, f.Name(), 0123) } -func checkUidGid(t *testing.T, path string, uid, gid int) { - dir, err := Stat(path) - if err != nil { - t.Fatalf("Stat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err) - } - if dir.Uid != uid { - t.Errorf("Stat %q: uid %d want %d", path, dir.Uid, uid) - } - if dir.Gid != gid { - t.Errorf("Stat %q: gid %d want %d", path, dir.Gid, gid) - } -} - -func TestChown(t *testing.T) { - // Chown is not supported under windows or Plan 9. - // Plan9 provides a native ChownPlan9 version instead. - if syscall.OS == "windows" || syscall.OS == "plan9" { - return - } - // Use TempDir() to make sure we're on a local file system, - // so that the group ids returned by Getgroups will be allowed - // on the file. On NFS, the Getgroups groups are - // basically useless. - f := newFile("TestChown", t) - defer Remove(f.Name()) - defer f.Close() - dir, err := f.Stat() - if err != nil { - t.Fatalf("stat %s: %s", f.Name(), err) - } - - // Can't change uid unless root, but can try - // changing the group id. First try our current group. - gid := Getgid() - t.Log("gid:", gid) - if err = Chown(f.Name(), -1, gid); err != nil { - t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err) - } - checkUidGid(t, f.Name(), dir.Uid, gid) - - // Then try all the auxiliary groups. - groups, err := Getgroups() - if err != nil { - t.Fatalf("getgroups: %s", err) - } - t.Log("groups: ", groups) - for _, g := range groups { - if err = Chown(f.Name(), -1, g); err != nil { - t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err) - } - checkUidGid(t, f.Name(), dir.Uid, g) - - // change back to gid to test fd.Chown - if err = f.Chown(-1, gid); err != nil { - t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) - } - checkUidGid(t, f.Name(), dir.Uid, gid) - } -} - func checkSize(t *testing.T, f *File, size int64) { dir, err := f.Stat() if err != nil { t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err) } - if dir.Size != size { - t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size, size) + if dir.Size() != size { + t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size(), size) } } @@ -711,37 +652,38 @@ func TestChtimes(t *testing.T) { f.Write([]byte("hello, world\n")) f.Close() - preStat, err := Stat(f.Name()) + st, err := Stat(f.Name()) if err != nil { t.Fatalf("Stat %s: %s", f.Name(), err) } + preStat := st.(*FileStat) // Move access and modification time back a second - const OneSecond = 1e9 // in nanoseconds - err = Chtimes(f.Name(), preStat.Atime_ns-OneSecond, preStat.Mtime_ns-OneSecond) + at := Atime(preStat) + mt := preStat.ModTime() + err = Chtimes(f.Name(), at.Add(-time.Second), mt.Add(-time.Second)) if err != nil { t.Fatalf("Chtimes %s: %s", f.Name(), err) } - postStat, err := Stat(f.Name()) + st, err = Stat(f.Name()) if err != nil { t.Fatalf("second Stat %s: %s", f.Name(), err) } + postStat := st.(*FileStat) /* Plan 9: Mtime is the time of the last change of content. Similarly, atime is set whenever the contents are accessed; also, it is set whenever mtime is set. */ - if postStat.Atime_ns >= preStat.Atime_ns && syscall.OS != "plan9" { - t.Errorf("Atime_ns didn't go backwards; was=%d, after=%d", - preStat.Atime_ns, - postStat.Atime_ns) + pat := Atime(postStat) + pmt := postStat.ModTime() + if !pat.Before(at) && syscall.OS != "plan9" { + t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat) } - if postStat.Mtime_ns >= preStat.Mtime_ns { - t.Errorf("Mtime_ns didn't go backwards; was=%d, after=%d", - preStat.Mtime_ns, - postStat.Mtime_ns) + if !pmt.Before(mt) { + t.Errorf("ModTime didn't go backwards; was=%d, after=%d", mt, pmt) } } @@ -883,7 +825,7 @@ func TestOpenError(t *testing.T) { } perr, ok := err.(*PathError) if !ok { - t.Errorf("Open(%q, %d) returns error of %T type; want *os.PathError", tt.path, tt.mode, err) + t.Errorf("Open(%q, %d) returns error of %T type; want *PathError", tt.path, tt.mode, err) } if perr.Err != tt.error { if syscall.OS == "plan9" { @@ -899,6 +841,14 @@ func TestOpenError(t *testing.T) { } } +func TestOpenNoName(t *testing.T) { + f, err := Open("") + if err == nil { + t.Fatal(`Open("") succeeded`) + f.Close() + } +} + func run(t *testing.T, cmd []string) string { // Run /bin/hostname and collect output. r, w, err := Pipe() diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go new file mode 100644 index 0000000..3109a81 --- /dev/null +++ b/libgo/go/os/os_unix_test.go @@ -0,0 +1,75 @@ +// Copyright 2009 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 darwin freebsd linux openbsd + +package os_test + +import ( + . "os" + "syscall" + "testing" +) + +func checkUidGid(t *testing.T, path string, uid, gid int) { + dir, err := Stat(path) + if err != nil { + t.Fatalf("Stat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err) + } + sys := dir.(*FileStat).Sys.(*syscall.Stat_t) + if int(sys.Uid) != uid { + t.Errorf("Stat %q: uid %d want %d", path, sys.Uid, uid) + } + if int(sys.Gid) != gid { + t.Errorf("Stat %q: gid %d want %d", path, sys.Gid, gid) + } +} + +func TestChown(t *testing.T) { + // Chown is not supported under windows or Plan 9. + // Plan9 provides a native ChownPlan9 version instead. + if syscall.OS == "windows" || syscall.OS == "plan9" { + return + } + // Use TempDir() to make sure we're on a local file system, + // so that the group ids returned by Getgroups will be allowed + // on the file. On NFS, the Getgroups groups are + // basically useless. + f := newFile("TestChown", t) + defer Remove(f.Name()) + defer f.Close() + dir, err := f.Stat() + if err != nil { + t.Fatalf("stat %s: %s", f.Name(), err) + } + + // Can't change uid unless root, but can try + // changing the group id. First try our current group. + gid := Getgid() + t.Log("gid:", gid) + if err = Chown(f.Name(), -1, gid); err != nil { + t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err) + } + sys := dir.(*FileStat).Sys.(*syscall.Stat_t) + checkUidGid(t, f.Name(), int(sys.Uid), gid) + + // Then try all the auxiliary groups. + groups, err := Getgroups() + if err != nil { + t.Fatalf("getgroups: %s", err) + } + t.Log("groups: ", groups) + for _, g := range groups { + if err = Chown(f.Name(), -1, g); err != nil { + t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err) + } + checkUidGid(t, f.Name(), int(sys.Uid), g) + + // change back to gid to test fd.Chown + if err = f.Chown(-1, gid); err != nil { + t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) + } + checkUidGid(t, f.Name(), int(sys.Uid), gid) + } +} diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go index 82fade6..bc14a78 100644 --- a/libgo/go/os/path.go +++ b/libgo/go/os/path.go @@ -17,7 +17,7 @@ func MkdirAll(path string, perm uint32) error { // If path exists, stop with success or error. dir, err := Stat(path) if err == nil { - if dir.IsDirectory() { + if dir.IsDir() { return nil } return &PathError{"mkdir", path, ENOTDIR} @@ -48,7 +48,7 @@ func MkdirAll(path string, perm uint32) error { // Handle arguments like "foo/." by // double-checking that directory doesn't exist. dir, err1 := Lstat(path) - if err1 == nil && dir.IsDirectory() { + if err1 == nil && dir.IsDir() { return nil } return err @@ -75,7 +75,7 @@ func RemoveAll(path string) error { } return serr } - if !dir.IsDirectory() { + if !dir.IsDir() { // Not a directory; return the error from Remove. return err } diff --git a/libgo/go/os/stat.go b/libgo/go/os/stat.go index 8eb4ab4..c664fc1 100644 --- a/libgo/go/os/stat.go +++ b/libgo/go/os/stat.go @@ -2,39 +2,55 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// AMD64, Linux - package os -import syscall "syscall" +import ( + "syscall" + "time" +) -func isSymlink(stat *syscall.Stat_t) bool { - return stat.Mode & syscall.S_IFMT == syscall.S_IFLNK +func sameFile(fs1, fs2 *FileStat) bool { + sys1 := fs1.Sys.(*syscall.Stat_t) + sys2 := fs2.Sys.(*syscall.Stat_t) + return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino } -func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { - fi.Dev = uint64(stat.Dev) - fi.Ino = uint64(stat.Ino) - fi.Nlink = uint64(stat.Nlink) - fi.Mode = uint32(stat.Mode) - fi.Uid = int(stat.Uid) - fi.Gid = int(stat.Gid) - fi.Rdev = uint64(stat.Rdev) - fi.Size = int64(stat.Size) - fi.Blksize = int64(stat.Blksize) - fi.Blocks = int64(stat.Blocks) - fi.Atime_ns = int64(stat.Atime.Sec)*1e9 + int64(stat.Atime.Nsec) - fi.Mtime_ns = int64(stat.Mtime.Sec)*1e9 + int64(stat.Mtime.Nsec) - fi.Ctime_ns = int64(stat.Ctime.Sec)*1e9 + int64(stat.Ctime.Nsec) - for i := len(name)-1; i >= 0; i-- { - if name[i] == '/' { - name = name[i+1:] - break - } +func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo { + fs := &FileStat{ + name: basename(name), + size: int64(st.Size), + modTime: timespecToTime(st.Mtime), + Sys: st, + } + fs.mode = FileMode(st.Mode & 0777) + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFBLK, syscall.S_IFCHR: + fs.mode |= ModeDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket } - fi.Name = name - if isSymlink(lstat) && !isSymlink(stat) { - fi.FollowedSymlink = true + if st.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid } - return fi + if st.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + return fs +} + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +// For testing. +func atime(fi FileInfo) time.Time { + return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atime) } diff --git a/libgo/go/os/stat_openbsd.go b/libgo/go/os/stat_openbsd.go index 6d3a381..66189a6 100644 --- a/libgo/go/os/stat_openbsd.go +++ b/libgo/go/os/stat_openbsd.go @@ -4,29 +4,53 @@ package os -import "syscall" +import ( + "syscall" + "time" +) -func isSymlink(stat *syscall.Stat_t) bool { - return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +func sameFile(fs1, fs2 *FileStat) bool { + sys1 := fs1.Sys.(*syscall.Stat_t) + sys2 := fs2.Sys.(*syscall.Stat_t) + return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino } -func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { - fi.Dev = uint64(stat.Dev) - fi.Ino = uint64(stat.Ino) - fi.Nlink = uint64(stat.Nlink) - fi.Mode = uint32(stat.Mode) - fi.Uid = int(stat.Uid) - fi.Gid = int(stat.Gid) - fi.Rdev = uint64(stat.Rdev) - fi.Size = int64(stat.Size) - fi.Blksize = int64(stat.Blksize) - fi.Blocks = stat.Blocks - fi.Atime_ns = syscall.TimespecToNsec(stat.Atim) - fi.Mtime_ns = syscall.TimespecToNsec(stat.Mtim) - fi.Ctime_ns = syscall.TimespecToNsec(stat.Ctim) - fi.Name = basename(name) - if isSymlink(lstat) && !isSymlink(stat) { - fi.FollowedSymlink = true +func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo { + fs := &FileStat{ + name: basename(name), + size: int64(st.Size), + modTime: timespecToTime(st.Mtim), + Sys: st, } - return fi + fs.mode = FileMode(st.Mode & 0777) + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFBLK, syscall.S_IFCHR: + fs.mode |= ModeDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket + } + if st.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid + } + if st.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + return fs +} + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +// For testing. +func atime(fi FileInfo) time.Time { + return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atim) } diff --git a/libgo/go/os/types.go b/libgo/go/os/types.go index df57b59..2638153 100644 --- a/libgo/go/os/types.go +++ b/libgo/go/os/types.go @@ -4,53 +4,111 @@ package os -import "syscall" - -// An operating-system independent representation of Unix data structures. -// OS-specific routines in this directory convert the OS-local versions to these. +import ( + "syscall" + "time" +) // Getpagesize returns the underlying system's memory page size. func Getpagesize() int { return syscall.Getpagesize() } -// A FileInfo describes a file and is returned by Stat, Fstat, and Lstat -type FileInfo struct { - Dev uint64 // device number of file system holding file. - Ino uint64 // inode number. - Nlink uint64 // number of hard links. - Mode uint32 // permission and mode bits. - Uid int // user id of owner. - Gid int // group id of owner. - Rdev uint64 // device type for special file. - Size int64 // length in bytes. - Blksize int64 // size of blocks, in bytes. - Blocks int64 // number of blocks allocated for file. - Atime_ns int64 // access time; nanoseconds since epoch. - Mtime_ns int64 // modified time; nanoseconds since epoch. - Ctime_ns int64 // status change time; nanoseconds since epoch. - Name string // base name of the file name provided in Open, Stat, etc. - FollowedSymlink bool // followed a symlink to get this information +// A FileInfo describes a file and is returned by Stat and Lstat +type FileInfo interface { + Name() string // base name of the file + Size() int64 // length in bytes + Mode() FileMode // file mode bits + ModTime() time.Time // modification time + IsDir() bool // abbreviation for Mode().IsDir() } -// IsFifo reports whether the FileInfo describes a FIFO file. -func (f *FileInfo) IsFifo() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFIFO } +// A FileMode represents a file's mode and permission bits. +// The bits have the same definition on all systems, so that +// information about files can be moved from one system +// to another portably. Not all bits apply to all systems. +// The only required bit is ModeDir for directories. +type FileMode uint32 -// IsChar reports whether the FileInfo describes a character special file. -func (f *FileInfo) IsChar() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFCHR } +// The defined file mode bits are the most significant bits of the FileMode. +// The nine least-significant bits are the standard Unix rwxrwxrwx permissions. +const ( + // The single letters are the abbreviations + // used by the String method's formatting. + ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory + ModeAppend // a: append-only + ModeExclusive // l: exclusive use + ModeTemporary // t: temporary file (not backed up) + ModeSymlink // L: symbolic link + ModeDevice // D: device file + ModeNamedPipe // p: named pipe (FIFO) + ModeSocket // S: Unix domain socket + ModeSetuid // u: setuid + ModeSetgid // g: setgid -// IsDirectory reports whether the FileInfo describes a directory. -func (f *FileInfo) IsDirectory() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFDIR } + // Mask for the type bits. For regular files, none will be set. + ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice -// IsBlock reports whether the FileInfo describes a block special file. -func (f *FileInfo) IsBlock() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFBLK } + ModePerm FileMode = 0777 // permission bits +) -// IsRegular reports whether the FileInfo describes a regular file. -func (f *FileInfo) IsRegular() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFREG } +func (m FileMode) String() string { + const str = "daltLDpSug" + var buf [20]byte + w := 0 + for i, c := range str { + if m&(1<<uint(32-1-i)) != 0 { + buf[w] = byte(c) + w++ + } + } + if w == 0 { + buf[w] = '-' + w++ + } + const rwx = "rwxrwxrwx" + for i, c := range rwx { + if m&(1<<uint(9-1-i)) != 0 { + buf[w] = byte(c) + } else { + buf[w] = '-' + } + w++ + } + return string(buf[:w]) +} -// IsSymlink reports whether the FileInfo describes a symbolic link. -func (f *FileInfo) IsSymlink() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFLNK } +// IsDir reports whether m describes a directory. +// That is, it tests for the ModeDir bit being set in m. +func (m FileMode) IsDir() bool { + return m&ModeDir != 0 +} + +// Perm returns the Unix permission bits in m. +func (m FileMode) Perm() FileMode { + return m & ModePerm +} -// IsSocket reports whether the FileInfo describes a socket. -func (f *FileInfo) IsSocket() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFSOCK } +// A FileStat is the implementation of FileInfo returned by Stat and Lstat. +// Clients that need access to the underlying system-specific stat information +// can test for *os.FileStat and then consult the Sys field. +type FileStat struct { + name string + size int64 + mode FileMode + modTime time.Time -// Permission returns the file permission bits. -func (f *FileInfo) Permission() uint32 { return f.Mode & 0777 } + Sys interface{} +} + +func (fs *FileStat) Name() string { return fs.name } +func (fs *FileStat) Size() int64 { return fs.size } +func (fs *FileStat) Mode() FileMode { return fs.mode } +func (fs *FileStat) ModTime() time.Time { return fs.modTime } +func (fs *FileStat) IsDir() bool { return fs.mode.IsDir() } + +// SameFile reports whether fs and other describe the same file. +// For example, on Unix this means that the device and inode fields +// of the two underlying structures are identical; on other systems +// the decision may be based on the path names. +func (fs *FileStat) SameFile(other *FileStat) bool { + return sameFile(fs, other) +} diff --git a/libgo/go/os/user/user_test.go b/libgo/go/os/user/user_test.go index 59f15e4..f9f44af 100644 --- a/libgo/go/os/user/user_test.go +++ b/libgo/go/os/user/user_test.go @@ -41,8 +41,8 @@ func TestLookup(t *testing.T) { t.Errorf("expected Uid of %d; got %d", e, g) } fi, err := os.Stat(u.HomeDir) - if err != nil || !fi.IsDirectory() { - t.Errorf("expected a valid HomeDir; stat(%q): err=%v, IsDirectory=%v", u.HomeDir, err, fi.IsDirectory()) + if err != nil || !fi.IsDir() { + t.Errorf("expected a valid HomeDir; stat(%q): err=%v, IsDir=%v", u.HomeDir, err, fi.IsDir()) } if u.Username == "" { t.Fatalf("didn't get a username") diff --git a/libgo/go/patch/git.go b/libgo/go/patch/git.go index 454eade..5c233fb 100644 --- a/libgo/go/patch/git.go +++ b/libgo/go/patch/git.go @@ -22,7 +22,7 @@ func gitSHA1(data []byte) []byte { h := sha1.New() fmt.Fprintf(h, "blob %d\x00", len(data)) h.Write(data) - return h.Sum() + return h.Sum(nil) } // BUG(rsc): The Git binary delta format is not implemented, only Git binary literals. diff --git a/libgo/go/path/filepath/match.go b/libgo/go/path/filepath/match.go index 8cf1f9a..c3678f5 100644 --- a/libgo/go/path/filepath/match.go +++ b/libgo/go/path/filepath/match.go @@ -260,7 +260,7 @@ func glob(dir, pattern string, matches []string) (m []string, e error) { if err != nil { return } - if !fi.IsDirectory() { + if !fi.IsDir() { return } d, err := os.Open(dir) diff --git a/libgo/go/path/filepath/path.go b/libgo/go/path/filepath/path.go index 1b5d6c3..e3d6c34 100644 --- a/libgo/go/path/filepath/path.go +++ b/libgo/go/path/filepath/path.go @@ -223,7 +223,7 @@ func EvalSymlinks(path string) (string, error) { if err != nil { return "", err } - if !fi.IsSymlink() { + if fi.Mode()&os.ModeSymlink == 0 { b.WriteString(p) if path != "" { b.WriteRune(Separator) @@ -312,7 +312,11 @@ func Rel(basepath, targpath string) (string, error) { if b0 != bl { // Base elements left. Must go up before going down. seps := strings.Count(base[b0:bl], string(Separator)) - buf := make([]byte, 3+seps*3+tl-t0) + size := 2 + seps*3 + if tl != t0 { + size += 1 + tl - t0 + } + buf := make([]byte, size) n := copy(buf, "..") for i := 0; i < seps; i++ { buf[n] = Separator @@ -341,19 +345,19 @@ var SkipDir = errors.New("skip this directory") // sole exception is that if path is a directory and the function returns the // special value SkipDir, the contents of the directory are skipped // and processing continues as usual on the next file. -type WalkFunc func(path string, info *os.FileInfo, err error) error +type WalkFunc func(path string, info os.FileInfo, err error) error // walk recursively descends path, calling w. -func walk(path string, info *os.FileInfo, walkFn WalkFunc) error { +func walk(path string, info os.FileInfo, walkFn WalkFunc) error { err := walkFn(path, info, nil) if err != nil { - if info.IsDirectory() && err == SkipDir { + if info.IsDir() && err == SkipDir { return nil } return err } - if !info.IsDirectory() { + if !info.IsDir() { return nil } @@ -363,7 +367,7 @@ func walk(path string, info *os.FileInfo, walkFn WalkFunc) error { } for _, fileInfo := range list { - if err = walk(Join(path, fileInfo.Name), fileInfo, walkFn); err != nil { + if err = walk(Join(path, fileInfo.Name()), fileInfo, walkFn); err != nil { return err } } @@ -386,7 +390,7 @@ func Walk(root string, walkFn WalkFunc) error { // readDir reads the directory named by dirname and returns // a sorted list of directory entries. // Copied from io/ioutil to avoid the circular import. -func readDir(dirname string) ([]*os.FileInfo, error) { +func readDir(dirname string) ([]os.FileInfo, error) { f, err := os.Open(dirname) if err != nil { return nil, err @@ -396,20 +400,16 @@ func readDir(dirname string) ([]*os.FileInfo, error) { if err != nil { return nil, err } - fi := make(fileInfoList, len(list)) - for i := range list { - fi[i] = &list[i] - } - sort.Sort(fi) - return fi, nil + sort.Sort(byName(list)) + return list, nil } -// A dirList implements sort.Interface. -type fileInfoList []*os.FileInfo +// byName implements sort.Interface. +type byName []os.FileInfo -func (f fileInfoList) Len() int { return len(f) } -func (f fileInfoList) Less(i, j int) bool { return f[i].Name < f[j].Name } -func (f fileInfoList) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +func (f byName) Len() int { return len(f) } +func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } +func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } // Base returns the last element of path. // Trailing path separators are removed before extracting the last element. diff --git a/libgo/go/path/filepath/path_test.go b/libgo/go/path/filepath/path_test.go index c81cbf0..2bd62d3 100644 --- a/libgo/go/path/filepath/path_test.go +++ b/libgo/go/path/filepath/path_test.go @@ -317,7 +317,7 @@ func checkMarks(t *testing.T, report bool) { // Assumes that each node name is unique. Good enough for a test. // If clear is true, any incoming error is cleared before return. The errors // are always accumulated, though. -func mark(path string, info *os.FileInfo, err error, errors *[]error, clear bool) error { +func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error { if err != nil { *errors = append(*errors, err) if clear { @@ -325,8 +325,9 @@ func mark(path string, info *os.FileInfo, err error, errors *[]error, clear bool } return err } + name := info.Name() walkTree(tree, tree.name, func(path string, n *Node) { - if n.name == info.Name { + if n.name == name { n.mark++ } }) @@ -337,7 +338,7 @@ func TestWalk(t *testing.T) { makeTree(t) errors := make([]error, 0, 10) clear := true - markFn := func(path string, info *os.FileInfo, err error) error { + markFn := func(path string, info os.FileInfo, err error) error { return mark(path, info, err, &errors, clear) } // Expect no errors. @@ -548,7 +549,7 @@ func TestEvalSymlinks(t *testing.T) { // relative testEvalSymlinks(t, tests) // absolute -/* These tests do not work in the gccgo test environment. + /* These tests do not work in the gccgo test environment. goroot, err := filepath.EvalSymlinks(os.Getenv("GOROOT")) if err != nil { t.Fatalf("EvalSymlinks(%q) error: %v", os.Getenv("GOROOT"), err) @@ -564,7 +565,7 @@ func TestEvalSymlinks(t *testing.T) { } } testEvalSymlinks(t, tests) -*/ + */ } /* These tests do not work in the gccgo test environment. @@ -603,7 +604,7 @@ func TestAbs(t *testing.T) { t.Errorf("Abs(%q) error: %v", path, err) } absinfo, err := os.Stat(abspath) - if err != nil || absinfo.Ino != info.Ino { + if err != nil || !absinfo.(*os.FileStat).SameFile(info.(*os.FileStat)) { t.Errorf("Abs(%q)=%q, not the same file", path, abspath) } if !filepath.IsAbs(abspath) { @@ -634,6 +635,10 @@ var reltests = []RelTests{ {"a/b/../c", "a/b", "../b"}, {"a/b/c", "a/c/d", "../../c/d"}, {"a/b", "c/d", "../../c/d"}, + {"a/b/c/d", "a/b", "../.."}, + {"a/b/c/d", "a/b/", "../.."}, + {"a/b/c/d/", "a/b", "../.."}, + {"a/b/c/d/", "a/b/", "../.."}, {"../../a/b", "../../a/b/c/d", "c/d"}, {"/a/b", "/a/b", "."}, {"/a/b/.", "/a/b", "."}, @@ -645,6 +650,10 @@ var reltests = []RelTests{ {"/a/b/../c", "/a/b", "../b"}, {"/a/b/c", "/a/c/d", "../../c/d"}, {"/a/b", "/c/d", "../../c/d"}, + {"/a/b/c/d", "/a/b", "../.."}, + {"/a/b/c/d", "/a/b/", "../.."}, + {"/a/b/c/d/", "/a/b", "../.."}, + {"/a/b/c/d/", "/a/b/", "../.."}, {"/../../a/b", "/../../a/b/c/d", "c/d"}, {".", "a/b", "a/b"}, {".", "..", ".."}, diff --git a/libgo/go/strings/strings.go b/libgo/go/strings/strings.go index b4d9207..53fdead 100644 --- a/libgo/go/strings/strings.go +++ b/libgo/go/strings/strings.go @@ -64,7 +64,17 @@ func Count(s, sep string) int { // Contains returns true if substr is within s. func Contains(s, substr string) bool { - return Index(s, substr) != -1 + return Index(s, substr) >= 0 +} + +// ContainsAny returns true if any Unicode code points in chars are within s. +func ContainsAny(s, chars string) bool { + return IndexAny(s, chars) >= 0 +} + +// ContainsRune returns true if the Unicode code point r is within s. +func ContainsRune(s string, r rune) bool { + return IndexRune(s, r) >= 0 } // Index returns the index of the first instance of sep in s, or -1 if sep is not present in s. @@ -269,7 +279,7 @@ func FieldsFunc(s string, f func(rune) bool) []string { fieldStart = i } } - if fieldStart != -1 { // Last field might end at EOF. + if fieldStart >= 0 { // Last field might end at EOF. a[na] = s[fieldStart:] } return a @@ -512,7 +522,7 @@ func lastIndexFunc(s string, f func(rune) bool, truth bool) int { } func makeCutsetFunc(cutset string) func(rune) bool { - return func(r rune) bool { return IndexRune(cutset, r) != -1 } + return func(r rune) bool { return IndexRune(cutset, r) >= 0 } } // Trim returns a slice of the string s with all leading and diff --git a/libgo/go/strings/strings_test.go b/libgo/go/strings/strings_test.go index 96207f5..957af67 100644 --- a/libgo/go/strings/strings_test.go +++ b/libgo/go/strings/strings_test.go @@ -527,7 +527,7 @@ func TestTrim(t *testing.T) { case "TrimRight": f = TrimRight default: - t.Error("Undefined trim function %s", name) + t.Errorf("Undefined trim function %s", name) } actual := f(tc.in, tc.cutset) if actual != tc.out { @@ -908,6 +908,56 @@ func TestContains(t *testing.T) { } } +var ContainsAnyTests = []struct { + str, substr string + expected bool +}{ + {"", "", false}, + {"", "a", false}, + {"", "abc", false}, + {"a", "", false}, + {"a", "a", true}, + {"aaa", "a", true}, + {"abc", "xyz", false}, + {"abc", "xcz", true}, + {"a☺b☻c☹d", "uvw☻xyz", true}, + {"aRegExp*", ".(|)*+?^$[]", true}, + {dots + dots + dots, " ", false}, +} + +func TestContainsAny(t *testing.T) { + for _, ct := range ContainsAnyTests { + if ContainsAny(ct.str, ct.substr) != ct.expected { + t.Errorf("ContainsAny(%s, %s) = %v, want %v", + ct.str, ct.substr, !ct.expected, ct.expected) + } + } +} + +var ContainsRuneTests = []struct { + str string + r rune + expected bool +}{ + {"", 'a', false}, + {"a", 'a', true}, + {"aaa", 'a', true}, + {"abc", 'y', false}, + {"abc", 'c', true}, + {"a☺b☻c☹d", 'x', false}, + {"a☺b☻c☹d", '☻', true}, + {"aRegExp*", '*', true}, +} + +func TestContainsRune(t *testing.T) { + for _, ct := range ContainsRuneTests { + if ContainsRune(ct.str, ct.r) != ct.expected { + t.Errorf("ContainsRune(%s, %s) = %v, want %v", + ct.str, ct.r, !ct.expected, ct.expected) + } + } +} + var EqualFoldTests = []struct { s, t string out bool diff --git a/libgo/go/testing/benchmark.go b/libgo/go/testing/benchmark.go index 4f049a3..e81e5c5 100644 --- a/libgo/go/testing/benchmark.go +++ b/libgo/go/testing/benchmark.go @@ -27,17 +27,19 @@ type InternalBenchmark struct { type B struct { N int benchmark InternalBenchmark - ns int64 + ns time.Duration bytes int64 - start int64 + start time.Time + timerOn bool } // StartTimer starts timing a test. This function is called automatically // before a benchmark starts, but it can also used to resume timing after // a call to StopTimer. func (b *B) StartTimer() { - if b.start == 0 { - b.start = time.Nanoseconds() + if !b.timerOn { + b.start = time.Now() + b.timerOn = true } } @@ -45,17 +47,17 @@ func (b *B) StartTimer() { // while performing complex initialization that you don't // want to measure. func (b *B) StopTimer() { - if b.start > 0 { - b.ns += time.Nanoseconds() - b.start + if b.timerOn { + b.ns += time.Now().Sub(b.start) + b.timerOn = false } - b.start = 0 } // ResetTimer sets the elapsed benchmark time to zero. // It does not affect whether the timer is running. func (b *B) ResetTimer() { - if b.start > 0 { - b.start = time.Nanoseconds() + if b.timerOn { + b.start = time.Now() } b.ns = 0 } @@ -68,7 +70,7 @@ func (b *B) nsPerOp() int64 { if b.N <= 0 { return 0 } - return b.ns / int64(b.N) + return b.ns.Nanoseconds() / int64(b.N) } // runN runs a single benchmark for the specified number of iterations. @@ -134,14 +136,14 @@ func (b *B) run() BenchmarkResult { n := 1 b.runN(n) // Run the benchmark for at least the specified amount of time. - time := int64(*benchTime * 1e9) - for b.ns < time && n < 1e9 { + d := time.Duration(*benchTime * float64(time.Second)) + for b.ns < d && n < 1e9 { last := n // Predict iterations/sec. if b.nsPerOp() == 0 { n = 1e9 } else { - n = int(time / b.nsPerOp()) + n = int(d.Nanoseconds() / b.nsPerOp()) } // Run more iterations than we think we'll need for a second (1.5x). // Don't grow too fast in case we had timing errors previously. @@ -156,23 +158,23 @@ func (b *B) run() BenchmarkResult { // The results of a benchmark run. type BenchmarkResult struct { - N int // The number of iterations. - Ns int64 // The total time taken. - Bytes int64 // Bytes processed in one iteration. + N int // The number of iterations. + T time.Duration // The total time taken. + Bytes int64 // Bytes processed in one iteration. } func (r BenchmarkResult) NsPerOp() int64 { if r.N <= 0 { return 0 } - return r.Ns / int64(r.N) + return r.T.Nanoseconds() / int64(r.N) } func (r BenchmarkResult) mbPerSec() float64 { - if r.Bytes <= 0 || r.Ns <= 0 || r.N <= 0 { + if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { return 0 } - return float64(r.Bytes) * float64(r.N) / float64(r.Ns) * 1e3 + return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() } func (r BenchmarkResult) String() string { @@ -187,9 +189,9 @@ func (r BenchmarkResult) String() string { // The format specifiers here make sure that // the ones digits line up for all three possible formats. if nsop < 10 { - ns = fmt.Sprintf("%13.2f ns/op", float64(r.Ns)/float64(r.N)) + ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) } else { - ns = fmt.Sprintf("%12.1f ns/op", float64(r.Ns)/float64(r.N)) + ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) } } return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb) diff --git a/libgo/go/testing/example.go b/libgo/go/testing/example.go index 3b026ee..e23f13b 100644 --- a/libgo/go/testing/example.go +++ b/libgo/go/testing/example.go @@ -56,9 +56,9 @@ func RunExamples(examples []InternalExample) (ok bool) { }() // run example - ns := -time.Nanoseconds() + t0 := time.Now() eg.F() - ns += time.Nanoseconds() + dt := time.Now().Sub(t0) // close pipe, restore stdout/stderr, get output w.Close() @@ -66,7 +66,7 @@ func RunExamples(examples []InternalExample) (ok bool) { out := <-outC // report any errors - tstr := fmt.Sprintf("(%.2f seconds)", float64(ns)/1e9) + tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds()) if out != eg.Output { fmt.Printf( "--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n", diff --git a/libgo/go/testing/testing.go b/libgo/go/testing/testing.go index 08443a3..0b3a071 100644 --- a/libgo/go/testing/testing.go +++ b/libgo/go/testing/testing.go @@ -111,12 +111,13 @@ func decorate(s string, addFileLine bool) string { // T is a type passed to Test functions to manage test state and support formatted test logs. // Logs are accumulated during execution and dumped to standard error when done. type T struct { - name string // Name of test. - errors string // Error string from test. - failed bool // Test has failed. - ch chan *T // Output for serial tests. - startParallel chan bool // Parallel tests will wait on this. - ns int64 // Duration of test in nanoseconds. + name string // Name of test. + errors string // Error string from test. + failed bool // Test has failed. + ch chan *T // Output for serial tests. + startParallel chan bool // Parallel tests will wait on this. + start time.Time // Time test started + dt time.Duration // Length of test } // Fail marks the Test function as having failed but continues execution. @@ -128,7 +129,7 @@ func (t *T) Failed() bool { return t.failed } // FailNow marks the Test function as having failed and stops its execution. // Execution will continue at the next Test. func (t *T) FailNow() { - t.ns = time.Nanoseconds() - t.ns + t.dt = time.Now().Sub(t.start) t.Fail() t.ch <- t runtime.Goexit() @@ -184,9 +185,9 @@ type InternalTest struct { } func tRunner(t *T, test *InternalTest) { - t.ns = time.Nanoseconds() + t.start = time.Now() test.F(t) - t.ns = time.Nanoseconds() - t.ns + t.dt = time.Now().Sub(t.start) t.ch <- t } @@ -211,7 +212,7 @@ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, } func report(t *T) { - tstr := fmt.Sprintf("(%.2f seconds)", float64(t.ns)/1e9) + tstr := fmt.Sprintf("(%.2f seconds)", t.dt.Seconds()) format := "--- %s: %s %s\n%s" if t.failed { fmt.Printf(format, "FAIL", t.name, tstr, t.errors) diff --git a/libgo/go/text/template/doc.go b/libgo/go/text/template/doc.go index 42f9e56..4208d53 100644 --- a/libgo/go/text/template/doc.go +++ b/libgo/go/text/template/doc.go @@ -18,8 +18,7 @@ The input text for a template is UTF-8-encoded text in any format. "{{" and "}}"; all text outside actions is copied to the output unchanged. Actions may not span newlines, although comments can. -Once constructed, templates and template sets can be executed safely in -parallel. +Once constructed, a template may be executed safely in parallel. Actions @@ -221,10 +220,9 @@ All produce the quoted word "output": Functions -During execution functions are found in three function maps: first in the -template, then in the "template set" (described below), and finally in the -global function map. By default, no functions are defined in the template or -the set but the Funcs methods can be used to add them. +During execution functions are found in two function maps: first in the +template, then in the global function map. By default, no functions are defined +in the template but the Funcs methods can be used to add them. Predefined global functions are named as follows. @@ -265,49 +263,63 @@ Predefined global functions are named as follows. The boolean functions take any zero value to be false and a non-zero value to be true. -Template sets +Associated templates -Each template is named by a string specified when it is created. A template may -use a template invocation to instantiate another template directly or by its -name; see the explanation of the template action above. The name is looked up -in the template set associated with the template. +Each template is named by a string specified when it is created. Also, each +template is associated with zero or more other templates that it may invoke by +name; such associations are transitive and form a name space of templates. -If no template invocation actions occur in the template, the issue of template -sets can be ignored. If it does contain invocations, though, the template -containing the invocations must be part of a template set in which to look up -the names. +A template may use a template invocation to instantiate another associated +template; see the explanation of the "template" action above. The name must be +that of a template associated with the template that contains the invocation. -There are two ways to construct template sets. +Nested template definitions -The first is to use a Set's Parse method to create a set of named templates from -a single input defining multiple templates. The syntax of the definitions is to -surround each template declaration with a define and end action. +When parsing a template, another template may be defined and associated with the +template being parsed. Template definitions must appear at the top level of the +template, much like global variables in a Go program. + +The syntax of such definitions is to surround each template declaration with a +"define" and "end" action. The define action names the template being created by providing a string -constant. Here is a simple example of input to Set.Parse: +constant. Here is a simple example: - `{{define "T1"}} definition of template T1 {{end}} - {{define "T2"}} definition of template T2 {{end}} - {{define "T3"}} {{template "T1"}} {{template "T2"}} {{end}}` + `{{define "T1"}}ONE{{end}} + {{define "T2"}}TWO{{end}} + {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}} + {{template "T3"}}` This defines two templates, T1 and T2, and a third T3 that invokes the other two -when it is executed. +when it is executed. Finally it invokes T3. If executed this template will +produce the text + + ONE TWO + +By construction, a template may reside in only one association. If it's +necessary to have a template addressable from multiple associations, the +template definition must be parsed multiple times to create distinct *Template +values. + +Parse may be called multiple times to assemble the various associated templates; +see the ParseFiles and ParseGlob functions and methods for simple ways to parse +related templates stored in files. -The second way to build a template set is to use Set's Add method to add a -parsed template to a set. A template may be bound to at most one set. If it's -necessary to have a template in multiple sets, the template definition must be -parsed multiple times to create distinct *Template values. +A template may be executed directly or through ExecuteTemplate, which executes +an associated template identified by name. To invoke our example above, we +might write, -Set.Parse may be called multiple times on different inputs to construct the set. -Two sets may therefore be constructed with a common base set of templates plus, -through a second Parse call each, specializations for some elements. + err := tmpl.Execute(os.Stdout, "no data needed") + if err != nil { + log.Fatalf("execution failed: %s", err) + } -A template may be executed directly or through Set.Execute, which executes a -named template from the set. To invoke our example above, we might write, +or to invoke a particular template explicitly by name, - err := set.Execute(os.Stdout, "T3", "no data needed") + err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed") if err != nil { log.Fatalf("execution failed: %s", err) } + */ package template diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go index 1910882..b74bc3b 100644 --- a/libgo/go/text/template/exec.go +++ b/libgo/go/text/template/exec.go @@ -85,8 +85,18 @@ func errRecover(errp *error) { } } +// ExecuteTemplate applies the template associated with t that has the given name +// to the specified data object and writes the output to wr. +func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { + tmpl := t.tmpl[name] + if tmpl == nil { + return fmt.Errorf("template: no template %q associated with template %q", name, t.name) + } + return tmpl.Execute(wr, data) +} + // Execute applies a parsed template to the specified data object, -// writing the output to wr. +// and writes the output to wr. func (t *Template) Execute(wr io.Writer, data interface{}) (err error) { defer errRecover(&err) value := reflect.ValueOf(data) @@ -251,13 +261,9 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) { - set := s.tmpl.set - if set == nil { - s.errorf("no set defined in which to invoke template named %q", t.Name) - } - tmpl := set.tmpl[t.Name] + tmpl := s.tmpl.tmpl[t.Name] if tmpl == nil { - s.errorf("template %q not in set", t.Name) + s.errorf("template %q not defined", t.Name) } // Variables declared by the pipeline persist. dot = s.evalPipeline(dot, t.Pipe) @@ -376,7 +382,7 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args } func (s *state) evalFunction(dot reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value { - function, ok := findFunction(name, s.tmpl, s.tmpl.set) + function, ok := findFunction(name, s.tmpl) if !ok { s.errorf("%q is not a defined function", name) } @@ -398,7 +404,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node if ptr.Kind() != reflect.Interface && ptr.CanAddr() { ptr = ptr.Addr() } - if method, ok := methodByName(ptr, fieldName); ok { + if method := ptr.MethodByName(fieldName); method.IsValid() { return s.evalCall(dot, method, fieldName, args, final) } hasArgs := len(args) > 1 || final.IsValid() @@ -433,17 +439,6 @@ func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node panic("not reached") } -// TODO: delete when reflect's own MethodByName is released. -func methodByName(receiver reflect.Value, name string) (reflect.Value, bool) { - typ := receiver.Type() - for i := 0; i < typ.NumMethod(); i++ { - if typ.Method(i).Name == name { - return receiver.Method(i), true // This value includes the receiver. - } - } - return zero, false -} - var ( errorType = reflect.TypeOf((*error)(nil)).Elem() fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go index 67b9416..cf3c415 100644 --- a/libgo/go/text/template/exec_test.go +++ b/libgo/go/text/template/exec_test.go @@ -476,7 +476,7 @@ func vfunc(V, *V) string { return "vfunc" } -func testExecute(execTests []execTest, set *Set, t *testing.T) { +func testExecute(execTests []execTest, template *Template, t *testing.T) { b := new(bytes.Buffer) funcs := FuncMap{ "count": count, @@ -486,12 +486,13 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) { "zeroArgs": zeroArgs, } for _, test := range execTests { - tmpl := New(test.name).Funcs(funcs) - theSet := set - if theSet == nil { - theSet = new(Set) + var tmpl *Template + var err error + if template == nil { + tmpl, err = New(test.name).Funcs(funcs).Parse(test.input) + } else { + tmpl, err = template.New(test.name).Funcs(funcs).Parse(test.input) } - _, err := tmpl.ParseInSet(test.input, theSet) if err != nil { t.Errorf("%s: parse error: %s", test.name, err) continue @@ -663,24 +664,34 @@ func TestTree(t *testing.T) { }, }, } - set := new(Set) - _, err := set.Delims("(", ")").Parse(treeTemplate) + tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate) if err != nil { t.Fatal("parse error:", err) } var b bytes.Buffer - err = set.Execute(&b, "tree", tree) - if err != nil { - t.Fatal("exec error:", err) - } stripSpace := func(r rune) rune { if r == '\t' || r == '\n' { return -1 } return r } - result := strings.Map(stripSpace, b.String()) const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]" + // First by looking up the template. + err = tmpl.Lookup("tree").Execute(&b, tree) + if err != nil { + t.Fatal("exec error:", err) + } + result := strings.Map(stripSpace, b.String()) + if result != expect { + t.Errorf("expected %q got %q", expect, result) + } + // Then direct to execution. + b.Reset() + err = tmpl.ExecuteTemplate(&b, "tree", tree) + if err != nil { + t.Fatal("exec error:", err) + } + result = strings.Map(stripSpace, b.String()) if result != expect { t.Errorf("expected %q got %q", expect, result) } diff --git a/libgo/go/text/template/funcs.go b/libgo/go/text/template/funcs.go index 2ca09a7..d6e4bf1 100644 --- a/libgo/go/text/template/funcs.go +++ b/libgo/go/text/template/funcs.go @@ -17,8 +17,9 @@ import ( // FuncMap is the type of the map defining the mapping from names to functions. // Each function must have either a single return value, or two return values of -// which the second has type error. If the second argument evaluates to non-nil -// during execution, execution terminates and Execute returns an error. +// which the second has type error. In that case, if the second (error) +// argument evaluates to non-nil during execution, execution terminates and +// Execute returns that error. type FuncMap map[string]interface{} var builtins = FuncMap{ @@ -78,18 +79,13 @@ func goodFunc(typ reflect.Type) bool { return false } -// findFunction looks for a function in the template, set, and global map. -func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) { - if tmpl != nil { +// findFunction looks for a function in the template, and global map. +func findFunction(name string, tmpl *Template) (reflect.Value, bool) { + if tmpl != nil && tmpl.common != nil { if fn := tmpl.execFuncs[name]; fn.IsValid() { return fn, true } } - if set != nil { - if fn := set.execFuncs[name]; fn.IsValid() { - return fn, true - } - } if fn := builtinFuncs[name]; fn.IsValid() { return fn, true } @@ -310,7 +306,6 @@ func JSEscape(w io.Writer, b []byte) { if unicode.IsPrint(r) { w.Write(b[i : i+size]) } else { - // TODO(dsymonds): Do this without fmt? fmt.Fprintf(w, "\\u%04X", r) } i += size - 1 diff --git a/libgo/go/text/template/helper.go b/libgo/go/text/template/helper.go index a743a83..3636fb5 100644 --- a/libgo/go/text/template/helper.go +++ b/libgo/go/text/template/helper.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. -// Helper functions to make constructing templates and sets easier. +// Helper functions to make constructing templates easier. package template @@ -12,11 +12,11 @@ import ( "path/filepath" ) -// Functions and methods to parse a single template. +// Functions and methods to parse templates. // Must is a helper that wraps a call to a function returning (*Template, error) -// and panics if the error is non-nil. It is intended for use in variable initializations -// such as +// and panics if the error is non-nil. It is intended for use in variable +// initializations such as // var t = template.Must(template.New("name").Parse("text")) func Must(t *Template, err error) *Template { if err != nil { @@ -25,217 +25,84 @@ func Must(t *Template, err error) *Template { return t } -// ParseFile creates a new Template and parses the template definition from -// the named file. The template name is the base name of the file. -func ParseFile(filename string) (*Template, error) { - t := New(filepath.Base(filename)) - return t.ParseFile(filename) +// ParseFiles creates a new Template and parses the template definitions from +// the named files. The returned template's name will have the (base) name and +// (parsed) contents of the first file. There must be at least one file. +// If an error occurs, parsing stops and the returned *Template is nil. +func ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(nil, filenames...) } -// parseFileInSet creates a new Template and parses the template -// definition from the named file. The template name is the base name -// of the file. It also adds the template to the set. Function bindings are -// checked against those in the set. -func parseFileInSet(filename string, set *Set) (*Template, error) { - t := New(filepath.Base(filename)) - return t.parseFileInSet(filename, set) +// ParseFiles parses the named files and associates the resulting templates with +// t. If an error occurs, parsing stops and the returned template is nil; +// otherwise it is t. There must be at least one file. +func (t *Template) ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(t, filenames...) } -// ParseFile reads the template definition from a file and parses it to -// construct an internal representation of the template for execution. -// The returned template will be nil if an error occurs. -func (t *Template) ParseFile(filename string) (*Template, error) { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - return t.Parse(string(b)) -} - -// parseFileInSet is the same as ParseFile except that function bindings -// are checked against those in the set and the template is added -// to the set. -// The returned template will be nil if an error occurs. -func (t *Template) parseFileInSet(filename string, set *Set) (*Template, error) { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - return t.ParseInSet(string(b), set) -} - -// Functions and methods to parse a set. - -// SetMust is a helper that wraps a call to a function returning (*Set, error) -// and panics if the error is non-nil. It is intended for use in variable initializations -// such as -// var s = template.SetMust(template.ParseSetFiles("file")) -func SetMust(s *Set, err error) *Set { - if err != nil { - panic(err) +// parseFiles is the helper for the method and function. If the argument +// template is nil, it is created from the first file. +func parseFiles(t *Template, filenames ...string) (*Template, error) { + if len(filenames) == 0 { + // Not really a problem, but be consistent. + return nil, fmt.Errorf("template: no files named in call to ParseFiles") } - return s -} - -// ParseFiles parses the named files into a set of named templates. -// Each file must be parseable by itself. -// If an error occurs, parsing stops and the returned set is nil. -func (s *Set) ParseFiles(filenames ...string) (*Set, error) { for _, filename := range filenames { b, err := ioutil.ReadFile(filename) if err != nil { return nil, err } - _, err = s.Parse(string(b)) - if err != nil { - return nil, err + s := string(b) + name := filepath.Base(filename) + // First template becomes return value if not already defined, + // and we use that one for subsequent New calls to associate + // all the templates together. Also, if this file has the same name + // as t, this file becomes the contents of t, so + // t, err := New(name).Funcs(xxx).ParseFiles(name) + // works. Otherwise we create a new template associated with t. + var tmpl *Template + if t == nil { + t = New(name) } - } - return s, nil -} - -// ParseSetFiles creates a new Set and parses the set definition from the -// named files. Each file must be individually parseable. -func ParseSetFiles(filenames ...string) (*Set, error) { - s := new(Set) - for _, filename := range filenames { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err + if name == t.Name() { + tmpl = t + } else { + tmpl = t.New(name) } - _, err = s.Parse(string(b)) + _, err = tmpl.Parse(s) if err != nil { return nil, err } } - return s, nil + return t, nil } -// ParseGlob parses the set definition from the files identified by the -// pattern. The pattern is processed by filepath.Glob and must match at -// least one file. -// If an error occurs, parsing stops and the returned set is nil. -func (s *Set) ParseGlob(pattern string) (*Set, error) { - filenames, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - if len(filenames) == 0 { - return nil, fmt.Errorf("pattern matches no files: %#q", pattern) - } - return s.ParseFiles(filenames...) +// ParseGlob creates a new Template and parses the template definitions from the +// files identified by the pattern, which must match at least one file. The +// returned template will have the (base) name and (parsed) contents of the +// first file matched by the pattern. ParseGlob is equivalent to calling +// ParseFiles with the list of files matched by the pattern. +func ParseGlob(pattern string) (*Template, error) { + return parseGlob(nil, pattern) } -// ParseSetGlob creates a new Set and parses the set definition from the -// files identified by the pattern. The pattern is processed by filepath.Glob -// and must match at least one file. -func ParseSetGlob(pattern string) (*Set, error) { - set, err := new(Set).ParseGlob(pattern) - if err != nil { - return nil, err - } - return set, nil +// ParseGlob parses the template definitions in the files identified by the +// pattern and associates the resulting templates with t. The pattern is +// processed by filepath.Glob and must match at least one file. ParseGlob is +// equivalent to calling t.ParseFiles with the list of files matched by the +// pattern. +func (t *Template) ParseGlob(pattern string) (*Template, error) { + return parseGlob(t, pattern) } -// Functions and methods to parse stand-alone template files into a set. - -// ParseTemplateFiles parses the named template files and adds -// them to the set. Each template will be named the base name of -// its file. -// Unlike with ParseFiles, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateFiles is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. -// If an error occurs, parsing stops and the returned set is nil. -func (s *Set) ParseTemplateFiles(filenames ...string) (*Set, error) { - for _, filename := range filenames { - _, err := parseFileInSet(filename, s) - if err != nil { - return nil, err - } - } - return s, nil -} - -// ParseTemplateGlob parses the template files matched by the -// patern and adds them to the set. Each template will be named -// the base name of its file. -// Unlike with ParseGlob, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateGlob is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. -// If an error occurs, parsing stops and the returned set is nil. -func (s *Set) ParseTemplateGlob(pattern string) (*Set, error) { - filenames, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - for _, filename := range filenames { - _, err := parseFileInSet(filename, s) - if err != nil { - return nil, err - } - } - return s, nil -} - -// ParseTemplateFiles creates a set by parsing the named files, -// each of which defines a single template. Each template will be -// named the base name of its file. -// Unlike with ParseFiles, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateFiles is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. Parsing stops if an error is -// encountered. -func ParseTemplateFiles(filenames ...string) (*Set, error) { - set := new(Set) - set.init() - for _, filename := range filenames { - t, err := ParseFile(filename) - if err != nil { - return nil, err - } - if err := set.add(t); err != nil { - return nil, err - } - } - return set, nil -} - -// ParseTemplateGlob creates a set by parsing the files matched -// by the pattern, each of which defines a single template. The pattern -// is processed by filepath.Glob and must match at least one file. Each -// template will be named the base name of its file. -// Unlike with ParseGlob, each file should be a stand-alone template -// definition suitable for Template.Parse (not Set.Parse); that is, the -// file does not contain {{define}} clauses. ParseTemplateGlob is -// therefore equivalent to calling the ParseFile function to create -// individual templates, which are then added to the set. -// Each file must be parseable by itself. Parsing stops if an error is -// encountered. -func ParseTemplateGlob(pattern string) (*Set, error) { - set := new(Set) +// parseGlob is the implementation of the function and method ParseGlob. +func parseGlob(t *Template, pattern string) (*Template, error) { filenames, err := filepath.Glob(pattern) if err != nil { return nil, err } if len(filenames) == 0 { - return nil, fmt.Errorf("pattern matches no files: %#q", pattern) - } - for _, filename := range filenames { - t, err := ParseFile(filename) - if err != nil { - return nil, err - } - if err := set.add(t); err != nil { - return nil, err - } + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) } - return set, nil + return parseFiles(t, filenames...) } diff --git a/libgo/go/text/template/multi_test.go b/libgo/go/text/template/multi_test.go new file mode 100644 index 0000000..7b35d263 --- /dev/null +++ b/libgo/go/text/template/multi_test.go @@ -0,0 +1,288 @@ +// Copyright 2011 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 template + +// Tests for mulitple-template parsing and execution. + +import ( + "bytes" + "fmt" + "testing" + "text/template/parse" +) + +type isEmptyTest struct { + name string + input string + empty bool +} + +var isEmptyTests = []isEmptyTest{ + {"empty", ``, true}, + {"nonempty", `hello`, false}, + {"spaces only", " \t\n \t\n", true}, + {"definition", `{{define "x"}}something{{end}}`, true}, + {"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true}, + {"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n}}", false}, + {"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false}, +} + +func TestIsEmpty(t *testing.T) { + for _, test := range isEmptyTests { + template, err := New("root").Parse(test.input) + if err != nil { + t.Errorf("%q: unexpected error: %v", test.name, err) + continue + } + if empty := isEmpty(template.Root); empty != test.empty { + t.Errorf("%q: expected %t got %t", test.name, test.empty, empty) + } + } +} + +const ( + noError = true + hasError = false +) + +type multiParseTest struct { + name string + input string + ok bool + names []string + results []string +} + +var multiParseTests = []multiParseTest{ + {"empty", "", noError, + nil, + nil}, + {"one", `{{define "foo"}} FOO {{end}}`, noError, + []string{"foo"}, + []string{`[(text: " FOO ")]`}}, + {"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError, + []string{"foo", "bar"}, + []string{`[(text: " FOO ")]`, `[(text: " BAR ")]`}}, + // errors + {"missing end", `{{define "foo"}} FOO `, hasError, + nil, + nil}, + {"malformed name", `{{define "foo}} FOO `, hasError, + nil, + nil}, +} + +func TestMultiParse(t *testing.T) { + for _, test := range multiParseTests { + template, err := New("root").Parse(test.input) + switch { + case err == nil && !test.ok: + t.Errorf("%q: expected error; got none", test.name) + continue + case err != nil && test.ok: + t.Errorf("%q: unexpected error: %v", test.name, err) + continue + case err != nil && !test.ok: + // expected error, got one + if *debug { + fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) + } + continue + } + if template == nil { + continue + } + if len(template.tmpl) != len(test.names)+1 { // +1 for root + t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tmpl)) + continue + } + for i, name := range test.names { + tmpl, ok := template.tmpl[name] + if !ok { + t.Errorf("%s: can't find template %q", test.name, name) + continue + } + result := tmpl.Root.String() + if result != test.results[i] { + t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i]) + } + } + } +} + +var multiExecTests = []execTest{ + {"empty", "", "", nil, true}, + {"text", "some text", "some text", nil, true}, + {"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true}, + {"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true}, + {"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true}, + {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true}, + {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true}, + {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true}, + {"variable declared by template", `{{template "nested" $x=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true}, + + // User-defined function: test argument evaluator. + {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true}, + {"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true}, +} + +// These strings are also in testdata/*. +const multiText1 = ` + {{define "x"}}TEXT{{end}} + {{define "dotV"}}{{.V}}{{end}} +` + +const multiText2 = ` + {{define "dot"}}{{.}}{{end}} + {{define "nested"}}{{template "dot" .}}{{end}} +` + +func TestMultiExecute(t *testing.T) { + // Declare a couple of templates first. + template, err := New("root").Parse(multiText1) + if err != nil { + t.Fatalf("parse error for 1: %s", err) + } + _, err = template.Parse(multiText2) + if err != nil { + t.Fatalf("parse error for 2: %s", err) + } + testExecute(multiExecTests, template, t) +} + +func TestParseFiles(t *testing.T) { + _, err := ParseFiles("DOES NOT EXIST") + if err == nil { + t.Error("expected error for non-existent file; got none") + } + template := New("root") + _, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(multiExecTests, template, t) +} + +func TestParseGlob(t *testing.T) { + _, err := ParseGlob("DOES NOT EXIST") + if err == nil { + t.Error("expected error for non-existent file; got none") + } + _, err = New("error").ParseGlob("[x") + if err == nil { + t.Error("expected error for bad pattern; got none") + } + template := New("root") + _, err = template.ParseGlob("testdata/file*.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(multiExecTests, template, t) +} + +// In these tests, actual content (not just template definitions) comes from the parsed files. + +var templateFileExecTests = []execTest{ + {"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true}, +} + +func TestParseFilesWithData(t *testing.T) { + template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(templateFileExecTests, template, t) +} + +func TestParseGlobWithData(t *testing.T) { + template, err := New("root").ParseGlob("testdata/tmpl*.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(templateFileExecTests, template, t) +} + +const ( + cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}` + cloneText2 = `{{define "b"}}b{{end}}` + cloneText3 = `{{define "c"}}root{{end}}` + cloneText4 = `{{define "c"}}clone{{end}}` +) + +func TestClone(t *testing.T) { + // Create some templates and clone the root. + root, err := New("root").Parse(cloneText1) + if err != nil { + t.Fatal(err) + } + _, err = root.Parse(cloneText2) + if err != nil { + t.Fatal(err) + } + clone := root.Clone() + // Add variants to both. + _, err = root.Parse(cloneText3) + if err != nil { + t.Fatal(err) + } + _, err = clone.Parse(cloneText4) + if err != nil { + t.Fatal(err) + } + // Verify that the clone is self-consistent. + for k, v := range clone.tmpl { + if k == clone.name && v.tmpl[k] != clone { + t.Error("clone does not contain root") + } + if v != v.tmpl[v.name] { + t.Errorf("clone does not contain self for %q", k) + } + } + // Execute root. + var b bytes.Buffer + err = root.ExecuteTemplate(&b, "a", 0) + if err != nil { + t.Fatal(err) + } + if b.String() != "broot" { + t.Errorf("expected %q got %q", "broot", b.String()) + } + // Execute copy. + b.Reset() + err = clone.ExecuteTemplate(&b, "a", 0) + if err != nil { + t.Fatal(err) + } + if b.String() != "bclone" { + t.Errorf("expected %q got %q", "bclone", b.String()) + } +} + +func TestAddParseTree(t *testing.T) { + // Create some templates. + root, err := New("root").Parse(cloneText1) + if err != nil { + t.Fatal(err) + } + _, err = root.Parse(cloneText2) + if err != nil { + t.Fatal(err) + } + // Add a new parse tree. + tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins) + if err != nil { + t.Fatal(err) + } + added, err := root.AddParseTree("c", tree["c"]) + // Execute. + var b bytes.Buffer + err = added.ExecuteTemplate(&b, "a", 0) + if err != nil { + t.Fatal(err) + } + if b.String() != "broot" { + t.Errorf("expected %q got %q", "broot", b.String()) + } +} diff --git a/libgo/go/text/template/parse.go b/libgo/go/text/template/parse.go deleted file mode 100644 index 7075f2a..0000000 --- a/libgo/go/text/template/parse.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2011 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 template - -import ( - "reflect" - "text/template/parse" -) - -// Template is the representation of a parsed template. -type Template struct { - name string - *parse.Tree - leftDelim string - rightDelim string - // We use two maps, one for parsing and one for execution. - // This separation makes the API cleaner since it doesn't - // expose reflection to the client. - parseFuncs FuncMap - execFuncs map[string]reflect.Value - set *Set // can be nil. -} - -// Name returns the name of the template. -func (t *Template) Name() string { - return t.name -} - -// Parsing. - -// New allocates a new template with the given name. -func New(name string) *Template { - return &Template{ - name: name, - parseFuncs: make(FuncMap), - execFuncs: make(map[string]reflect.Value), - } -} - -// Delims sets the action delimiters, to be used in a subsequent -// parse, to the specified strings. -// An empty delimiter stands for the corresponding default: {{ or }}. -// The return value is the template, so calls can be chained. -func (t *Template) Delims(left, right string) *Template { - t.leftDelim = left - t.rightDelim = right - return t -} - -// Funcs adds the elements of the argument map to the template's function -// map. It panics if a value in the map is not a function with appropriate -// return type. -// The return value is the template, so calls can be chained. -func (t *Template) Funcs(funcMap FuncMap) *Template { - addValueFuncs(t.execFuncs, funcMap) - addFuncs(t.parseFuncs, funcMap) - return t -} - -// Parse parses the template definition string to construct an internal -// representation of the template for execution. -func (t *Template) Parse(s string) (tmpl *Template, err error) { - t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, nil, t.parseFuncs, builtins) - if err != nil { - return nil, err - } - return t, nil -} - -// ParseInSet parses the template definition string to construct an internal -// representation of the template for execution. It also adds the template -// to the set, which must not be nil. It is an error if s is already defined in the set. -// Function bindings are checked against those in the set. -func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err error) { - t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, set.trees, t.parseFuncs, set.parseFuncs, builtins) - if err != nil { - return nil, err - } - err = set.add(t) - return t, err -} diff --git a/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go index e906ee8..346f613 100644 --- a/libgo/go/text/template/parse/parse.go +++ b/libgo/go/text/template/parse/parse.go @@ -13,10 +13,10 @@ import ( "unicode" ) -// Tree is the representation of a parsed template. +// Tree is the representation of a single parsed template. type Tree struct { - Name string // Name is the name of the template. - Root *ListNode // Root is the top-level root of the parse tree. + Name string // name of the template represented by the tree. + Root *ListNode // top-level root of the tree. // Parsing only; cleared after parse. funcs []map[string]interface{} lex *lexer @@ -25,6 +25,16 @@ type Tree struct { vars []string // variables defined at the moment. } +// Parse returns a map from template name to parse.Tree, created by parsing the +// templates described in the argument string. The top-level template will be +// given the specified name. If an error is encountered, parsing stops and an +// empty map is returned with the error. +func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) { + treeSet = make(map[string]*Tree) + _, err = New(name).Parse(text, leftDelim, rightDelim, treeSet, funcs...) + return +} + // next returns the next token. func (t *Tree) next() item { if t.peekCount > 0 { @@ -58,7 +68,7 @@ func (t *Tree) peek() item { // Parsing. -// New allocates a new template with the given name. +// New allocates a new parse tree with the given name. func New(name string, funcs ...map[string]interface{}) *Tree { return &Tree{ Name: name, @@ -87,6 +97,15 @@ func (t *Tree) expect(expected itemType, context string) item { return token } +// expectEither consumes the next token and guarantees it has one of the required types. +func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { + token := t.next() + if token.typ != expected1 && token.typ != expected2 { + t.errorf("expected %s or %s in %s; got %s", expected1, expected2, context, token) + } + return token +} + // unexpected complains about the token and terminates processing. func (t *Tree) unexpected(token item, context string) { t.errorf("unexpected %s in %s", token, context) @@ -107,7 +126,7 @@ func (t *Tree) recover(errp *error) { return } -// startParse starts the template parsing from the lexer. +// startParse initializes the parser, using the lexer. func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) { t.Root = nil t.lex = lex @@ -143,17 +162,27 @@ func (t *Tree) atEOF() bool { return false } -// Parse parses the template definition string to construct an internal -// representation of the template for execution. If either action delimiter -// string is empty, the default ("{{" or "}}") is used. +// Parse parses the template definition string to construct a representation of +// the template for execution. If either action delimiter string is empty, the +// default ("{{" or "}}") is used. Embedded template definitions are added to +// the treeSet map. func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { defer t.recover(&err) t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim)) t.parse(treeSet) + t.add(treeSet) t.stopParse() return t, nil } +// add adds tree to the treeSet. +func (t *Tree) add(treeSet map[string]*Tree) { + if _, present := treeSet[t.Name]; present { + t.errorf("template: multiple definition of template %q", t.Name) + } + treeSet[t.Name] = t +} + // parse is the top-level parser for a template, essentially the same // as itemList except it also parses {{define}} actions. // It runs to EOF. @@ -163,7 +192,7 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) { if t.peek().typ == itemLeftDelim { delim := t.next() if t.next().typ == itemDefine { - newT := New("new definition") // name will be updated once we know it. + newT := New("definition") // name will be updated once we know it. newT.startParse(t.funcs, t.lex) newT.parseDefinition(treeSet) continue @@ -183,11 +212,8 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) { // installs the definition in the treeSet map. The "define" keyword has already // been scanned. func (t *Tree) parseDefinition(treeSet map[string]*Tree) { - if treeSet == nil { - t.errorf("no set specified for template definition") - } const context = "define clause" - name := t.expect(itemString, context) + name := t.expectOneOf(itemString, itemRawString, context) var err error t.Name, err = strconv.Unquote(name.val) if err != nil { @@ -200,10 +226,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) { t.errorf("unexpected %s in %s", end, context) } t.stopParse() - if _, present := treeSet[t.Name]; present { - t.errorf("template: %q multiply defined", name) - } - treeSet[t.Name] = t + t.add(treeSet) } // itemList: diff --git a/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go index 5c10086..fc93455 100644 --- a/libgo/go/text/template/parse/parse_test.go +++ b/libgo/go/text/template/parse/parse_test.go @@ -236,7 +236,7 @@ var builtins = map[string]interface{}{ func TestParse(t *testing.T) { for _, test := range parseTests { - tmpl, err := New(test.name).Parse(test.input, "", "", nil, builtins) + tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins) switch { case err == nil && !test.ok: t.Errorf("%q: expected error; got none", test.name) diff --git a/libgo/go/text/template/parse/set.go b/libgo/go/text/template/parse/set.go deleted file mode 100644 index 55f3ceb..0000000 --- a/libgo/go/text/template/parse/set.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2011 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 parse - -// Set returns a slice of Trees created by parsing the template set -// definition in the argument string. If an error is encountered, -// parsing stops and an empty slice is returned with the error. -func Set(text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree map[string]*Tree, err error) { - tree = make(map[string]*Tree) - // Top-level template name is needed but unused. TODO: clean this up. - _, err = New("ROOT").Parse(text, leftDelim, rightDelim, tree, funcs...) - return -} diff --git a/libgo/go/text/template/set.go b/libgo/go/text/template/set.go deleted file mode 100644 index 4841704..0000000 --- a/libgo/go/text/template/set.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2011 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 template - -import ( - "fmt" - "io" - "reflect" - "text/template/parse" -) - -// Set holds a set of related templates that can refer to one another by name. -// The zero value represents an empty set. -// A template may be a member of multiple sets. -type Set struct { - tmpl map[string]*Template - trees map[string]*parse.Tree // maintained by parse package - leftDelim string - rightDelim string - parseFuncs FuncMap - execFuncs map[string]reflect.Value -} - -func (s *Set) init() { - if s.tmpl == nil { - s.tmpl = make(map[string]*Template) - s.parseFuncs = make(FuncMap) - s.execFuncs = make(map[string]reflect.Value) - } -} - -// Delims sets the action delimiters, to be used in a subsequent -// parse, to the specified strings. -// An empty delimiter stands for the corresponding default: {{ or }}. -// The return value is the set, so calls can be chained. -func (s *Set) Delims(left, right string) *Set { - s.leftDelim = left - s.rightDelim = right - return s -} - -// Funcs adds the elements of the argument map to the set's function map. It -// panics if a value in the map is not a function with appropriate return -// type. -// The return value is the set, so calls can be chained. -func (s *Set) Funcs(funcMap FuncMap) *Set { - s.init() - addValueFuncs(s.execFuncs, funcMap) - addFuncs(s.parseFuncs, funcMap) - return s -} - -// Add adds the argument templates to the set. It panics if two templates -// with the same name are added or if a template is already a member of -// a set. -// The return value is the set, so calls can be chained. -func (s *Set) Add(templates ...*Template) *Set { - for _, t := range templates { - if err := s.add(t); err != nil { - panic(err) - } - } - return s -} - -// add adds the argument template to the set. -func (s *Set) add(t *Template) error { - s.init() - if t.set != nil { - return fmt.Errorf("template: %q already in a set", t.name) - } - if _, ok := s.tmpl[t.name]; ok { - return fmt.Errorf("template: %q already defined in set", t.name) - } - s.tmpl[t.name] = t - t.set = s - return nil -} - -// Template returns the template with the given name in the set, -// or nil if there is no such template. -func (s *Set) Template(name string) *Template { - return s.tmpl[name] -} - -// FuncMap returns the set's function map. -func (s *Set) FuncMap() FuncMap { - return s.parseFuncs -} - -// Execute applies the named template to the specified data object, writing -// the output to wr. -func (s *Set) Execute(wr io.Writer, name string, data interface{}) error { - tmpl := s.tmpl[name] - if tmpl == nil { - return fmt.Errorf("template: no template %q in set", name) - } - return tmpl.Execute(wr, data) -} - -// Parse parses a string into a set of named templates. Parse may be called -// multiple times for a given set, adding the templates defined in the string -// to the set. It is an error if a template has a name already defined in the set. -func (s *Set) Parse(text string) (*Set, error) { - trees, err := parse.Set(text, s.leftDelim, s.rightDelim, s.parseFuncs, builtins) - if err != nil { - return nil, err - } - s.init() - for name, tree := range trees { - tmpl := New(name) - tmpl.Tree = tree - err = s.add(tmpl) - if err != nil { - return s, err - } - } - return s, nil -} diff --git a/libgo/go/text/template/set_test.go b/libgo/go/text/template/set_test.go deleted file mode 100644 index f437bc7..0000000 --- a/libgo/go/text/template/set_test.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2011 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 template - -import ( - "fmt" - "testing" -) - -const ( - noError = true - hasError = false -) - -type setParseTest struct { - name string - input string - ok bool - names []string - results []string -} - -var setParseTests = []setParseTest{ - {"empty", "", noError, - nil, - nil}, - {"one", `{{define "foo"}} FOO {{end}}`, noError, - []string{"foo"}, - []string{`[(text: " FOO ")]`}}, - {"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError, - []string{"foo", "bar"}, - []string{`[(text: " FOO ")]`, `[(text: " BAR ")]`}}, - // errors - {"missing end", `{{define "foo"}} FOO `, hasError, - nil, - nil}, - {"malformed name", `{{define "foo}} FOO `, hasError, - nil, - nil}, -} - -func TestSetParse(t *testing.T) { - for _, test := range setParseTests { - set, err := new(Set).Parse(test.input) - switch { - case err == nil && !test.ok: - t.Errorf("%q: expected error; got none", test.name) - continue - case err != nil && test.ok: - t.Errorf("%q: unexpected error: %v", test.name, err) - continue - case err != nil && !test.ok: - // expected error, got one - if *debug { - fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) - } - continue - } - if set == nil { - continue - } - if len(set.tmpl) != len(test.names) { - t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(set.tmpl)) - continue - } - for i, name := range test.names { - tmpl, ok := set.tmpl[name] - if !ok { - t.Errorf("%s: can't find template %q", test.name, name) - continue - } - result := tmpl.Root.String() - if result != test.results[i] { - t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i]) - } - } - } -} - -var setExecTests = []execTest{ - {"empty", "", "", nil, true}, - {"text", "some text", "some text", nil, true}, - {"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true}, - {"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true}, - {"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true}, - {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true}, - {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true}, - {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true}, - {"variable declared by template", `{{template "nested" $x=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true}, - - // User-defined function: test argument evaluator. - {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true}, - {"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true}, -} - -// These strings are also in testdata/*. -const setText1 = ` - {{define "x"}}TEXT{{end}} - {{define "dotV"}}{{.V}}{{end}} -` - -const setText2 = ` - {{define "dot"}}{{.}}{{end}} - {{define "nested"}}{{template "dot" .}}{{end}} -` - -func TestSetExecute(t *testing.T) { - // Declare a set with a couple of templates first. - set := new(Set) - _, err := set.Parse(setText1) - if err != nil { - t.Fatalf("error parsing set: %s", err) - } - _, err = set.Parse(setText2) - if err != nil { - t.Fatalf("error parsing set: %s", err) - } - testExecute(setExecTests, set, t) -} - -func TestSetParseFiles(t *testing.T) { - set := new(Set) - _, err := set.ParseFiles("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = set.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(setExecTests, set, t) -} - -func TestParseSetFiles(t *testing.T) { - set := new(Set) - _, err := ParseSetFiles("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - set, err = ParseSetFiles("testdata/file1.tmpl", "testdata/file2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(setExecTests, set, t) -} - -func TestSetParseGlob(t *testing.T) { - _, err := new(Set).ParseGlob("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = new(Set).ParseGlob("[x") - if err == nil { - t.Error("expected error for bad pattern; got none") - } - set, err := new(Set).ParseGlob("testdata/file*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(setExecTests, set, t) -} - -func TestParseSetGlob(t *testing.T) { - _, err := ParseSetGlob("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = ParseSetGlob("[x") - if err == nil { - t.Error("expected error for bad pattern; got none") - } - set, err := ParseSetGlob("testdata/file*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(setExecTests, set, t) -} - -var templateFileExecTests = []execTest{ - {"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\ntemplate2\n", 0, true}, -} - -func TestSetParseTemplateFiles(t *testing.T) { - _, err := ParseTemplateFiles("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - set, err := new(Set).ParseTemplateFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, set, t) -} - -func TestParseTemplateFiles(t *testing.T) { - _, err := ParseTemplateFiles("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - set, err := new(Set).ParseTemplateFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, set, t) -} - -func TestSetParseTemplateGlob(t *testing.T) { - _, err := ParseTemplateGlob("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = new(Set).ParseTemplateGlob("[x") - if err == nil { - t.Error("expected error for bad pattern; got none") - } - set, err := new(Set).ParseTemplateGlob("testdata/tmpl*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, set, t) -} - -func TestParseTemplateGlob(t *testing.T) { - _, err := ParseTemplateGlob("DOES NOT EXIST") - if err == nil { - t.Error("expected error for non-existent file; got none") - } - _, err = ParseTemplateGlob("[x") - if err == nil { - t.Error("expected error for bad pattern; got none") - } - set, err := ParseTemplateGlob("testdata/tmpl*.tmpl") - if err != nil { - t.Fatalf("error parsing files: %v", err) - } - testExecute(templateFileExecTests, set, t) -} diff --git a/libgo/go/text/template/template.go b/libgo/go/text/template/template.go new file mode 100644 index 0000000..04fca40 --- /dev/null +++ b/libgo/go/text/template/template.go @@ -0,0 +1,236 @@ +// Copyright 2011 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 template + +import ( + "bytes" + "fmt" + "reflect" + "text/template/parse" +) + +// common holds the information shared by related templates. +type common struct { + tmpl map[string]*Template + // We use two maps, one for parsing and one for execution. + // This separation makes the API cleaner since it doesn't + // expose reflection to the client. + parseFuncs FuncMap + execFuncs map[string]reflect.Value +} + +// Template is the representation of a parsed template. The *parse.Tree +// field is exported only for use by html/template and should be treated +// as unexported by all other clients. +type Template struct { + name string + *parse.Tree + *common + leftDelim string + rightDelim string +} + +// New allocates a new template with the given name. +func New(name string) *Template { + return &Template{ + name: name, + } +} + +// Name returns the name of the template. +func (t *Template) Name() string { + return t.name +} + +// New allocates a new template associated with the given one and with the same +// delimiters. The association, which is transitive, allows one template to +// invoke another with a {{template}} action. +func (t *Template) New(name string) *Template { + t.init() + return &Template{ + name: name, + common: t.common, + leftDelim: t.leftDelim, + rightDelim: t.rightDelim, + } +} + +func (t *Template) init() { + if t.common == nil { + t.common = new(common) + t.tmpl = make(map[string]*Template) + t.parseFuncs = make(FuncMap) + t.execFuncs = make(map[string]reflect.Value) + } +} + +// Clone returns a duplicate of the template, including all associated +// templates. The actual representation is not copied, but the name space of +// associated templates is, so further calls to Parse in the copy will add +// templates to the copy but not to the original. Clone can be used to prepare +// common templates and use them with variant definitions for other templates by +// adding the variants after the clone is made. +func (t *Template) Clone() *Template { + nt := t.copy(nil) + nt.init() + nt.tmpl[t.name] = nt + for k, v := range t.tmpl { + if k == t.name { // Already installed. + continue + } + // The associated templates share nt's common structure. + tmpl := v.copy(nt.common) + nt.tmpl[k] = tmpl + } + for k, v := range t.parseFuncs { + nt.parseFuncs[k] = v + } + for k, v := range t.execFuncs { + nt.execFuncs[k] = v + } + return nt +} + +// copy returns a shallow copy of t, with common set to the argument. +func (t *Template) copy(c *common) *Template { + nt := New(t.name) + nt.Tree = t.Tree + nt.common = c + nt.leftDelim = t.leftDelim + nt.rightDelim = t.rightDelim + return nt +} + +// AddParseTree creates a new template with the name and parse tree +// and associates it with t. +func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { + if t.tmpl[name] != nil { + return nil, fmt.Errorf("template: redefinition of template %q", name) + } + nt := t.New(name) + nt.Tree = tree + t.tmpl[name] = nt + return nt, nil +} + +// Templates returns a slice of the templates associated with t, including t +// itself. +func (t *Template) Templates() []*Template { + // Return a slice so we don't expose the map. + m := make([]*Template, 0, len(t.tmpl)) + for _, v := range t.tmpl { + m = append(m, v) + } + return m +} + +// Delims sets the action delimiters to the specified strings, to be used in +// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template +// definitions will inherit the settings. An empty delimiter stands for the +// corresponding default: {{ or }}. +// The return value is the template, so calls can be chained. +func (t *Template) Delims(left, right string) *Template { + t.leftDelim = left + t.rightDelim = right + return t +} + +// Funcs adds the elements of the argument map to the template's function map. +// It panics if a value in the map is not a function with appropriate return +// type. However, it is legal to overwrite elements of the map. The return +// value is the template, so calls can be chained. +func (t *Template) Funcs(funcMap FuncMap) *Template { + t.init() + addValueFuncs(t.execFuncs, funcMap) + addFuncs(t.parseFuncs, funcMap) + return t +} + +// Lookup returns the template with the given name that is associated with t, +// or nil if there is no such template. +func (t *Template) Lookup(name string) *Template { + if t.common == nil { + return nil + } + return t.tmpl[name] +} + +// Parse parses a string into a template. Nested template definitions will be +// associated with the top-level template t. Parse may be called multiple times +// to parse definitions of templates to associate with t. It is an error if a +// resulting template is non-empty (contains content other than template +// definitions) and would replace a non-empty template with the same name. +// (In multiple calls to Parse with the same receiver template, only one call +// can contain text other than space, comments, and template definitions.) +func (t *Template) Parse(text string) (*Template, error) { + t.init() + trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins) + if err != nil { + return nil, err + } + // Add the newly parsed trees, including the one for t, into our common structure. + for name, tree := range trees { + // If the name we parsed is the name of this template, overwrite this template. + // The associate method checks it's not a redefinition. + tmpl := t + if name != t.name { + tmpl = t.New(name) + } + // Even if t == tmpl, we need to install it in the common.tmpl map. + if err := t.associate(tmpl); err != nil { + return nil, err + } + tmpl.Tree = tree + tmpl.leftDelim = t.leftDelim + tmpl.rightDelim = t.rightDelim + } + return t, nil +} + +// associate installs the new template into the group of templates associated +// with t. It is an error to reuse a name except to overwrite an empty +// template. The two are already known to share the common structure. +func (t *Template) associate(new *Template) error { + if new.common != t.common { + panic("internal error: associate not common") + } + name := new.name + if old := t.tmpl[name]; old != nil { + oldIsEmpty := isEmpty(old.Root) + newIsEmpty := isEmpty(new.Root) + if !oldIsEmpty && !newIsEmpty { + return fmt.Errorf("template: redefinition of template %q", name) + } + if newIsEmpty { + // Whether old is empty or not, new is empty; no reason to replace old. + return nil + } + } + t.tmpl[name] = new + return nil +} + +// isEmpty reports whether this tree (node) is empty of everything but space. +func isEmpty(n parse.Node) bool { + switch n := n.(type) { + case *parse.ActionNode: + case *parse.IfNode: + case *parse.ListNode: + for _, node := range n.Nodes { + if !isEmpty(node) { + return false + } + } + return true + case *parse.RangeNode: + case *parse.TemplateNode: + case *parse.TextNode: + return len(bytes.TrimSpace(n.Text)) == 0 + case *parse.WithNode: + default: + panic("unknown node: " + n.String()) + } + return false +} diff --git a/libgo/go/text/template/testdata/tmpl1.tmpl b/libgo/go/text/template/testdata/tmpl1.tmpl index 3d15b81..b72b3a3 100644 --- a/libgo/go/text/template/testdata/tmpl1.tmpl +++ b/libgo/go/text/template/testdata/tmpl1.tmpl @@ -1 +1,3 @@ template1 +{{define "x"}}x{{end}} +{{template "y"}} diff --git a/libgo/go/text/template/testdata/tmpl2.tmpl b/libgo/go/text/template/testdata/tmpl2.tmpl index a374d2f..16beba6 100644 --- a/libgo/go/text/template/testdata/tmpl2.tmpl +++ b/libgo/go/text/template/testdata/tmpl2.tmpl @@ -1 +1,3 @@ template2 +{{define "y"}}y{{end}} +{{template "x"}} diff --git a/libgo/go/time/example_test.go b/libgo/go/time/example_test.go new file mode 100644 index 0000000..153b1a3 --- /dev/null +++ b/libgo/go/time/example_test.go @@ -0,0 +1,58 @@ +// Copyright 2011 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 time_test + +import ( + "fmt" + "time" +) + +func expensiveCall() {} + +func ExampleDuration() { + t0 := time.Now() + expensiveCall() + t1 := time.Now() + fmt.Printf("The call took %v to run.\n", t1.Sub(t0)) +} + +var c chan int + +func handle(int) {} + +func ExampleAfter() { + select { + case m := <-c: + handle(m) + case <-time.After(5 * time.Minute): + fmt.Println("timed out") + } +} + +func ExampleSleep() { + time.Sleep(100 * time.Millisecond) +} + +func statusUpdate() string { return "" } + +func ExampleTick() { + c := time.Tick(1 * time.Minute) + for now := range c { + fmt.Printf("%v %s\n", now, statusUpdate()) + } +} + +func ExampleMonth() { + _, month, day := time.Now().Date() + if month == time.November && day == 10 { + fmt.Println("Happy Go day!") + } +} + +// Go launched at Tue Nov 10 15:00:00 -0800 PST 2009 +func ExampleDate() { + t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + fmt.Printf("Go launched at %s\n", t.Local()) +} diff --git a/libgo/go/time/format.go b/libgo/go/time/format.go index 14b712a..082a51a 100644 --- a/libgo/go/time/format.go +++ b/libgo/go/time/format.go @@ -1,10 +1,6 @@ package time -import ( - "bytes" - "errors" - "strconv" -) +import "errors" const ( numeric = iota @@ -259,8 +255,60 @@ func lookup(tab []string, val string) (int, string, error) { return -1, val, errBad } +// Duplicates functionality in strconv, but avoids dependency. +func itoa(x int) string { + var buf [32]byte + n := len(buf) + if x == 0 { + return "0" + } + u := uint(x) + if x < 0 { + u = -u + } + for u > 0 { + n-- + buf[n] = byte(u%10 + '0') + u /= 10 + } + if x < 0 { + n-- + buf[n] = '-' + } + return string(buf[n:]) +} + +// Never printed, just needs to be non-nil for return by atoi. +var atoiError = errors.New("time: invalid number") + +// Duplicates functionality in strconv, but avoids dependency. +func atoi(s string) (x int, err error) { + i := 0 + if len(s) > 0 && s[0] == '-' { + i++ + } + if i >= len(s) { + return 0, atoiError + } + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return 0, atoiError + } + if x >= (1<<31-10)/10 { + // will overflow + return 0, atoiError + } + x = x*10 + int(c) - '0' + } + if s[0] == '-' { + x = -x + } + return x, nil +} + func pad(i int, padding string) string { - s := strconv.Itoa(i) + s := itoa(i) if i < 10 { s = padding + s } @@ -273,7 +321,7 @@ func zeroPad(i int) string { return pad(i, "0") } func formatNano(nanosec, n int) string { // User might give us bad data. Make sure it's positive and in range. // They'll get nonsense output but it will have the right format. - s := strconv.Uitoa(uint(nanosec) % 1e9) + s := itoa(int(uint(nanosec) % 1e9)) // Zero pad left without fmt. if len(s) < 9 { s = "000000000"[:9-len(s)] + s @@ -284,14 +332,42 @@ func formatNano(nanosec, n int) string { return "." + s[:n] } +// String returns the time formatted using the format string +// "Mon Jan _2 15:04:05 -0700 MST 2006" +func (t Time) String() string { + return t.Format("Mon Jan _2 15:04:05 -0700 MST 2006") +} + +type buffer []byte + +func (b *buffer) WriteString(s string) { + *b = append(*b, s...) +} + +func (b *buffer) WriteByte(c byte) { + *b = append(*b, c) +} + +func (b *buffer) String() string { + return string([]byte(*b)) +} + // Format returns a textual representation of the time value formatted // according to layout. The layout defines the format by showing the // representation of a standard time, which is then used to describe // the time to be formatted. Predefined layouts ANSIC, UnixDate, // RFC3339 and others describe standard representations. For more // information about the formats, see the documentation for ANSIC. -func (t *Time) Format(layout string) string { - b := new(bytes.Buffer) +func (t Time) Format(layout string) string { + var ( + year int = -1 + month Month + day int + hour int = -1 + min int + sec int + b buffer + ) // Each iteration generates one std value. for { prefix, std, suffix := nextStdChunk(layout) @@ -299,62 +375,92 @@ func (t *Time) Format(layout string) string { if std == "" { break } + + // Compute year, month, day if needed. + if year < 0 { + // Jan 01 02 2006 + if a, z := std[0], std[len(std)-1]; a == 'J' || a == 'j' || z == '1' || z == '2' || z == '6' { + year, month, day = t.Date() + } + } + + // Compute hour, minute, second if needed. + if hour < 0 { + // 03 04 05 15 pm + if z := std[len(std)-1]; z == '3' || z == '4' || z == '5' || z == 'm' || z == 'M' { + hour, min, sec = t.Clock() + } + } + var p string switch std { case stdYear: - p = zeroPad(int(t.Year % 100)) + p = zeroPad(year % 100) case stdLongYear: - p = strconv.Itoa64(t.Year) + p = itoa(year) case stdMonth: - p = shortMonthNames[t.Month] + p = month.String()[:3] case stdLongMonth: - p = longMonthNames[t.Month] + p = month.String() case stdNumMonth: - p = strconv.Itoa(t.Month) + p = itoa(int(month)) case stdZeroMonth: - p = zeroPad(t.Month) + p = zeroPad(int(month)) case stdWeekDay: - p = shortDayNames[t.Weekday()] + p = t.Weekday().String()[:3] case stdLongWeekDay: - p = longDayNames[t.Weekday()] + p = t.Weekday().String() case stdDay: - p = strconv.Itoa(t.Day) + p = itoa(day) case stdUnderDay: - p = pad(t.Day, " ") + p = pad(day, " ") case stdZeroDay: - p = zeroPad(t.Day) + p = zeroPad(day) case stdHour: - p = zeroPad(t.Hour) + p = zeroPad(hour) case stdHour12: // Noon is 12PM, midnight is 12AM. - hr := t.Hour % 12 + hr := hour % 12 if hr == 0 { hr = 12 } - p = strconv.Itoa(hr) + p = itoa(hr) case stdZeroHour12: // Noon is 12PM, midnight is 12AM. - hr := t.Hour % 12 + hr := hour % 12 if hr == 0 { hr = 12 } p = zeroPad(hr) case stdMinute: - p = strconv.Itoa(t.Minute) + p = itoa(min) case stdZeroMinute: - p = zeroPad(t.Minute) + p = zeroPad(min) case stdSecond: - p = strconv.Itoa(t.Second) + p = itoa(sec) case stdZeroSecond: - p = zeroPad(t.Second) + p = zeroPad(sec) + case stdPM: + if hour >= 12 { + p = "PM" + } else { + p = "AM" + } + case stdpm: + if hour >= 12 { + p = "pm" + } else { + p = "am" + } case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ: // Ugly special case. We cheat and take the "Z" variants // to mean "the time zone as formatted for ISO 8601". - if t.ZoneOffset == 0 && std[0] == 'Z' { + _, offset := t.Zone() + if offset == 0 && std[0] == 'Z' { p = "Z" break } - zone := t.ZoneOffset / 60 // convert to minutes + zone := offset / 60 // convert to minutes if zone < 0 { p = "-" zone = -zone @@ -366,25 +472,14 @@ func (t *Time) Format(layout string) string { p += ":" } p += zeroPad(zone % 60) - case stdPM: - if t.Hour >= 12 { - p = "PM" - } else { - p = "AM" - } - case stdpm: - if t.Hour >= 12 { - p = "pm" - } else { - p = "am" - } case stdTZ: - if t.Zone != "" { - p = t.Zone + name, offset := t.Zone() + if name != "" { + p = name } else { // No time zone known for this time, but we must print one. // Use the -0700 format. - zone := t.ZoneOffset / 60 // convert to minutes + zone := offset / 60 // convert to minutes if zone < 0 { p = "-" zone = -zone @@ -396,7 +491,7 @@ func (t *Time) Format(layout string) string { } default: if len(std) >= 2 && std[0:2] == ".0" { - p = formatNano(t.Nanosecond, len(std)-1) + p = formatNano(t.Nanosecond(), len(std)-1) } } b.WriteString(p) @@ -405,14 +500,6 @@ func (t *Time) Format(layout string) string { return b.String() } -// String returns a Unix-style representation of the time value. -func (t *Time) String() string { - if t == nil { - return "<nil>" - } - return t.Format(UnixDate) -} - var errBad = errors.New("bad value for field") // placeholder not passed to user // ParseError describes a problem parsing a time string. @@ -424,17 +511,21 @@ type ParseError struct { Message string } -// String is the string representation of a ParseError. +func quote(s string) string { + return "\"" + s + "\"" +} + +// Error returns the string representation of a ParseError. func (e *ParseError) Error() string { if e.Message == "" { return "parsing time " + - strconv.Quote(e.Value) + " as " + - strconv.Quote(e.Layout) + ": cannot parse " + - strconv.Quote(e.ValueElem) + " as " + - strconv.Quote(e.LayoutElem) + quote(e.Value) + " as " + + quote(e.Layout) + ": cannot parse " + + quote(e.ValueElem) + " as " + + quote(e.LayoutElem) } return "parsing time " + - strconv.Quote(e.Value) + e.Message + quote(e.Value) + e.Message } // isDigit returns true if s[i] is a decimal digit, false if not or @@ -498,30 +589,42 @@ func skip(value, prefix string) (string, error) { // representations.For more information about the formats, see the // documentation for ANSIC. // -// Only those elements present in the value will be set in the returned time -// structure. Also, if the input string represents an inconsistent time -// (such as having the wrong day of the week), the returned value will also -// be inconsistent. In any case, the elements of the returned time will be -// sane: hours in 0..23, minutes in 0..59, day of month in 1..31, etc. +// Elements omitted from the value are assumed to be zero, or when +// zero is impossible, one, so parsing "3:04pm" returns the time +// corresponding to Jan 1, year 0, 15:04:00 UTC. // Years must be in the range 0000..9999. The day of the week is checked // for syntax but it is otherwise ignored. -func Parse(alayout, avalue string) (*Time, error) { - var t Time +func Parse(layout, value string) (Time, error) { + alayout, avalue := layout, value rangeErrString := "" // set if a value is out of range amSet := false // do we need to subtract 12 from the hour for midnight? pmSet := false // do we need to add 12 to the hour? - layout, value := alayout, avalue + + // Time being constructed. + var ( + year int + month int = 1 // January + day int = 1 + hour int + min int + sec int + nsec int + z *Location + zoneOffset int = -1 + zoneName string + ) + // Each iteration processes one std value. for { var err error prefix, std, suffix := nextStdChunk(layout) value, err = skip(value, prefix) if err != nil { - return nil, &ParseError{alayout, avalue, prefix, value, ""} + return Time{}, &ParseError{alayout, avalue, prefix, value, ""} } if len(std) == 0 { if len(value) != 0 { - return nil, &ParseError{alayout, avalue, "", value, ": extra text: " + value} + return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + value} } break } @@ -534,11 +637,11 @@ func Parse(alayout, avalue string) (*Time, error) { break } p, value = value[0:2], value[2:] - t.Year, err = strconv.Atoi64(p) - if t.Year >= 69 { // Unix time starts Dec 31 1969 in some time zones - t.Year += 1900 + year, err = atoi(p) + if year >= 69 { // Unix time starts Dec 31 1969 in some time zones + year += 1900 } else { - t.Year += 2000 + year += 2000 } case stdLongYear: if len(value) < 4 || !isDigit(value, 0) { @@ -546,14 +649,14 @@ func Parse(alayout, avalue string) (*Time, error) { break } p, value = value[0:4], value[4:] - t.Year, err = strconv.Atoi64(p) + year, err = atoi(p) case stdMonth: - t.Month, value, err = lookup(shortMonthNames, value) + month, value, err = lookup(shortMonthNames, value) case stdLongMonth: - t.Month, value, err = lookup(longMonthNames, value) + month, value, err = lookup(longMonthNames, value) case stdNumMonth, stdZeroMonth: - t.Month, value, err = getnum(value, std == stdZeroMonth) - if t.Month <= 0 || 12 < t.Month { + month, value, err = getnum(value, std == stdZeroMonth) + if month <= 0 || 12 < month { rangeErrString = "month" } case stdWeekDay: @@ -565,29 +668,28 @@ func Parse(alayout, avalue string) (*Time, error) { if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } - t.Day, value, err = getnum(value, std == stdZeroDay) - if t.Day < 0 || 31 < t.Day { - // TODO: be more thorough in date check? + day, value, err = getnum(value, std == stdZeroDay) + if day < 0 || 31 < day { rangeErrString = "day" } case stdHour: - t.Hour, value, err = getnum(value, false) - if t.Hour < 0 || 24 <= t.Hour { + hour, value, err = getnum(value, false) + if hour < 0 || 24 <= hour { rangeErrString = "hour" } case stdHour12, stdZeroHour12: - t.Hour, value, err = getnum(value, std == stdZeroHour12) - if t.Hour < 0 || 12 < t.Hour { + hour, value, err = getnum(value, std == stdZeroHour12) + if hour < 0 || 12 < hour { rangeErrString = "hour" } case stdMinute, stdZeroMinute: - t.Minute, value, err = getnum(value, std == stdZeroMinute) - if t.Minute < 0 || 60 <= t.Minute { + min, value, err = getnum(value, std == stdZeroMinute) + if min < 0 || 60 <= min { rangeErrString = "minute" } case stdSecond, stdZeroSecond: - t.Second, value, err = getnum(value, std == stdZeroSecond) - if t.Second < 0 || 60 <= t.Second { + sec, value, err = getnum(value, std == stdZeroSecond) + if sec < 0 || 60 <= sec { rangeErrString = "second" } // Special case: do we have a fractional second but no @@ -602,16 +704,44 @@ func Parse(alayout, avalue string) (*Time, error) { n := 2 for ; n < len(value) && isDigit(value, n); n++ { } - rangeErrString, err = t.parseNanoseconds(value, n) + nsec, rangeErrString, err = parseNanoseconds(value, n) value = value[n:] } + case stdPM: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "PM": + pmSet = true + case "AM": + amSet = true + default: + err = errBad + } + case stdpm: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "pm": + pmSet = true + case "am": + amSet = true + default: + err = errBad + } case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ: if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' { value = value[1:] - t.Zone = "UTC" + z = UTC break } - var sign, hh, mm string + var sign, hour, min string if std == stdISO8601ColonTZ || std == stdNumColonTZ { if len(value) < 6 { err = errBad @@ -621,65 +751,38 @@ func Parse(alayout, avalue string) (*Time, error) { err = errBad break } - sign, hh, mm, value = value[0:1], value[1:3], value[4:6], value[6:] + sign, hour, min, value = value[0:1], value[1:3], value[4:6], value[6:] } else if std == stdNumShortTZ { if len(value) < 3 { err = errBad break } - sign, hh, mm, value = value[0:1], value[1:3], "00", value[3:] + sign, hour, min, value = value[0:1], value[1:3], "00", value[3:] } else { if len(value) < 5 { err = errBad break } - sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:] + sign, hour, min, value = value[0:1], value[1:3], value[3:5], value[5:] } - var hr, min int - hr, err = strconv.Atoi(hh) + var hr, mm int + hr, err = atoi(hour) if err == nil { - min, err = strconv.Atoi(mm) + mm, err = atoi(min) } - t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds + zoneOffset = (hr*60 + mm) * 60 // offset is in seconds switch sign[0] { case '+': case '-': - t.ZoneOffset = -t.ZoneOffset - default: - err = errBad - } - case stdPM: - if len(value) < 2 { - err = errBad - break - } - p, value = value[0:2], value[2:] - switch p { - case "PM": - pmSet = true - case "AM": - amSet = true - default: - err = errBad - } - case stdpm: - if len(value) < 2 { - err = errBad - break - } - p, value = value[0:2], value[2:] - switch p { - case "pm": - pmSet = true - case "am": - amSet = true + zoneOffset = -zoneOffset default: err = errBad } case stdTZ: // Does it look like a time zone? if len(value) >= 3 && value[0:3] == "UTC" { - t.Zone, value = value[0:3], value[3:] + z = UTC + value = value[3:] break } @@ -700,47 +803,86 @@ func Parse(alayout, avalue string) (*Time, error) { break } // It's a valid format. - t.Zone = p - // Can we find its offset? - if offset, found := lookupByName(p); found { - t.ZoneOffset = offset - } + zoneName = p default: if len(value) < len(std) { err = errBad break } if len(std) >= 2 && std[0:2] == ".0" { - rangeErrString, err = t.parseNanoseconds(value, len(std)) + nsec, rangeErrString, err = parseNanoseconds(value, len(std)) value = value[len(std):] } } if rangeErrString != "" { - return nil, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"} + return Time{}, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"} } if err != nil { - return nil, &ParseError{alayout, avalue, std, value, ""} + return Time{}, &ParseError{alayout, avalue, std, value, ""} + } + } + if pmSet && hour < 12 { + hour += 12 + } else if amSet && hour == 12 { + hour = 0 + } + + // TODO: be more aggressive checking day? + if z != nil { + return Date(year, Month(month), day, hour, min, sec, nsec, z), nil + } + + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + if zoneOffset != -1 { + t.sec -= int64(zoneOffset) + + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + name, offset, _, _, _ := Local.lookup(t.sec + internalToUnix) + if offset == zoneOffset && (zoneName == "" || name == zoneName) { + t.loc = Local + return t, nil } + + // Otherwise create fake zone to record offset. + t.loc = FixedZone(zoneName, zoneOffset) + return t, nil } - if pmSet && t.Hour < 12 { - t.Hour += 12 - } else if amSet && t.Hour == 12 { - t.Hour = 0 + + if zoneName != "" { + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + offset, _, ok := Local.lookupName(zoneName) + if ok { + name, off, _, _, _ := Local.lookup(t.sec + internalToUnix - int64(offset)) + if name == zoneName && off == offset { + t.sec -= int64(offset) + t.loc = Local + return t, nil + } + } + + // Otherwise, create fake zone with unknown offset. + t.loc = FixedZone(zoneName, 0) + return t, nil } - return &t, nil + + // Otherwise, fall back to UTC. + return t, nil } -func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, err error) { +func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { if value[0] != '.' { - return "", errBad + err = errBad + return } - var ns int - ns, err = strconv.Atoi(value[1:nbytes]) + ns, err = atoi(value[1:nbytes]) if err != nil { - return "", err + return } if ns < 0 || 1e9 <= ns { - return "fractional second", nil + rangeErrString = "fractional second" + return } // We need nanoseconds, which means scaling by the number // of missing digits in the format, maximum length 10. If it's @@ -749,6 +891,5 @@ func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, for i := 0; i < scaleDigits; i++ { ns *= 10 } - t.Nanosecond = ns return } diff --git a/libgo/go/time/internal_test.go b/libgo/go/time/internal_test.go index d7e7076..2c4df33 100644 --- a/libgo/go/time/internal_test.go +++ b/libgo/go/time/internal_test.go @@ -6,7 +6,7 @@ package time func init() { // force US/Pacific for time zone tests - onceSetupZone.Do(setupTestingZone) + localOnce.Do(initTestingZone) } var Interrupt = interrupt diff --git a/libgo/go/time/sleep.go b/libgo/go/time/sleep.go index 967fca0..1e23118 100644 --- a/libgo/go/time/sleep.go +++ b/libgo/go/time/sleep.go @@ -4,6 +4,11 @@ package time +func nano() int64 { + sec, nsec := now() + return sec*1e9 + int64(nsec) +} + // Interface to timers implemented in package runtime. // Must be in sync with ../runtime/runtime.h:/^struct.Timer$ type runtimeTimer struct { @@ -21,7 +26,7 @@ func stopTimer(*runtimeTimer) bool // When the Timer expires, the current time will be sent on C, // unless the Timer was created by AfterFunc. type Timer struct { - C <-chan int64 + C <-chan Time r runtimeTimer } @@ -34,12 +39,12 @@ func (t *Timer) Stop() (ok bool) { // NewTimer creates a new Timer that will send // the current time on its channel after at least ns nanoseconds. -func NewTimer(ns int64) *Timer { - c := make(chan int64, 1) +func NewTimer(d Duration) *Timer { + c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ - when: Nanoseconds() + ns, + when: nano() + int64(d), f: sendTime, arg: c, }, @@ -55,16 +60,16 @@ func sendTime(now int64, c interface{}) { // the desired behavior when the reader gets behind, // because the sends are periodic. select { - case c.(chan int64) <- now: + case c.(chan Time) <- Unix(0, now): default: } } -// After waits at least ns nanoseconds before sending the current time +// After waits for the duration to elapse and then sends the current time // on the returned channel. // It is equivalent to NewTimer(ns).C. -func After(ns int64) <-chan int64 { - return NewTimer(ns).C +func After(d Duration) <-chan Time { + return NewTimer(d).C } // AfterFunc waits at least ns nanoseconds before calling f @@ -73,7 +78,7 @@ func After(ns int64) <-chan int64 { func AfterFunc(ns int64, f func()) *Timer { t := &Timer{ r: runtimeTimer{ - when: Nanoseconds() + ns, + when: nano() + ns, f: goFunc, arg: f, }, diff --git a/libgo/go/time/sleep_test.go b/libgo/go/time/sleep_test.go index 9171da3..cbcc897 100644 --- a/libgo/go/time/sleep_test.go +++ b/libgo/go/time/sleep_test.go @@ -15,16 +15,16 @@ import ( ) func TestSleep(t *testing.T) { - const delay = int64(100e6) + const delay = 100 * Millisecond go func() { Sleep(delay / 2) Interrupt() }() - start := Nanoseconds() + start := Now() Sleep(delay) - duration := Nanoseconds() - start + duration := Now().Sub(start) if duration < delay { - t.Fatalf("Sleep(%d) slept for only %d ns", delay, duration) + t.Fatalf("Sleep(%s) slept for only %s", delay, duration) } } @@ -96,32 +96,32 @@ func BenchmarkStop(b *testing.B) { } func TestAfter(t *testing.T) { - const delay = int64(100e6) - start := Nanoseconds() + const delay = 100 * Millisecond + start := Now() end := <-After(delay) - if duration := Nanoseconds() - start; duration < delay { - t.Fatalf("After(%d) slept for only %d ns", delay, duration) + if duration := Now().Sub(start); duration < delay { + t.Fatalf("After(%s) slept for only %d ns", delay, duration) } - if min := start + delay; end < min { - t.Fatalf("After(%d) expect >= %d, got %d", delay, min, end) + if min := start.Add(delay); end.Before(min) { + t.Fatalf("After(%s) expect >= %s, got %s", delay, min, end) } } func TestAfterTick(t *testing.T) { const ( - Delta = 100 * 1e6 + Delta = 100 * Millisecond Count = 10 ) - t0 := Nanoseconds() + t0 := Now() for i := 0; i < Count; i++ { <-After(Delta) } - t1 := Nanoseconds() - ns := t1 - t0 - target := int64(Delta * Count) + t1 := Now() + d := t1.Sub(t0) + target := Delta * Count slop := target * 2 / 10 - if ns < target-slop || ns > target+slop { - t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target)) + if d < target-slop || d > target+slop { + t.Fatalf("%d ticks of %s took %s, expected %s", Count, Delta, d, target) } } @@ -171,38 +171,54 @@ var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8 /*0*/ } type afterResult struct { slot int - t int64 + t Time } -func await(slot int, result chan<- afterResult, ac <-chan int64) { +func await(slot int, result chan<- afterResult, ac <-chan Time) { result <- afterResult{slot, <-ac} } func testAfterQueuing(t *testing.T) error { const ( - Delta = 100 * 1e6 + Delta = 100 * Millisecond ) // make the result channel buffered because we don't want // to depend on channel queueing semantics that might // possibly change in the future. result := make(chan afterResult, len(slots)) - t0 := Nanoseconds() + t0 := Now() for _, slot := range slots { - go await(slot, result, After(int64(slot)*Delta)) + go await(slot, result, After(Duration(slot)*Delta)) } sort.Ints(slots) for _, slot := range slots { r := <-result if r.slot != slot { - return fmt.Errorf("after queue got slot %d, expected %d", r.slot, slot) + return fmt.Errorf("after slot %d, expected %d", r.slot, slot) } - ns := r.t - t0 - target := int64(slot * Delta) - slop := int64(Delta) / 4 - if ns < target-slop || ns > target+slop { - return fmt.Errorf("after queue slot %d arrived at %g, expected [%g,%g]", slot, float64(ns), float64(target-slop), float64(target+slop)) + dt := r.t.Sub(t0) + target := Duration(slot) * Delta + slop := Delta / 4 + if dt < target-slop || dt > target+slop { + return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-slop, target+slop) } } return nil } + +func TestTimerStopStress(t *testing.T) { + if testing.Short() { + return + } + for i := 0; i < 100; i++ { + go func(i int) { + timer := AfterFunc(2e9, func() { + t.Fatalf("timer %d was not stopped", i) + }) + Sleep(1e9) + timer.Stop() + }(i) + } + Sleep(3e9) +} diff --git a/libgo/go/time/sys.go b/libgo/go/time/sys.go index a5e529b..fe6bc27d 100644 --- a/libgo/go/time/sys.go +++ b/libgo/go/time/sys.go @@ -4,17 +4,33 @@ package time -// Seconds reports the number of seconds since the Unix epoch, -// January 1, 1970 00:00:00 UTC. -func Seconds() int64 { - return Nanoseconds() / 1e9 -} - -// Nanoseconds is implemented by package runtime. +import "syscall" -// Nanoseconds reports the number of nanoseconds since the Unix epoch, -// January 1, 1970 00:00:00 UTC. -func Nanoseconds() int64 +// Sleep pauses the current goroutine for the duration d. +func Sleep(d Duration) -// Sleep pauses the current goroutine for at least ns nanoseconds. -func Sleep(ns int64) +// readFile reads and returns the content of the named file. +// It is a trivial implementation of ioutil.ReadFile, reimplemented +// here to avoid depending on io/ioutil or os. +func readFile(name string) ([]byte, error) { + f, err := syscall.Open(name, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer syscall.Close(f) + var ( + buf [4096]byte + ret []byte + n int + ) + for { + n, err = syscall.Read(f, buf[:]) + if n > 0 { + ret = append(ret, buf[:n]...) + } + if n == 0 || err != nil { + break + } + } + return ret, err +} diff --git a/libgo/go/time/sys_unix.go b/libgo/go/time/sys_unix.go index 3d31322..715d186 100644 --- a/libgo/go/time/sys_unix.go +++ b/libgo/go/time/sys_unix.go @@ -6,12 +6,9 @@ package time -import ( - "os" - "syscall" -) +import "syscall" // for testing: whatever interrupts a sleep func interrupt() { - syscall.Kill(os.Getpid(), syscall.SIGCHLD) + syscall.Kill(syscall.Getpid(), syscall.SIGCHLD) } diff --git a/libgo/go/time/tick.go b/libgo/go/time/tick.go index 95941a1..4440c22 100644 --- a/libgo/go/time/tick.go +++ b/libgo/go/time/tick.go @@ -9,27 +9,27 @@ import "errors" // A Ticker holds a synchronous channel that delivers `ticks' of a clock // at intervals. type Ticker struct { - C <-chan int64 // The channel on which the ticks are delivered. + C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer } -// NewTicker returns a new Ticker containing a channel that will -// send the time, in nanoseconds, every ns nanoseconds. It adjusts the -// intervals to make up for pauses in delivery of the ticks. The value of -// ns must be greater than zero; if not, NewTicker will panic. -func NewTicker(ns int64) *Ticker { - if ns <= 0 { +// NewTicker returns a new Ticker containing a channel that will send the +// time, in nanoseconds, with a period specified by the duration argument. +// It adjusts the intervals or drops ticks to make up for slow receivers. +// The duration d must be greater than zero; if not, NewTicker will panic. +func NewTicker(d Duration) *Ticker { + if d <= 0 { panic(errors.New("non-positive interval for NewTicker")) } // Give the channel a 1-element time buffer. // If the client falls behind while reading, we drop ticks // on the floor until the client catches up. - c := make(chan int64, 1) + c := make(chan Time, 1) t := &Ticker{ C: c, r: runtimeTimer{ - when: Nanoseconds() + ns, - period: ns, + when: nano() + int64(d), + period: int64(d), f: sendTime, arg: c, }, @@ -45,9 +45,9 @@ func (t *Ticker) Stop() { // Tick is a convenience wrapper for NewTicker providing access to the ticking // channel only. Useful for clients that have no need to shut down the ticker. -func Tick(ns int64) <-chan int64 { - if ns <= 0 { +func Tick(d Duration) <-chan Time { + if d <= 0 { return nil } - return NewTicker(ns).C + return NewTicker(d).C } diff --git a/libgo/go/time/tick_test.go b/libgo/go/time/tick_test.go index 4dcb639..3634934 100644 --- a/libgo/go/time/tick_test.go +++ b/libgo/go/time/tick_test.go @@ -11,21 +11,21 @@ import ( func TestTicker(t *testing.T) { const ( - Delta = 100 * 1e6 + Delta = 100 * Millisecond Count = 10 ) ticker := NewTicker(Delta) - t0 := Nanoseconds() + t0 := Now() for i := 0; i < Count; i++ { <-ticker.C } ticker.Stop() - t1 := Nanoseconds() - ns := t1 - t0 - target := int64(Delta * Count) + t1 := Now() + dt := t1.Sub(t0) + target := Delta * Count slop := target * 2 / 10 - if ns < target-slop || ns > target+slop { - t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target)) + if dt < target-slop || dt > target+slop { + t.Fatalf("%d %s ticks took %s, expected [%s,%s]", Count, Delta, dt, target-slop, target+slop) } // Now test that the ticker stopped Sleep(2 * Delta) diff --git a/libgo/go/time/time.go b/libgo/go/time/time.go index e11d177..04ed86c 100644 --- a/libgo/go/time/time.go +++ b/libgo/go/time/time.go @@ -3,11 +3,109 @@ // license that can be found in the LICENSE file. // Package time provides functionality for measuring and displaying time. +// +// The calendrical calculations always assume a Gregorian calendar. package time -// Days of the week. +// A Time represents an instant in time with nanosecond precision. +// +// Programs using times should typically store and pass them as values, +// not pointers. That is, time variables and struct fields should be of +// type time.Time, not *time.Time. +// +// Time instants can be compared using the Before, After, and Equal methods. +// The Sub method subtracts two instants, producing a Duration. +// The Add method adds a Time and a Duration, producing a Time. +// +// The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. +// As this time is unlikely to come up in practice, the IsZero method gives +// a simple way of detecting a time that has not been initialized explicitly. +// +// Each Time has associated with it a Location, consulted when computing the +// presentation form of the time, such as in the Format, Hour, and Year methods. +// The methods Local, UTC, and In return a Time with a specific location. +// Changing the location in this way changes only the presentation; it does not +// change the instant in time being denoted and therefore does not affect the +// computations described in earlier paragraphs. +// +type Time struct { + // sec gives the number of seconds elapsed since + // January 1, year 1 00:00:00 UTC. + sec int64 + + // nsec specifies a non-negative nanosecond + // offset within the second named by Seconds. + // It must be in the range [0, 999999999]. + nsec int32 + + // loc specifies the Location that should be used to + // determine the minute, hour, month, day, and year + // that correspond to this Time. + // Only the zero Time has a nil Location. + // In that case it is interpreted to mean UTC. + loc *Location +} + +// After reports whether the time instant t is after u. +func (t Time) After(u Time) bool { + return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec +} + +// Before reports whether the time instant t is before u. +func (t Time) Before(u Time) bool { + return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec +} + +// Equal reports whether t and u represent the same time instant. +// Two times can be equal even if they are in different locations. +// For example, 6:00 +0200 CEST and 4:00 UTC are Equal. +// This comparison is different from using t == u, which also compares +// the locations. +func (t Time) Equal(u Time) bool { + return t.sec == u.sec && t.nsec == u.nsec +} + +// A Month specifies a month of the year (January = 1, ...). +type Month int + const ( - Sunday = iota + January Month = 1 + iota + February + March + April + May + June + July + August + September + October + November + December +) + +var months = [...]string{ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +} + +// String returns the English name of the month ("January", "February", ...). +func (m Month) String() string { return months[m-1] } + +// A Weekday specifies a day of the week (Sunday = 0, ...). +type Weekday int + +const ( + Sunday Weekday = iota Monday Tuesday Wednesday @@ -16,284 +114,749 @@ const ( Saturday ) -// Time is the struct representing a parsed time value. -type Time struct { - Year int64 // 2006 is 2006 - Month, Day int // Jan-2 is 1, 2 - Hour, Minute, Second int // 15:04:05 is 15, 4, 5. - Nanosecond int // Fractional second. - ZoneOffset int // seconds east of UTC, e.g. -7*60*60 for -0700 - Zone string // e.g., "MST" +var days = [...]string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", } -var nonleapyear = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} -var leapyear = []int{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +// String returns the English name of the day ("Sunday", "Monday", ...). +func (d Weekday) String() string { return days[d] } + +// Computations on time. +// +// The zero value for a Time is defined to be +// January 1, year 1, 00:00:00.000000000 UTC +// which (1) looks like a zero, or as close as you can get in a date +// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to +// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a +// non-negative year even in time zones west of UTC, unlike 1-1-0 +// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York. +// +// The zero Time value does not force a specific epoch for the time +// representation. For example, to use the Unix epoch internally, we +// could define that to distinguish a zero value from Jan 1 1970, that +// time would be represented by sec=-1, nsec=1e9. However, it does +// suggest a representation, namely using 1-1-1 00:00:00 UTC as the +// epoch, and that's what we do. +// +// The Add and Sub computations are oblivious to the choice of epoch. +// +// The presentation computations - year, month, minute, and so on - all +// rely heavily on division and modulus by positive constants. For +// calendrical calculations we want these divisions to round down, even +// for negative values, so that the remainder is always positive, but +// Go's division (like most hardware divison instructions) rounds to +// zero. We can still do those computations and then adjust the result +// for a negative numerator, but it's annoying to write the adjustment +// over and over. Instead, we can change to a different epoch so long +// ago that all the times we care about will be positive, and then round +// to zero and round down coincide. These presentation routines already +// have to add the zone offset, so adding the translation to the +// alternate epoch is cheap. For example, having a non-negative time t +// means that we can write +// +// sec = t % 60 +// +// instead of +// +// sec = t % 60 +// if sec < 0 { +// sec += 60 +// } +// +// everywhere. +// +// The calendar runs on an exact 400 year cycle: a 400-year calendar +// printed for 1970-2469 will apply as well to 2470-2869. Even the days +// of the week match up. It simplifies the computations to choose the +// cycle boundaries so that the exceptional years are always delayed as +// long as possible. That means choosing a year equal to 1 mod 400, so +// that the first leap year is the 4th year, the first missed leap year +// is the 100th year, and the missed missed leap year is the 400th year. +// So we'd prefer instead to print a calendar for 2001-2400 and reuse it +// for 2401-2800. +// +// Finally, it's convenient if the delta between the Unix epoch and +// long-ago epoch is representable by an int64 constant. +// +// These three considerations—choose an epoch as early as possible, that +// uses a year equal to 1 mod 400, and that is no more than 2⁶³ seconds +// earlier than 1970—bring us to the year -292277022399. We refer to +// this year as the absolute zero year, and to times measured as a uint64 +// seconds since this year as absolute times. +// +// Times measured as an int64 seconds since the year 1—the representation +// used for Time's sec field—are called internal times. +// +// Times measured as an int64 seconds since the year 1970 are called Unix +// times. +// +// It is tempting to just use the year 1 as the absolute epoch, defining +// that the routines are only valid for years >= 1. However, the +// routines would then be invalid when displaying the epoch in time zones +// west of UTC, since it is year 0. It doesn't seem tenable to say that +// printing the zero time correctly isn't supported in half the time +// zones. By comparison, it's reasonable to mishandle some times in +// the year -292277022399. +// +// All this is opaque to clients of the API and can be changed if a +// better implementation presents itself. -func months(year int64) []int { - if year%4 == 0 && (year%100 != 0 || year%400 == 0) { - return leapyear +const ( + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 + + // The year of the zero Time. + // Assumed by the unixToInternal computation below. + internalYear = 1 + + // The year of the zero Unix time. + unixYear = 1970 + + // Offsets to convert between internal and absolute or Unix times. + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + internalToAbsolute = -absoluteToInternal + + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal +) + +// IsZero reports whether t represents the zero time instant, +// January 1, year 1, 00:00:00 UTC. +func (t Time) IsZero() bool { + return t.sec == 0 && t.nsec == 0 +} + +// abs returns the time t as an absolute time, adjusted by the zone offset. +// It is called when computing a presentation property like Month or Hour. +func (t Time) abs() uint64 { + l := t.loc + if l == nil { + l = &utcLoc } - return nonleapyear + // Avoid function call if we hit the local time cache. + sec := t.sec + internalToUnix + if l != &utcLoc { + if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + sec += int64(l.cacheZone.offset) + } else { + _, offset, _, _, _ := l.lookup(sec) + sec += int64(offset) + } + } + return uint64(sec + (unixToInternal + internalToAbsolute)) } -const ( - secondsPerDay = 24 * 60 * 60 - daysPer400Years = 365*400 + 97 - daysPer100Years = 365*100 + 24 - daysPer4Years = 365*4 + 1 - days1970To2001 = 31*365 + 8 -) +// Date returns the year, month, and day in which t occurs. +func (t Time) Date() (year int, month Month, day int) { + year, month, day, _ = t.date(true) + return +} -// SecondsToUTC converts sec, in number of seconds since the Unix epoch, -// into a parsed Time value in the UTC time zone. -func SecondsToUTC(sec int64) *Time { - t := new(Time) +// Year returns the year in which t occurs. +func (t Time) Year() int { + year, _, _, _ := t.date(false) + return year +} - // Split into time and day. - day := sec / secondsPerDay - sec -= day * secondsPerDay - if sec < 0 { - day-- - sec += secondsPerDay +// Month returns the month of the year specified by t. +func (t Time) Month() Month { + _, month, _, _ := t.date(true) + return month +} + +// Day returns the day of the month specified by t. +func (t Time) Day() int { + _, _, day, _ := t.date(true) + return day +} + +// Weekday returns the day of the week specified by t. +func (t Time) Weekday() Weekday { + // January 1 of the absolute year, like January 1 of 2001, was a Monday. + sec := (t.abs() + uint64(Monday)*secondsPerDay) % secondsPerWeek + return Weekday(int(sec) / secondsPerDay) +} + +// ISOWeek returns the ISO 8601 year and week number in which t occurs. +// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to +// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 +// of year n+1. +func (t Time) ISOWeek() (year, week int) { + year, month, day, yday := t.date(true) + wday := int(t.Weekday()+6) % 7 // weekday but Monday = 0. + const ( + Mon int = iota + Tue + Wed + Thu + Fri + Sat + Sun + ) + + // Calculate week as number of Mondays in year up to + // and including today, plus 1 because the first week is week 0. + // Putting the + 1 inside the numerator as a + 7 keeps the + // numerator from being negative, which would cause it to + // round incorrectly. + week = (yday - wday + 7) / 7 + + // The week number is now correct under the assumption + // that the first Monday of the year is in week 1. + // If Jan 1 is a Tuesday, Wednesday, or Thursday, the first Monday + // is actually in week 2. + jan1wday := (wday - yday + 7*53) % 7 + if Tue <= jan1wday && jan1wday <= Thu { + week++ } - // Time - t.Hour = int(sec / 3600) - t.Minute = int((sec / 60) % 60) - t.Second = int(sec % 60) - - // Change day from 0 = 1970 to 0 = 2001, - // to make leap year calculations easier - // (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.) - day -= days1970To2001 - - year := int64(2001) - if day < 0 { - // Go back enough 400 year cycles to make day positive. - n := -day/daysPer400Years + 1 - year -= 400 * n - day += daysPer400Years * n + // If the week number is still 0, we're in early January but in + // the last week of last year. + if week == 0 { + year-- + week = 52 + // A year has 53 weeks when Jan 1 or Dec 31 is a Thursday, + // meaning Jan 1 of the next year is a Friday + // or it was a leap year and Jan 1 of the next year is a Saturday. + if jan1wday == Fri || (jan1wday == Sat && isLeap(year)) { + week++ + } } - // Cut off 400 year cycles. - n := day / daysPer400Years - year += 400 * n - day -= daysPer400Years * n + // December 29 to 31 are in week 1 of next year if + // they are after the last Thursday of the year and + // December 31 is a Monday, Tuesday, or Wednesday. + if month == December && day >= 29 && wday < Thu { + if dec31wday := (wday + 31 - day) % 7; Mon <= dec31wday && dec31wday <= Wed { + year++ + week = 1 + } + } + + return +} + +// Clock returns the hour, minute, and second within the day specified by t. +func (t Time) Clock() (hour, min, sec int) { + sec = int(t.abs() % secondsPerDay) + hour = sec / secondsPerHour + sec -= hour * secondsPerHour + min = sec / secondsPerMinute + sec -= min * secondsPerMinute + return +} + +// Hour returns the hour within the day specified by t, in the range [0, 23]. +func (t Time) Hour() int { + return int(t.abs()%secondsPerDay) / secondsPerHour +} + +// Minute returns the minute offset within the hour specified by t, in the range [0, 59]. +func (t Time) Minute() int { + return int(t.abs()%secondsPerHour) / secondsPerMinute +} + +// Second returns the second offset within the minute specified by t, in the range [0, 59]. +func (t Time) Second() int { + return int(t.abs() % secondsPerMinute) +} + +// Nanosecond returns the nanosecond offset within the second specified by t, +// in the range [0, 999999999]. +func (t Time) Nanosecond() int { + return int(t.nsec) +} + +// A Duration represents the elapsed time between two instants +// as an int64 nanosecond count. The representation limits the +// largest representable duration to approximately 290 years. +type Duration int64 - // Cut off 100-year cycles - n = day / daysPer100Years - if n > 3 { // happens on last day of 400th year - n = 3 +// Common durations. There is no definition for units of Day or larger +// to avoid confusion across daylight savings time zone transitions. +const ( + Nanosecond Duration = 1 + Microsecond = 1000 * Nanosecond + Millisecond = 1000 * Microsecond + Second = 1000 * Millisecond + Minute = 60 * Second + Hour = 60 * Minute +) + +// Duration returns a string representing the duration in the form "72h3m0.5s". +// Leading zero units are omitted. As a special case, durations less than one +// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure +// that the leading digit is non-zero. The zero duration formats as 0, +// with no unit. +func (d Duration) String() string { + // Largest time is 2540400h10m10.000000000s + var buf [32]byte + w := len(buf) + + u := uint64(d) + neg := d < 0 + if neg { + u = -u } - year += 100 * n - day -= daysPer100Years * n - // Cut off 4-year cycles - n = day / daysPer4Years - if n > 24 { // happens on last day of 100th year - n = 24 + if u < uint64(Second) { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + var ( + prec int + unit byte + ) + switch { + case u == 0: + return "0" + case u < uint64(Microsecond): + // print nanoseconds + prec = 0 + unit = 'n' + case u < uint64(Millisecond): + // print microseconds + prec = 3 + unit = 'u' + default: + // print milliseconds + prec = 6 + unit = 'm' + } + w -= 2 + buf[w] = unit + buf[w+1] = 's' + w, u = fmtFrac(buf[:w], u, prec) + w = fmtInt(buf[:w], u) + } else { + w-- + buf[w] = 's' + + w, u = fmtFrac(buf[:w], u, 9) + + // u is now integer seconds + w = fmtInt(buf[:w], u%60) + u /= 60 + + // u is now integer minutes + if u > 0 { + w-- + buf[w] = 'm' + w = fmtInt(buf[:w], u%60) + u /= 60 + + // u is now integer hours + // Stop at hours because days can be different lengths. + if u > 0 { + w-- + buf[w] = 'h' + w = fmtInt(buf[:w], u) + } + } } - year += 4 * n - day -= daysPer4Years * n - // Cut off non-leap years. - n = day / 365 - if n > 3 { // happens on last day of 4th year - n = 3 + if neg { + w-- + buf[w] = '-' } - year += n - day -= 365 * n - t.Year = year + return string(buf[w:]) +} - // If someone ever needs yearday, - // tyearday = day (+1?) +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. it omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { + // Omit trailing zeros up to and including decimal point. + w := len(buf) + print := false + for i := 0; i < prec; i++ { + digit := v % 10 + print = print || digit != 0 + if print { + w-- + buf[w] = byte(digit) + '0' + } + v /= 10 + } + if print { + w-- + buf[w] = '.' + } + return w, v +} - months := months(year) - var m int - yday := int(day) - for m = 0; m < 12 && yday >= months[m]; m++ { - yday -= months[m] +// fmtInt formats v into the tail of buf. +// It returns the index where the output begins. +func fmtInt(buf []byte, v uint64) int { + w := len(buf) + if v == 0 { + w-- + buf[w] = '0' + } else { + for v > 0 { + w-- + buf[w] = byte(v%10) + '0' + v /= 10 + } } - t.Month = m + 1 - t.Day = yday + 1 - t.Zone = "UTC" + return w +} - return t +// Nanoseconds returns the duration as an integer nanosecond count. +func (d Duration) Nanoseconds() int64 { return int64(d) } + +// These methods return float64 because the dominant +// use case is for printing a floating point number like 1.5s, and +// a truncation to integer would make them not useful in those cases. +// Splitting the integer and fraction ourselves guarantees that +// converting the returned float64 to an integer rounds the same +// way that a pure integer conversion would have, even in cases +// where, say, float64(d.Nanoseconds())/1e9 would have rounded +// differently. + +// Seconds returns the duration as a floating point number of seconds. +func (d Duration) Seconds() float64 { + sec := d / Second + nsec := d % Second + return float64(sec) + float64(nsec)*1e-9 } -// NanosecondsToUTC converts nsec, in number of nanoseconds since the Unix epoch, -// into a parsed Time value in the UTC time zone. -func NanosecondsToUTC(nsec int64) *Time { - // This one calls SecondsToUTC rather than the other way around because - // that admits a much larger span of time; NanosecondsToUTC is limited - // to a few hundred years only. - t := SecondsToUTC(nsec / 1e9) - t.Nanosecond = int(nsec % 1e9) - return t +// Minutes returns the duration as a floating point number of minutes. +func (d Duration) Minutes() float64 { + min := d / Minute + nsec := d % Minute + return float64(min) + float64(nsec)*(1e-9/60) } -// UTC returns the current time as a parsed Time value in the UTC time zone. -func UTC() *Time { return NanosecondsToUTC(Nanoseconds()) } +// Hours returns the duration as a floating point number of hours. +func (d Duration) Hours() float64 { + hour := d / Hour + nsec := d % Hour + return float64(hour) + float64(nsec)*(1e-9/60/60) +} -// SecondsToLocalTime converts sec, in number of seconds since the Unix epoch, -// into a parsed Time value in the local time zone. -func SecondsToLocalTime(sec int64) *Time { - z, offset := lookupTimezone(sec) - t := SecondsToUTC(sec + int64(offset)) - t.Zone = z - t.ZoneOffset = offset +// Add returns the time t+d. +func (t Time) Add(d Duration) Time { + t.sec += int64(d / 1e9) + t.nsec += int32(d % 1e9) + if t.nsec > 1e9 { + t.sec++ + t.nsec -= 1e9 + } else if t.nsec < 0 { + t.sec-- + t.nsec += 1e9 + } return t } -// NanosecondsToLocalTime converts nsec, in number of nanoseconds since the Unix epoch, -// into a parsed Time value in the local time zone. -func NanosecondsToLocalTime(nsec int64) *Time { - t := SecondsToLocalTime(nsec / 1e9) - t.Nanosecond = int(nsec % 1e9) - return t +// Sub returns the duration t-u. +// To compute t-d for a duration d, use t.Add(-d). +func (t Time) Sub(u Time) Duration { + return Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec) } -// LocalTime returns the current time as a parsed Time value in the local time zone. -func LocalTime() *Time { return NanosecondsToLocalTime(Nanoseconds()) } - -// Seconds returns the number of seconds since January 1, 1970 represented by the -// parsed Time value. -func (t *Time) Seconds() int64 { - // First, accumulate days since January 1, 2001. - // Using 2001 instead of 1970 makes the leap-year - // handling easier (see SecondsToUTC), because - // it is at the beginning of the 4-, 100-, and 400-year cycles. - day := int64(0) - - // Rewrite year to be >= 2001. - year := t.Year - if year < 2001 { - n := (2001-year)/400 + 1 - year += 400 * n - day -= daysPer400Years * n +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * 60 + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 + days1970To2001 = 31*365 + 8 +) + +// date computes the year and, only when full=true, +// the month and day in which t occurs. +func (t Time) date(full bool) (year int, month Month, day int, yday int) { + // Split into time and day. + d := t.abs() / secondsPerDay + + // Account for 400 year cycles. + n := d / daysPer400Years + y := 400 * n + d -= daysPer400Years * n + + // Cut off 100-year cycles. + // The last cycle has one extra leap year, so on the last day + // of that year, day / daysPer100Years will be 4 instead of 3. + // Cut it back down to 3 by subtracting n>>2. + n = d / daysPer100Years + n -= n >> 2 + y += 100 * n + d -= daysPer100Years * n + + // Cut off 4-year cycles. + // The last cycle has a missing leap year, which does not + // affect the computation. + n = d / daysPer4Years + y += 4 * n + d -= daysPer4Years * n + + // Cut off years within a 4-year cycle. + // The last year is a leap year, so on the last day of that year, + // day / 365 will be 4 instead of 3. Cut it back down to 3 + // by subtracting n>>2. + n = d / 365 + n -= n >> 2 + y += n + d -= 365 * n + + year = int(int64(y) + absoluteZeroYear) + yday = int(d) + + if !full { + return } - // Add in days from 400-year cycles. - n := (year - 2001) / 400 - year -= 400 * n - day += daysPer400Years * n + day = yday + if isLeap(year) { + // Leap year + switch { + case day > 31+29-1: + // After leap day; pretend it wasn't there. + day-- + case day == 31+29-1: + // Leap day. + month = February + day = 29 + return + } + } - // Add in 100-year cycles. - n = (year - 2001) / 100 - year -= 100 * n - day += daysPer100Years * n + // Estimate month on assumption that every month has 31 days. + // The estimate may be too low by at most one month, so adjust. + month = Month(day / 31) + end := int(daysBefore[month+1]) + var begin int + if day >= end { + month++ + begin = end + } else { + begin = int(daysBefore[month]) + } - // Add in 4-year cycles. - n = (year - 2001) / 4 - year -= 4 * n - day += daysPer4Years * n + month++ // because January is 1 + day = day - begin + 1 + return +} - // Add in non-leap years. - n = year - 2001 - day += 365 * n +// daysBefore[m] counts the number of days in a non-leap year +// before month m begins. There is an entry for m=12, counting +// the number of days before January of next year (365). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} - // Add in days this year. - months := months(t.Year) - for m := 0; m < t.Month-1; m++ { - day += int64(months[m]) +func daysIn(m Month, year int) int { + if m == February && isLeap(year) { + return 29 } - day += int64(t.Day - 1) - - // Convert days to seconds since January 1, 2001. - sec := day * secondsPerDay + return int(daysBefore[m+1] - daysBefore[m]) +} - // Add in time elapsed today. - sec += int64(t.Hour) * 3600 - sec += int64(t.Minute) * 60 - sec += int64(t.Second) +// Provided by package runtime. +func now() (sec int64, nsec int32) - // Convert from seconds since 2001 to seconds since 1970. - sec += days1970To2001 * secondsPerDay +// Now returns the current local time. +func Now() Time { + sec, nsec := now() + return Time{sec + unixToInternal, nsec, Local} +} - // Account for local time zone. - sec -= int64(t.ZoneOffset) - return sec +// UTC returns t with the location set to UTC. +func (t Time) UTC() Time { + t.loc = UTC + return t } -// Nanoseconds returns the number of nanoseconds since January 1, 1970 represented by the -// parsed Time value. -func (t *Time) Nanoseconds() int64 { - return t.Seconds()*1e9 + int64(t.Nanosecond) +// Local returns t with the location set to local time. +func (t Time) Local() Time { + t.loc = Local + return t } -// Weekday returns the time's day of the week. Sunday is day 0. -func (t *Time) Weekday() int { - sec := t.Seconds() + int64(t.ZoneOffset) - day := sec / secondsPerDay - sec -= day * secondsPerDay - if sec < 0 { - day-- +// In returns t with the location information set to loc. +// +// In panics if loc is nil. +func (t Time) In(loc *Location) Time { + if loc == nil { + panic("time: missing Location in call to Time.In") } - // Day 0 = January 1, 1970 was a Thursday - weekday := int((day + Thursday) % 7) - if weekday < 0 { - weekday += 7 - } - return weekday + t.loc = loc + return t } -// julianDayNumber returns the time's Julian Day Number -// relative to the epoch 12:00 January 1, 4713 BC, Monday. -func julianDayNumber(year int64, month, day int) int64 { - a := int64(14-month) / 12 - y := year + 4800 - a - m := int64(month) + 12*a - 3 - return int64(day) + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 +// Location returns the time zone information associated with t. +func (t Time) Location() *Location { + l := t.loc + if l == nil { + l = UTC + } + return l } -// startOfFirstWeek returns the julian day number of the first day -// of the first week of the given year. -func startOfFirstWeek(year int64) (d int64) { - jan01 := julianDayNumber(year, 1, 1) - weekday := (jan01 % 7) + 1 - if weekday <= 4 { - d = jan01 - weekday + 1 - } else { - d = jan01 + 8 - weekday - } +// Zone computes the time zone in effect at time t, returning the abbreviated +// name of the zone (such as "CET") and its offset in seconds east of UTC. +func (t Time) Zone() (name string, offset int) { + name, offset, _, _, _ = t.loc.lookup(t.sec + internalToUnix) return } -// dayOfWeek returns the weekday of the given date. -func dayOfWeek(year int64, month, day int) int { - t := Time{Year: year, Month: month, Day: day} - return t.Weekday() +// Unix returns the Unix time, the number of seconds elapsed +// since January 1, 1970 UTC. +func (t Time) Unix() int64 { + return t.sec + internalToUnix } -// ISOWeek returns the time's year and week number according to ISO 8601. -// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to -// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 -// of year n+1. -func (t *Time) ISOWeek() (year int64, week int) { - d := julianDayNumber(t.Year, t.Month, t.Day) - week1Start := startOfFirstWeek(t.Year) - - if d < week1Start { - // Previous year, week 52 or 53 - year = t.Year - 1 - if dayOfWeek(t.Year-1, 1, 1) == 4 || dayOfWeek(t.Year-1, 12, 31) == 4 { - week = 53 - } else { - week = 52 +// UnixNano returns the Unix time, the number of nanoseconds elapsed +// since January 1, 1970 UTC. +func (t Time) UnixNano() int64 { + return (t.sec+internalToUnix)*1e9 + int64(t.nsec) +} + +// Unix returns the local Time corresponding to the given Unix time, +// sec seconds and nsec nanoseconds since January 1, 1970 UTC. +// It is valid to pass nsec outside the range [0, 999999999]. +func Unix(sec int64, nsec int64) Time { + if nsec < 0 || nsec >= 1e9 { + n := nsec / 1e9 + sec += n + nsec -= n * 1e9 + if nsec < 0 { + nsec += 1e9 + sec-- } - return } + return Time{sec + unixToInternal, int32(nsec), Local} +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +// norm returns nhi, nlo such that +// hi * base + lo == nhi * base + nlo +// 0 <= nlo < base +func norm(hi, lo, base int) (nhi, nlo int) { + if lo < 0 { + n := (-lo-1)/base + 1 + hi -= n + lo += n * base + } + if lo >= base { + n := lo / base + hi += n + lo -= n * base + } + return hi, lo +} - if d < startOfFirstWeek(t.Year+1) { - // Current year, week 01..52(,53) - year = t.Year - week = int((d-week1Start)/7 + 1) - return +// Date returns the Time corresponding to +// yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// in the appropriate zone for that time in the given location. +// +// The month, day, hour, min, sec, and nsec values may be outside +// their usual ranges and will be normalized during the conversion. +// For example, October 32 converts to November 1. +// +// A daylight savings time transition skips or repeats times. +// For example, in the United States, March 13, 2011 2:15am never occurred, +// while November 6, 2011 1:15am occurred twice. In such cases, the +// choice of time zone, and therefore the time, is not well-defined. +// Date returns a time that is correct in one of the two zones involved +// in the transition, but it does not guarantee which. +// +// Date panics if loc is nil. +func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { + if loc == nil { + panic("time: missing Location in call to Date") } - // Next year, week 1 - year = t.Year + 1 - week = 1 - return + // Normalize month, overflowing into year. + m := int(month) - 1 + year, m = norm(year, m, 12) + month = Month(m) + 1 + + // Normalize nsec, sec, min, hour, overflowing into day. + sec, nsec = norm(sec, nsec, 1e9) + min, sec = norm(min, sec, 60) + 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 + + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 + } + + // Add in days before today. + d += uint64(day - 1) + + // Add in time elapsed today. + abs := d * secondsPerDay + abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + + unix := int64(abs) + (absoluteToInternal + internalToUnix) + + // Look for zone offset for t, so we can adjust to UTC. + // The lookup function expects UTC, so we pass t in the + // hope that it will not be too close to a zone transition, + // and then adjust if it is. + _, offset, _, start, end := loc.lookup(unix) + if offset != 0 { + switch utc := unix - int64(offset); { + case utc < start: + _, offset, _, _, _ = loc.lookup(start - 1) + case utc >= end: + _, offset, _, _, _ = loc.lookup(end) + } + unix -= int64(offset) + } + + return Time{unix + unixToInternal, int32(nsec), loc} } diff --git a/libgo/go/time/time_test.go b/libgo/go/time/time_test.go index 01b8bea4a..9590e28 100644 --- a/libgo/go/time/time_test.go +++ b/libgo/go/time/time_test.go @@ -16,73 +16,89 @@ import ( // won't be. The purpose of this test is to at least explain why some of // the subsequent tests fail. func TestZoneData(t *testing.T) { - lt := LocalTime() + lt := Now() // PST is 8 hours west, PDT is 7 hours west. We could use the name but it's not unique. - if off := lt.ZoneOffset; off != -8*60*60 && off != -7*60*60 { - t.Errorf("Unable to find US Pacific time zone data for testing; time zone is %q offset %d", lt.Zone, off) + if name, off := lt.Zone(); off != -8*60*60 && off != -7*60*60 { + t.Errorf("Unable to find US Pacific time zone data for testing; time zone is %q offset %d", name, off) t.Error("Likely problem: the time zone files have not been installed.") } } +// parsedTime is the struct representing a parsed time value. +type parsedTime struct { + Year int + Month Month + Day int + Hour, Minute, Second int // 15:04:05 is 15, 4, 5. + Nanosecond int // Fractional second. + Weekday Weekday + ZoneOffset int // seconds east of UTC, e.g. -7*60*60 for -0700 + Zone string // e.g., "MST" +} + type TimeTest struct { seconds int64 - golden Time + golden parsedTime } var utctests = []TimeTest{ - {0, Time{1970, 1, 1, 0, 0, 0, 0, 0, "UTC"}}, - {1221681866, Time{2008, 9, 17, 20, 4, 26, 0, 0, "UTC"}}, - {-1221681866, Time{1931, 4, 16, 3, 55, 34, 0, 0, "UTC"}}, - {-11644473600, Time{1601, 1, 1, 0, 0, 0, 0, 0, "UTC"}}, - {599529660, Time{1988, 12, 31, 0, 1, 0, 0, 0, "UTC"}}, - {978220860, Time{2000, 12, 31, 0, 1, 0, 0, 0, "UTC"}}, - {1e18, Time{31688740476, 10, 23, 1, 46, 40, 0, 0, "UTC"}}, - {-1e18, Time{-31688736537, 3, 10, 22, 13, 20, 0, 0, "UTC"}}, - {0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, 0, 0, "UTC"}}, - {-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, 0, 0, "UTC"}}, + {0, parsedTime{1970, January, 1, 0, 0, 0, 0, Thursday, 0, "UTC"}}, + {1221681866, parsedTime{2008, September, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}}, + {-1221681866, parsedTime{1931, April, 16, 3, 55, 34, 0, Thursday, 0, "UTC"}}, + {-11644473600, parsedTime{1601, January, 1, 0, 0, 0, 0, Monday, 0, "UTC"}}, + {599529660, parsedTime{1988, December, 31, 0, 1, 0, 0, Saturday, 0, "UTC"}}, + {978220860, parsedTime{2000, December, 31, 0, 1, 0, 0, Sunday, 0, "UTC"}}, } var nanoutctests = []TimeTest{ - {0, Time{1970, 1, 1, 0, 0, 0, 1e8, 0, "UTC"}}, - {1221681866, Time{2008, 9, 17, 20, 4, 26, 2e8, 0, "UTC"}}, + {0, parsedTime{1970, January, 1, 0, 0, 0, 1e8, Thursday, 0, "UTC"}}, + {1221681866, parsedTime{2008, September, 17, 20, 4, 26, 2e8, Wednesday, 0, "UTC"}}, } var localtests = []TimeTest{ - {0, Time{1969, 12, 31, 16, 0, 0, 0, -8 * 60 * 60, "PST"}}, - {1221681866, Time{2008, 9, 17, 13, 4, 26, 0, -7 * 60 * 60, "PDT"}}, + {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"}}, } var nanolocaltests = []TimeTest{ - {0, Time{1969, 12, 31, 16, 0, 0, 1e8, -8 * 60 * 60, "PST"}}, - {1221681866, Time{2008, 9, 17, 13, 4, 26, 3e8, -7 * 60 * 60, "PDT"}}, -} - -func same(t, u *Time) bool { - return t.Year == u.Year && - t.Month == u.Month && - t.Day == u.Day && - t.Hour == u.Hour && - t.Minute == u.Minute && - t.Second == u.Second && - t.Nanosecond == u.Nanosecond && - t.Weekday() == u.Weekday() && - t.ZoneOffset == u.ZoneOffset && - t.Zone == u.Zone + {0, parsedTime{1969, December, 31, 16, 0, 0, 1e8, Wednesday, -8 * 60 * 60, "PST"}}, + {1221681866, parsedTime{2008, September, 17, 13, 4, 26, 3e8, Wednesday, -7 * 60 * 60, "PDT"}}, +} + +func same(t Time, u *parsedTime) bool { + // Check aggregates. + year, month, day := t.Date() + hour, min, sec := t.Clock() + name, offset := t.Zone() + if year != u.Year || month != u.Month || day != u.Day || + hour != u.Hour || min != u.Minute || sec != u.Second || + name != u.Zone || offset != u.ZoneOffset { + return false + } + // Check individual entries. + return t.Year() == u.Year && + t.Month() == u.Month && + t.Day() == u.Day && + t.Hour() == u.Hour && + t.Minute() == u.Minute && + t.Second() == u.Second && + t.Nanosecond() == u.Nanosecond && + t.Weekday() == u.Weekday } func TestSecondsToUTC(t *testing.T) { for _, test := range utctests { sec := test.seconds golden := &test.golden - tm := SecondsToUTC(sec) - newsec := tm.Seconds() + tm := Unix(sec, 0).UTC() + newsec := tm.Unix() if newsec != sec { t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec) } if !same(tm, golden) { - t.Errorf("SecondsToUTC(%d):", sec) + t.Errorf("SecondsToUTC(%d): // %#v", sec, tm) t.Errorf(" want=%+v", *golden) - t.Errorf(" have=%+v", *tm) + t.Errorf(" have=%v", tm.Format(RFC3339+" MST")) } } } @@ -91,15 +107,15 @@ func TestNanosecondsToUTC(t *testing.T) { for _, test := range nanoutctests { golden := &test.golden nsec := test.seconds*1e9 + int64(golden.Nanosecond) - tm := NanosecondsToUTC(nsec) - newnsec := tm.Nanoseconds() + tm := Unix(0, nsec).UTC() + newnsec := tm.Unix()*1e9 + int64(tm.Nanosecond()) if newnsec != nsec { t.Errorf("NanosecondsToUTC(%d).Nanoseconds() = %d", nsec, newnsec) } if !same(tm, golden) { t.Errorf("NanosecondsToUTC(%d):", nsec) t.Errorf(" want=%+v", *golden) - t.Errorf(" have=%+v", *tm) + t.Errorf(" have=%+v", tm.Format(RFC3339+" MST")) } } } @@ -108,38 +124,38 @@ func TestSecondsToLocalTime(t *testing.T) { for _, test := range localtests { sec := test.seconds golden := &test.golden - tm := SecondsToLocalTime(sec) - newsec := tm.Seconds() + tm := Unix(sec, 0) + newsec := tm.Unix() if newsec != sec { t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec) } if !same(tm, golden) { t.Errorf("SecondsToLocalTime(%d):", sec) t.Errorf(" want=%+v", *golden) - t.Errorf(" have=%+v", *tm) + t.Errorf(" have=%+v", tm.Format(RFC3339+" MST")) } } } -func TestNanoecondsToLocalTime(t *testing.T) { +func TestNanosecondsToLocalTime(t *testing.T) { for _, test := range nanolocaltests { golden := &test.golden nsec := test.seconds*1e9 + int64(golden.Nanosecond) - tm := NanosecondsToLocalTime(nsec) - newnsec := tm.Nanoseconds() + tm := Unix(0, nsec) + newnsec := tm.Unix()*1e9 + int64(tm.Nanosecond()) if newnsec != nsec { t.Errorf("NanosecondsToLocalTime(%d).Seconds() = %d", nsec, newnsec) } if !same(tm, golden) { t.Errorf("NanosecondsToLocalTime(%d):", nsec) t.Errorf(" want=%+v", *golden) - t.Errorf(" have=%+v", *tm) + t.Errorf(" have=%+v", tm.Format(RFC3339+" MST")) } } } func TestSecondsToUTCAndBack(t *testing.T) { - f := func(sec int64) bool { return SecondsToUTC(sec).Seconds() == sec } + f := func(sec int64) bool { return Unix(sec, 0).UTC().Unix() == sec } f32 := func(sec int32) bool { return f(int64(sec)) } cfg := &quick.Config{MaxCount: 10000} @@ -153,7 +169,11 @@ func TestSecondsToUTCAndBack(t *testing.T) { } func TestNanosecondsToUTCAndBack(t *testing.T) { - f := func(nsec int64) bool { return NanosecondsToUTC(nsec).Nanoseconds() == nsec } + f := func(nsec int64) bool { + t := Unix(0, nsec).UTC() + ns := t.Unix()*1e9 + int64(t.Nanosecond()) + return ns == nsec + } f32 := func(nsec int32) bool { return f(int64(nsec)) } cfg := &quick.Config{MaxCount: 10000} @@ -173,9 +193,9 @@ type TimeFormatTest struct { } var rfc3339Formats = []TimeFormatTest{ - {Time{2008, 9, 17, 20, 4, 26, 0, 0, "UTC"}, "2008-09-17T20:04:26Z"}, - {Time{1994, 9, 17, 20, 4, 26, 0, -18000, "EST"}, "1994-09-17T20:04:26-05:00"}, - {Time{2000, 12, 26, 1, 15, 6, 0, 15600, "OTO"}, "2000-12-26T01:15:06+04:20"}, + {Date(2008, 9, 17, 20, 4, 26, 0, UTC), "2008-09-17T20:04:26Z"}, + {Date(1994, 9, 17, 20, 4, 26, 0, FixedZone("EST", -18000)), "1994-09-17T20:04:26-05:00"}, + {Date(2000, 12, 26, 1, 15, 6, 0, FixedZone("OTO", 15600)), "2000-12-26T01:15:06+04:20"}, } func TestRFC3339Conversion(t *testing.T) { @@ -216,7 +236,7 @@ var formatTests = []FormatTest{ func TestFormat(t *testing.T) { // The numeric time represents Thu Feb 4 21:00:57.012345678 PST 2010 - time := NanosecondsToLocalTime(1233810057012345678) + time := Unix(0, 1233810057012345678) for _, test := range formatTests { result := time.Format(test.format) if result != test.result { @@ -229,10 +249,10 @@ type ParseTest struct { name string format string value string - hasTZ bool // contains a time zone - hasWD bool // contains a weekday - yearSign int64 // sign of year - fracDigits int // number of digits of fractional second + hasTZ bool // contains a time zone + hasWD bool // contains a weekday + yearSign int // sign of year + fracDigits int // number of digits of fractional second } var parseTests = []ParseTest{ @@ -298,47 +318,48 @@ func TestRubyParse(t *testing.T) { } } -func checkTime(time *Time, test *ParseTest, t *testing.T) { +func checkTime(time Time, test *ParseTest, t *testing.T) { // The time should be Thu Feb 4 21:00:57 PST 2010 - if test.yearSign*time.Year != 2010 { - t.Errorf("%s: bad year: %d not %d", test.name, time.Year, 2010) + if test.yearSign*time.Year() != 2010 { + t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010) } - if time.Month != 2 { - t.Errorf("%s: bad month: %d not %d", test.name, time.Month, 2) + if time.Month() != February { + t.Errorf("%s: bad month: %s not %s", test.name, time.Month(), February) } - if time.Day != 4 { - t.Errorf("%s: bad day: %d not %d", test.name, time.Day, 4) + if time.Day() != 4 { + t.Errorf("%s: bad day: %d not %d", test.name, time.Day(), 4) } - if time.Hour != 21 { - t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour, 21) + if time.Hour() != 21 { + t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour(), 21) } - if time.Minute != 0 { - t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute, 0) + if time.Minute() != 0 { + t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute(), 0) } - if time.Second != 57 { - t.Errorf("%s: bad second: %d not %d", test.name, time.Second, 57) + if time.Second() != 57 { + t.Errorf("%s: bad second: %d not %d", test.name, time.Second(), 57) } // Nanoseconds must be checked against the precision of the input. nanosec, err := strconv.Atoui("012345678"[:test.fracDigits] + "000000000"[:9-test.fracDigits]) if err != nil { panic(err) } - if time.Nanosecond != int(nanosec) { - t.Errorf("%s: bad nanosecond: %d not %d", test.name, time.Nanosecond, nanosec) + if time.Nanosecond() != int(nanosec) { + t.Errorf("%s: bad nanosecond: %d not %d", test.name, time.Nanosecond(), nanosec) } - if test.hasTZ && time.ZoneOffset != -28800 { - t.Errorf("%s: bad tz offset: %d not %d", test.name, time.ZoneOffset, -28800) + name, offset := time.Zone() + if test.hasTZ && offset != -28800 { + t.Errorf("%s: bad tz offset: %s %d not %d", test.name, name, offset, -28800) } - if test.hasWD && time.Weekday() != 4 { - t.Errorf("%s: bad weekday: %d not %d", test.name, time.Weekday(), 4) + if test.hasWD && time.Weekday() != Thursday { + t.Errorf("%s: bad weekday: %s not %s", test.name, time.Weekday(), Thursday) } } func TestFormatAndParse(t *testing.T) { const fmt = "Mon MST " + RFC3339 // all fields f := func(sec int64) bool { - t1 := SecondsToLocalTime(sec) - if t1.Year < 1000 || t1.Year > 9999 { + t1 := Unix(sec, 0) + if t1.Year() < 1000 || t1.Year() > 9999 { // not required to work return true } @@ -347,8 +368,8 @@ func TestFormatAndParse(t *testing.T) { t.Errorf("error: %s", err) return false } - if !same(t1, t2) { - t.Errorf("different: %q %q", t1, t2) + if t1.Unix() != t2.Unix() || t1.Nanosecond() != t2.Nanosecond() { + t.Errorf("FormatAndParse %d: %q(%d) %q(%d)", sec, t1, t1.Unix(), t2, t2.Unix()) return false } return true @@ -394,7 +415,7 @@ func TestParseErrors(t *testing.T) { } func TestNoonIs12PM(t *testing.T) { - noon := Time{Hour: 12} + noon := Date(0, January, 1, 12, 0, 0, 0, UTC) const expect = "12:00PM" got := noon.Format("3:04PM") if got != expect { @@ -407,7 +428,7 @@ func TestNoonIs12PM(t *testing.T) { } func TestMidnightIs12AM(t *testing.T) { - midnight := Time{Hour: 0} + midnight := Date(0, January, 1, 0, 0, 0, 0, UTC) expect := "12:00AM" got := midnight.Format("3:04PM") if got != expect { @@ -424,15 +445,15 @@ func Test12PMIsNoon(t *testing.T) { if err != nil { t.Fatal("error parsing date:", err) } - if noon.Hour != 12 { - t.Errorf("got %d; expect 12", noon.Hour) + if noon.Hour() != 12 { + t.Errorf("got %d; expect 12", noon.Hour()) } noon, err = Parse("03:04PM", "12:00PM") if err != nil { t.Fatal("error parsing date:", err) } - if noon.Hour != 12 { - t.Errorf("got %d; expect 12", noon.Hour) + if noon.Hour() != 12 { + t.Errorf("got %d; expect 12", noon.Hour()) } } @@ -441,15 +462,15 @@ func Test12AMIsMidnight(t *testing.T) { if err != nil { t.Fatal("error parsing date:", err) } - if midnight.Hour != 0 { - t.Errorf("got %d; expect 0", midnight.Hour) + if midnight.Hour() != 0 { + t.Errorf("got %d; expect 0", midnight.Hour()) } midnight, err = Parse("03:04PM", "12:00AM") if err != nil { t.Fatal("error parsing date:", err) } - if midnight.Hour != 0 { - t.Errorf("got %d; expect 0", midnight.Hour) + if midnight.Hour() != 0 { + t.Errorf("got %d; expect 0", midnight.Hour()) } } @@ -463,7 +484,7 @@ func TestMissingZone(t *testing.T) { expect := "Thu Feb 2 16:10:03 -0500 2006" // -0500 not EST str := time.Format(UnixDate) // uses MST as its time zone if str != expect { - t.Errorf("expected %q got %q", expect, str) + t.Errorf("got %s; expect %s", str, expect) } } @@ -473,16 +494,17 @@ func TestMinutesInTimeZone(t *testing.T) { t.Fatal("error parsing date:", err) } expected := (1*60 + 23) * 60 - if time.ZoneOffset != expected { - t.Errorf("ZoneOffset incorrect, expected %d got %d", expected, time.ZoneOffset) + _, offset := time.Zone() + if offset != expected { + t.Errorf("ZoneOffset = %d, want %d", offset, expected) } } type ISOWeekTest struct { - year int64 // year - month, day int // month and day - yex int64 // expected year - wex int // expected week + year int // year + month, day int // month and day + yex int // expected year + wex int // expected week } var isoWeekTests = []ISOWeekTest{ @@ -524,7 +546,7 @@ var isoWeekTests = []ISOWeekTest{ func TestISOWeek(t *testing.T) { // Selected dates and corner cases for _, wt := range isoWeekTests { - dt := &Time{Year: wt.year, Month: wt.month, Day: wt.day} + dt := Date(wt.year, Month(wt.month), wt.day, 0, 0, 0, 0, UTC) y, w := dt.ISOWeek() if w != wt.wex || y != wt.yex { t.Errorf("got %d/%d; expected %d/%d for %d-%02d-%02d", @@ -533,27 +555,91 @@ func TestISOWeek(t *testing.T) { } // The only real invariant: Jan 04 is in week 1 - for year := int64(1950); year < 2100; year++ { - if y, w := (&Time{Year: year, Month: 1, Day: 4}).ISOWeek(); y != year || w != 1 { + for year := 1950; year < 2100; year++ { + if y, w := Date(year, January, 4, 0, 0, 0, 0, UTC).ISOWeek(); y != year || w != 1 { t.Errorf("got %d/%d; expected %d/1 for Jan 04", y, w, year) } } } -func BenchmarkSeconds(b *testing.B) { - for i := 0; i < b.N; i++ { - Seconds() +var durationTests = []struct { + str string + d Duration +}{ + {"0", 0}, + {"1ns", 1 * Nanosecond}, + {"1.1us", 1100 * Nanosecond}, + {"2.2ms", 2200 * Microsecond}, + {"3.3s", 3300 * Millisecond}, + {"4m5s", 4*Minute + 5*Second}, + {"4m5.001s", 4*Minute + 5001*Millisecond}, + {"5h6m7.001s", 5*Hour + 6*Minute + 7001*Millisecond}, + {"8m0.000000001s", 8*Minute + 1*Nanosecond}, + {"2562047h47m16.854775807s", 1<<63 - 1}, + {"-2562047h47m16.854775808s", -1 << 63}, +} + +func TestDurationString(t *testing.T) { + for _, tt := range durationTests { + if str := tt.d.String(); str != tt.str { + t.Errorf("Duration(%d).String() = %s, want %s", int64(tt.d), str, tt.str) + } + if tt.d > 0 { + if str := (-tt.d).String(); str != "-"+tt.str { + t.Errorf("Duration(%d).String() = %s, want %s", int64(-tt.d), str, "-"+tt.str) + } + } } } -func BenchmarkNanoseconds(b *testing.B) { +var dateTests = []struct { + year, month, day, hour, min, sec, nsec int + z *Location + unix int64 +}{ + {2011, 11, 6, 1, 0, 0, 0, Local, 1320566400}, // 1:00:00 PDT + {2011, 11, 6, 1, 59, 59, 0, Local, 1320569999}, // 1:59:59 PDT + {2011, 11, 6, 2, 0, 0, 0, Local, 1320573600}, // 2:00:00 PST + + {2011, 3, 13, 1, 0, 0, 0, Local, 1300006800}, // 1:00:00 PST + {2011, 3, 13, 1, 59, 59, 0, Local, 1300010399}, // 1:59:59 PST + {2011, 3, 13, 3, 0, 0, 0, Local, 1300010400}, // 3:00:00 PDT + {2011, 3, 13, 2, 30, 0, 0, Local, 1300008600}, // 2:30:00 PDT ≡ 1:30 PST + + // Many names for Fri Nov 18 7:56:35 PST 2011 + {2011, 11, 18, 7, 56, 35, 0, Local, 1321631795}, // Nov 18 7:56:35 + {2011, 11, 19, -17, 56, 35, 0, Local, 1321631795}, // Nov 19 -17:56:35 + {2011, 11, 17, 31, 56, 35, 0, Local, 1321631795}, // Nov 17 31:56:35 + {2011, 11, 18, 6, 116, 35, 0, Local, 1321631795}, // Nov 18 6:116:35 + {2011, 10, 49, 7, 56, 35, 0, Local, 1321631795}, // Oct 49 7:56:35 + {2011, 11, 18, 7, 55, 95, 0, Local, 1321631795}, // Nov 18 7:55:95 + {2011, 11, 18, 7, 56, 34, 1e9, Local, 1321631795}, // Nov 18 7:56:34 + 10⁹ns + {2011, 12, -12, 7, 56, 35, 0, Local, 1321631795}, // Dec -21 7:56:35 + {2012, 1, -43, 7, 56, 35, 0, Local, 1321631795}, // Jan -52 7:56:35 2012 + {2012, int(January - 2), 18, 7, 56, 35, 0, Local, 1321631795}, // (Jan-2) 18 7:56:35 2012 + {2010, int(December + 11), 18, 7, 56, 35, 0, Local, 1321631795}, // (Dec+11) 18 7:56:35 2010 +} + +func TestDate(t *testing.T) { + for _, tt := range dateTests { + time := Date(tt.year, Month(tt.month), tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.z) + want := Unix(tt.unix, 0) + if !time.Equal(want) { + t.Errorf("Date(%d, %d, %d, %d, %d, %d, %d, %s) = %v, want %v", + tt.year, tt.month, tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.z, + time, want) + } + } +} + +func BenchmarkNow(b *testing.B) { for i := 0; i < b.N; i++ { - Nanoseconds() + Now() } } func BenchmarkFormat(b *testing.B) { - time := SecondsToLocalTime(1265346057) + time := Unix(1265346057, 0) for i := 0; i < b.N; i++ { time.Format("Mon Jan 2 15:04:05 2006") } @@ -564,3 +650,31 @@ func BenchmarkParse(b *testing.B) { Parse(ANSIC, "Mon Jan 2 15:04:05 2006") } } + +func BenchmarkHour(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _ = t.Hour() + } +} + +func BenchmarkSecond(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _ = t.Second() + } +} + +func BenchmarkYear(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _ = t.Year() + } +} + +func BenchmarkDay(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _ = t.Day() + } +} diff --git a/libgo/go/time/zoneinfo.go b/libgo/go/time/zoneinfo.go new file mode 100644 index 0000000..aca56e7 --- /dev/null +++ b/libgo/go/time/zoneinfo.go @@ -0,0 +1,191 @@ +// Copyright 2011 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 time + +import "sync" + +// A Location maps time instants to the zone in use at that time. +// Typically, the Location represents the collection of time offsets +// in use in a geographical area, such as CEST and CET for central Europe. +type Location struct { + name string + zone []zone + tx []zoneTrans + + // 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 + // zone for the time when the Location was created. + // if cacheStart <= t <= cacheEnd, + // lookup can return cacheZone. + // The units for cacheStart and cacheEnd are seconds + // since January 1, 1970 UTC, to match the argument + // to lookup. + cacheStart int64 + cacheEnd int64 + cacheZone *zone +} + +// A zone represents a single time zone such as CEST or CET. +type zone struct { + name string // abbreviated name, "CET" + offset int // seconds east of UTC + isDST bool // is this zone Daylight Savings Time? +} + +// A zoneTrans represents a single time zone transition. +type zoneTrans struct { + when int64 // transition time, in seconds since 1970 GMT + index uint8 // the index of the zone that goes into effect at that time + isstd, isutc bool // ignored - no idea what these mean +} + +// UTC represents Universal Coordinated Time (UTC). +var UTC *Location = &utcLoc + +// utcLoc is separate so that get can refer to &utcLoc +// and ensure that it never returns a nil *Location, +// even if a badly behaved client has changed UTC. +var utcLoc = Location{name: "UTC"} + +// Local represents the system's local time zone. +var Local *Location = &localLoc + +// localLoc is separate so that initLocal can initialize +// it even if a client has changed Local. +var localLoc Location +var localOnce sync.Once + +func (l *Location) get() *Location { + if l == nil { + return &utcLoc + } + if l == &localLoc { + localOnce.Do(initLocal) + } + return l +} + +// String returns a descriptive name for the time zone information, +// corresponding to the argument to LoadLocation. +func (l *Location) String() string { + return l.get().name +} + +// FixedZone returns a Location that always uses +// the given zone name and offset (seconds east of UTC). +func FixedZone(name string, offset int) *Location { + l := &Location{ + name: name, + zone: []zone{{name, offset, false}}, + tx: []zoneTrans{{-1 << 63, 0, false, false}}, + cacheStart: -1 << 63, + cacheEnd: 1<<63 - 1, + } + l.cacheZone = &l.zone[0] + return l +} + +// lookup returns information about the time zone in use at an +// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. +// +// The returned information gives the name of the zone (such as "CET"), +// the start and end times bracketing sec when that zone is in effect, +// the offset in seconds east of UTC (such as -5*60*60), and whether +// the daylight savings is being observed at that time. +func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) { + l = l.get() + + if len(l.tx) == 0 { + name = "UTC" + offset = 0 + isDST = false + start = -1 << 63 + end = 1<<63 - 1 + return + } + + if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + name = zone.name + offset = zone.offset + isDST = zone.isDST + start = l.cacheStart + end = l.cacheEnd + return + } + + // Binary search for entry with largest time <= sec. + // Not using sort.Search to avoid dependencies. + tx := l.tx + end = 1<<63 - 1 + for len(tx) > 1 { + m := len(tx) / 2 + lim := tx[m].when + if sec < lim { + end = lim + tx = tx[0:m] + } else { + tx = tx[m:] + } + } + zone := &l.zone[tx[0].index] + name = zone.name + offset = zone.offset + isDST = zone.isDST + start = tx[0].when + // end = maintained during the search + return +} + +// lookupName returns information about the time zone with +// the given name (such as "EST"). +func (l *Location) lookupName(name string) (offset int, isDST bool, ok bool) { + l = l.get() + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + return zone.offset, zone.isDST, true + } + } + return +} + +// lookupOffset returns information about the time zone with +// the given offset (such as -5*60*60). +func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) { + l = l.get() + for i := range l.zone { + zone := &l.zone[i] + if zone.offset == offset { + return zone.name, zone.isDST, true + } + } + return +} + +// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment +// syntax too, but I don't feel like implementing it today. + +// NOTE(rsc): Using the IANA names below means ensuring we have access +// to the database. Probably we will ship the files in $GOROOT/lib/zoneinfo/ +// and only look there if there are no system files available (such as on Windows). +// The files total 200 kB. + +// LoadLocation returns the Location with the given name. +// +// If the name is "" or "UTC", LoadLocation returns UTC. +// If the name is "Local", LoadLocation returns Local. +// +// Otherwise, the name is taken to be a location name corresponding to a file +// in the IANA Time Zone database, such as "America/New_York". +func LoadLocation(name string) (*Location, error) { + if name == "" || name == "UTC" { + return UTC, nil + } + if name == "Local" { + return Local, nil + } + return loadLocation(name) +} diff --git a/libgo/go/time/zoneinfo_plan9.go b/libgo/go/time/zoneinfo_plan9.go index 577ef85..38aefc7 100644 --- a/libgo/go/time/zoneinfo_plan9.go +++ b/libgo/go/time/zoneinfo_plan9.go @@ -6,11 +6,10 @@ package time -import ( - "os" - "strconv" - "strings" -) +//import ( +// "strconv" +// "strings" +//) func parseZones(s string) (zt []zonetime) { f := strings.Fields(s) @@ -49,7 +48,7 @@ func parseZones(s string) (zt []zonetime) { return } -func setupZone() { +func initLocal() { t, err := os.Getenverror("timezone") if err != nil { // do nothing: use UTC @@ -58,16 +57,8 @@ func setupZone() { zones = parseZones(t) } -func setupTestingZone() { - f, err := os.Open("/adm/timezone/US_Pacific") - if err != nil { - return - } - defer f.Close() - l, _ := f.Seek(0, 2) - f.Seek(0, 0) - buf := make([]byte, l) - _, err = f.Read(buf) +func initTestingZone() { + buf, err := readFile("/adm/timezone/US_Pacific") if err != nil { return } diff --git a/libgo/go/time/zoneinfo_posix.go b/libgo/go/time/zoneinfo_posix.go deleted file mode 100644 index b0fa6c3..0000000 --- a/libgo/go/time/zoneinfo_posix.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2011 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 darwin freebsd linux openbsd plan9 - -package time - -import "sync" - -// Parsed representation -type zone struct { - utcoff int - isdst bool - name string -} - -type zonetime struct { - time int32 // transition time, in seconds since 1970 GMT - zone *zone // the zone that goes into effect at that time - isstd, isutc bool // ignored - no idea what these mean -} - -var zones []zonetime -var onceSetupZone sync.Once - -// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location. -func lookupTimezone(sec int64) (zone string, offset int) { - onceSetupZone.Do(setupZone) - if len(zones) == 0 { - return "UTC", 0 - } - - // Binary search for entry with largest time <= sec - tz := zones - for len(tz) > 1 { - m := len(tz) / 2 - if sec < int64(tz[m].time) { - tz = tz[0:m] - } else { - tz = tz[m:] - } - } - z := tz[0].zone - return z.name, z.utcoff -} - -// lookupByName returns the time offset for the -// time zone with the given abbreviation. It only considers -// time zones that apply to the current system. -// For example, for a system configured as being in New York, -// it only recognizes "EST" and "EDT". -// For a system in San Francisco, "PST" and "PDT". -// For a system in Sydney, "EST" and "EDT", though they have -// different meanings than they do in New York. -func lookupByName(name string) (off int, found bool) { - onceSetupZone.Do(setupZone) - for _, z := range zones { - if name == z.zone.name { - return z.zone.utcoff, true - } - } - return 0, false -} diff --git a/libgo/go/time/zoneinfo_unix.go b/libgo/go/time/zoneinfo_unix.go index b552e58..83d5b98 100644 --- a/libgo/go/time/zoneinfo_unix.go +++ b/libgo/go/time/zoneinfo_unix.go @@ -12,8 +12,8 @@ package time import ( - "bytes" - "os" + "errors" + "syscall" ) const ( @@ -65,18 +65,20 @@ func byteString(p []byte) string { return string(p) } -func parseinfo(bytes []byte) (zt []zonetime, ok bool) { +var badData = errors.New("malformed time zone information") + +func loadZoneData(bytes []byte) (l *Location, err error) { d := data{bytes, false} // 4-byte magic "TZif" if magic := d.read(4); string(magic) != "TZif" { - return nil, false + return nil, badData } // 1-byte version, then 15 bytes of padding var p []byte if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' { - return nil, false + return nil, badData } // six big-endian 32-bit integers: @@ -98,7 +100,7 @@ func parseinfo(bytes []byte) (zt []zonetime, ok bool) { for i := 0; i < 6; i++ { nn, ok := d.big4() if !ok { - return nil, false + return nil, badData } n[i] = int(nn) } @@ -127,7 +129,7 @@ func parseinfo(bytes []byte) (zt []zonetime, ok bool) { isutc := d.read(n[NUTCLocal]) if d.error { // ran out of data - return nil, false + return nil, badData } // If version == 2, the entire file repeats, this time using @@ -137,90 +139,119 @@ func parseinfo(bytes []byte) (zt []zonetime, ok bool) { // Now we can build up a useful data structure. // First the zone information. // utcoff[4] isdst[1] nameindex[1] - z := make([]zone, n[NZone]) - for i := 0; i < len(z); i++ { + zone := make([]zone, n[NZone]) + for i := range zone { var ok bool var n uint32 if n, ok = zonedata.big4(); !ok { - return nil, false + return nil, badData } - z[i].utcoff = int(n) + zone[i].offset = int(n) var b byte if b, ok = zonedata.byte(); !ok { - return nil, false + return nil, badData } - z[i].isdst = b != 0 + zone[i].isDST = b != 0 if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { - return nil, false + return nil, badData } - z[i].name = byteString(abbrev[b:]) + zone[i].name = byteString(abbrev[b:]) } // Now the transition time info. - zt = make([]zonetime, n[NTime]) - for i := 0; i < len(zt); i++ { + tx := make([]zoneTrans, n[NTime]) + for i := range tx { var ok bool var n uint32 if n, ok = txtimes.big4(); !ok { - return nil, false + return nil, badData } - zt[i].time = int32(n) - if int(txzones[i]) >= len(z) { - return nil, false + tx[i].when = int64(int32(n)) + if int(txzones[i]) >= len(zone) { + return nil, badData } - zt[i].zone = &z[txzones[i]] + tx[i].index = txzones[i] if i < len(isstd) { - zt[i].isstd = isstd[i] != 0 + tx[i].isstd = isstd[i] != 0 } if i < len(isutc) { - zt[i].isutc = isutc[i] != 0 + tx[i].isutc = isutc[i] != 0 } } - return zt, true -} -func readinfofile(name string) ([]zonetime, bool) { - var b bytes.Buffer + // Commited to succeed. + l = &Location{zone: zone, tx: tx} - f, err := os.Open(name) - if err != nil { - return nil, false + // Fill in the cache with information about right now, + // since that will be the most common lookup. + sec, _ := now() + for i := range tx { + if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) { + l.cacheStart = tx[i].when + l.cacheEnd = 1<<63 - 1 + if i+1 < len(tx) { + l.cacheEnd = tx[i+1].when + } + l.cacheZone = &l.zone[tx[i].index] + } } - defer f.Close() - if _, err := b.ReadFrom(f); err != nil { - return nil, false + + return l, nil +} + +func loadZoneFile(name string) (l *Location, err error) { + buf, err := readFile(name) + if err != nil { + return } - return parseinfo(b.Bytes()) + return loadZoneData(buf) } -func setupTestingZone() { - os.Setenv("TZ", "America/Los_Angeles") - setupZone() +func initTestingZone() { + syscall.Setenv("TZ", "America/Los_Angeles") + initLocal() } -func setupZone() { +// Many systems use /usr/share/zoneinfo, Solaris 2 has +// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. +var zoneDirs = []string{ + "/usr/share/zoneinfo/", + "/usr/share/lib/zoneinfo/", + "/usr/lib/locale/TZ/", +} + +func initLocal() { // consult $TZ to find the time zone to use. // no $TZ means use the system default /etc/localtime. // $TZ="" means use UTC. // $TZ="foo" means use /usr/share/zoneinfo/foo. - // Many systems use /usr/share/zoneinfo, Solaris 2 has - // /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. - zoneDirs := []string{"/usr/share/zoneinfo/", - "/usr/share/lib/zoneinfo/", - "/usr/lib/locale/TZ/"} - tz, err := os.Getenverror("TZ") + tz, ok := syscall.Getenv("TZ") switch { - case err == os.ENOENV: - zones, _ = readinfofile("/etc/localtime") - case len(tz) > 0: - for _, zoneDir := range zoneDirs { - var ok bool - if zones, ok = readinfofile(zoneDir + tz); ok { - break - } + case !ok: + z, err := loadZoneFile("/etc/localtime") + if err == nil { + localLoc = *z + localLoc.name = "Local" + return + } + case tz != "" && tz != "UTC": + if z, err := loadLocation(tz); err == nil { + localLoc = *z + return + } + } + + // Fall back to UTC. + localLoc.name = "UTC" +} + +func loadLocation(name string) (*Location, error) { + for _, zoneDir := range zoneDirs { + if z, err := loadZoneFile(zoneDir + name); err == nil { + z.name = name + return z, nil } - case len(tz) == 0: - // do nothing: use UTC } + return nil, errors.New("unknown time zone " + name) } diff --git a/libgo/go/time/zoneinfo_windows.go b/libgo/go/time/zoneinfo_windows.go index 995fd44..beef4de 100644 --- a/libgo/go/time/zoneinfo_windows.go +++ b/libgo/go/time/zoneinfo_windows.go @@ -5,34 +5,21 @@ package time import ( - "os" - "sync" + "errors" "syscall" ) -// BUG(brainman): The Windows implementation assumes that -// this year's rules for daylight savings time apply to all previous -// and future years as well. - -// TODO(brainman): use GetDynamicTimeZoneInformation, whenever possible (Vista and up), -// to improve on situation described in the bug above. - -type zone struct { - name string - offset int - year int64 - month, day, dayofweek int - hour, minute, second int - abssec int64 - prev *zone -} +// TODO(rsc): Fall back to copy of zoneinfo files. -// BUG(rsc): On Windows, time zone abbreviations are unavailable. -// This package constructs them using the capital letters from a longer -// time zone description. +// BUG(brainman,rsc): On Windows, the operating system does not provide complete +// time zone information. +// The implementation assumes that this year's rules for daylight savings +// time apply to all previous and future years as well. +// Also, time zone abbreviations are unavailable. The implementation constructs +// them using the capital letters from a longer time zone description. -// Populate zone struct with Windows supplied information. Returns true, if data is valid. -func (z *zone) populate(bias, biasdelta int32, d *syscall.Systemtime, name []uint16) (dateisgood bool) { +// abbrev returns the abbreviation to use for the given zone name. +func abbrev(name []uint16) string { // name is 'Pacific Standard Time' but we want 'PST'. // Extract just capital letters. It's not perfect but the // information we need is not available from the kernel. @@ -41,147 +28,101 @@ func (z *zone) populate(bias, biasdelta int32, d *syscall.Systemtime, name []uin // // http://social.msdn.microsoft.com/Forums/eu/vclanguage/thread/a87e1d25-fb71-4fe0-ae9c-a9578c9753eb // http://stackoverflow.com/questions/4195948/windows-time-zone-abbreviations-in-asp-net - short := make([]uint16, len(name)) - w := 0 + var short []rune for _, c := range name { if 'A' <= c && c <= 'Z' { - short[w] = c - w++ - } - } - z.name = syscall.UTF16ToString(short[:w]) - - z.offset = int(bias) - z.year = int64(d.Year) - z.month = int(d.Month) - z.day = int(d.Day) - z.dayofweek = int(d.DayOfWeek) - z.hour = int(d.Hour) - z.minute = int(d.Minute) - z.second = int(d.Second) - dateisgood = d.Month != 0 - if dateisgood { - z.offset += int(biasdelta) - } - z.offset = -z.offset * 60 - return -} - -// Pre-calculate cutoff time in seconds since the Unix epoch, if data is supplied in "absolute" format. -func (z *zone) preCalculateAbsSec() { - if z.year != 0 { - t := &Time{ - Year: z.year, - Month: int(z.month), - Day: int(z.day), - Hour: int(z.hour), - Minute: int(z.minute), - Second: int(z.second), + short = append(short, rune(c)) } - z.abssec = t.Seconds() - // Time given is in "local" time. Adjust it for "utc". - z.abssec -= int64(z.prev.offset) } + return string(short) } -// Convert zone cutoff time to sec in number of seconds since the Unix epoch, given particular year. -func (z *zone) cutoffSeconds(year int64) int64 { +// pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*) +// denoted by the system date+time d in the given year. +// It is up to the caller to convert this local time into a UTC-based time. +func pseudoUnix(year int, d *syscall.Systemtime) int64 { // Windows specifies daylight savings information in "day in month" format: - // z.month is month number (1-12) - // z.dayofweek is appropriate weekday (Sunday=0 to Saturday=6) - // z.day is week within the month (1 to 5, where 5 is last week of the month) - // z.hour, z.minute and z.second are absolute time - t := &Time{ - Year: year, - Month: int(z.month), - Day: 1, - Hour: int(z.hour), - Minute: int(z.minute), - Second: int(z.second), - } - t = SecondsToUTC(t.Seconds()) - i := int(z.dayofweek) - t.Weekday() + // d.Month is month number (1-12) + // d.DayOfWeek is appropriate weekday (Sunday=0 to Saturday=6) + // d.Day is week within the month (1 to 5, where 5 is last week of the month) + // d.Hour, d.Minute and d.Second are absolute time + day := 1 + t := Date(year, Month(d.Month), day, int(d.Hour), int(d.Minute), int(d.Second), 0, UTC) + i := int(d.DayOfWeek) - int(t.Weekday()) if i < 0 { i += 7 } - t.Day += i - if week := int(z.day) - 1; week < 4 { - t.Day += week * 7 + day += i + if week := int(d.Day) - 1; week < 4 { + day += week * 7 } else { // "Last" instance of the day. - t.Day += 4 * 7 - if t.Day > months(year)[t.Month] { - t.Day -= 7 + day += 4 * 7 + if day > daysIn(Month(d.Month), year) { + day -= 7 } } - // Result is in "local" time. Adjust it for "utc". - return t.Seconds() - int64(z.prev.offset) + return t.sec + int64(day-1)*secondsPerDay + internalToUnix } -// Is t before the cutoff for switching to z? -func (z *zone) isBeforeCutoff(t *Time) bool { - var coff int64 - if z.year == 0 { - // "day in month" format used - coff = z.cutoffSeconds(t.Year) - } else { - // "absolute" format used - coff = z.abssec - } - return t.Seconds() < coff -} - -type zoneinfo struct { - disabled bool // daylight saving time is not used locally - offsetIfDisabled int - januaryIsStd bool // is january 1 standard time? - std, dst zone -} +func initLocalFromTZI(i *syscall.Timezoneinformation) { + l := &localLoc -// Pick zone (std or dst) t time belongs to. -func (zi *zoneinfo) pickZone(t *Time) *zone { - z := &zi.std - if tz.januaryIsStd { - if !zi.dst.isBeforeCutoff(t) && zi.std.isBeforeCutoff(t) { - // after switch to daylight time and before the switch back to standard - z = &zi.dst - } - } else { - if zi.std.isBeforeCutoff(t) || !zi.dst.isBeforeCutoff(t) { - // before switch to standard time or after the switch back to daylight - z = &zi.dst - } + nzone := 1 + if i.StandardDate.Month > 0 { + nzone++ } - return z -} - -var tz zoneinfo -var initError error -var onceSetupZone sync.Once + l.zone = make([]zone, nzone) -func setupZone() { - var i syscall.Timezoneinformation - if _, e := syscall.GetTimeZoneInformation(&i); e != nil { - initError = os.NewSyscallError("GetTimeZoneInformation", e) + std := &l.zone[0] + std.name = abbrev(i.StandardName[0:]) + if nzone == 1 { + // No daylight savings. + std.offset = -int(i.Bias) * 60 + l.cacheStart = -1 << 63 + l.cacheEnd = 1<<63 - 1 + l.cacheZone = std return } - setupZoneFromTZI(&i) -} -func setupZoneFromTZI(i *syscall.Timezoneinformation) { - if !tz.std.populate(i.Bias, i.StandardBias, &i.StandardDate, i.StandardName[0:]) { - tz.disabled = true - tz.offsetIfDisabled = tz.std.offset - return + // StandardBias must be ignored if StandardDate is not set, + // so this computation is delayed until after the nzone==1 + // return above. + std.offset = -int(i.Bias+i.StandardBias) * 60 + + dst := &l.zone[1] + dst.name = abbrev(i.DaylightName[0:]) + dst.offset = -int(i.Bias+i.DaylightBias) * 60 + dst.isDST = true + + // Arrange so that d0 is first transition date, d1 second, + // i0 is index of zone after first transition, i1 second. + d0 := &i.StandardDate + d1 := &i.DaylightDate + i0 := 0 + i1 := 1 + if d0.Month > d1.Month { + d0, d1 = d1, d0 + i0, i1 = i1, i0 + } + + // 2 tx per year, 100 years on each side of this year + l.tx = make([]zoneTrans, 400) + + t := Now().UTC() + year := t.Year() + txi := 0 + for y := year - 100; y < year+100; y++ { + tx := &l.tx[txi] + tx.when = pseudoUnix(y, d0) - int64(l.zone[i1].offset) + tx.index = uint8(i0) + txi++ + + tx = &l.tx[txi] + tx.when = pseudoUnix(y, d1) - int64(l.zone[i0].offset) + tx.index = uint8(i1) + txi++ } - tz.std.prev = &tz.dst - tz.dst.populate(i.Bias, i.DaylightBias, &i.DaylightDate, i.DaylightName[0:]) - tz.dst.prev = &tz.std - tz.std.preCalculateAbsSec() - tz.dst.preCalculateAbsSec() - // Is january 1 standard time this year? - t := UTC() - tz.januaryIsStd = tz.dst.cutoffSeconds(t.Year) < tz.std.cutoffSeconds(t.Year) } var usPacific = syscall.Timezoneinformation{ @@ -197,53 +138,20 @@ var usPacific = syscall.Timezoneinformation{ DaylightBias: -60, } -func setupTestingZone() { - setupZoneFromTZI(&usPacific) +func initTestingZone() { + initLocalFromTZI(&usPacific) } -// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location. -func lookupTimezone(sec int64) (zone string, offset int) { - onceSetupZone.Do(setupZone) - if initError != nil { - return "", 0 - } - if tz.disabled { - return "", tz.offsetIfDisabled - } - t := SecondsToUTC(sec) - z := &tz.std - if tz.std.year == 0 { - // "day in month" format used - z = tz.pickZone(t) - } else { - // "absolute" format used - if tz.std.year == t.Year { - // we have rule for the year in question - z = tz.pickZone(t) - } else { - // we do not have any information for that year, - // will assume standard offset all year around - } +func initLocal() { + var i syscall.Timezoneinformation + if _, err := syscall.GetTimeZoneInformation(&i); err != nil { + localLoc.name = "UTC" + return } - return z.name, z.offset + initLocalFromTZI(&i) } -// lookupByName returns the time offset for the -// time zone with the given abbreviation. It only considers -// time zones that apply to the current system. -func lookupByName(name string) (off int, found bool) { - onceSetupZone.Do(setupZone) - if initError != nil { - return 0, false - } - if tz.disabled { - return tz.offsetIfDisabled, false - } - switch name { - case tz.std.name: - return tz.std.offset, true - case tz.dst.name: - return tz.dst.offset, true - } - return 0, false +// TODO(rsc): Implement. +func loadLocation(name string) (*Location, error) { + return nil, errors.New("unknown time zone " + name) } diff --git a/libgo/go/websocket/client.go b/libgo/go/websocket/client.go index 5dfd824..89cdcda 100644 --- a/libgo/go/websocket/client.go +++ b/libgo/go/websocket/client.go @@ -72,8 +72,8 @@ A trivial example client: package main import ( - "http" "log" + "net/http" "strings" "websocket" ) diff --git a/libgo/go/websocket/hixie.go b/libgo/go/websocket/hixie.go index 4d5360ff..ec7b7ae 100644 --- a/libgo/go/websocket/hixie.go +++ b/libgo/go/websocket/hixie.go @@ -274,7 +274,7 @@ func getChallengeResponse(number1, number2 uint32, key3 []byte) (expected []byte if _, err = h.Write(challenge); err != nil { return } - expected = h.Sum() + expected = h.Sum(nil) return } diff --git a/libgo/go/websocket/hybi.go b/libgo/go/websocket/hybi.go index b17d947..ff386dc 100644 --- a/libgo/go/websocket/hybi.go +++ b/libgo/go/websocket/hybi.go @@ -371,7 +371,7 @@ func getNonceAccept(nonce []byte) (expected []byte, err error) { return } expected = make([]byte, 28) - base64.StdEncoding.Encode(expected, h.Sum()) + base64.StdEncoding.Encode(expected, h.Sum(nil)) return } diff --git a/libgo/go/websocket/server.go b/libgo/go/websocket/server.go index 57dc4fd..8320b03 100644 --- a/libgo/go/websocket/server.go +++ b/libgo/go/websocket/server.go @@ -60,8 +60,8 @@ A trivial example server: package main import ( - "http" "io" + "net/http" "websocket" ) |