diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2012-03-06 17:57:23 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2012-03-06 17:57:23 +0000 |
commit | 593f74bbab63d34c7060918088bcbad686c31c66 (patch) | |
tree | 4ce83ca433796a728e9fdd00af105bce158532b5 /libgo | |
parent | 46402cbe0ba3ea92be9642cf18eedaefe57a414c (diff) | |
download | gcc-593f74bbab63d34c7060918088bcbad686c31c66.tar.gz |
libgo: Update to weekly.2012-03-04 release.
From-SVN: r185010
Diffstat (limited to 'libgo')
147 files changed, 3079 insertions, 2021 deletions
diff --git a/libgo/MERGE b/libgo/MERGE index 9605a8a9040..17d01ce7265 100644 --- a/libgo/MERGE +++ b/libgo/MERGE @@ -1,4 +1,4 @@ -96bd78e7d35e +f4470a54e6db The first line of this file holds the Mercurial revision number of the last merge done from the master library sources. diff --git a/libgo/Makefile.am b/libgo/Makefile.am index eb764df0eab..d43f0542938 100644 --- a/libgo/Makefile.am +++ b/libgo/Makefile.am @@ -658,10 +658,17 @@ go_net_sock_file = go/net/sock_linux.go go_net_sockopt_file = go/net/sockopt_linux.go go_net_sockoptip_file = go/net/sockoptip_linux.go else +if LIBGO_IS_FREEBSD go_net_cgo_file = go/net/cgo_bsd.go go_net_sock_file = go/net/sock_bsd.go go_net_sockopt_file = go/net/sockopt_bsd.go -go_net_sockoptip_file = go/net/sockoptip_bsd.go +go_net_sockoptip_file = go/net/sockoptip_bsd.go go/net/sockoptip_freebsd.go +else +go_net_cgo_file = go/net/cgo_bsd.go +go_net_sock_file = go/net/sock_bsd.go +go_net_sockopt_file = go/net/sockopt_bsd.go +go_net_sockoptip_file = go/net/sockoptip_bsd.go go/net/sockoptip_netbsd.go +endif endif endif endif @@ -704,6 +711,7 @@ go_net_files = \ go/net/ipsock.go \ go/net/ipsock_posix.go \ go/net/lookup_unix.go \ + go/net/mac.go \ go/net/net.go \ go/net/parse.go \ go/net/pipe.go \ @@ -1126,8 +1134,7 @@ go_go_ast_files = \ go/go/ast/walk.go go_go_build_files = \ go/go/build/build.go \ - go/go/build/dir.go \ - go/go/build/path.go \ + go/go/build/doc.go \ syslist.go go_go_doc_files = \ go/go/doc/comment.go \ diff --git a/libgo/Makefile.in b/libgo/Makefile.in index 4604e560c9b..418c7876a31 100644 --- a/libgo/Makefile.in +++ b/libgo/Makefile.in @@ -1012,19 +1012,23 @@ go_mime_files = \ @LIBGO_IS_LINUX_FALSE@@LIBGO_IS_NETBSD_TRUE@@LIBGO_IS_RTEMS_FALSE@go_net_newpollserver_file = go/net/newpollserver.go @LIBGO_IS_LINUX_TRUE@@LIBGO_IS_RTEMS_FALSE@go_net_newpollserver_file = go/net/newpollserver.go @LIBGO_IS_RTEMS_TRUE@go_net_newpollserver_file = go/net/newpollserver_rtems.go -@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_cgo_file = go/net/cgo_bsd.go +@LIBGO_IS_FREEBSD_FALSE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_cgo_file = go/net/cgo_bsd.go +@LIBGO_IS_FREEBSD_TRUE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_cgo_file = go/net/cgo_bsd.go @LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_cgo_file = go/net/cgo_linux.go @LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_cgo_file = go/net/cgo_linux.go @LIBGO_IS_LINUX_TRUE@go_net_cgo_file = go/net/cgo_linux.go -@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sock_file = go/net/sock_bsd.go +@LIBGO_IS_FREEBSD_FALSE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sock_file = go/net/sock_bsd.go +@LIBGO_IS_FREEBSD_TRUE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sock_file = go/net/sock_bsd.go @LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_sock_file = go/net/sock_linux.go @LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_sock_file = go/net/sock_linux.go @LIBGO_IS_LINUX_TRUE@go_net_sock_file = go/net/sock_linux.go -@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockopt_file = go/net/sockopt_bsd.go +@LIBGO_IS_FREEBSD_FALSE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockopt_file = go/net/sockopt_bsd.go +@LIBGO_IS_FREEBSD_TRUE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockopt_file = go/net/sockopt_bsd.go @LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_sockopt_file = go/net/sockopt_linux.go @LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_sockopt_file = go/net/sockopt_linux.go @LIBGO_IS_LINUX_TRUE@go_net_sockopt_file = go/net/sockopt_linux.go -@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockoptip_file = go/net/sockoptip_bsd.go +@LIBGO_IS_FREEBSD_FALSE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockoptip_file = go/net/sockoptip_bsd.go go/net/sockoptip_netbsd.go +@LIBGO_IS_FREEBSD_TRUE@@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockoptip_file = go/net/sockoptip_bsd.go go/net/sockoptip_freebsd.go @LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_sockoptip_file = go/net/sockoptip_linux.go @LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_sockoptip_file = go/net/sockoptip_linux.go @LIBGO_IS_LINUX_TRUE@go_net_sockoptip_file = go/net/sockoptip_linux.go @@ -1055,6 +1059,7 @@ go_net_files = \ go/net/ipsock.go \ go/net/ipsock_posix.go \ go/net/lookup_unix.go \ + go/net/mac.go \ go/net/net.go \ go/net/parse.go \ go/net/pipe.go \ @@ -1467,8 +1472,7 @@ go_go_ast_files = \ go_go_build_files = \ go/go/build/build.go \ - go/go/build/dir.go \ - go/go/build/path.go \ + go/go/build/doc.go \ syslist.go go_go_doc_files = \ diff --git a/libgo/go/archive/zip/reader.go b/libgo/go/archive/zip/reader.go index c3009869b60..f3826dcc48d 100644 --- a/libgo/go/archive/zip/reader.go +++ b/libgo/go/archive/zip/reader.go @@ -169,48 +169,21 @@ func (r *checksumReader) Read(b []byte) (n int, err error) { func (r *checksumReader) Close() error { return r.rc.Close() } -func readFileHeader(f *File, r io.Reader) error { - var b [fileHeaderLen]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - c := binary.LittleEndian - if sig := c.Uint32(b[:4]); sig != fileHeaderSignature { - return ErrFormat - } - f.ReaderVersion = c.Uint16(b[4:6]) - f.Flags = c.Uint16(b[6:8]) - f.Method = c.Uint16(b[8:10]) - f.ModifiedTime = c.Uint16(b[10:12]) - f.ModifiedDate = c.Uint16(b[12:14]) - f.CRC32 = c.Uint32(b[14:18]) - f.CompressedSize = c.Uint32(b[18:22]) - f.UncompressedSize = c.Uint32(b[22:26]) - filenameLen := int(c.Uint16(b[26:28])) - extraLen := int(c.Uint16(b[28:30])) - d := make([]byte, filenameLen+extraLen) - if _, err := io.ReadFull(r, d); err != nil { - return err - } - f.Name = string(d[:filenameLen]) - f.Extra = d[filenameLen:] - return nil -} - // findBodyOffset does the minimum work to verify the file has a header // and returns the file body offset. func (f *File) findBodyOffset() (int64, error) { r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset) - var b [fileHeaderLen]byte - if _, err := io.ReadFull(r, b[:]); err != nil { + var buf [fileHeaderLen]byte + if _, err := io.ReadFull(r, buf[:]); err != nil { return 0, err } - c := binary.LittleEndian - if sig := c.Uint32(b[:4]); sig != fileHeaderSignature { + b := readBuf(buf[:]) + if sig := b.uint32(); sig != fileHeaderSignature { return 0, ErrFormat } - filenameLen := int(c.Uint16(b[26:28])) - extraLen := int(c.Uint16(b[28:30])) + b = b[22:] // skip over most of the header + filenameLen := int(b.uint16()) + extraLen := int(b.uint16()) return int64(fileHeaderLen + filenameLen + extraLen), nil } @@ -218,30 +191,29 @@ func (f *File) findBodyOffset() (int64, error) { // It returns io.ErrUnexpectedEOF if it cannot read a complete header, // and ErrFormat if it doesn't find a valid header signature. func readDirectoryHeader(f *File, r io.Reader) error { - var b [directoryHeaderLen]byte - if _, err := io.ReadFull(r, b[:]); err != nil { + var buf [directoryHeaderLen]byte + if _, err := io.ReadFull(r, buf[:]); err != nil { return err } - c := binary.LittleEndian - if sig := c.Uint32(b[:4]); sig != directoryHeaderSignature { + b := readBuf(buf[:]) + if sig := b.uint32(); sig != directoryHeaderSignature { return ErrFormat } - f.CreatorVersion = c.Uint16(b[4:6]) - f.ReaderVersion = c.Uint16(b[6:8]) - f.Flags = c.Uint16(b[8:10]) - f.Method = c.Uint16(b[10:12]) - f.ModifiedTime = c.Uint16(b[12:14]) - f.ModifiedDate = c.Uint16(b[14:16]) - f.CRC32 = c.Uint32(b[16:20]) - f.CompressedSize = c.Uint32(b[20:24]) - f.UncompressedSize = c.Uint32(b[24:28]) - filenameLen := int(c.Uint16(b[28:30])) - extraLen := int(c.Uint16(b[30:32])) - commentLen := int(c.Uint16(b[32:34])) - // startDiskNumber := c.Uint16(b[34:36]) // Unused - // internalAttributes := c.Uint16(b[36:38]) // Unused - f.ExternalAttrs = c.Uint32(b[38:42]) - f.headerOffset = int64(c.Uint32(b[42:46])) + f.CreatorVersion = b.uint16() + f.ReaderVersion = b.uint16() + f.Flags = b.uint16() + f.Method = b.uint16() + f.ModifiedTime = b.uint16() + f.ModifiedDate = b.uint16() + f.CRC32 = b.uint32() + f.CompressedSize = b.uint32() + f.UncompressedSize = b.uint32() + filenameLen := int(b.uint16()) + extraLen := int(b.uint16()) + commentLen := int(b.uint16()) + b = b[4:] // skipped start disk number and internal attributes (2x uint16) + f.ExternalAttrs = b.uint32() + f.headerOffset = int64(b.uint32()) d := make([]byte, filenameLen+extraLen+commentLen) if _, err := io.ReadFull(r, d); err != nil { return err @@ -253,30 +225,30 @@ func readDirectoryHeader(f *File, r io.Reader) error { } func readDataDescriptor(r io.Reader, f *File) error { - var b [dataDescriptorLen]byte - if _, err := io.ReadFull(r, b[:]); err != nil { + var buf [dataDescriptorLen]byte + if _, err := io.ReadFull(r, buf[:]); err != nil { return err } - c := binary.LittleEndian - f.CRC32 = c.Uint32(b[:4]) - f.CompressedSize = c.Uint32(b[4:8]) - f.UncompressedSize = c.Uint32(b[8:12]) + b := readBuf(buf[:]) + f.CRC32 = b.uint32() + f.CompressedSize = b.uint32() + f.UncompressedSize = b.uint32() return nil } func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) { // look for directoryEndSignature in the last 1k, then in the last 65k - var b []byte + var buf []byte for i, bLen := range []int64{1024, 65 * 1024} { if bLen > size { bLen = size } - b = make([]byte, int(bLen)) - if _, err := r.ReadAt(b, size-bLen); err != nil && err != io.EOF { + buf = make([]byte, int(bLen)) + if _, err := r.ReadAt(buf, size-bLen); err != nil && err != io.EOF { return nil, err } - if p := findSignatureInBlock(b); p >= 0 { - b = b[p:] + if p := findSignatureInBlock(buf); p >= 0 { + buf = buf[p:] break } if i == 1 || bLen == size { @@ -285,16 +257,21 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) } // read header into struct - c := binary.LittleEndian - d := new(directoryEnd) - d.diskNbr = c.Uint16(b[4:6]) - d.dirDiskNbr = c.Uint16(b[6:8]) - d.dirRecordsThisDisk = c.Uint16(b[8:10]) - d.directoryRecords = c.Uint16(b[10:12]) - d.directorySize = c.Uint32(b[12:16]) - d.directoryOffset = c.Uint32(b[16:20]) - d.commentLen = c.Uint16(b[20:22]) - d.comment = string(b[22 : 22+int(d.commentLen)]) + b := readBuf(buf[4:]) // skip signature + d := &directoryEnd{ + diskNbr: b.uint16(), + dirDiskNbr: b.uint16(), + dirRecordsThisDisk: b.uint16(), + directoryRecords: b.uint16(), + directorySize: b.uint32(), + directoryOffset: b.uint32(), + commentLen: b.uint16(), + } + l := int(d.commentLen) + if l > len(b) { + return nil, errors.New("zip: invalid comment length") + } + d.comment = string(b[:l]) return d, nil } @@ -311,3 +288,17 @@ func findSignatureInBlock(b []byte) int { } return -1 } + +type readBuf []byte + +func (b *readBuf) uint16() uint16 { + v := binary.LittleEndian.Uint16(*b) + *b = (*b)[2:] + return v +} + +func (b *readBuf) uint32() uint32 { + v := binary.LittleEndian.Uint32(*b) + *b = (*b)[4:] + return v +} diff --git a/libgo/go/archive/zip/reader_test.go b/libgo/go/archive/zip/reader_test.go index ea9e0020db4..066a61580c5 100644 --- a/libgo/go/archive/zip/reader_test.go +++ b/libgo/go/archive/zip/reader_test.go @@ -165,7 +165,7 @@ func readTestZip(t *testing.T, zt ZipTest) { t.Errorf("%s: comment=%q, want %q", zt.Name, z.Comment, zt.Comment) } if len(z.File) != len(zt.File) { - t.Errorf("%s: file count=%d, want %d", zt.Name, len(z.File), len(zt.File)) + t.Fatalf("%s: file count=%d, want %d", zt.Name, len(z.File), len(zt.File)) } // test read of each file diff --git a/libgo/go/archive/zip/struct.go b/libgo/go/archive/zip/struct.go index 35dcec6468b..fdbd16da048 100644 --- a/libgo/go/archive/zip/struct.go +++ b/libgo/go/archive/zip/struct.go @@ -100,16 +100,6 @@ type directoryEnd struct { comment string } -func recoverError(errp *error) { - if e := recover(); e != nil { - if err, ok := e.(error); ok { - *errp = err - return - } - panic(e) - } -} - // msDosTimeToTime converts an MS-DOS date and time into a time.Time. // The resolution is 2s. // See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx diff --git a/libgo/go/archive/zip/writer.go b/libgo/go/archive/zip/writer.go index c591aed5ced..b2cc55bc93b 100644 --- a/libgo/go/archive/zip/writer.go +++ b/libgo/go/archive/zip/writer.go @@ -37,10 +37,10 @@ func NewWriter(w io.Writer) *Writer { // Close finishes writing the zip file by writing the central directory. // It does not (and can not) close the underlying writer. -func (w *Writer) Close() (err error) { +func (w *Writer) Close() error { if w.last != nil && !w.last.closed { - if err = w.last.close(); err != nil { - return + if err := w.last.close(); err != nil { + return err } w.last = nil } @@ -49,43 +49,55 @@ func (w *Writer) Close() (err error) { } w.closed = true - defer recoverError(&err) - // write central directory start := w.cw.count for _, h := range w.dir { - write(w.cw, uint32(directoryHeaderSignature)) - write(w.cw, h.CreatorVersion) - write(w.cw, h.ReaderVersion) - write(w.cw, h.Flags) - write(w.cw, h.Method) - write(w.cw, h.ModifiedTime) - write(w.cw, h.ModifiedDate) - write(w.cw, h.CRC32) - write(w.cw, h.CompressedSize) - write(w.cw, h.UncompressedSize) - write(w.cw, uint16(len(h.Name))) - write(w.cw, uint16(len(h.Extra))) - write(w.cw, uint16(len(h.Comment))) - write(w.cw, uint16(0)) // disk number start - write(w.cw, uint16(0)) // internal file attributes - write(w.cw, h.ExternalAttrs) - write(w.cw, h.offset) - writeBytes(w.cw, []byte(h.Name)) - writeBytes(w.cw, h.Extra) - writeBytes(w.cw, []byte(h.Comment)) + var buf [directoryHeaderLen]byte + b := writeBuf(buf[:]) + b.uint32(uint32(directoryHeaderSignature)) + b.uint16(h.CreatorVersion) + b.uint16(h.ReaderVersion) + b.uint16(h.Flags) + b.uint16(h.Method) + b.uint16(h.ModifiedTime) + b.uint16(h.ModifiedDate) + b.uint32(h.CRC32) + b.uint32(h.CompressedSize) + b.uint32(h.UncompressedSize) + b.uint16(uint16(len(h.Name))) + b.uint16(uint16(len(h.Extra))) + b.uint16(uint16(len(h.Comment))) + b = b[4:] // skip disk number start and internal file attr (2x uint16) + b.uint32(h.ExternalAttrs) + b.uint32(h.offset) + if _, err := w.cw.Write(buf[:]); err != nil { + return err + } + if _, err := io.WriteString(w.cw, h.Name); err != nil { + return err + } + if _, err := w.cw.Write(h.Extra); err != nil { + return err + } + if _, err := io.WriteString(w.cw, h.Comment); err != nil { + return err + } } end := w.cw.count // write end record - write(w.cw, uint32(directoryEndSignature)) - write(w.cw, uint16(0)) // disk number - write(w.cw, uint16(0)) // disk number where directory starts - write(w.cw, uint16(len(w.dir))) // number of entries this disk - write(w.cw, uint16(len(w.dir))) // number of entries total - write(w.cw, uint32(end-start)) // size of directory - write(w.cw, uint32(start)) // start of directory - write(w.cw, uint16(0)) // size of comment + var buf [directoryEndLen]byte + b := writeBuf(buf[:]) + b.uint32(uint32(directoryEndSignature)) + b = b[4:] // skip over disk number and first disk number (2x uint16) + b.uint16(uint16(len(w.dir))) // number of entries this disk + b.uint16(uint16(len(w.dir))) // number of entries total + b.uint32(uint32(end - start)) // size of directory + b.uint32(uint32(start)) // start of directory + // skipped size of comment (always zero) + if _, err := w.cw.Write(buf[:]); err != nil { + return err + } return w.cw.w.(*bufio.Writer).Flush() } @@ -152,22 +164,28 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { return fw, nil } -func writeHeader(w io.Writer, h *FileHeader) (err error) { - defer recoverError(&err) - write(w, uint32(fileHeaderSignature)) - write(w, h.ReaderVersion) - write(w, h.Flags) - write(w, h.Method) - write(w, h.ModifiedTime) - write(w, h.ModifiedDate) - write(w, h.CRC32) - write(w, h.CompressedSize) - write(w, h.UncompressedSize) - write(w, uint16(len(h.Name))) - write(w, uint16(len(h.Extra))) - writeBytes(w, []byte(h.Name)) - writeBytes(w, h.Extra) - return nil +func writeHeader(w io.Writer, h *FileHeader) error { + var buf [fileHeaderLen]byte + b := writeBuf(buf[:]) + b.uint32(uint32(fileHeaderSignature)) + b.uint16(h.ReaderVersion) + b.uint16(h.Flags) + b.uint16(h.Method) + b.uint16(h.ModifiedTime) + b.uint16(h.ModifiedDate) + b.uint32(h.CRC32) + b.uint32(h.CompressedSize) + b.uint32(h.UncompressedSize) + b.uint16(uint16(len(h.Name))) + b.uint16(uint16(len(h.Extra))) + if _, err := w.Write(buf[:]); err != nil { + return err + } + if _, err := io.WriteString(w, h.Name); err != nil { + return err + } + _, err := w.Write(h.Extra) + return err } type fileWriter struct { @@ -188,13 +206,13 @@ func (w *fileWriter) Write(p []byte) (int, error) { return w.rawCount.Write(p) } -func (w *fileWriter) close() (err error) { +func (w *fileWriter) close() error { if w.closed { return errors.New("zip: file closed twice") } w.closed = true - if err = w.comp.Close(); err != nil { - return + if err := w.comp.Close(); err != nil { + return err } // update FileHeader @@ -204,12 +222,13 @@ func (w *fileWriter) close() (err error) { fh.UncompressedSize = uint32(w.rawCount.count) // write data descriptor - defer recoverError(&err) - write(w.zipw, fh.CRC32) - write(w.zipw, fh.CompressedSize) - write(w.zipw, fh.UncompressedSize) - - return nil + var buf [dataDescriptorLen]byte + b := writeBuf(buf[:]) + b.uint32(fh.CRC32) + b.uint32(fh.CompressedSize) + b.uint32(fh.UncompressedSize) + _, err := w.zipw.Write(buf[:]) + return err } type countWriter struct { @@ -231,18 +250,14 @@ func (w nopCloser) Close() error { return nil } -func write(w io.Writer, data interface{}) { - if err := binary.Write(w, binary.LittleEndian, data); err != nil { - panic(err) - } +type writeBuf []byte + +func (b *writeBuf) uint16(v uint16) { + binary.LittleEndian.PutUint16(*b, v) + *b = (*b)[2:] } -func writeBytes(w io.Writer, b []byte) { - n, err := w.Write(b) - if err != nil { - panic(err) - } - if n != len(b) { - panic(io.ErrShortWrite) - } +func (b *writeBuf) uint32(v uint32) { + binary.LittleEndian.PutUint32(*b, v) + *b = (*b)[4:] } diff --git a/libgo/go/bufio/bufio.go b/libgo/go/bufio/bufio.go index 6f3b1eec971..b44d0e7d167 100644 --- a/libgo/go/bufio/bufio.go +++ b/libgo/go/bufio/bufio.go @@ -23,7 +23,6 @@ var ( ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune") ErrBufferFull = errors.New("bufio: buffer full") ErrNegativeCount = errors.New("bufio: negative count") - errInternal = errors.New("bufio: internal error") ) // Buffered input. diff --git a/libgo/go/crypto/ecdsa/ecdsa.go b/libgo/go/crypto/ecdsa/ecdsa.go index d2f7d8f9bb3..b28239b7862 100644 --- a/libgo/go/crypto/ecdsa/ecdsa.go +++ b/libgo/go/crypto/ecdsa/ecdsa.go @@ -7,7 +7,7 @@ package ecdsa // References: -// [NSA]: Suite B implementor's guide to FIPS 186-3, +// [NSA]: Suite B implementer's guide to FIPS 186-3, // http://www.nsa.gov/ia/_files/ecdsa.pdf // [SECG]: SECG, SEC1 // http://www.secg.org/download/aid-780/sec1-v2.pdf diff --git a/libgo/go/crypto/tls/root_darwin.go b/libgo/go/crypto/tls/root_darwin.go index db1b18b3c07..911a9a62e3c 100644 --- a/libgo/go/crypto/tls/root_darwin.go +++ b/libgo/go/crypto/tls/root_darwin.go @@ -5,11 +5,9 @@ package tls /* -// Note: We disable -Werror here because the code in this file uses a deprecated API to stay -// compatible with both Mac OS X 10.6 and 10.7. Using a deprecated function on Darwin generates -// a warning. -#cgo CFLAGS: -Wno-error -Wno-deprecated-declarations +#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060 #cgo LDFLAGS: -framework CoreFoundation -framework Security + #include <CoreFoundation/CoreFoundation.h> #include <Security/Security.h> @@ -40,26 +38,12 @@ int FetchPEMRoots(CFDataRef *pemRoots) { continue; } - // SecKeychainImportExport is deprecated in >= OS X 10.7, and has been replaced by - // SecItemExport. If we're built on a host with a Lion SDK, this code gets conditionally - // included in the output, also for binaries meant for 10.6. - // - // To make sure that we run on both Mac OS X 10.6 and 10.7 we use weak linking - // and check whether SecItemExport is available before we attempt to call it. On - // 10.6, this won't be the case, and we'll fall back to calling SecKeychainItemExport. -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - if (SecItemExport) { - err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); - if (err != noErr) { - continue; - } - } else -#endif - if (data == NULL) { - err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); - if (err != noErr) { - continue; - } + // Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport. + // Once we support weak imports via cgo we should prefer that, and fall back to this + // for older systems. + err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); + if (err != noErr) { + continue; } if (data != NULL) { diff --git a/libgo/go/crypto/x509/verify.go b/libgo/go/crypto/x509/verify.go index 87b1cb7bb1c..3859dd8d488 100644 --- a/libgo/go/crypto/x509/verify.go +++ b/libgo/go/crypto/x509/verify.go @@ -135,8 +135,8 @@ func (c *Certificate) isValid(certType int, opts *VerifyOptions) error { // Verify attempts to verify c by building one or more chains from c to a // certificate in opts.roots, using certificates in opts.Intermediates if -// needed. If successful, it returns one or chains where the first element of -// the chain is c and the last element is from opts.Roots. +// needed. If successful, it returns one or more chains where the first +// element of the chain is c and the last element is from opts.Roots. // // WARNING: this doesn't do any revocation checking. func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) { diff --git a/libgo/go/crypto/x509/x509.go b/libgo/go/crypto/x509/x509.go index 3116525d33d..f5da86b54a0 100644 --- a/libgo/go/crypto/x509/x509.go +++ b/libgo/go/crypto/x509/x509.go @@ -153,7 +153,7 @@ const ( // // md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 } // -// md5WithRSAEncryption OBJECT IDENTIFER ::= { pkcs-1 4 } +// md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } // // sha-1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } // @@ -172,9 +172,9 @@ const ( // // RFC 5758 3.1 DSA Signature Algorithms // -// dsaWithSha356 OBJECT IDENTIFER ::= { +// dsaWithSha256 OBJECT IDENTIFIER ::= { // joint-iso-ccitt(2) country(16) us(840) organization(1) gov(101) -// algorithms(4) id-dsa-with-sha2(3) 2} +// csor(3) algorithms(4) id-dsa-with-sha2(3) 2} // var ( oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} diff --git a/libgo/go/encoding/binary/binary.go b/libgo/go/encoding/binary/binary.go index b26b1bb2c93..02f090d53f3 100644 --- a/libgo/go/encoding/binary/binary.go +++ b/libgo/go/encoding/binary/binary.go @@ -29,17 +29,13 @@ type ByteOrder interface { String() string } -// This is byte instead of struct{} so that it can be compared, -// allowing, e.g., order == binary.LittleEndian. -type unused byte - // LittleEndian is the little-endian implementation of ByteOrder. var LittleEndian littleEndian // BigEndian is the big-endian implementation of ByteOrder. var BigEndian bigEndian -type littleEndian unused +type littleEndian struct{} func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } @@ -79,7 +75,7 @@ func (littleEndian) String() string { return "LittleEndian" } func (littleEndian) GoString() string { return "binary.LittleEndian" } -type bigEndian unused +type bigEndian struct{} func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 } diff --git a/libgo/go/encoding/gob/codec_test.go b/libgo/go/encoding/gob/codec_test.go index d365f826345..ebcbb78ebe6 100644 --- a/libgo/go/encoding/gob/codec_test.go +++ b/libgo/go/encoding/gob/codec_test.go @@ -1455,11 +1455,14 @@ func TestFuzz(t *testing.T) { func TestFuzzRegressions(t *testing.T) { // An instance triggering a type name of length ~102 GB. testFuzz(t, 1328492090837718000, 100, new(float32)) + // An instance triggering a type name of 1.6 GB. + // Commented out because it takes 5m to run. + //testFuzz(t, 1330522872628565000, 100, new(int)) } func testFuzz(t *testing.T, seed int64, n int, input ...interface{}) { - t.Logf("seed=%d n=%d\n", seed, n) for _, e := range input { + t.Logf("seed=%d n=%d e=%T", seed, n, e) rng := rand.New(rand.NewSource(seed)) for i := 0; i < n; i++ { encFuzzDec(rng, e) diff --git a/libgo/go/encoding/gob/debug.go b/libgo/go/encoding/gob/debug.go index b54ef46f52c..31d1351fc4f 100644 --- a/libgo/go/encoding/gob/debug.go +++ b/libgo/go/encoding/gob/debug.go @@ -3,14 +3,15 @@ // license that can be found in the LICENSE file. // Delete the next line to include in the gob package. -// +build gob-debug +// +build ignore package gob // This file is not normally included in the gob package. Used only for debugging the package itself. -// Add debug.go to the files listed in the Makefile to add Debug to the gob package. // Except for reading uints, it is an implementation of a reader that is independent of // the one implemented by Decoder. +// To enable the Debug function, delete the +build ignore line above and do +// go install import ( "bytes" diff --git a/libgo/go/encoding/gob/decode.go b/libgo/go/encoding/gob/decode.go index a0bb985300f..0708a83c99a 100644 --- a/libgo/go/encoding/gob/decode.go +++ b/libgo/go/encoding/gob/decode.go @@ -392,12 +392,12 @@ func decUint8Slice(i *decInstr, state *decoderState, p unsafe.Pointer) { } p = *(*unsafe.Pointer)(p) } - n := int(state.decodeUint()) - if n < 0 { - errorf("negative length decoding []byte") + n := state.decodeUint() + if n > uint64(state.b.Len()) { + errorf("length of []byte exceeds input size (%d bytes)", n) } slice := (*[]uint8)(p) - if cap(*slice) < n { + if uint64(cap(*slice)) < n { *slice = make([]uint8, n) } else { *slice = (*slice)[0:n] @@ -417,7 +417,11 @@ func decString(i *decInstr, state *decoderState, p unsafe.Pointer) { } p = *(*unsafe.Pointer)(p) } - b := make([]byte, state.decodeUint()) + n := state.decodeUint() + if n > uint64(state.b.Len()) { + errorf("string length exceeds input size (%d bytes)", n) + } + b := make([]byte, n) state.b.Read(b) // It would be a shame to do the obvious thing here, // *(*string)(p) = string(b) @@ -647,7 +651,11 @@ func (dec *Decoder) ignoreMap(state *decoderState, keyOp, elemOp decOp) { // decodeSlice decodes a slice and stores the slice header through p. // Slices are encoded as an unsigned length followed by the elements. func (dec *Decoder) decodeSlice(atyp reflect.Type, state *decoderState, p uintptr, elemOp decOp, elemWid uintptr, indir, elemIndir int, ovfl error) { - n := int(uintptr(state.decodeUint())) + nr := state.decodeUint() + if nr > uint64(state.b.Len()) { + errorf("length of slice exceeds input size (%d elements)", nr) + } + n := int(nr) if indir > 0 { up := unsafe.Pointer(p) if *(*unsafe.Pointer)(up) == nil { @@ -702,6 +710,9 @@ func (dec *Decoder) decodeInterface(ityp reflect.Type, state *decoderState, p ui *(*[2]uintptr)(unsafe.Pointer(p)) = ivalue.InterfaceData() return } + if len(name) > 1024 { + errorf("name too long (%d bytes): %.20q...", len(name), name) + } // The concrete type must be registered. typ, ok := nameToConcreteType[name] if !ok { diff --git a/libgo/go/encoding/gob/dump.go b/libgo/go/encoding/gob/dump.go index e23a11e48bf..17238c98df0 100644 --- a/libgo/go/encoding/gob/dump.go +++ b/libgo/go/encoding/gob/dump.go @@ -7,6 +7,7 @@ package main // Need to compile package gob with debug.go to build this program. +// See comments in debug.go for how to do this. import ( "encoding/gob" diff --git a/libgo/go/encoding/gob/encoder_test.go b/libgo/go/encoding/gob/encoder_test.go index 3bfae30f39a..050786dfd1f 100644 --- a/libgo/go/encoding/gob/encoder_test.go +++ b/libgo/go/encoding/gob/encoder_test.go @@ -709,7 +709,7 @@ func TestGobPtrSlices(t *testing.T) { t.Fatal("decode:", err) } if !reflect.DeepEqual(in, out) { - t.Fatal("got %v; wanted %v", out, in) + t.Fatalf("got %v; wanted %v", out, in) } } diff --git a/libgo/go/encoding/json/decode_test.go b/libgo/go/encoding/json/decode_test.go index 0eec586a9bb..d758758d978 100644 --- a/libgo/go/encoding/json/decode_test.go +++ b/libgo/go/encoding/json/decode_test.go @@ -239,16 +239,6 @@ func TestEscape(t *testing.T) { } } -func TestHTMLEscape(t *testing.T) { - b, err := MarshalForHTML("foobarbaz<>&quux") - if err != nil { - t.Fatalf("MarshalForHTML error: %v", err) - } - if !bytes.Equal(b, []byte(`"foobarbaz\u003c\u003e\u0026quux"`)) { - t.Fatalf("Unexpected encoding of \"<>&\": %s", b) - } -} - // WrongString is a struct that's misusing the ,string modifier. type WrongString struct { Message string `json:"result,string"` diff --git a/libgo/go/encoding/json/encode.go b/libgo/go/encoding/json/encode.go index 8a794b79bd5..5425a3a90a1 100644 --- a/libgo/go/encoding/json/encode.go +++ b/libgo/go/encoding/json/encode.go @@ -123,17 +123,6 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { return buf.Bytes(), nil } -// MarshalForHTML is like Marshal but applies HTMLEscape to the output. -func MarshalForHTML(v interface{}) ([]byte, error) { - b, err := Marshal(v) - if err != nil { - return nil, err - } - var buf bytes.Buffer - HTMLEscape(&buf, b) - return buf.Bytes(), nil -} - // HTMLEscape appends to dst the JSON-encoded src with <, >, and & // characters inside string literals changed to \u003c, \u003e, \u0026 // so that the JSON will be safe to embed inside HTML <script> tags. @@ -200,11 +189,6 @@ func (e *MarshalerError) Error() string { return "json: error calling MarshalJSON for type " + e.Type.String() + ": " + e.Err.Error() } -type interfaceOrPtrValue interface { - IsNil() bool - Elem() reflect.Value -} - var hex = "0123456789abcdef" // An encodeState encodes JSON into a bytes.Buffer. @@ -276,7 +260,7 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) { b, err := m.MarshalJSON() if err == nil { // copy JSON into buffer, checking validity. - err = Compact(&e.Buffer, b) + err = compact(&e.Buffer, b, true) } if err != nil { e.error(&MarshalerError{v.Type(), err}) diff --git a/libgo/go/encoding/json/encode_test.go b/libgo/go/encoding/json/encode_test.go index 7a726a91c47..cb1c77eb529 100644 --- a/libgo/go/encoding/json/encode_test.go +++ b/libgo/go/encoding/json/encode_test.go @@ -167,3 +167,22 @@ func TestRefValMarshal(t *testing.T) { t.Errorf("got %q, want %q", got, want) } } + +// C implements Marshaler and returns unescaped JSON. +type C int + +func (C) MarshalJSON() ([]byte, error) { + return []byte(`"<&>"`), nil +} + +func TestMarshalerEscaping(t *testing.T) { + var c C + const want = `"\u003c\u0026\u003e"` + b, err := Marshal(c) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got := string(b); got != want { + t.Errorf("got %q, want %q", got, want) + } +} diff --git a/libgo/go/encoding/json/indent.go b/libgo/go/encoding/json/indent.go index 5ba19b07ac6..e8dfa4ec436 100644 --- a/libgo/go/encoding/json/indent.go +++ b/libgo/go/encoding/json/indent.go @@ -9,11 +9,24 @@ import "bytes" // Compact appends to dst the JSON-encoded src with // insignificant space characters elided. func Compact(dst *bytes.Buffer, src []byte) error { + return compact(dst, src, false) +} + +func compact(dst *bytes.Buffer, src []byte, escape bool) error { origLen := dst.Len() var scan scanner scan.reset() start := 0 for i, c := range src { + if escape && (c == '<' || c == '>' || c == '&') { + if start < i { + dst.Write(src[start:i]) + } + dst.WriteString(`\u00`) + dst.WriteByte(hex[c>>4]) + dst.WriteByte(hex[c&0xF]) + start = i + 1 + } v := scan.step(&scan, int(c)) if v >= scanSkipSpace { if v == scanError { diff --git a/libgo/go/encoding/xml/marshal_test.go b/libgo/go/encoding/xml/marshal_test.go index 9170fccd243..b6978a1e65b 100644 --- a/libgo/go/encoding/xml/marshal_test.go +++ b/libgo/go/encoding/xml/marshal_test.go @@ -136,12 +136,12 @@ type NamePrecedence struct { type XMLNameWithTag struct { XMLName Name `xml:"InXMLNameTag"` - Value string ",chardata" + Value string `xml:",chardata"` } type XMLNameWithoutTag struct { XMLName Name - Value string ",chardata" + Value string `xml:",chardata"` } type NameInField struct { @@ -532,9 +532,9 @@ var marshalTests = []struct { InFieldName: "D", }, ExpectXML: `<Parent>` + - `<InTag><Value>A</Value></InTag>` + - `<InXMLName><Value>B</Value></InXMLName>` + - `<InXMLNameTag><Value>C</Value></InXMLNameTag>` + + `<InTag>A</InTag>` + + `<InXMLName>B</InXMLName>` + + `<InXMLNameTag>C</InXMLNameTag>` + `<InFieldName>D</InFieldName>` + `</Parent>`, MarshalOnly: true, @@ -548,9 +548,9 @@ var marshalTests = []struct { InFieldName: "D", }, ExpectXML: `<Parent>` + - `<InTag><Value>A</Value></InTag>` + - `<FromNameVal><Value>B</Value></FromNameVal>` + - `<InXMLNameTag><Value>C</Value></InXMLNameTag>` + + `<InTag>A</InTag>` + + `<FromNameVal>B</FromNameVal>` + + `<InXMLNameTag>C</InXMLNameTag>` + `<InFieldName>D</InFieldName>` + `</Parent>`, UnmarshalOnly: true, diff --git a/libgo/go/exp/gotype/doc.go b/libgo/go/exp/gotype/doc.go index 1aa0faa751a..1168086771f 100644 --- a/libgo/go/exp/gotype/doc.go +++ b/libgo/go/exp/gotype/doc.go @@ -34,6 +34,8 @@ The flags are: Verbose mode. Debugging flags: + -comments + Parse comments (ignored if -ast not set). -ast Print AST (disables concurrent parsing). -trace diff --git a/libgo/go/exp/gotype/gotype.go b/libgo/go/exp/gotype/gotype.go index a2a9361866d..30eaf22fca6 100644 --- a/libgo/go/exp/gotype/gotype.go +++ b/libgo/go/exp/gotype/gotype.go @@ -27,8 +27,9 @@ var ( allErrors = flag.Bool("e", false, "print all (including spurious) errors") // debugging support - printTrace = flag.Bool("trace", false, "print parse trace") - printAST = flag.Bool("ast", false, "print AST") + parseComments = flag.Bool("comments", false, "parse comments (ignored if -ast not set)") + printTrace = flag.Bool("trace", false, "print parse trace") + printAST = flag.Bool("ast", false, "print AST") ) var exitCode = 0 @@ -73,6 +74,9 @@ func parse(fset *token.FileSet, filename string, src []byte) *ast.File { if *allErrors { mode |= parser.SpuriousErrors } + if *parseComments && *printAST { + mode |= parser.ParseComments + } if *printTrace { mode |= parser.Trace } diff --git a/libgo/go/exp/html/node.go b/libgo/go/exp/html/node.go index 83f17308b18..c105a4e709a 100644 --- a/libgo/go/exp/html/node.go +++ b/libgo/go/exp/html/node.go @@ -110,7 +110,7 @@ func (s *nodeStack) top() *Node { return nil } -// index returns the index of the top-most occurence of n in the stack, or -1 +// index returns the index of the top-most occurrence of n in the stack, or -1 // if n is not present. func (s *nodeStack) index(n *Node) int { for i := len(*s) - 1; i >= 0; i-- { diff --git a/libgo/go/exp/norm/forminfo.go b/libgo/go/exp/norm/forminfo.go index fd7b3956233..c443b78d825 100644 --- a/libgo/go/exp/norm/forminfo.go +++ b/libgo/go/exp/norm/forminfo.go @@ -18,17 +18,17 @@ package norm // has the form: // <header> <decomp_byte>* [<tccc> [<lccc>]] // The header contains the number of bytes in the decomposition (excluding this -// length byte). The two most significant bits of this lenght byte correspond +// length byte). The two most significant bits of this length byte correspond // to bit 2 and 3 of qcIfo (see below). The byte sequence itself starts at v+1. // The byte sequence is followed by a trailing and leading CCC if the values // for these are not zero. The value of v determines which ccc are appended // to the sequences. For v < firstCCC, there are none, for v >= firstCCC, -// the seqence is followed by a trailing ccc, and for v >= firstLeadingCC +// the sequence is followed by a trailing ccc, and for v >= firstLeadingCC // there is an additional leading ccc. const ( qcInfoMask = 0xF // to clear all but the relevant bits in a qcInfo - headerLenMask = 0x3F // extract the lenght value from the header byte + headerLenMask = 0x3F // extract the length value from the header byte headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte ) diff --git a/libgo/go/exp/proxy/per_host.go b/libgo/go/exp/proxy/per_host.go index 397ef57cd92..0c627e9ab54 100644 --- a/libgo/go/exp/proxy/per_host.go +++ b/libgo/go/exp/proxy/per_host.go @@ -75,7 +75,7 @@ func (p *PerHost) dialerForRequest(host string) Dialer { } // AddFromString parses a string that contains comma-separated values -// specifing hosts that should use the bypass proxy. Each value is either an +// specifying hosts that should use the bypass proxy. Each value is either an // IP address, a CIDR range, a zone (*.example.com) or a hostname // (localhost). A best effort is made to parse the string and errors are // ignored. diff --git a/libgo/go/exp/types/gcimporter.go b/libgo/go/exp/types/gcimporter.go index 8b28aede1ec..cb996f28055 100644 --- a/libgo/go/exp/types/gcimporter.go +++ b/libgo/go/exp/types/gcimporter.go @@ -18,6 +18,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "text/scanner" ) @@ -39,11 +40,14 @@ func findPkg(path string) (filename, id string) { switch path[0] { default: // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" - tree, pkg, err := build.FindTree(path) - if err != nil { + bp, _ := build.Import(path, "", build.FindOnly) + if bp.PkgObj == "" { return } - noext = filepath.Join(tree.PkgDir(), pkg) + noext = bp.PkgObj + if strings.HasSuffix(noext, ".a") { + noext = noext[:len(noext)-2] + } case '.': // "./x" -> "/this/directory/x.ext", "/this/directory/x" @@ -742,7 +746,7 @@ func (p *gcParser) parseVarDecl() { } // FuncBody = "{" ... "}" . -// +// func (p *gcParser) parseFuncBody() { p.expect('{') for i := 1; i > 0; p.next() { diff --git a/libgo/go/exp/types/types.go b/libgo/go/exp/types/types.go index 3aa896892e3..85d244cf04a 100644 --- a/libgo/go/exp/types/types.go +++ b/libgo/go/exp/types/types.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// PACKAGE UNDER CONSTRUCTION. ANY AND ALL PARTS MAY CHANGE. -// Package types declares the types used to represent Go types. +// Package types declares the types used to represent Go types +// (UNDER CONSTRUCTION). ANY AND ALL PARTS MAY CHANGE. // package types diff --git a/libgo/go/go/ast/print.go b/libgo/go/go/ast/print.go index f6c63c0d889..02cf9e02234 100644 --- a/libgo/go/go/ast/print.go +++ b/libgo/go/go/ast/print.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. -// This file contains printing suppport for ASTs. +// This file contains printing support for ASTs. package ast diff --git a/libgo/go/go/build/build.go b/libgo/go/go/build/build.go index 68e8d342005..eece7610562 100644 --- a/libgo/go/go/build/build.go +++ b/libgo/go/go/build/build.go @@ -2,10 +2,948 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package build provides tools for building Go packages. package build -import "errors" +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/doc" + "go/parser" + "go/token" + "io" + "io/ioutil" + "log" + "os" + pathpkg "path" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "unicode" +) + +// A Context specifies the supporting context for a build. +type Context struct { + GOARCH string // target architecture + GOOS string // target operating system + GOROOT string // Go root + GOPATH string // Go path + CgoEnabled bool // whether cgo can be used + BuildTags []string // additional tags to recognize in +build lines + UseAllFiles bool // use files regardless of +build lines, file names + Gccgo bool // assume use of gccgo when computing object paths + + // By default, Import uses the operating system's file system calls + // to read directories and files. To read from other sources, + // callers can set the following functions. They all have default + // behaviors that use the local file system, so clients need only set + // the functions whose behaviors they wish to change. + + // JoinPath joins the sequence of path fragments into a single path. + // If JoinPath is nil, Import uses filepath.Join. + JoinPath func(elem ...string) string + + // SplitPathList splits the path list into a slice of individual paths. + // If SplitPathList is nil, Import uses filepath.SplitList. + SplitPathList func(list string) []string + + // IsAbsPath reports whether path is an absolute path. + // If IsAbsPath is nil, Import uses filepath.IsAbs. + IsAbsPath func(path string) bool + + // IsDir reports whether the path names a directory. + // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. + IsDir func(path string) bool + + // HasSubdir reports whether dir is a subdirectory of + // (perhaps multiple levels below) root. + // If so, HasSubdir sets rel to a slash-separated path that + // can be joined to root to produce a path equivalent to dir. + // If HasSubdir is nil, Import uses an implementation built on + // filepath.EvalSymlinks. + HasSubdir func(root, dir string) (rel string, ok bool) + + // ReadDir returns a slice of os.FileInfo, sorted by Name, + // describing the content of the named directory. + // If ReadDir is nil, Import uses io.ReadDir. + ReadDir func(dir string) (fi []os.FileInfo, err error) + + // OpenFile opens a file (not a directory) for reading. + // If OpenFile is nil, Import uses os.Open. + OpenFile func(path string) (r io.ReadCloser, err error) +} + +// joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join. +func (ctxt *Context) joinPath(elem ...string) string { + if f := ctxt.JoinPath; f != nil { + return f(elem...) + } + return filepath.Join(elem...) +} + +// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList. +func (ctxt *Context) splitPathList(s string) []string { + if f := ctxt.SplitPathList; f != nil { + return f(s) + } + return filepath.SplitList(s) +} + +// isAbsPath calls ctxt.IsAbsSPath (if not nil) or else filepath.IsAbs. +func (ctxt *Context) isAbsPath(path string) bool { + if f := ctxt.IsAbsPath; f != nil { + return f(path) + } + return filepath.IsAbs(path) +} + +// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat. +func (ctxt *Context) isDir(path string) bool { + if f := ctxt.IsDir; f != nil { + return f(path) + } + fi, err := os.Stat(path) + return err == nil && fi.IsDir() +} + +// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses +// the local file system to answer the question. +func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) { + if f := ctxt.HasSubdir; f != nil { + return f(root, dir) + } + + if p, err := filepath.EvalSymlinks(root); err == nil { + root = p + } + if p, err := filepath.EvalSymlinks(dir); err == nil { + dir = p + } + const sep = string(filepath.Separator) + root = filepath.Clean(root) + if !strings.HasSuffix(root, sep) { + root += sep + } + dir = filepath.Clean(dir) + if !strings.HasPrefix(dir, root) { + return "", false + } + return filepath.ToSlash(dir[len(root):]), true +} + +// readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir. +func (ctxt *Context) readDir(path string) ([]os.FileInfo, error) { + if f := ctxt.ReadDir; f != nil { + return f(path) + } + return ioutil.ReadDir(path) +} + +// openFile calls ctxt.OpenFile (if not nil) or else os.Open. +func (ctxt *Context) openFile(path string) (io.ReadCloser, error) { + if fn := ctxt.OpenFile; fn != nil { + return fn(path) + } + + f, err := os.Open(path) + if err != nil { + return nil, err // nil interface + } + return f, nil +} + +// isFile determines whether path is a file by trying to open it. +// It reuses openFile instead of adding another function to the +// list in Context. +func (ctxt *Context) isFile(path string) bool { + f, err := ctxt.openFile(path) + if err != nil { + return false + } + f.Close() + return true +} + +// gopath returns the list of Go path directories. +func (ctxt *Context) gopath() []string { + var all []string + for _, p := range ctxt.splitPathList(ctxt.GOPATH) { + if p == "" || p == ctxt.GOROOT { + // Empty paths are uninteresting. + // If the path is the GOROOT, ignore it. + // People sometimes set GOPATH=$GOROOT, which is useless + // but would cause us to find packages with import paths + // like "pkg/math". + // Do not get confused by this common mistake. + continue + } + all = append(all, p) + } + return all +} + +// SrcDirs returns a list of package source root directories. +// It draws from the current Go root and Go path but omits directories +// that do not exist. +func (ctxt *Context) SrcDirs() []string { + var all []string + if ctxt.GOROOT != "" { + dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg") + if ctxt.isDir(dir) { + all = append(all, dir) + } + } + for _, p := range ctxt.gopath() { + dir := ctxt.joinPath(p, "src") + if ctxt.isDir(dir) { + all = append(all, dir) + } + } + return all +} + +// Default is the default Context for builds. +// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables +// if set, or else the compiled code's GOARCH, GOOS, and GOROOT. +var Default Context = defaultContext() + +var cgoEnabled = map[string]bool{ + "darwin/386": true, + "darwin/amd64": true, + "linux/386": true, + "linux/amd64": true, + "freebsd/386": true, + "freebsd/amd64": true, + "windows/386": true, + "windows/amd64": true, +} + +func defaultContext() Context { + var c Context + + c.GOARCH = envOr("GOARCH", runtime.GOARCH) + c.GOOS = envOr("GOOS", runtime.GOOS) + c.GOROOT = runtime.GOROOT() + c.GOPATH = envOr("GOPATH", "") + + switch os.Getenv("CGO_ENABLED") { + case "1": + c.CgoEnabled = true + case "0": + c.CgoEnabled = false + default: + c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH] + } + + return c +} + +func envOr(name, def string) string { + s := os.Getenv(name) + if s == "" { + return def + } + return s +} + +// An ImportMode controls the behavior of the Import method. +type ImportMode uint + +const ( + // If FindOnly is set, Import stops after locating the directory + // that should contain the sources for a package. It does not + // read any files in the directory. + FindOnly ImportMode = 1 << iota + + // If AllowBinary is set, Import can be satisfied by a compiled + // package object without corresponding sources. + AllowBinary +) + +// A Package describes the Go package found in a directory. +type Package struct { + Dir string // directory containing package sources + Name string // package name + Doc string // documentation synopsis + ImportPath string // import path of package ("" if unknown) + Root string // root of Go tree where this package lives + SrcRoot string // package source root directory ("" if unknown) + PkgRoot string // package install root directory ("" if unknown) + BinDir string // command install directory ("" if unknown) + Goroot bool // package found in Go root + PkgObj string // installed .a file + + // Source files + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) + CgoFiles []string // .go source files that import "C" + CFiles []string // .c source files + HFiles []string // .h source files + SFiles []string // .s source files + + // Cgo directives + CgoPkgConfig []string // Cgo pkg-config directives + CgoCFLAGS []string // Cgo CFLAGS directives + CgoLDFLAGS []string // Cgo LDFLAGS directives + + // Dependency information + Imports []string // imports from GoFiles, CgoFiles + ImportPos map[string][]token.Position // line information for Imports + + // Test information + TestGoFiles []string // _test.go files in package + TestImports []string // imports from TestGoFiles + TestImportPos map[string][]token.Position // line information for TestImports + XTestGoFiles []string // _test.go files outside package + XTestImports []string // imports from XTestGoFiles + XTestImportPos map[string][]token.Position // line information for XTestImports +} + +// IsCommand reports whether the package is considered a +// command to be installed (not just a library). +// Packages named "main" are treated as commands. +func (p *Package) IsCommand() bool { + return p.Name == "main" +} + +// ImportDir is like Import but processes the Go package found in +// the named directory. +func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { + return ctxt.Import(".", dir, mode) +} + +// Import returns details about the Go package named by the import path, +// interpreting local import paths relative to the src directory. If the path +// is a local import path naming a package that can be imported using a +// standard import path, the returned package will set p.ImportPath to +// that path. +// +// In the directory containing the package, .go, .c, .h, and .s files are +// considered part of the package except for: +// +// - .go files in package documentation +// - files starting with _ or . +// - files with build constraints not satisfied by the context +// +// If an error occurs, Import returns a non-nil error also returns a non-nil +// *Package containing partial information. +// +func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error) { + p := &Package{ + ImportPath: path, + } + + var pkga string + if ctxt.Gccgo { + dir, elem := pathpkg.Split(p.ImportPath) + pkga = "pkg/gccgo/" + dir + "lib" + elem + ".a" + } else { + pkga = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + "/" + p.ImportPath + ".a" + } + + binaryOnly := false + if IsLocalImport(path) { + if src == "" { + return p, fmt.Errorf("import %q: import relative to unknown directory", path) + } + if !ctxt.isAbsPath(path) { + p.Dir = ctxt.joinPath(src, path) + } + // Determine canonical import path, if any. + if ctxt.GOROOT != "" { + root := ctxt.joinPath(ctxt.GOROOT, "src", "pkg") + if sub, ok := ctxt.hasSubdir(root, p.Dir); ok { + p.Goroot = true + p.ImportPath = sub + p.Root = ctxt.GOROOT + goto Found + } + } + all := ctxt.gopath() + for i, root := range all { + rootsrc := ctxt.joinPath(root, "src") + if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok { + // We found a potential import path for dir, + // but check that using it wouldn't find something + // else first. + if ctxt.GOROOT != "" { + if dir := ctxt.joinPath(ctxt.GOROOT, "src", sub); ctxt.isDir(dir) { + goto Found + } + } + for _, earlyRoot := range all[:i] { + if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) { + goto Found + } + } + + // sub would not name some other directory instead of this one. + // Record it. + p.ImportPath = sub + p.Root = root + goto Found + } + } + // It's okay that we didn't find a root containing dir. + // Keep going with the information we have. + } else { + if strings.HasPrefix(path, "/") { + return p, fmt.Errorf("import %q: cannot import absolute path", path) + } + // Determine directory from import path. + if ctxt.GOROOT != "" { + dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", path) + isDir := ctxt.isDir(dir) + binaryOnly = !isDir && mode&AllowBinary != 0 && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) + if isDir || binaryOnly { + p.Dir = dir + p.Goroot = true + p.Root = ctxt.GOROOT + goto Found + } + } + for _, root := range ctxt.gopath() { + dir := ctxt.joinPath(root, "src", path) + isDir := ctxt.isDir(dir) + binaryOnly = !isDir && mode&AllowBinary != 0 && ctxt.isFile(ctxt.joinPath(root, pkga)) + if isDir || binaryOnly { + p.Dir = dir + p.Root = root + goto Found + } + } + return p, fmt.Errorf("import %q: cannot find package", path) + } + +Found: + if p.Root != "" { + if p.Goroot { + p.SrcRoot = ctxt.joinPath(p.Root, "src", "pkg") + } else { + p.SrcRoot = ctxt.joinPath(p.Root, "src") + } + p.PkgRoot = ctxt.joinPath(p.Root, "pkg") + p.BinDir = ctxt.joinPath(p.Root, "bin") + p.PkgObj = ctxt.joinPath(p.Root, pkga) + } + + if mode&FindOnly != 0 { + return p, nil + } + if binaryOnly && (mode&AllowBinary) != 0 { + return p, nil + } + + dirs, err := ctxt.readDir(p.Dir) + if err != nil { + return p, err + } + + var Sfiles []string // files with ".S" (capital S) + var firstFile string + imported := make(map[string][]token.Position) + testImported := make(map[string][]token.Position) + xTestImported := make(map[string][]token.Position) + fset := token.NewFileSet() + for _, d := range dirs { + if d.IsDir() { + continue + } + name := d.Name() + if strings.HasPrefix(name, "_") || + strings.HasPrefix(name, ".") { + continue + } + if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) { + continue + } + + i := strings.LastIndex(name, ".") + if i < 0 { + i = len(name) + } + ext := name[i:] + switch ext { + case ".go", ".c", ".s", ".h", ".S": + // tentatively okay + default: + // skip + continue + } + + filename := ctxt.joinPath(p.Dir, name) + f, err := ctxt.openFile(filename) + if err != nil { + return p, err + } + data, err := ioutil.ReadAll(f) + f.Close() + if err != nil { + return p, fmt.Errorf("read %s: %v", filename, err) + } + + // Look for +build comments to accept or reject the file. + if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) { + continue + } + + // Going to save the file. For non-Go files, can stop here. + switch ext { + case ".c": + p.CFiles = append(p.CFiles, name) + continue + case ".h": + p.HFiles = append(p.HFiles, name) + continue + case ".s": + p.SFiles = append(p.SFiles, name) + continue + case ".S": + Sfiles = append(Sfiles, name) + continue + } + + pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments) + if err != nil { + return p, err + } + + pkg := string(pf.Name.Name) + if pkg == "documentation" { + continue + } + + isTest := strings.HasSuffix(name, "_test.go") + isXTest := false + if isTest && strings.HasSuffix(pkg, "_test") { + isXTest = true + pkg = pkg[:len(pkg)-len("_test")] + } + + if p.Name == "" { + p.Name = pkg + firstFile = name + } else if pkg != p.Name { + return p, fmt.Errorf("found packages %s (%s) and %s (%s) in %s", p.Name, firstFile, pkg, name, p.Dir) + } + if pf.Doc != nil && p.Doc == "" { + p.Doc = doc.Synopsis(pf.Doc.Text()) + } + + // Record imports and information about cgo. + isCgo := false + for _, decl := range pf.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, dspec := range d.Specs { + spec, ok := dspec.(*ast.ImportSpec) + if !ok { + continue + } + quoted := string(spec.Path.Value) + path, err := strconv.Unquote(quoted) + if err != nil { + log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted) + } + if isXTest { + xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos())) + } else if isTest { + testImported[path] = append(testImported[path], fset.Position(spec.Pos())) + } else { + imported[path] = append(imported[path], fset.Position(spec.Pos())) + } + if path == "C" { + if isTest { + return p, fmt.Errorf("use of cgo in test %s not supported", filename) + } + cg := spec.Doc + if cg == nil && len(d.Specs) == 1 { + cg = d.Doc + } + if cg != nil { + if err := ctxt.saveCgo(filename, p, cg); err != nil { + return p, err + } + } + isCgo = true + } + } + } + if isCgo { + if ctxt.CgoEnabled { + p.CgoFiles = append(p.CgoFiles, name) + } + } else if isXTest { + p.XTestGoFiles = append(p.XTestGoFiles, name) + } else if isTest { + p.TestGoFiles = append(p.TestGoFiles, name) + } else { + p.GoFiles = append(p.GoFiles, name) + } + } + if p.Name == "" { + return p, fmt.Errorf("no Go source files in %s", p.Dir) + } + + p.Imports, p.ImportPos = cleanImports(imported) + p.TestImports, p.TestImportPos = cleanImports(testImported) + p.XTestImports, p.XTestImportPos = cleanImports(xTestImported) + + // add the .S files only if we are using cgo + // (which means gcc will compile them). + // The standard assemblers expect .s files. + if len(p.CgoFiles) > 0 { + p.SFiles = append(p.SFiles, Sfiles...) + sort.Strings(p.SFiles) + } + + return p, nil +} + +func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) { + all := make([]string, 0, len(m)) + for path := range m { + all = append(all, path) + } + sort.Strings(all) + return all, m +} + +// Import is shorthand for Default.Import. +func Import(path, src string, mode ImportMode) (*Package, error) { + return Default.Import(path, src, mode) +} + +// ImportDir is shorthand for Default.ImportDir. +func ImportDir(dir string, mode ImportMode) (*Package, error) { + return Default.ImportDir(dir, mode) +} + +var slashslash = []byte("//") + +// shouldBuild reports whether it is okay to use this file, +// The rule is that in the file's leading run of // comments +// and blank lines, which must be followed by a blank line +// (to avoid including a Go package clause doc comment), +// lines beginning with '// +build' are taken as build directives. +// +// The file is accepted only if each such line lists something +// matching the file. For example: +// +// // +build windows linux +// +// marks the file as applicable only on Windows and Linux. +// +func (ctxt *Context) shouldBuild(content []byte) bool { + // Pass 1. Identify leading run of // comments and blank lines, + // which must be followed by a blank line. + end := 0 + p := content + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if len(line) == 0 { // Blank line + end = cap(content) - cap(line) // &line[0] - &content[0] + continue + } + if !bytes.HasPrefix(line, slashslash) { // Not comment line + break + } + } + content = content[:end] + + // Pass 2. Process each line in the run. + p = content + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if bytes.HasPrefix(line, slashslash) { + line = bytes.TrimSpace(line[len(slashslash):]) + if len(line) > 0 && line[0] == '+' { + // Looks like a comment +line. + f := strings.Fields(string(line)) + if f[0] == "+build" { + ok := false + for _, tok := range f[1:] { + if ctxt.match(tok) { + ok = true + break + } + } + if !ok { + return false // this one doesn't match + } + } + } + } + } + return true // everything matches +} + +// saveCgo saves the information from the #cgo lines in the import "C" comment. +// These lines set CFLAGS and LDFLAGS and pkg-config directives that affect +// the way cgo's C code is built. +// +// TODO(rsc): This duplicates code in cgo. +// Once the dust settles, remove this code from cgo. +func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error { + text := cg.Text() + for _, line := range strings.Split(text, "\n") { + orig := line + + // Line is + // #cgo [GOOS/GOARCH...] LDFLAGS: stuff + // + line = strings.TrimSpace(line) + if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { + continue + } + + // Split at colon. + line = strings.TrimSpace(line[4:]) + i := strings.Index(line, ":") + if i < 0 { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + line, argstr := line[:i], line[i+1:] + + // Parse GOOS/GOARCH stuff. + f := strings.Fields(line) + if len(f) < 1 { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + + cond, verb := f[:len(f)-1], f[len(f)-1] + if len(cond) > 0 { + ok := false + for _, c := range cond { + if ctxt.match(c) { + ok = true + break + } + } + if !ok { + continue + } + } + + args, err := splitQuoted(argstr) + if err != nil { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + for _, arg := range args { + if !safeName(arg) { + return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) + } + } + + switch verb { + case "CFLAGS": + di.CgoCFLAGS = append(di.CgoCFLAGS, args...) + case "LDFLAGS": + di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) + case "pkg-config": + di.CgoPkgConfig = append(di.CgoPkgConfig, args...) + default: + return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig) + } + } + return nil +} + +var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:") + +func safeName(s string) bool { + if s == "" { + return false + } + for i := 0; i < len(s); i++ { + if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 { + return false + } + } + return true +} + +// splitQuoted splits the string s around each instance of one or more consecutive +// white space characters while taking into account quotes and escaping, and +// returns an array of substrings of s or an empty list if s contains only white space. +// Single quotes and double quotes are recognized to prevent splitting within the +// quoted region, and are removed from the resulting substrings. If a quote in s +// isn't closed err will be set and r will have the unclosed argument as the +// last element. The backslash is used for escaping. +// +// For example, the following string: +// +// a b:"c d" 'e''f' "g\"" +// +// Would be parsed as: +// +// []string{"a", "b:c d", "ef", `g"`} +// +func splitQuoted(s string) (r []string, err error) { + var args []string + arg := make([]rune, len(s)) + escaped := false + quoted := false + quote := '\x00' + i := 0 + for _, rune := range s { + switch { + case escaped: + escaped = false + case rune == '\\': + escaped = true + continue + case quote != '\x00': + if rune == quote { + quote = '\x00' + continue + } + case rune == '"' || rune == '\'': + quoted = true + quote = rune + continue + case unicode.IsSpace(rune): + if quoted || i > 0 { + quoted = false + args = append(args, string(arg[:i])) + i = 0 + } + continue + } + arg[i] = rune + i++ + } + if quoted || i > 0 { + args = append(args, string(arg[:i])) + } + if quote != 0 { + err = errors.New("unclosed quote") + } else if escaped { + err = errors.New("unfinished escaping") + } + return args, err +} + +// match returns true if the name is one of: +// +// $GOOS +// $GOARCH +// cgo (if cgo is enabled) +// !cgo (if cgo is disabled) +// tag (if tag is listed in ctxt.BuildTags) +// !tag (if tag is not listed in ctxt.BuildTags) +// a slash-separated list of any of these +// +func (ctxt *Context) match(name string) bool { + if name == "" { + return false + } + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return !ctxt.match(name[1:]) + } + + // Tags must be letters, digits, underscores. + // Unlike in Go identifiers, all digits is fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { + return false + } + } + + // special tags + if ctxt.CgoEnabled && name == "cgo" { + return true + } + if name == ctxt.GOOS || name == ctxt.GOARCH { + return true + } + + // other tags + for _, tag := range ctxt.BuildTags { + if tag == name { + return true + } + } + + return false +} + +// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH +// suffix which does not match the current system. +// The recognized name formats are: +// +// name_$(GOOS).* +// name_$(GOARCH).* +// name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* +// +func (ctxt *Context) goodOSArchFile(name string) bool { + if dot := strings.Index(name, "."); dot != -1 { + name = name[:dot] + } + l := strings.Split(name, "_") + if n := len(l); n > 0 && l[n-1] == "test" { + l = l[:n-1] + } + n := len(l) + if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { + return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH + } + if n >= 1 && knownOS[l[n-1]] { + return l[n-1] == ctxt.GOOS + } + if n >= 1 && knownArch[l[n-1]] { + return l[n-1] == ctxt.GOARCH + } + return true +} + +var knownOS = make(map[string]bool) +var knownArch = make(map[string]bool) + +func init() { + for _, v := range strings.Fields(goosList) { + knownOS[v] = true + } + for _, v := range strings.Fields(goarchList) { + knownArch[v] = true + } +} + +// ToolDir is the directory containing build tools. +var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) + +// IsLocalImport reports whether the import path is +// a local import path, like ".", "..", "./foo", or "../foo". +func IsLocalImport(path string) bool { + return path == "." || path == ".." || + strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") +} // ArchChar returns the architecture character for the given goarch. // For example, ArchChar("amd64") returns "6". diff --git a/libgo/go/go/build/build_test.go b/libgo/go/go/build/build_test.go index 3c706a46edd..06b8b0e94f8 100644 --- a/libgo/go/go/build/build_test.go +++ b/libgo/go/go/build/build_test.go @@ -5,83 +5,14 @@ package build import ( + "os" "path/filepath" - "reflect" "runtime" - "sort" "testing" ) -func sortstr(x []string) []string { - sort.Strings(x) - return x -} - -var buildPkgs = []struct { - dir string - info *DirInfo -}{ - { - "go/build/pkgtest", - &DirInfo{ - GoFiles: []string{"pkgtest.go"}, - SFiles: []string{"sqrt_" + runtime.GOARCH + ".s"}, - Package: "pkgtest", - Imports: []string{"bytes"}, - TestImports: []string{"fmt", "pkgtest"}, - TestGoFiles: sortstr([]string{"sqrt_test.go", "sqrt_" + runtime.GOARCH + "_test.go"}), - XTestGoFiles: []string{"xsqrt_test.go"}, - }, - }, - { - "go/build/cmdtest", - &DirInfo{ - GoFiles: []string{"main.go"}, - Package: "main", - Imports: []string{"go/build/pkgtest"}, - TestImports: []string{}, - }, - }, - { - "go/build/cgotest", - &DirInfo{ - CgoFiles: ifCgo([]string{"cgotest.go"}), - CFiles: []string{"cgotest.c"}, - HFiles: []string{"cgotest.h"}, - Imports: []string{"C", "unsafe"}, - TestImports: []string{}, - Package: "cgotest", - }, - }, -} - -func ifCgo(x []string) []string { - if DefaultContext.CgoEnabled { - return x - } - return nil -} - -func TestBuild(t *testing.T) { - for _, tt := range buildPkgs { - tree := Path[0] // Goroot - dir := filepath.Join(tree.SrcDir(), tt.dir) - info, err := ScanDir(dir) - if err != nil { - t.Errorf("ScanDir(%#q): %v", tt.dir, err) - continue - } - // Don't bother testing import positions. - tt.info.ImportPos, tt.info.TestImportPos = info.ImportPos, info.TestImportPos - if !reflect.DeepEqual(info, tt.info) { - t.Errorf("ScanDir(%#q) = %#v, want %#v\n", tt.dir, info, tt.info) - continue - } - } -} - func TestMatch(t *testing.T) { - ctxt := DefaultContext + ctxt := Default what := "default" match := func(tag string) { if !ctxt.match(tag) { @@ -106,3 +37,40 @@ func TestMatch(t *testing.T) { match(runtime.GOOS + "," + runtime.GOARCH + ",!bar") nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar") } + +func TestDotSlashImport(t *testing.T) { + p, err := ImportDir("testdata/other", 0) + if err != nil { + t.Fatal(err) + } + if len(p.Imports) != 1 || p.Imports[0] != "./file" { + t.Fatalf("testdata/other: Imports=%v, want [./file]", p.Imports) + } + + p1, err := Import("./file", "testdata/other", 0) + if err != nil { + t.Fatal(err) + } + if p1.Name != "file" { + t.Fatalf("./file: Name=%q, want %q", p1.Name, "file") + } + dir := filepath.Clean("testdata/other/file") // Clean to use \ on Windows + if p1.Dir != dir { + t.Fatalf("./file: Dir=%q, want %q", p1.Name, dir) + } +} + +func TestLocalDirectory(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + p, err := ImportDir(cwd, 0) + if err != nil { + t.Fatal(err) + } + if p.ImportPath != "go/build" { + t.Fatalf("ImportPath=%q, want %q", p.ImportPath, "go/build") + } +} diff --git a/libgo/go/go/build/cgotest/cgotest.go b/libgo/go/go/build/cgotest/cgotest.go deleted file mode 100644 index 93bbf06883f..00000000000 --- a/libgo/go/go/build/cgotest/cgotest.go +++ /dev/null @@ -1,19 +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 cgotest - -/* -char* greeting = "hello, world"; -*/ -// #include "cgotest.h" -import "C" -import "unsafe" - -var Greeting = C.GoString(C.greeting) - -func DoAdd(x, y int) (sum int) { - C.Add(C.int(x), C.int(y), (*C.int)(unsafe.Pointer(&sum))) - return -} diff --git a/libgo/go/go/build/cmdtest/main.go b/libgo/go/go/build/cmdtest/main.go deleted file mode 100644 index bed4f485a0a..00000000000 --- a/libgo/go/go/build/cmdtest/main.go +++ /dev/null @@ -1,12 +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 main - -import "go/build/pkgtest" - -func main() { - pkgtest.Foo() - print(int(pkgtest.Sqrt(9))) -} diff --git a/libgo/go/go/build/dir.go b/libgo/go/go/build/dir.go deleted file mode 100644 index 6b30f76265b..00000000000 --- a/libgo/go/go/build/dir.go +++ /dev/null @@ -1,705 +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 build - -import ( - "bytes" - "errors" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/ioutil" - "log" - "os" - "path" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "unicode" -) - -// A Context specifies the supporting context for a build. -type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - CgoEnabled bool // whether cgo can be used - BuildTags []string // additional tags to recognize in +build lines - UseAllFiles bool // use files regardless of +build lines, file names - - // By default, ScanDir uses the operating system's - // file system calls to read directories and files. - // Callers can override those calls to provide other - // ways to read data by setting ReadDir and ReadFile. - // ScanDir does not make any assumptions about the - // 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, - // 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) - - // 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. - // The returned path is the full name of the file, to be - // used in error messages. - // - // If ReadFile is nil, ScanDir uses filepath.Join(dir, file) - // as the path and ioutil.ReadFile to read the data. - ReadFile func(dir, file string) (path string, content []byte, err error) -} - -func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) { - if f := ctxt.ReadDir; f != nil { - return f(dir) - } - return ioutil.ReadDir(dir) -} - -func (ctxt *Context) readFile(dir, file string) (string, []byte, error) { - if f := ctxt.ReadFile; f != nil { - return f(dir, file) - } - p := filepath.Join(dir, file) - content, err := ioutil.ReadFile(p) - return p, content, err -} - -// The DefaultContext is the default Context for builds. -// It uses the GOARCH and GOOS environment variables -// if set, or else the compiled code's GOARCH and GOOS. -var DefaultContext Context = defaultContext() - -var cgoEnabled = map[string]bool{ - "darwin/386": true, - "darwin/amd64": true, - "linux/386": true, - "linux/amd64": true, - "freebsd/386": true, - "freebsd/amd64": true, - "windows/386": true, - "windows/amd64": true, -} - -func defaultContext() Context { - var c Context - - c.GOARCH = envOr("GOARCH", runtime.GOARCH) - c.GOOS = envOr("GOOS", runtime.GOOS) - - s := os.Getenv("CGO_ENABLED") - switch s { - case "1": - c.CgoEnabled = true - case "0": - c.CgoEnabled = false - default: - c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH] - } - - return c -} - -func envOr(name, def string) string { - s := os.Getenv(name) - if s == "" { - return def - } - return s -} - -type DirInfo struct { - Package string // Name of package in dir - PackageComment *ast.CommentGroup // Package comments from GoFiles - ImportPath string // Import path of package in dir - Imports []string // All packages imported by GoFiles - ImportPos map[string][]token.Position // Source code location of imports - - // Source files - GoFiles []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles) - HFiles []string // .h files in dir - CFiles []string // .c files in dir - SFiles []string // .s (and, when using cgo, .S files in dir) - CgoFiles []string // .go files that import "C" - - // Cgo directives - CgoPkgConfig []string // Cgo pkg-config directives - CgoCFLAGS []string // Cgo CFLAGS directives - CgoLDFLAGS []string // Cgo LDFLAGS directives - - // Test information - TestGoFiles []string // _test.go files in package - XTestGoFiles []string // _test.go files outside package - TestImports []string // All packages imported by (X)TestGoFiles - TestImportPos map[string][]token.Position -} - -func (d *DirInfo) IsCommand() bool { - // TODO(rsc): This is at least a little bogus. - return d.Package == "main" -} - -// ScanDir calls DefaultContext.ScanDir. -func ScanDir(dir string) (info *DirInfo, err error) { - return DefaultContext.ScanDir(dir) -} - -// TODO(rsc): Move this comment to a more appropriate place. - -// ScanDir returns a structure with details about the Go package -// found in the given directory. -// -// Most .go, .c, .h, and .s files in the directory are considered part -// of the package. The exceptions are: -// -// - .go files in package main (unless no other package is found) -// - .go files in package documentation -// - files starting with _ or . -// - files with build constraints not satisfied by the context -// -// Build Constraints -// -// A build constraint is a line comment beginning with the directive +build -// that lists the conditions under which a file should be included in the package. -// Constraints may appear in any kind of source file (not just Go), but -// they must be appear near the top of the file, preceded -// only by blank lines and other line comments. -// -// A build constraint is evaluated as the OR of space-separated options; -// each option evaluates as the AND of its comma-separated terms; -// and each term is an alphanumeric word or, preceded by !, its negation. -// That is, the build constraint: -// -// // +build linux,386 darwin,!cgo -// -// corresponds to the boolean formula: -// -// (linux AND 386) OR (darwin AND (NOT cgo)) -// -// During a particular build, the following words are satisfied: -// -// - the target operating system, as spelled by runtime.GOOS -// - the target architecture, as spelled by runtime.GOARCH -// - "cgo", if ctxt.CgoEnabled is true -// - any additional words listed in ctxt.BuildTags -// -// If a file's name, after stripping the extension and a possible _test suffix, -// matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating -// system and architecture values, then the file is considered to have an implicit -// build constraint requiring those terms. -// -// Examples -// -// To keep a file from being considered for the build: -// -// // +build ignore -// -// (any other unsatisfied word will work as well, but ``ignore'' is conventional.) -// -// To build a file only when using cgo, and only on Linux and OS X: -// -// // +build linux,cgo darwin,cgo -// -// Such a file is usually paired with another file implementing the -// default functionality for other systems, which in this case would -// carry the constraint: -// -// // +build !linux !darwin !cgo -// -// Naming a file dns_windows.go will cause it to be included only when -// building the package for Windows; similarly, math_386.s will be included -// only when building the package for 32-bit x86. -// -func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { - dirs, err := ctxt.readDir(dir) - if err != nil { - return nil, err - } - - var Sfiles []string // files with ".S" (capital S) - var di DirInfo - var firstFile string - imported := make(map[string][]token.Position) - testImported := make(map[string][]token.Position) - fset := token.NewFileSet() - for _, d := range dirs { - if d.IsDir() { - continue - } - name := d.Name() - if strings.HasPrefix(name, "_") || - strings.HasPrefix(name, ".") { - continue - } - if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) { - continue - } - - ext := path.Ext(name) - switch ext { - case ".go", ".c", ".s", ".h", ".S": - // tentatively okay - default: - // skip - continue - } - - filename, data, err := ctxt.readFile(dir, name) - if err != nil { - return nil, err - } - - // Look for +build comments to accept or reject the file. - if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) { - continue - } - - // Going to save the file. For non-Go files, can stop here. - switch ext { - case ".c": - di.CFiles = append(di.CFiles, name) - continue - case ".h": - di.HFiles = append(di.HFiles, name) - continue - case ".s": - di.SFiles = append(di.SFiles, name) - continue - case ".S": - Sfiles = append(Sfiles, name) - continue - } - - pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments) - if err != nil { - return nil, err - } - - pkg := string(pf.Name.Name) - if pkg == "documentation" { - continue - } - - isTest := strings.HasSuffix(name, "_test.go") - if isTest && strings.HasSuffix(pkg, "_test") { - pkg = pkg[:len(pkg)-len("_test")] - } - - if di.Package == "" { - di.Package = pkg - firstFile = name - } else if pkg != di.Package { - return nil, fmt.Errorf("%s: found packages %s (%s) and %s (%s)", dir, di.Package, firstFile, pkg, name) - } - if pf.Doc != nil { - if di.PackageComment != nil { - di.PackageComment.List = append(di.PackageComment.List, pf.Doc.List...) - } else { - di.PackageComment = pf.Doc - } - } - - // Record imports and information about cgo. - isCgo := false - for _, decl := range pf.Decls { - d, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - for _, dspec := range d.Specs { - spec, ok := dspec.(*ast.ImportSpec) - if !ok { - continue - } - quoted := string(spec.Path.Value) - path, err := strconv.Unquote(quoted) - if err != nil { - log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted) - } - if isTest { - testImported[path] = append(testImported[path], fset.Position(spec.Pos())) - } else { - imported[path] = append(imported[path], fset.Position(spec.Pos())) - } - if path == "C" { - if isTest { - return nil, fmt.Errorf("%s: use of cgo in test not supported", filename) - } - cg := spec.Doc - if cg == nil && len(d.Specs) == 1 { - cg = d.Doc - } - if cg != nil { - if err := ctxt.saveCgo(filename, &di, cg); err != nil { - return nil, err - } - } - isCgo = true - } - } - } - if isCgo { - if ctxt.CgoEnabled { - di.CgoFiles = append(di.CgoFiles, name) - } - } else if isTest { - if pkg == string(pf.Name.Name) { - di.TestGoFiles = append(di.TestGoFiles, name) - } else { - di.XTestGoFiles = append(di.XTestGoFiles, name) - } - } else { - di.GoFiles = append(di.GoFiles, name) - } - } - if di.Package == "" { - return nil, fmt.Errorf("%s: no Go source files", dir) - } - di.Imports = make([]string, len(imported)) - di.ImportPos = imported - i := 0 - for p := range imported { - di.Imports[i] = p - i++ - } - di.TestImports = make([]string, len(testImported)) - di.TestImportPos = testImported - i = 0 - for p := range testImported { - di.TestImports[i] = p - i++ - } - - // add the .S files only if we are using cgo - // (which means gcc will compile them). - // The standard assemblers expect .s files. - if len(di.CgoFiles) > 0 { - di.SFiles = append(di.SFiles, Sfiles...) - sort.Strings(di.SFiles) - } - - // File name lists are sorted because ReadDir sorts. - sort.Strings(di.Imports) - sort.Strings(di.TestImports) - return &di, nil -} - -var slashslash = []byte("//") - -// shouldBuild reports whether it is okay to use this file, -// The rule is that in the file's leading run of // comments -// and blank lines, which must be followed by a blank line -// (to avoid including a Go package clause doc comment), -// lines beginning with '// +build' are taken as build directives. -// -// The file is accepted only if each such line lists something -// matching the file. For example: -// -// // +build windows linux -// -// marks the file as applicable only on Windows and Linux. -// -func (ctxt *Context) shouldBuild(content []byte) bool { - // Pass 1. Identify leading run of // comments and blank lines, - // which must be followed by a blank line. - end := 0 - p := content - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if len(line) == 0 { // Blank line - end = cap(content) - cap(line) // &line[0] - &content[0] - continue - } - if !bytes.HasPrefix(line, slashslash) { // Not comment line - break - } - } - content = content[:end] - - // Pass 2. Process each line in the run. - p = content - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if bytes.HasPrefix(line, slashslash) { - line = bytes.TrimSpace(line[len(slashslash):]) - if len(line) > 0 && line[0] == '+' { - // Looks like a comment +line. - f := strings.Fields(string(line)) - if f[0] == "+build" { - ok := false - for _, tok := range f[1:] { - if ctxt.match(tok) { - ok = true - break - } - } - if !ok { - return false // this one doesn't match - } - } - } - } - } - return true // everything matches -} - -// saveCgo saves the information from the #cgo lines in the import "C" comment. -// These lines set CFLAGS and LDFLAGS and pkg-config directives that affect -// the way cgo's C code is built. -// -// TODO(rsc): This duplicates code in cgo. -// Once the dust settles, remove this code from cgo. -func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) error { - text := cg.Text() - for _, line := range strings.Split(text, "\n") { - orig := line - - // Line is - // #cgo [GOOS/GOARCH...] LDFLAGS: stuff - // - line = strings.TrimSpace(line) - if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { - continue - } - - // Split at colon. - line = strings.TrimSpace(line[4:]) - i := strings.Index(line, ":") - if i < 0 { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - line, argstr := line[:i], line[i+1:] - - // Parse GOOS/GOARCH stuff. - f := strings.Fields(line) - if len(f) < 1 { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - - cond, verb := f[:len(f)-1], f[len(f)-1] - if len(cond) > 0 { - ok := false - for _, c := range cond { - if ctxt.match(c) { - ok = true - break - } - } - if !ok { - continue - } - } - - args, err := splitQuoted(argstr) - if err != nil { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - for _, arg := range args { - if !safeName(arg) { - return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) - } - } - - switch verb { - case "CFLAGS": - di.CgoCFLAGS = append(di.CgoCFLAGS, args...) - case "LDFLAGS": - di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) - case "pkg-config": - di.CgoPkgConfig = append(di.CgoPkgConfig, args...) - default: - return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig) - } - } - return nil -} - -var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:") - -func safeName(s string) bool { - if s == "" { - return false - } - for i := 0; i < len(s); i++ { - if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 { - return false - } - } - return true -} - -// splitQuoted splits the string s around each instance of one or more consecutive -// white space characters while taking into account quotes and escaping, and -// returns an array of substrings of s or an empty list if s contains only white space. -// Single quotes and double quotes are recognized to prevent splitting within the -// quoted region, and are removed from the resulting substrings. If a quote in s -// isn't closed err will be set and r will have the unclosed argument as the -// last element. The backslash is used for escaping. -// -// For example, the following string: -// -// a b:"c d" 'e''f' "g\"" -// -// Would be parsed as: -// -// []string{"a", "b:c d", "ef", `g"`} -// -func splitQuoted(s string) (r []string, err error) { - var args []string - arg := make([]rune, len(s)) - escaped := false - quoted := false - quote := '\x00' - i := 0 - for _, rune := range s { - switch { - case escaped: - escaped = false - case rune == '\\': - escaped = true - continue - case quote != '\x00': - if rune == quote { - quote = '\x00' - continue - } - case rune == '"' || rune == '\'': - quoted = true - quote = rune - continue - case unicode.IsSpace(rune): - if quoted || i > 0 { - quoted = false - args = append(args, string(arg[:i])) - i = 0 - } - continue - } - arg[i] = rune - i++ - } - if quoted || i > 0 { - args = append(args, string(arg[:i])) - } - if quote != 0 { - err = errors.New("unclosed quote") - } else if escaped { - err = errors.New("unfinished escaping") - } - return args, err -} - -// match returns true if the name is one of: -// -// $GOOS -// $GOARCH -// cgo (if cgo is enabled) -// !cgo (if cgo is disabled) -// tag (if tag is listed in ctxt.BuildTags) -// !tag (if tag is not listed in ctxt.BuildTags) -// a slash-separated list of any of these -// -func (ctxt *Context) match(name string) bool { - if name == "" { - return false - } - if i := strings.Index(name, ","); i >= 0 { - // comma-separated list - return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) - } - if strings.HasPrefix(name, "!!") { // bad syntax, reject always - return false - } - if strings.HasPrefix(name, "!") { // negation - return !ctxt.match(name[1:]) - } - - // Tags must be letters, digits, underscores. - // Unlike in Go identifiers, all digits is fine (e.g., "386"). - for _, c := range name { - if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { - return false - } - } - - // special tags - if ctxt.CgoEnabled && name == "cgo" { - return true - } - if name == ctxt.GOOS || name == ctxt.GOARCH { - return true - } - - // other tags - for _, tag := range ctxt.BuildTags { - if tag == name { - return true - } - } - - return false -} - -// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH -// suffix which does not match the current system. -// The recognized name formats are: -// -// name_$(GOOS).* -// name_$(GOARCH).* -// name_$(GOOS)_$(GOARCH).* -// name_$(GOOS)_test.* -// name_$(GOARCH)_test.* -// name_$(GOOS)_$(GOARCH)_test.* -// -func (ctxt *Context) goodOSArchFile(name string) bool { - if dot := strings.Index(name, "."); dot != -1 { - name = name[:dot] - } - l := strings.Split(name, "_") - if n := len(l); n > 0 && l[n-1] == "test" { - l = l[:n-1] - } - n := len(l) - if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { - return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH - } - if n >= 1 && knownOS[l[n-1]] { - return l[n-1] == ctxt.GOOS - } - if n >= 1 && knownArch[l[n-1]] { - return l[n-1] == ctxt.GOARCH - } - return true -} - -var knownOS = make(map[string]bool) -var knownArch = make(map[string]bool) - -func init() { - for _, v := range strings.Fields(goosList) { - knownOS[v] = true - } - for _, v := range strings.Fields(goarchList) { - knownArch[v] = true - } -} diff --git a/libgo/go/go/build/doc.go b/libgo/go/go/build/doc.go new file mode 100644 index 00000000000..67c26ac7f4f --- /dev/null +++ b/libgo/go/go/build/doc.go @@ -0,0 +1,109 @@ +// 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 build gathers information about Go packages. +// +// Go Path +// +// The Go path is a list of directory trees containing Go source code. +// It is consulted to resolve imports that cannot be found in the standard +// Go tree. The default path is the value of the GOPATH environment +// variable, interpreted as a path list appropriate to the operating system +// (on Unix, the variable is a colon-separated string; +// on Windows, a semicolon-separated string; +// on Plan 9, a list). +// +// Each directory listed in the Go path must have a prescribed structure: +// +// The src/ directory holds source code. The path below 'src' determines +// the import path or executable name. +// +// The pkg/ directory holds installed package objects. +// As in the Go tree, each target operating system and +// architecture pair has its own subdirectory of pkg +// (pkg/GOOS_GOARCH). +// +// If DIR is a directory listed in the Go path, a package with +// source in DIR/src/foo/bar can be imported as "foo/bar" and +// has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a" +// (or, for gccgo, "DIR/pkg/gccgo/foo/libbar.a"). +// +// The bin/ directory holds compiled commands. +// Each command is named for its source directory, but only +// using the final element, not the entire path. That is, the +// command with source in DIR/src/foo/quux is installed into +// DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped +// so that you can add DIR/bin to your PATH to get at the +// installed commands. +// +// Here's an example directory layout: +// +// GOPATH=/home/user/gocode +// +// /home/user/gocode/ +// src/ +// foo/ +// bar/ (go code in package bar) +// x.go +// quux/ (go code in package main) +// y.go +// bin/ +// quux (installed command) +// pkg/ +// linux_amd64/ +// foo/ +// bar.a (installed package object) +// +// Build Constraints +// +// A build constraint is a line comment beginning with the directive +build +// that lists the conditions under which a file should be included in the package. +// Constraints may appear in any kind of source file (not just Go), but +// they must be appear near the top of the file, preceded +// only by blank lines and other line comments. +// +// A build constraint is evaluated as the OR of space-separated options; +// each option evaluates as the AND of its comma-separated terms; +// and each term is an alphanumeric word or, preceded by !, its negation. +// That is, the build constraint: +// +// // +build linux,386 darwin,!cgo +// +// corresponds to the boolean formula: +// +// (linux AND 386) OR (darwin AND (NOT cgo)) +// +// During a particular build, the following words are satisfied: +// +// - the target operating system, as spelled by runtime.GOOS +// - the target architecture, as spelled by runtime.GOARCH +// - "cgo", if ctxt.CgoEnabled is true +// - any additional words listed in ctxt.BuildTags +// +// If a file's name, after stripping the extension and a possible _test suffix, +// matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating +// system and architecture values, then the file is considered to have an implicit +// build constraint requiring those terms. +// +// To keep a file from being considered for the build: +// +// // +build ignore +// +// (any other unsatisfied word will work as well, but ``ignore'' is conventional.) +// +// To build a file only when using cgo, and only on Linux and OS X: +// +// // +build linux,cgo darwin,cgo +// +// Such a file is usually paired with another file implementing the +// default functionality for other systems, which in this case would +// carry the constraint: +// +// // +build !linux !darwin !cgo +// +// Naming a file dns_windows.go will cause it to be included only when +// building the package for Windows; similarly, math_386.s will be included +// only when building the package for 32-bit x86. +// +package build diff --git a/libgo/go/go/build/path.go b/libgo/go/go/build/path.go deleted file mode 100644 index e160ac3b280..00000000000 --- a/libgo/go/go/build/path.go +++ /dev/null @@ -1,182 +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 build - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" -) - -// ToolDir is the directory containing build tools. -var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) - -// Path is a validated list of Trees derived from $GOROOT and $GOPATH at init. -var Path []*Tree - -// Tree describes a Go source tree, either $GOROOT or one from $GOPATH. -type Tree struct { - Path string - Goroot bool -} - -func newTree(p string) (*Tree, error) { - if !filepath.IsAbs(p) { - return nil, errors.New("must be absolute") - } - ep, err := filepath.EvalSymlinks(p) - if err != nil { - return nil, err - } - return &Tree{Path: ep}, nil -} - -// SrcDir returns the tree's package source directory. -func (t *Tree) SrcDir() string { - if t.Goroot { - return filepath.Join(t.Path, "src", "pkg") - } - return filepath.Join(t.Path, "src") -} - -// PkgDir returns the tree's package object directory. -func (t *Tree) PkgDir() string { - goos, goarch := runtime.GOOS, runtime.GOARCH - if e := os.Getenv("GOOS"); e != "" { - goos = e - } - if e := os.Getenv("GOARCH"); e != "" { - goarch = e - } - return filepath.Join(t.Path, "pkg", goos+"_"+goarch) -} - -// BinDir returns the tree's binary executable directory. -func (t *Tree) BinDir() string { - if t.Goroot { - if gobin := os.Getenv("GOBIN"); gobin != "" { - return filepath.Clean(gobin) - } - } - return filepath.Join(t.Path, "bin") -} - -// HasSrc returns whether the given package's -// source can be found inside this Tree. -func (t *Tree) HasSrc(pkg string) bool { - fi, err := os.Stat(filepath.Join(t.SrcDir(), pkg)) - if err != nil { - return false - } - return fi.IsDir() -} - -// HasPkg returns whether the given package's -// object file can be found inside this Tree. -func (t *Tree) HasPkg(pkg string) bool { - fi, err := os.Stat(filepath.Join(t.PkgDir(), pkg+".a")) - if err != nil { - return false - } - return !fi.IsDir() -} - -var ( - ErrNotFound = errors.New("package could not be found locally") - ErrTreeNotFound = errors.New("no valid GOROOT or GOPATH could be found") -) - -// FindTree takes an import or filesystem path and returns the -// tree where the package source should be and the package import path. -func FindTree(path string) (tree *Tree, pkg string, err error) { - if isLocalPath(path) { - if path, err = filepath.Abs(path); err != nil { - return - } - if path, err = filepath.EvalSymlinks(path); err != nil { - return - } - for _, t := range Path { - tpath := t.SrcDir() + string(filepath.Separator) - if !filepath.HasPrefix(path, tpath) { - continue - } - tree = t - pkg = filepath.ToSlash(path[len(tpath):]) - return - } - err = fmt.Errorf("path %q not inside a GOPATH", path) - return - } - tree = defaultTree - pkg = filepath.ToSlash(path) - for _, t := range Path { - if t.HasSrc(pkg) { - tree = t - return - } - } - if tree == nil { - err = ErrTreeNotFound - } else { - err = ErrNotFound - } - return -} - -// isLocalPath returns whether the given path is local (/foo ./foo ../foo . ..) -// Windows paths that starts with drive letter (c:\foo c:foo) are considered local. -func isLocalPath(s string) bool { - const sep = string(filepath.Separator) - return s == "." || s == ".." || - filepath.HasPrefix(s, sep) || - filepath.HasPrefix(s, "."+sep) || filepath.HasPrefix(s, ".."+sep) || - filepath.VolumeName(s) != "" -} - -var ( - // argument lists used by the build's gc and ld methods - gcImportArgs []string - ldImportArgs []string - - // default tree for remote packages - defaultTree *Tree -) - -// set up Path: parse and validate GOROOT and GOPATH variables -func init() { - root := runtime.GOROOT() - t, err := newTree(root) - if err == nil { - t.Goroot = true - Path = []*Tree{t} - } - - for _, p := range filepath.SplitList(os.Getenv("GOPATH")) { - if p == "" { - continue - } - t, err := newTree(p) - if err != nil { - continue - } - - Path = append(Path, t) - gcImportArgs = append(gcImportArgs, "-I", t.PkgDir()) - ldImportArgs = append(ldImportArgs, "-L", t.PkgDir()) - - // select first GOPATH entry as default - if defaultTree == nil { - defaultTree = t - } - } - - // use GOROOT if no valid GOPATH specified - if defaultTree == nil && len(Path) > 0 { - defaultTree = Path[0] - } -} diff --git a/libgo/go/go/build/pkgtest/pkgtest.go b/libgo/go/go/build/pkgtest/pkgtest.go deleted file mode 100644 index 08eea1e2bc3..00000000000 --- a/libgo/go/go/build/pkgtest/pkgtest.go +++ /dev/null @@ -1,13 +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 pkgtest - -import "bytes" - -func Foo() *bytes.Buffer { - return nil -} - -func Sqrt(x float64) float64 diff --git a/libgo/go/go/build/pkgtest/sqrt_386_test.go b/libgo/go/go/build/pkgtest/sqrt_386_test.go deleted file mode 100644 index 26b483fa0ba..00000000000 --- a/libgo/go/go/build/pkgtest/sqrt_386_test.go +++ /dev/null @@ -1 +0,0 @@ -package pkgtest diff --git a/libgo/go/go/build/pkgtest/sqrt_amd64_test.go b/libgo/go/go/build/pkgtest/sqrt_amd64_test.go deleted file mode 100644 index 26b483fa0ba..00000000000 --- a/libgo/go/go/build/pkgtest/sqrt_amd64_test.go +++ /dev/null @@ -1 +0,0 @@ -package pkgtest diff --git a/libgo/go/go/build/pkgtest/sqrt_arm_test.go b/libgo/go/go/build/pkgtest/sqrt_arm_test.go deleted file mode 100644 index 26b483fa0ba..00000000000 --- a/libgo/go/go/build/pkgtest/sqrt_arm_test.go +++ /dev/null @@ -1 +0,0 @@ -package pkgtest diff --git a/libgo/go/go/build/pkgtest/sqrt_test.go b/libgo/go/go/build/pkgtest/sqrt_test.go deleted file mode 100644 index ee9fd5de688..00000000000 --- a/libgo/go/go/build/pkgtest/sqrt_test.go +++ /dev/null @@ -1,9 +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 pkgtest - -import "fmt" - -var _ = fmt.Printf diff --git a/libgo/go/go/build/pkgtest/xsqrt_test.go b/libgo/go/go/build/pkgtest/xsqrt_test.go deleted file mode 100644 index 3898d1dda59..00000000000 --- a/libgo/go/go/build/pkgtest/xsqrt_test.go +++ /dev/null @@ -1,9 +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 pkgtest_test - -import "pkgtest" - -var _ = pkgtest.Foo diff --git a/libgo/go/go/build/syslist_test.go b/libgo/go/go/build/syslist_test.go index d27630d758d..9157faf8cb9 100644 --- a/libgo/go/go/build/syslist_test.go +++ b/libgo/go/go/build/syslist_test.go @@ -55,7 +55,7 @@ var tests = []GoodFileTest{ func TestGoodOSArch(t *testing.T) { for _, test := range tests { - if DefaultContext.goodOSArchFile(test.name) != test.result { + if Default.goodOSArchFile(test.name) != test.result { t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result) } } diff --git a/libgo/go/go/build/testdata/other/file/file.go b/libgo/go/go/build/testdata/other/file/file.go new file mode 100644 index 00000000000..bbfd3e9e598 --- /dev/null +++ b/libgo/go/go/build/testdata/other/file/file.go @@ -0,0 +1,5 @@ +// Test data - not compiled. + +package file + +func F() {} diff --git a/libgo/go/go/build/testdata/other/main.go b/libgo/go/go/build/testdata/other/main.go new file mode 100644 index 00000000000..e0904357c92 --- /dev/null +++ b/libgo/go/go/build/testdata/other/main.go @@ -0,0 +1,11 @@ +// Test data - not compiled. + +package main + +import ( + "./file" +) + +func main() { + file.F() +} diff --git a/libgo/go/go/doc/reader.go b/libgo/go/go/doc/reader.go index 3558892ebd0..5eaae37b7de 100644 --- a/libgo/go/go/doc/reader.go +++ b/libgo/go/go/doc/reader.go @@ -432,6 +432,17 @@ func (r *reader) readFile(src *ast.File) { r.readValue(d) case token.TYPE: // types are handled individually + if len(d.Specs) == 1 && !d.Lparen.IsValid() { + // common case: single declaration w/o parentheses + // (if a single declaration is parenthesized, + // create a new fake declaration below, so that + // go/doc type declarations always appear w/o + // parentheses) + if s, ok := d.Specs[0].(*ast.TypeSpec); ok { + r.readType(d, s) + } + break + } for _, spec := range d.Specs { if s, ok := spec.(*ast.TypeSpec); ok { // use an individual (possibly fake) declaration @@ -439,8 +450,13 @@ func (r *reader) readFile(src *ast.File) { // gets to (re-)use the declaration documentation // if there's none associated with the spec itself fake := &ast.GenDecl{ - Doc: d.Doc, - TokPos: d.Pos(), + Doc: d.Doc, + // don't use the existing TokPos because it + // will lead to the wrong selection range for + // the fake declaration if there are more + // than one type in the group (this affects + // src/cmd/godoc/godoc.go's posLink_urlFunc) + TokPos: s.Pos(), Tok: token.TYPE, Specs: []ast.Spec{s}, } diff --git a/libgo/go/go/parser/parser.go b/libgo/go/go/parser/parser.go index c1e61904486..a122baf0879 100644 --- a/libgo/go/go/parser/parser.go +++ b/libgo/go/go/parser/parser.go @@ -14,6 +14,9 @@ import ( "go/ast" "go/scanner" "go/token" + "strconv" + "strings" + "unicode" ) // The parser structure holds the parser's internal state. @@ -1913,6 +1916,17 @@ func (p *parser) parseStmt() (s ast.Stmt) { type parseSpecFunction func(p *parser, doc *ast.CommentGroup, iota int) ast.Spec +func isValidImport(lit string) bool { + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + s, _ := strconv.Unquote(lit) // go/scanner returns a legal string literal + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return false + } + } + return s != "" +} + func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { if p.trace { defer un(trace(p, "ImportSpec")) @@ -1929,6 +1943,9 @@ func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { var path *ast.BasicLit if p.tok == token.STRING { + if !isValidImport(p.lit) { + p.error(p.pos, "invalid import path: "+p.lit) + } path = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} p.next() } else { diff --git a/libgo/go/go/parser/parser_test.go b/libgo/go/go/parser/parser_test.go index a3ee8525de2..93ca3d6aa39 100644 --- a/libgo/go/go/parser/parser_test.go +++ b/libgo/go/go/parser/parser_test.go @@ -5,6 +5,7 @@ package parser import ( + "fmt" "go/ast" "go/token" "os" @@ -204,3 +205,48 @@ func TestVarScope(t *testing.T) { } } } + +var imports = map[string]bool{ + `"a"`: true, + "`a`": true, + `"a/b"`: true, + `"a.b"`: true, + `"m\x61th"`: true, + `"greek/αβ"`: true, + `""`: false, + + // Each of these pairs tests both `` vs "" strings + // and also use of invalid characters spelled out as + // escape sequences and written directly. + // For example `"\x00"` tests import "\x00" + // while "`\x00`" tests import `<actual-NUL-byte>`. + `"\x00"`: false, + "`\x00`": false, + `"\x7f"`: false, + "`\x7f`": false, + `"a!"`: false, + "`a!`": false, + `"a b"`: false, + "`a b`": false, + `"a\\b"`: false, + "`a\\b`": false, + "\"`a`\"": false, + "`\"a\"`": false, + `"\x80\x80"`: false, + "`\x80\x80`": false, + `"\xFFFD"`: false, + "`\xFFFD`": false, +} + +func TestImports(t *testing.T) { + for path, isValid := range imports { + src := fmt.Sprintf("package p; import %s", path) + _, err := ParseFile(fset, "", src, 0) + switch { + case err != nil && isValid: + t.Errorf("ParseFile(%s): got %v; expected no error", src, err) + case err == nil && !isValid: + t.Errorf("ParseFile(%s): got no error; expected one", src) + } + } +} diff --git a/libgo/go/go/printer/nodes.go b/libgo/go/go/printer/nodes.go index cd5e075c16c..05b4ef59a2d 100644 --- a/libgo/go/go/printer/nodes.go +++ b/libgo/go/go/printer/nodes.go @@ -12,6 +12,7 @@ import ( "bytes" "go/ast" "go/token" + "unicode/utf8" ) // Other formatting issues: @@ -82,46 +83,37 @@ func (p *printer) setComment(g *ast.CommentGroup) { type exprListMode uint const ( - blankStart exprListMode = 1 << iota // print a blank before a non-empty list - blankEnd // print a blank after a non-empty list - commaSep // elements are separated by commas - commaTerm // list is optionally terminated by a comma - noIndent // no extra indentation in multi-line lists + commaTerm exprListMode = 1 << iota // list is optionally terminated by a comma + noIndent // no extra indentation in multi-line lists ) -// Sets multiLine to true if the identifier list spans multiple lines. // If indent is set, a multi-line identifier list is indented after the // first linebreak encountered. -func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) { +func (p *printer) identList(list []*ast.Ident, indent bool) { // convert into an expression list so we can re-use exprList formatting xlist := make([]ast.Expr, len(list)) for i, x := range list { xlist[i] = x } - mode := commaSep + var mode exprListMode if !indent { - mode |= noIndent + mode = noIndent } - p.exprList(token.NoPos, xlist, 1, mode, multiLine, token.NoPos) + p.exprList(token.NoPos, xlist, 1, mode, token.NoPos) } // Print a list of expressions. If the list spans multiple // source lines, the original line breaks are respected between -// expressions. Sets multiLine to true if the list spans multiple -// lines. +// expressions. // // TODO(gri) Consider rewriting this to be independent of []ast.Expr // so that we can use the algorithm for any kind of list // (e.g., pass list via a channel over which to range). -func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next0 token.Pos) { +func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos) { if len(list) == 0 { return } - if mode&blankStart != 0 { - p.print(blank) - } - prev := p.posFor(prev0) next := p.posFor(next0) line := p.lineFor(list[0].Pos()) @@ -131,17 +123,11 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp // all list entries on a single line for i, x := range list { if i > 0 { - if mode&commaSep != 0 { - // use position of expression following the comma as - // comma position for correct comment placement - p.print(x.Pos(), token.COMMA) - } - p.print(blank) + // use position of expression following the comma as + // comma position for correct comment placement + p.print(x.Pos(), token.COMMA, blank) } - p.expr0(x, depth, multiLine) - } - if mode&blankEnd != 0 { - p.print(blank) + p.expr0(x, depth) } return } @@ -161,7 +147,6 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp prevBreak := -1 // index of last expression that was followed by a linebreak if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) { ws = ignore - *multiLine = true prevBreak = 0 } @@ -215,15 +200,13 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp if i > 0 { needsLinebreak := prevLine < line && prevLine > 0 && line > 0 - if mode&commaSep != 0 { - // use position of expression following the comma as - // comma position for correct comment placement, but - // only if the expression is on the same line - if !needsLinebreak { - p.print(x.Pos()) - } - p.print(token.COMMA) + // use position of expression following the comma as + // comma position for correct comment placement, but + // only if the expression is on the same line + if !needsLinebreak { + p.print(x.Pos()) } + p.print(token.COMMA) needsBlank := true if needsLinebreak { // lines are broken using newlines so comments remain aligned @@ -231,7 +214,6 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp // the same line in which case formfeed is used if p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) { ws = ignore - *multiLine = true prevBreak = i needsBlank = false // we got a line break instead } @@ -245,11 +227,11 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp // we have a key:value expression that fits onto one line and // is in a list with more then one entry: use a column for the // key such that consecutive entries can align if possible - p.expr(pair.Key, multiLine) + p.expr(pair.Key) p.print(pair.Colon, token.COLON, vtab) - p.expr(pair.Value, multiLine) + p.expr(pair.Value) } else { - p.expr0(x, depth, multiLine) + p.expr0(x, depth) } } @@ -264,18 +246,13 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp return } - if mode&blankEnd != 0 { - p.print(blank) - } - if ws == ignore && mode&noIndent == 0 { // unindent if we indented p.print(unindent) } } -// Sets multiLine to true if the the parameter list spans multiple lines. -func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) { +func (p *printer) parameters(fields *ast.FieldList) { p.print(fields.Opening, token.LPAREN) if len(fields.List) > 0 { prevLine := p.lineFor(fields.Opening) @@ -306,7 +283,6 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) { if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) { // break line if the opening "(" or previous parameter ended on a different line ws = ignore - *multiLine = true } else if i > 0 { p.print(blank) } @@ -318,11 +294,11 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) { // again at the end (and still ws == indent). Thus, a subsequent indent // by a linebreak call after a type, or in the next multi-line identList // will do the right thing. - p.identList(par.Names, ws == indent, multiLine) + p.identList(par.Names, ws == indent) p.print(blank) } // parameter type - p.expr(par.Type, multiLine) + p.expr(par.Type) prevLine = parLineEnd } // if the closing ")" is on a separate line from the last parameter, @@ -339,27 +315,26 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) { p.print(fields.Closing, token.RPAREN) } -// Sets multiLine to true if the signature spans multiple lines. -func (p *printer) signature(params, result *ast.FieldList, multiLine *bool) { - p.parameters(params, multiLine) +func (p *printer) signature(params, result *ast.FieldList) { + p.parameters(params) n := result.NumFields() if n > 0 { p.print(blank) if n == 1 && result.List[0].Names == nil { // single anonymous result; no ()'s - p.expr(result.List[0].Type, multiLine) + p.expr(result.List[0].Type) return } - p.parameters(result, multiLine) + p.parameters(result) } } func identListSize(list []*ast.Ident, maxSize int) (size int) { for i, x := range list { if i > 0 { - size += 2 // ", " + size += len(", ") } - size += len(x.Name) + size += utf8.RuneCountInString(x.Name) if size >= maxSize { break } @@ -389,6 +364,10 @@ func (p *printer) setLineComment(text string) { p.setComment(&ast.CommentGroup{List: []*ast.Comment{{Slash: token.NoPos, Text: text}}}) } +func (p *printer) isMultiLine(n ast.Node) bool { + return p.lineFor(n.End())-p.lineFor(n.Pos()) > 1 +} + func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) { lbrace := fields.Opening list := fields.List @@ -412,12 +391,12 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) // no comments so no need for comma position p.print(token.COMMA, blank) } - p.expr(x, ignoreMultiLine) + p.expr(x) } if len(f.Names) > 0 { p.print(blank) } - p.expr(f.Type, ignoreMultiLine) + p.expr(f.Type) p.print(blank, rbrace, token.RBRACE) return } @@ -435,23 +414,22 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) if len(list) == 1 { sep = blank } - var ml bool + newSection := false for i, f := range list { if i > 0 { - p.linebreak(p.lineFor(f.Pos()), 1, ignore, ml) + p.linebreak(p.lineFor(f.Pos()), 1, ignore, newSection) } - ml = false extraTabs := 0 p.setComment(f.Doc) if len(f.Names) > 0 { // named fields - p.identList(f.Names, false, &ml) + p.identList(f.Names, false) p.print(sep) - p.expr(f.Type, &ml) + p.expr(f.Type) extraTabs = 1 } else { // anonymous field - p.expr(f.Type, &ml) + p.expr(f.Type) extraTabs = 2 } if f.Tag != nil { @@ -459,7 +437,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) p.print(sep) } p.print(sep) - p.expr(f.Tag, &ml) + p.expr(f.Tag) extraTabs = 0 } if f.Comment != nil { @@ -468,6 +446,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) } p.setComment(f.Comment) } + newSection = p.isMultiLine(f) } if isIncomplete { if len(list) > 0 { @@ -479,22 +458,22 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) } else { // interface - var ml bool + newSection := false for i, f := range list { if i > 0 { - p.linebreak(p.lineFor(f.Pos()), 1, ignore, ml) + p.linebreak(p.lineFor(f.Pos()), 1, ignore, newSection) } - ml = false p.setComment(f.Doc) if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { // method - p.expr(f.Names[0], &ml) - p.signature(ftyp.Params, ftyp.Results, &ml) + p.expr(f.Names[0]) + p.signature(ftyp.Params, ftyp.Results) } else { // embedded interface - p.expr(f.Type, &ml) + p.expr(f.Type) } p.setComment(f.Comment) + newSection = p.isMultiLine(f) } if isIncomplete { if len(list) > 0 { @@ -635,15 +614,14 @@ func reduceDepth(depth int) int { // cutoff is 6 (always use spaces) in Normal mode // and 4 (never use spaces) in Compact mode. // -// Sets multiLine to true if the binary expression spans multiple lines. -func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiLine *bool) { +func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) { prec := x.Op.Precedence() if prec < prec1 { // parenthesis needed // Note: The parser inserts an ast.ParenExpr node; thus this case // can only occur if the AST is created in a different way. p.print(token.LPAREN) - p.expr0(x, reduceDepth(depth), multiLine) // parentheses undo one level of depth + p.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth p.print(token.RPAREN) return } @@ -651,7 +629,7 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiL printBlank := prec < cutoff ws := indent - p.expr1(x.X, prec, depth+diffPrec(x.X, prec), multiLine) + p.expr1(x.X, prec, depth+diffPrec(x.X, prec)) if printBlank { p.print(blank) } @@ -663,14 +641,13 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiL // in the source if p.linebreak(yline, 1, ws, true) { ws = ignore - *multiLine = true printBlank = false // no blank after line break } } if printBlank { p.print(blank) } - p.expr1(x.Y, prec+1, depth+1, multiLine) + p.expr1(x.Y, prec+1, depth+1) if ws == ignore { p.print(unindent) } @@ -681,8 +658,7 @@ func isBinary(expr ast.Expr) bool { return ok } -// Sets multiLine to true if the expression spans multiple lines. -func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { +func (p *printer) expr1(expr ast.Expr, prec1, depth int) { p.print(expr.Pos()) switch x := expr.(type) { @@ -697,12 +673,12 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { p.internalError("depth < 1:", depth) depth = 1 } - p.binaryExpr(x, prec1, cutoff(x, depth), depth, multiLine) + p.binaryExpr(x, prec1, cutoff(x, depth), depth) case *ast.KeyValueExpr: - p.expr(x.Key, multiLine) + p.expr(x.Key) p.print(x.Colon, token.COLON, blank) - p.expr(x.Value, multiLine) + p.expr(x.Value) case *ast.StarExpr: const prec = token.UnaryPrec @@ -710,12 +686,12 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { // parenthesis needed p.print(token.LPAREN) p.print(token.MUL) - p.expr(x.X, multiLine) + p.expr(x.X) p.print(token.RPAREN) } else { // no parenthesis needed p.print(token.MUL) - p.expr(x.X, multiLine) + p.expr(x.X) } case *ast.UnaryExpr: @@ -723,7 +699,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { if prec < prec1 { // parenthesis needed p.print(token.LPAREN) - p.expr(x, multiLine) + p.expr(x) p.print(token.RPAREN) } else { // no parenthesis needed @@ -732,42 +708,41 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { // TODO(gri) Remove this code if it cannot be reached. p.print(blank) } - p.expr1(x.X, prec, depth, multiLine) + p.expr1(x.X, prec, depth) } case *ast.BasicLit: p.print(x) case *ast.FuncLit: - p.expr(x.Type, multiLine) - p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true, multiLine) + p.expr(x.Type) + p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true) case *ast.ParenExpr: if _, hasParens := x.X.(*ast.ParenExpr); hasParens { // don't print parentheses around an already parenthesized expression // TODO(gri) consider making this more general and incorporate precedence levels - p.expr0(x.X, reduceDepth(depth), multiLine) // parentheses undo one level of depth + p.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth } else { p.print(token.LPAREN) - p.expr0(x.X, reduceDepth(depth), multiLine) // parentheses undo one level of depth + p.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth p.print(x.Rparen, token.RPAREN) } case *ast.SelectorExpr: - p.expr1(x.X, token.HighestPrec, depth, multiLine) + p.expr1(x.X, token.HighestPrec, depth) p.print(token.PERIOD) if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line { p.print(indent, newline, x.Sel.Pos(), x.Sel, unindent) - *multiLine = true } else { p.print(x.Sel.Pos(), x.Sel) } case *ast.TypeAssertExpr: - p.expr1(x.X, token.HighestPrec, depth, multiLine) + p.expr1(x.X, token.HighestPrec, depth) p.print(token.PERIOD, token.LPAREN) if x.Type != nil { - p.expr(x.Type, multiLine) + p.expr(x.Type) } else { p.print(token.TYPE) } @@ -775,17 +750,17 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { case *ast.IndexExpr: // TODO(gri): should treat[] like parentheses and undo one level of depth - p.expr1(x.X, token.HighestPrec, 1, multiLine) + p.expr1(x.X, token.HighestPrec, 1) p.print(x.Lbrack, token.LBRACK) - p.expr0(x.Index, depth+1, multiLine) + p.expr0(x.Index, depth+1) p.print(x.Rbrack, token.RBRACK) case *ast.SliceExpr: // TODO(gri): should treat[] like parentheses and undo one level of depth - p.expr1(x.X, token.HighestPrec, 1, multiLine) + p.expr1(x.X, token.HighestPrec, 1) p.print(x.Lbrack, token.LBRACK) if x.Low != nil { - p.expr0(x.Low, depth+1, multiLine) + p.expr0(x.Low, depth+1) } // blanks around ":" if both sides exist and either side is a binary expression if depth <= 1 && x.Low != nil && x.High != nil && (isBinary(x.Low) || isBinary(x.High)) { @@ -794,7 +769,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { p.print(token.COLON) } if x.High != nil { - p.expr0(x.High, depth+1, multiLine) + p.expr0(x.High, depth+1) } p.print(x.Rbrack, token.RBRACK) @@ -802,21 +777,26 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { if len(x.Args) > 1 { depth++ } - p.expr1(x.Fun, token.HighestPrec, depth, multiLine) + p.expr1(x.Fun, token.HighestPrec, depth) p.print(x.Lparen, token.LPAREN) - p.exprList(x.Lparen, x.Args, depth, commaSep|commaTerm, multiLine, x.Rparen) if x.Ellipsis.IsValid() { + p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis) p.print(x.Ellipsis, token.ELLIPSIS) + if x.Rparen.IsValid() && p.lineFor(x.Ellipsis) < p.lineFor(x.Rparen) { + p.print(token.COMMA, formfeed) + } + } else { + p.exprList(x.Lparen, x.Args, depth, commaTerm, x.Rparen) } p.print(x.Rparen, token.RPAREN) case *ast.CompositeLit: // composite literal elements that are composite literals themselves may have the type omitted if x.Type != nil { - p.expr1(x.Type, token.HighestPrec, depth, multiLine) + p.expr1(x.Type, token.HighestPrec, depth) } p.print(x.Lbrace, token.LBRACE) - p.exprList(x.Lbrace, x.Elts, 1, commaSep|commaTerm, multiLine, x.Rbrace) + p.exprList(x.Lbrace, x.Elts, 1, commaTerm, x.Rbrace) // do not insert extra line breaks because of comments before // the closing '}' as it might break the code if there is no // trailing ',' @@ -825,16 +805,16 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { case *ast.Ellipsis: p.print(token.ELLIPSIS) if x.Elt != nil { - p.expr(x.Elt, multiLine) + p.expr(x.Elt) } case *ast.ArrayType: p.print(token.LBRACK) if x.Len != nil { - p.expr(x.Len, multiLine) + p.expr(x.Len) } p.print(token.RBRACK) - p.expr(x.Elt, multiLine) + p.expr(x.Elt) case *ast.StructType: p.print(token.STRUCT) @@ -842,7 +822,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { case *ast.FuncType: p.print(token.FUNC) - p.signature(x.Params, x.Results, multiLine) + p.signature(x.Params, x.Results) case *ast.InterfaceType: p.print(token.INTERFACE) @@ -850,9 +830,9 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { case *ast.MapType: p.print(token.MAP, token.LBRACK) - p.expr(x.Key, multiLine) + p.expr(x.Key) p.print(token.RBRACK) - p.expr(x.Value, multiLine) + p.expr(x.Value) case *ast.ChanType: switch x.Dir { @@ -864,7 +844,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { p.print(token.CHAN, token.ARROW) } p.print(blank) - p.expr(x.Value, multiLine) + p.expr(x.Value) default: panic("unreachable") @@ -873,14 +853,13 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) { return } -func (p *printer) expr0(x ast.Expr, depth int, multiLine *bool) { - p.expr1(x, token.LowestPrec, depth, multiLine) +func (p *printer) expr0(x ast.Expr, depth int) { + p.expr1(x, token.LowestPrec, depth) } -// Sets multiLine to true if the expression spans multiple lines. -func (p *printer) expr(x ast.Expr, multiLine *bool) { +func (p *printer) expr(x ast.Expr) { const depth = 1 - p.expr1(x, token.LowestPrec, depth, multiLine) + p.expr1(x, token.LowestPrec, depth) } // ---------------------------------------------------------------------------- @@ -894,13 +873,13 @@ func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) { if _indent > 0 { p.print(indent) } - var multiLine bool + multiLine := false for i, s := range list { // _indent == 0 only for lists of switch/select case clauses; // in those cases each clause is a new section p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || _indent == 0 || multiLine) - multiLine = false - p.stmt(s, nextIsRBrace && i == len(list)-1, &multiLine) + p.stmt(s, nextIsRBrace && i == len(list)-1) + multiLine = p.isMultiLine(s) } if _indent > 0 { p.print(unindent) @@ -957,25 +936,25 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po if init == nil && post == nil { // no semicolons required if expr != nil { - p.expr(stripParens(expr), ignoreMultiLine) + p.expr(stripParens(expr)) needsBlank = true } } else { // all semicolons required // (they are not separators, print them explicitly) if init != nil { - p.stmt(init, false, ignoreMultiLine) + p.stmt(init, false) } p.print(token.SEMICOLON, blank) if expr != nil { - p.expr(stripParens(expr), ignoreMultiLine) + p.expr(stripParens(expr)) needsBlank = true } if isForStmt { p.print(token.SEMICOLON, blank) needsBlank = false if post != nil { - p.stmt(post, false, ignoreMultiLine) + p.stmt(post, false) needsBlank = true } } @@ -985,8 +964,7 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po } } -// Sets multiLine to true if the statements spans multiple lines. -func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { +func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { p.print(stmt.Pos()) switch s := stmt.(type) { @@ -994,7 +972,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { p.print("BadStmt") case *ast.DeclStmt: - p.decl(s.Decl, multiLine) + p.decl(s.Decl) case *ast.EmptyStmt: // nothing to do @@ -1004,7 +982,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { // is applied before the line break if there is no comment // between (see writeWhitespace) p.print(unindent) - p.expr(s.Label, multiLine) + p.expr(s.Label) p.print(s.Colon, token.COLON, indent) if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { if !nextIsRBrace { @@ -1014,21 +992,21 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { } else { p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true) } - p.stmt(s.Stmt, nextIsRBrace, multiLine) + p.stmt(s.Stmt, nextIsRBrace) case *ast.ExprStmt: const depth = 1 - p.expr0(s.X, depth, multiLine) + p.expr0(s.X, depth) case *ast.SendStmt: const depth = 1 - p.expr0(s.Chan, depth, multiLine) + p.expr0(s.Chan, depth) p.print(blank, s.Arrow, token.ARROW, blank) - p.expr0(s.Value, depth, multiLine) + p.expr0(s.Value, depth) case *ast.IncDecStmt: const depth = 1 - p.expr0(s.X, depth+1, multiLine) + p.expr0(s.X, depth+1) p.print(s.TokPos, s.Tok) case *ast.AssignStmt: @@ -1036,56 +1014,55 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { if len(s.Lhs) > 1 && len(s.Rhs) > 1 { depth++ } - p.exprList(s.Pos(), s.Lhs, depth, commaSep, multiLine, s.TokPos) - p.print(blank, s.TokPos, s.Tok) - p.exprList(s.TokPos, s.Rhs, depth, blankStart|commaSep, multiLine, token.NoPos) + p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos) + p.print(blank, s.TokPos, s.Tok, blank) + p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos) case *ast.GoStmt: p.print(token.GO, blank) - p.expr(s.Call, multiLine) + p.expr(s.Call) case *ast.DeferStmt: p.print(token.DEFER, blank) - p.expr(s.Call, multiLine) + p.expr(s.Call) case *ast.ReturnStmt: p.print(token.RETURN) if s.Results != nil { - p.exprList(s.Pos(), s.Results, 1, blankStart|commaSep, multiLine, token.NoPos) + p.print(blank) + p.exprList(s.Pos(), s.Results, 1, 0, token.NoPos) } case *ast.BranchStmt: p.print(s.Tok) if s.Label != nil { p.print(blank) - p.expr(s.Label, multiLine) + p.expr(s.Label) } case *ast.BlockStmt: p.block(s, 1) - *multiLine = true case *ast.IfStmt: p.print(token.IF) p.controlClause(false, s.Init, s.Cond, nil) p.block(s.Body, 1) - *multiLine = true if s.Else != nil { p.print(blank, token.ELSE, blank) switch s.Else.(type) { case *ast.BlockStmt, *ast.IfStmt: - p.stmt(s.Else, nextIsRBrace, ignoreMultiLine) + p.stmt(s.Else, nextIsRBrace) default: p.print(token.LBRACE, indent, formfeed) - p.stmt(s.Else, true, ignoreMultiLine) + p.stmt(s.Else, true) p.print(unindent, formfeed, token.RBRACE) } } case *ast.CaseClause: if s.List != nil { - p.print(token.CASE) - p.exprList(s.Pos(), s.List, 1, blankStart|commaSep, multiLine, s.Colon) + p.print(token.CASE, blank) + p.exprList(s.Pos(), s.List, 1, 0, s.Colon) } else { p.print(token.DEFAULT) } @@ -1096,25 +1073,23 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { p.print(token.SWITCH) p.controlClause(false, s.Init, s.Tag, nil) p.block(s.Body, 0) - *multiLine = true case *ast.TypeSwitchStmt: p.print(token.SWITCH) if s.Init != nil { p.print(blank) - p.stmt(s.Init, false, ignoreMultiLine) + p.stmt(s.Init, false) p.print(token.SEMICOLON) } p.print(blank) - p.stmt(s.Assign, false, ignoreMultiLine) + p.stmt(s.Assign, false) p.print(blank) p.block(s.Body, 0) - *multiLine = true case *ast.CommClause: if s.Comm != nil { p.print(token.CASE, blank) - p.stmt(s.Comm, false, ignoreMultiLine) + p.stmt(s.Comm, false) } else { p.print(token.DEFAULT) } @@ -1129,29 +1104,26 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE) } else { p.block(body, 0) - *multiLine = true } case *ast.ForStmt: p.print(token.FOR) p.controlClause(true, s.Init, s.Cond, s.Post) p.block(s.Body, 1) - *multiLine = true case *ast.RangeStmt: p.print(token.FOR, blank) - p.expr(s.Key, multiLine) + p.expr(s.Key) if s.Value != nil { // use position of value following the comma as // comma position for correct comment placement p.print(s.Value.Pos(), token.COMMA, blank) - p.expr(s.Value, multiLine) + p.expr(s.Value) } p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank) - p.expr(stripParens(s.X), multiLine) + p.expr(stripParens(s.X)) p.print(blank) p.block(s.Body, 1) - *multiLine = true default: panic("unreachable") @@ -1228,20 +1200,20 @@ func keepTypeColumn(specs []ast.Spec) []bool { return m } -func (p *printer) valueSpec(s *ast.ValueSpec, keepType, doIndent bool, multiLine *bool) { +func (p *printer) valueSpec(s *ast.ValueSpec, keepType, doIndent bool) { p.setComment(s.Doc) - p.identList(s.Names, doIndent, multiLine) // always present + p.identList(s.Names, doIndent) // always present extraTabs := 3 if s.Type != nil || keepType { p.print(vtab) extraTabs-- } if s.Type != nil { - p.expr(s.Type, multiLine) + p.expr(s.Type) } if s.Values != nil { - p.print(vtab, token.ASSIGN) - p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos) + p.print(vtab, token.ASSIGN, blank) + p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos) extraTabs-- } if s.Comment != nil { @@ -1255,17 +1227,16 @@ func (p *printer) valueSpec(s *ast.ValueSpec, keepType, doIndent bool, multiLine // The parameter n is the number of specs in the group. If doIndent is set, // multi-line identifier lists in the spec are indented when the first // linebreak is encountered. -// Sets multiLine to true if the spec spans multiple lines. // -func (p *printer) spec(spec ast.Spec, n int, doIndent bool, multiLine *bool) { +func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { switch s := spec.(type) { case *ast.ImportSpec: p.setComment(s.Doc) if s.Name != nil { - p.expr(s.Name, multiLine) + p.expr(s.Name) p.print(blank) } - p.expr(s.Path, multiLine) + p.expr(s.Path) p.setComment(s.Comment) p.print(s.EndPos) @@ -1274,26 +1245,26 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool, multiLine *bool) { p.internalError("expected n = 1; got", n) } p.setComment(s.Doc) - p.identList(s.Names, doIndent, multiLine) // always present + p.identList(s.Names, doIndent) // always present if s.Type != nil { p.print(blank) - p.expr(s.Type, multiLine) + p.expr(s.Type) } if s.Values != nil { - p.print(blank, token.ASSIGN) - p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos) + p.print(blank, token.ASSIGN, blank) + p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos) } p.setComment(s.Comment) case *ast.TypeSpec: p.setComment(s.Doc) - p.expr(s.Name, multiLine) + p.expr(s.Name) if n == 1 { p.print(blank) } else { p.print(vtab) } - p.expr(s.Type, multiLine) + p.expr(s.Type) p.setComment(s.Comment) default: @@ -1301,8 +1272,7 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool, multiLine *bool) { } } -// Sets multiLine to true if the declaration spans multiple lines. -func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) { +func (p *printer) genDecl(d *ast.GenDecl) { p.setComment(d.Doc) p.print(d.Pos(), d.Tok, blank) @@ -1315,32 +1285,31 @@ func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) { // two or more grouped const/var declarations: // determine if the type column must be kept keepType := keepTypeColumn(d.Specs) - var ml bool + newSection := false for i, s := range d.Specs { if i > 0 { - p.linebreak(p.lineFor(s.Pos()), 1, ignore, ml) + p.linebreak(p.lineFor(s.Pos()), 1, ignore, newSection) } - ml = false - p.valueSpec(s.(*ast.ValueSpec), keepType[i], false, &ml) + p.valueSpec(s.(*ast.ValueSpec), keepType[i], false) + newSection = p.isMultiLine(s) } } else { - var ml bool + newSection := false for i, s := range d.Specs { if i > 0 { - p.linebreak(p.lineFor(s.Pos()), 1, ignore, ml) + p.linebreak(p.lineFor(s.Pos()), 1, ignore, newSection) } - ml = false - p.spec(s, n, false, &ml) + p.spec(s, n, false) + newSection = p.isMultiLine(s) } } p.print(unindent, formfeed) - *multiLine = true } p.print(d.Rparen, token.RPAREN) } else { // single declaration - p.spec(d.Specs[0], 1, true, multiLine) + p.spec(d.Specs[0], 1, true) } } @@ -1404,8 +1373,7 @@ func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool { return headerSize+bodySize <= maxSize } -// Sets multiLine to true if the function body spans multiple lines. -func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLine *bool) { +func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool) { if b == nil { return } @@ -1422,7 +1390,7 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi if i > 0 { p.print(token.SEMICOLON, blank) } - p.stmt(s, i == len(b.List)-1, ignoreMultiLine) + p.stmt(s, i == len(b.List)-1) } p.print(blank) } @@ -1432,7 +1400,6 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi p.print(blank) p.block(b, 1) - *multiLine = true } // distance returns the column difference between from and to if both @@ -1446,28 +1413,26 @@ func (p *printer) distance(from0 token.Pos, to token.Position) int { return infinity } -// Sets multiLine to true if the declaration spans multiple lines. -func (p *printer) funcDecl(d *ast.FuncDecl, multiLine *bool) { +func (p *printer) funcDecl(d *ast.FuncDecl) { p.setComment(d.Doc) p.print(d.Pos(), token.FUNC, blank) if d.Recv != nil { - p.parameters(d.Recv, multiLine) // method: print receiver + p.parameters(d.Recv) // method: print receiver p.print(blank) } - p.expr(d.Name, multiLine) - p.signature(d.Type.Params, d.Type.Results, multiLine) - p.funcBody(d.Body, p.distance(d.Pos(), p.pos), false, multiLine) + p.expr(d.Name) + p.signature(d.Type.Params, d.Type.Results) + p.funcBody(d.Body, p.distance(d.Pos(), p.pos), false) } -// Sets multiLine to true if the declaration spans multiple lines. -func (p *printer) decl(decl ast.Decl, multiLine *bool) { +func (p *printer) decl(decl ast.Decl) { switch d := decl.(type) { case *ast.BadDecl: p.print(d.Pos(), "BadDecl") case *ast.GenDecl: - p.genDecl(d, multiLine) + p.genDecl(d) case *ast.FuncDecl: - p.funcDecl(d, multiLine) + p.funcDecl(d) default: panic("unreachable") } @@ -1490,7 +1455,7 @@ func declToken(decl ast.Decl) (tok token.Token) { func (p *printer) file(src *ast.File) { p.setComment(src.Doc) p.print(src.Pos(), token.PACKAGE, blank) - p.expr(src.Name, ignoreMultiLine) + p.expr(src.Name) if len(src.Decls) > 0 { tok := token.ILLEGAL @@ -1509,7 +1474,7 @@ func (p *printer) file(src *ast.File) { min = 2 } p.linebreak(p.lineFor(d.Pos()), min, ignore, false) - p.decl(d, ignoreMultiLine) + p.decl(d) } } diff --git a/libgo/go/go/printer/printer.go b/libgo/go/go/printer/printer.go index 72f65a1d852..a027d32da89 100644 --- a/libgo/go/go/printer/printer.go +++ b/libgo/go/go/printer/printer.go @@ -34,9 +34,6 @@ const ( unindent = whiteSpace('<') ) -// Use ignoreMultiLine if the multiLine information is not important. -var ignoreMultiLine = new(bool) - // A pmode value represents the current printer mode. type pmode int @@ -280,10 +277,9 @@ func (p *printer) writeString(pos token.Position, s string, isLit bool) { // it as is likely to help position the comment nicely. // pos is the comment position, next the position of the item // after all pending comments, prev is the previous comment in -// a group of comments (or nil), and isKeyword indicates if the -// next item is a keyword. +// a group of comments (or nil), and tok is the next token. // -func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, isKeyword bool) { +func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, tok token.Token) { if len(p.output) == 0 { // the comment is the first item to be printed - don't write any whitespace return @@ -338,38 +334,41 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as // comment on a different line: // separate with at least one line break droppedLinebreak := false - if prev == nil { - // first comment of a comment group - j := 0 - for i, ch := range p.wsbuf { - switch ch { - case blank, vtab: - // ignore any horizontal whitespace before line breaks - p.wsbuf[i] = ignore + j := 0 + for i, ch := range p.wsbuf { + switch ch { + case blank, vtab: + // ignore any horizontal whitespace before line breaks + p.wsbuf[i] = ignore + continue + case indent: + // apply pending indentation + continue + case unindent: + // if this is not the last unindent, apply it + // as it is (likely) belonging to the last + // construct (e.g., a multi-line expression list) + // and is not part of closing a block + if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { continue - case indent: - // apply pending indentation + } + // if the next token is not a closing }, apply the unindent + // if it appears that the comment is aligned with the + // token; otherwise assume the unindent is part of a + // closing block and stop (this scenario appears with + // comments before a case label where the comments + // apply to the next case instead of the current one) + if tok != token.RBRACE && pos.Column == next.Column { continue - case unindent: - // if the next token is a keyword, apply the outdent - // if it appears that the comment is aligned with the - // keyword; otherwise assume the outdent is part of a - // closing block and stop (this scenario appears with - // comments before a case label where the comments - // apply to the next case instead of the current one) - if isKeyword && pos.Column == next.Column { - continue - } - case newline, formfeed: - // TODO(gri): may want to keep formfeed info in some cases - p.wsbuf[i] = ignore - droppedLinebreak = true } - j = i - break + case newline, formfeed: + p.wsbuf[i] = ignore + droppedLinebreak = prev == nil // record only if first comment of a group } - p.writeWhitespace(j) + j = i + break } + p.writeWhitespace(j) // determine number of linebreaks before the comment n := 0 @@ -678,7 +677,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro var last *ast.Comment for p.commentBefore(next) { for _, c := range p.comment.List { - p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok.IsKeyword()) + p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok) p.writeComment(c) last = c } @@ -1011,18 +1010,18 @@ func (p *printer) printNode(node interface{}) error { // format node switch n := node.(type) { case ast.Expr: - p.expr(n, ignoreMultiLine) + p.expr(n) case ast.Stmt: // 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) + p.stmt(n, false) case ast.Decl: - p.decl(n, ignoreMultiLine) + p.decl(n) case ast.Spec: - p.spec(n, 1, false, ignoreMultiLine) + p.spec(n, 1, false) case *ast.File: p.file(n) default: diff --git a/libgo/go/go/printer/printer_test.go b/libgo/go/go/printer/printer_test.go index 2d4f61356c1..497d671f240 100644 --- a/libgo/go/go/printer/printer_test.go +++ b/libgo/go/go/printer/printer_test.go @@ -154,15 +154,12 @@ var data = []entry{ } func TestFiles(t *testing.T) { - for i, e := range data { + for _, e := range data { source := filepath.Join(dataDir, e.source) golden := filepath.Join(dataDir, e.golden) check(t, source, golden, e.mode) // TODO(gri) check that golden is idempotent //check(t, golden, golden, e.mode) - if testing.Short() && i >= 3 { - break - } } } diff --git a/libgo/go/go/printer/testdata/comments.golden b/libgo/go/go/printer/testdata/comments.golden index 4c6f1ab8274..d9aa2d82f7d 100644 --- a/libgo/go/go/printer/testdata/comments.golden +++ b/libgo/go/go/printer/testdata/comments.golden @@ -168,6 +168,91 @@ func typeswitch(x interface{}) { // this comment should not be indented } +// +// Indentation of comments after possibly indented multi-line constructs +// (test cases for issue 3147). +// + +func _() { + s := 1 + + 2 + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + + // should be indented like s + _ = 0 +} + +// Test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // should be aligned with f() + f() +} + +// Modified test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // may not be aligned with f() (source is not aligned) + f() +} + +// +// Test cases for alignment of lines in general comments. +// + func _() { /* freestanding comment aligned line diff --git a/libgo/go/go/printer/testdata/comments.input b/libgo/go/go/printer/testdata/comments.input index c0f8cca3a92..6084b3fe450 100644 --- a/libgo/go/go/printer/testdata/comments.input +++ b/libgo/go/go/printer/testdata/comments.input @@ -171,6 +171,91 @@ func typeswitch(x interface{}) { // this comment should not be indented } +// +// Indentation of comments after possibly indented multi-line constructs +// (test cases for issue 3147). +// + +func _() { + s := 1 + + 2 +// should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + +// should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + + // should be indented like s + _ = 0 +} + +// Test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // should be aligned with f() + f() +} + +// Modified test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // may not be aligned with f() (source is not aligned) + f() +} + +// +// Test cases for alignment of lines in general comments. +// + func _() { /* freestanding comment aligned line diff --git a/libgo/go/go/printer/testdata/declarations.golden b/libgo/go/go/printer/testdata/declarations.golden index 928b8ce0a9f..7ed7cb61ae5 100644 --- a/libgo/go/go/printer/testdata/declarations.golden +++ b/libgo/go/go/printer/testdata/declarations.golden @@ -83,13 +83,13 @@ import ( // more import examples import ( "xxx" - "much longer name" // comment - "short name" // comment + "much_longer_name" // comment + "short_name" // comment ) import ( _ "xxx" - "much longer name" // comment + "much_longer_name" // comment ) import ( @@ -500,7 +500,7 @@ type _ struct { type _ struct { a, b, - c, d int // this line should be indented + c, d int // this line should be indented u, v, w, x float // this line should be indented p, q, r, s float // this line should be indented @@ -562,7 +562,7 @@ var a2, b2, var ( a3, b3, - c3, d3 int // this line should be indented + c3, d3 int // this line should be indented a4, b4, c4 int // this line should be indented ) diff --git a/libgo/go/go/printer/testdata/declarations.input b/libgo/go/go/printer/testdata/declarations.input index 68f90308a36..df8c2b167e1 100644 --- a/libgo/go/go/printer/testdata/declarations.input +++ b/libgo/go/go/printer/testdata/declarations.input @@ -84,13 +84,13 @@ import ( // more import examples import ( "xxx" - "much longer name" // comment - "short name" // comment + "much_longer_name" // comment + "short_name" // comment ) import ( _ "xxx" - "much longer name" // comment + "much_longer_name" // comment ) import ( diff --git a/libgo/go/go/printer/testdata/expressions.golden b/libgo/go/go/printer/testdata/expressions.golden index 95fdd95ffbb..45fa4d97a4a 100644 --- a/libgo/go/go/printer/testdata/expressions.golden +++ b/libgo/go/go/printer/testdata/expressions.golden @@ -625,3 +625,25 @@ func f() { log.Fatal(err) } } + +// Handle multi-line argument lists ending in ... correctly. +// Was issue 3130. +func _() { + _ = append(s, a...) + _ = append( + s, a...) + _ = append(s, + a...) + _ = append( + s, + a...) + _ = append(s, a..., + ) + _ = append(s, + a..., + ) + _ = append( + s, + a..., + ) +} diff --git a/libgo/go/go/printer/testdata/expressions.input b/libgo/go/go/printer/testdata/expressions.input index d1131498352..f545c66057c 100644 --- a/libgo/go/go/printer/testdata/expressions.input +++ b/libgo/go/go/printer/testdata/expressions.input @@ -654,3 +654,25 @@ func f() { log.Fatal(err) } } + +// Handle multi-line argument lists ending in ... correctly. +// Was issue 3130. +func _() { + _ = append(s, a...) + _ = append( + s, a...) + _ = append(s, + a...) + _ = append( + s, + a...) + _ = append(s, a..., + ) + _ = append(s, + a..., + ) + _ = append( + s, + a..., + ) +} diff --git a/libgo/go/go/printer/testdata/expressions.raw b/libgo/go/go/printer/testdata/expressions.raw index 3442ba9b950..87a4b00836d 100644 --- a/libgo/go/go/printer/testdata/expressions.raw +++ b/libgo/go/go/printer/testdata/expressions.raw @@ -625,3 +625,25 @@ func f() { log.Fatal(err) } } + +// Handle multi-line argument lists ending in ... correctly. +// Was issue 3130. +func _() { + _ = append(s, a...) + _ = append( + s, a...) + _ = append(s, + a...) + _ = append( + s, + a...) + _ = append(s, a..., + ) + _ = append(s, + a..., + ) + _ = append( + s, + a..., + ) +} diff --git a/libgo/go/go/printer/testdata/parser.go b/libgo/go/go/printer/testdata/parser.go index c85297f5831..dba8bbd4351 100644 --- a/libgo/go/go/printer/testdata/parser.go +++ b/libgo/go/go/printer/testdata/parser.go @@ -52,7 +52,7 @@ type parser struct { // Non-syntactic parser control exprLev int // < 0: in control clause, >= 0: in expression - // Ordinary identifer scopes + // Ordinary identifier scopes pkgScope *ast.Scope // pkgScope.Outer == nil topScope *ast.Scope // top-most scope; may be pkgScope unresolved []*ast.Ident // unresolved identifiers diff --git a/libgo/go/go/printer/testdata/statements.golden b/libgo/go/go/printer/testdata/statements.golden index 90e1743557d..ffca21edba9 100644 --- a/libgo/go/go/printer/testdata/statements.golden +++ b/libgo/go/go/printer/testdata/statements.golden @@ -8,6 +8,82 @@ var expr bool func use(x interface{}) {} +// Formatting of multi-line return statements. +func _f() { + return + return x, y, z + return T{} + return T{1, 2, 3}, + x, y, z + return T{1, 2, 3}, + x, y, + z + return T{1, + 2, + 3} + return T{1, + 2, + 3, + } + return T{ + 1, + 2, + 3} + return T{ + 1, + 2, + 3, + } + return T{ + 1, + T{1, 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, + 3}, + 3, + } + return T{ + 1, + 2, + }, + nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, + nil + return x + y + + z + return func() {} + return func() { + _ = 0 + }, T{ + 1, 2, + } + return func() { + _ = 0 + } + return func() T { + return T{ + 1, 2, + } + } +} + // Formatting of if-statement headers. func _() { if true { diff --git a/libgo/go/go/printer/testdata/statements.input b/libgo/go/go/printer/testdata/statements.input index 86a753c5ad9..99945e9551a 100644 --- a/libgo/go/go/printer/testdata/statements.input +++ b/libgo/go/go/printer/testdata/statements.input @@ -8,6 +8,82 @@ var expr bool func use(x interface{}) {} +// Formatting of multi-line return statements. +func _f() { + return + return x, y, z + return T{} + return T{1, 2, 3}, + x, y, z + return T{1, 2, 3}, + x, y, + z + return T{1, + 2, + 3} + return T{1, + 2, + 3, + } + return T{ + 1, + 2, + 3} + return T{ + 1, + 2, + 3, + } + return T{ + 1, + T{1, 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, + 3}, + 3, + } + return T{ + 1, + 2, + }, + nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, + nil + return x + y + + z + return func() {} + return func() { + _ = 0 + }, T{ + 1, 2, + } + return func() { + _ = 0 + } + return func() T { + return T { + 1, 2, + } + } +} + // Formatting of if-statement headers. func _() { if true {} diff --git a/libgo/go/html/template/doc.go b/libgo/go/html/template/doc.go index 7f60f3b9680..3699ea1a91c 100644 --- a/libgo/go/html/template/doc.go +++ b/libgo/go/html/template/doc.go @@ -19,7 +19,7 @@ to parse and execute HTML templates safely. tmpl, err := template.New("name").Parse(...) // Error checking elided - err = tmpl.Execute(out, "Foo", data) + err = tmpl.Execute(out, data) If successful, tmpl will now be injection-safe. Otherwise, err is an error defined in the docs for ErrorCode. diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go index 02fa3eaad6b..a058e20d7b3 100644 --- a/libgo/go/html/template/escape.go +++ b/libgo/go/html/template/escape.go @@ -593,7 +593,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context { } } for j := i; j < end; j++ { - if s[j] == '<' && !bytes.HasPrefix(s[j:], doctypeBytes) { + if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) { b.Write(s[written:j]) b.WriteString("<") written = j + 1 diff --git a/libgo/go/html/template/escape_test.go b/libgo/go/html/template/escape_test.go index 70cada3f50b..2bbb1b1bc94 100644 --- a/libgo/go/html/template/escape_test.go +++ b/libgo/go/html/template/escape_test.go @@ -223,14 +223,14 @@ func TestEscape(t *testing.T) { `<button onclick='alert("\x3cHello\x3e")'>`, }, { - "badMarshaller", + "badMarshaler", `<button onclick='alert(1/{{.B}}in numbers)'>`, `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character 'f' looking for beginning of object key string */null in numbers)'>`, }, { - "jsMarshaller", + "jsMarshaler", `<button onclick='alert({{.M}})'>`, - `<button onclick='alert({"<foo>":"O'Reilly"})'>`, + `<button onclick='alert({"\u003cfoo\u003e":"O'Reilly"})'>`, }, { "jsStrNotUnderEscaped", @@ -432,6 +432,11 @@ func TestEscape(t *testing.T) { "<!DOCTYPE html>Hello, World!", }, { + "HTML doctype not case-insensitive", + "<!doCtYPE htMl>Hello, World!", + "<!doCtYPE htMl>Hello, World!", + }, + { "No doctype injection", `<!{{"DOCTYPE"}}`, "<!DOCTYPE", diff --git a/libgo/go/html/template/html.go b/libgo/go/html/template/html.go index 7b77d6531ab..36c88e23e6e 100644 --- a/libgo/go/html/template/html.go +++ b/libgo/go/html/template/html.go @@ -134,7 +134,7 @@ var htmlNospaceNormReplacementTable = []string{ '`': "`", } -// htmlReplacer returns s with runes replaced acccording to replacementTable +// htmlReplacer returns s with runes replaced according to replacementTable // and when badRunes is true, certain bad runes are allowed through unescaped. func htmlReplacer(s string, replacementTable []string, badRunes bool) string { written, b := 0, new(bytes.Buffer) diff --git a/libgo/go/io/io.go b/libgo/go/io/io.go index bbfa6c2b2aa..7074834d613 100644 --- a/libgo/go/io/io.go +++ b/libgo/go/io/io.go @@ -6,6 +6,10 @@ // Its primary job is to wrap existing implementations of such primitives, // such as those in package os, into shared public interfaces that // abstract the functionality, plus some other related primitives. +// +// Because these interfaces and primitives wrap lower-level operations with +// various implementations, unless otherwise informed clients should not +// assume they are safe for parallel execution. package io import ( @@ -156,6 +160,9 @@ type WriterTo interface { // If ReadAt is reading from an input source with a seek offset, // ReadAt should not affect nor be affected by the underlying // seek offset. +// +// Clients of ReadAt can execute parallel ReadAt calls on the +// same input source. type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) } diff --git a/libgo/go/io/pipe.go b/libgo/go/io/pipe.go index cf05e0c1ad7..f3f0f175706 100644 --- a/libgo/go/io/pipe.go +++ b/libgo/go/io/pipe.go @@ -175,6 +175,10 @@ func (w *PipeWriter) CloseWithError(err error) error { // with code expecting an io.Writer. // Reads on one end are matched with writes on the other, // copying data directly between the two; there is no internal buffering. +// It is safe to call Read and Write in parallel with each other or with +// Close. Close will complete once pending I/O is done. Parallel calls to +// Read, and parallel calls to Write, are also safe: +// the individual calls will be gated sequentially. func Pipe() (*PipeReader, *PipeWriter) { p := new(pipe) p.rwait.L = &p.l diff --git a/libgo/go/math/const.go b/libgo/go/math/const.go index edb14f6fa43..f1247c383fd 100644 --- a/libgo/go/math/const.go +++ b/libgo/go/math/const.go @@ -27,11 +27,11 @@ const ( // Max is the largest finite value representable by the type. // SmallestNonzero is the smallest positive, non-zero value representable by the type. const ( - MaxFloat32 = 3.40282346638528859811704183484516925440e+38 /* 2**127 * (2**24 - 1) / 2**23 */ - SmallestNonzeroFloat32 = 1.401298464324817070923729583289916131280e-45 /* 1 / 2**(127 - 1 + 23) */ + MaxFloat32 = 3.40282346638528859811704183484516925440e+38 // 2**127 * (2**24 - 1) / 2**23 + SmallestNonzeroFloat32 = 1.401298464324817070923729583289916131280e-45 // 1 / 2**(127 - 1 + 23) - MaxFloat64 = 1.797693134862315708145274237317043567981e+308 /* 2**1023 * (2**53 - 1) / 2**52 */ - SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 /* 1 / 2**(1023 - 1 + 52) */ + MaxFloat64 = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52 + SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52) ) // Integer limit values. diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index 61b8911fcf9..10ca5faf707 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -69,7 +69,7 @@ func resolveNetAddr(op, net, addr string) (afnet string, a Addr, err error) { // // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), // "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" -// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket". +// (IPv4-only), "ip6" (IPv6-only), "unix" and "unixpacket". // // For TCP and UDP networks, addresses have the form host:port. // If host is a literal IPv6 address, it must be enclosed diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index e5a797e13bf..5f5aea146a6 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -5,6 +5,8 @@ package net import ( + "flag" + "regexp" "runtime" "testing" "time" @@ -128,3 +130,82 @@ func TestSelfConnect(t *testing.T) { } } } + +var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors") + +type DialErrorTest struct { + Net string + Raddr string + Pattern string +} + +var dialErrorTests = []DialErrorTest{ + { + "datakit", "mh/astro/r70", + "dial datakit mh/astro/r70: unknown network datakit", + }, + { + "tcp", "127.0.0.1:☺", + "dial tcp 127.0.0.1:☺: unknown port tcp/☺", + }, + { + "tcp", "no-such-name.google.com.:80", + "dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)", + }, + { + "tcp", "no-such-name.no-such-top-level-domain.:80", + "dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)", + }, + { + "tcp", "no-such-name:80", + `dial tcp no-such-name:80: lookup no-such-name\.(.*\.)?( on .*)?: no (.*)`, + }, + { + "tcp", "mh/astro/r70:http", + "dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name", + }, + { + "unix", "/etc/file-not-found", + "dial unix /etc/file-not-found: no such file or directory", + }, + { + "unix", "/etc/", + "dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)", + }, + { + "unixpacket", "/etc/file-not-found", + "dial unixpacket /etc/file-not-found: no such file or directory", + }, + { + "unixpacket", "/etc/", + "dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)", + }, +} + +var duplicateErrorPattern = `dial (.*) dial (.*)` + +func TestDialError(t *testing.T) { + if !*runErrorTest { + t.Logf("test disabled; use -run_error_test to enable") + return + } + for i, tt := range dialErrorTests { + c, err := Dial(tt.Net, tt.Raddr) + if c != nil { + c.Close() + } + if err == nil { + t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern) + continue + } + s := err.Error() + match, _ := regexp.MatchString(tt.Pattern, s) + if !match { + t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern) + } + match, _ = regexp.MatchString(duplicateErrorPattern, s) + if match { + t.Errorf("#%d: %q, duplicate error return from Dial", i, s) + } + } +} diff --git a/libgo/go/net/dialgoogle_test.go b/libgo/go/net/dialgoogle_test.go index 14356da4ce3..03c4499720f 100644 --- a/libgo/go/net/dialgoogle_test.go +++ b/libgo/go/net/dialgoogle_test.go @@ -42,9 +42,8 @@ func doDial(t *testing.T, network, addr string) { } func TestLookupCNAME(t *testing.T) { - if testing.Short() { - // Don't use external network. - t.Logf("skipping external network test during -short") + if testing.Short() || !*testExternal { + t.Logf("skipping test to avoid external network") return } cname, err := LookupCNAME("www.google.com") @@ -67,9 +66,8 @@ var googleaddrsipv4 = []string{ } func TestDialGoogleIPv4(t *testing.T) { - if testing.Short() { - // Don't use external network. - t.Logf("skipping external network test during -short") + if testing.Short() || !*testExternal { + t.Logf("skipping test to avoid external network") return } @@ -124,9 +122,8 @@ var googleaddrsipv6 = []string{ } func TestDialGoogleIPv6(t *testing.T) { - if testing.Short() { - // Don't use external network. - t.Logf("skipping external network test during -short") + if testing.Short() || !*testExternal { + t.Logf("skipping test to avoid external network") return } // Only run tcp6 if the kernel will take it. diff --git a/libgo/go/net/http/cgi/child.go b/libgo/go/net/http/cgi/child.go index e6c3ef911ab..1ba7bec5fc5 100644 --- a/libgo/go/net/http/cgi/child.go +++ b/libgo/go/net/http/cgi/child.go @@ -144,6 +144,7 @@ func Serve(handler http.Handler) error { bufw: bufio.NewWriter(os.Stdout), } handler.ServeHTTP(rw, req) + rw.Write(nil) // make sure a response is sent if err = rw.bufw.Flush(); err != nil { return err } diff --git a/libgo/go/net/http/cgi/host_test.go b/libgo/go/net/http/cgi/host_test.go index fec35b72f99..859911f9805 100644 --- a/libgo/go/net/http/cgi/host_test.go +++ b/libgo/go/net/http/cgi/host_test.go @@ -41,6 +41,7 @@ func runCgiTest(t *testing.T, h *Handler, httpreq string, expectedMap map[string // Make a map to hold the test map that the CGI returns. m := make(map[string]string) + m["_body"] = rw.Body.String() linesRead := 0 readlines: for { diff --git a/libgo/go/net/http/cgi/matryoshka_test.go b/libgo/go/net/http/cgi/matryoshka_test.go index 1a44df20401..e1a78c8f62f 100644 --- a/libgo/go/net/http/cgi/matryoshka_test.go +++ b/libgo/go/net/http/cgi/matryoshka_test.go @@ -51,6 +51,22 @@ func TestHostingOurselves(t *testing.T) { } } +// Test that a child handler only writing headers works. +func TestChildOnlyHeaders(t *testing.T) { + h := &Handler{ + Path: os.Args[0], + Root: "/test.go", + Args: []string{"-test.run=TestBeChildCGIProcess"}, + } + expectedMap := map[string]string{ + "_body": "", + } + replay := runCgiTest(t, h, "GET /test.go?no-body=1 HTTP/1.0\nHost: example.com\n\n", expectedMap) + if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected { + t.Errorf("got a X-Test-Header of %q; expected %q", got, expected) + } +} + // Note: not actually a test. func TestBeChildCGIProcess(t *testing.T) { if os.Getenv("REQUEST_METHOD") == "" { @@ -59,8 +75,11 @@ func TestBeChildCGIProcess(t *testing.T) { } Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("X-Test-Header", "X-Test-Value") - fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n") req.ParseForm() + if req.FormValue("no-body") == "1" { + return + } + fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n") for k, vv := range req.Form { for _, v := range vv { fmt.Fprintf(rw, "param-%s=%s\n", k, v) diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go index 0409008b675..5aa93ce5837 100644 --- a/libgo/go/net/http/fs_test.go +++ b/libgo/go/net/http/fs_test.go @@ -152,12 +152,19 @@ func TestFileServerCleans(t *testing.T) { } } +func mustRemoveAll(dir string) { + err := os.RemoveAll(dir) + if err != nil { + panic(err) + } +} + func TestFileServerImplicitLeadingSlash(t *testing.T) { tempDir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("TempDir: %v", err) } - defer os.RemoveAll(tempDir) + defer mustRemoveAll(tempDir) if err := ioutil.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } @@ -172,6 +179,7 @@ func TestFileServerImplicitLeadingSlash(t *testing.T) { if err != nil { t.Fatalf("ReadAll %s: %v", suffix, err) } + res.Body.Close() return string(b) } if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") { diff --git a/libgo/go/net/http/httptest/server.go b/libgo/go/net/http/httptest/server.go index 8d911f7575b..57cf0c9417d 100644 --- a/libgo/go/net/http/httptest/server.go +++ b/libgo/go/net/http/httptest/server.go @@ -13,6 +13,7 @@ import ( "net" "net/http" "os" + "sync" ) // A Server is an HTTP server listening on a system-chosen port on the @@ -25,6 +26,10 @@ type Server struct { // Config may be changed after calling NewUnstartedServer and // before Start or StartTLS. Config *http.Server + + // wg counts the number of outstanding HTTP requests on this server. + // Close blocks until all requests are finished. + wg sync.WaitGroup } // historyListener keeps track of all connections that it's ever @@ -93,6 +98,7 @@ func (s *Server) Start() { } s.Listener = &historyListener{s.Listener, make([]net.Conn, 0)} s.URL = "http://" + s.Listener.Addr().String() + s.wrapHandler() go s.Config.Serve(s.Listener) if *serve != "" { fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL) @@ -118,9 +124,21 @@ func (s *Server) StartTLS() { s.Listener = &historyListener{tlsListener, make([]net.Conn, 0)} s.URL = "https://" + s.Listener.Addr().String() + s.wrapHandler() go s.Config.Serve(s.Listener) } +func (s *Server) wrapHandler() { + h := s.Config.Handler + if h == nil { + h = http.DefaultServeMux + } + s.Config.Handler = &waitGroupHandler{ + s: s, + h: h, + } +} + // NewTLSServer starts and returns a new Server using TLS. // The caller should call Close when finished, to shut it down. func NewTLSServer(handler http.Handler) *Server { @@ -129,9 +147,11 @@ func NewTLSServer(handler http.Handler) *Server { return ts } -// Close shuts down the server. +// Close shuts down the server and blocks until all outstanding +// requests on this server have completed. func (s *Server) Close() { s.Listener.Close() + s.wg.Wait() } // CloseClientConnections closes any currently open HTTP connections @@ -146,6 +166,20 @@ func (s *Server) CloseClientConnections() { } } +// waitGroupHandler wraps a handler, incrementing and decrementing a +// sync.WaitGroup on each request, to enable Server.Close to block +// until outstanding requests are finished. +type waitGroupHandler struct { + s *Server + h http.Handler // non-nil +} + +func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.s.wg.Add(1) + defer h.s.wg.Done() // a defer, in case ServeHTTP below panics + h.h.ServeHTTP(w, r) +} + // localhostCert is a PEM-encoded TLS cert with SAN DNS names // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end // of ASN.1 time). diff --git a/libgo/go/net/http/httputil/dump.go b/libgo/go/net/http/httputil/dump.go index c853066f1cf..892ef4eded0 100644 --- a/libgo/go/net/http/httputil/dump.go +++ b/libgo/go/net/http/httputil/dump.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "net" "net/http" + "net/url" "strings" "time" ) @@ -59,6 +60,19 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { } } + // Since we're using the actual Transport code to write the request, + // switch to http so the Transport doesn't try to do an SSL + // negotiation with our dumpConn and its bytes.Buffer & pipe. + // The wire format for https and http are the same, anyway. + reqSend := req + if req.URL.Scheme == "https" { + reqSend = new(http.Request) + *reqSend = *req + reqSend.URL = new(url.URL) + *reqSend.URL = *req.URL + reqSend.URL.Scheme = "http" + } + // Use the actual Transport code to record what we would send // on the wire, but not using TCP. Use a Transport with a // customer dialer that returns a fake net.Conn that waits @@ -79,7 +93,7 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { }, } - _, err := t.RoundTrip(req) + _, err := t.RoundTrip(reqSend) req.Body = save if err != nil { diff --git a/libgo/go/net/http/httputil/dump_test.go b/libgo/go/net/http/httputil/dump_test.go index 819efb5847b..5afe9ba74e3 100644 --- a/libgo/go/net/http/httputil/dump_test.go +++ b/libgo/go/net/http/httputil/dump_test.go @@ -71,6 +71,18 @@ var dumpTests = []dumpTest{ "User-Agent: Go http package\r\n" + "Accept-Encoding: gzip\r\n\r\n", }, + + // Test that an https URL doesn't try to do an SSL negotiation + // with a bytes.Buffer and hang with all goroutines not + // runnable. + { + Req: *mustNewRequest("GET", "https://example.com/foo", nil), + + WantDumpOut: "GET /foo HTTP/1.1\r\n" + + "Host: example.com\r\n" + + "User-Agent: Go http package\r\n" + + "Accept-Encoding: gzip\r\n\r\n", + }, } func TestDumpRequest(t *testing.T) { diff --git a/libgo/go/net/http/httputil/persist.go b/libgo/go/net/http/httputil/persist.go index 32f4662cc0e..507938acac7 100644 --- a/libgo/go/net/http/httputil/persist.go +++ b/libgo/go/net/http/httputil/persist.go @@ -383,7 +383,7 @@ func (cc *ClientConn) Read(req *http.Request) (resp *http.Response, err error) { // Make sure body is fully consumed, even if user does not call body.Close if lastbody != nil { // body.Close is assumed to be idempotent and multiple calls to - // it should return the error that its first invokation + // it should return the error that its first invocation // returned. err = lastbody.Close() if err != nil { diff --git a/libgo/go/net/http/lex.go b/libgo/go/net/http/lex.go index 93b67e70176..ffb393ccf6a 100644 --- a/libgo/go/net/http/lex.go +++ b/libgo/go/net/http/lex.go @@ -14,14 +14,6 @@ func isSeparator(c byte) bool { return false } -func isSpace(c byte) bool { - switch c { - case ' ', '\t', '\r', '\n': - return true - } - return false -} - func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 } func isChar(c byte) bool { return 0 <= c && c <= 127 } diff --git a/libgo/go/net/http/sniff_test.go b/libgo/go/net/http/sniff_test.go index 6efa8ce1ca2..8ab72ac23f9 100644 --- a/libgo/go/net/http/sniff_test.go +++ b/libgo/go/net/http/sniff_test.go @@ -129,9 +129,10 @@ func TestSniffWriteSize(t *testing.T) { })) 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)) + res, err := Get(fmt.Sprintf("%s/?size=%d", ts.URL, size)) if err != nil { t.Fatalf("size %d: %v", size, err) } + res.Body.Close() } } diff --git a/libgo/go/net/http/status.go b/libgo/go/net/http/status.go index b6e2d65c6a5..5af0b77c428 100644 --- a/libgo/go/net/http/status.go +++ b/libgo/go/net/http/status.go @@ -43,6 +43,7 @@ const ( StatusUnsupportedMediaType = 415 StatusRequestedRangeNotSatisfiable = 416 StatusExpectationFailed = 417 + StatusTeapot = 418 StatusInternalServerError = 500 StatusNotImplemented = 501 @@ -90,6 +91,7 @@ var statusText = map[int]string{ StatusUnsupportedMediaType: "Unsupported Media Type", StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", StatusExpectationFailed: "Expectation Failed", + StatusTeapot: "I'm a teapot", StatusInternalServerError: "Internal Server Error", StatusNotImplemented: "Not Implemented", diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index ef9564af9c5..3c8fe7f5b51 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -383,7 +383,7 @@ func fixTransferEncoding(requestMethod string, header Header) ([]string, error) // chunked encoding must always come first. for _, encoding := range encodings { encoding = strings.ToLower(strings.TrimSpace(encoding)) - // "identity" encoding is not recored + // "identity" encoding is not recorded if encoding == "identity" { break } diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index 3e48abafb5e..09579f8a093 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -76,7 +76,9 @@ type Transport struct { // ProxyFromEnvironment returns the URL of the proxy to use for a // given request, as indicated by the environment variables // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). -// Either URL or an error is returned. +// An error is returned if the proxy environment is invalid. +// A nil URL and nil error are returned if no proxy is defined in the +// environment, or a proxy should not be used for the given request. func ProxyFromEnvironment(req *Request) (*url.URL, error) { proxy := getenvEitherCase("HTTP_PROXY") if proxy == "" { @@ -86,7 +88,7 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) { return nil, nil } proxyURL, err := url.Parse(proxy) - if err != nil { + if err != nil || proxyURL.Scheme == "" { if u, err := url.Parse("http://" + proxy); err == nil { proxyURL = u err = nil diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index 1a629c1727c..cbb3884f9ea 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -16,6 +16,7 @@ import ( . "net/http" "net/http/httptest" "net/url" + "os" "runtime" "strconv" "strings" @@ -727,6 +728,36 @@ func TestTransportAltProto(t *testing.T) { } } +var proxyFromEnvTests = []struct { + env string + wanturl string + wanterr error +}{ + {"127.0.0.1:8080", "http://127.0.0.1:8080", nil}, + {"http://127.0.0.1:8080", "http://127.0.0.1:8080", nil}, + {"https://127.0.0.1:8080", "https://127.0.0.1:8080", nil}, + {"", "<nil>", nil}, +} + +func TestProxyFromEnvironment(t *testing.T) { + os.Setenv("HTTP_PROXY", "") + os.Setenv("http_proxy", "") + os.Setenv("NO_PROXY", "") + os.Setenv("no_proxy", "") + for i, tt := range proxyFromEnvTests { + os.Setenv("HTTP_PROXY", tt.env) + req, _ := NewRequest("GET", "http://example.com", nil) + url, err := ProxyFromEnvironment(req) + if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { + t.Errorf("%d. got error = %q, want %q", i, g, e) + continue + } + if got := fmt.Sprintf("%s", url); got != tt.wanturl { + t.Errorf("%d. got URL = %q, want %q", i, url, tt.wanturl) + } + } +} + // rgz is a gzip quine that uncompresses to itself. var rgz = []byte{ 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, diff --git a/libgo/go/net/http/triv.go b/libgo/go/net/http/triv.go index c88a0fbce73..269af0ca3d5 100644 --- a/libgo/go/net/http/triv.go +++ b/libgo/go/net/http/triv.go @@ -108,7 +108,6 @@ func DateServer(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, "fork/exec: %s\n", err) return } - defer p.Release() io.Copy(rw, r) wait, err := p.Wait(0) if err != nil { diff --git a/libgo/go/net/interface.go b/libgo/go/net/interface.go index 5e7b352ed50..f25d046c171 100644 --- a/libgo/go/net/interface.go +++ b/libgo/go/net/interface.go @@ -6,11 +6,7 @@ package net -import ( - "bytes" - "errors" - "fmt" -) +import "errors" var ( errInvalidInterface = errors.New("net: invalid interface") @@ -20,77 +16,6 @@ var ( errNoSuchMulticastInterface = errors.New("net: no such multicast interface") ) -// A HardwareAddr represents a physical hardware address. -type HardwareAddr []byte - -func (a HardwareAddr) String() string { - var buf bytes.Buffer - for i, b := range a { - if i > 0 { - buf.WriteByte(':') - } - fmt.Fprintf(&buf, "%02x", b) - } - return buf.String() -} - -// ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, or EUI-64 using one of the -// following formats: -// 01:23:45:67:89:ab -// 01:23:45:67:89:ab:cd:ef -// 01-23-45-67-89-ab -// 01-23-45-67-89-ab-cd-ef -// 0123.4567.89ab -// 0123.4567.89ab.cdef -func ParseMAC(s string) (hw HardwareAddr, err error) { - if len(s) < 14 { - goto error - } - - if s[2] == ':' || s[2] == '-' { - if (len(s)+1)%3 != 0 { - goto error - } - n := (len(s) + 1) / 3 - if n != 6 && n != 8 { - goto error - } - hw = make(HardwareAddr, n) - for x, i := 0, 0; i < n; i++ { - var ok bool - if hw[i], ok = xtoi2(s[x:], s[2]); !ok { - goto error - } - x += 3 - } - } else if s[4] == '.' { - if (len(s)+1)%5 != 0 { - goto error - } - n := 2 * (len(s) + 1) / 5 - if n != 6 && n != 8 { - goto error - } - hw = make(HardwareAddr, n) - for x, i := 0, 0; i < n; i += 2 { - var ok bool - if hw[i], ok = xtoi2(s[x:x+2], 0); !ok { - goto error - } - if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok { - goto error - } - x += 5 - } - } else { - goto error - } - return hw, nil - -error: - return nil, errors.New("invalid MAC address: " + s) -} - // Interface represents a mapping between network interface name // and index. It also represents network interface facility // information. diff --git a/libgo/go/net/interface_test.go b/libgo/go/net/interface_test.go index 769414e0eef..0a33bfdb517 100644 --- a/libgo/go/net/interface_test.go +++ b/libgo/go/net/interface_test.go @@ -6,8 +6,6 @@ package net import ( "bytes" - "reflect" - "strings" "testing" ) @@ -96,46 +94,3 @@ func testMulticastAddrs(t *testing.T, ifmat []Addr) { } } } - -var mactests = []struct { - in string - out HardwareAddr - err string -}{ - {"01:23:45:67:89:AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""}, - {"01-23-45-67-89-AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""}, - {"0123.4567.89AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""}, - {"ab:cd:ef:AB:CD:EF", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, ""}, - {"01.02.03.04.05.06", nil, "invalid MAC address"}, - {"01:02:03:04:05:06:", nil, "invalid MAC address"}, - {"x1:02:03:04:05:06", nil, "invalid MAC address"}, - {"01002:03:04:05:06", nil, "invalid MAC address"}, - {"01:02003:04:05:06", nil, "invalid MAC address"}, - {"01:02:03004:05:06", nil, "invalid MAC address"}, - {"01:02:03:04005:06", nil, "invalid MAC address"}, - {"01:02:03:04:05006", nil, "invalid MAC address"}, - {"01-02:03:04:05:06", nil, "invalid MAC address"}, - {"01:02-03-04-05-06", nil, "invalid MAC address"}, - {"0123:4567:89AF", nil, "invalid MAC address"}, - {"0123-4567-89AF", nil, "invalid MAC address"}, - {"01:23:45:67:89:AB:CD:EF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, - {"01-23-45-67-89-AB-CD-EF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, - {"0123.4567.89AB.CDEF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, -} - -func match(err error, s string) bool { - if s == "" { - return err == nil - } - return err != nil && strings.Contains(err.Error(), s) -} - -func TestParseMAC(t *testing.T) { - for _, tt := range mactests { - out, err := ParseMAC(tt.in) - if !reflect.DeepEqual(out, tt.out) || !match(err, tt.err) { - t.Errorf("ParseMAC(%q) = %v, %v, want %v, %v", tt.in, out, err, tt.out, - tt.err) - } - } -} diff --git a/libgo/go/net/lookup_plan9.go b/libgo/go/net/lookup_plan9.go index b08a9fb98e0..2c698304b21 100644 --- a/libgo/go/net/lookup_plan9.go +++ b/libgo/go/net/lookup_plan9.go @@ -76,7 +76,7 @@ func lookupProtocol(name string) (proto int, err error) { } func lookupHost(host string) (addrs []string, err error) { - // Use /net/cs insead of /net/dns because cs knows about + // Use /net/cs instead of /net/dns because cs knows about // host names in local network (e.g. from /lib/ndb/local) lines, err := queryCS("tcp", host, "1") if err != nil { diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go index 7b9ea844cd4..3a61dfb29c2 100644 --- a/libgo/go/net/lookup_test.go +++ b/libgo/go/net/lookup_test.go @@ -12,7 +12,7 @@ import ( "testing" ) -var testExternal = flag.Bool("external", false, "allow use of external networks during test") +var testExternal = flag.Bool("external", true, "allow use of external networks during long test") func TestGoogleSRV(t *testing.T) { if testing.Short() || !*testExternal { @@ -78,3 +78,40 @@ func TestGoogleDNSAddr(t *testing.T) { t.Errorf("no results") } } + +var revAddrTests = []struct { + Addr string + Reverse string + ErrPrefix string +}{ + {"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""}, + {"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""}, + {"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""}, + {"::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", ""}, + {"1::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa.", ""}, + {"1234:567::89a:bcde", "e.d.c.b.a.9.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.6.5.0.4.3.2.1.ip6.arpa.", ""}, + {"1234:567:fefe:bcbc:adad:9e4a:89a:bcde", "e.d.c.b.a.9.8.0.a.4.e.9.d.a.d.a.c.b.c.b.e.f.e.f.7.6.5.0.4.3.2.1.ip6.arpa.", ""}, + {"1.2.3", "", "unrecognized address"}, + {"1.2.3.4.5", "", "unrecognized address"}, + {"1234:567:bcbca::89a:bcde", "", "unrecognized address"}, + {"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"}, +} + +func TestReverseAddress(t *testing.T) { + for i, tt := range revAddrTests { + a, err := reverseaddr(tt.Addr) + if len(tt.ErrPrefix) > 0 && err == nil { + t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix) + continue + } + if len(tt.ErrPrefix) == 0 && err != nil { + t.Errorf("#%d: expected <nil>, got %q (error)", i, err) + } + if err != nil && err.(*DNSError).Err != tt.ErrPrefix { + t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err) + } + if a != tt.Reverse { + t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a) + } + } +} diff --git a/libgo/go/net/mac.go b/libgo/go/net/mac.go new file mode 100644 index 00000000000..e0637d00b74 --- /dev/null +++ b/libgo/go/net/mac.go @@ -0,0 +1,84 @@ +// 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. + +// MAC address manipulations + +package net + +import ( + "bytes" + "errors" + "fmt" +) + +// A HardwareAddr represents a physical hardware address. +type HardwareAddr []byte + +func (a HardwareAddr) String() string { + var buf bytes.Buffer + for i, b := range a { + if i > 0 { + buf.WriteByte(':') + } + fmt.Fprintf(&buf, "%02x", b) + } + return buf.String() +} + +// ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, or EUI-64 using one of the +// following formats: +// 01:23:45:67:89:ab +// 01:23:45:67:89:ab:cd:ef +// 01-23-45-67-89-ab +// 01-23-45-67-89-ab-cd-ef +// 0123.4567.89ab +// 0123.4567.89ab.cdef +func ParseMAC(s string) (hw HardwareAddr, err error) { + if len(s) < 14 { + goto error + } + + if s[2] == ':' || s[2] == '-' { + if (len(s)+1)%3 != 0 { + goto error + } + n := (len(s) + 1) / 3 + if n != 6 && n != 8 { + goto error + } + hw = make(HardwareAddr, n) + for x, i := 0, 0; i < n; i++ { + var ok bool + if hw[i], ok = xtoi2(s[x:], s[2]); !ok { + goto error + } + x += 3 + } + } else if s[4] == '.' { + if (len(s)+1)%5 != 0 { + goto error + } + n := 2 * (len(s) + 1) / 5 + if n != 6 && n != 8 { + goto error + } + hw = make(HardwareAddr, n) + for x, i := 0, 0; i < n; i += 2 { + var ok bool + if hw[i], ok = xtoi2(s[x:x+2], 0); !ok { + goto error + } + if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok { + goto error + } + x += 5 + } + } else { + goto error + } + return hw, nil + +error: + return nil, errors.New("invalid MAC address: " + s) +} diff --git a/libgo/go/net/mac_test.go b/libgo/go/net/mac_test.go new file mode 100644 index 00000000000..3837e740cf9 --- /dev/null +++ b/libgo/go/net/mac_test.go @@ -0,0 +1,54 @@ +// 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 net + +import ( + "reflect" + "strings" + "testing" +) + +var mactests = []struct { + in string + out HardwareAddr + err string +}{ + {"01:23:45:67:89:AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""}, + {"01-23-45-67-89-AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""}, + {"0123.4567.89AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""}, + {"ab:cd:ef:AB:CD:EF", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, ""}, + {"01.02.03.04.05.06", nil, "invalid MAC address"}, + {"01:02:03:04:05:06:", nil, "invalid MAC address"}, + {"x1:02:03:04:05:06", nil, "invalid MAC address"}, + {"01002:03:04:05:06", nil, "invalid MAC address"}, + {"01:02003:04:05:06", nil, "invalid MAC address"}, + {"01:02:03004:05:06", nil, "invalid MAC address"}, + {"01:02:03:04005:06", nil, "invalid MAC address"}, + {"01:02:03:04:05006", nil, "invalid MAC address"}, + {"01-02:03:04:05:06", nil, "invalid MAC address"}, + {"01:02-03-04-05-06", nil, "invalid MAC address"}, + {"0123:4567:89AF", nil, "invalid MAC address"}, + {"0123-4567-89AF", nil, "invalid MAC address"}, + {"01:23:45:67:89:AB:CD:EF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, + {"01-23-45-67-89-AB-CD-EF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, + {"0123.4567.89AB.CDEF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, +} + +func match(err error, s string) bool { + if s == "" { + return err == nil + } + return err != nil && strings.Contains(err.Error(), s) +} + +func TestParseMAC(t *testing.T) { + for _, tt := range mactests { + out, err := ParseMAC(tt.in) + if !reflect.DeepEqual(out, tt.out) || !match(err, tt.err) { + t.Errorf("ParseMAC(%q) = %v, %v, want %v, %v", tt.in, out, err, tt.out, + tt.err) + } + } +} diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go index 69d7ac87880..c1a90de0131 100644 --- a/libgo/go/net/net_test.go +++ b/libgo/go/net/net_test.go @@ -5,130 +5,12 @@ package net import ( - "flag" "io" - "regexp" "runtime" "testing" "time" ) -var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors") - -type DialErrorTest struct { - Net string - Raddr string - Pattern string -} - -var dialErrorTests = []DialErrorTest{ - { - "datakit", "mh/astro/r70", - "dial datakit mh/astro/r70: unknown network datakit", - }, - { - "tcp", "127.0.0.1:☺", - "dial tcp 127.0.0.1:☺: unknown port tcp/☺", - }, - { - "tcp", "no-such-name.google.com.:80", - "dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)", - }, - { - "tcp", "no-such-name.no-such-top-level-domain.:80", - "dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)", - }, - { - "tcp", "no-such-name:80", - `dial tcp no-such-name:80: lookup no-such-name\.(.*\.)?( on .*)?: no (.*)`, - }, - { - "tcp", "mh/astro/r70:http", - "dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name", - }, - { - "unix", "/etc/file-not-found", - "dial unix /etc/file-not-found: [nN]o such file or directory", - }, - { - "unix", "/etc/", - "dial unix /etc/: ([pP]ermission denied|socket operation on non-socket|connection refused)", - }, - { - "unixpacket", "/etc/file-not-found", - "dial unixpacket /etc/file-not-found: no such file or directory", - }, - { - "unixpacket", "/etc/", - "dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)", - }, -} - -var duplicateErrorPattern = `dial (.*) dial (.*)` - -func TestDialError(t *testing.T) { - if !*runErrorTest { - t.Logf("test disabled; use --run_error_test to enable") - return - } - for i, tt := range dialErrorTests { - c, err := Dial(tt.Net, tt.Raddr) - if c != nil { - c.Close() - } - if err == nil { - t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern) - continue - } - s := err.Error() - match, _ := regexp.MatchString(tt.Pattern, s) - if !match { - t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern) - } - match, _ = regexp.MatchString(duplicateErrorPattern, s) - if match { - t.Errorf("#%d: %q, duplicate error return from Dial", i, s) - } - } -} - -var revAddrTests = []struct { - Addr string - Reverse string - ErrPrefix string -}{ - {"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""}, - {"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""}, - {"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""}, - {"::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", ""}, - {"1::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa.", ""}, - {"1234:567::89a:bcde", "e.d.c.b.a.9.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.6.5.0.4.3.2.1.ip6.arpa.", ""}, - {"1234:567:fefe:bcbc:adad:9e4a:89a:bcde", "e.d.c.b.a.9.8.0.a.4.e.9.d.a.d.a.c.b.c.b.e.f.e.f.7.6.5.0.4.3.2.1.ip6.arpa.", ""}, - {"1.2.3", "", "unrecognized address"}, - {"1.2.3.4.5", "", "unrecognized address"}, - {"1234:567:bcbca::89a:bcde", "", "unrecognized address"}, - {"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"}, -} - -func TestReverseAddress(t *testing.T) { - for i, tt := range revAddrTests { - a, err := reverseaddr(tt.Addr) - if len(tt.ErrPrefix) > 0 && err == nil { - t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix) - continue - } - if len(tt.ErrPrefix) == 0 && err != nil { - t.Errorf("#%d: expected <nil>, got %q (error)", i, err) - } - if err != nil && err.(*DNSError).Err != tt.ErrPrefix { - t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err) - } - if a != tt.Reverse { - t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a) - } - } -} - func TestShutdown(t *testing.T) { if runtime.GOOS == "plan9" { return diff --git a/libgo/go/net/rpc/client.go b/libgo/go/net/rpc/client.go index 34f9ae317e2..f7abf21f157 100644 --- a/libgo/go/net/rpc/client.go +++ b/libgo/go/net/rpc/client.go @@ -140,7 +140,7 @@ func (client *Client) input() { } client.mutex.Unlock() client.sending.Unlock() - if err != io.EOF || !closing { + if err != io.EOF && !closing { log.Println("rpc: client protocol error:", err) } } diff --git a/libgo/go/net/rpc/server.go b/libgo/go/net/rpc/server.go index 920ae9137a6..1680e2f0d70 100644 --- a/libgo/go/net/rpc/server.go +++ b/libgo/go/net/rpc/server.go @@ -13,13 +13,19 @@ Only methods that satisfy these criteria will be made available for remote access; other methods will be ignored: - - the method name is exported, that is, begins with an upper case letter. - - the method receiver is exported or local (defined in the package - registering the service). - - the method has two arguments, both exported or local types. + - the method is exported. + - the method has two arguments, both exported (or builtin) types. - the method's second argument is a pointer. - the method has return type error. + In effect, the method must look schematically like + + func (t *T) MethodName(argType T1, replyType *T2) error + + where T, T1 and T2 can be marshaled by encoding/gob. + These requirements apply even if a different codec is used. + (In future, these requirements may soften for custom codecs.) + The method's first argument represents the arguments provided by the caller; the second argument represents the result parameters to be returned to the caller. The method's return value, if non-nil, is passed back as a string that the client @@ -36,10 +42,12 @@ call, a pointer containing the arguments, and a pointer to receive the result parameters. - Call waits for the remote call to complete; Go launches the call asynchronously - and returns a channel that will signal completion. + The Call method waits for the remote call to complete while the Go method + launches the call asynchronously and signals completion using the Call + structure's Done channel. - Package "gob" is used to transport the data. + Unless an explicit codec is set up, package encoding/gob is used to + transport the data. Here is a simple example. A server wishes to export an object of type Arith: @@ -256,6 +264,7 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro method := s.typ.Method(m) mtype := method.Type mname := method.Name + // Method must be exported. if method.PkgPath != "" { continue } @@ -267,7 +276,7 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro // First arg need not be a pointer. argType := mtype.In(1) if !isExportedOrBuiltinType(argType) { - log.Println(mname, "argument type not exported or local:", argType) + log.Println(mname, "argument type not exported:", argType) continue } // Second arg must be a pointer. @@ -276,15 +285,17 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro log.Println("method", mname, "reply type not a pointer:", replyType) continue } + // Reply type must be exported. if !isExportedOrBuiltinType(replyType) { - log.Println("method", mname, "reply type not exported or local:", replyType) + log.Println("method", mname, "reply type not exported:", replyType) continue } - // Method needs one out: error. + // Method needs one out. if mtype.NumOut() != 1 { log.Println("method", mname, "has wrong number of outs:", mtype.NumOut()) continue } + // The return type of the method must be error. if returnType := mtype.Out(0); returnType != typeOfError { log.Println("method", mname, "returns", returnType.String(), "not error") continue @@ -301,10 +312,10 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro return nil } -// A value sent as a placeholder for the response when the server receives an invalid request. -type InvalidRequest struct{} - -var invalidRequest = InvalidRequest{} +// A value sent as a placeholder for the server's response value when the server +// receives an invalid request. It is never decoded by the client since the Response +// contains an error when it is used. +var invalidRequest = struct{}{} func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply interface{}, codec ServerCodec, errmsg string) { resp := server.getResponse() diff --git a/libgo/go/net/sockoptip_netbsd.go b/libgo/go/net/sockoptip_netbsd.go new file mode 100644 index 00000000000..446d92aa343 --- /dev/null +++ b/libgo/go/net/sockoptip_netbsd.go @@ -0,0 +1,39 @@ +// Copyright 2012 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. + +// IP-level socket options for NetBSD + +package net + +import "syscall" + +func ipv4MulticastInterface(fd *netFD) (*Interface, error) { + // TODO: Implement this + return nil, syscall.EAFNOSUPPORT +} + +func setIPv4MulticastInterface(fd *netFD, ifi *Interface) error { + // TODO: Implement this + return syscall.EAFNOSUPPORT +} + +func ipv4MulticastLoopback(fd *netFD) (bool, error) { + // TODO: Implement this + return false, syscall.EAFNOSUPPORT +} + +func setIPv4MulticastLoopback(fd *netFD, v bool) error { + // TODO: Implement this + return syscall.EAFNOSUPPORT +} + +func ipv4ReceiveInterface(fd *netFD) (bool, error) { + // TODO: Implement this + return false, syscall.EAFNOSUPPORT +} + +func setIPv4ReceiveInterface(fd *netFD, v bool) error { + // TODO: Implement this + return syscall.EAFNOSUPPORT +} diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go index bae37c86b2e..ef350f0f946 100644 --- a/libgo/go/net/timeout_test.go +++ b/libgo/go/net/timeout_test.go @@ -40,7 +40,7 @@ func testTimeout(t *testing.T, network, addr string, readFrom bool) { errc <- fmt.Errorf("fd.%s on %s %s did not return 0, timeout: %v, %v", what, network, addr, n, err1) return } - if dt := t1.Sub(t0); dt < 50*time.Millisecond || dt > 250*time.Millisecond { + if dt := t1.Sub(t0); dt < 50*time.Millisecond || !testing.Short() && dt > 250*time.Millisecond { errc <- fmt.Errorf("fd.%s on %s %s took %s, expected 0.1s", what, network, addr, dt) return } diff --git a/libgo/go/net/udp_test.go b/libgo/go/net/udp_test.go index 6ba762b1f78..ea5fad41a53 100644 --- a/libgo/go/net/udp_test.go +++ b/libgo/go/net/udp_test.go @@ -38,18 +38,18 @@ func testWriteToConn(t *testing.T, raddr string) { _, err = c.(*UDPConn).WriteToUDP([]byte("Connection-oriented mode socket"), ra) if err == nil { - t.Fatal("WriteToUDP should be failed") + t.Fatal("WriteToUDP should fail") } if err != nil && err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteToUDP should be failed as ErrWriteToConnected: %v", err) + t.Fatalf("WriteToUDP should fail as ErrWriteToConnected: %v", err) } _, err = c.(*UDPConn).WriteTo([]byte("Connection-oriented mode socket"), ra) if err == nil { - t.Fatal("WriteTo should be failed") + t.Fatal("WriteTo should fail") } if err != nil && err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteTo should be failed as ErrWriteToConnected: %v", err) + t.Fatalf("WriteTo should fail as ErrWriteToConnected: %v", err) } _, err = c.Write([]byte("Connection-oriented mode socket")) @@ -82,6 +82,6 @@ func testWriteToPacketConn(t *testing.T, raddr string) { _, err = c.(*UDPConn).Write([]byte("Connection-less mode socket")) if err == nil { - t.Fatal("Write should be failed") + t.Fatal("Write should fail") } } diff --git a/libgo/go/os/doc.go b/libgo/go/os/doc.go index ef857c0429f..6a531e0d74e 100644 --- a/libgo/go/os/doc.go +++ b/libgo/go/os/doc.go @@ -4,6 +4,8 @@ package os +import "time" + // FindProcess looks for a running process by its pid. // The Process it returns can be used to obtain information // about the underlying operating system process. @@ -11,6 +13,76 @@ func FindProcess(pid int) (p *Process, err error) { return findProcess(pid) } +// StartProcess starts a new process with the program, arguments and attributes +// specified by name, argv and attr. +// +// StartProcess is a low-level interface. The os/exec package provides +// higher-level interfaces. +// +// If there is an error, it will be of type *PathError. +func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) { + return startProcess(name, argv, attr) +} + +// Release releases any resources associated with the Process p, +// rendering it unusable in the future. +// Release only needs to be called if Wait is not. +func (p *Process) Release() error { + return p.release() +} + +// Kill causes the Process to exit immediately. +func (p *Process) Kill() error { + return p.kill() +} + +// Wait waits for the Process to exit, and then returns a +// ProcessState describing its status and an error, if any. +// Wait releases any resources associated with the Process. +func (p *Process) Wait() (*ProcessState, error) { + return p.wait() +} + +// Signal sends a signal to the Process. +func (p *Process) Signal(sig Signal) error { + return p.signal(sig) +} + +// UserTime returns the user CPU time of the exited process and its children. +func (p *ProcessState) UserTime() time.Duration { + return p.userTime() +} + +// SystemTime returns the system CPU time of the exited process and its children. +func (p *ProcessState) SystemTime() time.Duration { + return p.systemTime() +} + +// Exited returns whether the program has exited. +func (p *ProcessState) Exited() bool { + return p.exited() +} + +// Success reports whether the program exited successfully, +// such as with exit status 0 on Unix. +func (p *ProcessState) Success() bool { + return p.success() +} + +// Sys returns system-dependent exit information about +// the process. Convert it to the appropriate underlying +// type, such as syscall.WaitStatus on Unix, to access its contents. +func (p *ProcessState) Sys() interface{} { + return p.sys() +} + +// SysUsage returns system-dependent resource usage information about +// the exited process. Convert it to the appropriate underlying +// type, such as *syscall.Rusage on Unix, to access its contents. +func (p *ProcessState) SysUsage() interface{} { + return p.sysUsage() +} + // Hostname returns the host name reported by the kernel. func Hostname() (name string, err error) { return hostname() diff --git a/libgo/go/os/exec.go b/libgo/go/os/exec.go index 37a0051c5dc..531b87ca556 100644 --- a/libgo/go/os/exec.go +++ b/libgo/go/os/exec.go @@ -13,7 +13,7 @@ import ( type Process struct { Pid int handle uintptr - done bool // process has been successfuly waited on + done bool // process has been successfully waited on } func newProcess(pid int, handle uintptr) *Process { diff --git a/libgo/go/os/exec_plan9.go b/libgo/go/os/exec_plan9.go index 1c9e2b997f8..41cc8c26f4c 100644 --- a/libgo/go/os/exec_plan9.go +++ b/libgo/go/os/exec_plan9.go @@ -11,10 +11,7 @@ import ( "time" ) -// StartProcess starts a new process with the program, arguments and attributes -// specified by name, argv and attr. -// If there is an error, it will be of type *PathError. -func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { +func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { sysattr := &syscall.ProcAttr{ Dir: attr.Dir, Env: attr.Env, @@ -40,7 +37,7 @@ func (note Plan9Note) String() string { return string(note) } -func (p *Process) Signal(sig Signal) error { +func (p *Process) signal(sig Signal) error { if p.done { return errors.New("os: process already finished") } @@ -54,8 +51,7 @@ func (p *Process) Signal(sig Signal) error { return e } -// Kill causes the Process to exit immediately. -func (p *Process) Kill() error { +func (p *Process) kill() error { f, e := OpenFile("/proc/"+itoa(p.Pid)+"/ctl", O_WRONLY, 0) if e != nil { return NewSyscallError("kill", e) @@ -65,9 +61,7 @@ func (p *Process) Kill() error { return e } -// Wait waits for the Process to exit or stop, and then returns a -// ProcessState describing its status and an error, if any. -func (p *Process) Wait() (ps *ProcessState, err error) { +func (p *Process) wait() (ps *ProcessState, err error) { var waitmsg syscall.Waitmsg if p.Pid == -1 { @@ -94,8 +88,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) { return ps, nil } -// Release releases any resources associated with the Process. -func (p *Process) Release() error { +func (p *Process) release() error { // NOOP for Plan 9. p.Pid = -1 // no need for a finalizer anymore @@ -108,7 +101,7 @@ func findProcess(pid int) (p *Process, err error) { return newProcess(pid, 0), nil } -// ProcessState stores information about process as reported by Wait. +// ProcessState stores information about a process, as reported by Wait. type ProcessState struct { pid int // The process's id. status *syscall.Waitmsg // System-dependent status info. @@ -119,40 +112,27 @@ func (p *ProcessState) Pid() int { return p.pid } -// Exited returns whether the program has exited. -func (p *ProcessState) Exited() bool { +func (p *ProcessState) exited() bool { return p.status.Exited() } -// Success reports whether the program exited successfully, -// such as with exit status 0 on Unix. -func (p *ProcessState) Success() bool { +func (p *ProcessState) success() bool { return p.status.ExitStatus() == 0 } -// Sys returns system-dependent exit information about -// the process. Convert it to the appropriate underlying -// type, such as *syscall.Waitmsg on Plan 9, to access its contents. -func (p *ProcessState) Sys() interface{} { +func (p *ProcessState) sys() interface{} { return p.status } -// SysUsage returns system-dependent resource usage information about -// the exited process. Convert it to the appropriate underlying -// type, such as *syscall.Waitmsg on Plan 9, to access its contents. -func (p *ProcessState) SysUsage() interface{} { +func (p *ProcessState) sysUsage() interface{} { return p.status } -// UserTime returns the user CPU time of the exited process and its children. -// It is always reported as 0 on Windows. -func (p *ProcessState) UserTime() time.Duration { +func (p *ProcessState) userTime() time.Duration { return time.Duration(p.status.Time[0]) * time.Millisecond } -// SystemTime returns the system CPU time of the exited process and its children. -// It is always reported as 0 on Windows. -func (p *ProcessState) SystemTime() time.Duration { +func (p *ProcessState) systemTime() time.Duration { return time.Duration(p.status.Time[1]) * time.Millisecond } diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go index 4a75cb67fb5..70351cfb313 100644 --- a/libgo/go/os/exec_posix.go +++ b/libgo/go/os/exec_posix.go @@ -10,14 +10,17 @@ import ( "syscall" ) -// StartProcess starts a new process with the program, arguments and attributes -// specified by name, argv and attr. -// -// StartProcess is a low-level interface. The os/exec package provides -// higher-level interfaces. -// -// If there is an error, it will be of type *PathError. -func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { +func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { + // Double-check existence of the directory we want + // to chdir into. We can make the error clearer this way. + if attr != nil && attr.Dir != "" { + if _, err := Stat(attr.Dir); err != nil { + pe := err.(*PathError) + pe.Op = "chdir" + return nil, pe + } + } + sysattr := &syscall.ProcAttr{ Dir: attr.Dir, Env: attr.Env, @@ -37,12 +40,11 @@ func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e return newProcess(pid, h), nil } -// Kill causes the Process to exit immediately. -func (p *Process) Kill() error { +func (p *Process) kill() error { return p.Signal(Kill) } -// ProcessState stores information about process as reported by Wait. +// ProcessState stores information about a process, as reported by Wait. type ProcessState struct { pid int // The process's id. status syscall.WaitStatus // System-dependent status info. @@ -54,28 +56,19 @@ func (p *ProcessState) Pid() int { return p.pid } -// Exited returns whether the program has exited. -func (p *ProcessState) Exited() bool { +func (p *ProcessState) exited() bool { return p.status.Exited() } -// Success reports whether the program exited successfully, -// such as with exit status 0 on Unix. -func (p *ProcessState) Success() bool { +func (p *ProcessState) success() bool { return p.status.ExitStatus() == 0 } -// Sys returns system-dependent exit information about -// the process. Convert it to the appropriate underlying -// type, such as syscall.WaitStatus on Unix, to access its contents. -func (p *ProcessState) Sys() interface{} { +func (p *ProcessState) sys() interface{} { return p.status } -// SysUsage returns system-dependent resource usage information about -// the exited process. Convert it to the appropriate underlying -// type, such as *syscall.Rusage on Unix, to access its contents. -func (p *ProcessState) SysUsage() interface{} { +func (p *ProcessState) sysUsage() interface{} { return p.rusage } diff --git a/libgo/go/os/exec_unix.go b/libgo/go/os/exec_unix.go index 8d000e9ef15..ecfe5353bc1 100644 --- a/libgo/go/os/exec_unix.go +++ b/libgo/go/os/exec_unix.go @@ -13,9 +13,7 @@ import ( "time" ) -// Wait waits for the Process to exit or stop, and then returns a -// ProcessState describing its status and an error, if any. -func (p *Process) Wait() (ps *ProcessState, err error) { +func (p *Process) wait() (ps *ProcessState, err error) { if p.Pid == -1 { return nil, syscall.EINVAL } @@ -36,8 +34,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) { return ps, nil } -// Signal sends a signal to the Process. -func (p *Process) Signal(sig Signal) error { +func (p *Process) signal(sig Signal) error { if p.done { return errors.New("os: process already finished") } @@ -51,8 +48,7 @@ func (p *Process) Signal(sig Signal) error { return nil } -// Release releases any resources associated with the Process. -func (p *Process) Release() error { +func (p *Process) release() error { // NOOP for unix. p.Pid = -1 // no need for a finalizer anymore @@ -65,12 +61,10 @@ func findProcess(pid int) (p *Process, err error) { return newProcess(pid, 0), nil } -// UserTime returns the user CPU time of the exited process and its children. -func (p *ProcessState) UserTime() time.Duration { +func (p *ProcessState) userTime() time.Duration { return time.Duration(p.rusage.Utime.Nano()) * time.Nanosecond } -// SystemTime returns the system CPU time of the exited process and its children. -func (p *ProcessState) SystemTime() time.Duration { +func (p *ProcessState) systemTime() time.Duration { return time.Duration(p.rusage.Stime.Nano()) * time.Nanosecond } diff --git a/libgo/go/os/exec_windows.go b/libgo/go/os/exec_windows.go index dab0dc97571..5beca4a6509 100644 --- a/libgo/go/os/exec_windows.go +++ b/libgo/go/os/exec_windows.go @@ -12,9 +12,7 @@ import ( "unsafe" ) -// Wait waits for the Process to exit or stop, and then returns a -// ProcessState describing its status and an error, if any. -func (p *Process) Wait() (ps *ProcessState, err error) { +func (p *Process) wait() (ps *ProcessState, err error) { s, e := syscall.WaitForSingleObject(syscall.Handle(p.handle), syscall.INFINITE) switch s { case syscall.WAIT_OBJECT_0: @@ -29,12 +27,22 @@ func (p *Process) Wait() (ps *ProcessState, err error) { if e != nil { return nil, NewSyscallError("GetExitCodeProcess", e) } + var u syscall.Rusage + e = syscall.GetProcessTimes(syscall.Handle(p.handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime) + if e != nil { + return nil, NewSyscallError("GetProcessTimes", e) + } p.done = true - return &ProcessState{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil + // NOTE(brainman): It seems that sometimes process is not dead + // when WaitForSingleObject returns. But we do not know any + // other way to wait for it. Sleeping for a while seems to do + // the trick sometimes. So we will sleep and smell the roses. + defer time.Sleep(5 * time.Millisecond) + defer p.Release() + return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil } -// Signal sends a signal to the Process. -func (p *Process) Signal(sig Signal) error { +func (p *Process) signal(sig Signal) error { if p.done { return errors.New("os: process already finished") } @@ -46,8 +54,7 @@ func (p *Process) Signal(sig Signal) error { return syscall.Errno(syscall.EWINDOWS) } -// Release releases any resources associated with the Process. -func (p *Process) Release() error { +func (p *Process) release() error { if p.handle == uintptr(syscall.InvalidHandle) { return syscall.EINVAL } @@ -85,14 +92,15 @@ func init() { } } -// UserTime returns the user CPU time of the exited process and its children. -// For now, it is always reported as 0 on Windows. -func (p *ProcessState) UserTime() time.Duration { - return 0 +func ftToDuration(ft *syscall.Filetime) time.Duration { + n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals + return time.Duration(n*100) * time.Nanosecond +} + +func (p *ProcessState) userTime() time.Duration { + return ftToDuration(&p.rusage.UserTime) } -// SystemTime returns the system CPU time of the exited process and its children. -// For now, it is always reported as 0 on Windows. -func (p *ProcessState) SystemTime() time.Duration { - return 0 +func (p *ProcessState) systemTime() time.Duration { + return ftToDuration(&p.rusage.KernelTime) } diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index 1c3d0172d34..4acf35d6755 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -25,7 +25,7 @@ // open file.go: no such file or directory // // The file's data can then be read into a slice of bytes. Read and -// Write take their byte counts from the length of the artument slice. +// Write take their byte counts from the length of the argument slice. // // data := make([]byte, 100) // count, err := file.Read(data) diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index cff35fcef75..aa01669a5da 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -528,7 +528,6 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) { if err != nil { t.Fatalf("StartProcess: %v", err) } - defer p.Release() w.Close() var b bytes.Buffer @@ -846,7 +845,6 @@ func run(t *testing.T, cmd []string) string { if err != nil { t.Fatal(err) } - defer p.Release() w.Close() var b bytes.Buffer @@ -1012,3 +1010,38 @@ func TestNilProcessStateString(t *testing.T) { t.Errorf("(*ProcessState)(nil).String() = %q, want %q", s, "<nil>") } } + +func TestSameFile(t *testing.T) { + fa, err := Create("a") + if err != nil { + t.Fatalf("Create(a): %v", err) + } + defer Remove(fa.Name()) + fa.Close() + fb, err := Create("b") + if err != nil { + t.Fatalf("Create(b): %v", err) + } + defer Remove(fb.Name()) + fb.Close() + + ia1, err := Stat("a") + if err != nil { + t.Fatalf("Stat(a): %v", err) + } + ia2, err := Stat("a") + if err != nil { + t.Fatalf("Stat(a): %v", err) + } + if !SameFile(ia1, ia2) { + t.Errorf("files should be same") + } + + ib, err := Stat("b") + if err != nil { + t.Fatalf("Stat(b): %v", err) + } + if SameFile(ia1, ib) { + t.Errorf("files should be different") + } +} diff --git a/libgo/go/path/filepath/path.go b/libgo/go/path/filepath/path.go index f468d33264b..cfe46981f13 100644 --- a/libgo/go/path/filepath/path.go +++ b/libgo/go/path/filepath/path.go @@ -139,6 +139,7 @@ func FromSlash(path string) string { // SplitList splits a list of paths joined by the OS-specific ListSeparator, // usually found in PATH or GOPATH environment variables. +// Unlike strings.Split, SplitList returns an empty slice when passed an empty string. func SplitList(path string) []string { if path == "" { return []string{} diff --git a/libgo/go/path/filepath/path_plan9.go b/libgo/go/path/filepath/path_plan9.go index 17b873f1a9b..cf028a75c52 100644 --- a/libgo/go/path/filepath/path_plan9.go +++ b/libgo/go/path/filepath/path_plan9.go @@ -17,7 +17,7 @@ func VolumeName(path string) string { return "" } -// HasPrefix tests whether the path p begins with prefix. +// HasPrefix exists for historical compatibility and should not be used. func HasPrefix(p, prefix string) bool { return strings.HasPrefix(p, prefix) } diff --git a/libgo/go/path/filepath/path_unix.go b/libgo/go/path/filepath/path_unix.go index c5ac71efe21..305e307272f 100644 --- a/libgo/go/path/filepath/path_unix.go +++ b/libgo/go/path/filepath/path_unix.go @@ -19,7 +19,7 @@ func VolumeName(path string) string { return "" } -// HasPrefix tests whether the path p begins with prefix. +// HasPrefix exists for historical compatibility and should not be used. func HasPrefix(p, prefix string) bool { return strings.HasPrefix(p, prefix) } diff --git a/libgo/go/path/filepath/path_windows.go b/libgo/go/path/filepath/path_windows.go index 9692fd978c5..1d1d23bfe7c 100644 --- a/libgo/go/path/filepath/path_windows.go +++ b/libgo/go/path/filepath/path_windows.go @@ -67,8 +67,7 @@ func VolumeName(path string) (v string) { return "" } -// HasPrefix tests whether the path p begins with prefix. -// It ignores case while comparing. +// HasPrefix exists for historical compatibility and should not be used. func HasPrefix(p, prefix string) bool { if strings.HasPrefix(p, prefix) { return true diff --git a/libgo/go/reflect/all_test.go b/libgo/go/reflect/all_test.go index 557fb8e843e..56744a514bc 100644 --- a/libgo/go/reflect/all_test.go +++ b/libgo/go/reflect/all_test.go @@ -629,6 +629,13 @@ type DeepEqualTest struct { eq bool } +// Simple functions for DeepEqual tests. +var ( + fn1 func() // nil. + fn2 func() // nil. + fn3 = func() { fn1() } // Not nil. +) + var deepEqualTests = []DeepEqualTest{ // Equalities {1, 1, true}, @@ -641,6 +648,7 @@ var deepEqualTests = []DeepEqualTest{ {Basic{1, 0.5}, Basic{1, 0.5}, true}, {error(nil), error(nil), true}, {map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true}, + {fn1, fn2, true}, // Inequalities {1, 2, false}, @@ -658,6 +666,8 @@ var deepEqualTests = []DeepEqualTest{ {map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false}, {nil, 1, false}, {1, nil, false}, + {fn1, fn3, false}, + {fn3, fn3, false}, // Nil vs empty: not the same. {[]int{}, []int(nil), false}, @@ -1737,3 +1747,15 @@ func isValid(v Value) { panic("zero Value") } } + +func TestAlias(t *testing.T) { + x := string("hello") + v := ValueOf(&x).Elem() + oldvalue := v.Interface() + v.SetString("world") + newvalue := v.Interface() + + if oldvalue != "hello" || newvalue != "world" { + t.Errorf("aliasing: old=%q new=%q, want hello, world", oldvalue, newvalue) + } +} diff --git a/libgo/go/reflect/deepequal.go b/libgo/go/reflect/deepequal.go index df5ec0a6099..c12e90f36cc 100644 --- a/libgo/go/reflect/deepequal.go +++ b/libgo/go/reflect/deepequal.go @@ -108,6 +108,12 @@ func deepValueEqual(v1, v2 Value, visited map[uintptr]*visit, depth int) (b bool } } return true + case Func: + if v1.IsNil() && v2.IsNil() { + return true + } + // Can't do better than this: + return false default: // Normal equality suffices return valueInterface(v1, false) == valueInterface(v2, false) @@ -117,8 +123,8 @@ func deepValueEqual(v1, v2 Value, visited map[uintptr]*visit, depth int) (b bool } // DeepEqual tests for deep equality. It uses normal == equality where possible -// but will scan members of arrays, slices, and fields of structs. It correctly -// handles recursive types. +// but will scan members of arrays, slices, maps, and fields of structs. It correctly +// handles recursive types. Functions are equal only if they are both nil. func DeepEqual(a1, a2 interface{}) bool { if a1 == nil || a2 == nil { return a1 == a2 diff --git a/libgo/go/reflect/type.go b/libgo/go/reflect/type.go index 9568e41136f..b1dd0a1fecd 100644 --- a/libgo/go/reflect/type.go +++ b/libgo/go/reflect/type.go @@ -12,7 +12,7 @@ // for that type. // // See "The Laws of Reflection" for an introduction to reflection in Go: -// http://blog.golang.org/2011/09/laws-of-reflection.html +// http://golang.org/doc/articles/laws_of_reflection.html package reflect import ( diff --git a/libgo/go/reflect/value.go b/libgo/go/reflect/value.go index ab56bdf56b6..b490e99f409 100644 --- a/libgo/go/reflect/value.go +++ b/libgo/go/reflect/value.go @@ -792,13 +792,15 @@ func (v Value) CanInterface() bool { return v.flag&(flagMethod|flagRO) == 0 } -// Interface returns v's value as an interface{}. +// Interface returns v's current value as an interface{}. +// It is equivalent to: +// var i interface{} = (v's underlying value) // If v is a method obtained by invoking Value.Method // (as opposed to Type.Method), Interface cannot return an // interface value, so it panics. // It also panics if the Value was obtained by accessing // unexported struct fields. -func (v Value) Interface() interface{} { +func (v Value) Interface() (i interface{}) { return valueInterface(v, true) } @@ -834,6 +836,16 @@ func valueInterface(v Value, safe bool) interface{} { var eface emptyInterface eface.typ = v.typ.runtimeType() eface.word = v.iword() + + if v.flag&flagIndir != 0 && v.typ.size > ptrSize { + // eface.word is a pointer to the actual data, + // which might be changed. We need to return + // a pointer to unchanging data, so make a copy. + ptr := unsafe_New(v.typ) + memmove(ptr, unsafe.Pointer(eface.word), v.typ.size) + eface.word = iword(ptr) + } + return *(*interface{})(unsafe.Pointer(&eface)) } diff --git a/libgo/go/runtime/pprof/pprof.go b/libgo/go/runtime/pprof/pprof.go index 099bb6a92f9..f67e8a8f9ae 100644 --- a/libgo/go/runtime/pprof/pprof.go +++ b/libgo/go/runtime/pprof/pprof.go @@ -20,8 +20,8 @@ import ( "text/tabwriter" ) -// BUG(rsc): CPU profiling is broken on OS X, due to an Apple kernel bug. -// For details, see http://code.google.com/p/go/source/detail?r=35b716c94225. +// BUG(rsc): A bug in the OS X Snow Leopard 64-bit kernel prevents +// CPU profiling from giving accurate results on that system. // A Profile is a collection of stack traces showing the call sequences // that led to instances of a particular event, such as allocation. @@ -156,7 +156,7 @@ func (p *Profile) Count() int { } // Add adds the current execution stack to the profile, associated with value. -// Add stores value in an internal map, so value must be suitable for use as +// Add stores value in an internal map, so value must be suitable for use as // a map key and will not be garbage collected until the corresponding // call to Remove. Add panics if the profile already contains a stack for value. // diff --git a/libgo/go/runtime/pprof/pprof_test.go b/libgo/go/runtime/pprof/pprof_test.go index 5f128c01cf1..2dc7aef7e96 100644 --- a/libgo/go/runtime/pprof/pprof_test.go +++ b/libgo/go/runtime/pprof/pprof_test.go @@ -7,6 +7,7 @@ package pprof_test import ( "bytes" "hash/crc32" + "os/exec" "runtime" . "runtime/pprof" "strings" @@ -17,8 +18,16 @@ import ( func TestCPUProfile(t *testing.T) { switch runtime.GOOS { case "darwin": - // see Apple Bug Report #9177434 (copied into change description) - return + out, err := exec.Command("uname", "-a").CombinedOutput() + if err != nil { + t.Fatal(err) + } + vers := string(out) + t.Logf("uname -a: %v", vers) + if strings.Contains(vers, "Darwin Kernel Version 10.8.0") && strings.Contains(vers, "root:xnu-1504.15.3~1/RELEASE_X86_64") { + t.Logf("skipping test on known-broken kernel (64-bit Snow Leopard)") + return + } case "plan9": // unimplemented return diff --git a/libgo/go/strconv/extfloat.go b/libgo/go/strconv/extfloat.go index 64ab84f4554..aa5e5607ca0 100644 --- a/libgo/go/strconv/extfloat.go +++ b/libgo/go/strconv/extfloat.go @@ -477,7 +477,7 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool { // all data is known with a error estimate of ulpBinary*ε. func adjustLastDigit(d *decimal, currentDiff, targetDiff, maxDiff, ulpDecimal, ulpBinary uint64) bool { if ulpDecimal < 2*ulpBinary { - // Appromixation is too wide. + // Approximation is too wide. return false } for currentDiff+ulpDecimal/2+ulpBinary < targetDiff { diff --git a/libgo/go/strings/example_test.go b/libgo/go/strings/example_test.go index 0b583411331..daeb85ef6bb 100644 --- a/libgo/go/strings/example_test.go +++ b/libgo/go/strings/example_test.go @@ -41,6 +41,7 @@ func ExampleContainsAny() { func ExampleCount() { fmt.Println(strings.Count("cheese", "e")) fmt.Println(strings.Count("five", "")) // before & after each rune + // Output: // 3 // 5 diff --git a/libgo/go/sync/example_test.go b/libgo/go/sync/example_test.go index 1424b1e79e6..15649240035 100644 --- a/libgo/go/sync/example_test.go +++ b/libgo/go/sync/example_test.go @@ -5,6 +5,7 @@ package sync_test import ( + "fmt" "net/http" "sync" ) @@ -32,3 +33,22 @@ func ExampleWaitGroup() { // Wait for all HTTP fetches to complete. wg.Wait() } + +func ExampleOnce() { + var once sync.Once + onceBody := func() { + fmt.Printf("Only once\n") + } + done := make(chan bool) + for i := 0; i < 10; i++ { + go func() { + once.Do(onceBody) + done <- true + }() + } + for i := 0; i < 10; i++ { + <-done + } + // Output: + // Only once +} diff --git a/libgo/go/sync/waitgroup.go b/libgo/go/sync/waitgroup.go index 3e7d9d3c8f4..0165b1ffb2b 100644 --- a/libgo/go/sync/waitgroup.go +++ b/libgo/go/sync/waitgroup.go @@ -11,21 +11,6 @@ import "sync/atomic" // goroutines to wait for. Then each of the goroutines // runs and calls Done when finished. At the same time, // Wait can be used to block until all goroutines have finished. -// -// For example: -// -// for i := 0; i < n; i++ { -// if !condition(i) { -// continue -// } -// wg.Add(1) -// go func() { -// // Do something. -// wg.Done() -// }() -// } -// wg.Wait() -// type WaitGroup struct { m Mutex counter int32 diff --git a/libgo/go/syscall/socket.go b/libgo/go/syscall/socket.go index 7a2e95ca2a2..6d36e3985f3 100644 --- a/libgo/go/syscall/socket.go +++ b/libgo/go/syscall/socket.go @@ -217,6 +217,13 @@ func Socketpair(domain, typ, proto int) (fd [2]int, err error) { //sys getsockopt(s int, level int, name int, val uintptr, vallen *Socklen_t) (err error) //getsockopt(s int, level int, name int, val *byte, vallen *Socklen_t) int +func GetsockoptByte(fd, level, opt int) (value byte, err error) { + var n byte + vallen := Socklen_t(1) + err = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), &vallen) + return n, err +} + func GetsockoptInt(fd, level, opt int) (value int, err error) { var n int32 vallen := Socklen_t(4) @@ -254,6 +261,11 @@ func GetsockoptIPv6Mreq(fd, level, opt int) (*IPv6Mreq, error) { //sys setsockopt(s int, level int, name int, val *byte, vallen Socklen_t) (err error) //setsockopt(s int, level int, optname int, val *byte, vallen Socklen_t) int +func SetsockoptByte(fd, level, opt int, value byte) (err error) { + var n = byte(value) + return setsockopt(fd, level, opt, (*byte)(unsafe.Pointer(&n)), 1) +} + func SetsockoptInt(fd, level, opt int, value int) (err error) { var n = int32(value) return setsockopt(fd, level, opt, (*byte)(unsafe.Pointer(&n)), 4) diff --git a/libgo/go/testing/example.go b/libgo/go/testing/example.go index 7f8ff2d0541..671c798760b 100644 --- a/libgo/go/testing/example.go +++ b/libgo/go/testing/example.go @@ -19,7 +19,7 @@ type InternalExample struct { Output string } -func RunExamples(examples []InternalExample) (ok bool) { +func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool) { ok = true var eg InternalExample @@ -27,6 +27,14 @@ func RunExamples(examples []InternalExample) (ok bool) { stdout, stderr := os.Stdout, os.Stderr for _, eg = range examples { + matched, err := matchString(*match, eg.Name) + if err != nil { + fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err) + os.Exit(1) + } + if !matched { + continue + } if *chatty { fmt.Printf("=== RUN: %s\n", eg.Name) } diff --git a/libgo/go/testing/testing.go b/libgo/go/testing/testing.go index adc8c09f217..477d2ac23ae 100644 --- a/libgo/go/testing/testing.go +++ b/libgo/go/testing/testing.go @@ -99,7 +99,7 @@ var ( // Report as tests are run; default is silent for success. chatty = flag.Bool("test.v", false, "verbose: print additional output") - match = flag.String("test.run", "", "regular expression to select tests to run") + match = flag.String("test.run", "", "regular expression to select tests and examples to run") memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution") memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate") cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution") @@ -280,7 +280,7 @@ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, before() startAlarm() testOk := RunTests(matchString, tests) - exampleOk := RunExamples(examples) + exampleOk := RunExamples(matchString, examples) if !testOk || !exampleOk { fmt.Println("FAIL") os.Exit(1) diff --git a/libgo/go/text/template/doc.go b/libgo/go/text/template/doc.go index ae91f4a5419..10e0f7fc373 100644 --- a/libgo/go/text/template/doc.go +++ b/libgo/go/text/template/doc.go @@ -142,11 +142,6 @@ An argument is a simple value, denoted by one of the following. .Field1.Key1.Method1.Field2.Key2.Method2 Methods can also be evaluated on variables, including chaining: $x.Method1.Field - - The name of a niladic function-valued struct field of the data, - preceded by a period, such as - .Function - Function-valued fields behave like methods (of structs) but do not - pass a receiver. - The name of a niladic function, such as fun The result is the value of invoking the function, fun(). The return @@ -155,6 +150,10 @@ An argument is a simple value, denoted by one of the following. Arguments may evaluate to any type; if they are pointers the implementation automatically indirects to the base type when required. +If an evaluation yields a function value, such as a function-valued +field of a struct, the function is not invoked automatically, but it +can be used as a truth value for an if action and the like. To invoke +it, use the call function, defined below. A pipeline is a possibly chained sequence of "commands". A command is a simple value (argument) or a function or method call, possibly with multiple arguments: @@ -167,9 +166,6 @@ value (argument) or a function or method call, possibly with multiple arguments: The result is the value of calling the method with the arguments: dot.Method(Argument1, etc.) - .Function [Argument...] - A function-valued field of a struct works like a method but does - not pass the receiver. functionName [Argument...] The result is the value of calling the function associated with the name: @@ -257,6 +253,17 @@ Predefined global functions are named as follows. first empty argument or the last argument, that is, "and x y" behaves as "if x then y else x". All the arguments are evaluated. + call + Returns the result of calling the first argument, which + must be a function, with the remaining arguments as parameters. + Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where + Y is a func-valued field, map entry, or the like. + The first argument must be the result of an evaluation + that yields a value of function type (as distinct from + a predefined function such as print). The function must + return either one or two result values, the second of which + is of type error. If the arguments don't match the function + or the returned error value is non-nil, execution stops. html Returns the escaped HTML equivalent of the textual representation of its arguments. diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go index af745286c0b..ad0118e4e68 100644 --- a/libgo/go/text/template/exec.go +++ b/libgo/go/text/template/exec.go @@ -421,11 +421,8 @@ func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node field := receiver.FieldByIndex(tField.Index) if tField.PkgPath == "" { // field is exported // If it's a function, we must call it. - if field.Type().Kind() == reflect.Func { - return s.evalCall(dot, field, fieldName, args, final) - } if hasArgs { - s.errorf("%s is not a method or function but has arguments", fieldName) + s.errorf("%s has arguments but cannot be invoked as function", fieldName) } return field } diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go index 159cf5100d9..70ab39cad20 100644 --- a/libgo/go/text/template/exec_test.go +++ b/libgo/go/text/template/exec_test.go @@ -60,7 +60,9 @@ type T struct { PSI *[]int NIL *int // Function (not method) - Func func(...string) string + BinaryFunc func(string, string) string + VariadicFunc func(...string) string + VariadicFuncInt func(int, ...string) string // Template to test evaluation of templates. Tmpl *Template } @@ -120,7 +122,9 @@ var tVal = &T{ Err: errors.New("erroozle"), PI: newInt(23), PSI: newIntSlice(21, 22, 23), - Func: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") }, + BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) }, + VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") }, + VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") }, Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X } @@ -300,13 +304,26 @@ var execTests = []execTest{ "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}", "true", tVal, true}, - // Function call - {".Func", "-{{.Func}}-", "-<>-", tVal, true}, - {".Func2", "-{{.Func `he` `llo`}}-", "-<he+llo>-", tVal, true}, + // Function call builtin. + {".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true}, + {".VariadicFunc0", "{{call .VariadicFunc}}", "<>", tVal, true}, + {".VariadicFunc2", "{{call .VariadicFunc `he` `llo`}}", "<he+llo>", tVal, true}, + {".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true}, + {"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true}, + {"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true}, + + // Erroneous function calls (check args). + {".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false}, + {".BinaryFuncTooMany", "{{call .BinaryFunc `1` `2` `3`}}", "", tVal, false}, + {".BinaryFuncBad0", "{{call .BinaryFunc 1 3}}", "", tVal, false}, + {".BinaryFuncBad1", "{{call .BinaryFunc `1` 3}}", "", tVal, false}, + {".VariadicFuncBad0", "{{call .VariadicFunc 3}}", "", tVal, false}, + {".VariadicFuncIntBad0", "{{call .VariadicFuncInt}}", "", tVal, false}, + {".VariadicFuncIntBad`", "{{call .VariadicFuncInt `x`}}", "", tVal, false}, // Pipelines. {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true}, - {"pipeline func", "-{{.Func `llo` | .Func `he` }}-", "-<he+<llo>>-", tVal, true}, + {"pipeline func", "-{{call .VariadicFunc `llo` | call .VariadicFunc `he` }}-", "-<he+<llo>>-", tVal, true}, // If. {"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true}, diff --git a/libgo/go/text/template/funcs.go b/libgo/go/text/template/funcs.go index d6e4bf1a216..525179cb499 100644 --- a/libgo/go/text/template/funcs.go +++ b/libgo/go/text/template/funcs.go @@ -24,6 +24,7 @@ type FuncMap map[string]interface{} var builtins = FuncMap{ "and": and, + "call": call, "html": HTMLEscaper, "index": index, "js": JSEscaper, @@ -151,6 +152,53 @@ func length(item interface{}) (int, error) { return 0, fmt.Errorf("len of type %s", v.Type()) } +// Function invocation + +// call returns the result of evaluating the the first argument as a function. +// The function must return 1 result, or 2 results, the second of which is an error. +func call(fn interface{}, args ...interface{}) (interface{}, error) { + v := reflect.ValueOf(fn) + typ := v.Type() + if typ.Kind() != reflect.Func { + return nil, fmt.Errorf("non-function of type %s", typ) + } + if !goodFunc(typ) { + return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) + } + numIn := typ.NumIn() + var dddType reflect.Type + if typ.IsVariadic() { + if len(args) < numIn-1 { + return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) + } + dddType = typ.In(numIn - 1).Elem() + } else { + if len(args) != numIn { + return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) + } + } + argv := make([]reflect.Value, len(args)) + for i, arg := range args { + value := reflect.ValueOf(arg) + // Compute the expected type. Clumsy because of variadics. + var argType reflect.Type + if !typ.IsVariadic() || i < numIn-1 { + argType = typ.In(i) + } else { + argType = dddType + } + if !value.Type().AssignableTo(argType) { + return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType) + } + argv[i] = reflect.ValueOf(arg) + } + result := v.Call(argv) + if len(result) == 2 { + return result[0].Interface(), result[1].Interface().(error) + } + return result[0].Interface(), nil +} + // Boolean logic. func truth(a interface{}) bool { diff --git a/libgo/go/text/template/multi_test.go b/libgo/go/text/template/multi_test.go index f205e6be1b4..22dedc4f839 100644 --- a/libgo/go/text/template/multi_test.go +++ b/libgo/go/text/template/multi_test.go @@ -265,6 +265,12 @@ func TestRedefinition(t *testing.T) { if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil { t.Fatalf("parse 1: %v", err) } + if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "redefinition") { + t.Fatalf("expected redefinition error; got %v", err) + } if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err == nil { t.Fatal("expected error") } diff --git a/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go index 35194f7dfdb..d67b3888085 100644 --- a/libgo/go/text/template/parse/parse.go +++ b/libgo/go/text/template/parse/parse.go @@ -193,6 +193,8 @@ func (t *Tree) add(treeSet map[string]*Tree) { // IsEmptyTree reports whether this tree (node) is empty of everything but space. func IsEmptyTree(n Node) bool { switch n := n.(type) { + case nil: + return true case *ActionNode: case *IfNode: case *ListNode: diff --git a/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go index efa7d8be742..18c0a8b835e 100644 --- a/libgo/go/text/template/parse/parse_test.go +++ b/libgo/go/text/template/parse/parse_test.go @@ -287,6 +287,9 @@ var isEmptyTests = []isEmptyTest{ } func TestIsEmpty(t *testing.T) { + if !IsEmptyTree(nil) { + t.Errorf("nil tree is not empty") + } for _, test := range isEmptyTests { tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil) if err != nil { diff --git a/libgo/go/text/template/template.go b/libgo/go/text/template/template.go index 7494f9d8c45..82fc9e5e39d 100644 --- a/libgo/go/text/template/template.go +++ b/libgo/go/text/template/template.go @@ -178,10 +178,11 @@ func (t *Template) Parse(text string) (*Template, error) { 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 { + if replace, err := t.associate(tmpl, tree); err != nil { return nil, err + } else if replace { + tmpl.Tree = tree } - tmpl.Tree = tree tmpl.leftDelim = t.leftDelim tmpl.rightDelim = t.rightDelim } @@ -191,22 +192,23 @@ func (t *Template) Parse(text string) (*Template, error) { // 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 { +// The boolean return value reports wither to store this tree as t.Tree. +func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) { if new.common != t.common { panic("internal error: associate not common") } name := new.name if old := t.tmpl[name]; old != nil { oldIsEmpty := parse.IsEmptyTree(old.Root) - newIsEmpty := new.Tree != nil && parse.IsEmptyTree(new.Root) - if !oldIsEmpty && !newIsEmpty { - return fmt.Errorf("template: redefinition of template %q", name) - } + newIsEmpty := parse.IsEmptyTree(tree.Root) if newIsEmpty { // Whether old is empty or not, new is empty; no reason to replace old. - return nil + return false, nil + } + if !oldIsEmpty { + return false, fmt.Errorf("template: redefinition of template %q", name) } } t.tmpl[name] = new - return nil + return true, nil } diff --git a/libgo/go/time/format.go b/libgo/go/time/format.go index ef6f1f33514..ad52bab216f 100644 --- a/libgo/go/time/format.go +++ b/libgo/go/time/format.go @@ -6,14 +6,6 @@ package time import "errors" -const ( - numeric = iota - alphabetic - separator - plus - minus -) - // These are predefined layouts for use in Time.Format. // The standard time used in the layouts is: // Mon Jan 2 15:04:05 MST 2006 diff --git a/libgo/go/time/sleep_test.go b/libgo/go/time/sleep_test.go index e572b678419..440d3b42f12 100644 --- a/libgo/go/time/sleep_test.go +++ b/libgo/go/time/sleep_test.go @@ -120,8 +120,11 @@ func TestAfterTick(t *testing.T) { t1 := Now() d := t1.Sub(t0) target := Delta * Count - if d < target*9/10 || d > target*30/10 { - t.Fatalf("%d ticks of %s took %s, expected %s", Count, Delta, d, target) + if d < target*9/10 { + t.Fatalf("%d ticks of %s too fast: took %s, expected %s", Count, Delta, d, target) + } + if !testing.Short() && d > target*30/10 { + t.Fatalf("%d ticks of %s too slow: took %s, expected %s", Count, Delta, d, target) } } diff --git a/libgo/go/time/time.go b/libgo/go/time/time.go index 709a4226724..f7ded24d292 100644 --- a/libgo/go/time/time.go +++ b/libgo/go/time/time.go @@ -152,7 +152,7 @@ func (d Weekday) String() string { return days[d] } // 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 +// Go's division (like most hardware division 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 @@ -384,6 +384,15 @@ type Duration int64 // Common durations. There is no definition for units of Day or larger // to avoid confusion across daylight savings time zone transitions. +// +// To count the number of units in a Duration, divide: +// second := time.Second +// fmt.Print(int64(second/time.Millisecond)) // prints 1000 +// +// To convert an integer number of units to a Duration, multiply: +// seconds := 10 +// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s +// const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond @@ -758,10 +767,6 @@ func (t Time) UnixNano() int64 { return (t.sec+internalToUnix)*1e9 + int64(t.nsec) } -type gobError string - -func (g gobError) Error() string { return string(g) } - const timeGobVersion byte = 1 // GobEncode implements the gob.GobEncoder interface. diff --git a/libgo/runtime/go-main.c b/libgo/runtime/go-main.c index 5871981f2b8..7e8bb9b234f 100644 --- a/libgo/runtime/go-main.c +++ b/libgo/runtime/go-main.c @@ -40,7 +40,7 @@ static void mainstart (void *); int main (int argc, char **argv) { - runtime_initsig (); + runtime_check (); runtime_args (argc, (byte **) argv); runtime_osinit (); runtime_schedinit (); diff --git a/libgo/runtime/lock_futex.c b/libgo/runtime/lock_futex.c index cdc12d7c75c..9a533a577a4 100644 --- a/libgo/runtime/lock_futex.c +++ b/libgo/runtime/lock_futex.c @@ -118,8 +118,12 @@ runtime_notewakeup(Note *n) void runtime_notesleep(Note *n) { + if(runtime_m()->profilehz > 0) + runtime_setprof(false); while(runtime_atomicload(&n->key) == 0) runtime_futexsleep(&n->key, 0, -1); + if(runtime_m()->profilehz > 0) + runtime_setprof(true); } void @@ -135,14 +139,18 @@ runtime_notetsleep(Note *n, int64 ns) if(runtime_atomicload(&n->key) != 0) return; + if(runtime_m()->profilehz > 0) + runtime_setprof(false); deadline = runtime_nanotime() + ns; for(;;) { runtime_futexsleep(&n->key, 0, ns); if(runtime_atomicload(&n->key) != 0) - return; + break; now = runtime_nanotime(); if(now >= deadline) - return; + break; ns = deadline - now; } + if(runtime_m()->profilehz > 0) + runtime_setprof(true); } diff --git a/libgo/runtime/lock_sema.c b/libgo/runtime/lock_sema.c index b2a8f53be41..8c4b3973bdc 100644 --- a/libgo/runtime/lock_sema.c +++ b/libgo/runtime/lock_sema.c @@ -159,7 +159,11 @@ runtime_notesleep(Note *n) return; } // Queued. Sleep. + if(m->profilehz > 0) + runtime_setprof(false); runtime_semasleep(-1); + if(m->profilehz > 0) + runtime_setprof(true); } void @@ -185,12 +189,16 @@ runtime_notetsleep(Note *n, int64 ns) return; } + if(m->profilehz > 0) + runtime_setprof(false); deadline = runtime_nanotime() + ns; for(;;) { // Registered. Sleep. if(runtime_semasleep(ns) >= 0) { // Acquired semaphore, semawakeup unregistered us. // Done. + if(m->profilehz > 0) + runtime_setprof(true); return; } @@ -203,6 +211,9 @@ runtime_notetsleep(Note *n, int64 ns) ns = deadline - now; } + if(m->profilehz > 0) + runtime_setprof(true); + // Deadline arrived. Still registered. Semaphore not acquired. // Want to give up and return, but have to unregister first, // so that any notewakeup racing with the return does not diff --git a/libgo/runtime/malloc.goc b/libgo/runtime/malloc.goc index 23641e8298f..3fde250af3b 100644 --- a/libgo/runtime/malloc.goc +++ b/libgo/runtime/malloc.goc @@ -277,6 +277,7 @@ runtime_mallocinit(void) uintptr arena_size, bitmap_size; extern byte end[]; byte *want; + uintptr limit; runtime_sizeof_C_MStats = sizeof(MStats); @@ -291,10 +292,12 @@ runtime_mallocinit(void) runtime_InitSizes(); + limit = runtime_memlimit(); + // Set up the allocation arena, a contiguous area of memory where // allocated data will be found. The arena begins with a bitmap large // enough to hold 4 bits per allocated word. - if(sizeof(void*) == 8) { + if(sizeof(void*) == 8 && (limit == 0 || limit > (1<<30))) { // On a 64-bit machine, allocate from a single contiguous reservation. // 16 GB should be big enough for now. // @@ -343,6 +346,10 @@ runtime_mallocinit(void) // of address space, which is probably too much in a 32-bit world. bitmap_size = MaxArena32 / (sizeof(void*)*8/4); arena_size = 512<<20; + if(limit > 0 && arena_size+bitmap_size > limit) { + bitmap_size = (limit / 9) & ~((1<<PageShift) - 1); + arena_size = bitmap_size * 8; + } // SysReserve treats the address we ask for, end, as a hint, // not as an absolute requirement. If we ask for the end @@ -359,6 +366,8 @@ runtime_mallocinit(void) p = runtime_SysReserve(want, bitmap_size + arena_size); if(p == nil) runtime_throw("runtime: cannot reserve arena virtual address space"); + if((uintptr)p & (((uintptr)1<<PageShift)-1)) + runtime_printf("runtime: SysReserve returned unaligned address %p; asked for %p", p, (void*)(bitmap_size+arena_size)); } if((uintptr)p & (((uintptr)1<<PageShift)-1)) runtime_throw("runtime: SysReserve returned unaligned address"); diff --git a/libgo/runtime/mgc0.c b/libgo/runtime/mgc0.c index d852946cdbb..4aa7c45dcb3 100644 --- a/libgo/runtime/mgc0.c +++ b/libgo/runtime/mgc0.c @@ -654,14 +654,6 @@ markfin(void *v) scanblock(v, size); } -struct root_list { - struct root_list *next; - struct root { - void *decl; - size_t size; - } roots[]; -}; - static struct root_list* roots; void diff --git a/libgo/runtime/mheap.c b/libgo/runtime/mheap.c index 79359d9dfca..6bf38aa9934 100644 --- a/libgo/runtime/mheap.c +++ b/libgo/runtime/mheap.c @@ -326,7 +326,7 @@ MHeap_FreeLocked(MHeap *h, MSpan *s) } // Release (part of) unused memory to OS. -// Goroutine created in runtime_schedinit. +// Goroutine created at startup. // Loop forever. void runtime_MHeap_Scavenger(void* dummy) diff --git a/libgo/runtime/proc.c b/libgo/runtime/proc.c index d0ae09c45a0..31e8287e704 100644 --- a/libgo/runtime/proc.c +++ b/libgo/runtime/proc.c @@ -416,8 +416,6 @@ runtime_schedinit(void) // Can not enable GC until all roots are registered. // mstats.enablegc = 1; m->nomemprof--; - - scvg = __go_go(runtime_MHeap_Scavenger, nil); } extern void main_init(void) __asm__ ("__go_init_main"); @@ -435,6 +433,7 @@ runtime_main(void) // to preserve the lock. runtime_LockOSThread(); runtime_sched.init = true; + scvg = __go_go(runtime_MHeap_Scavenger, nil); main_init(); runtime_sched.init = false; if(!runtime_sched.lockmain) @@ -548,7 +547,7 @@ mcommoninit(M *m) m->mcache = runtime_allocmcache(); runtime_callers(1, m->createstack, nelem(m->createstack)); - + // Add to runtime_allm so garbage collector doesn't free m // when it is just in a register or thread-local storage. m->alllink = runtime_allm; @@ -791,10 +790,11 @@ top: mput(m); } - // Look for deadlock situation: one single active g which happens to be scvg. - if(runtime_sched.grunning == 1 && runtime_sched.gwait == 0) { - if(scvg->status == Grunning || scvg->status == Gsyscall) - runtime_throw("all goroutines are asleep - deadlock!"); + // Look for deadlock situation. + if((scvg == nil && runtime_sched.grunning == 0) || + (scvg != nil && runtime_sched.grunning == 1 && runtime_sched.gwait == 0 && + (scvg->status == Grunning || scvg->status == Gsyscall))) { + runtime_throw("all goroutines are asleep - deadlock!"); } m->nextg = nil; @@ -1135,6 +1135,9 @@ runtime_entersyscall(void) { uint32 v; + if(m->profilehz > 0) + runtime_setprof(false); + // Leave SP around for gc and traceback. #ifdef USING_SPLIT_STACK g->gcstack = __splitstack_find(NULL, NULL, &g->gcstack_size, @@ -1205,6 +1208,9 @@ runtime_exitsyscall(void) #endif gp->gcnext_sp = nil; runtime_memclr(gp->gcregs, sizeof gp->gcregs); + + if(m->profilehz > 0) + runtime_setprof(true); return; } diff --git a/libgo/runtime/runtime.c b/libgo/runtime/runtime.c index 7c8c436deeb..78c865ba175 100644 --- a/libgo/runtime/runtime.c +++ b/libgo/runtime/runtime.c @@ -184,6 +184,21 @@ runtime_fastrand1(void) return x; } +static struct root_list runtime_roots = +{ NULL, + { { &syscall_Envs, sizeof syscall_Envs }, + { &os_Args, sizeof os_Args }, + { NULL, 0 } }, +}; + +void +runtime_check(void) +{ + __go_register_gc_roots(&runtime_roots); + + runtime_initsig (); +} + int64 runtime_cputicks(void) { diff --git a/libgo/runtime/runtime.h b/libgo/runtime/runtime.h index 113bb7163c5..40c59a82777 100644 --- a/libgo/runtime/runtime.h +++ b/libgo/runtime/runtime.h @@ -427,6 +427,8 @@ void runtime_osyield(void); void runtime_LockOSThread(void) __asm__("libgo_runtime.runtime.LockOSThread"); void runtime_UnlockOSThread(void) __asm__("libgo_runtime.runtime.UnlockOSThread"); +uintptr runtime_memlimit(void); + // If appropriate, ask the operating system to control whether this // thread should receive profiling signals. This is only necessary on OS X. // An operating system should not deliver a profiling signal to a @@ -441,3 +443,16 @@ void runtime_time_scan(void (*)(byte*, int64)); void runtime_setsig(int32, bool, bool); #define runtime_setitimer setitimer + +void runtime_check(void); + +// A list of global variables that the garbage collector must scan. +struct root_list { + struct root_list *next; + struct root { + void *decl; + size_t size; + } roots[]; +}; + +void __go_register_gc_roots(struct root_list*); diff --git a/libgo/runtime/thread-linux.c b/libgo/runtime/thread-linux.c index 8dd5fc4b481..6a69fb429a4 100644 --- a/libgo/runtime/thread-linux.c +++ b/libgo/runtime/thread-linux.c @@ -3,6 +3,16 @@ // license that can be found in the LICENSE file. #include "runtime.h" +#include "defs.h" + +// Linux futex. +// +// futexsleep(uint32 *addr, uint32 val) +// futexwakeup(uint32 *addr) +// +// Futexsleep atomically checks if *addr == val and if so, sleeps on addr. +// Futexwakeup wakes up threads sleeping on addr. +// Futexsleep is allowed to wake up spuriously. #include <errno.h> #include <string.h> diff --git a/libgo/runtime/thread.c b/libgo/runtime/thread.c index 748a62d59f5..12d009926e3 100644 --- a/libgo/runtime/thread.c +++ b/libgo/runtime/thread.c @@ -4,6 +4,8 @@ #include <errno.h> #include <signal.h> +#include <sys/time.h> +#include <sys/resource.h> #include "runtime.h" #include "go-assert.h" @@ -138,6 +140,7 @@ runtime_minit(void) byte* stack; size_t stacksize; stack_t ss; + sigset_t sigs; // Initialize signal handling. runtime_m()->gsignal = runtime_malg(32*1024, &stack, &stacksize); // OS X wants >=8K, Linux >=2K @@ -146,4 +149,34 @@ runtime_minit(void) ss.ss_size = stacksize; if(sigaltstack(&ss, nil) < 0) *(int *)0xf1 = 0xf1; + if (sigemptyset(&sigs) != 0) + runtime_throw("sigemptyset"); + sigprocmask(SIG_SETMASK, &sigs, nil); +} + +uintptr +runtime_memlimit(void) +{ + struct rlimit rl; + uintptr used; + + if(getrlimit(RLIMIT_AS, &rl) != 0) + return 0; + if(rl.rlim_cur >= 0x7fffffff) + return 0; + + // Estimate our VM footprint excluding the heap. + // Not an exact science: use size of binary plus + // some room for thread stacks. + used = (64<<20); + if(used >= rl.rlim_cur) + return 0; + + // If there's not at least 16 MB left, we're probably + // not going to be able to do much. Treat as no limit. + rl.rlim_cur -= used; + if(rl.rlim_cur < (16<<20)) + return 0; + + return rl.rlim_cur - used; } |