libgo: Update to revision 15193:6fdc1974457c of master library.

From-SVN: r194692
This commit is contained in:
Ian Lance Taylor 2012-12-22 01:15:33 +00:00
parent 7e9268b4cf
commit 409a5e7eb4
104 changed files with 2434 additions and 686 deletions

View File

@ -1,4 +1,4 @@
c031aa767edf 6fdc1974457c
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

@ -502,6 +502,7 @@ runtime_files = \
runtime/go-unwind.c \ runtime/go-unwind.c \
runtime/chan.c \ runtime/chan.c \
runtime/cpuprof.c \ runtime/cpuprof.c \
runtime/env_posix.c \
runtime/lfstack.c \ runtime/lfstack.c \
$(runtime_lock_files) \ $(runtime_lock_files) \
runtime/mcache.c \ runtime/mcache.c \
@ -1657,6 +1658,13 @@ else
syscall_lsf_file = syscall_lsf_file =
endif endif
# GNU/Linux specific utimesnano support.
if LIBGO_IS_LINUX
syscall_utimesnano_file = go/syscall/libcall_linux_utimesnano.go
else
syscall_utimesnano_file = go/syscall/libcall_posix_utimesnano.go
endif
go_base_syscall_files = \ go_base_syscall_files = \
go/syscall/env_unix.go \ go/syscall/env_unix.go \
go/syscall/syscall_errno.go \ go/syscall/syscall_errno.go \
@ -1679,6 +1687,7 @@ go_base_syscall_files = \
$(syscall_uname_file) \ $(syscall_uname_file) \
$(syscall_netlink_file) \ $(syscall_netlink_file) \
$(syscall_lsf_file) \ $(syscall_lsf_file) \
$(syscall_utimesnano_file) \
$(GO_LIBCALL_OS_FILE) \ $(GO_LIBCALL_OS_FILE) \
$(GO_LIBCALL_OS_ARCH_FILE) \ $(GO_LIBCALL_OS_ARCH_FILE) \
$(GO_SYSCALL_OS_FILE) \ $(GO_SYSCALL_OS_FILE) \

View File

@ -213,12 +213,13 @@ am__objects_5 = go-append.lo go-assert.lo go-assert-interface.lo \
go-type-float.lo go-type-identity.lo go-type-interface.lo \ go-type-float.lo go-type-identity.lo go-type-interface.lo \
go-type-string.lo go-typedesc-equal.lo go-typestring.lo \ go-type-string.lo go-typedesc-equal.lo go-typestring.lo \
go-unsafe-new.lo go-unsafe-newarray.lo go-unsafe-pointer.lo \ go-unsafe-new.lo go-unsafe-newarray.lo go-unsafe-pointer.lo \
go-unwind.lo chan.lo cpuprof.lo lfstack.lo $(am__objects_1) \ go-unwind.lo chan.lo cpuprof.lo env_posix.lo lfstack.lo \
mcache.lo mcentral.lo $(am__objects_2) mfinal.lo mfixalloc.lo \ $(am__objects_1) mcache.lo mcentral.lo $(am__objects_2) \
mgc0.lo mheap.lo msize.lo panic.lo parfor.lo print.lo proc.lo \ mfinal.lo mfixalloc.lo mgc0.lo mheap.lo msize.lo panic.lo \
runtime.lo signal_unix.lo thread.lo yield.lo $(am__objects_3) \ parfor.lo print.lo proc.lo runtime.lo signal_unix.lo thread.lo \
iface.lo malloc.lo map.lo mprof.lo reflect.lo runtime1.lo \ yield.lo $(am__objects_3) iface.lo malloc.lo map.lo mprof.lo \
sema.lo sigqueue.lo string.lo time.lo $(am__objects_4) reflect.lo runtime1.lo sema.lo sigqueue.lo string.lo time.lo \
$(am__objects_4)
am_libgo_la_OBJECTS = $(am__objects_5) am_libgo_la_OBJECTS = $(am__objects_5)
libgo_la_OBJECTS = $(am_libgo_la_OBJECTS) libgo_la_OBJECTS = $(am_libgo_la_OBJECTS)
libgo_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ libgo_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
@ -835,6 +836,7 @@ runtime_files = \
runtime/go-unwind.c \ runtime/go-unwind.c \
runtime/chan.c \ runtime/chan.c \
runtime/cpuprof.c \ runtime/cpuprof.c \
runtime/env_posix.c \
runtime/lfstack.c \ runtime/lfstack.c \
$(runtime_lock_files) \ $(runtime_lock_files) \
runtime/mcache.c \ runtime/mcache.c \
@ -1840,6 +1842,10 @@ go_unicode_utf8_files = \
# GNU/Linux specific socket filters. # GNU/Linux specific socket filters.
@LIBGO_IS_LINUX_TRUE@syscall_lsf_file = go/syscall/lsf_linux.go @LIBGO_IS_LINUX_TRUE@syscall_lsf_file = go/syscall/lsf_linux.go
@LIBGO_IS_LINUX_FALSE@syscall_utimesnano_file = go/syscall/libcall_posix_utimesnano.go
# GNU/Linux specific utimesnano support.
@LIBGO_IS_LINUX_TRUE@syscall_utimesnano_file = go/syscall/libcall_linux_utimesnano.go
go_base_syscall_files = \ go_base_syscall_files = \
go/syscall/env_unix.go \ go/syscall/env_unix.go \
go/syscall/syscall_errno.go \ go/syscall/syscall_errno.go \
@ -1862,6 +1868,7 @@ go_base_syscall_files = \
$(syscall_uname_file) \ $(syscall_uname_file) \
$(syscall_netlink_file) \ $(syscall_netlink_file) \
$(syscall_lsf_file) \ $(syscall_lsf_file) \
$(syscall_utimesnano_file) \
$(GO_LIBCALL_OS_FILE) \ $(GO_LIBCALL_OS_FILE) \
$(GO_LIBCALL_OS_ARCH_FILE) \ $(GO_LIBCALL_OS_ARCH_FILE) \
$(GO_SYSCALL_OS_FILE) \ $(GO_SYSCALL_OS_FILE) \
@ -2418,6 +2425,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chan.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chan.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpuprof.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpuprof.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/env_posix.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getncpu-bsd.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getncpu-bsd.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getncpu-irix.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getncpu-irix.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getncpu-linux.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getncpu-linux.Plo@am__quote@
@ -3027,6 +3035,13 @@ cpuprof.lo: runtime/cpuprof.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cpuprof.lo `test -f 'runtime/cpuprof.c' || echo '$(srcdir)/'`runtime/cpuprof.c @am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cpuprof.lo `test -f 'runtime/cpuprof.c' || echo '$(srcdir)/'`runtime/cpuprof.c
env_posix.lo: runtime/env_posix.c
@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT env_posix.lo -MD -MP -MF $(DEPDIR)/env_posix.Tpo -c -o env_posix.lo `test -f 'runtime/env_posix.c' || echo '$(srcdir)/'`runtime/env_posix.c
@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/env_posix.Tpo $(DEPDIR)/env_posix.Plo
@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='runtime/env_posix.c' object='env_posix.lo' libtool=yes @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o env_posix.lo `test -f 'runtime/env_posix.c' || echo '$(srcdir)/'`runtime/env_posix.c
lfstack.lo: runtime/lfstack.c lfstack.lo: runtime/lfstack.c
@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lfstack.lo -MD -MP -MF $(DEPDIR)/lfstack.Tpo -c -o lfstack.lo `test -f 'runtime/lfstack.c' || echo '$(srcdir)/'`runtime/lfstack.c @am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lfstack.lo -MD -MP -MF $(DEPDIR)/lfstack.Tpo -c -o lfstack.lo `test -f 'runtime/lfstack.c' || echo '$(srcdir)/'`runtime/lfstack.c
@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/lfstack.Tpo $(DEPDIR)/lfstack.Plo @am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/lfstack.Tpo $(DEPDIR)/lfstack.Plo

View File

@ -124,8 +124,8 @@ func append(slice []Type, elems ...Type) []Type
func copy(dst, src []Type) int func copy(dst, src []Type) int
// The delete built-in function deletes the element with the specified key // The delete built-in function deletes the element with the specified key
// (m[key]) from the map. If there is no such element, delete is a no-op. // (m[key]) from the map. If m is nil or there is no such element, delete
// If m is nil, delete panics. // is a no-op.
func delete(m map[Type]Type1, key Type) func delete(m map[Type]Type1, key Type)
// The len built-in function returns the length of v, according to its type: // The len built-in function returns the length of v, according to its type:

View File

@ -22,7 +22,7 @@ const (
logMaxOffsetSize = 15 // Standard DEFLATE logMaxOffsetSize = 15 // Standard DEFLATE
minMatchLength = 3 // The smallest match that the compressor looks for minMatchLength = 3 // The smallest match that the compressor looks for
maxMatchLength = 258 // The longest match for the compressor maxMatchLength = 258 // The longest match for the compressor
minOffsetSize = 1 // The shortest offset that makes any sence minOffsetSize = 1 // The shortest offset that makes any sense
// The maximum number of tokens we put into a single flat block, just too // The maximum number of tokens we put into a single flat block, just too
// stop things from getting too large. // stop things from getting too large.

View File

@ -241,7 +241,7 @@ func ExampleStreamReader() {
// Note that this example is simplistic in that it omits any // Note that this example is simplistic in that it omits any
// authentication of the encrypted data. It you were actually to use // authentication of the encrypted data. It you were actually to use
// StreamReader in this manner, an attacker could flip arbitary bits in // StreamReader in this manner, an attacker could flip arbitrary bits in
// the output. // the output.
} }
@ -278,6 +278,6 @@ func ExampleStreamWriter() {
// Note that this example is simplistic in that it omits any // Note that this example is simplistic in that it omits any
// authentication of the encrypted data. It you were actually to use // authentication of the encrypted data. It you were actually to use
// StreamReader in this manner, an attacker could flip arbitary bits in // StreamReader in this manner, an attacker could flip arbitrary bits in
// the decrypted result. // the decrypted result.
} }

View File

@ -439,7 +439,7 @@ func TestECDSA(t *testing.T) {
t.Errorf("%d: public key algorithm is %v, want ECDSA", i, pka) t.Errorf("%d: public key algorithm is %v, want ECDSA", i, pka)
} }
if err = cert.CheckSignatureFrom(cert); err != nil { if err = cert.CheckSignatureFrom(cert); err != nil {
t.Errorf("%d: certificate verfication failed: %s", i, err) t.Errorf("%d: certificate verification failed: %s", i, err)
} }
} }
} }
@ -519,7 +519,7 @@ func TestVerifyCertificateWithDSASignature(t *testing.T) {
} }
// test cert is self-signed // test cert is self-signed
if err = cert.CheckSignatureFrom(cert); err != nil { if err = cert.CheckSignatureFrom(cert); err != nil {
t.Fatalf("DSA Certificate verfication failed: %s", err) t.Fatalf("DSA Certificate verification failed: %s", err)
} }
} }

View File

@ -42,9 +42,10 @@ type fakeDriver struct {
type fakeDB struct { type fakeDB struct {
name string name string
mu sync.Mutex mu sync.Mutex
free []*fakeConn free []*fakeConn
tables map[string]*table tables map[string]*table
badConn bool
} }
type table struct { type table struct {
@ -83,6 +84,7 @@ type fakeConn struct {
stmtsMade int stmtsMade int
stmtsClosed int stmtsClosed int
numPrepare int numPrepare int
bad bool
} }
func (c *fakeConn) incrStat(v *int) { func (c *fakeConn) incrStat(v *int) {
@ -122,7 +124,9 @@ func init() {
// Supports dsn forms: // Supports dsn forms:
// <dbname> // <dbname>
// <dbname>;<opts> (no currently supported options) // <dbname>;<opts> (only currently supported option is `badConn`,
// which causes driver.ErrBadConn to be returned on
// every other conn.Begin())
func (d *fakeDriver) Open(dsn string) (driver.Conn, error) { func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
parts := strings.Split(dsn, ";") parts := strings.Split(dsn, ";")
if len(parts) < 1 { if len(parts) < 1 {
@ -135,7 +139,12 @@ func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
d.mu.Lock() d.mu.Lock()
d.openCount++ d.openCount++
d.mu.Unlock() d.mu.Unlock()
return &fakeConn{db: db}, nil conn := &fakeConn{db: db}
if len(parts) >= 2 && parts[1] == "badConn" {
conn.bad = true
}
return conn, nil
} }
func (d *fakeDriver) getDB(name string) *fakeDB { func (d *fakeDriver) getDB(name string) *fakeDB {
@ -199,7 +208,20 @@ func (db *fakeDB) columnType(table, column string) (typ string, ok bool) {
return "", false return "", false
} }
func (c *fakeConn) isBad() bool {
// if not simulating bad conn, do nothing
if !c.bad {
return false
}
// alternate between bad conn and not bad conn
c.db.badConn = !c.db.badConn
return c.db.badConn
}
func (c *fakeConn) Begin() (driver.Tx, error) { func (c *fakeConn) Begin() (driver.Tx, error) {
if c.isBad() {
return nil, driver.ErrBadConn
}
if c.currTx != nil { if c.currTx != nil {
return nil, errors.New("already in a transaction") return nil, errors.New("already in a transaction")
} }

View File

@ -266,7 +266,7 @@ func (db *DB) connIfFree(wanted driver.Conn) (conn driver.Conn, ok bool) {
var putConnHook func(*DB, driver.Conn) var putConnHook func(*DB, driver.Conn)
// putConn adds a connection to the db's free pool. // putConn adds a connection to the db's free pool.
// err is optionally the last error that occured on this connection. // err is optionally the last error that occurred on this connection.
func (db *DB) putConn(c driver.Conn, err error) { func (db *DB) putConn(c driver.Conn, err error) {
if err == driver.ErrBadConn { if err == driver.ErrBadConn {
// Don't reuse bad connections. // Don't reuse bad connections.
@ -426,7 +426,7 @@ func (db *DB) begin() (tx *Tx, err error) {
txi, err := ci.Begin() txi, err := ci.Begin()
if err != nil { if err != nil {
db.putConn(ci, err) db.putConn(ci, err)
return nil, fmt.Errorf("sql: failed to Begin transaction: %v", err) return nil, err
} }
return &Tx{ return &Tx{
db: db, db: db,

View File

@ -402,6 +402,39 @@ func TestTxQueryInvalid(t *testing.T) {
} }
} }
// Tests fix for issue 4433, that retries in Begin happen when
// conn.Begin() returns ErrBadConn
func TestTxErrBadConn(t *testing.T) {
db, err := Open("test", fakeDBName+";badConn")
if err != nil {
t.Fatalf("Open: %v", err)
}
if _, err := db.Exec("WIPE"); err != nil {
t.Fatalf("exec wipe: %v", err)
}
defer closeDB(t, db)
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
stmt, err := db.Prepare("INSERT|t1|name=?,age=?")
if err != nil {
t.Fatalf("Stmt, err = %v, %v", stmt, err)
}
defer stmt.Close()
tx, err := db.Begin()
if err != nil {
t.Fatalf("Begin = %v", err)
}
txs := tx.Stmt(stmt)
defer txs.Close()
_, err = txs.Exec("Bobby", 7)
if err != nil {
t.Fatalf("Exec = %v", err)
}
err = tx.Commit()
if err != nil {
t.Fatalf("Commit = %v", err)
}
}
// Tests fix for issue 2542, that we release a lock when querying on // Tests fix for issue 2542, that we release a lock when querying on
// a closed connection. // a closed connection.
func TestIssue2542Deadlock(t *testing.T) { func TestIssue2542Deadlock(t *testing.T) {

View File

@ -272,7 +272,8 @@ func NewFile(r io.ReaderAt) (*File, error) {
shnum = int(hdr.Shnum) shnum = int(hdr.Shnum)
shstrndx = int(hdr.Shstrndx) shstrndx = int(hdr.Shstrndx)
} }
if shstrndx < 0 || shstrndx >= shnum {
if shnum > 0 && shoff > 0 && (shstrndx < 0 || shstrndx >= shnum) {
return nil, &FormatError{0, "invalid ELF shstrndx", shstrndx} return nil, &FormatError{0, "invalid ELF shstrndx", shstrndx}
} }
@ -367,6 +368,10 @@ func NewFile(r io.ReaderAt) (*File, error) {
f.Sections[i] = s f.Sections[i] = s
} }
if len(f.Sections) == 0 {
return f, nil
}
// Load section header string table. // Load section header string table.
shstrtab, err := f.Sections[shstrndx].Data() shstrtab, err := f.Sections[shstrndx].Data()
if err != nil { if err != nil {

View File

@ -5,10 +5,14 @@
package elf package elf
import ( import (
"bytes"
"compress/gzip"
"debug/dwarf" "debug/dwarf"
"encoding/binary" "encoding/binary"
"io"
"net" "net"
"os" "os"
"path"
"reflect" "reflect"
"runtime" "runtime"
"testing" "testing"
@ -121,15 +125,49 @@ var fileTests = []fileTest{
}, },
[]string{"libc.so.6"}, []string{"libc.so.6"},
}, },
{
"testdata/hello-world-core.gz",
FileHeader{ELFCLASS64, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE, 0x0, binary.LittleEndian, ET_CORE, EM_X86_64, 0x0},
[]SectionHeader{},
[]ProgHeader{
{Type: PT_NOTE, Flags: 0x0, Off: 0x3f8, Vaddr: 0x0, Paddr: 0x0, Filesz: 0x8ac, Memsz: 0x0, Align: 0x0},
{Type: PT_LOAD, Flags: PF_X + PF_R, Off: 0x1000, Vaddr: 0x400000, Paddr: 0x0, Filesz: 0x0, Memsz: 0x1000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_R, Off: 0x1000, Vaddr: 0x401000, Paddr: 0x0, Filesz: 0x1000, Memsz: 0x1000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0x2000, Vaddr: 0x402000, Paddr: 0x0, Filesz: 0x1000, Memsz: 0x1000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_X + PF_R, Off: 0x3000, Vaddr: 0x7f54078b8000, Paddr: 0x0, Filesz: 0x0, Memsz: 0x1b5000, Align: 0x1000},
{Type: PT_LOAD, Flags: 0x0, Off: 0x3000, Vaddr: 0x7f5407a6d000, Paddr: 0x0, Filesz: 0x0, Memsz: 0x1ff000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_R, Off: 0x3000, Vaddr: 0x7f5407c6c000, Paddr: 0x0, Filesz: 0x4000, Memsz: 0x4000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0x7000, Vaddr: 0x7f5407c70000, Paddr: 0x0, Filesz: 0x2000, Memsz: 0x2000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0x9000, Vaddr: 0x7f5407c72000, Paddr: 0x0, Filesz: 0x5000, Memsz: 0x5000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_X + PF_R, Off: 0xe000, Vaddr: 0x7f5407c77000, Paddr: 0x0, Filesz: 0x0, Memsz: 0x22000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0xe000, Vaddr: 0x7f5407e81000, Paddr: 0x0, Filesz: 0x3000, Memsz: 0x3000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0x11000, Vaddr: 0x7f5407e96000, Paddr: 0x0, Filesz: 0x3000, Memsz: 0x3000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_R, Off: 0x14000, Vaddr: 0x7f5407e99000, Paddr: 0x0, Filesz: 0x1000, Memsz: 0x1000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0x15000, Vaddr: 0x7f5407e9a000, Paddr: 0x0, Filesz: 0x2000, Memsz: 0x2000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_W + PF_R, Off: 0x17000, Vaddr: 0x7fff79972000, Paddr: 0x0, Filesz: 0x23000, Memsz: 0x23000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_X + PF_R, Off: 0x3a000, Vaddr: 0x7fff799f8000, Paddr: 0x0, Filesz: 0x1000, Memsz: 0x1000, Align: 0x1000},
{Type: PT_LOAD, Flags: PF_X + PF_R, Off: 0x3b000, Vaddr: 0xffffffffff600000, Paddr: 0x0, Filesz: 0x1000, Memsz: 0x1000, Align: 0x1000},
},
nil,
},
} }
func TestOpen(t *testing.T) { func TestOpen(t *testing.T) {
for i := range fileTests { for i := range fileTests {
tt := &fileTests[i] tt := &fileTests[i]
f, err := Open(tt.file) var f *File
var err error
if path.Ext(tt.file) == ".gz" {
var r io.ReaderAt
if r, err = decompress(tt.file); err == nil {
f, err = NewFile(r)
}
} else {
f, err = Open(tt.file)
}
if err != nil { if err != nil {
t.Error(err) t.Errorf("cannot open file %s: %v", tt.file, err)
continue continue
} }
if !reflect.DeepEqual(f.FileHeader, tt.hdr) { if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
@ -175,6 +213,23 @@ func TestOpen(t *testing.T) {
} }
} }
// elf.NewFile requires io.ReaderAt, which compress/gzip cannot
// provide. Decompress the file to a bytes.Reader.
func decompress(gz string) (io.ReaderAt, error) {
in, err := os.Open(gz)
if err != nil {
return nil, err
}
defer in.Close()
r, err := gzip.NewReader(in)
if err != nil {
return nil, err
}
var out bytes.Buffer
_, err = io.Copy(&out, r)
return bytes.NewReader(out.Bytes()), err
}
type relocationTestEntry struct { type relocationTestEntry struct {
entryNumber int entryNumber int
entry *dwarf.Entry entry *dwarf.Entry

Binary file not shown.

View File

@ -92,10 +92,17 @@ func (w *Writer) Write(record []string) (err error) {
} }
// Flush writes any buffered data to the underlying io.Writer. // Flush writes any buffered data to the underlying io.Writer.
// To check if an error occurred during the Flush, call Error.
func (w *Writer) Flush() { func (w *Writer) Flush() {
w.w.Flush() w.w.Flush()
} }
// Error reports any error that has occurred during a previous Write or Flush.
func (w *Writer) Error() error {
_, err := w.w.Write(nil)
return err
}
// WriteAll writes multiple CSV records to w using Write and then calls Flush. // WriteAll writes multiple CSV records to w using Write and then calls Flush.
func (w *Writer) WriteAll(records [][]string) (err error) { func (w *Writer) WriteAll(records [][]string) (err error) {
for _, record := range records { for _, record := range records {

View File

@ -6,6 +6,7 @@ package csv
import ( import (
"bytes" "bytes"
"errors"
"testing" "testing"
) )
@ -42,3 +43,30 @@ func TestWrite(t *testing.T) {
} }
} }
} }
type errorWriter struct{}
func (e errorWriter) Write(b []byte) (int, error) {
return 0, errors.New("Test")
}
func TestError(t *testing.T) {
b := &bytes.Buffer{}
f := NewWriter(b)
f.Write([]string{"abc"})
f.Flush()
err := f.Error()
if err != nil {
t.Errorf("Unexpected error: %s\n", err)
}
f = NewWriter(errorWriter{})
f.Write([]string{"abc"})
f.Flush()
err = f.Error()
if err == nil {
t.Error("Error should not be nil")
}
}

View File

@ -62,15 +62,15 @@ func overflow(name string) error {
// Used only by the Decoder to read the message length. // Used only by the Decoder to read the message length.
func decodeUintReader(r io.Reader, buf []byte) (x uint64, width int, err error) { func decodeUintReader(r io.Reader, buf []byte) (x uint64, width int, err error) {
width = 1 width = 1
_, err = r.Read(buf[0:width]) n, err := io.ReadFull(r, buf[0:width])
if err != nil { if n == 0 {
return return
} }
b := buf[0] b := buf[0]
if b <= 0x7f { if b <= 0x7f {
return uint64(b), width, nil return uint64(b), width, nil
} }
n := -int(int8(b)) n = -int(int8(b))
if n > uint64Size { if n > uint64Size {
err = errBadUint err = errBadUint
return return

View File

@ -125,13 +125,12 @@ func (d *decodeState) unmarshal(v interface{}) (err error) {
}() }()
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
pv := rv if rv.Kind() != reflect.Ptr || rv.IsNil() {
if pv.Kind() != reflect.Ptr || pv.IsNil() {
return &InvalidUnmarshalError{reflect.TypeOf(v)} return &InvalidUnmarshalError{reflect.TypeOf(v)}
} }
d.scan.reset() d.scan.reset()
// We decode rv not pv.Elem because the Unmarshaler interface // We decode rv not rv.Elem because the Unmarshaler interface
// test must be applied at the top level of the value. // test must be applied at the top level of the value.
d.value(rv) d.value(rv)
return d.savedError return d.savedError
@ -423,17 +422,12 @@ func (d *decodeState) object(v reflect.Value) {
v = pv v = pv
// Decoding into nil interface? Switch to non-reflect code. // Decoding into nil interface? Switch to non-reflect code.
iv := v if v.Kind() == reflect.Interface {
if iv.Kind() == reflect.Interface { v.Set(reflect.ValueOf(d.objectInterface()))
iv.Set(reflect.ValueOf(d.objectInterface()))
return return
} }
// Check type of target: struct or map[string]T // Check type of target: struct or map[string]T
var (
mv reflect.Value
sv reflect.Value
)
switch v.Kind() { switch v.Kind() {
case reflect.Map: case reflect.Map:
// map must have string type // map must have string type
@ -442,17 +436,15 @@ func (d *decodeState) object(v reflect.Value) {
d.saveError(&UnmarshalTypeError{"object", v.Type()}) d.saveError(&UnmarshalTypeError{"object", v.Type()})
break break
} }
mv = v if v.IsNil() {
if mv.IsNil() { v.Set(reflect.MakeMap(t))
mv.Set(reflect.MakeMap(t))
} }
case reflect.Struct: case reflect.Struct:
sv = v
default: default:
d.saveError(&UnmarshalTypeError{"object", v.Type()}) d.saveError(&UnmarshalTypeError{"object", v.Type()})
} }
if !mv.IsValid() && !sv.IsValid() { if !v.IsValid() {
d.off-- d.off--
d.next() // skip over { } in input d.next() // skip over { } in input
return return
@ -484,8 +476,8 @@ func (d *decodeState) object(v reflect.Value) {
var subv reflect.Value var subv reflect.Value
destring := false // whether the value is wrapped in a string to be decoded first destring := false // whether the value is wrapped in a string to be decoded first
if mv.IsValid() { if v.Kind() == reflect.Map {
elemType := mv.Type().Elem() elemType := v.Type().Elem()
if !mapElem.IsValid() { if !mapElem.IsValid() {
mapElem = reflect.New(elemType).Elem() mapElem = reflect.New(elemType).Elem()
} else { } else {
@ -494,7 +486,7 @@ func (d *decodeState) object(v reflect.Value) {
subv = mapElem subv = mapElem
} else { } else {
var f *field var f *field
fields := cachedTypeFields(sv.Type()) fields := cachedTypeFields(v.Type())
for i := range fields { for i := range fields {
ff := &fields[i] ff := &fields[i]
if ff.name == key { if ff.name == key {
@ -506,7 +498,7 @@ func (d *decodeState) object(v reflect.Value) {
} }
} }
if f != nil { if f != nil {
subv = sv subv = v
destring = f.quoted destring = f.quoted
for _, i := range f.index { for _, i := range f.index {
if subv.Kind() == reflect.Ptr { if subv.Kind() == reflect.Ptr {
@ -519,7 +511,7 @@ func (d *decodeState) object(v reflect.Value) {
} }
} else { } else {
// To give a good error, a quick scan for unexported fields in top level. // To give a good error, a quick scan for unexported fields in top level.
st := sv.Type() st := v.Type()
for i := 0; i < st.NumField(); i++ { for i := 0; i < st.NumField(); i++ {
f := st.Field(i) f := st.Field(i)
if f.PkgPath != "" && strings.EqualFold(f.Name, key) { if f.PkgPath != "" && strings.EqualFold(f.Name, key) {
@ -546,8 +538,8 @@ func (d *decodeState) object(v reflect.Value) {
} }
// Write value back to map; // Write value back to map;
// if using struct, subv points into struct already. // if using struct, subv points into struct already.
if mv.IsValid() { if v.Kind() == reflect.Map {
mv.SetMapIndex(reflect.ValueOf(key), subv) v.SetMapIndex(reflect.ValueOf(key), subv)
} }
// Next token must be , or }. // Next token must be , or }.

View File

@ -25,7 +25,9 @@ func runTest(t *testing.T, path string) {
} else { } else {
// package directory // package directory
// TODO(gri) gotype should use the build package instead // TODO(gri) gotype should use the build package instead
pkg, err := build.Import(path, "", 0) ctxt := build.Default
ctxt.CgoEnabled = false
pkg, err := ctxt.Import(path, "", 0)
if err != nil { if err != nil {
t.Errorf("build.Import error for path = %s: %s", path, err) t.Errorf("build.Import error for path = %s: %s", path, err)
return return
@ -50,7 +52,7 @@ var tests = []string{
// directories // directories
// Note: packages that don't typecheck yet are commented out // Note: packages that don't typecheck yet are commented out
// "archive/tar", // investigate "archive/tar",
"archive/zip", "archive/zip",
"bufio", "bufio",
@ -77,13 +79,13 @@ var tests = []string{
"crypto/md5", "crypto/md5",
"crypto/rand", "crypto/rand",
"crypto/rc4", "crypto/rc4",
// "crypto/rsa", // investigate (GOARCH=386) // "crypto/rsa", // intermittent failure: /home/gri/go2/src/pkg/crypto/rsa/pkcs1v15.go:21:27: undeclared name: io
"crypto/sha1", "crypto/sha1",
"crypto/sha256", "crypto/sha256",
"crypto/sha512", "crypto/sha512",
"crypto/subtle", "crypto/subtle",
"crypto/tls", "crypto/tls",
// "crypto/x509", // investigate "crypto/x509",
"crypto/x509/pkix", "crypto/x509/pkix",
"database/sql", "database/sql",
@ -99,9 +101,9 @@ var tests = []string{
"encoding/asn1", "encoding/asn1",
"encoding/base32", "encoding/base32",
"encoding/base64", "encoding/base64",
// "encoding/binary", // complex() doesn't work yet "encoding/binary",
"encoding/csv", "encoding/csv",
// "encoding/gob", // complex() doesn't work yet "encoding/gob",
"encoding/hex", "encoding/hex",
"encoding/json", "encoding/json",
"encoding/pem", "encoding/pem",
@ -117,7 +119,7 @@ var tests = []string{
"go/ast", "go/ast",
"go/build", "go/build",
// "go/doc", // variadic parameters don't work yet fully "go/doc",
"go/format", "go/format",
"go/parser", "go/parser",
"go/printer", "go/printer",
@ -125,7 +127,7 @@ var tests = []string{
"go/token", "go/token",
"hash/adler32", "hash/adler32",
// "hash/crc32", // investigate "hash/crc32",
"hash/crc64", "hash/crc64",
"hash/fnv", "hash/fnv",
@ -139,54 +141,54 @@ var tests = []string{
"index/suffixarray", "index/suffixarray",
"io", "io",
// "io/ioutil", // investigate "io/ioutil",
"log", "log",
"log/syslog", "log/syslog",
"math", "math",
// "math/big", // investigate "math/big",
// "math/cmplx", // complex doesn't work yet "math/cmplx",
"math/rand", "math/rand",
"mime", "mime",
"mime/multipart", "mime/multipart",
// "net", // depends on C files // "net", // c:\go\root\src\pkg\net\interface_windows.go:54:13: invalid operation: division by zero
"net/http", "net/http",
"net/http/cgi", "net/http/cgi",
// "net/http/fcgi", // investigate "net/http/fcgi",
"net/http/httptest", "net/http/httptest",
"net/http/httputil", "net/http/httputil",
// "net/http/pprof", // investigate "net/http/pprof",
"net/mail", "net/mail",
// "net/rpc", // investigate "net/rpc",
"net/rpc/jsonrpc", "net/rpc/jsonrpc",
"net/smtp", "net/smtp",
"net/textproto", "net/textproto",
"net/url", "net/url",
// "path", // variadic parameters don't work yet fully "path",
// "path/filepath", // investigate "path/filepath",
// "reflect", // investigate // "reflect", // unsafe.Sizeof must return size > 0 for pointer types
"regexp", "regexp",
"regexp/syntax", "regexp/syntax",
"runtime", "runtime",
// "runtime/cgo", // import "C" "runtime/cgo",
"runtime/debug", "runtime/debug",
"runtime/pprof", "runtime/pprof",
"sort", "sort",
// "strconv", // investigate // "strconv", // bug in switch case duplicate detection
"strings", "strings",
// "sync", // platform-specific files "sync",
// "sync/atomic", // platform-specific files "sync/atomic",
// "syscall", // platform-specific files // "syscall", c:\go\root\src\pkg\syscall\syscall_windows.go:35:16: cannot convert EINVAL (constant 536870951) to error
"testing", "testing",
"testing/iotest", "testing/iotest",
@ -194,10 +196,10 @@ var tests = []string{
"text/scanner", "text/scanner",
"text/tabwriter", "text/tabwriter",
// "text/template", // variadic parameters don't work yet fully "text/template",
// "text/template/parse", // variadic parameters don't work yet fully "text/template/parse",
// "time", // platform-specific files // "time", // local const decls without initialization expressions
"unicode", "unicode",
"unicode/utf16", "unicode/utf16",
"unicode/utf8", "unicode/utf8",

View File

@ -20,7 +20,7 @@ import (
const ( const (
blockSize = 64 blockSize = 64
blockOffset = 2 // Substract 2 blocks to compensate for the 0x80 added to continuation bytes. blockOffset = 2 // Subtract 2 blocks to compensate for the 0x80 added to continuation bytes.
) )
type trieHandle struct { type trieHandle struct {

View File

@ -179,7 +179,7 @@ doNorm:
i.rb.src.copySlice(out[outCopyStart:], inCopyStart, i.p) i.rb.src.copySlice(out[outCopyStart:], inCopyStart, i.p)
if !i.rb.insertDecomposed(out[i.outStart:outp]) { if !i.rb.insertDecomposed(out[i.outStart:outp]) {
// Start over to prevent decompositions from crossing segment boundaries. // Start over to prevent decompositions from crossing segment boundaries.
// This is a rare occurance. // This is a rare occurrence.
i.p = i.inStart i.p = i.inStart
i.info = i.rb.f.info(i.rb.src, i.p) i.info = i.rb.f.info(i.rb.src, i.p)
} }

View File

@ -31,7 +31,7 @@ func runPosTests(t *testing.T, name string, f Form, fn positionFunc, tests []Pos
} }
runes := []rune(test.buffer) runes := []rune(test.buffer)
if rb.nrune != len(runes) { if rb.nrune != len(runes) {
t.Errorf("%s:%d: reorder buffer lenght is %d; want %d", name, i, rb.nrune, len(runes)) t.Errorf("%s:%d: reorder buffer length is %d; want %d", name, i, rb.nrune, len(runes))
continue continue
} }
for j, want := range runes { for j, want := range runes {

View File

@ -21,7 +21,7 @@ import (
const ( const (
blockSize = 64 blockSize = 64
blockOffset = 2 // Substract two blocks to compensate for the 0x80 added to continuation bytes. blockOffset = 2 // Subtract two blocks to compensate for the 0x80 added to continuation bytes.
maxSparseEntries = 16 maxSparseEntries = 16
) )

View File

@ -128,13 +128,51 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
x.mode = novalue x.mode = novalue
case _Complex: case _Complex:
if !check.complexArg(x) {
goto Error
}
var y operand var y operand
check.expr(&y, args[1], nil, iota) check.expr(&y, args[1], nil, iota)
if y.mode == invalid { if y.mode == invalid {
goto Error goto Error
} }
// TODO(gri) handle complex(a, b) like (a + toImag(b)) if !check.complexArg(&y) {
unimplemented() goto Error
}
check.convertUntyped(x, y.typ)
if x.mode == invalid {
goto Error
}
check.convertUntyped(&y, x.typ)
if y.mode == invalid {
goto Error
}
if !isIdentical(x.typ, y.typ) {
check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
goto Error
}
typ := underlying(x.typ).(*Basic)
if x.mode == constant && y.mode == constant {
x.val = binaryOpConst(x.val, toImagConst(y.val), token.ADD, typ)
} else {
x.mode = value
}
switch typ.Kind {
case Float32:
x.typ = Typ[Complex64]
case Float64:
x.typ = Typ[Complex128]
case UntypedInt, UntypedRune, UntypedFloat:
x.typ = Typ[UntypedComplex]
default:
check.invalidArg(x.pos(), "float32 or float64 arguments expected")
goto Error
}
case _Copy: case _Copy:
// TODO(gri) implements checks // TODO(gri) implements checks
@ -361,3 +399,12 @@ func unparen(x ast.Expr) ast.Expr {
} }
return x return x
} }
func (check *checker) complexArg(x *operand) bool {
t, _ := underlying(x.typ).(*Basic)
if t != nil && (t.Info&IsFloat != 0 || t.Kind == UntypedInt || t.Kind == UntypedRune) {
return true
}
check.invalidArg(x.pos(), "%s must be a float32, float64, or an untyped non-complex numeric constant", x)
return false
}

View File

@ -49,12 +49,17 @@ func (nilType) String() string {
return "nil" return "nil"
} }
// Frequently used constants. // Implementation-specific constants.
// TODO(gri) These need to go elsewhere.
const (
intBits = 32
ptrBits = 64
)
// Frequently used values.
var ( var (
zeroConst = int64(0) nilConst = nilType{}
oneConst = int64(1) zeroConst = int64(0)
minusOneConst = int64(-1)
nilConst = nilType{}
) )
// int64 bounds // int64 bounds
@ -74,7 +79,7 @@ func normalizeIntConst(x *big.Int) interface{} {
} }
// normalizeRatConst returns the smallest constant representation // normalizeRatConst returns the smallest constant representation
// for the specific value of x; either an int64, *big.Int value, // for the specific value of x; either an int64, *big.Int,
// or *big.Rat value. // or *big.Rat value.
// //
func normalizeRatConst(x *big.Rat) interface{} { func normalizeRatConst(x *big.Rat) interface{} {
@ -84,15 +89,15 @@ func normalizeRatConst(x *big.Rat) interface{} {
return x return x
} }
// normalizeComplexConst returns the smallest constant representation // newComplex returns the smallest constant representation
// for the specific value of x; either an int64, *big.Int value, *big.Rat, // for the specific value re + im*i; either an int64, *big.Int,
// or complex value. // *big.Rat, or complex value.
// //
func normalizeComplexConst(x complex) interface{} { func newComplex(re, im *big.Rat) interface{} {
if x.im.Sign() == 0 { if im.Sign() == 0 {
return normalizeRatConst(x.re) return normalizeRatConst(re)
} }
return x return complex{re, im}
} }
// makeRuneConst returns the int64 code point for the rune literal // makeRuneConst returns the int64 code point for the rune literal
@ -140,7 +145,7 @@ func makeComplexConst(lit string) interface{} {
n := len(lit) n := len(lit)
if n > 0 && lit[n-1] == 'i' { if n > 0 && lit[n-1] == 'i' {
if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok { if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok {
return normalizeComplexConst(complex{big.NewRat(0, 1), im}) return newComplex(big.NewRat(0, 1), im)
} }
} }
return nil return nil
@ -157,6 +162,22 @@ func makeStringConst(lit string) interface{} {
return nil return nil
} }
// toImagConst returns the constant complex(0, x) for a non-complex x.
func toImagConst(x interface{}) interface{} {
var im *big.Rat
switch x := x.(type) {
case int64:
im = big.NewRat(x, 1)
case *big.Int:
im = new(big.Rat).SetFrac(x, int1)
case *big.Rat:
im = x
default:
unreachable()
}
return complex{rat0, im}
}
// isZeroConst reports whether the value of constant x is 0. // isZeroConst reports whether the value of constant x is 0.
// x must be normalized. // x must be normalized.
// //
@ -186,9 +207,6 @@ func isNegConst(x interface{}) bool {
// of precision. // of precision.
// //
func isRepresentableConst(x interface{}, as BasicKind) bool { func isRepresentableConst(x interface{}, as BasicKind) bool {
const intBits = 32 // TODO(gri) implementation-specific constant
const ptrBits = 64 // TODO(gri) implementation-specific constant
switch x := x.(type) { switch x := x.(type) {
case bool: case bool:
return as == Bool || as == UntypedBool return as == Bool || as == UntypedBool
@ -370,13 +388,71 @@ func is63bit(x int64) bool {
return -1<<62 <= x && x <= 1<<62-1 return -1<<62 <= x && x <= 1<<62-1
} }
// unaryOpConst returns the result of the constant evaluation op x where x is of the given type.
func unaryOpConst(x interface{}, op token.Token, typ *Basic) interface{} {
switch op {
case token.ADD:
return x // nothing to do
case token.SUB:
switch x := x.(type) {
case int64:
if z := -x; z != x {
return z // no overflow
}
// overflow - need to convert to big.Int
return normalizeIntConst(new(big.Int).Neg(big.NewInt(x)))
case *big.Int:
return normalizeIntConst(new(big.Int).Neg(x))
case *big.Rat:
return normalizeRatConst(new(big.Rat).Neg(x))
case complex:
return newComplex(new(big.Rat).Neg(x.re), new(big.Rat).Neg(x.im))
}
case token.XOR:
var z big.Int
switch x := x.(type) {
case int64:
z.Not(big.NewInt(x))
case *big.Int:
z.Not(x)
default:
unreachable()
}
// For unsigned types, the result will be negative and
// thus "too large": We must limit the result size to
// the type's size.
if typ.Info&IsUnsigned != 0 {
s := uint(typ.Size) * 8
if s == 0 {
// platform-specific type
// TODO(gri) this needs to be factored out
switch typ.Kind {
case Uint:
s = intBits
case Uintptr:
s = ptrBits
default:
unreachable()
}
}
// z &^= (-1)<<s
z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s))
}
return normalizeIntConst(&z)
case token.NOT:
return !x.(bool)
}
unreachable()
return nil
}
// binaryOpConst returns the result of the constant evaluation x op y; // binaryOpConst returns the result of the constant evaluation x op y;
// both operands must be of the same "kind" (boolean, numeric, or string). // both operands must be of the same constant "kind" (boolean, numeric, or string).
// If intDiv is true, division (op == token.QUO) is using integer division // If typ is an integer type, division (op == token.QUO) is using integer division
// (and the result is guaranteed to be integer) rather than floating-point // (and the result is guaranteed to be integer) rather than floating-point
// division. Division by zero leads to a run-time panic. // division. Division by zero leads to a run-time panic.
// //
func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} { func binaryOpConst(x, y interface{}, op token.Token, typ *Basic) interface{} {
x, y = matchConst(x, y) x, y = matchConst(x, y)
switch x := x.(type) { switch x := x.(type) {
@ -387,8 +463,6 @@ func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} {
return x && y return x && y
case token.LOR: case token.LOR:
return x || y return x || y
default:
unreachable()
} }
case int64: case int64:
@ -415,7 +489,7 @@ func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} {
case token.REM: case token.REM:
return x % y return x % y
case token.QUO: case token.QUO:
if intDiv { if typ.Info&IsInteger != 0 {
return x / y return x / y
} }
return normalizeRatConst(new(big.Rat).SetFrac(big.NewInt(x), big.NewInt(y))) return normalizeRatConst(new(big.Rat).SetFrac(big.NewInt(x), big.NewInt(y)))
@ -427,8 +501,6 @@ func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} {
return x ^ y return x ^ y
case token.AND_NOT: case token.AND_NOT:
return x &^ y return x &^ y
default:
unreachable()
} }
case *big.Int: case *big.Int:
@ -444,7 +516,7 @@ func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} {
case token.REM: case token.REM:
z.Rem(x, y) z.Rem(x, y)
case token.QUO: case token.QUO:
if intDiv { if typ.Info&IsInteger != 0 {
z.Quo(x, y) z.Quo(x, y)
} else { } else {
return normalizeRatConst(new(big.Rat).SetFrac(x, y)) return normalizeRatConst(new(big.Rat).SetFrac(x, y))
@ -517,7 +589,7 @@ func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} {
default: default:
unreachable() unreachable()
} }
return normalizeComplexConst(complex{&re, &im}) return newComplex(&re, &im)
case string: case string:
if op == token.ADD { if op == token.ADD {

View File

@ -266,15 +266,8 @@ func writeType(buf *bytes.Buffer, typ Type) {
buf.WriteByte('*') buf.WriteByte('*')
writeType(buf, t.Base) writeType(buf, t.Base)
case *tuple: case *Result:
buf.WriteByte('(') writeParams(buf, t.Values, false)
for i, typ := range t.list {
if i > 0 {
buf.WriteString("; ")
}
writeType(buf, typ)
}
buf.WriteByte(')')
case *Signature: case *Signature:
buf.WriteString("func") buf.WriteString("func")

View File

@ -19,7 +19,6 @@ import (
// - at the moment, iota is passed around almost everywhere - in many places we know it cannot be used // - at the moment, iota is passed around almost everywhere - in many places we know it cannot be used
// TODO(gri) API issues // TODO(gri) API issues
// - clients need access to result type information (tuples)
// - clients need access to constant values // - clients need access to constant values
// - clients need access to built-in type information // - clients need access to built-in type information
@ -212,21 +211,11 @@ func (check *checker) unary(x *operand, op token.Token) {
} }
if x.mode == constant { if x.mode == constant {
switch op { typ := underlying(x.typ).(*Basic)
case token.ADD: x.val = unaryOpConst(x.val, op, typ)
// nothing to do
case token.SUB:
x.val = binaryOpConst(zeroConst, x.val, token.SUB, false)
case token.XOR:
x.val = binaryOpConst(minusOneConst, x.val, token.XOR, false)
case token.NOT:
x.val = !x.val.(bool)
default:
unreachable() // operators where checked by check.op
}
// Typed constants must be representable in // Typed constants must be representable in
// their type after each constant operation. // their type after each constant operation.
check.isRepresentable(x, underlying(x.typ).(*Basic)) check.isRepresentable(x, typ)
return return
} }
@ -304,6 +293,8 @@ func (check *checker) convertUntyped(x *operand, target Type) {
if !x.isNil() { if !x.isNil() {
goto Error goto Error
} }
default:
unreachable()
} }
x.typ = target x.typ = target
@ -332,7 +323,7 @@ func (check *checker) comparison(x, y *operand, op token.Token) {
} }
if !valid { if !valid {
check.invalidOp(x.pos(), "cannot compare %s and %s", x, y) check.invalidOp(x.pos(), "cannot compare %s %s %s", x, op, y)
x.mode = invalid x.mode = invalid
return return
} }
@ -465,10 +456,11 @@ func (check *checker) binary(x, y *operand, op token.Token, hint Type) {
} }
if x.mode == constant && y.mode == constant { if x.mode == constant && y.mode == constant {
x.val = binaryOpConst(x.val, y.val, op, isInteger(x.typ)) typ := underlying(x.typ).(*Basic)
x.val = binaryOpConst(x.val, y.val, op, typ)
// Typed constants must be representable in // Typed constants must be representable in
// their type after each constant operation. // their type after each constant operation.
check.isRepresentable(x, underlying(x.typ).(*Basic)) check.isRepresentable(x, typ)
return return
} }
@ -554,9 +546,15 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota
return max return max
} }
func (check *checker) argument(sig *Signature, i int, arg ast.Expr) { // argument typechecks passing an argument arg (if arg != nil) or
// x (if arg == nil) to the i'th parameter of the given signature.
// If passSlice is set, the argument is followed by ... in the call.
//
func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, passSlice bool) {
// determine parameter
var par *ast.Object var par *ast.Object
if n := len(sig.Params); i < n { n := len(sig.Params)
if i < n {
par = sig.Params[i] par = sig.Params[i]
} else if sig.IsVariadic { } else if sig.IsVariadic {
par = sig.Params[n-1] par = sig.Params[n-1]
@ -565,16 +563,32 @@ func (check *checker) argument(sig *Signature, i int, arg ast.Expr) {
return return
} }
// TODO(gri) deal with ... last argument // determine argument
var z, x operand var z operand
z.mode = variable z.mode = variable
z.expr = nil // TODO(gri) can we do better here? z.expr = nil // TODO(gri) can we do better here? (for good error messages)
z.typ = par.Type.(Type) // TODO(gri) should become something like checkObj(&z, ...) eventually z.typ = par.Type.(Type)
check.expr(&x, arg, z.typ, -1)
if arg != nil {
check.expr(x, arg, z.typ, -1)
}
if x.mode == invalid { if x.mode == invalid {
return // ignore this argument return // ignore this argument
} }
check.assignOperand(&z, &x)
// check last argument of the form x...
if passSlice {
if i+1 != n {
check.errorf(x.pos(), "can only use ... with matching parameter")
return // ignore this argument
}
// spec: "If the final argument is assignable to a slice type []T,
// it may be passed unchanged as the value for a ...T parameter if
// the argument is followed by ..."
z.typ = &Slice{Elt: z.typ} // change final parameter type to []T
}
check.assignOperand(&z, x)
} }
func (check *checker) recordType(x *operand) { func (check *checker) recordType(x *operand) {
@ -584,7 +598,7 @@ func (check *checker) recordType(x *operand) {
} }
// rawExpr typechecks expression e and initializes x with the expression // rawExpr typechecks expression e and initializes x with the expression
// value or type. If an error occured, x.mode is set to invalid. // value or type. If an error occurred, x.mode is set to invalid.
// A hint != nil is used as operand type for untyped shifted operands; // A hint != nil is used as operand type for untyped shifted operands;
// iota >= 0 indicates that the expression is part of a constant declaration. // iota >= 0 indicates that the expression is part of a constant declaration.
// cycleOk indicates whether it is ok for a type expression to refer to itself. // cycleOk indicates whether it is ok for a type expression to refer to itself.
@ -653,7 +667,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
x.typ = obj.Type.(Type) x.typ = obj.Type.(Type)
case *ast.Ellipsis: case *ast.Ellipsis:
// ellipses are handled explictly where they are legal // ellipses are handled explicitly where they are legal
// (array composite literals and parameter lists) // (array composite literals and parameter lists)
check.errorf(e.Pos(), "invalid use of '...'") check.errorf(e.Pos(), "invalid use of '...'")
goto Error goto Error
@ -1052,25 +1066,79 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
check.conversion(x, e, x.typ, iota) check.conversion(x, e, x.typ, iota)
} else if sig, ok := underlying(x.typ).(*Signature); ok { } else if sig, ok := underlying(x.typ).(*Signature); ok {
// check parameters // check parameters
// TODO(gri)
// - deal with single multi-valued function arguments: f(g()) // If we have a trailing ... at the end of the parameter
// - variadic functions only partially addressed // list, the last argument must match the parameter type
for i, arg := range e.Args { // []T of a variadic function parameter x ...T.
check.argument(sig, i, arg) passSlice := false
if e.Ellipsis.IsValid() {
if sig.IsVariadic {
passSlice = true
} else {
check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun)
// ok to continue
}
}
// If we have a single argument that is a function call
// we need to handle it separately. Determine if this
// is the case without checking the argument.
var call *ast.CallExpr
if len(e.Args) == 1 {
call, _ = unparen(e.Args[0]).(*ast.CallExpr)
}
n := 0 // parameter count
if call != nil {
// We have a single argument that is a function call.
check.expr(x, call, nil, -1)
if x.mode == invalid {
goto Error // TODO(gri): we can do better
}
if t, _ := x.typ.(*Result); t != nil {
// multiple result values
n = len(t.Values)
for i, obj := range t.Values {
x.mode = value
x.expr = nil // TODO(gri) can we do better here? (for good error messages)
x.typ = obj.Type.(Type)
check.argument(sig, i, nil, x, passSlice && i+1 == n)
}
} else {
// single result value
n = 1
check.argument(sig, 0, nil, x, passSlice)
}
} else {
// We don't have a single argument or it is not a function call.
n = len(e.Args)
for i, arg := range e.Args {
check.argument(sig, i, arg, x, passSlice && i+1 == n)
}
}
// determine if we have enough arguments
if sig.IsVariadic {
// a variadic function accepts an "empty"
// last argument: count one extra
n++
}
if n < len(sig.Params) {
check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun)
// ok to continue
} }
// determine result // determine result
x.mode = value switch len(sig.Results) {
if len(sig.Results) == 1 { case 0:
x.mode = novalue
case 1:
x.mode = value
x.typ = sig.Results[0].Type.(Type) x.typ = sig.Results[0].Type.(Type)
} else { default:
// TODO(gri) change Signature representation to use tuples, x.mode = value
// then this conversion is not required x.typ = &Result{Values: sig.Results}
list := make([]Type, len(sig.Results))
for i, obj := range sig.Results {
list[i] = obj.Type.(Type)
}
x.typ = &tuple{list: list}
} }
} else if bin, ok := x.typ.(*builtin); ok { } else if bin, ok := x.typ.(*builtin); ok {
@ -1216,14 +1284,14 @@ func (check *checker) rawTyp(e ast.Expr, cycleOk, nilOk bool) Type {
} }
// typOrNil is like rawExpr but reports an error if e doesn't represents a type or the predeclared value nil. // typOrNil is like rawExpr but reports an error if e doesn't represents a type or the predeclared value nil.
// It returns e's type, nil, or Typ[Invalid] if an error occured. // It returns e's type, nil, or Typ[Invalid] if an error occurred.
// //
func (check *checker) typOrNil(e ast.Expr, cycleOk bool) Type { func (check *checker) typOrNil(e ast.Expr, cycleOk bool) Type {
return check.rawTyp(e, cycleOk, true) return check.rawTyp(e, cycleOk, true)
} }
// typ is like rawExpr but reports an error if e doesn't represents a type. // typ is like rawExpr but reports an error if e doesn't represents a type.
// It returns e's type, or Typ[Invalid] if an error occured. // It returns e's type, or Typ[Invalid] if an error occurred.
// //
func (check *checker) typ(e ast.Expr, cycleOk bool) Type { func (check *checker) typ(e ast.Expr, cycleOk bool) Type {
return check.rawTyp(e, cycleOk, false) return check.rawTyp(e, cycleOk, false)

View File

@ -182,7 +182,14 @@ func (x *operand) isAssignable(T Type) bool {
if isUntyped(Vu) { if isUntyped(Vu) {
switch t := Tu.(type) { switch t := Tu.(type) {
case *Basic: case *Basic:
return x.mode == constant && isRepresentableConst(x.val, t.Kind) if x.mode == constant {
return isRepresentableConst(x.val, t.Kind)
}
// The result of a comparison is an untyped boolean,
// but may not be a constant.
if Vb, _ := Vu.(*Basic); Vb != nil {
return Vb.Kind == UntypedBool && isBoolean(Tu)
}
case *Interface: case *Interface:
return x.isNil() || len(t.Methods) == 0 return x.isNil() || len(t.Methods) == 0
case *Pointer, *Signature, *Slice, *Map, *Chan: case *Pointer, *Signature, *Slice, *Map, *Chan:

View File

@ -225,25 +225,28 @@ func deref(typ Type) Type {
} }
// defaultType returns the default "typed" type for an "untyped" type; // defaultType returns the default "typed" type for an "untyped" type;
// it returns the argument typ for all other types. // it returns the incoming type for all other types. If there is no
// corresponding untyped type, the result is Typ[Invalid].
//
func defaultType(typ Type) Type { func defaultType(typ Type) Type {
if t, ok := typ.(*Basic); ok { if t, ok := typ.(*Basic); ok {
var k BasicKind k := Invalid
switch t.Kind { switch t.Kind {
// case UntypedNil:
// There is no default type for nil. For a good error message,
// catch this case before calling this function.
case UntypedBool: case UntypedBool:
k = Bool k = Bool
case UntypedRune:
k = Rune
case UntypedInt: case UntypedInt:
k = Int k = Int
case UntypedRune:
k = Rune
case UntypedFloat: case UntypedFloat:
k = Float64 k = Float64
case UntypedComplex: case UntypedComplex:
k = Complex128 k = Complex128
case UntypedString: case UntypedString:
k = String k = String
default:
unreachable()
} }
typ = Typ[k] typ = Typ[k]
} }

View File

@ -12,9 +12,9 @@ import (
) )
func (check *checker) assignOperand(z, x *operand) { func (check *checker) assignOperand(z, x *operand) {
if t, ok := x.typ.(*tuple); ok { if t, ok := x.typ.(*Result); ok {
// TODO(gri) elsewhere we use "assignment count mismatch" (consolidate) // TODO(gri) elsewhere we use "assignment count mismatch" (consolidate)
check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.list), x) check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.Values), x)
x.mode = invalid x.mode = invalid
return return
} }
@ -95,7 +95,12 @@ func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota
if x.mode != invalid { if x.mode != invalid {
typ = x.typ typ = x.typ
if obj.Kind == ast.Var && isUntyped(typ) { if obj.Kind == ast.Var && isUntyped(typ) {
typ = defaultType(typ) if x.isNil() {
check.errorf(x.pos(), "use of untyped nil")
x.mode = invalid
} else {
typ = defaultType(typ)
}
} }
} }
obj.Type = typ obj.Type = typ
@ -177,12 +182,12 @@ func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) {
return return
} }
if t, ok := x.typ.(*tuple); ok && len(lhs) == len(t.list) { if t, ok := x.typ.(*Result); ok && len(lhs) == len(t.Values) {
// function result // function result
x.mode = value x.mode = value
for i, typ := range t.list { for i, obj := range t.Values {
x.expr = nil // TODO(gri) should do better here x.expr = nil // TODO(gri) should do better here
x.typ = typ x.typ = obj.Type.(Type)
check.assign1to1(lhs[i], nil, &x, decl, iota) check.assign1to1(lhs[i], nil, &x, decl, iota)
} }
return return
@ -427,25 +432,58 @@ func (check *checker) stmt(s ast.Stmt) {
case *ast.SwitchStmt: case *ast.SwitchStmt:
check.optionalStmt(s.Init) check.optionalStmt(s.Init)
var x operand var x operand
if s.Tag != nil { tag := s.Tag
check.expr(&x, s.Tag, nil, -1) if tag == nil {
} else { // use fake true tag value and position it at the opening { of the switch
// TODO(gri) should provide a position (see IncDec) for good error messages tag = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")}
x.mode = constant
x.typ = Typ[UntypedBool]
x.val = true
} }
check.expr(&x, tag, nil, -1)
check.multipleDefaults(s.Body.List) check.multipleDefaults(s.Body.List)
seen := make(map[interface{}]token.Pos)
for _, s := range s.Body.List { for _, s := range s.Body.List {
clause, _ := s.(*ast.CaseClause) clause, _ := s.(*ast.CaseClause)
if clause == nil { if clause == nil {
continue // error reported before continue // error reported before
} }
for _, expr := range clause.List { if x.mode != invalid {
var y operand for _, expr := range clause.List {
check.expr(&y, expr, nil, -1) x := x // copy of x (don't modify original)
// TODO(gri) x and y must be comparable var y operand
check.expr(&y, expr, nil, -1)
if y.mode == invalid {
continue // error reported before
}
// If we have a constant case value, it must appear only
// once in the switch statement. Determine if there is a
// duplicate entry, but only report an error if there are
// no other errors.
var dupl token.Pos
if y.mode == constant {
// TODO(gri) This code doesn't work correctly for
// large integer, floating point, or
// complex values - the respective struct
// comparisons are shallow. Need to use a
// hash function to index the map.
dupl = seen[y.val]
seen[y.val] = y.pos()
}
// TODO(gri) The convertUntyped call pair below appears in other places. Factor!
// Order matters: By comparing y against x, error positions are at the case values.
check.convertUntyped(&y, x.typ)
if y.mode == invalid {
continue // error reported before
}
check.convertUntyped(&x, y.typ)
if x.mode == invalid {
continue // error reported before
}
check.comparison(&y, &x, token.EQL)
if y.mode != invalid && dupl.IsValid() {
check.errorf(y.pos(), "%s is duplicate case (previous at %s)",
&y, check.fset.Position(dupl))
}
}
} }
check.stmtList(clause.Body) check.stmtList(clause.Body)
} }

View File

@ -46,10 +46,33 @@ func _close() {
} }
func _complex() { func _complex() {
_0 := complex /* ERROR "argument" */ () var i32 int32
_1 := complex /* ERROR "argument" */ (1) var f32 float32
_2 := complex(1, 2) var f64 float64
// TODO(gri) add tests checking types var c64 complex64
_ = complex /* ERROR "argument" */ ()
_ = complex /* ERROR "argument" */ (1)
_ = complex(true /* ERROR "invalid argument" */ , 0)
_ = complex(i32 /* ERROR "invalid argument" */ , 0)
_ = complex("foo" /* ERROR "invalid argument" */ , 0)
_ = complex(c64 /* ERROR "invalid argument" */ , 0)
_ = complex(0, true /* ERROR "invalid argument" */ )
_ = complex(0, i32 /* ERROR "invalid argument" */ )
_ = complex(0, "foo" /* ERROR "invalid argument" */ )
_ = complex(0, c64 /* ERROR "invalid argument" */ )
_ = complex(f32, f32)
_ = complex(f32, 1)
_ = complex(f32, 1.0)
_ = complex(f32, 'a')
_ = complex(f64, f64)
_ = complex(f64, 1)
_ = complex(f64, 1.0)
_ = complex(f64, 'a')
_ = complex(f32 /* ERROR "mismatched types" */, f64)
_ = complex(f64 /* ERROR "mismatched types" */, f32)
_ = complex(1, 1)
_ = complex(1, 1.1)
_ = complex(1, 'a')
complex /* ERROR "not used" */ (1, 2) complex /* ERROR "not used" */ (1, 2)
} }

View File

@ -46,7 +46,7 @@ var (
s14 = i << j /* ERROR "must be unsigned" */ s14 = i << j /* ERROR "must be unsigned" */
s18 = math.Pi * 10.0 s18 = math.Pi * 10.0
s19 = s1 /* ERROR "cannot call" */ () s19 = s1 /* ERROR "cannot call" */ ()
s20 = f0 /* ERROR "used as single value" */ () s20 = f0 /* ERROR "no value" */ ()
s21 = f6(1, s1, i) s21 = f6(1, s1, i)
s22 = f6(1, s1, uu /* ERROR "cannot assign" */ ) s22 = f6(1, s1, uu /* ERROR "cannot assign" */ )
@ -68,7 +68,7 @@ var (
t17 math /* ERROR "not a type" */ .Pi t17 math /* ERROR "not a type" */ .Pi
t18 float64 = math.Pi * 10.0 t18 float64 = math.Pi * 10.0
t19 int = t1 /* ERROR "cannot call" */ () t19 int = t1 /* ERROR "cannot call" */ ()
t20 int = f0 /* ERROR "used as single value" */ () t20 int = f0 /* ERROR "no value" */ ()
) )
// Various more complex expressions // Various more complex expressions
@ -94,6 +94,7 @@ var (
v10 byte = 1024 /* ERROR "overflows" */ v10 byte = 1024 /* ERROR "overflows" */
v11 = xx/yy*yy - xx v11 = xx/yy*yy - xx
v12 = true && false v12 = true && false
v13 = nil /* ERROR "use of untyped nil" */
) )
// Multiple assignment expressions // Multiple assignment expressions

View File

@ -63,6 +63,7 @@ var (
u16 = &u0 u16 = &u0
u17 = *u16 u17 = *u16
u18 = <-u16 /* ERROR "cannot receive" */ u18 = <-u16 /* ERROR "cannot receive" */
u19 = ^uint(0)
// float64 // float64
f0 = float64(1) f0 = float64(1)
@ -131,5 +132,4 @@ var (
ch7 = <-ch ch7 = <-ch
ch8 = <-rc ch8 = <-rc
ch9 = <-sc /* ERROR "cannot receive" */ ch9 = <-sc /* ERROR "cannot receive" */
)
)

View File

@ -6,6 +6,17 @@
package expr2 package expr2
func _bool() {
const t = true == true
const f = true == false
_ = t /* ERROR "cannot compare" */ < f
_ = 0 /* ERROR "cannot convert" */ == t
var b bool
var x, y float32
b = x < y
_ = struct{b bool}{x < y}
}
// corner cases // corner cases
var ( var (
v0 = nil /* ERROR "cannot compare" */ == nil v0 = nil /* ERROR "cannot compare" */ == nil

View File

@ -286,3 +286,64 @@ func type_asserts() {
_ = t.(T2 /* ERROR "wrong type for method m" */ ) _ = t.(T2 /* ERROR "wrong type for method m" */ )
_ = t.(I2 /* ERROR "wrong type for method m" */ ) _ = t.(I2 /* ERROR "wrong type for method m" */ )
} }
func f0() {}
func f1(x int) {}
func f2(u float32, s string) {}
func fs(s []byte) {}
func fv(x ...int) {}
func fi(x ... interface{}) {}
func g0() {}
func g1() int { return 0}
func g2() (u float32, s string) { return }
func gs() []byte { return nil }
func _calls() {
var x int
var y float32
var s []int
f0()
_ = f0 /* ERROR "used as value" */ ()
f0(g0 /* ERROR "too many arguments" */ )
f1(0)
f1(x)
f1(10.0)
f1 /* ERROR "too few arguments" */ ()
f1(x, y /* ERROR "too many arguments" */ )
f1(s /* ERROR "cannot assign" */ )
f1(x ... /* ERROR "cannot use ..." */ )
f1(g0 /* ERROR "used as value" */ ())
f1(g1())
// f1(g2()) // TODO(gri) missing position in error message
f2 /* ERROR "too few arguments" */ ()
f2 /* ERROR "too few arguments" */ (3.14)
f2(3.14, "foo")
f2(x /* ERROR "cannot assign" */ , "foo")
f2(g0 /* ERROR "used as value" */ ())
f2 /* ERROR "too few arguments" */ (g1 /* ERROR "cannot assign" */ ())
f2(g2())
fs /* ERROR "too few arguments" */ ()
fs(g0 /* ERROR "used as value" */ ())
fs(g1 /* ERROR "cannot assign" */ ())
// fs(g2()) // TODO(gri) missing position in error message
fs(gs())
fv()
fv(1, 2.0, x)
fv(s /* ERROR "cannot assign" */ )
fv(s...)
fv(1, s /* ERROR "can only use ... with matching parameter" */ ...)
fv(gs /* ERROR "cannot assign" */ ())
fv(gs /* ERROR "cannot assign" */ ()...)
fi()
fi(1, 2.0, x, 3.14, "foo")
fi(g2())
fi(0, g2)
fi(0, g2 /* ERROR "2-valued expression" */ ())
}

View File

@ -101,7 +101,31 @@ func _switches() {
default /* ERROR "multiple defaults" */ : default /* ERROR "multiple defaults" */ :
} }
// TODO(gri) more tests switch {
case 1 /* ERROR "cannot convert" */ :
}
switch int32(x) {
case 1, 2:
case x /* ERROR "cannot compare" */ :
}
switch x {
case 1 /* ERROR "overflows int" */ << 100:
}
switch x {
case 1:
case 1 /* ERROR "duplicate case" */ :
case 2, 3, 4:
case 1 /* ERROR "duplicate case" */ :
}
// TODO(gri) duplicate 64bit values that don't fit into an int64 are not yet detected
switch uint64(x) {
case 1<<64-1:
case 1<<64-1:
}
} }
type I interface { type I interface {

View File

@ -141,15 +141,15 @@ type Pointer struct {
Base Type Base Type
} }
// A tuple represents a multi-value function return. // A Result represents a (multi-value) function call result.
// TODO(gri) use better name to avoid confusion (Go doesn't have tuples). // TODO(gri) consider using an empty Result (Values == nil)
type tuple struct { // as representation for the novalue operand mode.
type Result struct {
implementsType implementsType
list []Type Values ObjList // Signature.Results of the function called
} }
// A Signature represents a user-defined function type func(...) (...). // A Signature represents a user-defined function type func(...) (...).
// TODO(gri) consider using "tuples" to represent parameters and results (see comment on tuples).
type Signature struct { type Signature struct {
implementsType implementsType
Recv *ast.Object // nil if not a method Recv *ast.Object // nil if not a method

View File

@ -155,7 +155,7 @@ var testExprs = []testEntry{
dup("-f(10, 20)"), dup("-f(10, 20)"),
dup("f(x + y, +3.1415)"), dup("f(x + y, +3.1415)"),
{"func(a, b int) {}", "(func literal)"}, {"func(a, b int) {}", "(func literal)"},
{"func(a, b int) []int {}()[x]", "(func literal)()[x]"}, {"func(a, b int) []int {}(1, 2)[x]", "(func literal)(1, 2)[x]"},
{"[]int{1, 2, 3}", "(composite literal)"}, {"[]int{1, 2, 3}", "(composite literal)"},
{"[]int{1, 2, 3}[x:]", "(composite literal)[x:]"}, {"[]int{1, 2, 3}[x:]", "(composite literal)[x:]"},
{"i.([]string)", "i.(...)"}, {"i.([]string)", "i.(...)"},

View File

@ -476,6 +476,11 @@ var fmttests = []struct {
// Used to crash because nByte didn't allow for a sign. // Used to crash because nByte didn't allow for a sign.
{"%b", int64(-1 << 63), "-1000000000000000000000000000000000000000000000000000000000000000"}, {"%b", int64(-1 << 63), "-1000000000000000000000000000000000000000000000000000000000000000"},
// Complex fmt used to leave the plus flag set for future entries in the array
// causing +2+0i and +3+0i instead of 2+0i and 3+0i.
{"%v", []complex64{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"},
{"%v", []complex128{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"},
} }
func TestSprintf(t *testing.T) { func TestSprintf(t *testing.T) {

View File

@ -396,7 +396,7 @@ func (f *fmt) fmt_f64(v float64) { f.formatFloat(v, 'f', doPrec(f, 6), 64) }
// fmt_g64 formats a float64 in the 'f' or 'e' form according to size. // fmt_g64 formats a float64 in the 'f' or 'e' form according to size.
func (f *fmt) fmt_g64(v float64) { f.formatFloat(v, 'g', doPrec(f, -1), 64) } func (f *fmt) fmt_g64(v float64) { f.formatFloat(v, 'g', doPrec(f, -1), 64) }
// fmt_g64 formats a float64 in the 'f' or 'E' form according to size. // fmt_G64 formats a float64 in the 'f' or 'E' form according to size.
func (f *fmt) fmt_G64(v float64) { f.formatFloat(v, 'G', doPrec(f, -1), 64) } func (f *fmt) fmt_G64(v float64) { f.formatFloat(v, 'G', doPrec(f, -1), 64) }
// fmt_fb64 formats a float64 in the form -123p3 (exponent is power of 2). // fmt_fb64 formats a float64 in the form -123p3 (exponent is power of 2).
@ -428,6 +428,7 @@ func (f *fmt) fmt_fb32(v float32) { f.formatFloat(float64(v), 'b', 0, 32) }
func (f *fmt) fmt_c64(v complex64, verb rune) { func (f *fmt) fmt_c64(v complex64, verb rune) {
f.buf.WriteByte('(') f.buf.WriteByte('(')
r := real(v) r := real(v)
oldPlus := f.plus
for i := 0; ; i++ { for i := 0; ; i++ {
switch verb { switch verb {
case 'e': case 'e':
@ -447,6 +448,7 @@ func (f *fmt) fmt_c64(v complex64, verb rune) {
f.plus = true f.plus = true
r = imag(v) r = imag(v)
} }
f.plus = oldPlus
f.buf.Write(irparenBytes) f.buf.Write(irparenBytes)
} }
@ -454,6 +456,7 @@ func (f *fmt) fmt_c64(v complex64, verb rune) {
func (f *fmt) fmt_c128(v complex128, verb rune) { func (f *fmt) fmt_c128(v complex128, verb rune) {
f.buf.WriteByte('(') f.buf.WriteByte('(')
r := real(v) r := real(v)
oldPlus := f.plus
for i := 0; ; i++ { for i := 0; ; i++ {
switch verb { switch verb {
case 'e': case 'e':
@ -473,5 +476,6 @@ func (f *fmt) fmt_c128(v complex128, verb rune) {
f.plus = true f.plus = true
r = imag(v) r = imag(v)
} }
f.plus = oldPlus
f.buf.Write(irparenBytes) f.buf.Write(irparenBytes)
} }

View File

@ -337,7 +337,10 @@ func (r *readRune) readByte() (b byte, err error) {
r.pending-- r.pending--
return return
} }
_, err = r.reader.Read(r.pendBuf[0:1]) n, err := io.ReadFull(r.reader, r.pendBuf[0:1])
if n != 1 {
return 0, err
}
return r.pendBuf[0], err return r.pendBuf[0], err
} }

View File

@ -555,7 +555,7 @@ type (
// A DeclStmt node represents a declaration in a statement list. // A DeclStmt node represents a declaration in a statement list.
DeclStmt struct { DeclStmt struct {
Decl Decl Decl Decl // *GenDecl with CONST, TYPE, or VAR token
} }
// An EmptyStmt node represents an empty statement. // An EmptyStmt node represents an empty statement.

View File

@ -222,6 +222,8 @@ var cgoEnabled = map[string]bool{
"linux/arm": true, "linux/arm": true,
"netbsd/386": true, "netbsd/386": true,
"netbsd/amd64": true, "netbsd/amd64": true,
"openbsd/386": true,
"openbsd/amd64": true,
"windows/386": true, "windows/386": true,
"windows/amd64": true, "windows/amd64": true,
} }
@ -424,6 +426,13 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
if strings.HasPrefix(path, "/") { if strings.HasPrefix(path, "/") {
return p, fmt.Errorf("import %q: cannot import absolute path", path) return p, fmt.Errorf("import %q: cannot import absolute path", path)
} }
// tried records the location of unsucsessful package lookups
var tried struct {
goroot string
gopath []string
}
// Determine directory from import path. // Determine directory from import path.
if ctxt.GOROOT != "" { if ctxt.GOROOT != "" {
dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", path) dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", path)
@ -435,6 +444,7 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
p.Root = ctxt.GOROOT p.Root = ctxt.GOROOT
goto Found goto Found
} }
tried.goroot = dir
} }
for _, root := range ctxt.gopath() { for _, root := range ctxt.gopath() {
dir := ctxt.joinPath(root, "src", path) dir := ctxt.joinPath(root, "src", path)
@ -445,8 +455,28 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
p.Root = root p.Root = root
goto Found goto Found
} }
tried.gopath = append(tried.gopath, dir)
} }
return p, fmt.Errorf("import %q: cannot find package", path)
// package was not found
var paths []string
if tried.goroot != "" {
paths = append(paths, fmt.Sprintf("\t%s (from $GOROOT)", tried.goroot))
} else {
paths = append(paths, "\t($GOROOT not set)")
}
var i int
var format = "\t%s (from $GOPATH)"
for ; i < len(tried.gopath); i++ {
if i > 0 {
format = "\t%s"
}
paths = append(paths, fmt.Sprintf(format, tried.gopath[i]))
}
if i == 0 {
paths = append(paths, "\t($GOPATH not set)")
}
return p, fmt.Errorf("cannot find package %q in any of:\n%s", path, strings.Join(paths, "\n"))
} }
Found: Found:

View File

@ -229,7 +229,8 @@ type block struct {
var nonAlphaNumRx = regexp.MustCompile(`[^a-zA-Z0-9]`) var nonAlphaNumRx = regexp.MustCompile(`[^a-zA-Z0-9]`)
func anchorID(line string) string { func anchorID(line string) string {
return nonAlphaNumRx.ReplaceAllString(line, "_") // Add a "hdr-" prefix to avoid conflicting with IDs used for package symbols.
return "hdr-" + nonAlphaNumRx.ReplaceAllString(line, "_")
} }
// ToHTML converts comment text to formatted HTML. // ToHTML converts comment text to formatted HTML.

View File

@ -119,8 +119,29 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
return nil return nil
} }
// Find unresolved identifiers // Find top-level declarations in the file.
topDecls := make(map[*ast.Object]bool)
for _, decl := range file.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
topDecls[d.Name.Obj] = true
case *ast.GenDecl:
for _, spec := range d.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
topDecls[s.Name.Obj] = true
case *ast.ValueSpec:
for _, id := range s.Names {
topDecls[id.Obj] = true
}
}
}
}
}
// Find unresolved identifiers and uses of top-level declarations.
unresolved := make(map[string]bool) unresolved := make(map[string]bool)
usesTopDecl := false
ast.Inspect(body, func(n ast.Node) bool { ast.Inspect(body, func(n ast.Node) bool {
// For an expression like fmt.Println, only add "fmt" to the // For an expression like fmt.Println, only add "fmt" to the
// set of unresolved names. // set of unresolved names.
@ -130,11 +151,19 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
} }
return false return false
} }
if id, ok := n.(*ast.Ident); ok && id.Obj == nil { if id, ok := n.(*ast.Ident); ok {
unresolved[id.Name] = true if id.Obj == nil {
unresolved[id.Name] = true
} else if topDecls[id.Obj] {
usesTopDecl = true
}
} }
return true return true
}) })
if usesTopDecl {
// We don't support examples that are not self-contained (yet).
return nil
}
// Remove predeclared identifiers from unresolved list. // Remove predeclared identifiers from unresolved list.
for n := range unresolved { for n := range unresolved {

View File

@ -46,7 +46,7 @@ func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
// Sort imports if necessary. // Sort imports if necessary.
if file != nil && hasUnsortedImports(file) { if file != nil && hasUnsortedImports(file) {
// Make a copy of the AST because ast.SortImports is destructive. // Make a copy of the AST because ast.SortImports is destructive.
// TODO(gri) Do this more efficently. // TODO(gri) Do this more efficiently.
var buf bytes.Buffer var buf bytes.Buffer
err := config.Fprint(&buf, fset, file) err := config.Fprint(&buf, fset, file)
if err != nil { if err != nil {

View File

@ -83,7 +83,7 @@ func (p *printer) setComment(g *ast.CommentGroup) {
// don't overwrite any pending comment in the p.comment cache // don't overwrite any pending comment in the p.comment cache
// (there may be a pending comment when a line comment is // (there may be a pending comment when a line comment is
// immediately followed by a lead comment with no other // immediately followed by a lead comment with no other
// tokens inbetween) // tokens between)
if p.commentOffset == infinity { if p.commentOffset == infinity {
p.nextComment() // get comment ready for use p.nextComment() // get comment ready for use
} }

View File

@ -295,9 +295,9 @@ type FileSet struct {
// NewFileSet creates a new file set. // NewFileSet creates a new file set.
func NewFileSet() *FileSet { func NewFileSet() *FileSet {
s := new(FileSet) return &FileSet{
s.base = 1 // 0 == NoPos base: 1, // 0 == NoPos
return s }
} }
// Base returns the minimum base offset that must be provided to // Base returns the minimum base offset that must be provided to
@ -367,8 +367,10 @@ func searchFiles(a []*File, x int) int {
} }
func (s *FileSet) file(p Pos) *File { func (s *FileSet) file(p Pos) *File {
s.mutex.RLock()
// common case: p is in last file // common case: p is in last file
if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size {
s.mutex.RUnlock()
return f return f
} }
// p is not in last file - search all files // p is not in last file - search all files
@ -376,10 +378,14 @@ func (s *FileSet) file(p Pos) *File {
f := s.files[i] f := s.files[i]
// f.base <= int(p) by definition of searchFiles // f.base <= int(p) by definition of searchFiles
if int(p) <= f.base+f.size { if int(p) <= f.base+f.size {
s.last = f s.mutex.RUnlock()
s.mutex.Lock()
s.last = f // race is ok - s.last is only a cache
s.mutex.Unlock()
return f return f
} }
} }
s.mutex.RUnlock()
return nil return nil
} }
@ -389,9 +395,7 @@ func (s *FileSet) file(p Pos) *File {
// //
func (s *FileSet) File(p Pos) (f *File) { func (s *FileSet) File(p Pos) (f *File) {
if p != NoPos { if p != NoPos {
s.mutex.RLock()
f = s.file(p) f = s.file(p)
s.mutex.RUnlock()
} }
return return
} }
@ -399,11 +403,9 @@ func (s *FileSet) File(p Pos) (f *File) {
// Position converts a Pos in the fileset into a general Position. // Position converts a Pos in the fileset into a general Position.
func (s *FileSet) Position(p Pos) (pos Position) { func (s *FileSet) Position(p Pos) (pos Position) {
if p != NoPos { if p != NoPos {
s.mutex.RLock()
if f := s.file(p); f != nil { if f := s.file(p); f != nil {
pos = f.position(p) pos = f.position(p)
} }
s.mutex.RUnlock()
} }
return return
} }

View File

@ -6,6 +6,8 @@ package token
import ( import (
"fmt" "fmt"
"math/rand"
"sync"
"testing" "testing"
) )
@ -179,3 +181,52 @@ func TestFiles(t *testing.T) {
} }
} }
} }
// FileSet.File should return nil if Pos is past the end of the FileSet.
func TestFileSetPastEnd(t *testing.T) {
fset := NewFileSet()
for _, test := range tests {
fset.AddFile(test.filename, fset.Base(), test.size)
}
if f := fset.File(Pos(fset.Base())); f != nil {
t.Errorf("expected nil, got %v", f)
}
}
func TestFileSetCacheUnlikely(t *testing.T) {
fset := NewFileSet()
offsets := make(map[string]int)
for _, test := range tests {
offsets[test.filename] = fset.Base()
fset.AddFile(test.filename, fset.Base(), test.size)
}
for file, pos := range offsets {
f := fset.File(Pos(pos))
if f.Name() != file {
t.Errorf("expecting %q at position %d, got %q", file, pos, f.Name())
}
}
}
// issue 4345. Test concurrent use of FileSet.Pos does not trigger a
// race in the FileSet position cache.
func TestFileSetRace(t *testing.T) {
fset := NewFileSet()
for i := 0; i < 100; i++ {
fset.AddFile(fmt.Sprintf("file-%d", i), fset.Base(), 1031)
}
max := int32(fset.Base())
var stop sync.WaitGroup
r := rand.New(rand.NewSource(7))
for i := 0; i < 2; i++ {
r := rand.New(rand.NewSource(r.Int63()))
stop.Add(1)
go func() {
for i := 0; i < 1000; i++ {
fset.Position(Pos(r.Int31n(max)))
}
stop.Done()
}()
}
stop.Wait()
}

View File

@ -468,6 +468,11 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) {
off += s.base off += s.base
if max := s.limit - off; int64(len(p)) > max { if max := s.limit - off; int64(len(p)) > max {
p = p[0:max] p = p[0:max]
n, err = s.r.ReadAt(p, off)
if err == nil {
err = EOF
}
return n, err
} }
return s.r.ReadAt(p, off) return s.r.ReadAt(p, off)
} }

View File

@ -203,3 +203,35 @@ func TestTeeReader(t *testing.T) {
t.Errorf("closed tee: ReadFull(r, dst) = %d, %v; want 0, EPIPE", n, err) t.Errorf("closed tee: ReadFull(r, dst) = %d, %v; want 0, EPIPE", n, err)
} }
} }
func TestSectionReader_ReadAt(tst *testing.T) {
dat := "a long sample data, 1234567890"
tests := []struct {
data string
off int
n int
bufLen int
at int
exp string
err error
}{
{data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: EOF},
{data: dat, off: 0, n: len(dat), bufLen: 0, at: 0, exp: "", err: nil},
{data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: EOF},
{data: dat, off: 0, n: len(dat) + 2, bufLen: len(dat), at: 0, exp: dat, err: nil},
{data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 0, exp: dat[:len(dat)/2], err: nil},
{data: dat, off: 0, n: len(dat), bufLen: len(dat), at: 0, exp: dat, err: nil},
{data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[2 : 2+len(dat)/2], err: nil},
{data: dat, off: 3, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[5 : 5+len(dat)/2], err: nil},
{data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 - 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: nil},
{data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: EOF},
}
for i, t := range tests {
r := strings.NewReader(t.data)
s := NewSectionReader(r, int64(t.off), int64(t.n))
buf := make([]byte, t.bufLen)
if n, err := s.ReadAt(buf, int64(t.at)); n != len(t.exp) || string(buf[:n]) != t.exp || err != t.err {
tst.Fatalf("%d: ReadAt(%d) = %q, %v; expected %q, %v", i, t.at, buf[:n], err, t.exp, t.err)
}
}
}

View File

@ -20,13 +20,14 @@ var serverAddr string
func runSyslog(c net.PacketConn, done chan<- string) { func runSyslog(c net.PacketConn, done chan<- string) {
var buf [4096]byte var buf [4096]byte
var rcvd string = "" var rcvd string
for { for {
n, _, err := c.ReadFrom(buf[0:]) c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
if err != nil || n == 0 { n, _, err := c.ReadFrom(buf[:])
rcvd += string(buf[:n])
if err != nil {
break break
} }
rcvd += string(buf[0:n])
} }
done <- rcvd done <- rcvd
} }
@ -37,7 +38,6 @@ func startServer(done chan<- string) {
log.Fatalf("net.ListenPacket failed udp :0 %v", e) log.Fatalf("net.ListenPacket failed udp :0 %v", e)
} }
serverAddr = c.LocalAddr().String() serverAddr = c.LocalAddr().String()
c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
go runSyslog(c, done) go runSyslog(c, done)
} }

View File

@ -2281,6 +2281,13 @@ func TestLog2(t *testing.T) {
t.Errorf("Log2(%g) = %g, want %g", vflogSC[i], f, logSC[i]) t.Errorf("Log2(%g) = %g, want %g", vflogSC[i], f, logSC[i])
} }
} }
for i := -1074; i <= 1023; i++ {
f := Ldexp(1, i)
l := Log2(f)
if l != float64(i) {
t.Errorf("Log2(2**%d) = %g, want %d", i, l, i)
}
}
} }
func TestModf(t *testing.T) { func TestModf(t *testing.T) {

View File

@ -51,6 +51,13 @@ func (z *Int) SetInt64(x int64) *Int {
return z return z
} }
// SetUint64 sets z to x and returns z.
func (z *Int) SetUint64(x uint64) *Int {
z.abs = z.abs.setUint64(uint64(x))
z.neg = false
return z
}
// NewInt allocates and returns a new Int set to x. // NewInt allocates and returns a new Int set to x.
func NewInt(x int64) *Int { func NewInt(x int64) *Int {
return new(Int).SetInt64(x) return new(Int).SetInt64(x)
@ -519,6 +526,19 @@ func (x *Int) Int64() int64 {
return v return v
} }
// Uint64 returns the int64 representation of x.
// If x cannot be represented in an uint64, the result is undefined.
func (x *Int) Uint64() uint64 {
if len(x.abs) == 0 {
return 0
}
v := uint64(x.abs[0])
if _W == 32 && len(x.abs) > 1 {
v |= uint64(x.abs[1]) << 32
}
return v
}
// SetString sets z to the value of s, interpreted in the given base, // SetString sets z to the value of s, interpreted in the given base,
// and returns z and a boolean indicating success. If SetString fails, // and returns z and a boolean indicating success. If SetString fails,
// the value of z is undefined but the returned value is nil. // the value of z is undefined but the returned value is nil.

View File

@ -1135,6 +1135,36 @@ func TestInt64(t *testing.T) {
} }
} }
var uint64Tests = []uint64{
0,
1,
4294967295,
4294967296,
8589934591,
8589934592,
9223372036854775807,
9223372036854775808,
18446744073709551615, // 1<<64 - 1
}
func TestUint64(t *testing.T) {
in := new(Int)
for i, testVal := range uint64Tests {
in.SetUint64(testVal)
out := in.Uint64()
if out != testVal {
t.Errorf("#%d got %d want %d", i, out, testVal)
}
str := fmt.Sprint(testVal)
strOut := in.String()
if strOut != str {
t.Errorf("#%d.String got %s want %s", i, strOut, str)
}
}
}
var bitwiseTests = []struct { var bitwiseTests = []struct {
x, y string x, y string
and, or, xor, andNot string and, or, xor, andNot string

View File

@ -826,7 +826,7 @@ func (x nat) string(charset string) string {
// Convert words of q to base b digits in s. If q is large, it is recursively "split in half" // Convert words of q to base b digits in s. If q is large, it is recursively "split in half"
// by nat/nat division using tabulated divisors. Otherwise, it is converted iteratively using // by nat/nat division using tabulated divisors. Otherwise, it is converted iteratively using
// repeated nat/Word divison. // repeated nat/Word division.
// //
// The iterative method processes n Words by n divW() calls, each of which visits every Word in the // The iterative method processes n Words by n divW() calls, each of which visits every Word in the
// incrementally shortened q for a total of n + (n-1) + (n-2) ... + 2 + 1, or n(n+1)/2 divW()'s. // incrementally shortened q for a total of n + (n-1) + (n-2) ... + 2 + 1, or n(n+1)/2 divW()'s.

View File

@ -26,5 +26,6 @@ func Log2(x float64) float64 {
} }
func log2(x float64) float64 { func log2(x float64) float64 {
return Log(x) * (1 / Ln2) frac, exp := Frexp(x)
return Log(frac)*(1/Ln2) + float64(exp)
} }

View File

@ -0,0 +1,14 @@
// 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
/*
#include <netdb.h>
*/
import "C"
func cgoAddrInfoFlags() C.int {
return C.AI_CANONNAME
}

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.
// +build darwin freebsd linux netbsd // +build darwin freebsd linux netbsd openbsd
package net package net

View File

@ -17,8 +17,8 @@ var connTests = []struct {
addr string addr string
}{ }{
{"tcp", "127.0.0.1:0"}, {"tcp", "127.0.0.1:0"},
{"unix", "/tmp/gotest.net"}, {"unix", "/tmp/gotest.net1"},
{"unixpacket", "/tmp/gotest.net"}, {"unixpacket", "/tmp/gotest.net2"},
} }
func TestConnAndListener(t *testing.T) { func TestConnAndListener(t *testing.T) {
@ -41,7 +41,13 @@ func TestConnAndListener(t *testing.T) {
return return
} }
ln.Addr() ln.Addr()
defer ln.Close() defer func(ln net.Listener, net, addr string) {
ln.Close()
switch net {
case "unix", "unixpacket":
os.Remove(addr)
}
}(ln, tt.net, tt.addr)
done := make(chan int) done := make(chan int)
go transponder(t, ln, done) go transponder(t, ln, done)
@ -68,10 +74,6 @@ func TestConnAndListener(t *testing.T) {
} }
<-done <-done
switch tt.net {
case "unix", "unixpacket":
os.Remove(tt.addr)
}
} }
} }

View File

@ -238,7 +238,7 @@ func ListenPacket(net, laddr string) (PacketConn, error) {
if a != nil { if a != nil {
la = a.(*UnixAddr) la = a.(*UnixAddr)
} }
return DialUnix(net, la, nil) return ListenUnixgram(net, la)
} }
return nil, UnknownNetworkError(net) return nil, UnknownNetworkError(net)
} }

View File

@ -240,7 +240,8 @@ func TestDialTimeoutFDLeak(t *testing.T) {
err error err error
} }
dials := listenerBacklog + 100 dials := listenerBacklog + 100
maxGoodConnect := listenerBacklog + 5 // empirically 131 good ones (of 128). who knows? // used to be listenerBacklog + 5, but was found to be unreliable, issue 4384.
maxGoodConnect := listenerBacklog + runtime.NumCPU()*10
resc := make(chan connErr) resc := make(chan connErr)
for i := 0; i < dials; i++ { for i := 0; i < dials; i++ {
go func() { go func() {

View File

@ -98,7 +98,9 @@ func (c *Client) send(req *Request) (*Response, error) {
return nil, err return nil, err
} }
if c.Jar != nil { if c.Jar != nil {
c.Jar.SetCookies(req.URL, resp.Cookies()) if rc := resp.Cookies(); len(rc) > 0 {
c.Jar.SetCookies(req.URL, rc)
}
} }
return resp, err return resp, err
} }
@ -120,7 +122,10 @@ func (c *Client) send(req *Request) (*Response, error) {
// Generally Get, Post, or PostForm will be used instead of Do. // Generally Get, Post, or PostForm will be used instead of Do.
func (c *Client) Do(req *Request) (resp *Response, err error) { func (c *Client) Do(req *Request) (resp *Response, err error) {
if req.Method == "GET" || req.Method == "HEAD" { if req.Method == "GET" || req.Method == "HEAD" {
return c.doFollowingRedirects(req) return c.doFollowingRedirects(req, shouldRedirectGet)
}
if req.Method == "POST" || req.Method == "PUT" {
return c.doFollowingRedirects(req, shouldRedirectPost)
} }
return c.send(req) return c.send(req)
} }
@ -166,7 +171,7 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) {
// True if the specified HTTP status code is one for which the Get utility should // True if the specified HTTP status code is one for which the Get utility should
// automatically redirect. // automatically redirect.
func shouldRedirect(statusCode int) bool { func shouldRedirectGet(statusCode int) bool {
switch statusCode { switch statusCode {
case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:
return true return true
@ -174,6 +179,16 @@ func shouldRedirect(statusCode int) bool {
return false return false
} }
// True if the specified HTTP status code is one for which the Post utility should
// automatically redirect.
func shouldRedirectPost(statusCode int) bool {
switch statusCode {
case StatusFound, StatusSeeOther:
return true
}
return false
}
// Get issues a GET to the specified URL. If the response is one of the following // Get issues a GET to the specified URL. If the response is one of the following
// redirect codes, Get follows the redirect, up to a maximum of 10 redirects: // redirect codes, Get follows the redirect, up to a maximum of 10 redirects:
// //
@ -214,12 +229,10 @@ func (c *Client) Get(url string) (resp *Response, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.doFollowingRedirects(req) return c.doFollowingRedirects(req, shouldRedirectGet)
} }
func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error) { func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bool) (resp *Response, err error) {
// TODO: if/when we add cookie support, the redirected request shouldn't
// necessarily supply the same cookies as the original.
var base *url.URL var base *url.URL
redirectChecker := c.CheckRedirect redirectChecker := c.CheckRedirect
if redirectChecker == nil { if redirectChecker == nil {
@ -238,6 +251,9 @@ func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error)
if redirect != 0 { if redirect != 0 {
req = new(Request) req = new(Request)
req.Method = ireq.Method req.Method = ireq.Method
if ireq.Method == "POST" || ireq.Method == "PUT" {
req.Method = "GET"
}
req.Header = make(Header) req.Header = make(Header)
req.URL, err = base.Parse(urlStr) req.URL, err = base.Parse(urlStr)
if err != nil { if err != nil {
@ -321,7 +337,7 @@ func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Respon
return nil, err return nil, err
} }
req.Header.Set("Content-Type", bodyType) req.Header.Set("Content-Type", bodyType)
return c.send(req) return c.doFollowingRedirects(req, shouldRedirectPost)
} }
// PostForm issues a POST to the specified URL, with data's keys and // PostForm issues a POST to the specified URL, with data's keys and
@ -371,5 +387,5 @@ func (c *Client) Head(url string) (resp *Response, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.doFollowingRedirects(req) return c.doFollowingRedirects(req, shouldRedirectGet)
} }

View File

@ -7,6 +7,7 @@
package http_test package http_test
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"errors" "errors"
@ -246,6 +247,52 @@ func TestRedirects(t *testing.T) {
} }
} }
func TestPostRedirects(t *testing.T) {
var log struct {
sync.Mutex
bytes.Buffer
}
var ts *httptest.Server
ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
log.Lock()
fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI)
log.Unlock()
if v := r.URL.Query().Get("code"); v != "" {
code, _ := strconv.Atoi(v)
if code/100 == 3 {
w.Header().Set("Location", ts.URL)
}
w.WriteHeader(code)
}
}))
tests := []struct {
suffix string
want int // response code
}{
{"/", 200},
{"/?code=301", 301},
{"/?code=302", 200},
{"/?code=303", 200},
{"/?code=404", 404},
}
for _, tt := range tests {
res, err := Post(ts.URL+tt.suffix, "text/plain", strings.NewReader("Some content"))
if err != nil {
t.Fatal(err)
}
if res.StatusCode != tt.want {
t.Errorf("POST %s: status code = %d; want %d", tt.suffix, res.StatusCode, tt.want)
}
}
log.Lock()
got := log.String()
log.Unlock()
want := "POST / POST /?code=301 POST /?code=302 GET / POST /?code=303 GET / POST /?code=404 "
if got != want {
t.Errorf("Log differs.\n Got: %q\nWant: %q", got, want)
}
}
var expectedCookies = []*Cookie{ var expectedCookies = []*Cookie{
{Name: "ChocolateChip", Value: "tasty"}, {Name: "ChocolateChip", Value: "tasty"},
{Name: "First", Value: "Hit"}, {Name: "First", Value: "Hit"},
@ -304,6 +351,9 @@ type TestJar struct {
func (j *TestJar) SetCookies(u *url.URL, cookies []*Cookie) { func (j *TestJar) SetCookies(u *url.URL, cookies []*Cookie) {
j.m.Lock() j.m.Lock()
defer j.m.Unlock() defer j.m.Unlock()
if j.perURL == nil {
j.perURL = make(map[string][]*Cookie)
}
j.perURL[u.Host] = cookies j.perURL[u.Host] = cookies
} }
@ -334,8 +384,9 @@ func TestRedirectCookiesJar(t *testing.T) {
var ts *httptest.Server var ts *httptest.Server
ts = httptest.NewServer(echoCookiesRedirectHandler) ts = httptest.NewServer(echoCookiesRedirectHandler)
defer ts.Close() defer ts.Close()
c := &Client{} c := &Client{
c.Jar = &TestJar{perURL: make(map[string][]*Cookie)} Jar: new(TestJar),
}
u, _ := url.Parse(ts.URL) u, _ := url.Parse(ts.URL)
c.Jar.SetCookies(u, []*Cookie{expectedCookies[0]}) c.Jar.SetCookies(u, []*Cookie{expectedCookies[0]})
resp, err := c.Get(ts.URL) resp, err := c.Get(ts.URL)
@ -364,6 +415,69 @@ func matchReturnedCookies(t *testing.T, expected, given []*Cookie) {
} }
} }
func TestJarCalls(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
pathSuffix := r.RequestURI[1:]
if r.RequestURI == "/nosetcookie" {
return // dont set cookies for this path
}
SetCookie(w, &Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix})
if r.RequestURI == "/" {
Redirect(w, r, "http://secondhost.fake/secondpath", 302)
}
}))
defer ts.Close()
jar := new(RecordingJar)
c := &Client{
Jar: jar,
Transport: &Transport{
Dial: func(_ string, _ string) (net.Conn, error) {
return net.Dial("tcp", ts.Listener.Addr().String())
},
},
}
_, err := c.Get("http://firsthost.fake/")
if err != nil {
t.Fatal(err)
}
_, err = c.Get("http://firsthost.fake/nosetcookie")
if err != nil {
t.Fatal(err)
}
got := jar.log.String()
want := `Cookies("http://firsthost.fake/")
SetCookie("http://firsthost.fake/", [name=val])
Cookies("http://secondhost.fake/secondpath")
SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
Cookies("http://firsthost.fake/nosetcookie")
`
if got != want {
t.Errorf("Got Jar calls:\n%s\nWant:\n%s", got, want)
}
}
// RecordingJar keeps a log of calls made to it, without
// tracking any cookies.
type RecordingJar struct {
mu sync.Mutex
log bytes.Buffer
}
func (j *RecordingJar) SetCookies(u *url.URL, cookies []*Cookie) {
j.logf("SetCookie(%q, %v)\n", u, cookies)
}
func (j *RecordingJar) Cookies(u *url.URL) []*Cookie {
j.logf("Cookies(%q)\n", u)
return nil
}
func (j *RecordingJar) logf(format string, args ...interface{}) {
j.mu.Lock()
defer j.mu.Unlock()
fmt.Fprintf(&j.log, format, args...)
}
func TestStreamingGet(t *testing.T) { func TestStreamingGet(t *testing.T) {
say := make(chan string) say := make(chan string)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {

View File

@ -34,9 +34,8 @@
// //
// go tool pprof http://localhost:6060/debug/pprof/block // go tool pprof http://localhost:6060/debug/pprof/block
// //
// Or to view all available profiles: // To view all available profiles, open http://localhost:6060/debug/pprof/
// // in your browser.
// go tool pprof http://localhost:6060/debug/pprof/
// //
// For a study of the facility in action, visit // For a study of the facility in action, visit
// //

View File

@ -247,6 +247,54 @@ var reqTests = []reqTest{
noTrailer, noTrailer,
noError, noError,
}, },
// SSDP Notify request. golang.org/issue/3692
{
"NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n",
&Request{
Method: "NOTIFY",
URL: &url.URL{
Path: "*",
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: Header{
"Server": []string{"foo"},
},
Close: false,
ContentLength: 0,
RequestURI: "*",
},
noBody,
noTrailer,
noError,
},
// OPTIONS request. Similar to golang.org/issue/3692
{
"OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n",
&Request{
Method: "OPTIONS",
URL: &url.URL{
Path: "*",
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: Header{
"Server": []string{"foo"},
},
Close: false,
ContentLength: 0,
RequestURI: "*",
},
noBody,
noTrailer,
noError,
},
} }
func TestReadRequest(t *testing.T) { func TestReadRequest(t *testing.T) {

View File

@ -918,15 +918,19 @@ func TestZeroLengthPostAndResponse(t *testing.T) {
} }
} }
func TestHandlerPanicNil(t *testing.T) {
testHandlerPanic(t, false, nil)
}
func TestHandlerPanic(t *testing.T) { func TestHandlerPanic(t *testing.T) {
testHandlerPanic(t, false) testHandlerPanic(t, false, "intentional death for testing")
} }
func TestHandlerPanicWithHijack(t *testing.T) { func TestHandlerPanicWithHijack(t *testing.T) {
testHandlerPanic(t, true) testHandlerPanic(t, true, "intentional death for testing")
} }
func testHandlerPanic(t *testing.T, withHijack bool) { func testHandlerPanic(t *testing.T, withHijack bool, panicValue interface{}) {
// Unlike the other tests that set the log output to ioutil.Discard // Unlike the other tests that set the log output to ioutil.Discard
// to quiet the output, this test uses a pipe. The pipe serves three // to quiet the output, this test uses a pipe. The pipe serves three
// purposes: // purposes:
@ -955,7 +959,7 @@ func testHandlerPanic(t *testing.T, withHijack bool) {
} }
defer rwc.Close() defer rwc.Close()
} }
panic("intentional death for testing") panic(panicValue)
})) }))
defer ts.Close() defer ts.Close()
@ -968,7 +972,7 @@ func testHandlerPanic(t *testing.T, withHijack bool) {
_, err := pr.Read(buf) _, err := pr.Read(buf)
pr.Close() pr.Close()
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
} }
done <- true done <- true
}() }()
@ -978,6 +982,10 @@ func testHandlerPanic(t *testing.T, withHijack bool) {
t.Logf("expected an error") t.Logf("expected an error")
} }
if panicValue == nil {
return
}
select { select {
case <-done: case <-done:
return return
@ -1288,6 +1296,58 @@ For:
ts.Close() ts.Close()
} }
func TestOptions(t *testing.T) {
uric := make(chan string, 2) // only expect 1, but leave space for 2
mux := NewServeMux()
mux.HandleFunc("/", func(w ResponseWriter, r *Request) {
uric <- r.RequestURI
})
ts := httptest.NewServer(mux)
defer ts.Close()
conn, err := net.Dial("tcp", ts.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
// An OPTIONS * request should succeed.
_, err = conn.Write([]byte("OPTIONS * HTTP/1.1\r\nHost: foo.com\r\n\r\n"))
if err != nil {
t.Fatal(err)
}
br := bufio.NewReader(conn)
res, err := ReadResponse(br, &Request{Method: "OPTIONS"})
if err != nil {
t.Fatal(err)
}
if res.StatusCode != 200 {
t.Errorf("Got non-200 response to OPTIONS *: %#v", res)
}
// A GET * request on a ServeMux should fail.
_, err = conn.Write([]byte("GET * HTTP/1.1\r\nHost: foo.com\r\n\r\n"))
if err != nil {
t.Fatal(err)
}
res, err = ReadResponse(br, &Request{Method: "GET"})
if err != nil {
t.Fatal(err)
}
if res.StatusCode != 400 {
t.Errorf("Got non-400 response to GET *: %#v", res)
}
res, err = Get(ts.URL + "/second")
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if got := <-uric; got != "/second" {
t.Errorf("Handler saw request for %q; want /second", got)
}
}
// goTimeout runs f, failing t if f takes more than ns to complete. // goTimeout runs f, failing t if f takes more than ns to complete.
func goTimeout(t *testing.T, d time.Duration, f func()) { func goTimeout(t *testing.T, d time.Duration, f func()) {
ch := make(chan bool, 2) ch := make(chan bool, 2)

View File

@ -702,24 +702,19 @@ func (c *conn) closeWriteAndWait() {
// Serve a new connection. // Serve a new connection.
func (c *conn) serve() { func (c *conn) serve() {
defer func() { defer func() {
err := recover() if err := recover(); err != nil {
if err == nil { const size = 4096
return buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
} }
if !c.hijacked() {
const size = 4096 c.close()
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
if c.rwc != nil { // may be nil if connection hijacked
c.rwc.Close()
} }
}() }()
if tlsConn, ok := c.rwc.(*tls.Conn); ok { if tlsConn, ok := c.rwc.(*tls.Conn); ok {
if err := tlsConn.Handshake(); err != nil { if err := tlsConn.Handshake(); err != nil {
c.close()
return return
} }
c.tlsState = new(tls.ConnectionState) c.tlsState = new(tls.ConnectionState)
@ -770,6 +765,9 @@ func (c *conn) serve() {
if handler == nil { if handler == nil {
handler = DefaultServeMux handler = DefaultServeMux
} }
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// HTTP cannot have multiple simultaneous active requests.[*] // HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another, // Until the server replies to this request, it can't read another,
@ -788,7 +786,6 @@ func (c *conn) serve() {
break break
} }
} }
c.close()
} }
func (w *response) sendExpectationFailed() { func (w *response) sendExpectationFailed() {
@ -1085,6 +1082,11 @@ func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
// ServeHTTP dispatches the request to the handler whose // ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL. // pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
w.Header().Set("Connection", "close")
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r) h, _ := mux.Handler(r)
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
} }
@ -1408,6 +1410,22 @@ func (tw *timeoutWriter) WriteHeader(code int) {
tw.w.WriteHeader(code) tw.w.WriteHeader(code)
} }
// globalOptionsHandler responds to "OPTIONS *" requests.
type globalOptionsHandler struct{}
func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "0")
if r.ContentLength != 0 {
// Read up to 4KB of OPTIONS body (as mentioned in the
// spec as being reserved for future use), but anything
// over that is considered a waste of server resources
// (or an attack) and we abort and close the connection,
// courtesy of MaxBytesReader's EOF behavior.
mb := MaxBytesReader(w, r.Body, 4<<10)
io.Copy(ioutil.Discard, mb)
}
}
// loggingConn is used for debugging. // loggingConn is used for debugging.
type loggingConn struct { type loggingConn struct {
name string name string

View File

@ -144,6 +144,9 @@ func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
} }
return rt.RoundTrip(req) return rt.RoundTrip(req)
} }
if req.URL.Host == "" {
return nil, errors.New("http: no Host in request URL")
}
treq := &transportRequest{Request: req} treq := &transportRequest{Request: req}
cm, err := t.connectMethodForRequest(treq) cm, err := t.connectMethodForRequest(treq)
if err != nil { if err != nil {
@ -739,6 +742,7 @@ WaitResponse:
case err := <-writeErrCh: case err := <-writeErrCh:
if err != nil { if err != nil {
re = responseAndError{nil, err} re = responseAndError{nil, err}
pc.close()
break WaitResponse break WaitResponse
} }
case <-pconnDeadCh: case <-pconnDeadCh:

View File

@ -778,6 +778,45 @@ func TestTransportPersistConnLeak(t *testing.T) {
} }
} }
// golang.org/issue/4531: Transport leaks goroutines when
// request.ContentLength is explicitly short
func TestTransportPersistConnLeakShortBody(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
}))
defer ts.Close()
tr := &Transport{}
c := &Client{Transport: tr}
n0 := runtime.NumGoroutine()
body := []byte("Hello")
for i := 0; i < 20; i++ {
req, err := NewRequest("POST", ts.URL, bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
req.ContentLength = int64(len(body) - 2) // explicitly short
_, err = c.Do(req)
if err == nil {
t.Fatal("Expect an error from writing too long of a body.")
}
}
nhigh := runtime.NumGoroutine()
tr.CloseIdleConnections()
time.Sleep(50 * time.Millisecond)
runtime.GC()
nfinal := runtime.NumGoroutine()
growth := nfinal - n0
// We expect 0 or 1 extra goroutine, empirically. Allow up to 5.
// Previously we were leaking one per numReq.
t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth)
if int(growth) > 5 {
t.Error("too many new goroutines")
}
}
// This used to crash; http://golang.org/issue/3266 // This used to crash; http://golang.org/issue/3266
func TestTransportIdleConnCrash(t *testing.T) { func TestTransportIdleConnCrash(t *testing.T) {
tr := &Transport{} tr := &Transport{}
@ -1062,6 +1101,20 @@ func TestTransportAltProto(t *testing.T) {
} }
} }
func TestTransportNoHost(t *testing.T) {
tr := &Transport{}
_, err := tr.RoundTrip(&Request{
Header: make(Header),
URL: &url.URL{
Scheme: "http",
},
})
want := "http: no Host in request URL"
if got := fmt.Sprint(err); got != want {
t.Errorf("error = %v; want %q", err, want)
}
}
var proxyFromEnvTests = []struct { var proxyFromEnvTests = []struct {
env string env string
wanturl string wanturl string

View File

@ -24,6 +24,15 @@ var packetConnTests = []struct {
} }
func TestPacketConn(t *testing.T) { func TestPacketConn(t *testing.T) {
closer := func(c net.PacketConn, net, addr1, addr2 string) {
c.Close()
switch net {
case "unixgram":
os.Remove(addr1)
os.Remove(addr2)
}
}
for _, tt := range packetConnTests { for _, tt := range packetConnTests {
var wb []byte var wb []byte
netstr := strings.Split(tt.net, ":") netstr := strings.Split(tt.net, ":")
@ -39,7 +48,7 @@ func TestPacketConn(t *testing.T) {
continue continue
} }
id := os.Getpid() & 0xffff id := os.Getpid() & 0xffff
wb = newICMPEchoRequest(id, 1, 128, []byte("IP PACKETCONN TEST ")) wb = newICMPEchoRequest(id, 1, 128, []byte("IP PACKETCONN TEST"))
case "unixgram": case "unixgram":
switch runtime.GOOS { switch runtime.GOOS {
case "plan9", "windows": case "plan9", "windows":
@ -60,7 +69,7 @@ func TestPacketConn(t *testing.T) {
c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) c1.SetDeadline(time.Now().Add(100 * time.Millisecond))
c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
defer c1.Close() defer closer(c1, netstr[0], tt.addr1, tt.addr2)
c2, err := net.ListenPacket(tt.net, tt.addr2) c2, err := net.ListenPacket(tt.net, tt.addr2)
if err != nil { if err != nil {
@ -70,7 +79,7 @@ func TestPacketConn(t *testing.T) {
c2.SetDeadline(time.Now().Add(100 * time.Millisecond)) c2.SetDeadline(time.Now().Add(100 * time.Millisecond))
c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
defer c2.Close() defer closer(c2, netstr[0], tt.addr1, tt.addr2)
if _, err := c1.WriteTo(wb, c2.LocalAddr()); err != nil { if _, err := c1.WriteTo(wb, c2.LocalAddr()); err != nil {
t.Fatalf("net.PacketConn.WriteTo failed: %v", err) t.Fatalf("net.PacketConn.WriteTo failed: %v", err)
@ -86,12 +95,6 @@ func TestPacketConn(t *testing.T) {
if _, _, err := c1.ReadFrom(rb1); err != nil { if _, _, err := c1.ReadFrom(rb1); err != nil {
t.Fatalf("net.PacketConn.ReadFrom failed: %v", err) t.Fatalf("net.PacketConn.ReadFrom failed: %v", err)
} }
switch netstr[0] {
case "unixgram":
os.Remove(tt.addr1)
os.Remove(tt.addr2)
}
} }
} }

View File

@ -263,9 +263,10 @@ func TestUnixConnSpecificMethods(t *testing.T) {
return return
} }
p1, p2 := "/tmp/gotest.net1", "/tmp/gotest.net2" p1, p2, p3 := "/tmp/gotest.net1", "/tmp/gotest.net2", "/tmp/gotest.net3"
os.Remove(p1) os.Remove(p1)
os.Remove(p2) os.Remove(p2)
os.Remove(p3)
a1, err := net.ResolveUnixAddr("unixgram", p1) a1, err := net.ResolveUnixAddr("unixgram", p1)
if err != nil { if err != nil {
@ -305,9 +306,30 @@ func TestUnixConnSpecificMethods(t *testing.T) {
defer c2.Close() defer c2.Close()
defer os.Remove(p2) defer os.Remove(p2)
a3, err := net.ResolveUnixAddr("unixgram", p3)
if err != nil {
t.Errorf("net.ResolveUnixAddr failed: %v", err)
return
}
c3, err := net.ListenUnixgram("unixgram", a3)
if err != nil {
t.Errorf("net.ListenUnixgram failed: %v", err)
return
}
c3.LocalAddr()
c3.RemoteAddr()
c3.SetDeadline(time.Now().Add(100 * time.Millisecond))
c3.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
c3.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
c3.SetReadBuffer(2048)
c3.SetWriteBuffer(2048)
defer c3.Close()
defer os.Remove(p3)
wb := []byte("UNIXCONN TEST") wb := []byte("UNIXCONN TEST")
rb1 := make([]byte, 128) rb1 := make([]byte, 128)
rb2 := make([]byte, 128) rb2 := make([]byte, 128)
rb3 := make([]byte, 128)
if _, _, err := c1.WriteMsgUnix(wb, nil, a2); err != nil { if _, _, err := c1.WriteMsgUnix(wb, nil, a2); err != nil {
t.Errorf("net.UnixConn.WriteMsgUnix failed: %v", err) t.Errorf("net.UnixConn.WriteMsgUnix failed: %v", err)
return return
@ -324,9 +346,22 @@ func TestUnixConnSpecificMethods(t *testing.T) {
t.Errorf("net.UnixConn.ReadFromUnix failed: %v", err) t.Errorf("net.UnixConn.ReadFromUnix failed: %v", err)
return return
} }
if _, err := c3.WriteToUnix(wb, a1); err != nil {
// TODO: http://golang.org/issue/3875 t.Errorf("net.UnixConn.WriteToUnix failed: %v", err)
net.ListenUnixgram("unixgram", nil) return
}
if _, _, err := c1.ReadFromUnix(rb1); err != nil {
t.Errorf("net.UnixConn.ReadFromUnix failed: %v", err)
return
}
if _, err := c2.WriteToUnix(wb, a3); err != nil {
t.Errorf("net.UnixConn.WriteToUnix failed: %v", err)
return
}
if _, _, err := c3.ReadFromUnix(rb3); err != nil {
t.Errorf("net.UnixConn.ReadFromUnix failed: %v", err)
return
}
if f, err := c1.File(); err != nil { if f, err := c1.File(); err != nil {
t.Errorf("net.UnixConn.File failed: %v", err) t.Errorf("net.UnixConn.File failed: %v", err)

View File

@ -13,6 +13,7 @@ package smtp
import ( import (
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"errors"
"io" "io"
"net" "net"
"net/textproto" "net/textproto"
@ -33,7 +34,10 @@ type Client struct {
// map of supported extensions // map of supported extensions
ext map[string]string ext map[string]string
// supported auth mechanisms // supported auth mechanisms
auth []string auth []string
localName string // the name to use in HELO/EHLO
didHello bool // whether we've said HELO/EHLO
helloError error // the error from the hello
} }
// Dial returns a new Client connected to an SMTP server at addr. // Dial returns a new Client connected to an SMTP server at addr.
@ -55,12 +59,33 @@ func NewClient(conn net.Conn, host string) (*Client, error) {
text.Close() text.Close()
return nil, err return nil, err
} }
c := &Client{Text: text, conn: conn, serverName: host} c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
err = c.ehlo() return c, nil
if err != nil { }
err = c.helo()
// hello runs a hello exchange if needed.
func (c *Client) hello() error {
if !c.didHello {
c.didHello = true
err := c.ehlo()
if err != nil {
c.helloError = c.helo()
}
} }
return c, err return c.helloError
}
// Hello sends a HELO or EHLO to the server as the given host name.
// Calling this method is only necessary if the client needs control
// over the host name used. The client will introduce itself as "localhost"
// automatically otherwise. If Hello is called, it must be called before
// any of the other methods.
func (c *Client) Hello(localName string) error {
if c.didHello {
return errors.New("smtp: Hello called after other methods")
}
c.localName = localName
return c.hello()
} }
// cmd is a convenience function that sends a command and returns the response // cmd is a convenience function that sends a command and returns the response
@ -79,14 +104,14 @@ func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, s
// server does not support ehlo. // server does not support ehlo.
func (c *Client) helo() error { func (c *Client) helo() error {
c.ext = nil c.ext = nil
_, _, err := c.cmd(250, "HELO localhost") _, _, err := c.cmd(250, "HELO %s", c.localName)
return err return err
} }
// ehlo sends the EHLO (extended hello) greeting to the server. It // ehlo sends the EHLO (extended hello) greeting to the server. It
// should be the preferred greeting for servers that support it. // should be the preferred greeting for servers that support it.
func (c *Client) ehlo() error { func (c *Client) ehlo() error {
_, msg, err := c.cmd(250, "EHLO localhost") _, msg, err := c.cmd(250, "EHLO %s", c.localName)
if err != nil { if err != nil {
return err return err
} }
@ -113,6 +138,9 @@ func (c *Client) ehlo() error {
// StartTLS sends the STARTTLS command and encrypts all further communication. // StartTLS sends the STARTTLS command and encrypts all further communication.
// Only servers that advertise the STARTTLS extension support this function. // Only servers that advertise the STARTTLS extension support this function.
func (c *Client) StartTLS(config *tls.Config) error { func (c *Client) StartTLS(config *tls.Config) error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(220, "STARTTLS") _, _, err := c.cmd(220, "STARTTLS")
if err != nil { if err != nil {
return err return err
@ -128,6 +156,9 @@ func (c *Client) StartTLS(config *tls.Config) error {
// does not necessarily indicate an invalid address. Many servers // does not necessarily indicate an invalid address. Many servers
// will not verify addresses for security reasons. // will not verify addresses for security reasons.
func (c *Client) Verify(addr string) error { func (c *Client) Verify(addr string) error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "VRFY %s", addr) _, _, err := c.cmd(250, "VRFY %s", addr)
return err return err
} }
@ -136,6 +167,9 @@ func (c *Client) Verify(addr string) error {
// A failed authentication closes the connection. // A failed authentication closes the connection.
// Only servers that advertise the AUTH extension support this function. // Only servers that advertise the AUTH extension support this function.
func (c *Client) Auth(a Auth) error { func (c *Client) Auth(a Auth) error {
if err := c.hello(); err != nil {
return err
}
encoding := base64.StdEncoding encoding := base64.StdEncoding
mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth}) mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
if err != nil { if err != nil {
@ -178,6 +212,9 @@ func (c *Client) Auth(a Auth) error {
// parameter. // parameter.
// This initiates a mail transaction and is followed by one or more Rcpt calls. // This initiates a mail transaction and is followed by one or more Rcpt calls.
func (c *Client) Mail(from string) error { func (c *Client) Mail(from string) error {
if err := c.hello(); err != nil {
return err
}
cmdStr := "MAIL FROM:<%s>" cmdStr := "MAIL FROM:<%s>"
if c.ext != nil { if c.ext != nil {
if _, ok := c.ext["8BITMIME"]; ok { if _, ok := c.ext["8BITMIME"]; ok {
@ -227,6 +264,9 @@ func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
if err != nil { if err != nil {
return err return err
} }
if err := c.hello(); err != nil {
return err
}
if ok, _ := c.Extension("STARTTLS"); ok { if ok, _ := c.Extension("STARTTLS"); ok {
if err = c.StartTLS(nil); err != nil { if err = c.StartTLS(nil); err != nil {
return err return err
@ -267,6 +307,9 @@ func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
// Extension also returns a string that contains any parameters the // Extension also returns a string that contains any parameters the
// server specifies for the extension. // server specifies for the extension.
func (c *Client) Extension(ext string) (bool, string) { func (c *Client) Extension(ext string) (bool, string) {
if err := c.hello(); err != nil {
return false, ""
}
if c.ext == nil { if c.ext == nil {
return false, "" return false, ""
} }
@ -278,12 +321,18 @@ func (c *Client) Extension(ext string) (bool, string) {
// Reset sends the RSET command to the server, aborting the current mail // Reset sends the RSET command to the server, aborting the current mail
// transaction. // transaction.
func (c *Client) Reset() error { func (c *Client) Reset() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "RSET") _, _, err := c.cmd(250, "RSET")
return err return err
} }
// Quit sends the QUIT command and closes the connection to the server. // Quit sends the QUIT command and closes the connection to the server.
func (c *Client) Quit() error { func (c *Client) Quit() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(221, "QUIT") _, _, err := c.cmd(221, "QUIT")
if err != nil { if err != nil {
return err return err

View File

@ -69,14 +69,14 @@ func (f faker) SetReadDeadline(time.Time) error { return nil }
func (f faker) SetWriteDeadline(time.Time) error { return nil } func (f faker) SetWriteDeadline(time.Time) error { return nil }
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
basicServer = strings.Join(strings.Split(basicServer, "\n"), "\r\n") server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
basicClient = strings.Join(strings.Split(basicClient, "\n"), "\r\n") client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
var cmdbuf bytes.Buffer var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf) bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(basicServer)), bcmdbuf) fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c := &Client{Text: textproto.NewConn(fake)} c := &Client{Text: textproto.NewConn(fake), localName: "localhost"}
if err := c.helo(); err != nil { if err := c.helo(); err != nil {
t.Fatalf("HELO failed: %s", err) t.Fatalf("HELO failed: %s", err)
@ -88,6 +88,7 @@ func TestBasic(t *testing.T) {
t.Fatalf("Second EHLO failed: %s", err) t.Fatalf("Second EHLO failed: %s", err)
} }
c.didHello = true
if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
t.Fatalf("Expected AUTH supported") t.Fatalf("Expected AUTH supported")
} }
@ -143,8 +144,8 @@ Goodbye.`
bcmdbuf.Flush() bcmdbuf.Flush()
actualcmds := cmdbuf.String() actualcmds := cmdbuf.String()
if basicClient != actualcmds { if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, basicClient) t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
} }
} }
@ -187,8 +188,8 @@ QUIT
` `
func TestNewClient(t *testing.T) { func TestNewClient(t *testing.T) {
newClientServer = strings.Join(strings.Split(newClientServer, "\n"), "\r\n") server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
newClientClient = strings.Join(strings.Split(newClientClient, "\n"), "\r\n") client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
var cmdbuf bytes.Buffer var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf) bcmdbuf := bufio.NewWriter(&cmdbuf)
@ -197,7 +198,7 @@ func TestNewClient(t *testing.T) {
return cmdbuf.String() return cmdbuf.String()
} }
var fake faker var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(newClientServer)), bcmdbuf) fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := NewClient(fake, "fake.host") c, err := NewClient(fake, "fake.host")
if err != nil { if err != nil {
t.Fatalf("NewClient: %v\n(after %v)", err, out()) t.Fatalf("NewClient: %v\n(after %v)", err, out())
@ -213,8 +214,8 @@ func TestNewClient(t *testing.T) {
} }
actualcmds := out() actualcmds := out()
if newClientClient != actualcmds { if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, newClientClient) t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
} }
} }
@ -231,13 +232,13 @@ QUIT
` `
func TestNewClient2(t *testing.T) { func TestNewClient2(t *testing.T) {
newClient2Server = strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
newClient2Client = strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
var cmdbuf bytes.Buffer var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf) bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(newClient2Server)), bcmdbuf) fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := NewClient(fake, "fake.host") c, err := NewClient(fake, "fake.host")
if err != nil { if err != nil {
t.Fatalf("NewClient: %v", err) t.Fatalf("NewClient: %v", err)
@ -251,8 +252,8 @@ func TestNewClient2(t *testing.T) {
bcmdbuf.Flush() bcmdbuf.Flush()
actualcmds := cmdbuf.String() actualcmds := cmdbuf.String()
if newClient2Client != actualcmds { if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, newClient2Client) t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
} }
} }
@ -269,3 +270,199 @@ var newClient2Client = `EHLO localhost
HELO localhost HELO localhost
QUIT QUIT
` `
func TestHello(t *testing.T) {
if len(helloServer) != len(helloClient) {
t.Fatalf("Hello server and client size mismatch")
}
for i := 0; i < len(helloServer); i++ {
server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := NewClient(fake, "fake.host")
if err != nil {
t.Fatalf("NewClient: %v", err)
}
c.localName = "customhost"
err = nil
switch i {
case 0:
err = c.Hello("customhost")
case 1:
err = c.StartTLS(nil)
if err.Error() == "502 Not implemented" {
err = nil
}
case 2:
err = c.Verify("test@example.com")
case 3:
c.tls = true
c.serverName = "smtp.google.com"
err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
case 4:
err = c.Mail("test@example.com")
case 5:
ok, _ := c.Extension("feature")
if ok {
t.Errorf("Expected FEATURE not to be supported")
}
case 6:
err = c.Reset()
case 7:
err = c.Quit()
case 8:
err = c.Verify("test@example.com")
if err != nil {
err = c.Hello("customhost")
if err != nil {
t.Errorf("Want error, got none")
}
}
default:
t.Fatalf("Unhandled command")
}
if err != nil {
t.Errorf("Command %d failed: %v", i, err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
if client != actualcmds {
t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
}
var baseHelloServer = `220 hello world
502 EH?
250-mx.google.com at your service
250 FEATURE
`
var helloServer = []string{
"",
"502 Not implemented\n",
"250 User is valid\n",
"235 Accepted\n",
"250 Sender ok\n",
"",
"250 Reset ok\n",
"221 Goodbye\n",
"250 Sender ok\n",
}
var baseHelloClient = `EHLO customhost
HELO customhost
`
var helloClient = []string{
"",
"STARTTLS\n",
"VRFY test@example.com\n",
"AUTH PLAIN AHVzZXIAcGFzcw==\n",
"MAIL FROM:<test@example.com>\n",
"",
"RSET\n",
"QUIT\n",
"VRFY test@example.com\n",
}
func TestSendMail(t *testing.T) {
server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n")
client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Unable to to create listener: %v", err)
}
defer l.Close()
// prevent data race on bcmdbuf
var done = make(chan struct{})
go func(data []string) {
defer close(done)
conn, err := l.Accept()
if err != nil {
t.Errorf("Accept error: %v", err)
return
}
defer conn.Close()
tc := textproto.NewConn(conn)
for i := 0; i < len(data) && data[i] != ""; i++ {
tc.PrintfLine(data[i])
for len(data[i]) >= 4 && data[i][3] == '-' {
i++
tc.PrintfLine(data[i])
}
if data[i] == "221 Goodbye" {
return
}
read := false
for !read || data[i] == "354 Go ahead" {
msg, err := tc.ReadLine()
bcmdbuf.Write([]byte(msg + "\r\n"))
read = true
if err != nil {
t.Errorf("Read error: %v", err)
return
}
if data[i] == "354 Go ahead" && msg == "." {
break
}
}
}
}(strings.Split(server, "\r\n"))
err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com
To: other@example.com
Subject: SendMail test
SendMail is working for me.
`, "\n", "\r\n", -1)))
if err != nil {
t.Errorf("%v", err)
}
<-done
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
if client != actualcmds {
t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
var sendMailServer = `220 hello world
502 EH?
250 mx.google.com at your service
250 Sender ok
250 Receiver ok
354 Go ahead
250 Data ok
221 Goodbye
`
var sendMailClient = `EHLO localhost
HELO localhost
MAIL FROM:<test@example.com>
RCPT TO:<other@example.com>
DATA
From: test@example.com
To: other@example.com
Subject: SendMail test
SendMail is working for me.
.
QUIT
`

View File

@ -64,21 +64,21 @@ func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err
return 0, 0, syscall.EPLAN9 return 0, 0, syscall.EPLAN9
} }
// CloseRead shuts down the reading side of the Unix domain // CloseRead shuts down the reading side of the Unix domain connection.
// connection. Most callers should just use Close. // Most callers should just use Close.
func (c *UnixConn) CloseRead() error { func (c *UnixConn) CloseRead() error {
return syscall.EPLAN9 return syscall.EPLAN9
} }
// CloseWrite shuts down the writing side of the Unix domain // CloseWrite shuts down the writing side of the Unix domain connection.
// connection. Most callers should just use Close. // Most callers should just use Close.
func (c *UnixConn) CloseWrite() error { func (c *UnixConn) CloseWrite() error {
return syscall.EPLAN9 return syscall.EPLAN9
} }
// DialUnix connects to the remote address raddr on the network net, // DialUnix connects to the remote address raddr on the network net,
// which must be "unix" or "unixgram". If laddr is not nil, it is // which must be "unix", "unixgram" or "unixpacket". If laddr is not
// used as the local address for the connection. // nil, it is used as the local address for the connection.
func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) {
return dialUnix(net, laddr, raddr, noDeadline) return dialUnix(net, laddr, raddr, noDeadline)
} }
@ -93,7 +93,8 @@ func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn
type UnixListener struct{} type UnixListener struct{}
// ListenUnix announces on the Unix domain socket laddr and returns a // ListenUnix announces on the Unix domain socket laddr and returns a
// Unix listener. Net must be "unix" (stream sockets). // Unix listener. The network net must be "unix", "unixgram" or
// "unixpacket".
func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) { func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) {
return nil, syscall.EPLAN9 return nil, syscall.EPLAN9
} }
@ -134,8 +135,8 @@ func (l *UnixListener) File() (*os.File, error) {
// ListenUnixgram listens for incoming Unix datagram packets addressed // ListenUnixgram listens for incoming Unix datagram packets addressed
// to the local address laddr. The returned connection c's ReadFrom // to the local address laddr. The returned connection c's ReadFrom
// and WriteTo methods can be used to receive and send UDP packets // and WriteTo methods can be used to receive and send packets with
// with per-packet addressing. The network net must be "unixgram". // per-packet addressing. The network net must be "unixgram".
func ListenUnixgram(net string, laddr *UnixAddr) (*UDPConn, error) { func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error) {
return nil, syscall.EPLAN9 return nil, syscall.EPLAN9
} }

View File

@ -9,29 +9,27 @@
package net package net
import ( import (
"errors"
"os" "os"
"syscall" "syscall"
"time" "time"
) )
func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.Time) (fd *netFD, err error) { func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.Time) (*netFD, error) {
var sotype int var sotype int
switch net { switch net {
default:
return nil, UnknownNetworkError(net)
case "unix": case "unix":
sotype = syscall.SOCK_STREAM sotype = syscall.SOCK_STREAM
case "unixgram": case "unixgram":
sotype = syscall.SOCK_DGRAM sotype = syscall.SOCK_DGRAM
case "unixpacket": case "unixpacket":
sotype = syscall.SOCK_SEQPACKET sotype = syscall.SOCK_SEQPACKET
default:
return nil, UnknownNetworkError(net)
} }
var la, ra syscall.Sockaddr var la, ra syscall.Sockaddr
switch mode { switch mode {
default:
panic("unixSocket mode " + mode)
case "dial": case "dial":
if laddr != nil { if laddr != nil {
la = &syscall.SockaddrUnix{Name: laddr.Name} la = &syscall.SockaddrUnix{Name: laddr.Name}
@ -41,15 +39,10 @@ func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.T
} else if sotype != syscall.SOCK_DGRAM || laddr == nil { } else if sotype != syscall.SOCK_DGRAM || laddr == nil {
return nil, &OpError{Op: mode, Net: net, Err: errMissingAddress} return nil, &OpError{Op: mode, Net: net, Err: errMissingAddress}
} }
case "listen": case "listen":
if laddr == nil {
return nil, &OpError{mode, net, nil, errMissingAddress}
}
la = &syscall.SockaddrUnix{Name: laddr.Name} la = &syscall.SockaddrUnix{Name: laddr.Name}
if raddr != nil { default:
return nil, &OpError{Op: mode, Net: net, Addr: raddr, Err: &AddrError{Err: "unexpected remote address", Addr: raddr.String()}} return nil, errors.New("unknown mode: " + mode)
}
} }
f := sockaddrToUnix f := sockaddrToUnix
@ -59,15 +52,16 @@ func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.T
f = sockaddrToUnixpacket f = sockaddrToUnixpacket
} }
fd, err = socket(net, syscall.AF_UNIX, sotype, 0, false, la, ra, deadline, f) fd, err := socket(net, syscall.AF_UNIX, sotype, 0, false, la, ra, deadline, f)
if err != nil { if err != nil {
goto Error goto error
} }
return fd, nil return fd, nil
Error: error:
addr := raddr addr := raddr
if mode == "listen" { switch mode {
case "listen":
addr = laddr addr = laddr
} }
return nil, &OpError{Op: mode, Net: net, Addr: addr, Err: err} return nil, &OpError{Op: mode, Net: net, Addr: addr, Err: err}
@ -108,21 +102,21 @@ func sotypeToNet(sotype int) string {
return "" return ""
} }
// UnixConn is an implementation of the Conn interface // UnixConn is an implementation of the Conn interface for connections
// for connections to Unix domain sockets. // to Unix domain sockets.
type UnixConn struct { type UnixConn struct {
conn conn
} }
func newUnixConn(fd *netFD) *UnixConn { return &UnixConn{conn{fd}} } func newUnixConn(fd *netFD) *UnixConn { return &UnixConn{conn{fd}} }
// ReadFromUnix reads a packet from c, copying the payload into b. // ReadFromUnix reads a packet from c, copying the payload into b. It
// It returns the number of bytes copied into b and the source address // returns the number of bytes copied into b and the source address of
// of the packet. // the packet.
// //
// ReadFromUnix can be made to time out and return // ReadFromUnix can be made to time out and return an error with
// an error with Timeout() == true after a fixed time limit; // Timeout() == true after a fixed time limit; see SetDeadline and
// see SetDeadline and SetReadDeadline. // SetReadDeadline.
func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) { func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) {
if !c.ok() { if !c.ok() {
return 0, nil, syscall.EINVAL return 0, nil, syscall.EINVAL
@ -144,12 +138,28 @@ func (c *UnixConn) ReadFrom(b []byte) (n int, addr Addr, err error) {
return n, uaddr.toAddr(), err return n, uaddr.toAddr(), err
} }
// ReadMsgUnix reads a packet from c, copying the payload into b and
// the associated out-of-band data into oob. It returns the number of
// bytes copied into b, the number of bytes copied into oob, the flags
// that were set on the packet, and the source address of the packet.
func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error) {
if !c.ok() {
return 0, 0, 0, nil, syscall.EINVAL
}
n, oobn, flags, sa, err := c.fd.ReadMsg(b, oob)
switch sa := sa.(type) {
case *syscall.SockaddrUnix:
addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
}
return
}
// WriteToUnix writes a packet to addr via c, copying the payload from b. // WriteToUnix writes a packet to addr via c, copying the payload from b.
// //
// WriteToUnix can be made to time out and return // WriteToUnix can be made to time out and return an error with
// an error with Timeout() == true after a fixed time limit; // Timeout() == true after a fixed time limit; see SetDeadline and
// see SetDeadline and SetWriteDeadline. // SetWriteDeadline. On packet-oriented connections, write timeouts
// On packet-oriented connections, write timeouts are rare. // are rare.
func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err error) { func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err error) {
if !c.ok() { if !c.ok() {
return 0, syscall.EINVAL return 0, syscall.EINVAL
@ -173,26 +183,9 @@ func (c *UnixConn) WriteTo(b []byte, addr Addr) (n int, err error) {
return c.WriteToUnix(b, a) return c.WriteToUnix(b, a)
} }
// ReadMsgUnix reads a packet from c, copying the payload into b // WriteMsgUnix writes a packet to addr via c, copying the payload
// and the associated out-of-band data into oob. // from b and the associated out-of-band data from oob. It returns
// It returns the number of bytes copied into b, the number of // the number of payload and out-of-band bytes written.
// bytes copied into oob, the flags that were set on the packet,
// and the source address of the packet.
func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error) {
if !c.ok() {
return 0, 0, 0, nil, syscall.EINVAL
}
n, oobn, flags, sa, err := c.fd.ReadMsg(b, oob)
switch sa := sa.(type) {
case *syscall.SockaddrUnix:
addr = &UnixAddr{sa.Name, sotypeToNet(c.fd.sotype)}
}
return
}
// WriteMsgUnix writes a packet to addr via c, copying the payload from b
// and the associated out-of-band data from oob. It returns the number
// of payload and out-of-band bytes written.
func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error) { func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error) {
if !c.ok() { if !c.ok() {
return 0, 0, syscall.EINVAL return 0, 0, syscall.EINVAL
@ -226,13 +219,18 @@ func (c *UnixConn) CloseWrite() error {
} }
// DialUnix connects to the remote address raddr on the network net, // DialUnix connects to the remote address raddr on the network net,
// which must be "unix" or "unixgram". If laddr is not nil, it is used // which must be "unix", "unixgram" or "unixpacket". If laddr is not
// as the local address for the connection. // nil, it is used as the local address for the connection.
func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) {
return dialUnix(net, laddr, raddr, noDeadline) return dialUnix(net, laddr, raddr, noDeadline)
} }
func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) { func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) {
switch net {
case "unix", "unixgram", "unixpacket":
default:
return nil, UnknownNetworkError(net)
}
fd, err := unixSocket(net, laddr, raddr, "dial", deadline) fd, err := unixSocket(net, laddr, raddr, "dial", deadline)
if err != nil { if err != nil {
return nil, err return nil, err
@ -240,22 +238,25 @@ func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn
return newUnixConn(fd), nil return newUnixConn(fd), nil
} }
// UnixListener is a Unix domain socket listener. // UnixListener is a Unix domain socket listener. Clients should
// Clients should typically use variables of type Listener // typically use variables of type Listener instead of assuming Unix
// instead of assuming Unix domain sockets. // domain sockets.
type UnixListener struct { type UnixListener struct {
fd *netFD fd *netFD
path string path string
} }
// ListenUnix announces on the Unix domain socket laddr and returns a Unix listener. // ListenUnix announces on the Unix domain socket laddr and returns a
// Net must be "unix" (stream sockets). // Unix listener. The network net must be "unix", "unixgram" or
// "unixpacket".
func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) { func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) {
if net != "unix" && net != "unixgram" && net != "unixpacket" { switch net {
case "unix", "unixgram", "unixpacket":
default:
return nil, UnknownNetworkError(net) return nil, UnknownNetworkError(net)
} }
if laddr != nil { if laddr == nil {
laddr = &UnixAddr{laddr.Name, net} // make our own copy return nil, &OpError{"listen", net, nil, errMissingAddress}
} }
fd, err := unixSocket(net, laddr, nil, "listen", noDeadline) fd, err := unixSocket(net, laddr, nil, "listen", noDeadline)
if err != nil { if err != nil {
@ -269,8 +270,8 @@ func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) {
return &UnixListener{fd, laddr.Name}, nil return &UnixListener{fd, laddr.Name}, nil
} }
// AcceptUnix accepts the next incoming call and returns the new connection // AcceptUnix accepts the next incoming call and returns the new
// and the remote address. // connection and the remote address.
func (l *UnixListener) AcceptUnix() (*UnixConn, error) { func (l *UnixListener) AcceptUnix() (*UnixConn, error) {
if l == nil || l.fd == nil { if l == nil || l.fd == nil {
return nil, syscall.EINVAL return nil, syscall.EINVAL
@ -283,8 +284,8 @@ func (l *UnixListener) AcceptUnix() (*UnixConn, error) {
return c, nil return c, nil
} }
// Accept implements the Accept method in the Listener interface; // Accept implements the Accept method in the Listener interface; it
// it waits for the next call and returns a generic Conn. // waits for the next call and returns a generic Conn.
func (l *UnixListener) Accept() (c Conn, err error) { func (l *UnixListener) Accept() (c Conn, err error) {
c1, err := l.AcceptUnix() c1, err := l.AcceptUnix()
if err != nil { if err != nil {
@ -293,8 +294,8 @@ func (l *UnixListener) Accept() (c Conn, err error) {
return c1, nil return c1, nil
} }
// Close stops listening on the Unix address. // Close stops listening on the Unix address. Already accepted
// Already accepted connections are not closed. // connections are not closed.
func (l *UnixListener) Close() error { func (l *UnixListener) Close() error {
if l == nil || l.fd == nil { if l == nil || l.fd == nil {
return syscall.EINVAL return syscall.EINVAL
@ -328,16 +329,16 @@ func (l *UnixListener) SetDeadline(t time.Time) (err error) {
return setDeadline(l.fd, t) return setDeadline(l.fd, t)
} }
// File returns a copy of the underlying os.File, set to blocking mode. // File returns a copy of the underlying os.File, set to blocking
// It is the caller's responsibility to close f when finished. // mode. It is the caller's responsibility to close f when finished.
// Closing l does not affect f, and closing f does not affect l. // Closing l does not affect f, and closing f does not affect l.
func (l *UnixListener) File() (f *os.File, err error) { return l.fd.dup() } func (l *UnixListener) File() (f *os.File, err error) { return l.fd.dup() }
// ListenUnixgram listens for incoming Unix datagram packets addressed to the // ListenUnixgram listens for incoming Unix datagram packets addressed
// local address laddr. The returned connection c's ReadFrom // to the local address laddr. The returned connection c's ReadFrom
// and WriteTo methods can be used to receive and send UDP // and WriteTo methods can be used to receive and send packets with
// packets with per-packet addressing. The network net must be "unixgram". // per-packet addressing. The network net must be "unixgram".
func ListenUnixgram(net string, laddr *UnixAddr) (*UDPConn, error) { func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error) {
switch net { switch net {
case "unixgram": case "unixgram":
default: default:
@ -350,5 +351,5 @@ func ListenUnixgram(net string, laddr *UnixAddr) (*UDPConn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newUDPConn(fd), nil return newUnixConn(fd), nil
} }

View File

@ -361,6 +361,11 @@ func parse(rawurl string, viaRequest bool) (url *URL, err error) {
} }
url = new(URL) url = new(URL)
if rawurl == "*" {
url.Path = "*"
return
}
// Split off possible leading "http:", "mailto:", etc. // Split off possible leading "http:", "mailto:", etc.
// Cannot contain escaped characters. // Cannot contain escaped characters.
if url.Scheme, rest, err = getscheme(rawurl); err != nil { if url.Scheme, rest, err = getscheme(rawurl); err != nil {
@ -572,23 +577,33 @@ func resolvePath(basepath string, refpath string) string {
if len(base) == 0 { if len(base) == 0 {
base = []string{""} base = []string{""}
} }
rm := true
for idx, ref := range refs { for idx, ref := range refs {
switch { switch {
case ref == ".": case ref == ".":
base[len(base)-1] = "" if idx == 0 {
base[len(base)-1] = ""
rm = true
} else {
rm = false
}
case ref == "..": case ref == "..":
newLen := len(base) - 1 newLen := len(base) - 1
if newLen < 1 { if newLen < 1 {
newLen = 1 newLen = 1
} }
base = base[0:newLen] base = base[0:newLen]
base[len(base)-1] = "" if rm {
base[len(base)-1] = ""
}
default: default:
if idx == 0 || base[len(base)-1] == "" { if idx == 0 || base[len(base)-1] == "" {
base[len(base)-1] = ref base[len(base)-1] = ref
} else { } else {
base = append(base, ref) base = append(base, ref)
} }
rm = false
} }
} }
return strings.Join(base, "/") return strings.Join(base, "/")

View File

@ -277,7 +277,7 @@ func TestParse(t *testing.T) {
const pathThatLooksSchemeRelative = "//not.a.user@not.a.host/just/a/path" const pathThatLooksSchemeRelative = "//not.a.user@not.a.host/just/a/path"
var parseRequestUrlTests = []struct { var parseRequestURLTests = []struct {
url string url string
expectedValid bool expectedValid bool
}{ }{
@ -289,10 +289,11 @@ var parseRequestUrlTests = []struct {
{"//not.a.user@%66%6f%6f.com/just/a/path/also", true}, {"//not.a.user@%66%6f%6f.com/just/a/path/also", true},
{"foo.html", false}, {"foo.html", false},
{"../dir/", false}, {"../dir/", false},
{"*", true},
} }
func TestParseRequestURI(t *testing.T) { func TestParseRequestURI(t *testing.T) {
for _, test := range parseRequestUrlTests { for _, test := range parseRequestURLTests {
_, err := ParseRequestURI(test.url) _, err := ParseRequestURI(test.url)
valid := err == nil valid := err == nil
if valid != test.expectedValid { if valid != test.expectedValid {
@ -536,6 +537,15 @@ var resolveReferenceTests = []struct {
{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"}, {"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
{"http://foo.com/bar", "..", "http://foo.com/"}, {"http://foo.com/bar", "..", "http://foo.com/"},
{"http://foo.com/bar/baz", "./..", "http://foo.com/"}, {"http://foo.com/bar/baz", "./..", "http://foo.com/"},
// ".." in the middle (issue 3560)
{"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot"},
// "." and ".." in the base aren't special // "." and ".." in the base aren't special
{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/./dotdot/../baz"}, {"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/./dotdot/../baz"},

View File

@ -9,7 +9,7 @@ package os
import "syscall" import "syscall"
// Expand replaces ${var} or $var in the string based on the mapping function. // Expand replaces ${var} or $var in the string based on the mapping function.
// Invocations of undefined variables are replaced with the empty string. // For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv).
func Expand(s string, mapping func(string) string) string { func Expand(s string, mapping func(string) string) string {
buf := make([]byte, 0, 2*len(s)) buf := make([]byte, 0, 2*len(s))
// ${} is all ASCII, so bytes are fine for this operation. // ${} is all ASCII, so bytes are fine for this operation.

View File

@ -153,12 +153,10 @@ func (f *File) Sync() (err error) {
// less precise time unit. // less precise time unit.
// If there is an error, it will be of type *PathError. // If there is an error, it will be of type *PathError.
func Chtimes(name string, atime time.Time, mtime time.Time) error { func Chtimes(name string, atime time.Time, mtime time.Time) error {
var utimes [2]syscall.Timeval var utimes [2]syscall.Timespec
atime_ns := atime.Unix()*1e9 + int64(atime.Nanosecond()) utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
mtime_ns := mtime.Unix()*1e9 + int64(mtime.Nanosecond()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
utimes[0] = syscall.NsecToTimeval(atime_ns) if e := syscall.UtimesNano(name, utimes[0:]); e != nil {
utimes[1] = syscall.NsecToTimeval(mtime_ns)
if e := syscall.Utimes(name, utimes[0:]); e != nil {
return &PathError{"chtimes", name, e} return &PathError{"chtimes", name, e}
} }
return nil return nil

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.
// +build darwin freebsd linux netbsd // +build darwin freebsd linux netbsd openbsd
// +build cgo // +build cgo
package user package user

View File

@ -31,53 +31,52 @@ var good_re = []string{
`\!\\`, `\!\\`,
} }
/*
type stringError struct { type stringError struct {
re string re string
err error err string
} }
var bad_re = []stringError{ var bad_re = []stringError{
{`*`, ErrBareClosure}, {`*`, "missing argument to repetition operator: `*`"},
{`+`, ErrBareClosure}, {`+`, "missing argument to repetition operator: `+`"},
{`?`, ErrBareClosure}, {`?`, "missing argument to repetition operator: `?`"},
{`(abc`, ErrUnmatchedLpar}, {`(abc`, "missing closing ): `(abc`"},
{`abc)`, ErrUnmatchedRpar}, {`abc)`, "unexpected ): `abc)`"},
{`x[a-z`, ErrUnmatchedLbkt}, {`x[a-z`, "missing closing ]: `[a-z`"},
{`abc]`, ErrUnmatchedRbkt}, {`[z-a]`, "invalid character class range: `z-a`"},
{`[z-a]`, ErrBadRange}, {`abc\`, "trailing backslash at end of expression"},
{`abc\`, ErrExtraneousBackslash}, {`a**`, "invalid nested repetition operator: `**`"},
{`a**`, ErrBadClosure}, {`a*+`, "invalid nested repetition operator: `*+`"},
{`a*+`, ErrBadClosure}, {`\x`, "invalid escape sequence: `\\x`"},
{`a??`, ErrBadClosure},
{`\x`, ErrBadBackslash},
} }
*/
func compileTest(t *testing.T, expr string, error error) *Regexp { func compileTest(t *testing.T, expr string, error string) *Regexp {
re, err := Compile(expr) re, err := Compile(expr)
if err != error { if error == "" && err != nil {
t.Error("compiling `", expr, "`; unexpected error: ", err.Error()) t.Error("compiling `", expr, "`; unexpected error: ", err.Error())
} }
if error != "" && err == nil {
t.Error("compiling `", expr, "`; missing error")
} else if error != "" && !strings.Contains(err.Error(), error) {
t.Error("compiling `", expr, "`; wrong error: ", err.Error(), "; want ", error)
}
return re return re
} }
func TestGoodCompile(t *testing.T) { func TestGoodCompile(t *testing.T) {
for i := 0; i < len(good_re); i++ { for i := 0; i < len(good_re); i++ {
compileTest(t, good_re[i], nil) compileTest(t, good_re[i], "")
} }
} }
/*
func TestBadCompile(t *testing.T) { func TestBadCompile(t *testing.T) {
for i := 0; i < len(bad_re); i++ { for i := 0; i < len(bad_re); i++ {
compileTest(t, bad_re[i].re, bad_re[i].err) compileTest(t, bad_re[i].re, bad_re[i].err)
} }
} }
*/
func matchTest(t *testing.T, test *FindTest) { func matchTest(t *testing.T, test *FindTest) {
re := compileTest(t, test.pat, nil) re := compileTest(t, test.pat, "")
if re == nil { if re == nil {
return return
} }

View File

@ -42,11 +42,9 @@ const (
ErrMissingParen ErrorCode = "missing closing )" ErrMissingParen ErrorCode = "missing closing )"
ErrMissingRepeatArgument ErrorCode = "missing argument to repetition operator" ErrMissingRepeatArgument ErrorCode = "missing argument to repetition operator"
ErrTrailingBackslash ErrorCode = "trailing backslash at end of expression" ErrTrailingBackslash ErrorCode = "trailing backslash at end of expression"
ErrUnexpectedParen ErrorCode = "unexpected )"
) )
// TODO: Export for Go 1.1.
const errUnexpectedParen ErrorCode = "unexpected )"
func (e ErrorCode) String() string { func (e ErrorCode) String() string {
return string(e) return string(e)
} }
@ -1167,13 +1165,13 @@ func (p *parser) parseRightParen() error {
n := len(p.stack) n := len(p.stack)
if n < 2 { if n < 2 {
return &Error{errUnexpectedParen, p.wholeRegexp} return &Error{ErrUnexpectedParen, p.wholeRegexp}
} }
re1 := p.stack[n-1] re1 := p.stack[n-1]
re2 := p.stack[n-2] re2 := p.stack[n-2]
p.stack = p.stack[:n-2] p.stack = p.stack[:n-2]
if re2.Op != opLeftParen { if re2.Op != opLeftParen {
return &Error{errUnexpectedParen, p.wholeRegexp} return &Error{ErrUnexpectedParen, p.wholeRegexp}
} }
// Restore flags at time of paren. // Restore flags at time of paren.
p.flags = re2.Flags p.flags = re2.Flags

View File

@ -255,7 +255,7 @@ func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
// d = mant << (exp - mantbits) // d = mant << (exp - mantbits)
// Next highest floating point number is mant+1 << exp-mantbits. // Next highest floating point number is mant+1 << exp-mantbits.
// Our upper bound is halfway inbetween, mant*2+1 << exp-mantbits-1. // Our upper bound is halfway between, mant*2+1 << exp-mantbits-1.
upper := new(decimal) upper := new(decimal)
upper.Assign(mant*2 + 1) upper.Assign(mant*2 + 1)
upper.Shift(exp - int(flt.mantbits) - 1) upper.Shift(exp - int(flt.mantbits) - 1)
@ -265,7 +265,7 @@ func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
// unless mant-1 drops the significant bit and exp is not the minimum exp, // unless mant-1 drops the significant bit and exp is not the minimum exp,
// in which case the next lowest is mant*2-1 << exp-mantbits-1. // in which case the next lowest is mant*2-1 << exp-mantbits-1.
// Either way, call it mantlo << explo-mantbits. // Either way, call it mantlo << explo-mantbits.
// Our lower bound is halfway inbetween, mantlo*2+1 << explo-mantbits-1. // Our lower bound is halfway between, mantlo*2+1 << explo-mantbits-1.
var mantlo uint64 var mantlo uint64
var explo int var explo int
if mant > 1<<flt.mantbits || exp == minexp { if mant > 1<<flt.mantbits || exp == minexp {

View File

@ -12,14 +12,17 @@ import (
) )
var ( var (
// envOnce guards initialization by copyenv, which populates env. // envOnce guards copyenv, which populates env.
envOnce sync.Once envOnce sync.Once
// envLock guards env. // envLock guards env.
envLock sync.RWMutex envLock sync.RWMutex
// env maps from an environment variable to its value. // env maps from an environment variable to its value.
env map[string]string env = make(map[string]string)
errZeroLengthKey = errors.New("zero length key")
errShortWrite = errors.New("i/o count too small")
) )
func readenv(key string) (string, error) { func readenv(key string) (string, error) {
@ -47,12 +50,18 @@ func writeenv(key, value string) error {
return err return err
} }
defer Close(fd) defer Close(fd)
_, err = Write(fd, []byte(value)) b := []byte(value)
return err n, err := Write(fd, b)
if err != nil {
return err
}
if n != len(b) {
return errShortWrite
}
return nil
} }
func copyenv() { func copyenv() {
env = make(map[string]string)
fd, err := Open("/env", O_RDONLY) fd, err := Open("/env", O_RDONLY)
if err != nil { if err != nil {
return return
@ -72,7 +81,6 @@ func copyenv() {
} }
func Getenv(key string) (value string, found bool) { func Getenv(key string) (value string, found bool) {
envOnce.Do(copyenv)
if len(key) == 0 { if len(key) == 0 {
return "", false return "", false
} }
@ -80,17 +88,20 @@ func Getenv(key string) (value string, found bool) {
envLock.RLock() envLock.RLock()
defer envLock.RUnlock() defer envLock.RUnlock()
v, ok := env[key] if v, ok := env[key]; ok {
if !ok { return v, true
}
v, err := readenv(key)
if err != nil {
return "", false return "", false
} }
env[key] = v
return v, true return v, true
} }
func Setenv(key, value string) error { func Setenv(key, value string) error {
envOnce.Do(copyenv)
if len(key) == 0 { if len(key) == 0 {
return errors.New("zero length key") return errZeroLengthKey
} }
envLock.Lock() envLock.Lock()
@ -105,8 +116,6 @@ func Setenv(key, value string) error {
} }
func Clearenv() { func Clearenv() {
envOnce.Do(copyenv) // prevent copyenv in Getenv/Setenv
envLock.Lock() envLock.Lock()
defer envLock.Unlock() defer envLock.Unlock()
@ -115,9 +124,10 @@ func Clearenv() {
} }
func Environ() []string { func Environ() []string {
envOnce.Do(copyenv)
envLock.RLock() envLock.RLock()
defer envLock.RUnlock() defer envLock.RUnlock()
envOnce.Do(copyenv)
a := make([]string, len(env)) a := make([]string, len(env))
i := 0 i := 0
for k, v := range env { for k, v := range env {

View File

@ -0,0 +1,29 @@
// 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.
// GNU/Linux version of UtimesNano.
package syscall
import "unsafe"
//sys utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error)
//utimensat(dirfd int, path *byte, times *[2]Timespec, flags int) int
func UtimesNano(path string, ts []Timespec) (err error) {
if len(ts) != 2 {
return EINVAL
}
err = utimensat(_AT_FDCWD, path, (*[2]Timespec)(unsafe.Pointer(&ts[0])), 0)
if err != ENOSYS {
return err
}
// If the utimensat syscall isn't available (utimensat was added to Linux
// in 2.6.22, Released, 8 July 2007) then fall back to utimes
var tv [2]Timeval
for i := 0; i < 2; i++ {
tv[i].Sec = Timeval_sec_t(ts[i].Sec)
tv[i].Usec = Timeval_usec_t(ts[i].Nsec / 1000)
}
return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0])))
}

View File

@ -0,0 +1,24 @@
// 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.
// General POSIX version of UtimesNano.
package syscall
import "unsafe"
func UtimesNano(path string, ts []Timespec) error {
// TODO: The BSDs can do utimensat with SYS_UTIMENSAT but it
// isn't supported by darwin so this uses utimes instead
if len(ts) != 2 {
return EINVAL
}
// Not as efficient as it could be because Timespec and
// Timeval have different types in the different OSes
tv := [2]Timeval{
NsecToTimeval(TimespecToNsec(ts[0])),
NsecToTimeval(TimespecToNsec(ts[1])),
}
return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0])))
}

View File

@ -3,10 +3,15 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package syscall contains an interface to the low-level operating system // Package syscall contains an interface to the low-level operating system
// primitives. The details vary depending on the underlying system. // primitives. The details vary depending on the underlying system, and
// Its primary use is inside other packages that provide a more portable // by default, godoc will display the syscall documentation for the current
// interface to the system, such as "os", "time" and "net". Use those // system. If you want godoc to display syscall documentation for another
// packages rather than this one if you can. // system, set $GOOS and $GOARCH to the desired system. For example, if
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
// to freebsd and $GOARCH to arm.
// The primary use of syscall is inside other packages that provide a more
// portable interface to the system, such as "os", "time" and "net". Use
// those packages rather than this one if you can.
// For details of the functions and data types in this package consult // For details of the functions and data types in this package consult
// the manuals for the appropriate operating system. // the manuals for the appropriate operating system.
// These calls return err == nil to indicate success; otherwise // These calls return err == nil to indicate success; otherwise

View File

@ -24,7 +24,7 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
var eg InternalExample var eg InternalExample
stdout, stderr := os.Stdout, os.Stderr stdout := os.Stdout
for _, eg = range examples { for _, eg = range examples {
matched, err := matchString(*match, eg.Name) matched, err := matchString(*match, eg.Name)
@ -39,19 +39,19 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
fmt.Printf("=== RUN: %s\n", eg.Name) fmt.Printf("=== RUN: %s\n", eg.Name)
} }
// capture stdout and stderr // capture stdout
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
os.Stdout, os.Stderr = w, w os.Stdout = w
outC := make(chan string) outC := make(chan string)
go func() { go func() {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err := io.Copy(buf, r) _, err := io.Copy(buf, r)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err) fmt.Fprintf(os.Stderr, "testing: copying pipe: %v\n", err)
os.Exit(1) os.Exit(1)
} }
outC <- buf.String() outC <- buf.String()
@ -62,9 +62,9 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
eg.F() eg.F()
dt := time.Now().Sub(t0) dt := time.Now().Sub(t0)
// close pipe, restore stdout/stderr, get output // close pipe, restore stdout, get output
w.Close() w.Close()
os.Stdout, os.Stderr = stdout, stderr os.Stdout = stdout
out := <-outC out := <-outC
// report any errors // report any errors

View File

@ -854,9 +854,15 @@ func Parse(layout, value string) (Time, error) {
zoneName = p zoneName = p
case stdFracSecond0: case stdFracSecond0:
ndigit := std >> stdArgShift // stdFracSecond0 requires the exact number of digits as specified in
nsec, rangeErrString, err = parseNanoseconds(value, 1+ndigit) // the layout.
value = value[1+ndigit:] ndigit := 1 + (std >> stdArgShift)
if len(value) < ndigit {
err = errBad
break
}
nsec, rangeErrString, err = parseNanoseconds(value, ndigit)
value = value[ndigit:]
case stdFracSecond9: case stdFracSecond9:
if len(value) < 2 || value[0] != '.' || value[1] < '0' || '9' < value[1] { if len(value) < 2 || value[0] != '.' || value[1] < '0' || '9' < value[1] {
@ -934,8 +940,7 @@ func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string,
err = errBad err = errBad
return return
} }
ns, err = atoi(value[1:nbytes]) if ns, err = atoi(value[1:nbytes]); err != nil {
if err != nil {
return return
} }
if ns < 0 || 1e9 <= ns { if ns < 0 || 1e9 <= ns {

View File

@ -469,7 +469,7 @@ type ParseTest struct {
value string value string
hasTZ bool // contains a time zone hasTZ bool // contains a time zone
hasWD bool // contains a weekday hasWD bool // contains a weekday
yearSign int // sign of year yearSign int // sign of year, -1 indicates the year is not present in the format
fracDigits int // number of digits of fractional second fracDigits int // number of digits of fractional second
} }
@ -514,6 +514,13 @@ var parseTests = []ParseTest{
{"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4}, {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4},
{"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9}, {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9},
{"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9}, {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9},
// issue 4502.
{"", StampNano, "Feb 4 21:00:57.012345678", false, false, -1, 9},
{"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012300000", false, false, -1, 4},
{"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012345678", false, false, -1, 9},
{"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.0123", false, false, -1, 4},
{"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.012345678", false, false, -1, 9},
} }
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
@ -549,7 +556,7 @@ func TestRubyParse(t *testing.T) {
func checkTime(time Time, test *ParseTest, t *testing.T) { func checkTime(time Time, test *ParseTest, t *testing.T) {
// The time should be Thu Feb 4 21:00:57 PST 2010 // The time should be Thu Feb 4 21:00:57 PST 2010
if test.yearSign*time.Year() != 2010 { if test.yearSign >= 0 && test.yearSign*time.Year() != 2010 {
t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010) t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010)
} }
if time.Month() != February { if time.Month() != February {
@ -630,6 +637,9 @@ var parseErrorTests = []ParseErrorTest{
{"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59x01 2010", "cannot parse"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59x01 2010", "cannot parse"},
{"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.xxx 2010", "cannot parse"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.xxx 2010", "cannot parse"},
{"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.-123 2010", "fractional second out of range"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.-123 2010", "fractional second out of range"},
// issue 4502. StampNano requires exactly 9 digits of precision.
{StampNano, "Dec 7 11:22:01.000000", `cannot parse ".000000" as ".000000000"`},
{StampNano, "Dec 7 11:22:01.0000000000", "extra text: 0"},
} }
func TestParseErrors(t *testing.T) { func TestParseErrors(t *testing.T) {

View File

@ -174,7 +174,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
} }
} }
// Commited to succeed. // Committed to succeed.
l = &Location{zone: zone, tx: tx} l = &Location{zone: zone, tx: tx}
// Fill in the cache with information about right now, // Fill in the cache with information about right now,

View File

@ -163,7 +163,7 @@ done
done done
done done
runtime="chan.c cpuprof.c lock_futex.c lock_sema.c mcache.c mcentral.c mfinal.c mfixalloc.c mgc0.c mheap.c msize.c panic.c print.c proc.c race.h runtime.c runtime.h signal_unix.c malloc.h malloc.goc mprof.goc parfor.c runtime1.goc sema.goc sigqueue.goc string.goc time.goc" runtime="chan.c cpuprof.c env_posix.c lock_futex.c lock_sema.c mcache.c mcentral.c mfinal.c mfixalloc.c mgc0.c mgc0.h mheap.c msize.c panic.c print.c proc.c race.h runtime.c runtime.h signal_unix.c malloc.h malloc.goc mprof.goc parfor.c runtime1.goc sema.goc sigqueue.goc string.goc time.goc"
for f in $runtime; do for f in $runtime; do
merge_c $f $f merge_c $f $f
done done

37
libgo/runtime/env_posix.c Normal file
View File

@ -0,0 +1,37 @@
// 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.
// +build darwin freebsd linux netbsd openbsd windows
#include "runtime.h"
#include "array.h"
extern Slice syscall_Envs asm ("syscall.Envs");
const byte*
runtime_getenv(const char *s)
{
int32 i, j, len;
const byte *v, *bs;
String* envv;
int32 envc;
bs = (const byte*)s;
len = runtime_findnull(bs);
envv = (String*)syscall_Envs.__values;
envc = syscall_Envs.__count;
for(i=0; i<envc; i++){
if(envv[i].len <= len)
continue;
v = (const byte*)envv[i].str;
for(j=0; j<len; j++)
if(bs[j] != v[j])
goto nomatch;
if(v[len] != '=')
goto nomatch;
return v+len+1;
nomatch:;
}
return nil;
}

View File

@ -106,8 +106,8 @@ __go_allocate_trampoline (uintptr_t size, void *closure)
no other references to it. */ no other references to it. */
void void
runtime_trampoline_scan (void (*addroot) (byte *, uintptr)) runtime_trampoline_scan (void (*addroot) (Obj))
{ {
if (trampoline_page != NULL) if (trampoline_page != NULL)
addroot ((byte *) &trampoline_page, sizeof trampoline_page); addroot ((Obj){(byte *) &trampoline_page, sizeof trampoline_page, 0});
} }

View File

@ -491,7 +491,7 @@ runtime_MHeap_SysAlloc(MHeap *h, uintptr n)
static Lock settype_lock; static Lock settype_lock;
void void
runtime_settype_flush(M *m, bool sysalloc) runtime_settype_flush(M *mp, bool sysalloc)
{ {
uintptr *buf, *endbuf; uintptr *buf, *endbuf;
uintptr size, ofs, j, t; uintptr size, ofs, j, t;
@ -503,8 +503,8 @@ runtime_settype_flush(M *m, bool sysalloc)
uintptr typ, p; uintptr typ, p;
MSpan *s; MSpan *s;
buf = m->settype_buf; buf = mp->settype_buf;
endbuf = buf + m->settype_bufsize; endbuf = buf + mp->settype_bufsize;
runtime_lock(&settype_lock); runtime_lock(&settype_lock);
while(buf < endbuf) { while(buf < endbuf) {
@ -602,7 +602,7 @@ runtime_settype_flush(M *m, bool sysalloc)
} }
runtime_unlock(&settype_lock); runtime_unlock(&settype_lock);
m->settype_bufsize = 0; mp->settype_bufsize = 0;
} }
// It is forbidden to use this function if it is possible that // It is forbidden to use this function if it is possible that
@ -610,7 +610,7 @@ runtime_settype_flush(M *m, bool sysalloc)
void void
runtime_settype(void *v, uintptr t) runtime_settype(void *v, uintptr t)
{ {
M *m1; M *mp;
uintptr *buf; uintptr *buf;
uintptr i; uintptr i;
MSpan *s; MSpan *s;
@ -618,16 +618,16 @@ runtime_settype(void *v, uintptr t)
if(t == 0) if(t == 0)
runtime_throw("settype: zero type"); runtime_throw("settype: zero type");
m1 = runtime_m(); mp = runtime_m();
buf = m1->settype_buf; buf = mp->settype_buf;
i = m1->settype_bufsize; i = mp->settype_bufsize;
buf[i+0] = (uintptr)v; buf[i+0] = (uintptr)v;
buf[i+1] = t; buf[i+1] = t;
i += 2; i += 2;
m1->settype_bufsize = i; mp->settype_bufsize = i;
if(i == nelem(m1->settype_buf)) { if(i == nelem(mp->settype_buf)) {
runtime_settype_flush(m1, false); runtime_settype_flush(mp, false);
} }
if(DebugTypeAtBlockEnd) { if(DebugTypeAtBlockEnd) {

View File

@ -468,17 +468,25 @@ enum
FlagNoGC = 1<<2, // must not free or scan for pointers FlagNoGC = 1<<2, // must not free or scan for pointers
}; };
typedef struct Obj Obj;
struct Obj
{
byte *p; // data pointer
uintptr n; // size of data in bytes
uintptr ti; // type info
};
void runtime_MProf_Malloc(void*, uintptr); void runtime_MProf_Malloc(void*, uintptr);
void runtime_MProf_Free(void*, uintptr); void runtime_MProf_Free(void*, uintptr);
void runtime_MProf_GC(void); void runtime_MProf_GC(void);
void runtime_MProf_Mark(void (*addroot)(byte *, uintptr)); void runtime_MProf_Mark(void (*addroot)(Obj));
int32 runtime_gcprocs(void); int32 runtime_gcprocs(void);
void runtime_helpgc(int32 nproc); void runtime_helpgc(int32 nproc);
void runtime_gchelper(void); void runtime_gchelper(void);
struct __go_func_type; struct __go_func_type;
bool runtime_getfinalizer(void *p, bool del, void (**fn)(void*), const struct __go_func_type **ft); bool runtime_getfinalizer(void *p, bool del, void (**fn)(void*), const struct __go_func_type **ft);
void runtime_walkfintab(void (*fn)(void*), void (*scan)(byte *, uintptr)); void runtime_walkfintab(void (*fn)(void*), void (*scan)(Obj));
enum enum
{ {
@ -494,3 +502,6 @@ enum
void runtime_gc_m_ptr(Eface*); void runtime_gc_m_ptr(Eface*);
void runtime_memorydump(void); void runtime_memorydump(void);
void runtime_time_scan(void (*)(Obj));
void runtime_trampoline_scan(void (*)(Obj));

View File

@ -193,7 +193,7 @@ runtime_getfinalizer(void *p, bool del, void (**fn)(void*), const struct __go_fu
} }
void void
runtime_walkfintab(void (*fn)(void*), void (*addroot)(byte *, uintptr)) runtime_walkfintab(void (*fn)(void*), void (*addroot)(Obj))
{ {
void **key; void **key;
void **ekey; void **ekey;
@ -206,8 +206,8 @@ runtime_walkfintab(void (*fn)(void*), void (*addroot)(byte *, uintptr))
for(; key < ekey; key++) for(; key < ekey; key++)
if(*key != nil && *key != ((void*)-1)) if(*key != nil && *key != ((void*)-1))
fn(*key); fn(*key);
addroot((byte*)&fintab[i].fkey, sizeof(void*)); addroot((Obj){(byte*)&fintab[i].fkey, sizeof(void*), 0});
addroot((byte*)&fintab[i].val, sizeof(void*)); addroot((Obj){(byte*)&fintab[i].val, sizeof(void*), 0});
runtime_unlock(&fintab[i]); runtime_unlock(&fintab[i]);
} }
} }

View File

@ -9,6 +9,7 @@
#include "runtime.h" #include "runtime.h"
#include "arch.h" #include "arch.h"
#include "malloc.h" #include "malloc.h"
#include "mgc0.h"
#include "race.h" #include "race.h"
#ifdef USING_SPLIT_STACK #ifdef USING_SPLIT_STACK
@ -24,11 +25,13 @@ extern void * __splitstack_find_context (void *context[10], size_t *, void **,
enum { enum {
Debug = 0, Debug = 0,
DebugMark = 0, // run second pass to check mark DebugMark = 0, // run second pass to check mark
DataBlock = 8*1024,
// Four bits per word (see #defines below). // Four bits per word (see #defines below).
wordsPerBitmapWord = sizeof(void*)*8/4, wordsPerBitmapWord = sizeof(void*)*8/4,
bitShift = sizeof(void*)*8/4, bitShift = sizeof(void*)*8/4,
handoffThreshold = 4,
IntermediateBufferCapacity = 64,
}; };
// Bits in per-word bitmap. // Bits in per-word bitmap.
@ -81,12 +84,16 @@ uint32 runtime_worldsema = 1;
static int32 gctrace; static int32 gctrace;
// The size of Workbuf is N*PageSize.
typedef struct Workbuf Workbuf; typedef struct Workbuf Workbuf;
struct Workbuf struct Workbuf
{ {
LFNode node; // must be first #define SIZE (2*PageSize-sizeof(LFNode)-sizeof(uintptr))
LFNode node; // must be first
uintptr nobj; uintptr nobj;
byte *obj[512-(sizeof(LFNode)+sizeof(uintptr))/sizeof(byte*)]; Obj obj[SIZE/sizeof(Obj) - 1];
uint8 _padding[SIZE%sizeof(Obj) + sizeof(Obj)];
#undef SIZE
}; };
typedef struct Finalizer Finalizer; typedef struct Finalizer Finalizer;
@ -120,13 +127,6 @@ static Workbuf* getfull(Workbuf*);
static void putempty(Workbuf*); static void putempty(Workbuf*);
static Workbuf* handoff(Workbuf*); static Workbuf* handoff(Workbuf*);
typedef struct GcRoot GcRoot;
struct GcRoot
{
byte *p;
uintptr n;
};
static struct { static struct {
uint64 full; // lock-free list of full blocks uint64 full; // lock-free list of full blocks
uint64 empty; // lock-free list of empty blocks uint64 empty; // lock-free list of empty blocks
@ -143,77 +143,122 @@ static struct {
byte *chunk; byte *chunk;
uintptr nchunk; uintptr nchunk;
GcRoot *roots; Obj *roots;
uint32 nroot; uint32 nroot;
uint32 rootcap; uint32 rootcap;
} work; } work;
// scanblock scans a block of n bytes starting at pointer b for references enum {
// to other objects, scanning any it finds recursively until there are no // TODO(atom): to be expanded in a next CL
// unscanned objects left. Instead of using an explicit recursion, it keeps GC_DEFAULT_PTR = GC_NUM_INSTR,
// a work list in the Workbuf* structures and loops in the main function };
// body. Keeping an explicit work list is easier on the stack allocator and
// more efficient. // PtrTarget and BitTarget are structures used by intermediate buffers.
static void // The intermediate buffers hold GC data before it
scanblock(byte *b, uintptr n) // is moved/flushed to the work buffer (Workbuf).
// The size of an intermediate buffer is very small,
// such as 32 or 64 elements.
struct PtrTarget
{ {
byte *obj, *arena_start, *arena_used, *p; void *p;
void **vp; uintptr ti;
uintptr size, *bitp, bits, shift, i, j, x, xbits, off, nobj, nproc; };
struct BitTarget
{
void *p;
uintptr ti;
uintptr *bitp, shift;
};
struct BufferList
{
struct PtrTarget ptrtarget[IntermediateBufferCapacity];
struct BitTarget bittarget[IntermediateBufferCapacity];
struct BufferList *next;
};
static struct BufferList *bufferList;
static Lock lock;
// flushptrbuf moves data from the PtrTarget buffer to the work buffer.
// The PtrTarget buffer contains blocks irrespective of whether the blocks have been marked or scanned,
// while the work buffer contains blocks which have been marked
// and are prepared to be scanned by the garbage collector.
//
// _wp, _wbuf, _nobj are input/output parameters and are specifying the work buffer.
// bitbuf holds temporary data generated by this function.
//
// A simplified drawing explaining how the todo-list moves from a structure to another:
//
// scanblock
// (find pointers)
// Obj ------> PtrTarget (pointer targets)
// ↑ |
// | | flushptrbuf (1st part,
// | | find block start)
// | ↓
// `--------- BitTarget (pointer targets and the corresponding locations in bitmap)
// flushptrbuf
// (2nd part, mark and enqueue)
static void
flushptrbuf(struct PtrTarget *ptrbuf, uintptr n, Obj **_wp, Workbuf **_wbuf, uintptr *_nobj, struct BitTarget *bitbuf)
{
byte *p, *arena_start, *obj;
uintptr size, *bitp, bits, shift, j, x, xbits, off, nobj, ti;
MSpan *s; MSpan *s;
PageID k; PageID k;
void **wp; Obj *wp;
Workbuf *wbuf; Workbuf *wbuf;
bool keepworking; struct PtrTarget *ptrbuf_end;
struct BitTarget *bitbufpos, *bt;
if((intptr)n < 0) {
runtime_printf("scanblock %p %D\n", b, (int64)n);
runtime_throw("scanblock");
}
// Memory arena parameters.
arena_start = runtime_mheap.arena_start; arena_start = runtime_mheap.arena_start;
arena_used = runtime_mheap.arena_used;
nproc = work.nproc;
wbuf = nil; // current work buffer wp = *_wp;
wp = nil; // storage for next queued pointer (write pointer) wbuf = *_wbuf;
nobj = 0; // number of queued objects nobj = *_nobj;
// Scanblock helpers pass b==nil. ptrbuf_end = ptrbuf + n;
// Procs needs to return to make more
// calls to scanblock. But if work.nproc==1 then
// might as well process blocks as soon as we
// have them.
keepworking = b == nil || work.nproc == 1;
// Align b to a word boundary. // If buffer is nearly full, get a new one.
off = (uintptr)b & (PtrSize-1); if(wbuf == nil || nobj+n >= nelem(wbuf->obj)) {
if(off != 0) { if(wbuf != nil)
b += PtrSize - off; wbuf->nobj = nobj;
n -= PtrSize - off; wbuf = getempty(wbuf);
wp = wbuf->obj;
nobj = 0;
if(n >= nelem(wbuf->obj))
runtime_throw("ptrbuf has to be smaller than WorkBuf");
} }
for(;;) { // TODO(atom): This block is a branch of an if-then-else statement.
// Each iteration scans the block b of length n, queueing pointers in // The single-threaded branch may be added in a next CL.
// the work buffer. {
if(Debug > 1) // Multi-threaded version.
runtime_printf("scanblock %p %D\n", b, (int64)n);
vp = (void**)b; bitbufpos = bitbuf;
n >>= (2+PtrSize/8); /* n /= PtrSize (4 or 8) */
for(i=0; i<(uintptr)n; i++) {
obj = (byte*)vp[i];
// Words outside the arena cannot be pointers. while(ptrbuf < ptrbuf_end) {
if((byte*)obj < arena_start || (byte*)obj >= arena_used) obj = ptrbuf->p;
continue; ti = ptrbuf->ti;
ptrbuf++;
// obj belongs to interval [mheap.arena_start, mheap.arena_used).
if(Debug > 1) {
if(obj < runtime_mheap.arena_start || obj >= runtime_mheap.arena_used)
runtime_throw("object is outside of mheap");
}
// obj may be a pointer to a live object. // obj may be a pointer to a live object.
// Try to find the beginning of the object. // Try to find the beginning of the object.
// Round down to word boundary. // Round down to word boundary.
obj = (void*)((uintptr)obj & ~((uintptr)PtrSize-1)); if(((uintptr)obj & ((uintptr)PtrSize-1)) != 0) {
obj = (void*)((uintptr)obj & ~((uintptr)PtrSize-1));
ti = 0;
}
// Find bits for this word. // Find bits for this word.
off = (uintptr*)obj - (uintptr*)arena_start; off = (uintptr*)obj - (uintptr*)arena_start;
@ -226,6 +271,8 @@ scanblock(byte *b, uintptr n)
if((bits & (bitAllocated|bitBlockBoundary)) != 0) if((bits & (bitAllocated|bitBlockBoundary)) != 0)
goto found; goto found;
ti = 0;
// Pointing just past the beginning? // Pointing just past the beginning?
// Scan backward a little to find a block boundary. // Scan backward a little to find a block boundary.
for(j=shift; j-->0; ) { for(j=shift; j-->0; ) {
@ -246,13 +293,13 @@ scanblock(byte *b, uintptr n)
s = runtime_mheap.map[x]; s = runtime_mheap.map[x];
if(s == nil || k < s->start || k - s->start >= s->npages || s->state != MSpanInUse) if(s == nil || k < s->start || k - s->start >= s->npages || s->state != MSpanInUse)
continue; continue;
p = (byte*)((uintptr)s->start<<PageShift); p = (byte*)((uintptr)s->start<<PageShift);
if(s->sizeclass == 0) { if(s->sizeclass == 0) {
obj = p; obj = p;
} else { } else {
if((byte*)obj >= (byte*)s->limit) if((byte*)obj >= (byte*)s->limit)
continue; continue;
size = runtime_class_to_size[s->sizeclass]; size = s->elemsize;
int32 i = ((byte*)obj - p)/size; int32 i = ((byte*)obj - p)/size;
obj = p+i*size; obj = p+i*size;
} }
@ -265,81 +312,203 @@ scanblock(byte *b, uintptr n)
bits = xbits >> shift; bits = xbits >> shift;
found: found:
// If another proc wants a pointer, give it some.
if(work.nwait > 0 && nobj > 4 && work.full == 0) {
wbuf->nobj = nobj;
wbuf = handoff(wbuf);
nobj = wbuf->nobj;
wp = (void**)(wbuf->obj + nobj);
}
// Now we have bits, bitp, and shift correct for // Now we have bits, bitp, and shift correct for
// obj pointing at the base of the object. // obj pointing at the base of the object.
// Only care about allocated and not marked. // Only care about allocated and not marked.
if((bits & (bitAllocated|bitMarked)) != bitAllocated) if((bits & (bitAllocated|bitMarked)) != bitAllocated)
continue; continue;
if(nproc == 1)
*bitp |= bitMarked<<shift; *bitbufpos = (struct BitTarget){obj, ti, bitp, shift};
else { bitbufpos++;
for(;;) { }
x = *bitp;
if(x & (bitMarked<<shift)) runtime_lock(&lock);
goto continue_obj; for(bt=bitbuf; bt<bitbufpos; bt++){
if(runtime_casp((void**)bitp, (void*)x, (void*)(x|(bitMarked<<shift)))) xbits = *bt->bitp;
break; bits = xbits >> bt->shift;
} if((bits & bitMarked) != 0)
} continue;
// Mark the block
*bt->bitp = xbits | (bitMarked << bt->shift);
// If object has no pointers, don't need to scan further. // If object has no pointers, don't need to scan further.
if((bits & bitNoPointers) != 0) if((bits & bitNoPointers) != 0)
continue; continue;
obj = bt->p;
// Ask span about size class.
// (Manually inlined copy of MHeap_Lookup.)
x = (uintptr)obj >> PageShift;
if(sizeof(void*) == 8)
x -= (uintptr)arena_start>>PageShift;
s = runtime_mheap.map[x];
PREFETCH(obj); PREFETCH(obj);
// If buffer is full, get a new one. *wp = (Obj){obj, s->elemsize, bt->ti};
if(wbuf == nil || nobj >= nelem(wbuf->obj)) { wp++;
if(wbuf != nil)
wbuf->nobj = nobj;
wbuf = getempty(wbuf);
wp = (void**)(wbuf->obj);
nobj = 0;
}
*wp++ = obj;
nobj++; nobj++;
continue_obj:; }
runtime_unlock(&lock);
// If another proc wants a pointer, give it some.
if(work.nwait > 0 && nobj > handoffThreshold && work.full == 0) {
wbuf->nobj = nobj;
wbuf = handoff(wbuf);
nobj = wbuf->nobj;
wp = wbuf->obj + nobj;
}
}
*_wp = wp;
*_wbuf = wbuf;
*_nobj = nobj;
}
// Program that scans the whole block and treats every block element as a potential pointer
static uintptr defaultProg[2] = {PtrSize, GC_DEFAULT_PTR};
// scanblock scans a block of n bytes starting at pointer b for references
// to other objects, scanning any it finds recursively until there are no
// unscanned objects left. Instead of using an explicit recursion, it keeps
// a work list in the Workbuf* structures and loops in the main function
// body. Keeping an explicit work list is easier on the stack allocator and
// more efficient.
//
// wbuf: current work buffer
// wp: storage for next queued pointer (write pointer)
// nobj: number of queued objects
static void
scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
{
byte *b, *arena_start, *arena_used;
uintptr n, i, end_b;
void *obj;
// TODO(atom): to be expanded in a next CL
struct Frame {uintptr count, b; uintptr *loop_or_ret;};
struct Frame stack_top;
uintptr *pc;
struct BufferList *scanbuffers;
struct PtrTarget *ptrbuf, *ptrbuf_end;
struct BitTarget *bitbuf;
struct PtrTarget *ptrbufpos;
// End of local variable declarations.
if(sizeof(Workbuf) % PageSize != 0)
runtime_throw("scanblock: size of Workbuf is suboptimal");
// Memory arena parameters.
arena_start = runtime_mheap.arena_start;
arena_used = runtime_mheap.arena_used;
// Allocate ptrbuf, bitbuf
{
runtime_lock(&lock);
if(bufferList == nil) {
bufferList = runtime_SysAlloc(sizeof(*bufferList));
bufferList->next = nil;
}
scanbuffers = bufferList;
bufferList = bufferList->next;
ptrbuf = &scanbuffers->ptrtarget[0];
ptrbuf_end = &scanbuffers->ptrtarget[0] + nelem(scanbuffers->ptrtarget);
bitbuf = &scanbuffers->bittarget[0];
runtime_unlock(&lock);
}
ptrbufpos = ptrbuf;
goto next_block;
for(;;) {
// Each iteration scans the block b of length n, queueing pointers in
// the work buffer.
if(Debug > 1) {
runtime_printf("scanblock %p %D\n", b, (int64)n);
} }
// TODO(atom): to be replaced in a next CL
pc = defaultProg;
pc++;
stack_top.b = (uintptr)b;
end_b = (uintptr)b + n - PtrSize;
next_instr:
// TODO(atom): to be expanded in a next CL
switch(pc[0]) {
case GC_DEFAULT_PTR:
while(true) {
i = stack_top.b;
if(i > end_b)
goto next_block;
stack_top.b += PtrSize;
obj = *(byte**)i;
if((byte*)obj >= arena_start && (byte*)obj < arena_used) {
*ptrbufpos = (struct PtrTarget){obj, 0};
ptrbufpos++;
if(ptrbufpos == ptrbuf_end)
goto flush_buffers;
}
}
default:
runtime_throw("scanblock: invalid GC instruction");
return;
}
flush_buffers:
flushptrbuf(ptrbuf, ptrbufpos-ptrbuf, &wp, &wbuf, &nobj, bitbuf);
ptrbufpos = ptrbuf;
goto next_instr;
next_block:
// Done scanning [b, b+n). Prepare for the next iteration of // Done scanning [b, b+n). Prepare for the next iteration of
// the loop by setting b and n to the parameters for the next block. // the loop by setting b, n to the parameters for the next block.
if(nobj == 0) {
flushptrbuf(ptrbuf, ptrbufpos-ptrbuf, &wp, &wbuf, &nobj, bitbuf);
ptrbufpos = ptrbuf;
if(nobj == 0) {
if(!keepworking) {
if(wbuf)
putempty(wbuf);
goto endscan;
}
// Emptied our buffer: refill.
wbuf = getfull(wbuf);
if(wbuf == nil)
goto endscan;
nobj = wbuf->nobj;
wp = wbuf->obj + wbuf->nobj;
}
}
// Fetch b from the work buffer. // Fetch b from the work buffer.
if(nobj == 0) { --wp;
if(!keepworking) { b = wp->p;
if(wbuf) n = wp->n;
putempty(wbuf);
return;
}
// Emptied our buffer: refill.
wbuf = getfull(wbuf);
if(wbuf == nil)
return;
nobj = wbuf->nobj;
wp = (void**)(wbuf->obj + wbuf->nobj);
}
b = *--wp;
nobj--; nobj--;
// Ask span about size class.
// (Manually inlined copy of MHeap_Lookup.)
x = (uintptr)b>>PageShift;
if(sizeof(void*) == 8)
x -= (uintptr)arena_start>>PageShift;
s = runtime_mheap.map[x];
if(s->sizeclass == 0)
n = s->npages<<PageShift;
else
n = runtime_class_to_size[s->sizeclass];
} }
endscan:
runtime_lock(&lock);
scanbuffers->next = bufferList;
bufferList = scanbuffers;
runtime_unlock(&lock);
} }
// debug_scanblock is the debug copy of scanblock. // debug_scanblock is the debug copy of scanblock.
@ -386,13 +555,12 @@ debug_scanblock(byte *b, uintptr n)
continue; continue;
p = (byte*)((uintptr)s->start<<PageShift); p = (byte*)((uintptr)s->start<<PageShift);
size = s->elemsize;
if(s->sizeclass == 0) { if(s->sizeclass == 0) {
obj = p; obj = p;
size = (uintptr)s->npages<<PageShift;
} else { } else {
if((byte*)obj >= (byte*)s->limit) if((byte*)obj >= (byte*)s->limit)
continue; continue;
size = runtime_class_to_size[s->sizeclass];
int32 i = ((byte*)obj - p)/size; int32 i = ((byte*)obj - p)/size;
obj = p+i*size; obj = p+i*size;
} }
@ -421,11 +589,74 @@ debug_scanblock(byte *b, uintptr n)
} }
} }
// Append obj to the work buffer.
// _wbuf, _wp, _nobj are input/output parameters and are specifying the work buffer.
static void
enqueue(Obj obj, Workbuf **_wbuf, Obj **_wp, uintptr *_nobj)
{
uintptr nobj, off;
Obj *wp;
Workbuf *wbuf;
if(Debug > 1)
runtime_printf("append obj(%p %D %p)\n", obj.p, (int64)obj.n, obj.ti);
// Align obj.b to a word boundary.
off = (uintptr)obj.p & (PtrSize-1);
if(off != 0) {
obj.p += PtrSize - off;
obj.n -= PtrSize - off;
obj.ti = 0;
}
if(obj.p == nil || obj.n == 0)
return;
// Load work buffer state
wp = *_wp;
wbuf = *_wbuf;
nobj = *_nobj;
// If another proc wants a pointer, give it some.
if(work.nwait > 0 && nobj > handoffThreshold && work.full == 0) {
wbuf->nobj = nobj;
wbuf = handoff(wbuf);
nobj = wbuf->nobj;
wp = wbuf->obj + nobj;
}
// If buffer is full, get a new one.
if(wbuf == nil || nobj >= nelem(wbuf->obj)) {
if(wbuf != nil)
wbuf->nobj = nobj;
wbuf = getempty(wbuf);
wp = wbuf->obj;
nobj = 0;
}
*wp = obj;
wp++;
nobj++;
// Save work buffer state
*_wp = wp;
*_wbuf = wbuf;
*_nobj = nobj;
}
static void static void
markroot(ParFor *desc, uint32 i) markroot(ParFor *desc, uint32 i)
{ {
Obj *wp;
Workbuf *wbuf;
uintptr nobj;
USED(&desc); USED(&desc);
scanblock(work.roots[i].p, work.roots[i].n); wp = nil;
wbuf = nil;
nobj = 0;
enqueue(work.roots[i], &wbuf, &wp, &nobj);
scanblock(wbuf, wp, nobj, false);
} }
// Get an empty work buffer off the work.empty list, // Get an empty work buffer off the work.empty list,
@ -520,25 +751,24 @@ handoff(Workbuf *b)
} }
static void static void
addroot(byte *p, uintptr n) addroot(Obj obj)
{ {
uint32 cap; uint32 cap;
GcRoot *new; Obj *new;
if(work.nroot >= work.rootcap) { if(work.nroot >= work.rootcap) {
cap = PageSize/sizeof(GcRoot); cap = PageSize/sizeof(Obj);
if(cap < 2*work.rootcap) if(cap < 2*work.rootcap)
cap = 2*work.rootcap; cap = 2*work.rootcap;
new = (GcRoot*)runtime_SysAlloc(cap*sizeof(GcRoot)); new = (Obj*)runtime_SysAlloc(cap*sizeof(Obj));
if(work.roots != nil) { if(work.roots != nil) {
runtime_memmove(new, work.roots, work.rootcap*sizeof(GcRoot)); runtime_memmove(new, work.roots, work.rootcap*sizeof(Obj));
runtime_SysFree(work.roots, work.rootcap*sizeof(GcRoot)); runtime_SysFree(work.roots, work.rootcap*sizeof(Obj));
} }
work.roots = new; work.roots = new;
work.rootcap = cap; work.rootcap = cap;
} }
work.roots[work.nroot].p = p; work.roots[work.nroot] = obj;
work.roots[work.nroot].n = n;
work.nroot++; work.nroot++;
} }
@ -582,11 +812,11 @@ addstackroots(G *gp)
} }
} }
if(sp != nil) { if(sp != nil) {
addroot(sp, spsize); addroot((Obj){sp, spsize, 0});
while((sp = __splitstack_find(next_segment, next_sp, while((sp = __splitstack_find(next_segment, next_sp,
&spsize, &next_segment, &spsize, &next_segment,
&next_sp, &initial_sp)) != nil) &next_sp, &initial_sp)) != nil)
addroot(sp, spsize); addroot((Obj){sp, spsize, 0});
} }
#else #else
M *mp; M *mp;
@ -608,9 +838,9 @@ addstackroots(G *gp)
} }
top = (byte*)gp->gcinitial_sp + gp->gcstack_size; top = (byte*)gp->gcinitial_sp + gp->gcstack_size;
if(top > bottom) if(top > bottom)
addroot(bottom, top - bottom); addroot((Obj){bottom, top - bottom, 0});
else else
addroot(top, bottom - top); addroot((Obj){top, bottom - top, 0});
#endif #endif
} }
@ -624,7 +854,7 @@ addfinroots(void *v)
runtime_throw("mark - finalizer inconsistency"); runtime_throw("mark - finalizer inconsistency");
// do not mark the finalizer block itself. just mark the things it points at. // do not mark the finalizer block itself. just mark the things it points at.
addroot(v, size); addroot((Obj){v, size, 0});
} }
static struct root_list* roots; static struct root_list* roots;
@ -656,15 +886,15 @@ addroots(void)
void *decl = pr->decl; void *decl = pr->decl;
if(decl == nil) if(decl == nil)
break; break;
addroot(decl, pr->size); addroot((Obj){decl, pr->size, 0});
pr++; pr++;
} }
} }
addroot((byte*)&runtime_m0, sizeof runtime_m0); addroot((Obj){(byte*)&runtime_m0, sizeof runtime_m0, 0});
addroot((byte*)&runtime_g0, sizeof runtime_g0); addroot((Obj){(byte*)&runtime_g0, sizeof runtime_g0, 0});
addroot((byte*)&runtime_allg, sizeof runtime_allg); addroot((Obj){(byte*)&runtime_allg, sizeof runtime_allg, 0});
addroot((byte*)&runtime_allm, sizeof runtime_allm); addroot((Obj){(byte*)&runtime_allm, sizeof runtime_allm, 0});
runtime_MProf_Mark(addroot); runtime_MProf_Mark(addroot);
runtime_time_scan(addroot); runtime_time_scan(addroot);
runtime_trampoline_scan(addroot); runtime_trampoline_scan(addroot);
@ -680,12 +910,14 @@ addroots(void)
break; break;
case MTypes_Words: case MTypes_Words:
case MTypes_Bytes: case MTypes_Bytes:
addroot((byte*)&s->types.data, sizeof(void*)); // TODO(atom): consider using defaultProg instead of 0
addroot((Obj){(byte*)&s->types.data, sizeof(void*), 0});
break; break;
} }
} }
} }
// stacks
for(gp=runtime_allg; gp!=nil; gp=gp->alllink) { for(gp=runtime_allg; gp!=nil; gp=gp->alllink) {
switch(gp->status){ switch(gp->status){
default: default:
@ -709,9 +941,9 @@ addroots(void)
runtime_walkfintab(addfinroots, addroot); runtime_walkfintab(addfinroots, addroot);
for(fb=allfin; fb; fb=fb->alllink) for(fb=allfin; fb; fb=fb->alllink)
addroot((byte*)fb->fin, fb->cnt*sizeof(fb->fin[0])); addroot((Obj){(byte*)fb->fin, fb->cnt*sizeof(fb->fin[0]), 0});
addroot((byte*)&work, sizeof work); addroot((Obj){(byte*)&work, sizeof work, 0});
} }
static bool static bool
@ -955,8 +1187,9 @@ runtime_gchelper(void)
{ {
// parallel mark for over gc roots // parallel mark for over gc roots
runtime_parfordo(work.markfor); runtime_parfordo(work.markfor);
// help other threads scan secondary blocks // help other threads scan secondary blocks
scanblock(nil, 0); scanblock(nil, nil, 0, true);
if(DebugMark) { if(DebugMark) {
// wait while the main thread executes mark(debug_scanblock) // wait while the main thread executes mark(debug_scanblock)
@ -983,16 +1216,16 @@ static int32 gcpercent = -2;
static void static void
stealcache(void) stealcache(void)
{ {
M *m; M *mp;
for(m=runtime_allm; m; m=m->alllink) for(mp=runtime_allm; mp; mp=mp->alllink)
runtime_MCache_ReleaseAll(m->mcache); runtime_MCache_ReleaseAll(mp->mcache);
} }
static void static void
cachestats(GCStats *stats) cachestats(GCStats *stats)
{ {
M *m; M *mp;
MCache *c; MCache *c;
uint32 i; uint32 i;
uint64 stacks_inuse; uint64 stacks_inuse;
@ -1003,17 +1236,17 @@ cachestats(GCStats *stats)
runtime_memclr((byte*)stats, sizeof(*stats)); runtime_memclr((byte*)stats, sizeof(*stats));
stacks_inuse = 0; stacks_inuse = 0;
stacks_sys = runtime_stacks_sys; stacks_sys = runtime_stacks_sys;
for(m=runtime_allm; m; m=m->alllink) { for(mp=runtime_allm; mp; mp=mp->alllink) {
c = m->mcache; c = mp->mcache;
runtime_purgecachedstats(c); runtime_purgecachedstats(c);
// stacks_inuse += m->stackalloc->inuse; // stacks_inuse += mp->stackalloc->inuse;
// stacks_sys += m->stackalloc->sys; // stacks_sys += mp->stackalloc->sys;
if(stats) { if(stats) {
src = (uint64*)&m->gcstats; src = (uint64*)&mp->gcstats;
dst = (uint64*)stats; dst = (uint64*)stats;
for(i=0; i<sizeof(*stats)/sizeof(uint64); i++) for(i=0; i<sizeof(*stats)/sizeof(uint64); i++)
dst[i] += src[i]; dst[i] += src[i];
runtime_memclr((byte*)&m->gcstats, sizeof(m->gcstats)); runtime_memclr((byte*)&mp->gcstats, sizeof(mp->gcstats));
} }
for(i=0; i<nelem(c->local_by_size); i++) { for(i=0; i<nelem(c->local_by_size); i++) {
mstats.by_size[i].nmalloc += c->local_by_size[i].nmalloc; mstats.by_size[i].nmalloc += c->local_by_size[i].nmalloc;
@ -1100,7 +1333,7 @@ gc(struct gc_args *args)
int64 t0, t1, t2, t3; int64 t0, t1, t2, t3;
uint64 heap0, heap1, obj0, obj1; uint64 heap0, heap1, obj0, obj1;
GCStats stats; GCStats stats;
M *m1; M *mp;
uint32 i; uint32 i;
runtime_semacquire(&runtime_worldsema); runtime_semacquire(&runtime_worldsema);
@ -1116,8 +1349,8 @@ gc(struct gc_args *args)
m->gcing = 1; m->gcing = 1;
runtime_stoptheworld(); runtime_stoptheworld();
for(m1=runtime_allm; m1; m1=m1->alllink) for(mp=runtime_allm; mp; mp=mp->alllink)
runtime_settype_flush(m1, false); runtime_settype_flush(mp, false);
heap0 = 0; heap0 = 0;
obj0 = 0; obj0 = 0;
@ -1127,26 +1360,27 @@ gc(struct gc_args *args)
obj0 = mstats.nmalloc - mstats.nfree; obj0 = mstats.nmalloc - mstats.nfree;
} }
m->locks++; // disable gc during mallocs in parforalloc
if(work.markfor == nil)
work.markfor = runtime_parforalloc(MaxGcproc);
if(work.sweepfor == nil)
work.sweepfor = runtime_parforalloc(MaxGcproc);
m->locks--;
work.nwait = 0; work.nwait = 0;
work.ndone = 0; work.ndone = 0;
work.debugmarkdone = 0; work.debugmarkdone = 0;
work.nproc = runtime_gcprocs(); work.nproc = runtime_gcprocs();
addroots(); addroots();
m->locks++; // disable gc during mallocs in parforalloc
if(work.markfor == nil)
work.markfor = runtime_parforalloc(MaxGcproc);
runtime_parforsetup(work.markfor, work.nproc, work.nroot, nil, false, markroot); runtime_parforsetup(work.markfor, work.nproc, work.nroot, nil, false, markroot);
if(work.sweepfor == nil)
work.sweepfor = runtime_parforalloc(MaxGcproc);
runtime_parforsetup(work.sweepfor, work.nproc, runtime_mheap.nspan, nil, true, sweepspan); runtime_parforsetup(work.sweepfor, work.nproc, runtime_mheap.nspan, nil, true, sweepspan);
m->locks--;
if(work.nproc > 1) { if(work.nproc > 1) {
runtime_noteclear(&work.alldone); runtime_noteclear(&work.alldone);
runtime_helpgc(work.nproc); runtime_helpgc(work.nproc);
} }
runtime_parfordo(work.markfor); runtime_parfordo(work.markfor);
scanblock(nil, 0); scanblock(nil, nil, 0, true);
if(DebugMark) { if(DebugMark) {
for(i=0; i<work.nroot; i++) for(i=0; i<work.nroot; i++)

42
libgo/runtime/mgc0.h Normal file
View File

@ -0,0 +1,42 @@
// 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.
// Garbage collector (GC)
// GC instruction opcodes.
//
// The opcode of an instruction is followed by zero or more
// arguments to the instruction.
//
// Meaning of arguments:
// off Offset (in bytes) from the start of the current object
// objgc Pointer to GC info of an object
// len Length of an array
// elemsize Size (in bytes) of an element
// size Size (in bytes)
enum {
GC_END, // End of object, loop or subroutine. Args: none
GC_PTR, // A typed pointer. Args: (off, objgc)
GC_APTR, // Pointer to an arbitrary object. Args: (off)
GC_ARRAY_START, // Start an array with a fixed length. Args: (off, len, elemsize)
GC_ARRAY_NEXT, // The next element of an array. Args: none
GC_CALL, // Call a subroutine. Args: (off, objgc)
GC_MAP_PTR, // Go map. Args: (off, MapType*)
GC_STRING, // Go string. Args: (off)
GC_EFACE, // interface{}. Args: (off)
GC_IFACE, // interface{...}. Args: (off)
GC_SLICE, // Go slice. Args: (off, objgc)
GC_REGION, // A region/part of the current object. Args: (off, size, objgc)
GC_NUM_INSTR, // Number of instruction opcodes
};
enum {
// Size of GC's fixed stack.
//
// The current GC implementation permits:
// - at most 1 stack allocation because of GC_CALL
// - at most GC_STACK_CAPACITY allocations because of GC_ARRAY_START
GC_STACK_CAPACITY = 8,
};

View File

@ -362,13 +362,13 @@ func MemProfile(p Slice, include_inuse_zero bool) (n int, ok bool) {
} }
void void
runtime_MProf_Mark(void (*addroot)(byte *, uintptr)) runtime_MProf_Mark(void (*addroot)(Obj))
{ {
// buckhash is not allocated via mallocgc. // buckhash is not allocated via mallocgc.
addroot((byte*)&mbuckets, sizeof mbuckets); addroot((Obj){(byte*)&mbuckets, sizeof mbuckets, 0});
addroot((byte*)&bbuckets, sizeof bbuckets); addroot((Obj){(byte*)&bbuckets, sizeof bbuckets, 0});
addroot((byte*)&addrhash, sizeof addrhash); addroot((Obj){(byte*)&addrhash, sizeof addrhash, 0});
addroot((byte*)&addrfree, sizeof addrfree); addroot((Obj){(byte*)&addrfree, sizeof addrfree, 0});
} }
// Must match BlockProfileRecord in debug.go. // Must match BlockProfileRecord in debug.go.
@ -412,18 +412,18 @@ struct TRecord {
func ThreadCreateProfile(p Slice) (n int, ok bool) { func ThreadCreateProfile(p Slice) (n int, ok bool) {
TRecord *r; TRecord *r;
M *first, *m; M *first, *mp;
first = runtime_atomicloadp(&runtime_allm); first = runtime_atomicloadp(&runtime_allm);
n = 0; n = 0;
for(m=first; m; m=m->alllink) for(mp=first; mp; mp=mp->alllink)
n++; n++;
ok = false; ok = false;
if(n <= p.__count) { if(n <= p.__count) {
ok = true; ok = true;
r = (TRecord*)p.__values; r = (TRecord*)p.__values;
for(m=first; m; m=m->alllink) { for(mp=first; mp; mp=mp->alllink) {
runtime_memmove(r->stk, m->createstack, sizeof r->stk); runtime_memmove(r->stk, mp->createstack, sizeof r->stk);
r++; r++;
} }
} }
@ -471,11 +471,11 @@ func Stack(b Slice, all bool) (n int) {
} }
static void static void
saveg(G *g, TRecord *r) saveg(G *gp, TRecord *r)
{ {
int32 n; int32 n;
if(g == runtime_g()) if(gp == runtime_g())
n = runtime_callers(0, r->stk, nelem(r->stk)); n = runtime_callers(0, r->stk, nelem(r->stk));
else { else {
// FIXME: Not implemented. // FIXME: Not implemented.

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