libgo: Update to weekly.2012-03-04 release.

From-SVN: r185010
This commit is contained in:
Ian Lance Taylor 2012-03-06 17:57:23 +00:00
parent 46402cbe0b
commit 593f74bbab
147 changed files with 3080 additions and 2022 deletions

View file

@ -1,4 +1,4 @@
96bd78e7d35e f4470a54e6db
The first line of this file holds the Mercurial revision number of the The first line of this file holds the Mercurial revision number of the
last merge done from the master library sources. last merge done from the master library sources.

View file

@ -658,10 +658,17 @@ go_net_sock_file = go/net/sock_linux.go
go_net_sockopt_file = go/net/sockopt_linux.go go_net_sockopt_file = go/net/sockopt_linux.go
go_net_sockoptip_file = go/net/sockoptip_linux.go go_net_sockoptip_file = go/net/sockoptip_linux.go
else else
if LIBGO_IS_FREEBSD
go_net_cgo_file = go/net/cgo_bsd.go go_net_cgo_file = go/net/cgo_bsd.go
go_net_sock_file = go/net/sock_bsd.go go_net_sock_file = go/net/sock_bsd.go
go_net_sockopt_file = go/net/sockopt_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 endif
endif endif
@ -704,6 +711,7 @@ go_net_files = \
go/net/ipsock.go \ go/net/ipsock.go \
go/net/ipsock_posix.go \ go/net/ipsock_posix.go \
go/net/lookup_unix.go \ go/net/lookup_unix.go \
go/net/mac.go \
go/net/net.go \ go/net/net.go \
go/net/parse.go \ go/net/parse.go \
go/net/pipe.go \ go/net/pipe.go \
@ -1126,8 +1134,7 @@ go_go_ast_files = \
go/go/ast/walk.go go/go/ast/walk.go
go_go_build_files = \ go_go_build_files = \
go/go/build/build.go \ go/go/build/build.go \
go/go/build/dir.go \ go/go/build/doc.go \
go/go/build/path.go \
syslist.go syslist.go
go_go_doc_files = \ go_go_doc_files = \
go/go/doc/comment.go \ go/go/doc/comment.go \

View file

@ -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_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_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_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_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_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_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_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_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_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_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_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_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_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_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 @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.go \
go/net/ipsock_posix.go \ go/net/ipsock_posix.go \
go/net/lookup_unix.go \ go/net/lookup_unix.go \
go/net/mac.go \
go/net/net.go \ go/net/net.go \
go/net/parse.go \ go/net/parse.go \
go/net/pipe.go \ go/net/pipe.go \
@ -1467,8 +1472,7 @@ go_go_ast_files = \
go_go_build_files = \ go_go_build_files = \
go/go/build/build.go \ go/go/build/build.go \
go/go/build/dir.go \ go/go/build/doc.go \
go/go/build/path.go \
syslist.go syslist.go
go_go_doc_files = \ go_go_doc_files = \

View file

@ -169,48 +169,21 @@ func (r *checksumReader) Read(b []byte) (n int, err error) {
func (r *checksumReader) Close() error { return r.rc.Close() } 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 // findBodyOffset does the minimum work to verify the file has a header
// and returns the file body offset. // and returns the file body offset.
func (f *File) findBodyOffset() (int64, error) { func (f *File) findBodyOffset() (int64, error) {
r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset) r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset)
var b [fileHeaderLen]byte var buf [fileHeaderLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil { if _, err := io.ReadFull(r, buf[:]); err != nil {
return 0, err return 0, err
} }
c := binary.LittleEndian b := readBuf(buf[:])
if sig := c.Uint32(b[:4]); sig != fileHeaderSignature { if sig := b.uint32(); sig != fileHeaderSignature {
return 0, ErrFormat return 0, ErrFormat
} }
filenameLen := int(c.Uint16(b[26:28])) b = b[22:] // skip over most of the header
extraLen := int(c.Uint16(b[28:30])) filenameLen := int(b.uint16())
extraLen := int(b.uint16())
return int64(fileHeaderLen + filenameLen + extraLen), nil 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, // It returns io.ErrUnexpectedEOF if it cannot read a complete header,
// and ErrFormat if it doesn't find a valid header signature. // and ErrFormat if it doesn't find a valid header signature.
func readDirectoryHeader(f *File, r io.Reader) error { func readDirectoryHeader(f *File, r io.Reader) error {
var b [directoryHeaderLen]byte var buf [directoryHeaderLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil { if _, err := io.ReadFull(r, buf[:]); err != nil {
return err return err
} }
c := binary.LittleEndian b := readBuf(buf[:])
if sig := c.Uint32(b[:4]); sig != directoryHeaderSignature { if sig := b.uint32(); sig != directoryHeaderSignature {
return ErrFormat return ErrFormat
} }
f.CreatorVersion = c.Uint16(b[4:6]) f.CreatorVersion = b.uint16()
f.ReaderVersion = c.Uint16(b[6:8]) f.ReaderVersion = b.uint16()
f.Flags = c.Uint16(b[8:10]) f.Flags = b.uint16()
f.Method = c.Uint16(b[10:12]) f.Method = b.uint16()
f.ModifiedTime = c.Uint16(b[12:14]) f.ModifiedTime = b.uint16()
f.ModifiedDate = c.Uint16(b[14:16]) f.ModifiedDate = b.uint16()
f.CRC32 = c.Uint32(b[16:20]) f.CRC32 = b.uint32()
f.CompressedSize = c.Uint32(b[20:24]) f.CompressedSize = b.uint32()
f.UncompressedSize = c.Uint32(b[24:28]) f.UncompressedSize = b.uint32()
filenameLen := int(c.Uint16(b[28:30])) filenameLen := int(b.uint16())
extraLen := int(c.Uint16(b[30:32])) extraLen := int(b.uint16())
commentLen := int(c.Uint16(b[32:34])) commentLen := int(b.uint16())
// startDiskNumber := c.Uint16(b[34:36]) // Unused b = b[4:] // skipped start disk number and internal attributes (2x uint16)
// internalAttributes := c.Uint16(b[36:38]) // Unused f.ExternalAttrs = b.uint32()
f.ExternalAttrs = c.Uint32(b[38:42]) f.headerOffset = int64(b.uint32())
f.headerOffset = int64(c.Uint32(b[42:46]))
d := make([]byte, filenameLen+extraLen+commentLen) d := make([]byte, filenameLen+extraLen+commentLen)
if _, err := io.ReadFull(r, d); err != nil { if _, err := io.ReadFull(r, d); err != nil {
return err return err
@ -253,30 +225,30 @@ func readDirectoryHeader(f *File, r io.Reader) error {
} }
func readDataDescriptor(r io.Reader, f *File) error { func readDataDescriptor(r io.Reader, f *File) error {
var b [dataDescriptorLen]byte var buf [dataDescriptorLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil { if _, err := io.ReadFull(r, buf[:]); err != nil {
return err return err
} }
c := binary.LittleEndian b := readBuf(buf[:])
f.CRC32 = c.Uint32(b[:4]) f.CRC32 = b.uint32()
f.CompressedSize = c.Uint32(b[4:8]) f.CompressedSize = b.uint32()
f.UncompressedSize = c.Uint32(b[8:12]) f.UncompressedSize = b.uint32()
return nil return nil
} }
func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) { func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) {
// look for directoryEndSignature in the last 1k, then in the last 65k // 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} { for i, bLen := range []int64{1024, 65 * 1024} {
if bLen > size { if bLen > size {
bLen = size bLen = size
} }
b = make([]byte, int(bLen)) buf = make([]byte, int(bLen))
if _, err := r.ReadAt(b, size-bLen); err != nil && err != io.EOF { if _, err := r.ReadAt(buf, size-bLen); err != nil && err != io.EOF {
return nil, err return nil, err
} }
if p := findSignatureInBlock(b); p >= 0 { if p := findSignatureInBlock(buf); p >= 0 {
b = b[p:] buf = buf[p:]
break break
} }
if i == 1 || bLen == size { if i == 1 || bLen == size {
@ -285,16 +257,21 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error)
} }
// read header into struct // read header into struct
c := binary.LittleEndian b := readBuf(buf[4:]) // skip signature
d := new(directoryEnd) d := &directoryEnd{
d.diskNbr = c.Uint16(b[4:6]) diskNbr: b.uint16(),
d.dirDiskNbr = c.Uint16(b[6:8]) dirDiskNbr: b.uint16(),
d.dirRecordsThisDisk = c.Uint16(b[8:10]) dirRecordsThisDisk: b.uint16(),
d.directoryRecords = c.Uint16(b[10:12]) directoryRecords: b.uint16(),
d.directorySize = c.Uint32(b[12:16]) directorySize: b.uint32(),
d.directoryOffset = c.Uint32(b[16:20]) directoryOffset: b.uint32(),
d.commentLen = c.Uint16(b[20:22]) commentLen: b.uint16(),
d.comment = string(b[22 : 22+int(d.commentLen)]) }
l := int(d.commentLen)
if l > len(b) {
return nil, errors.New("zip: invalid comment length")
}
d.comment = string(b[:l])
return d, nil return d, nil
} }
@ -311,3 +288,17 @@ func findSignatureInBlock(b []byte) int {
} }
return -1 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
}

View file

@ -165,7 +165,7 @@ func readTestZip(t *testing.T, zt ZipTest) {
t.Errorf("%s: comment=%q, want %q", zt.Name, z.Comment, zt.Comment) t.Errorf("%s: comment=%q, want %q", zt.Name, z.Comment, zt.Comment)
} }
if len(z.File) != len(zt.File) { 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 // test read of each file

View file

@ -100,16 +100,6 @@ type directoryEnd struct {
comment string 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. // msDosTimeToTime converts an MS-DOS date and time into a time.Time.
// The resolution is 2s. // The resolution is 2s.
// See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx // See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx

View file

@ -37,10 +37,10 @@ func NewWriter(w io.Writer) *Writer {
// Close finishes writing the zip file by writing the central directory. // Close finishes writing the zip file by writing the central directory.
// It does not (and can not) close the underlying writer. // 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 w.last != nil && !w.last.closed {
if err = w.last.close(); err != nil { if err := w.last.close(); err != nil {
return return err
} }
w.last = nil w.last = nil
} }
@ -49,43 +49,55 @@ func (w *Writer) Close() (err error) {
} }
w.closed = true w.closed = true
defer recoverError(&err)
// write central directory // write central directory
start := w.cw.count start := w.cw.count
for _, h := range w.dir { for _, h := range w.dir {
write(w.cw, uint32(directoryHeaderSignature)) var buf [directoryHeaderLen]byte
write(w.cw, h.CreatorVersion) b := writeBuf(buf[:])
write(w.cw, h.ReaderVersion) b.uint32(uint32(directoryHeaderSignature))
write(w.cw, h.Flags) b.uint16(h.CreatorVersion)
write(w.cw, h.Method) b.uint16(h.ReaderVersion)
write(w.cw, h.ModifiedTime) b.uint16(h.Flags)
write(w.cw, h.ModifiedDate) b.uint16(h.Method)
write(w.cw, h.CRC32) b.uint16(h.ModifiedTime)
write(w.cw, h.CompressedSize) b.uint16(h.ModifiedDate)
write(w.cw, h.UncompressedSize) b.uint32(h.CRC32)
write(w.cw, uint16(len(h.Name))) b.uint32(h.CompressedSize)
write(w.cw, uint16(len(h.Extra))) b.uint32(h.UncompressedSize)
write(w.cw, uint16(len(h.Comment))) b.uint16(uint16(len(h.Name)))
write(w.cw, uint16(0)) // disk number start b.uint16(uint16(len(h.Extra)))
write(w.cw, uint16(0)) // internal file attributes b.uint16(uint16(len(h.Comment)))
write(w.cw, h.ExternalAttrs) b = b[4:] // skip disk number start and internal file attr (2x uint16)
write(w.cw, h.offset) b.uint32(h.ExternalAttrs)
writeBytes(w.cw, []byte(h.Name)) b.uint32(h.offset)
writeBytes(w.cw, h.Extra) if _, err := w.cw.Write(buf[:]); err != nil {
writeBytes(w.cw, []byte(h.Comment)) 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 end := w.cw.count
// write end record // write end record
write(w.cw, uint32(directoryEndSignature)) var buf [directoryEndLen]byte
write(w.cw, uint16(0)) // disk number b := writeBuf(buf[:])
write(w.cw, uint16(0)) // disk number where directory starts b.uint32(uint32(directoryEndSignature))
write(w.cw, uint16(len(w.dir))) // number of entries this disk b = b[4:] // skip over disk number and first disk number (2x uint16)
write(w.cw, uint16(len(w.dir))) // number of entries total b.uint16(uint16(len(w.dir))) // number of entries this disk
write(w.cw, uint32(end-start)) // size of directory b.uint16(uint16(len(w.dir))) // number of entries total
write(w.cw, uint32(start)) // start of directory b.uint32(uint32(end - start)) // size of directory
write(w.cw, uint16(0)) // size of comment 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() return w.cw.w.(*bufio.Writer).Flush()
} }
@ -152,22 +164,28 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
return fw, nil return fw, nil
} }
func writeHeader(w io.Writer, h *FileHeader) (err error) { func writeHeader(w io.Writer, h *FileHeader) error {
defer recoverError(&err) var buf [fileHeaderLen]byte
write(w, uint32(fileHeaderSignature)) b := writeBuf(buf[:])
write(w, h.ReaderVersion) b.uint32(uint32(fileHeaderSignature))
write(w, h.Flags) b.uint16(h.ReaderVersion)
write(w, h.Method) b.uint16(h.Flags)
write(w, h.ModifiedTime) b.uint16(h.Method)
write(w, h.ModifiedDate) b.uint16(h.ModifiedTime)
write(w, h.CRC32) b.uint16(h.ModifiedDate)
write(w, h.CompressedSize) b.uint32(h.CRC32)
write(w, h.UncompressedSize) b.uint32(h.CompressedSize)
write(w, uint16(len(h.Name))) b.uint32(h.UncompressedSize)
write(w, uint16(len(h.Extra))) b.uint16(uint16(len(h.Name)))
writeBytes(w, []byte(h.Name)) b.uint16(uint16(len(h.Extra)))
writeBytes(w, h.Extra) if _, err := w.Write(buf[:]); err != nil {
return nil return err
}
if _, err := io.WriteString(w, h.Name); err != nil {
return err
}
_, err := w.Write(h.Extra)
return err
} }
type fileWriter struct { type fileWriter struct {
@ -188,13 +206,13 @@ func (w *fileWriter) Write(p []byte) (int, error) {
return w.rawCount.Write(p) return w.rawCount.Write(p)
} }
func (w *fileWriter) close() (err error) { func (w *fileWriter) close() error {
if w.closed { if w.closed {
return errors.New("zip: file closed twice") return errors.New("zip: file closed twice")
} }
w.closed = true w.closed = true
if err = w.comp.Close(); err != nil { if err := w.comp.Close(); err != nil {
return return err
} }
// update FileHeader // update FileHeader
@ -204,12 +222,13 @@ func (w *fileWriter) close() (err error) {
fh.UncompressedSize = uint32(w.rawCount.count) fh.UncompressedSize = uint32(w.rawCount.count)
// write data descriptor // write data descriptor
defer recoverError(&err) var buf [dataDescriptorLen]byte
write(w.zipw, fh.CRC32) b := writeBuf(buf[:])
write(w.zipw, fh.CompressedSize) b.uint32(fh.CRC32)
write(w.zipw, fh.UncompressedSize) b.uint32(fh.CompressedSize)
b.uint32(fh.UncompressedSize)
return nil _, err := w.zipw.Write(buf[:])
return err
} }
type countWriter struct { type countWriter struct {
@ -231,18 +250,14 @@ func (w nopCloser) Close() error {
return nil return nil
} }
func write(w io.Writer, data interface{}) { type writeBuf []byte
if err := binary.Write(w, binary.LittleEndian, data); err != nil {
panic(err) func (b *writeBuf) uint16(v uint16) {
} binary.LittleEndian.PutUint16(*b, v)
*b = (*b)[2:]
} }
func writeBytes(w io.Writer, b []byte) { func (b *writeBuf) uint32(v uint32) {
n, err := w.Write(b) binary.LittleEndian.PutUint32(*b, v)
if err != nil { *b = (*b)[4:]
panic(err)
}
if n != len(b) {
panic(io.ErrShortWrite)
}
} }

View file

@ -23,7 +23,6 @@ var (
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune") ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
ErrBufferFull = errors.New("bufio: buffer full") ErrBufferFull = errors.New("bufio: buffer full")
ErrNegativeCount = errors.New("bufio: negative count") ErrNegativeCount = errors.New("bufio: negative count")
errInternal = errors.New("bufio: internal error")
) )
// Buffered input. // Buffered input.

View file

@ -7,7 +7,7 @@
package ecdsa package ecdsa
// References: // 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 // http://www.nsa.gov/ia/_files/ecdsa.pdf
// [SECG]: SECG, SEC1 // [SECG]: SECG, SEC1
// http://www.secg.org/download/aid-780/sec1-v2.pdf // http://www.secg.org/download/aid-780/sec1-v2.pdf

View file

@ -5,11 +5,9 @@
package tls package tls
/* /*
// Note: We disable -Werror here because the code in this file uses a deprecated API to stay #cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060
// 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 LDFLAGS: -framework CoreFoundation -framework Security #cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h> #include <Security/Security.h>
@ -40,26 +38,12 @@ int FetchPEMRoots(CFDataRef *pemRoots) {
continue; continue;
} }
// SecKeychainImportExport is deprecated in >= OS X 10.7, and has been replaced by // Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
// SecItemExport. If we're built on a host with a Lion SDK, this code gets conditionally // Once we support weak imports via cgo we should prefer that, and fall back to this
// included in the output, also for binaries meant for 10.6. // for older systems.
// err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
// To make sure that we run on both Mac OS X 10.6 and 10.7 we use weak linking if (err != noErr) {
// and check whether SecItemExport is available before we attempt to call it. On continue;
// 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;
}
} }
if (data != NULL) { if (data != NULL) {

View file

@ -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 // 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 // certificate in opts.roots, using certificates in opts.Intermediates if
// needed. If successful, it returns one or chains where the first element of // needed. If successful, it returns one or more chains where the first
// the chain is c and the last element is from opts.Roots. // element of the chain is c and the last element is from opts.Roots.
// //
// WARNING: this doesn't do any revocation checking. // WARNING: this doesn't do any revocation checking.
func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) { func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {

View file

@ -153,7 +153,7 @@ const (
// //
// md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 } // 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 } // sha-1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
// //
@ -172,9 +172,9 @@ const (
// //
// RFC 5758 3.1 DSA Signature Algorithms // 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) // 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 ( var (
oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}

View file

@ -29,17 +29,13 @@ type ByteOrder interface {
String() string 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. // LittleEndian is the little-endian implementation of ByteOrder.
var LittleEndian littleEndian var LittleEndian littleEndian
// BigEndian is the big-endian implementation of ByteOrder. // BigEndian is the big-endian implementation of ByteOrder.
var BigEndian bigEndian var BigEndian bigEndian
type littleEndian unused type littleEndian struct{}
func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } 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" } 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 } func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 }

View file

@ -1455,11 +1455,14 @@ func TestFuzz(t *testing.T) {
func TestFuzzRegressions(t *testing.T) { func TestFuzzRegressions(t *testing.T) {
// An instance triggering a type name of length ~102 GB. // An instance triggering a type name of length ~102 GB.
testFuzz(t, 1328492090837718000, 100, new(float32)) 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{}) { func testFuzz(t *testing.T, seed int64, n int, input ...interface{}) {
t.Logf("seed=%d n=%d\n", seed, n)
for _, e := range input { for _, e := range input {
t.Logf("seed=%d n=%d e=%T", seed, n, e)
rng := rand.New(rand.NewSource(seed)) rng := rand.New(rand.NewSource(seed))
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
encFuzzDec(rng, e) encFuzzDec(rng, e)

View file

@ -3,14 +3,15 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Delete the next line to include in the gob package. // Delete the next line to include in the gob package.
// +build gob-debug // +build ignore
package gob package gob
// This file is not normally included in the gob package. Used only for debugging the package itself. // 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 // Except for reading uints, it is an implementation of a reader that is independent of
// the one implemented by Decoder. // the one implemented by Decoder.
// To enable the Debug function, delete the +build ignore line above and do
// go install
import ( import (
"bytes" "bytes"

View file

@ -392,12 +392,12 @@ func decUint8Slice(i *decInstr, state *decoderState, p unsafe.Pointer) {
} }
p = *(*unsafe.Pointer)(p) p = *(*unsafe.Pointer)(p)
} }
n := int(state.decodeUint()) n := state.decodeUint()
if n < 0 { if n > uint64(state.b.Len()) {
errorf("negative length decoding []byte") errorf("length of []byte exceeds input size (%d bytes)", n)
} }
slice := (*[]uint8)(p) slice := (*[]uint8)(p)
if cap(*slice) < n { if uint64(cap(*slice)) < n {
*slice = make([]uint8, n) *slice = make([]uint8, n)
} else { } else {
*slice = (*slice)[0:n] *slice = (*slice)[0:n]
@ -417,7 +417,11 @@ func decString(i *decInstr, state *decoderState, p unsafe.Pointer) {
} }
p = *(*unsafe.Pointer)(p) 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) state.b.Read(b)
// It would be a shame to do the obvious thing here, // It would be a shame to do the obvious thing here,
// *(*string)(p) = string(b) // *(*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. // decodeSlice decodes a slice and stores the slice header through p.
// Slices are encoded as an unsigned length followed by the elements. // 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) { 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 { if indir > 0 {
up := unsafe.Pointer(p) up := unsafe.Pointer(p)
if *(*unsafe.Pointer)(up) == nil { 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() *(*[2]uintptr)(unsafe.Pointer(p)) = ivalue.InterfaceData()
return return
} }
if len(name) > 1024 {
errorf("name too long (%d bytes): %.20q...", len(name), name)
}
// The concrete type must be registered. // The concrete type must be registered.
typ, ok := nameToConcreteType[name] typ, ok := nameToConcreteType[name]
if !ok { if !ok {

View file

@ -7,6 +7,7 @@
package main package main
// Need to compile package gob with debug.go to build this program. // Need to compile package gob with debug.go to build this program.
// See comments in debug.go for how to do this.
import ( import (
"encoding/gob" "encoding/gob"

View file

@ -709,7 +709,7 @@ func TestGobPtrSlices(t *testing.T) {
t.Fatal("decode:", err) t.Fatal("decode:", err)
} }
if !reflect.DeepEqual(in, out) { if !reflect.DeepEqual(in, out) {
t.Fatal("got %v; wanted %v", out, in) t.Fatalf("got %v; wanted %v", out, in)
} }
} }

View file

@ -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. // WrongString is a struct that's misusing the ,string modifier.
type WrongString struct { type WrongString struct {
Message string `json:"result,string"` Message string `json:"result,string"`

View file

@ -123,17 +123,6 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
return buf.Bytes(), nil 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 & // HTMLEscape appends to dst the JSON-encoded src with <, >, and &
// characters inside string literals changed to \u003c, \u003e, \u0026 // characters inside string literals changed to \u003c, \u003e, \u0026
// so that the JSON will be safe to embed inside HTML <script> tags. // 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() return "json: error calling MarshalJSON for type " + e.Type.String() + ": " + e.Err.Error()
} }
type interfaceOrPtrValue interface {
IsNil() bool
Elem() reflect.Value
}
var hex = "0123456789abcdef" var hex = "0123456789abcdef"
// An encodeState encodes JSON into a bytes.Buffer. // 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() b, err := m.MarshalJSON()
if err == nil { if err == nil {
// copy JSON into buffer, checking validity. // copy JSON into buffer, checking validity.
err = Compact(&e.Buffer, b) err = compact(&e.Buffer, b, true)
} }
if err != nil { if err != nil {
e.error(&MarshalerError{v.Type(), err}) e.error(&MarshalerError{v.Type(), err})

View file

@ -167,3 +167,22 @@ func TestRefValMarshal(t *testing.T) {
t.Errorf("got %q, want %q", got, want) 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)
}
}

View file

@ -9,11 +9,24 @@ import "bytes"
// Compact appends to dst the JSON-encoded src with // Compact appends to dst the JSON-encoded src with
// insignificant space characters elided. // insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error { 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() origLen := dst.Len()
var scan scanner var scan scanner
scan.reset() scan.reset()
start := 0 start := 0
for i, c := range src { 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)) v := scan.step(&scan, int(c))
if v >= scanSkipSpace { if v >= scanSkipSpace {
if v == scanError { if v == scanError {

View file

@ -136,12 +136,12 @@ type NamePrecedence struct {
type XMLNameWithTag struct { type XMLNameWithTag struct {
XMLName Name `xml:"InXMLNameTag"` XMLName Name `xml:"InXMLNameTag"`
Value string ",chardata" Value string `xml:",chardata"`
} }
type XMLNameWithoutTag struct { type XMLNameWithoutTag struct {
XMLName Name XMLName Name
Value string ",chardata" Value string `xml:",chardata"`
} }
type NameInField struct { type NameInField struct {
@ -532,9 +532,9 @@ var marshalTests = []struct {
InFieldName: "D", InFieldName: "D",
}, },
ExpectXML: `<Parent>` + ExpectXML: `<Parent>` +
`<InTag><Value>A</Value></InTag>` + `<InTag>A</InTag>` +
`<InXMLName><Value>B</Value></InXMLName>` + `<InXMLName>B</InXMLName>` +
`<InXMLNameTag><Value>C</Value></InXMLNameTag>` + `<InXMLNameTag>C</InXMLNameTag>` +
`<InFieldName>D</InFieldName>` + `<InFieldName>D</InFieldName>` +
`</Parent>`, `</Parent>`,
MarshalOnly: true, MarshalOnly: true,
@ -548,9 +548,9 @@ var marshalTests = []struct {
InFieldName: "D", InFieldName: "D",
}, },
ExpectXML: `<Parent>` + ExpectXML: `<Parent>` +
`<InTag><Value>A</Value></InTag>` + `<InTag>A</InTag>` +
`<FromNameVal><Value>B</Value></FromNameVal>` + `<FromNameVal>B</FromNameVal>` +
`<InXMLNameTag><Value>C</Value></InXMLNameTag>` + `<InXMLNameTag>C</InXMLNameTag>` +
`<InFieldName>D</InFieldName>` + `<InFieldName>D</InFieldName>` +
`</Parent>`, `</Parent>`,
UnmarshalOnly: true, UnmarshalOnly: true,

View file

@ -34,6 +34,8 @@ The flags are:
Verbose mode. Verbose mode.
Debugging flags: Debugging flags:
-comments
Parse comments (ignored if -ast not set).
-ast -ast
Print AST (disables concurrent parsing). Print AST (disables concurrent parsing).
-trace -trace

View file

@ -27,8 +27,9 @@ var (
allErrors = flag.Bool("e", false, "print all (including spurious) errors") allErrors = flag.Bool("e", false, "print all (including spurious) errors")
// debugging support // debugging support
printTrace = flag.Bool("trace", false, "print parse trace") parseComments = flag.Bool("comments", false, "parse comments (ignored if -ast not set)")
printAST = flag.Bool("ast", false, "print AST") printTrace = flag.Bool("trace", false, "print parse trace")
printAST = flag.Bool("ast", false, "print AST")
) )
var exitCode = 0 var exitCode = 0
@ -73,6 +74,9 @@ func parse(fset *token.FileSet, filename string, src []byte) *ast.File {
if *allErrors { if *allErrors {
mode |= parser.SpuriousErrors mode |= parser.SpuriousErrors
} }
if *parseComments && *printAST {
mode |= parser.ParseComments
}
if *printTrace { if *printTrace {
mode |= parser.Trace mode |= parser.Trace
} }

View file

@ -110,7 +110,7 @@ func (s *nodeStack) top() *Node {
return nil 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. // if n is not present.
func (s *nodeStack) index(n *Node) int { func (s *nodeStack) index(n *Node) int {
for i := len(*s) - 1; i >= 0; i-- { for i := len(*s) - 1; i >= 0; i-- {

View file

@ -18,17 +18,17 @@ package norm
// has the form: // has the form:
// <header> <decomp_byte>* [<tccc> [<lccc>]] // <header> <decomp_byte>* [<tccc> [<lccc>]]
// The header contains the number of bytes in the decomposition (excluding this // 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. // 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 // 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 // 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, // 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. // there is an additional leading ccc.
const ( const (
qcInfoMask = 0xF // to clear all but the relevant bits in a qcInfo 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 headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte
) )

View file

@ -75,7 +75,7 @@ func (p *PerHost) dialerForRequest(host string) Dialer {
} }
// AddFromString parses a string that contains comma-separated values // 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 // 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 // (localhost). A best effort is made to parse the string and errors are
// ignored. // ignored.

View file

@ -18,6 +18,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"text/scanner" "text/scanner"
) )
@ -39,11 +40,14 @@ func findPkg(path string) (filename, id string) {
switch path[0] { switch path[0] {
default: default:
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
tree, pkg, err := build.FindTree(path) bp, _ := build.Import(path, "", build.FindOnly)
if err != nil { if bp.PkgObj == "" {
return return
} }
noext = filepath.Join(tree.PkgDir(), pkg) noext = bp.PkgObj
if strings.HasSuffix(noext, ".a") {
noext = noext[:len(noext)-2]
}
case '.': case '.':
// "./x" -> "/this/directory/x.ext", "/this/directory/x" // "./x" -> "/this/directory/x.ext", "/this/directory/x"
@ -742,7 +746,7 @@ func (p *gcParser) parseVarDecl() {
} }
// FuncBody = "{" ... "}" . // FuncBody = "{" ... "}" .
// //
func (p *gcParser) parseFuncBody() { func (p *gcParser) parseFuncBody() {
p.expect('{') p.expect('{')
for i := 1; i > 0; p.next() { for i := 1; i > 0; p.next() {

View file

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package types

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package ast

View file

@ -2,10 +2,948 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package build provides tools for building Go packages.
package build 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. // ArchChar returns the architecture character for the given goarch.
// For example, ArchChar("amd64") returns "6". // For example, ArchChar("amd64") returns "6".

View file

@ -5,83 +5,14 @@
package build package build
import ( import (
"os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"sort"
"testing" "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) { func TestMatch(t *testing.T) {
ctxt := DefaultContext ctxt := Default
what := "default" what := "default"
match := func(tag string) { match := func(tag string) {
if !ctxt.match(tag) { if !ctxt.match(tag) {
@ -106,3 +37,40 @@ func TestMatch(t *testing.T) {
match(runtime.GOOS + "," + runtime.GOARCH + ",!bar") match(runtime.GOOS + "," + runtime.GOARCH + ",!bar")
nomatch(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")
}
}

View file

@ -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
}

View file

@ -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)))
}

View file

@ -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
}
}

109
libgo/go/go/build/doc.go Normal file
View file

@ -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

View file

@ -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]
}
}

View file

@ -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

View file

@ -1 +0,0 @@
package pkgtest

View file

@ -1 +0,0 @@
package pkgtest

View file

@ -1 +0,0 @@
package pkgtest

View file

@ -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

View file

@ -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

View file

@ -55,7 +55,7 @@ var tests = []GoodFileTest{
func TestGoodOSArch(t *testing.T) { func TestGoodOSArch(t *testing.T) {
for _, test := range tests { 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) t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result)
} }
} }

View file

@ -0,0 +1,5 @@
// Test data - not compiled.
package file
func F() {}

View file

@ -0,0 +1,11 @@
// Test data - not compiled.
package main
import (
"./file"
)
func main() {
file.F()
}

View file

@ -432,6 +432,17 @@ func (r *reader) readFile(src *ast.File) {
r.readValue(d) r.readValue(d)
case token.TYPE: case token.TYPE:
// types are handled individually // 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 { for _, spec := range d.Specs {
if s, ok := spec.(*ast.TypeSpec); ok { if s, ok := spec.(*ast.TypeSpec); ok {
// use an individual (possibly fake) declaration // use an individual (possibly fake) declaration
@ -439,8 +450,13 @@ func (r *reader) readFile(src *ast.File) {
// gets to (re-)use the declaration documentation // gets to (re-)use the declaration documentation
// if there's none associated with the spec itself // if there's none associated with the spec itself
fake := &ast.GenDecl{ fake := &ast.GenDecl{
Doc: d.Doc, Doc: d.Doc,
TokPos: d.Pos(), // 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, Tok: token.TYPE,
Specs: []ast.Spec{s}, Specs: []ast.Spec{s},
} }

View file

@ -14,6 +14,9 @@ import (
"go/ast" "go/ast"
"go/scanner" "go/scanner"
"go/token" "go/token"
"strconv"
"strings"
"unicode"
) )
// The parser structure holds the parser's internal state. // 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 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 { func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec {
if p.trace { if p.trace {
defer un(trace(p, "ImportSpec")) defer un(trace(p, "ImportSpec"))
@ -1929,6 +1943,9 @@ func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec {
var path *ast.BasicLit var path *ast.BasicLit
if p.tok == token.STRING { 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} path = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
p.next() p.next()
} else { } else {

View file

@ -5,6 +5,7 @@
package parser package parser
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"os" "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)
}
}
}

View file

@ -12,6 +12,7 @@ import (
"bytes" "bytes"
"go/ast" "go/ast"
"go/token" "go/token"
"unicode/utf8"
) )
// Other formatting issues: // Other formatting issues:
@ -82,46 +83,37 @@ func (p *printer) setComment(g *ast.CommentGroup) {
type exprListMode uint type exprListMode uint
const ( const (
blankStart exprListMode = 1 << iota // print a blank before a non-empty list commaTerm exprListMode = 1 << iota // list is optionally terminated by a comma
blankEnd // print a blank after a non-empty list noIndent // no extra indentation in multi-line lists
commaSep // elements are separated by commas
commaTerm // 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 // If indent is set, a multi-line identifier list is indented after the
// first linebreak encountered. // 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 // convert into an expression list so we can re-use exprList formatting
xlist := make([]ast.Expr, len(list)) xlist := make([]ast.Expr, len(list))
for i, x := range list { for i, x := range list {
xlist[i] = x xlist[i] = x
} }
mode := commaSep var mode exprListMode
if !indent { 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 // Print a list of expressions. If the list spans multiple
// source lines, the original line breaks are respected between // source lines, the original line breaks are respected between
// expressions. Sets multiLine to true if the list spans multiple // expressions.
// lines.
// //
// TODO(gri) Consider rewriting this to be independent of []ast.Expr // TODO(gri) Consider rewriting this to be independent of []ast.Expr
// so that we can use the algorithm for any kind of list // so that we can use the algorithm for any kind of list
// (e.g., pass list via a channel over which to range). // (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 { if len(list) == 0 {
return return
} }
if mode&blankStart != 0 {
p.print(blank)
}
prev := p.posFor(prev0) prev := p.posFor(prev0)
next := p.posFor(next0) next := p.posFor(next0)
line := p.lineFor(list[0].Pos()) 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 // all list entries on a single line
for i, x := range list { for i, x := range list {
if i > 0 { if i > 0 {
if mode&commaSep != 0 { // use position of expression following the comma as
// use position of expression following the comma as // comma position for correct comment placement
// comma position for correct comment placement p.print(x.Pos(), token.COMMA, blank)
p.print(x.Pos(), token.COMMA)
}
p.print(blank)
} }
p.expr0(x, depth, multiLine) p.expr0(x, depth)
}
if mode&blankEnd != 0 {
p.print(blank)
} }
return 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 prevBreak := -1 // index of last expression that was followed by a linebreak
if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) { if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) {
ws = ignore ws = ignore
*multiLine = true
prevBreak = 0 prevBreak = 0
} }
@ -215,15 +200,13 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
if i > 0 { if i > 0 {
needsLinebreak := prevLine < line && prevLine > 0 && line > 0 needsLinebreak := prevLine < line && prevLine > 0 && line > 0
if mode&commaSep != 0 { // use position of expression following the comma as
// use position of expression following the comma as // comma position for correct comment placement, but
// comma position for correct comment placement, but // only if the expression is on the same line
// only if the expression is on the same line if !needsLinebreak {
if !needsLinebreak { p.print(x.Pos())
p.print(x.Pos())
}
p.print(token.COMMA)
} }
p.print(token.COMMA)
needsBlank := true needsBlank := true
if needsLinebreak { if needsLinebreak {
// lines are broken using newlines so comments remain aligned // 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 // the same line in which case formfeed is used
if p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) { if p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) {
ws = ignore ws = ignore
*multiLine = true
prevBreak = i prevBreak = i
needsBlank = false // we got a line break instead 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 // 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 // is in a list with more then one entry: use a column for the
// key such that consecutive entries can align if possible // 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.print(pair.Colon, token.COLON, vtab)
p.expr(pair.Value, multiLine) p.expr(pair.Value)
} else { } 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 return
} }
if mode&blankEnd != 0 {
p.print(blank)
}
if ws == ignore && mode&noIndent == 0 { if ws == ignore && mode&noIndent == 0 {
// unindent if we indented // unindent if we indented
p.print(unindent) p.print(unindent)
} }
} }
// Sets multiLine to true if the the parameter list spans multiple lines. func (p *printer) parameters(fields *ast.FieldList) {
func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
p.print(fields.Opening, token.LPAREN) p.print(fields.Opening, token.LPAREN)
if len(fields.List) > 0 { if len(fields.List) > 0 {
prevLine := p.lineFor(fields.Opening) 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) { if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) {
// break line if the opening "(" or previous parameter ended on a different line // break line if the opening "(" or previous parameter ended on a different line
ws = ignore ws = ignore
*multiLine = true
} else if i > 0 { } else if i > 0 {
p.print(blank) 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 // 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 // by a linebreak call after a type, or in the next multi-line identList
// will do the right thing. // will do the right thing.
p.identList(par.Names, ws == indent, multiLine) p.identList(par.Names, ws == indent)
p.print(blank) p.print(blank)
} }
// parameter type // parameter type
p.expr(par.Type, multiLine) p.expr(par.Type)
prevLine = parLineEnd prevLine = parLineEnd
} }
// if the closing ")" is on a separate line from the last parameter, // 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) p.print(fields.Closing, token.RPAREN)
} }
// Sets multiLine to true if the signature spans multiple lines. func (p *printer) signature(params, result *ast.FieldList) {
func (p *printer) signature(params, result *ast.FieldList, multiLine *bool) { p.parameters(params)
p.parameters(params, multiLine)
n := result.NumFields() n := result.NumFields()
if n > 0 { if n > 0 {
p.print(blank) p.print(blank)
if n == 1 && result.List[0].Names == nil { if n == 1 && result.List[0].Names == nil {
// single anonymous result; no ()'s // single anonymous result; no ()'s
p.expr(result.List[0].Type, multiLine) p.expr(result.List[0].Type)
return return
} }
p.parameters(result, multiLine) p.parameters(result)
} }
} }
func identListSize(list []*ast.Ident, maxSize int) (size int) { func identListSize(list []*ast.Ident, maxSize int) (size int) {
for i, x := range list { for i, x := range list {
if i > 0 { if i > 0 {
size += 2 // ", " size += len(", ")
} }
size += len(x.Name) size += utf8.RuneCountInString(x.Name)
if size >= maxSize { if size >= maxSize {
break break
} }
@ -389,6 +364,10 @@ func (p *printer) setLineComment(text string) {
p.setComment(&ast.CommentGroup{List: []*ast.Comment{{Slash: token.NoPos, Text: text}}}) 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) { func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) {
lbrace := fields.Opening lbrace := fields.Opening
list := fields.List 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 // no comments so no need for comma position
p.print(token.COMMA, blank) p.print(token.COMMA, blank)
} }
p.expr(x, ignoreMultiLine) p.expr(x)
} }
if len(f.Names) > 0 { if len(f.Names) > 0 {
p.print(blank) p.print(blank)
} }
p.expr(f.Type, ignoreMultiLine) p.expr(f.Type)
p.print(blank, rbrace, token.RBRACE) p.print(blank, rbrace, token.RBRACE)
return return
} }
@ -435,23 +414,22 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
if len(list) == 1 { if len(list) == 1 {
sep = blank sep = blank
} }
var ml bool newSection := false
for i, f := range list { for i, f := range list {
if i > 0 { 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 extraTabs := 0
p.setComment(f.Doc) p.setComment(f.Doc)
if len(f.Names) > 0 { if len(f.Names) > 0 {
// named fields // named fields
p.identList(f.Names, false, &ml) p.identList(f.Names, false)
p.print(sep) p.print(sep)
p.expr(f.Type, &ml) p.expr(f.Type)
extraTabs = 1 extraTabs = 1
} else { } else {
// anonymous field // anonymous field
p.expr(f.Type, &ml) p.expr(f.Type)
extraTabs = 2 extraTabs = 2
} }
if f.Tag != nil { 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.print(sep) p.print(sep)
p.expr(f.Tag, &ml) p.expr(f.Tag)
extraTabs = 0 extraTabs = 0
} }
if f.Comment != nil { if f.Comment != nil {
@ -468,6 +446,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
} }
p.setComment(f.Comment) p.setComment(f.Comment)
} }
newSection = p.isMultiLine(f)
} }
if isIncomplete { if isIncomplete {
if len(list) > 0 { if len(list) > 0 {
@ -479,22 +458,22 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
} else { // interface } else { // interface
var ml bool newSection := false
for i, f := range list { for i, f := range list {
if i > 0 { 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) p.setComment(f.Doc)
if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp {
// method // method
p.expr(f.Names[0], &ml) p.expr(f.Names[0])
p.signature(ftyp.Params, ftyp.Results, &ml) p.signature(ftyp.Params, ftyp.Results)
} else { } else {
// embedded interface // embedded interface
p.expr(f.Type, &ml) p.expr(f.Type)
} }
p.setComment(f.Comment) p.setComment(f.Comment)
newSection = p.isMultiLine(f)
} }
if isIncomplete { if isIncomplete {
if len(list) > 0 { if len(list) > 0 {
@ -635,15 +614,14 @@ func reduceDepth(depth int) int {
// cutoff is 6 (always use spaces) in Normal mode // cutoff is 6 (always use spaces) in Normal mode
// and 4 (never use spaces) in Compact 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) {
func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiLine *bool) {
prec := x.Op.Precedence() prec := x.Op.Precedence()
if prec < prec1 { if prec < prec1 {
// parenthesis needed // parenthesis needed
// Note: The parser inserts an ast.ParenExpr node; thus this case // Note: The parser inserts an ast.ParenExpr node; thus this case
// can only occur if the AST is created in a different way. // can only occur if the AST is created in a different way.
p.print(token.LPAREN) 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) p.print(token.RPAREN)
return return
} }
@ -651,7 +629,7 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiL
printBlank := prec < cutoff printBlank := prec < cutoff
ws := indent 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 { if printBlank {
p.print(blank) p.print(blank)
} }
@ -663,14 +641,13 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiL
// in the source // in the source
if p.linebreak(yline, 1, ws, true) { if p.linebreak(yline, 1, ws, true) {
ws = ignore ws = ignore
*multiLine = true
printBlank = false // no blank after line break printBlank = false // no blank after line break
} }
} }
if printBlank { if printBlank {
p.print(blank) p.print(blank)
} }
p.expr1(x.Y, prec+1, depth+1, multiLine) p.expr1(x.Y, prec+1, depth+1)
if ws == ignore { if ws == ignore {
p.print(unindent) p.print(unindent)
} }
@ -681,8 +658,7 @@ func isBinary(expr ast.Expr) bool {
return ok return ok
} }
// Sets multiLine to true if the expression spans multiple lines. func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
p.print(expr.Pos()) p.print(expr.Pos())
switch x := expr.(type) { 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) p.internalError("depth < 1:", depth)
depth = 1 depth = 1
} }
p.binaryExpr(x, prec1, cutoff(x, depth), depth, multiLine) p.binaryExpr(x, prec1, cutoff(x, depth), depth)
case *ast.KeyValueExpr: case *ast.KeyValueExpr:
p.expr(x.Key, multiLine) p.expr(x.Key)
p.print(x.Colon, token.COLON, blank) p.print(x.Colon, token.COLON, blank)
p.expr(x.Value, multiLine) p.expr(x.Value)
case *ast.StarExpr: case *ast.StarExpr:
const prec = token.UnaryPrec const prec = token.UnaryPrec
@ -710,12 +686,12 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
// parenthesis needed // parenthesis needed
p.print(token.LPAREN) p.print(token.LPAREN)
p.print(token.MUL) p.print(token.MUL)
p.expr(x.X, multiLine) p.expr(x.X)
p.print(token.RPAREN) p.print(token.RPAREN)
} else { } else {
// no parenthesis needed // no parenthesis needed
p.print(token.MUL) p.print(token.MUL)
p.expr(x.X, multiLine) p.expr(x.X)
} }
case *ast.UnaryExpr: case *ast.UnaryExpr:
@ -723,7 +699,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
if prec < prec1 { if prec < prec1 {
// parenthesis needed // parenthesis needed
p.print(token.LPAREN) p.print(token.LPAREN)
p.expr(x, multiLine) p.expr(x)
p.print(token.RPAREN) p.print(token.RPAREN)
} else { } else {
// no parenthesis needed // 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. // TODO(gri) Remove this code if it cannot be reached.
p.print(blank) p.print(blank)
} }
p.expr1(x.X, prec, depth, multiLine) p.expr1(x.X, prec, depth)
} }
case *ast.BasicLit: case *ast.BasicLit:
p.print(x) p.print(x)
case *ast.FuncLit: case *ast.FuncLit:
p.expr(x.Type, multiLine) p.expr(x.Type)
p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true, multiLine) p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true)
case *ast.ParenExpr: case *ast.ParenExpr:
if _, hasParens := x.X.(*ast.ParenExpr); hasParens { if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
// don't print parentheses around an already parenthesized expression // don't print parentheses around an already parenthesized expression
// TODO(gri) consider making this more general and incorporate precedence levels // 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 { } else {
p.print(token.LPAREN) 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) p.print(x.Rparen, token.RPAREN)
} }
case *ast.SelectorExpr: case *ast.SelectorExpr:
p.expr1(x.X, token.HighestPrec, depth, multiLine) p.expr1(x.X, token.HighestPrec, depth)
p.print(token.PERIOD) p.print(token.PERIOD)
if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line { if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line {
p.print(indent, newline, x.Sel.Pos(), x.Sel, unindent) p.print(indent, newline, x.Sel.Pos(), x.Sel, unindent)
*multiLine = true
} else { } else {
p.print(x.Sel.Pos(), x.Sel) p.print(x.Sel.Pos(), x.Sel)
} }
case *ast.TypeAssertExpr: case *ast.TypeAssertExpr:
p.expr1(x.X, token.HighestPrec, depth, multiLine) p.expr1(x.X, token.HighestPrec, depth)
p.print(token.PERIOD, token.LPAREN) p.print(token.PERIOD, token.LPAREN)
if x.Type != nil { if x.Type != nil {
p.expr(x.Type, multiLine) p.expr(x.Type)
} else { } else {
p.print(token.TYPE) p.print(token.TYPE)
} }
@ -775,17 +750,17 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
case *ast.IndexExpr: case *ast.IndexExpr:
// TODO(gri): should treat[] like parentheses and undo one level of depth // 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.print(x.Lbrack, token.LBRACK)
p.expr0(x.Index, depth+1, multiLine) p.expr0(x.Index, depth+1)
p.print(x.Rbrack, token.RBRACK) p.print(x.Rbrack, token.RBRACK)
case *ast.SliceExpr: case *ast.SliceExpr:
// TODO(gri): should treat[] like parentheses and undo one level of depth // 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.print(x.Lbrack, token.LBRACK)
if x.Low != nil { 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 // 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)) { 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) p.print(token.COLON)
} }
if x.High != nil { if x.High != nil {
p.expr0(x.High, depth+1, multiLine) p.expr0(x.High, depth+1)
} }
p.print(x.Rbrack, token.RBRACK) 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 { if len(x.Args) > 1 {
depth++ depth++
} }
p.expr1(x.Fun, token.HighestPrec, depth, multiLine) p.expr1(x.Fun, token.HighestPrec, depth)
p.print(x.Lparen, token.LPAREN) p.print(x.Lparen, token.LPAREN)
p.exprList(x.Lparen, x.Args, depth, commaSep|commaTerm, multiLine, x.Rparen)
if x.Ellipsis.IsValid() { if x.Ellipsis.IsValid() {
p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis)
p.print(x.Ellipsis, token.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) p.print(x.Rparen, token.RPAREN)
case *ast.CompositeLit: case *ast.CompositeLit:
// composite literal elements that are composite literals themselves may have the type omitted // composite literal elements that are composite literals themselves may have the type omitted
if x.Type != nil { 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.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 // do not insert extra line breaks because of comments before
// the closing '}' as it might break the code if there is no // the closing '}' as it might break the code if there is no
// trailing ',' // trailing ','
@ -825,16 +805,16 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
case *ast.Ellipsis: case *ast.Ellipsis:
p.print(token.ELLIPSIS) p.print(token.ELLIPSIS)
if x.Elt != nil { if x.Elt != nil {
p.expr(x.Elt, multiLine) p.expr(x.Elt)
} }
case *ast.ArrayType: case *ast.ArrayType:
p.print(token.LBRACK) p.print(token.LBRACK)
if x.Len != nil { if x.Len != nil {
p.expr(x.Len, multiLine) p.expr(x.Len)
} }
p.print(token.RBRACK) p.print(token.RBRACK)
p.expr(x.Elt, multiLine) p.expr(x.Elt)
case *ast.StructType: case *ast.StructType:
p.print(token.STRUCT) p.print(token.STRUCT)
@ -842,7 +822,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
case *ast.FuncType: case *ast.FuncType:
p.print(token.FUNC) p.print(token.FUNC)
p.signature(x.Params, x.Results, multiLine) p.signature(x.Params, x.Results)
case *ast.InterfaceType: case *ast.InterfaceType:
p.print(token.INTERFACE) p.print(token.INTERFACE)
@ -850,9 +830,9 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
case *ast.MapType: case *ast.MapType:
p.print(token.MAP, token.LBRACK) p.print(token.MAP, token.LBRACK)
p.expr(x.Key, multiLine) p.expr(x.Key)
p.print(token.RBRACK) p.print(token.RBRACK)
p.expr(x.Value, multiLine) p.expr(x.Value)
case *ast.ChanType: case *ast.ChanType:
switch x.Dir { 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(token.CHAN, token.ARROW)
} }
p.print(blank) p.print(blank)
p.expr(x.Value, multiLine) p.expr(x.Value)
default: default:
panic("unreachable") panic("unreachable")
@ -873,14 +853,13 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
return return
} }
func (p *printer) expr0(x ast.Expr, depth int, multiLine *bool) { func (p *printer) expr0(x ast.Expr, depth int) {
p.expr1(x, token.LowestPrec, depth, multiLine) p.expr1(x, token.LowestPrec, depth)
} }
// Sets multiLine to true if the expression spans multiple lines. func (p *printer) expr(x ast.Expr) {
func (p *printer) expr(x ast.Expr, multiLine *bool) {
const depth = 1 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 { if _indent > 0 {
p.print(indent) p.print(indent)
} }
var multiLine bool multiLine := false
for i, s := range list { for i, s := range list {
// _indent == 0 only for lists of switch/select case clauses; // _indent == 0 only for lists of switch/select case clauses;
// in those cases each clause is a new section // in those cases each clause is a new section
p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || _indent == 0 || multiLine) p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || _indent == 0 || multiLine)
multiLine = false p.stmt(s, nextIsRBrace && i == len(list)-1)
p.stmt(s, nextIsRBrace && i == len(list)-1, &multiLine) multiLine = p.isMultiLine(s)
} }
if _indent > 0 { if _indent > 0 {
p.print(unindent) 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 { if init == nil && post == nil {
// no semicolons required // no semicolons required
if expr != nil { if expr != nil {
p.expr(stripParens(expr), ignoreMultiLine) p.expr(stripParens(expr))
needsBlank = true needsBlank = true
} }
} else { } else {
// all semicolons required // all semicolons required
// (they are not separators, print them explicitly) // (they are not separators, print them explicitly)
if init != nil { if init != nil {
p.stmt(init, false, ignoreMultiLine) p.stmt(init, false)
} }
p.print(token.SEMICOLON, blank) p.print(token.SEMICOLON, blank)
if expr != nil { if expr != nil {
p.expr(stripParens(expr), ignoreMultiLine) p.expr(stripParens(expr))
needsBlank = true needsBlank = true
} }
if isForStmt { if isForStmt {
p.print(token.SEMICOLON, blank) p.print(token.SEMICOLON, blank)
needsBlank = false needsBlank = false
if post != nil { if post != nil {
p.stmt(post, false, ignoreMultiLine) p.stmt(post, false)
needsBlank = true 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) {
func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
p.print(stmt.Pos()) p.print(stmt.Pos())
switch s := stmt.(type) { switch s := stmt.(type) {
@ -994,7 +972,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
p.print("BadStmt") p.print("BadStmt")
case *ast.DeclStmt: case *ast.DeclStmt:
p.decl(s.Decl, multiLine) p.decl(s.Decl)
case *ast.EmptyStmt: case *ast.EmptyStmt:
// nothing to do // 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 // is applied before the line break if there is no comment
// between (see writeWhitespace) // between (see writeWhitespace)
p.print(unindent) p.print(unindent)
p.expr(s.Label, multiLine) p.expr(s.Label)
p.print(s.Colon, token.COLON, indent) p.print(s.Colon, token.COLON, indent)
if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty {
if !nextIsRBrace { if !nextIsRBrace {
@ -1014,21 +992,21 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
} else { } else {
p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true) p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true)
} }
p.stmt(s.Stmt, nextIsRBrace, multiLine) p.stmt(s.Stmt, nextIsRBrace)
case *ast.ExprStmt: case *ast.ExprStmt:
const depth = 1 const depth = 1
p.expr0(s.X, depth, multiLine) p.expr0(s.X, depth)
case *ast.SendStmt: case *ast.SendStmt:
const depth = 1 const depth = 1
p.expr0(s.Chan, depth, multiLine) p.expr0(s.Chan, depth)
p.print(blank, s.Arrow, token.ARROW, blank) p.print(blank, s.Arrow, token.ARROW, blank)
p.expr0(s.Value, depth, multiLine) p.expr0(s.Value, depth)
case *ast.IncDecStmt: case *ast.IncDecStmt:
const depth = 1 const depth = 1
p.expr0(s.X, depth+1, multiLine) p.expr0(s.X, depth+1)
p.print(s.TokPos, s.Tok) p.print(s.TokPos, s.Tok)
case *ast.AssignStmt: 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 { if len(s.Lhs) > 1 && len(s.Rhs) > 1 {
depth++ depth++
} }
p.exprList(s.Pos(), s.Lhs, depth, commaSep, multiLine, s.TokPos) p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos)
p.print(blank, s.TokPos, s.Tok) p.print(blank, s.TokPos, s.Tok, blank)
p.exprList(s.TokPos, s.Rhs, depth, blankStart|commaSep, multiLine, token.NoPos) p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos)
case *ast.GoStmt: case *ast.GoStmt:
p.print(token.GO, blank) p.print(token.GO, blank)
p.expr(s.Call, multiLine) p.expr(s.Call)
case *ast.DeferStmt: case *ast.DeferStmt:
p.print(token.DEFER, blank) p.print(token.DEFER, blank)
p.expr(s.Call, multiLine) p.expr(s.Call)
case *ast.ReturnStmt: case *ast.ReturnStmt:
p.print(token.RETURN) p.print(token.RETURN)
if s.Results != nil { 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: case *ast.BranchStmt:
p.print(s.Tok) p.print(s.Tok)
if s.Label != nil { if s.Label != nil {
p.print(blank) p.print(blank)
p.expr(s.Label, multiLine) p.expr(s.Label)
} }
case *ast.BlockStmt: case *ast.BlockStmt:
p.block(s, 1) p.block(s, 1)
*multiLine = true
case *ast.IfStmt: case *ast.IfStmt:
p.print(token.IF) p.print(token.IF)
p.controlClause(false, s.Init, s.Cond, nil) p.controlClause(false, s.Init, s.Cond, nil)
p.block(s.Body, 1) p.block(s.Body, 1)
*multiLine = true
if s.Else != nil { if s.Else != nil {
p.print(blank, token.ELSE, blank) p.print(blank, token.ELSE, blank)
switch s.Else.(type) { switch s.Else.(type) {
case *ast.BlockStmt, *ast.IfStmt: case *ast.BlockStmt, *ast.IfStmt:
p.stmt(s.Else, nextIsRBrace, ignoreMultiLine) p.stmt(s.Else, nextIsRBrace)
default: default:
p.print(token.LBRACE, indent, formfeed) p.print(token.LBRACE, indent, formfeed)
p.stmt(s.Else, true, ignoreMultiLine) p.stmt(s.Else, true)
p.print(unindent, formfeed, token.RBRACE) p.print(unindent, formfeed, token.RBRACE)
} }
} }
case *ast.CaseClause: case *ast.CaseClause:
if s.List != nil { if s.List != nil {
p.print(token.CASE) p.print(token.CASE, blank)
p.exprList(s.Pos(), s.List, 1, blankStart|commaSep, multiLine, s.Colon) p.exprList(s.Pos(), s.List, 1, 0, s.Colon)
} else { } else {
p.print(token.DEFAULT) p.print(token.DEFAULT)
} }
@ -1096,25 +1073,23 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
p.print(token.SWITCH) p.print(token.SWITCH)
p.controlClause(false, s.Init, s.Tag, nil) p.controlClause(false, s.Init, s.Tag, nil)
p.block(s.Body, 0) p.block(s.Body, 0)
*multiLine = true
case *ast.TypeSwitchStmt: case *ast.TypeSwitchStmt:
p.print(token.SWITCH) p.print(token.SWITCH)
if s.Init != nil { if s.Init != nil {
p.print(blank) p.print(blank)
p.stmt(s.Init, false, ignoreMultiLine) p.stmt(s.Init, false)
p.print(token.SEMICOLON) p.print(token.SEMICOLON)
} }
p.print(blank) p.print(blank)
p.stmt(s.Assign, false, ignoreMultiLine) p.stmt(s.Assign, false)
p.print(blank) p.print(blank)
p.block(s.Body, 0) p.block(s.Body, 0)
*multiLine = true
case *ast.CommClause: case *ast.CommClause:
if s.Comm != nil { if s.Comm != nil {
p.print(token.CASE, blank) p.print(token.CASE, blank)
p.stmt(s.Comm, false, ignoreMultiLine) p.stmt(s.Comm, false)
} else { } else {
p.print(token.DEFAULT) 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) p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE)
} else { } else {
p.block(body, 0) p.block(body, 0)
*multiLine = true
} }
case *ast.ForStmt: case *ast.ForStmt:
p.print(token.FOR) p.print(token.FOR)
p.controlClause(true, s.Init, s.Cond, s.Post) p.controlClause(true, s.Init, s.Cond, s.Post)
p.block(s.Body, 1) p.block(s.Body, 1)
*multiLine = true
case *ast.RangeStmt: case *ast.RangeStmt:
p.print(token.FOR, blank) p.print(token.FOR, blank)
p.expr(s.Key, multiLine) p.expr(s.Key)
if s.Value != nil { if s.Value != nil {
// use position of value following the comma as // use position of value following the comma as
// comma position for correct comment placement // comma position for correct comment placement
p.print(s.Value.Pos(), token.COMMA, blank) 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.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.print(blank)
p.block(s.Body, 1) p.block(s.Body, 1)
*multiLine = true
default: default:
panic("unreachable") panic("unreachable")
@ -1228,20 +1200,20 @@ func keepTypeColumn(specs []ast.Spec) []bool {
return m 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.setComment(s.Doc)
p.identList(s.Names, doIndent, multiLine) // always present p.identList(s.Names, doIndent) // always present
extraTabs := 3 extraTabs := 3
if s.Type != nil || keepType { if s.Type != nil || keepType {
p.print(vtab) p.print(vtab)
extraTabs-- extraTabs--
} }
if s.Type != nil { if s.Type != nil {
p.expr(s.Type, multiLine) p.expr(s.Type)
} }
if s.Values != nil { if s.Values != nil {
p.print(vtab, token.ASSIGN) p.print(vtab, token.ASSIGN, blank)
p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos) p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos)
extraTabs-- extraTabs--
} }
if s.Comment != nil { 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, // 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 // multi-line identifier lists in the spec are indented when the first
// linebreak is encountered. // 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) { switch s := spec.(type) {
case *ast.ImportSpec: case *ast.ImportSpec:
p.setComment(s.Doc) p.setComment(s.Doc)
if s.Name != nil { if s.Name != nil {
p.expr(s.Name, multiLine) p.expr(s.Name)
p.print(blank) p.print(blank)
} }
p.expr(s.Path, multiLine) p.expr(s.Path)
p.setComment(s.Comment) p.setComment(s.Comment)
p.print(s.EndPos) 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.internalError("expected n = 1; got", n)
} }
p.setComment(s.Doc) p.setComment(s.Doc)
p.identList(s.Names, doIndent, multiLine) // always present p.identList(s.Names, doIndent) // always present
if s.Type != nil { if s.Type != nil {
p.print(blank) p.print(blank)
p.expr(s.Type, multiLine) p.expr(s.Type)
} }
if s.Values != nil { if s.Values != nil {
p.print(blank, token.ASSIGN) p.print(blank, token.ASSIGN, blank)
p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos) p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos)
} }
p.setComment(s.Comment) p.setComment(s.Comment)
case *ast.TypeSpec: case *ast.TypeSpec:
p.setComment(s.Doc) p.setComment(s.Doc)
p.expr(s.Name, multiLine) p.expr(s.Name)
if n == 1 { if n == 1 {
p.print(blank) p.print(blank)
} else { } else {
p.print(vtab) p.print(vtab)
} }
p.expr(s.Type, multiLine) p.expr(s.Type)
p.setComment(s.Comment) p.setComment(s.Comment)
default: 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) {
func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) {
p.setComment(d.Doc) p.setComment(d.Doc)
p.print(d.Pos(), d.Tok, blank) 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: // two or more grouped const/var declarations:
// determine if the type column must be kept // determine if the type column must be kept
keepType := keepTypeColumn(d.Specs) keepType := keepTypeColumn(d.Specs)
var ml bool newSection := false
for i, s := range d.Specs { for i, s := range d.Specs {
if i > 0 { 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)
p.valueSpec(s.(*ast.ValueSpec), keepType[i], false, &ml) newSection = p.isMultiLine(s)
} }
} else { } else {
var ml bool newSection := false
for i, s := range d.Specs { for i, s := range d.Specs {
if i > 0 { 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)
p.spec(s, n, false, &ml) newSection = p.isMultiLine(s)
} }
} }
p.print(unindent, formfeed) p.print(unindent, formfeed)
*multiLine = true
} }
p.print(d.Rparen, token.RPAREN) p.print(d.Rparen, token.RPAREN)
} else { } else {
// single declaration // 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 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) {
func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLine *bool) {
if b == nil { if b == nil {
return return
} }
@ -1422,7 +1390,7 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi
if i > 0 { if i > 0 {
p.print(token.SEMICOLON, blank) 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) p.print(blank)
} }
@ -1432,7 +1400,6 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi
p.print(blank) p.print(blank)
p.block(b, 1) p.block(b, 1)
*multiLine = true
} }
// distance returns the column difference between from and to if both // 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 return infinity
} }
// Sets multiLine to true if the declaration spans multiple lines. func (p *printer) funcDecl(d *ast.FuncDecl) {
func (p *printer) funcDecl(d *ast.FuncDecl, multiLine *bool) {
p.setComment(d.Doc) p.setComment(d.Doc)
p.print(d.Pos(), token.FUNC, blank) p.print(d.Pos(), token.FUNC, blank)
if d.Recv != nil { if d.Recv != nil {
p.parameters(d.Recv, multiLine) // method: print receiver p.parameters(d.Recv) // method: print receiver
p.print(blank) p.print(blank)
} }
p.expr(d.Name, multiLine) p.expr(d.Name)
p.signature(d.Type.Params, d.Type.Results, multiLine) p.signature(d.Type.Params, d.Type.Results)
p.funcBody(d.Body, p.distance(d.Pos(), p.pos), false, multiLine) 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) {
func (p *printer) decl(decl ast.Decl, multiLine *bool) {
switch d := decl.(type) { switch d := decl.(type) {
case *ast.BadDecl: case *ast.BadDecl:
p.print(d.Pos(), "BadDecl") p.print(d.Pos(), "BadDecl")
case *ast.GenDecl: case *ast.GenDecl:
p.genDecl(d, multiLine) p.genDecl(d)
case *ast.FuncDecl: case *ast.FuncDecl:
p.funcDecl(d, multiLine) p.funcDecl(d)
default: default:
panic("unreachable") panic("unreachable")
} }
@ -1490,7 +1455,7 @@ func declToken(decl ast.Decl) (tok token.Token) {
func (p *printer) file(src *ast.File) { func (p *printer) file(src *ast.File) {
p.setComment(src.Doc) p.setComment(src.Doc)
p.print(src.Pos(), token.PACKAGE, blank) p.print(src.Pos(), token.PACKAGE, blank)
p.expr(src.Name, ignoreMultiLine) p.expr(src.Name)
if len(src.Decls) > 0 { if len(src.Decls) > 0 {
tok := token.ILLEGAL tok := token.ILLEGAL
@ -1509,7 +1474,7 @@ func (p *printer) file(src *ast.File) {
min = 2 min = 2
} }
p.linebreak(p.lineFor(d.Pos()), min, ignore, false) p.linebreak(p.lineFor(d.Pos()), min, ignore, false)
p.decl(d, ignoreMultiLine) p.decl(d)
} }
} }

View file

@ -34,9 +34,6 @@ const (
unindent = whiteSpace('<') unindent = whiteSpace('<')
) )
// Use ignoreMultiLine if the multiLine information is not important.
var ignoreMultiLine = new(bool)
// A pmode value represents the current printer mode. // A pmode value represents the current printer mode.
type pmode int 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. // it as is likely to help position the comment nicely.
// pos is the comment position, next the position of the item // pos is the comment position, next the position of the item
// after all pending comments, prev is the previous comment in // after all pending comments, prev is the previous comment in
// a group of comments (or nil), and isKeyword indicates if the // a group of comments (or nil), and tok is the next token.
// next item is a keyword.
// //
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 { if len(p.output) == 0 {
// the comment is the first item to be printed - don't write any whitespace // the comment is the first item to be printed - don't write any whitespace
return return
@ -338,38 +334,41 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as
// comment on a different line: // comment on a different line:
// separate with at least one line break // separate with at least one line break
droppedLinebreak := false droppedLinebreak := false
if prev == nil { j := 0
// first comment of a comment group for i, ch := range p.wsbuf {
j := 0 switch ch {
for i, ch := range p.wsbuf { case blank, vtab:
switch ch { // ignore any horizontal whitespace before line breaks
case blank, vtab: p.wsbuf[i] = ignore
// ignore any horizontal whitespace before line breaks continue
p.wsbuf[i] = ignore 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 continue
case indent:
// apply pending indentation
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 // if the next token is not a closing }, apply the unindent
break // 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 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 // determine number of linebreaks before the comment
n := 0 n := 0
@ -678,7 +677,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro
var last *ast.Comment var last *ast.Comment
for p.commentBefore(next) { for p.commentBefore(next) {
for _, c := range p.comment.List { 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) p.writeComment(c)
last = c last = c
} }
@ -1011,18 +1010,18 @@ func (p *printer) printNode(node interface{}) error {
// format node // format node
switch n := node.(type) { switch n := node.(type) {
case ast.Expr: case ast.Expr:
p.expr(n, ignoreMultiLine) p.expr(n)
case ast.Stmt: case ast.Stmt:
// A labeled statement will un-indent to position the // A labeled statement will un-indent to position the
// label. Set indent to 1 so we don't get indent "underflow". // label. Set indent to 1 so we don't get indent "underflow".
if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt { if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt {
p.indent = 1 p.indent = 1
} }
p.stmt(n, false, ignoreMultiLine) p.stmt(n, false)
case ast.Decl: case ast.Decl:
p.decl(n, ignoreMultiLine) p.decl(n)
case ast.Spec: case ast.Spec:
p.spec(n, 1, false, ignoreMultiLine) p.spec(n, 1, false)
case *ast.File: case *ast.File:
p.file(n) p.file(n)
default: default:

View file

@ -154,15 +154,12 @@ var data = []entry{
} }
func TestFiles(t *testing.T) { func TestFiles(t *testing.T) {
for i, e := range data { for _, e := range data {
source := filepath.Join(dataDir, e.source) source := filepath.Join(dataDir, e.source)
golden := filepath.Join(dataDir, e.golden) golden := filepath.Join(dataDir, e.golden)
check(t, source, golden, e.mode) check(t, source, golden, e.mode)
// TODO(gri) check that golden is idempotent // TODO(gri) check that golden is idempotent
//check(t, golden, golden, e.mode) //check(t, golden, golden, e.mode)
if testing.Short() && i >= 3 {
break
}
} }
} }

View file

@ -168,6 +168,91 @@ func typeswitch(x interface{}) {
// this comment should not be indented // 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 _() { func _() {
/* freestanding comment /* freestanding comment
aligned line aligned line

View file

@ -171,6 +171,91 @@ func typeswitch(x interface{}) {
// this comment should not be indented // 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 _() { func _() {
/* freestanding comment /* freestanding comment
aligned line aligned line

View file

@ -83,13 +83,13 @@ import (
// more import examples // more import examples
import ( import (
"xxx" "xxx"
"much longer name" // comment "much_longer_name" // comment
"short name" // comment "short_name" // comment
) )
import ( import (
_ "xxx" _ "xxx"
"much longer name" // comment "much_longer_name" // comment
) )
import ( import (
@ -500,7 +500,7 @@ type _ struct {
type _ struct { type _ struct {
a, b, 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 u, v, w, x float // this line should be indented
p, q, p, q,
r, s float // this line should be indented r, s float // this line should be indented
@ -562,7 +562,7 @@ var a2, b2,
var ( var (
a3, b3, 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 a4, b4, c4 int // this line should be indented
) )

View file

@ -84,13 +84,13 @@ import (
// more import examples // more import examples
import ( import (
"xxx" "xxx"
"much longer name" // comment "much_longer_name" // comment
"short name" // comment "short_name" // comment
) )
import ( import (
_ "xxx" _ "xxx"
"much longer name" // comment "much_longer_name" // comment
) )
import ( import (

View file

@ -625,3 +625,25 @@ func f() {
log.Fatal(err) 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...,
)
}

View file

@ -654,3 +654,25 @@ func f() {
log.Fatal(err) 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...,
)
}

View file

@ -625,3 +625,25 @@ func f() {
log.Fatal(err) 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...,
)
}

View file

@ -52,7 +52,7 @@ type parser struct {
// Non-syntactic parser control // Non-syntactic parser control
exprLev int // < 0: in control clause, >= 0: in expression exprLev int // < 0: in control clause, >= 0: in expression
// Ordinary identifer scopes // Ordinary identifier scopes
pkgScope *ast.Scope // pkgScope.Outer == nil pkgScope *ast.Scope // pkgScope.Outer == nil
topScope *ast.Scope // top-most scope; may be pkgScope topScope *ast.Scope // top-most scope; may be pkgScope
unresolved []*ast.Ident // unresolved identifiers unresolved []*ast.Ident // unresolved identifiers

View file

@ -8,6 +8,82 @@ var expr bool
func use(x interface{}) {} 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. // Formatting of if-statement headers.
func _() { func _() {
if true { if true {

View file

@ -8,6 +8,82 @@ var expr bool
func use(x interface{}) {} 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. // Formatting of if-statement headers.
func _() { func _() {
if true {} if true {}

View file

@ -19,7 +19,7 @@ to parse and execute HTML templates safely.
tmpl, err := template.New("name").Parse(...) tmpl, err := template.New("name").Parse(...)
// Error checking elided // 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 If successful, tmpl will now be injection-safe. Otherwise, err is an error
defined in the docs for ErrorCode. defined in the docs for ErrorCode.

View file

@ -593,7 +593,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context {
} }
} }
for j := i; j < end; j++ { 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.Write(s[written:j])
b.WriteString("&lt;") b.WriteString("&lt;")
written = j + 1 written = j + 1

View file

@ -223,14 +223,14 @@ func TestEscape(t *testing.T) {
`<button onclick='alert(&quot;\x3cHello\x3e&quot;)'>`, `<button onclick='alert(&quot;\x3cHello\x3e&quot;)'>`,
}, },
{ {
"badMarshaller", "badMarshaler",
`<button onclick='alert(1/{{.B}}in numbers)'>`, `<button onclick='alert(1/{{.B}}in numbers)'>`,
`<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character &#39;f&#39; looking for beginning of object key string */null in numbers)'>`, `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character &#39;f&#39; looking for beginning of object key string */null in numbers)'>`,
}, },
{ {
"jsMarshaller", "jsMarshaler",
`<button onclick='alert({{.M}})'>`, `<button onclick='alert({{.M}})'>`,
`<button onclick='alert({&#34;&lt;foo&gt;&#34;:&#34;O&#39;Reilly&#34;})'>`, `<button onclick='alert({&#34;\u003cfoo\u003e&#34;:&#34;O&#39;Reilly&#34;})'>`,
}, },
{ {
"jsStrNotUnderEscaped", "jsStrNotUnderEscaped",
@ -431,6 +431,11 @@ func TestEscape(t *testing.T) {
"<!DOCTYPE html>Hello, World!", "<!DOCTYPE html>Hello, World!",
"<!DOCTYPE html>Hello, World!", "<!DOCTYPE html>Hello, World!",
}, },
{
"HTML doctype not case-insensitive",
"<!doCtYPE htMl>Hello, World!",
"<!doCtYPE htMl>Hello, World!",
},
{ {
"No doctype injection", "No doctype injection",
`<!{{"DOCTYPE"}}`, `<!{{"DOCTYPE"}}`,

View file

@ -134,7 +134,7 @@ var htmlNospaceNormReplacementTable = []string{
'`': "&#96;", '`': "&#96;",
} }
// 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. // and when badRunes is true, certain bad runes are allowed through unescaped.
func htmlReplacer(s string, replacementTable []string, badRunes bool) string { func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
written, b := 0, new(bytes.Buffer) written, b := 0, new(bytes.Buffer)

View file

@ -6,6 +6,10 @@
// Its primary job is to wrap existing implementations of such primitives, // Its primary job is to wrap existing implementations of such primitives,
// such as those in package os, into shared public interfaces that // such as those in package os, into shared public interfaces that
// abstract the functionality, plus some other related primitives. // 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 package io
import ( import (
@ -156,6 +160,9 @@ type WriterTo interface {
// If ReadAt is reading from an input source with a seek offset, // If ReadAt is reading from an input source with a seek offset,
// ReadAt should not affect nor be affected by the underlying // ReadAt should not affect nor be affected by the underlying
// seek offset. // seek offset.
//
// Clients of ReadAt can execute parallel ReadAt calls on the
// same input source.
type ReaderAt interface { type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error) ReadAt(p []byte, off int64) (n int, err error)
} }

View file

@ -175,6 +175,10 @@ func (w *PipeWriter) CloseWithError(err error) error {
// with code expecting an io.Writer. // with code expecting an io.Writer.
// Reads on one end are matched with writes on the other, // Reads on one end are matched with writes on the other,
// copying data directly between the two; there is no internal buffering. // 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) { func Pipe() (*PipeReader, *PipeWriter) {
p := new(pipe) p := new(pipe)
p.rwait.L = &p.l p.rwait.L = &p.l

View file

@ -27,11 +27,11 @@ const (
// Max is the largest finite value representable by the type. // Max is the largest finite value representable by the type.
// SmallestNonzero is the smallest positive, non-zero value representable by the type. // SmallestNonzero is the smallest positive, non-zero value representable by the type.
const ( const (
MaxFloat32 = 3.40282346638528859811704183484516925440e+38 /* 2**127 * (2**24 - 1) / 2**23 */ MaxFloat32 = 3.40282346638528859811704183484516925440e+38 // 2**127 * (2**24 - 1) / 2**23
SmallestNonzeroFloat32 = 1.401298464324817070923729583289916131280e-45 /* 1 / 2**(127 - 1 + 23) */ SmallestNonzeroFloat32 = 1.401298464324817070923729583289916131280e-45 // 1 / 2**(127 - 1 + 23)
MaxFloat64 = 1.797693134862315708145274237317043567981e+308 /* 2**1023 * (2**53 - 1) / 2**52 */ MaxFloat64 = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52
SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 /* 1 / 2**(1023 - 1 + 52) */ SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52)
) )
// Integer limit values. // Integer limit values.

View file

@ -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), // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" // "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. // For TCP and UDP networks, addresses have the form host:port.
// If host is a literal IPv6 address, it must be enclosed // If host is a literal IPv6 address, it must be enclosed

View file

@ -5,6 +5,8 @@
package net package net
import ( import (
"flag"
"regexp"
"runtime" "runtime"
"testing" "testing"
"time" "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)
}
}
}

View file

@ -42,9 +42,8 @@ func doDial(t *testing.T, network, addr string) {
} }
func TestLookupCNAME(t *testing.T) { func TestLookupCNAME(t *testing.T) {
if testing.Short() { if testing.Short() || !*testExternal {
// Don't use external network. t.Logf("skipping test to avoid external network")
t.Logf("skipping external network test during -short")
return return
} }
cname, err := LookupCNAME("www.google.com") cname, err := LookupCNAME("www.google.com")
@ -67,9 +66,8 @@ var googleaddrsipv4 = []string{
} }
func TestDialGoogleIPv4(t *testing.T) { func TestDialGoogleIPv4(t *testing.T) {
if testing.Short() { if testing.Short() || !*testExternal {
// Don't use external network. t.Logf("skipping test to avoid external network")
t.Logf("skipping external network test during -short")
return return
} }
@ -124,9 +122,8 @@ var googleaddrsipv6 = []string{
} }
func TestDialGoogleIPv6(t *testing.T) { func TestDialGoogleIPv6(t *testing.T) {
if testing.Short() { if testing.Short() || !*testExternal {
// Don't use external network. t.Logf("skipping test to avoid external network")
t.Logf("skipping external network test during -short")
return return
} }
// Only run tcp6 if the kernel will take it. // Only run tcp6 if the kernel will take it.

View file

@ -144,6 +144,7 @@ func Serve(handler http.Handler) error {
bufw: bufio.NewWriter(os.Stdout), bufw: bufio.NewWriter(os.Stdout),
} }
handler.ServeHTTP(rw, req) handler.ServeHTTP(rw, req)
rw.Write(nil) // make sure a response is sent
if err = rw.bufw.Flush(); err != nil { if err = rw.bufw.Flush(); err != nil {
return err return err
} }

View file

@ -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. // Make a map to hold the test map that the CGI returns.
m := make(map[string]string) m := make(map[string]string)
m["_body"] = rw.Body.String()
linesRead := 0 linesRead := 0
readlines: readlines:
for { for {

View file

@ -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. // Note: not actually a test.
func TestBeChildCGIProcess(t *testing.T) { func TestBeChildCGIProcess(t *testing.T) {
if os.Getenv("REQUEST_METHOD") == "" { if os.Getenv("REQUEST_METHOD") == "" {
@ -59,8 +75,11 @@ func TestBeChildCGIProcess(t *testing.T) {
} }
Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("X-Test-Header", "X-Test-Value") rw.Header().Set("X-Test-Header", "X-Test-Value")
fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
req.ParseForm() 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 k, vv := range req.Form {
for _, v := range vv { for _, v := range vv {
fmt.Fprintf(rw, "param-%s=%s\n", k, v) fmt.Fprintf(rw, "param-%s=%s\n", k, v)

View file

@ -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) { func TestFileServerImplicitLeadingSlash(t *testing.T) {
tempDir, err := ioutil.TempDir("", "") tempDir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatalf("TempDir: %v", err) 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 { if err := ioutil.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err) t.Fatalf("WriteFile: %v", err)
} }
@ -172,6 +179,7 @@ func TestFileServerImplicitLeadingSlash(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("ReadAll %s: %v", suffix, err) t.Fatalf("ReadAll %s: %v", suffix, err)
} }
res.Body.Close()
return string(b) return string(b)
} }
if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") { if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") {

View file

@ -13,6 +13,7 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"sync"
) )
// A Server is an HTTP server listening on a system-chosen port on the // 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 // Config may be changed after calling NewUnstartedServer and
// before Start or StartTLS. // before Start or StartTLS.
Config *http.Server 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 // 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.Listener = &historyListener{s.Listener, make([]net.Conn, 0)}
s.URL = "http://" + s.Listener.Addr().String() s.URL = "http://" + s.Listener.Addr().String()
s.wrapHandler()
go s.Config.Serve(s.Listener) go s.Config.Serve(s.Listener)
if *serve != "" { if *serve != "" {
fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL) 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.Listener = &historyListener{tlsListener, make([]net.Conn, 0)}
s.URL = "https://" + s.Listener.Addr().String() s.URL = "https://" + s.Listener.Addr().String()
s.wrapHandler()
go s.Config.Serve(s.Listener) 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. // NewTLSServer starts and returns a new Server using TLS.
// The caller should call Close when finished, to shut it down. // The caller should call Close when finished, to shut it down.
func NewTLSServer(handler http.Handler) *Server { func NewTLSServer(handler http.Handler) *Server {
@ -129,9 +147,11 @@ func NewTLSServer(handler http.Handler) *Server {
return ts 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() { func (s *Server) Close() {
s.Listener.Close() s.Listener.Close()
s.wg.Wait()
} }
// CloseClientConnections closes any currently open HTTP connections // 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 // 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 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
// of ASN.1 time). // of ASN.1 time).

View file

@ -12,6 +12,7 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time" "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 // Use the actual Transport code to record what we would send
// on the wire, but not using TCP. Use a Transport with a // on the wire, but not using TCP. Use a Transport with a
// customer dialer that returns a fake net.Conn that waits // 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 req.Body = save
if err != nil { if err != nil {

View file

@ -71,6 +71,18 @@ var dumpTests = []dumpTest{
"User-Agent: Go http package\r\n" + "User-Agent: Go http package\r\n" +
"Accept-Encoding: gzip\r\n\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) { func TestDumpRequest(t *testing.T) {

View file

@ -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 // Make sure body is fully consumed, even if user does not call body.Close
if lastbody != nil { if lastbody != nil {
// body.Close is assumed to be idempotent and multiple calls to // 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. // returned.
err = lastbody.Close() err = lastbody.Close()
if err != nil { if err != nil {

View file

@ -14,14 +14,6 @@ func isSeparator(c byte) bool {
return false 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 isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }
func isChar(c byte) bool { return 0 <= c && c <= 127 } func isChar(c byte) bool { return 0 <= c && c <= 127 }

View file

@ -129,9 +129,10 @@ func TestSniffWriteSize(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
for _, size := range []int{0, 1, 200, 600, 999, 1000, 1023, 1024, 512 << 10, 1 << 20} { 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 { if err != nil {
t.Fatalf("size %d: %v", size, err) t.Fatalf("size %d: %v", size, err)
} }
res.Body.Close()
} }
} }

View file

@ -43,6 +43,7 @@ const (
StatusUnsupportedMediaType = 415 StatusUnsupportedMediaType = 415
StatusRequestedRangeNotSatisfiable = 416 StatusRequestedRangeNotSatisfiable = 416
StatusExpectationFailed = 417 StatusExpectationFailed = 417
StatusTeapot = 418
StatusInternalServerError = 500 StatusInternalServerError = 500
StatusNotImplemented = 501 StatusNotImplemented = 501
@ -90,6 +91,7 @@ var statusText = map[int]string{
StatusUnsupportedMediaType: "Unsupported Media Type", StatusUnsupportedMediaType: "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed", StatusExpectationFailed: "Expectation Failed",
StatusTeapot: "I'm a teapot",
StatusInternalServerError: "Internal Server Error", StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented", StatusNotImplemented: "Not Implemented",

View file

@ -383,7 +383,7 @@ func fixTransferEncoding(requestMethod string, header Header) ([]string, error)
// chunked encoding must always come first. // chunked encoding must always come first.
for _, encoding := range encodings { for _, encoding := range encodings {
encoding = strings.ToLower(strings.TrimSpace(encoding)) encoding = strings.ToLower(strings.TrimSpace(encoding))
// "identity" encoding is not recored // "identity" encoding is not recorded
if encoding == "identity" { if encoding == "identity" {
break break
} }

View file

@ -76,7 +76,9 @@ type Transport struct {
// ProxyFromEnvironment returns the URL of the proxy to use for a // ProxyFromEnvironment returns the URL of the proxy to use for a
// given request, as indicated by the environment variables // given request, as indicated by the environment variables
// $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). // $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) { func ProxyFromEnvironment(req *Request) (*url.URL, error) {
proxy := getenvEitherCase("HTTP_PROXY") proxy := getenvEitherCase("HTTP_PROXY")
if proxy == "" { if proxy == "" {
@ -86,7 +88,7 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
return nil, nil return nil, nil
} }
proxyURL, err := url.Parse(proxy) proxyURL, err := url.Parse(proxy)
if err != nil { if err != nil || proxyURL.Scheme == "" {
if u, err := url.Parse("http://" + proxy); err == nil { if u, err := url.Parse("http://" + proxy); err == nil {
proxyURL = u proxyURL = u
err = nil err = nil

View file

@ -16,6 +16,7 @@ import (
. "net/http" . "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"os"
"runtime" "runtime"
"strconv" "strconv"
"strings" "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. // rgz is a gzip quine that uncompresses to itself.
var rgz = []byte{ var rgz = []byte{
0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,

View file

@ -108,7 +108,6 @@ func DateServer(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintf(rw, "fork/exec: %s\n", err) fmt.Fprintf(rw, "fork/exec: %s\n", err)
return return
} }
defer p.Release()
io.Copy(rw, r) io.Copy(rw, r)
wait, err := p.Wait(0) wait, err := p.Wait(0)
if err != nil { if err != nil {

View file

@ -6,11 +6,7 @@
package net package net
import ( import "errors"
"bytes"
"errors"
"fmt"
)
var ( var (
errInvalidInterface = errors.New("net: invalid interface") errInvalidInterface = errors.New("net: invalid interface")
@ -20,77 +16,6 @@ var (
errNoSuchMulticastInterface = errors.New("net: no such multicast interface") 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 // Interface represents a mapping between network interface name
// and index. It also represents network interface facility // and index. It also represents network interface facility
// information. // information.

View file

@ -6,8 +6,6 @@ package net
import ( import (
"bytes" "bytes"
"reflect"
"strings"
"testing" "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)
}
}
}

View file

@ -76,7 +76,7 @@ func lookupProtocol(name string) (proto int, err error) {
} }
func lookupHost(host string) (addrs []string, 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) // host names in local network (e.g. from /lib/ndb/local)
lines, err := queryCS("tcp", host, "1") lines, err := queryCS("tcp", host, "1")
if err != nil { if err != nil {

View file

@ -12,7 +12,7 @@ import (
"testing" "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) { func TestGoogleSRV(t *testing.T) {
if testing.Short() || !*testExternal { if testing.Short() || !*testExternal {
@ -78,3 +78,40 @@ func TestGoogleDNSAddr(t *testing.T) {
t.Errorf("no results") 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)
}
}
}

84
libgo/go/net/mac.go Normal file
View file

@ -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)
}

54
libgo/go/net/mac_test.go Normal file
View file

@ -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)
}
}
}

View file

@ -5,130 +5,12 @@
package net package net
import ( import (
"flag"
"io" "io"
"regexp"
"runtime" "runtime"
"testing" "testing"
"time" "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) { func TestShutdown(t *testing.T) {
if runtime.GOOS == "plan9" { if runtime.GOOS == "plan9" {
return return

View file

@ -140,7 +140,7 @@ func (client *Client) input() {
} }
client.mutex.Unlock() client.mutex.Unlock()
client.sending.Unlock() client.sending.Unlock()
if err != io.EOF || !closing { if err != io.EOF && !closing {
log.Println("rpc: client protocol error:", err) log.Println("rpc: client protocol error:", err)
} }
} }

View file

@ -13,13 +13,19 @@
Only methods that satisfy these criteria will be made available for remote access; Only methods that satisfy these criteria will be made available for remote access;
other methods will be ignored: other methods will be ignored:
- the method name is exported, that is, begins with an upper case letter. - the method is exported.
- the method receiver is exported or local (defined in the package - the method has two arguments, both exported (or builtin) types.
registering the service).
- the method has two arguments, both exported or local types.
- the method's second argument is a pointer. - the method's second argument is a pointer.
- the method has return type error. - 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 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. 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 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 call, a pointer containing the arguments, and a pointer to receive the result
parameters. parameters.
Call waits for the remote call to complete; Go launches the call asynchronously The Call method waits for the remote call to complete while the Go method
and returns a channel that will signal completion. 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: 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) method := s.typ.Method(m)
mtype := method.Type mtype := method.Type
mname := method.Name mname := method.Name
// Method must be exported.
if method.PkgPath != "" { if method.PkgPath != "" {
continue continue
} }
@ -267,7 +276,7 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro
// First arg need not be a pointer. // First arg need not be a pointer.
argType := mtype.In(1) argType := mtype.In(1)
if !isExportedOrBuiltinType(argType) { if !isExportedOrBuiltinType(argType) {
log.Println(mname, "argument type not exported or local:", argType) log.Println(mname, "argument type not exported:", argType)
continue continue
} }
// Second arg must be a pointer. // 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) log.Println("method", mname, "reply type not a pointer:", replyType)
continue continue
} }
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) { if !isExportedOrBuiltinType(replyType) {
log.Println("method", mname, "reply type not exported or local:", replyType) log.Println("method", mname, "reply type not exported:", replyType)
continue continue
} }
// Method needs one out: error. // Method needs one out.
if mtype.NumOut() != 1 { if mtype.NumOut() != 1 {
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut()) log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
continue continue
} }
// The return type of the method must be error.
if returnType := mtype.Out(0); returnType != typeOfError { if returnType := mtype.Out(0); returnType != typeOfError {
log.Println("method", mname, "returns", returnType.String(), "not error") log.Println("method", mname, "returns", returnType.String(), "not error")
continue continue
@ -301,10 +312,10 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro
return nil return nil
} }
// A value sent as a placeholder for the response when the server receives an invalid request. // A value sent as a placeholder for the server's response value when the server
type InvalidRequest struct{} // receives an invalid request. It is never decoded by the client since the Response
// contains an error when it is used.
var invalidRequest = InvalidRequest{} var invalidRequest = struct{}{}
func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply interface{}, codec ServerCodec, errmsg string) { func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply interface{}, codec ServerCodec, errmsg string) {
resp := server.getResponse() resp := server.getResponse()

View file

@ -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
}

View file

@ -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) errc <- fmt.Errorf("fd.%s on %s %s did not return 0, timeout: %v, %v", what, network, addr, n, err1)
return 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) errc <- fmt.Errorf("fd.%s on %s %s took %s, expected 0.1s", what, network, addr, dt)
return return
} }

View file

@ -38,18 +38,18 @@ func testWriteToConn(t *testing.T, raddr string) {
_, err = c.(*UDPConn).WriteToUDP([]byte("Connection-oriented mode socket"), ra) _, err = c.(*UDPConn).WriteToUDP([]byte("Connection-oriented mode socket"), ra)
if err == nil { if err == nil {
t.Fatal("WriteToUDP should be failed") t.Fatal("WriteToUDP should fail")
} }
if err != nil && err.(*OpError).Err != ErrWriteToConnected { 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) _, err = c.(*UDPConn).WriteTo([]byte("Connection-oriented mode socket"), ra)
if err == nil { if err == nil {
t.Fatal("WriteTo should be failed") t.Fatal("WriteTo should fail")
} }
if err != nil && err.(*OpError).Err != ErrWriteToConnected { 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")) _, 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")) _, err = c.(*UDPConn).Write([]byte("Connection-less mode socket"))
if err == nil { if err == nil {
t.Fatal("Write should be failed") t.Fatal("Write should fail")
} }
} }

Some files were not shown because too many files have changed in this diff Show more