Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56ebf80e57 | ||
|
|
b1959cf6f7 | ||
|
|
cdd8cf4988 | ||
|
|
8995e84ac6 | ||
|
|
749dff880a | ||
|
|
21ac81c1e1 | ||
|
|
c72fcab6d6 | ||
|
|
6e676ab2b8 | ||
|
|
ac94297758 | ||
|
|
6961c3775f | ||
|
|
ebee011a54 | ||
|
|
84fb1b8253 | ||
|
|
c95d3093ca | ||
|
|
561964c9a8 | ||
|
|
e73dadc758 | ||
|
|
2899144b8d | ||
|
|
b062eb46e8 | ||
|
|
8ac5714ef2 | ||
|
|
9546293d22 | ||
|
|
4b3a0b9785 | ||
|
|
5abb1d84f8 |
@@ -1 +1,2 @@
|
||||
branch: master
|
||||
branch: release-branch.go1.25
|
||||
parent-branch: master
|
||||
|
||||
@@ -109,6 +109,9 @@ func ModIsPrefix(path, vers string) bool {
|
||||
// The caller is assumed to have checked that ModIsValid(path, vers) is true.
|
||||
func ModIsPrerelease(path, vers string) bool {
|
||||
if IsToolchain(path) {
|
||||
if path == "toolchain" {
|
||||
return IsPrerelease(FromToolchain(vers))
|
||||
}
|
||||
return IsPrerelease(vers)
|
||||
}
|
||||
return semver.Prerelease(vers) != ""
|
||||
|
||||
@@ -277,6 +277,29 @@ func loadModTool(ctx context.Context, name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func builtTool(runAction *work.Action) string {
|
||||
linkAction := runAction.Deps[0]
|
||||
if toolN {
|
||||
// #72824: If -n is set, use the cached path if we can.
|
||||
// This is only necessary if the binary wasn't cached
|
||||
// before this invocation of the go command: if the binary
|
||||
// was cached, BuiltTarget() will be the cached executable.
|
||||
// It's only in the "first run", where we actually do the build
|
||||
// and save the result to the cache that BuiltTarget is not
|
||||
// the cached binary. Ideally, we would set BuiltTarget
|
||||
// to the cached path even in the first run, but if we
|
||||
// copy the binary to the cached path, and try to run it
|
||||
// in the same process, we'll run into the dreaded #22315
|
||||
// resulting in occasional ETXTBSYs. Instead of getting the
|
||||
// ETXTBSY and then retrying just don't use the cached path
|
||||
// on the first run if we're going to actually run the binary.
|
||||
if cached := linkAction.CachedExecutable(); cached != "" {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
return linkAction.BuiltTarget()
|
||||
}
|
||||
|
||||
func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []string) {
|
||||
// Override GOOS and GOARCH for the build to build the tool using
|
||||
// the same GOOS and GOARCH as this go command.
|
||||
@@ -288,7 +311,7 @@ func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []s
|
||||
modload.RootMode = modload.NoRoot
|
||||
|
||||
runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
|
||||
cmdline := str.StringList(a.Deps[0].BuiltTarget(), a.Args)
|
||||
cmdline := str.StringList(builtTool(a), a.Args)
|
||||
return runBuiltTool(toolName, nil, cmdline)
|
||||
}
|
||||
|
||||
@@ -300,7 +323,7 @@ func buildAndRunModtool(ctx context.Context, toolName, tool string, args []strin
|
||||
// Use the ExecCmd to run the binary, as go run does. ExecCmd allows users
|
||||
// to provide a runner to run the binary, for example a simulator for binaries
|
||||
// that are cross-compiled to a different platform.
|
||||
cmdline := str.StringList(work.FindExecCmd(), a.Deps[0].BuiltTarget(), a.Args)
|
||||
cmdline := str.StringList(work.FindExecCmd(), builtTool(a), a.Args)
|
||||
// Use same environment go run uses to start the executable:
|
||||
// the original environment with cfg.GOROOTbin added to the path.
|
||||
env := slices.Clip(cfg.OrigEnv)
|
||||
|
||||
@@ -97,11 +97,12 @@ type Action struct {
|
||||
CacheExecutable bool // Whether to cache executables produced by link steps
|
||||
|
||||
// Generated files, directories.
|
||||
Objdir string // directory for intermediate objects
|
||||
Target string // goal of the action: the created package or executable
|
||||
built string // the actual created package or executable
|
||||
actionID cache.ActionID // cache ID of action input
|
||||
buildID string // build ID of action output
|
||||
Objdir string // directory for intermediate objects
|
||||
Target string // goal of the action: the created package or executable
|
||||
built string // the actual created package or executable
|
||||
cachedExecutable string // the cached executable, if CacheExecutable was set
|
||||
actionID cache.ActionID // cache ID of action input
|
||||
buildID string // build ID of action output
|
||||
|
||||
VetxOnly bool // Mode=="vet": only being called to supply info about dependencies
|
||||
needVet bool // Mode=="build": need to fill in vet config
|
||||
@@ -133,6 +134,10 @@ func (a *Action) BuildID() string { return a.buildID }
|
||||
// from Target when the result was cached.
|
||||
func (a *Action) BuiltTarget() string { return a.built }
|
||||
|
||||
// CachedExecutable returns the cached executable, if CacheExecutable
|
||||
// was set and the executable could be cached, and "" otherwise.
|
||||
func (a *Action) CachedExecutable() string { return a.cachedExecutable }
|
||||
|
||||
// An actionQueue is a priority queue of actions.
|
||||
type actionQueue []*Action
|
||||
|
||||
|
||||
@@ -745,8 +745,9 @@ func (b *Builder) updateBuildID(a *Action, target string) error {
|
||||
}
|
||||
outputID, _, err := c.PutExecutable(a.actionID, name+cfg.ExeSuffix, r)
|
||||
r.Close()
|
||||
a.cachedExecutable = c.OutputFile(outputID)
|
||||
if err == nil && cfg.BuildX {
|
||||
sh.ShowCmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, c.OutputFile(outputID))))
|
||||
sh.ShowCmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, a.cachedExecutable)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
src/cmd/go/testdata/script/mod_get_toolchain.txt
vendored
10
src/cmd/go/testdata/script/mod_get_toolchain.txt
vendored
@@ -94,12 +94,14 @@ stderr '^go: added toolchain go1.24rc1$'
|
||||
grep 'go 1.22.9' go.mod # no longer implied
|
||||
grep 'toolchain go1.24rc1' go.mod
|
||||
|
||||
# go get toolchain@latest finds go1.999testmod.
|
||||
# go get toolchain@latest finds go1.23.9.
|
||||
cp go.mod.orig go.mod
|
||||
go get toolchain@latest
|
||||
stderr '^go: added toolchain go1.999testmod$'
|
||||
stderr '^go: added toolchain go1.23.9$'
|
||||
grep 'go 1.21' go.mod
|
||||
grep 'toolchain go1.999testmod' go.mod
|
||||
grep 'toolchain go1.23.9' go.mod
|
||||
|
||||
|
||||
|
||||
# Bug fixes.
|
||||
|
||||
@@ -115,7 +117,7 @@ stderr '^go: upgraded go 1.19 => 1.21.0'
|
||||
|
||||
# go get toolchain@1.24rc1 is OK too.
|
||||
go get toolchain@1.24rc1
|
||||
stderr '^go: downgraded toolchain go1.999testmod => go1.24rc1$'
|
||||
stderr '^go: upgraded toolchain go1.23.9 => go1.24rc1$'
|
||||
|
||||
# go get go@1.21 should work if we are the Go 1.21 language version,
|
||||
# even though there's no toolchain for it.
|
||||
|
||||
27
src/cmd/go/testdata/script/tool_n_issue72824.txt
vendored
Normal file
27
src/cmd/go/testdata/script/tool_n_issue72824.txt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
[short] skip 'does a build in using an empty cache'
|
||||
|
||||
# Start with a fresh cache because we want to verify the behavior
|
||||
# when the tool hasn't been cached previously.
|
||||
env GOCACHE=$WORK${/}cache
|
||||
|
||||
# Even when the tool hasn't been previously cached but was built and
|
||||
# saved to the cache in the invocation of 'go tool -n' we should return
|
||||
# its cached location.
|
||||
go tool -n foo
|
||||
stdout $GOCACHE
|
||||
|
||||
# And of course we should also return the cached location on subsequent
|
||||
# runs.
|
||||
go tool -n foo
|
||||
stdout $GOCACHE
|
||||
|
||||
-- go.mod --
|
||||
module example.com/foo
|
||||
|
||||
go 1.25
|
||||
|
||||
tool example.com/foo
|
||||
-- main.go --
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
@@ -335,7 +335,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
|
||||
if rows == nil {
|
||||
return errors.New("invalid context to convert cursor rows, missing parent *Rows")
|
||||
}
|
||||
rows.closemu.Lock()
|
||||
*d = Rows{
|
||||
dc: rows.dc,
|
||||
releaseConn: func(error) {},
|
||||
@@ -351,7 +350,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
|
||||
parentCancel()
|
||||
}
|
||||
}
|
||||
rows.closemu.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -91,8 +91,6 @@ func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) {
|
||||
type fakeDB struct {
|
||||
name string
|
||||
|
||||
useRawBytes atomic.Bool
|
||||
|
||||
mu sync.Mutex
|
||||
tables map[string]*table
|
||||
badConn bool
|
||||
@@ -684,8 +682,6 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm
|
||||
switch cmd {
|
||||
case "WIPE":
|
||||
// Nothing
|
||||
case "USE_RAWBYTES":
|
||||
c.db.useRawBytes.Store(true)
|
||||
case "SELECT":
|
||||
stmt, err = c.prepareSelect(stmt, parts)
|
||||
case "CREATE":
|
||||
@@ -789,9 +785,6 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d
|
||||
case "WIPE":
|
||||
db.wipe()
|
||||
return driver.ResultNoRows, nil
|
||||
case "USE_RAWBYTES":
|
||||
s.c.db.useRawBytes.Store(true)
|
||||
return driver.ResultNoRows, nil
|
||||
case "CREATE":
|
||||
if err := db.createTable(s.table, s.colName, s.colType); err != nil {
|
||||
return nil, err
|
||||
@@ -1076,10 +1069,9 @@ type rowsCursor struct {
|
||||
errPos int
|
||||
err error
|
||||
|
||||
// a clone of slices to give out to clients, indexed by the
|
||||
// original slice's first byte address. we clone them
|
||||
// just so we're able to corrupt them on close.
|
||||
bytesClone map[*byte][]byte
|
||||
// Data returned to clients.
|
||||
// We clone and stash it here so it can be invalidated by Close and Next.
|
||||
driverOwnedMemory [][]byte
|
||||
|
||||
// Every operation writes to line to enable the race detector
|
||||
// check for data races.
|
||||
@@ -1096,9 +1088,19 @@ func (rc *rowsCursor) touchMem() {
|
||||
rc.line++
|
||||
}
|
||||
|
||||
func (rc *rowsCursor) invalidateDriverOwnedMemory() {
|
||||
for _, buf := range rc.driverOwnedMemory {
|
||||
for i := range buf {
|
||||
buf[i] = 'x'
|
||||
}
|
||||
}
|
||||
rc.driverOwnedMemory = nil
|
||||
}
|
||||
|
||||
func (rc *rowsCursor) Close() error {
|
||||
rc.touchMem()
|
||||
rc.parentMem.touchMem()
|
||||
rc.invalidateDriverOwnedMemory()
|
||||
rc.closed = true
|
||||
return rc.closeErr
|
||||
}
|
||||
@@ -1129,6 +1131,8 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
|
||||
if rc.posRow >= len(rc.rows[rc.posSet]) {
|
||||
return io.EOF // per interface spec
|
||||
}
|
||||
// Corrupt any previously returned bytes.
|
||||
rc.invalidateDriverOwnedMemory()
|
||||
for i, v := range rc.rows[rc.posSet][rc.posRow].cols {
|
||||
// TODO(bradfitz): convert to subset types? naah, I
|
||||
// think the subset types should only be input to
|
||||
@@ -1136,20 +1140,13 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
|
||||
// a wider range of types coming out of drivers. all
|
||||
// for ease of drivers, and to prevent drivers from
|
||||
// messing up conversions or doing them differently.
|
||||
dest[i] = v
|
||||
|
||||
if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() {
|
||||
if rc.bytesClone == nil {
|
||||
rc.bytesClone = make(map[*byte][]byte)
|
||||
}
|
||||
clone, ok := rc.bytesClone[&bs[0]]
|
||||
if !ok {
|
||||
clone = make([]byte, len(bs))
|
||||
copy(clone, bs)
|
||||
rc.bytesClone[&bs[0]] = clone
|
||||
}
|
||||
dest[i] = clone
|
||||
if bs, ok := v.([]byte); ok {
|
||||
// Clone []bytes and stash for later invalidation.
|
||||
bs = bytes.Clone(bs)
|
||||
rc.driverOwnedMemory = append(rc.driverOwnedMemory, bs)
|
||||
v = bs
|
||||
}
|
||||
dest[i] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3368,38 +3368,36 @@ func (rs *Rows) Scan(dest ...any) error {
|
||||
// without calling Next.
|
||||
return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)")
|
||||
}
|
||||
|
||||
rs.closemu.RLock()
|
||||
|
||||
if rs.lasterr != nil && rs.lasterr != io.EOF {
|
||||
rs.closemu.RUnlock()
|
||||
return rs.lasterr
|
||||
}
|
||||
if rs.closed {
|
||||
err := rs.lasterrOrErrLocked(errRowsClosed)
|
||||
rs.closemu.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
if scanArgsContainRawBytes(dest) {
|
||||
rs.raw = rs.raw[:0]
|
||||
err := rs.scanLocked(dest...)
|
||||
if err == nil && scanArgsContainRawBytes(dest) {
|
||||
rs.closemuScanHold = true
|
||||
rs.raw = rs.raw[:0]
|
||||
} else {
|
||||
rs.closemu.RUnlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *Rows) scanLocked(dest ...any) error {
|
||||
if rs.lasterr != nil && rs.lasterr != io.EOF {
|
||||
return rs.lasterr
|
||||
}
|
||||
if rs.closed {
|
||||
return rs.lasterrOrErrLocked(errRowsClosed)
|
||||
}
|
||||
|
||||
if rs.lastcols == nil {
|
||||
rs.closemuRUnlockIfHeldByScan()
|
||||
return errors.New("sql: Scan called without calling Next")
|
||||
}
|
||||
if len(dest) != len(rs.lastcols) {
|
||||
rs.closemuRUnlockIfHeldByScan()
|
||||
return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
|
||||
}
|
||||
|
||||
for i, sv := range rs.lastcols {
|
||||
err := convertAssignRows(dest[i], sv, rs)
|
||||
if err != nil {
|
||||
rs.closemuRUnlockIfHeldByScan()
|
||||
return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@@ -4434,10 +4435,6 @@ func testContextCancelDuringRawBytesScan(t *testing.T, mode string) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
|
||||
if _, err := db.Exec("USE_RAWBYTES"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// cancel used to call close asynchronously.
|
||||
// This test checks that it waits so as not to interfere with RawBytes.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -4529,6 +4526,61 @@ func TestContextCancelBetweenNextAndErr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testScanner struct {
|
||||
scanf func(src any) error
|
||||
}
|
||||
|
||||
func (ts testScanner) Scan(src any) error { return ts.scanf(src) }
|
||||
|
||||
func TestContextCancelDuringScan(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
scanStart := make(chan any)
|
||||
scanEnd := make(chan error)
|
||||
scanner := &testScanner{
|
||||
scanf: func(src any) error {
|
||||
scanStart <- src
|
||||
return <-scanEnd
|
||||
},
|
||||
}
|
||||
|
||||
// Start a query, and pause it mid-scan.
|
||||
want := []byte("Alice")
|
||||
r, err := db.QueryContext(ctx, "SELECT|people|name|name=?", string(want))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !r.Next() {
|
||||
t.Fatalf("r.Next() = false, want true")
|
||||
}
|
||||
go func() {
|
||||
r.Scan(scanner)
|
||||
}()
|
||||
got := <-scanStart
|
||||
defer close(scanEnd)
|
||||
gotBytes, ok := got.([]byte)
|
||||
if !ok {
|
||||
t.Fatalf("r.Scan returned %T, want []byte", got)
|
||||
}
|
||||
if !bytes.Equal(gotBytes, want) {
|
||||
t.Fatalf("before cancel: r.Scan returned %q, want %q", gotBytes, want)
|
||||
}
|
||||
|
||||
// Cancel the query.
|
||||
// Sleep to give it a chance to finish canceling.
|
||||
cancel()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Cancelling the query should not have changed the result.
|
||||
if !bytes.Equal(gotBytes, want) {
|
||||
t.Fatalf("after cancel: r.Scan result is now %q, want %q", gotBytes, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilErrorAfterClose(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
@@ -4562,10 +4614,6 @@ func TestRawBytesReuse(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
|
||||
if _, err := db.Exec("USE_RAWBYTES"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var raw RawBytes
|
||||
|
||||
// The RawBytes in this query aliases driver-owned memory.
|
||||
|
||||
@@ -1106,6 +1106,12 @@ func (fd *FD) Seek(offset int64, whence int) (int64, error) {
|
||||
fd.l.Lock()
|
||||
defer fd.l.Unlock()
|
||||
|
||||
if !fd.isBlocking && whence == io.SeekCurrent {
|
||||
// Windows doesn't keep the file pointer for overlapped file handles.
|
||||
// We do it ourselves in case to account for any read or write
|
||||
// operations that may have occurred.
|
||||
offset += fd.offset
|
||||
}
|
||||
n, err := syscall.Seek(fd.Sysfd, offset, whence)
|
||||
fd.setOffset(n)
|
||||
return n, err
|
||||
|
||||
@@ -383,57 +383,59 @@ func TestChannelMovedOutOfBubble(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
f func(chan struct{})
|
||||
wantPanic string
|
||||
wantFatal string
|
||||
}{{
|
||||
desc: "receive",
|
||||
f: func(ch chan struct{}) {
|
||||
<-ch
|
||||
},
|
||||
wantPanic: "receive on synctest channel from outside bubble",
|
||||
wantFatal: "receive on synctest channel from outside bubble",
|
||||
}, {
|
||||
desc: "send",
|
||||
f: func(ch chan struct{}) {
|
||||
ch <- struct{}{}
|
||||
},
|
||||
wantPanic: "send on synctest channel from outside bubble",
|
||||
wantFatal: "send on synctest channel from outside bubble",
|
||||
}, {
|
||||
desc: "close",
|
||||
f: func(ch chan struct{}) {
|
||||
close(ch)
|
||||
},
|
||||
wantPanic: "close of synctest channel from outside bubble",
|
||||
wantFatal: "close of synctest channel from outside bubble",
|
||||
}} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
// Bubbled channel accessed from outside any bubble.
|
||||
t.Run("outside_bubble", func(t *testing.T) {
|
||||
donec := make(chan struct{})
|
||||
ch := make(chan chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
defer wantPanic(t, test.wantPanic)
|
||||
test.f(<-ch)
|
||||
}()
|
||||
synctest.Run(func() {
|
||||
ch <- make(chan struct{})
|
||||
wantFatal(t, test.wantFatal, func() {
|
||||
donec := make(chan struct{})
|
||||
ch := make(chan chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
test.f(<-ch)
|
||||
}()
|
||||
synctest.Run(func() {
|
||||
ch <- make(chan struct{})
|
||||
})
|
||||
<-donec
|
||||
})
|
||||
<-donec
|
||||
})
|
||||
// Bubbled channel accessed from a different bubble.
|
||||
t.Run("different_bubble", func(t *testing.T) {
|
||||
donec := make(chan struct{})
|
||||
ch := make(chan chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
c := <-ch
|
||||
wantFatal(t, test.wantFatal, func() {
|
||||
donec := make(chan struct{})
|
||||
ch := make(chan chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
c := <-ch
|
||||
synctest.Run(func() {
|
||||
test.f(c)
|
||||
})
|
||||
}()
|
||||
synctest.Run(func() {
|
||||
defer wantPanic(t, test.wantPanic)
|
||||
test.f(c)
|
||||
ch <- make(chan struct{})
|
||||
})
|
||||
}()
|
||||
synctest.Run(func() {
|
||||
ch <- make(chan struct{})
|
||||
<-donec
|
||||
})
|
||||
<-donec
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -443,39 +445,40 @@ func TestTimerFromInsideBubble(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
f func(tm *time.Timer)
|
||||
wantPanic string
|
||||
wantFatal string
|
||||
}{{
|
||||
desc: "read channel",
|
||||
f: func(tm *time.Timer) {
|
||||
<-tm.C
|
||||
},
|
||||
wantPanic: "receive on synctest channel from outside bubble",
|
||||
wantFatal: "receive on synctest channel from outside bubble",
|
||||
}, {
|
||||
desc: "Reset",
|
||||
f: func(tm *time.Timer) {
|
||||
tm.Reset(1 * time.Second)
|
||||
},
|
||||
wantPanic: "reset of synctest timer from outside bubble",
|
||||
wantFatal: "reset of synctest timer from outside bubble",
|
||||
}, {
|
||||
desc: "Stop",
|
||||
f: func(tm *time.Timer) {
|
||||
tm.Stop()
|
||||
},
|
||||
wantPanic: "stop of synctest timer from outside bubble",
|
||||
wantFatal: "stop of synctest timer from outside bubble",
|
||||
}} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
donec := make(chan struct{})
|
||||
ch := make(chan *time.Timer)
|
||||
go func() {
|
||||
defer close(donec)
|
||||
defer wantPanic(t, test.wantPanic)
|
||||
test.f(<-ch)
|
||||
}()
|
||||
synctest.Run(func() {
|
||||
tm := time.NewTimer(1 * time.Second)
|
||||
ch <- tm
|
||||
wantFatal(t, test.wantFatal, func() {
|
||||
donec := make(chan struct{})
|
||||
ch := make(chan *time.Timer)
|
||||
go func() {
|
||||
defer close(donec)
|
||||
test.f(<-ch)
|
||||
}()
|
||||
synctest.Run(func() {
|
||||
tm := time.NewTimer(1 * time.Second)
|
||||
ch <- tm
|
||||
})
|
||||
<-donec
|
||||
})
|
||||
<-donec
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,13 +77,21 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var noopHandler = HandlerFunc(func(w ResponseWriter, r *Request) {})
|
||||
type noopHandler struct{}
|
||||
|
||||
func (noopHandler) ServeHTTP(ResponseWriter, *Request) {}
|
||||
|
||||
var sentinelHandler Handler = &noopHandler{}
|
||||
|
||||
// AddInsecureBypassPattern permits all requests that match the given pattern.
|
||||
// The pattern syntax and precedence rules are the same as [ServeMux].
|
||||
//
|
||||
// AddInsecureBypassPattern can be called concurrently with other methods
|
||||
// or request handling, and applies to future requests.
|
||||
// The pattern syntax and precedence rules are the same as [ServeMux]. Only
|
||||
// requests that match the pattern directly are permitted. Those that ServeMux
|
||||
// would redirect to a pattern (e.g. after cleaning the path or adding a
|
||||
// trailing slash) are not.
|
||||
//
|
||||
// AddInsecureBypassPattern can be called concurrently with other methods or
|
||||
// request handling, and applies to future requests.
|
||||
func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
|
||||
var bypass *ServeMux
|
||||
|
||||
@@ -99,7 +107,7 @@ func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
|
||||
}
|
||||
}
|
||||
|
||||
bypass.Handle(pattern, noopHandler)
|
||||
bypass.Handle(pattern, sentinelHandler)
|
||||
}
|
||||
|
||||
// SetDenyHandler sets a handler to invoke when a request is rejected.
|
||||
@@ -172,7 +180,7 @@ var (
|
||||
// be deferred until the last moment.
|
||||
func (c *CrossOriginProtection) isRequestExempt(req *Request) bool {
|
||||
if bypass := c.bypass.Load(); bypass != nil {
|
||||
if _, pattern := bypass.Handler(req); pattern != "" {
|
||||
if h, _ := bypass.Handler(req); h == sentinelHandler {
|
||||
// The request matches a bypass pattern.
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -113,6 +113,11 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) {
|
||||
protection := http.NewCrossOriginProtection()
|
||||
protection.AddInsecureBypassPattern("/bypass/")
|
||||
protection.AddInsecureBypassPattern("/only/{foo}")
|
||||
protection.AddInsecureBypassPattern("/no-trailing")
|
||||
protection.AddInsecureBypassPattern("/yes-trailing/")
|
||||
protection.AddInsecureBypassPattern("PUT /put-only/")
|
||||
protection.AddInsecureBypassPattern("GET /get-only/")
|
||||
protection.AddInsecureBypassPattern("POST /post-only/")
|
||||
handler := protection.Handler(okHandler)
|
||||
|
||||
tests := []struct {
|
||||
@@ -126,13 +131,23 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) {
|
||||
{"non-bypass path without sec-fetch-site", "/api/", "", http.StatusForbidden},
|
||||
{"non-bypass path with cross-site", "/api/", "cross-site", http.StatusForbidden},
|
||||
|
||||
{"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusOK},
|
||||
{"redirect to bypass path with trailing slash", "/bypass", "", http.StatusOK},
|
||||
{"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusForbidden},
|
||||
{"redirect to bypass path with trailing slash", "/bypass", "", http.StatusForbidden},
|
||||
{"redirect to non-bypass path with ..", "/foo/../api/bar", "", http.StatusForbidden},
|
||||
{"redirect to non-bypass path with trailing slash", "/api", "", http.StatusForbidden},
|
||||
|
||||
{"wildcard bypass", "/only/123", "", http.StatusOK},
|
||||
{"non-wildcard", "/only/123/foo", "", http.StatusForbidden},
|
||||
|
||||
// https://go.dev/issue/75054
|
||||
{"no trailing slash exact match", "/no-trailing", "", http.StatusOK},
|
||||
{"no trailing slash with slash", "/no-trailing/", "", http.StatusForbidden},
|
||||
{"yes trailing slash exact match", "/yes-trailing/", "", http.StatusOK},
|
||||
{"yes trailing slash without slash", "/yes-trailing", "", http.StatusForbidden},
|
||||
|
||||
{"method-specific hit", "/post-only/", "", http.StatusOK},
|
||||
{"method-specific miss (PUT)", "/put-only/", "", http.StatusForbidden},
|
||||
{"method-specific miss (GET)", "/get-only/", "", http.StatusForbidden},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
@@ -237,8 +237,12 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e
|
||||
func addrPortToSockaddrInet4(ap netip.AddrPort) (syscall.SockaddrInet4, error) {
|
||||
// ipToSockaddrInet4 has special handling here for zero length slices.
|
||||
// We do not, because netip has no concept of a generic zero IP address.
|
||||
//
|
||||
// addr is allowed to be an IPv4-mapped IPv6 address.
|
||||
// As4 will unmap it to an IPv4 address.
|
||||
// The error message is kept consistent with ipToSockaddrInet4.
|
||||
addr := ap.Addr()
|
||||
if !addr.Is4() {
|
||||
if !addr.Is4() && !addr.Is4In6() {
|
||||
return syscall.SockaddrInet4{}, &AddrError{Err: "non-IPv4 address", Addr: addr.String()}
|
||||
}
|
||||
sa := syscall.SockaddrInet4{
|
||||
|
||||
@@ -705,3 +705,35 @@ func TestIPv6WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion verifies that
|
||||
// WriteMsgUDPAddrPort accepts IPv4 and IPv4-mapped IPv6 destination addresses,
|
||||
// and rejects IPv6 destination addresses on a "udp4" connection.
|
||||
func TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) {
|
||||
if !testableNetwork("udp4") {
|
||||
t.Skipf("skipping: udp4 not available")
|
||||
}
|
||||
|
||||
conn, err := ListenUDP("udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
daddr4 := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), 12345)
|
||||
daddr4in6 := netip.AddrPortFrom(netip.MustParseAddr("::ffff:127.0.0.1"), 12345)
|
||||
daddr6 := netip.AddrPortFrom(netip.MustParseAddr("::1"), 12345)
|
||||
buf := make([]byte, 8)
|
||||
|
||||
if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr4); err != nil {
|
||||
t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr4) failed: %v", err)
|
||||
}
|
||||
|
||||
if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr4in6); err != nil {
|
||||
t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr4in6) failed: %v", err)
|
||||
}
|
||||
|
||||
if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr6); err == nil {
|
||||
t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr6) should have failed, but got no error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,4 +177,48 @@ func TestLookPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
checker := func(test string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Logf("PATH=%s", os.Getenv("PATH"))
|
||||
p, err := LookPath(test)
|
||||
if err == nil {
|
||||
t.Errorf("%q: error expected, got nil", test)
|
||||
}
|
||||
if p != "" {
|
||||
t.Errorf("%q: path returned should be \"\". Got %q", test, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reference behavior for the next test
|
||||
t.Run(pathVar+"=$OTHER2", func(t *testing.T) {
|
||||
t.Run("empty", checker(""))
|
||||
t.Run("dot", checker("."))
|
||||
t.Run("dotdot1", checker("abc/.."))
|
||||
t.Run("dotdot2", checker(".."))
|
||||
})
|
||||
|
||||
// Test the behavior when PATH contains an executable file which is not a directory
|
||||
t.Run(pathVar+"=exe", func(t *testing.T) {
|
||||
// Inject an executable file (not a directory) in PATH.
|
||||
// Use our own binary os.Args[0].
|
||||
t.Setenv(pathVar, testenv.Executable(t))
|
||||
t.Run("empty", checker(""))
|
||||
t.Run("dot", checker("."))
|
||||
t.Run("dotdot1", checker("abc/.."))
|
||||
t.Run("dotdot2", checker(".."))
|
||||
})
|
||||
|
||||
// Test the behavior when PATH contains an executable file which is not a directory
|
||||
t.Run(pathVar+"=exe/xx", func(t *testing.T) {
|
||||
// Inject an executable file (not a directory) in PATH.
|
||||
// Use our own binary os.Args[0].
|
||||
t.Setenv(pathVar, filepath.Join(testenv.Executable(t), "xx"))
|
||||
t.Run("empty", checker(""))
|
||||
t.Run("dot", checker("."))
|
||||
t.Run("dotdot1", checker("abc/.."))
|
||||
t.Run("dotdot2", checker(".."))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1328,3 +1328,13 @@ func addCriticalEnv(env []string) []string {
|
||||
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
|
||||
// to test whether a returned error err is due to this condition.
|
||||
var ErrDot = errors.New("cannot run executable found relative to current directory")
|
||||
|
||||
// validateLookPath excludes paths that can't be valid
|
||||
// executable names. See issue #74466 and CVE-2025-47906.
|
||||
func validateLookPath(s string) error {
|
||||
switch s {
|
||||
case "", ".", "..":
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ func findExecutable(file string) error {
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
if err := validateLookPath(filepath.Clean(file)); err != nil {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
// skip the path lookup for these prefixes
|
||||
skip := []string{"/", "#", "./", "../"}
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ func LookPath(file string) (string, error) {
|
||||
// (only bypass the path if file begins with / or ./ or ../)
|
||||
// but that would not match all the Unix shells.
|
||||
|
||||
if err := validateLookPath(file); err != nil {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file)
|
||||
if err == nil {
|
||||
|
||||
@@ -67,6 +67,10 @@ func findExecutable(file string, exts []string) (string, error) {
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
if err := validateLookPath(file); err != nil {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
return lookPath(file, pathExt())
|
||||
}
|
||||
|
||||
@@ -80,6 +84,10 @@ func LookPath(file string) (string, error) {
|
||||
// "C:\foo\example.com" would be returned as-is even if the
|
||||
// program is actually "C:\foo\example.com.exe".
|
||||
func lookExtensions(path, dir string) (string, error) {
|
||||
if err := validateLookPath(path); err != nil {
|
||||
return "", &Error{path, err}
|
||||
}
|
||||
|
||||
if filepath.Base(path) == path {
|
||||
path = "." + string(filepath.Separator) + path
|
||||
}
|
||||
|
||||
@@ -1845,6 +1845,44 @@ func TestFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileOverlappedSeek(t *testing.T) {
|
||||
t.Parallel()
|
||||
name := filepath.Join(t.TempDir(), "foo")
|
||||
f := newFileOverlapped(t, name, true)
|
||||
content := []byte("foo")
|
||||
if _, err := f.Write(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check that the file pointer is at the expected offset.
|
||||
n, err := f.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != int64(len(content)) {
|
||||
t.Errorf("expected file pointer to be at offset %d, got %d", len(content), n)
|
||||
}
|
||||
// Set the file pointer to the start of the file.
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Read the first byte.
|
||||
var buf [1]byte
|
||||
if _, err := f.Read(buf[:]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf[:], content[:len(buf)]) {
|
||||
t.Errorf("expected %q, got %q", content[:len(buf)], buf[:])
|
||||
}
|
||||
// Check that the file pointer is at the expected offset.
|
||||
n, err = f.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != int64(len(buf)) {
|
||||
t.Errorf("expected file pointer to be at offset %d, got %d", len(buf), n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipe(t *testing.T) {
|
||||
t.Parallel()
|
||||
r, w, err := os.Pipe()
|
||||
|
||||
@@ -7,6 +7,7 @@ package user
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/syscall/windows"
|
||||
@@ -16,11 +17,92 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// addUserAccount creates a local user account.
|
||||
// It returns the name and password of the new account.
|
||||
// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
|
||||
func addUserAccount(t *testing.T) (name, password string) {
|
||||
t.TempDir()
|
||||
pattern := t.Name()
|
||||
// Windows limits the user name to 20 characters,
|
||||
// leave space for a 4 digits random suffix.
|
||||
const maxNameLen, suffixLen = 20, 4
|
||||
pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
|
||||
// Drop unusual characters from the account name.
|
||||
mapper := func(r rune) rune {
|
||||
if r < utf8.RuneSelf {
|
||||
if '0' <= r && r <= '9' ||
|
||||
'a' <= r && r <= 'z' ||
|
||||
'A' <= r && r <= 'Z' {
|
||||
return r
|
||||
}
|
||||
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}
|
||||
pattern = strings.Map(mapper, pattern)
|
||||
|
||||
// Generate a long random password.
|
||||
var pwd [33]byte
|
||||
rand.Read(pwd[:])
|
||||
// Add special chars to ensure it satisfies password requirements.
|
||||
password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
|
||||
password16, err := syscall.UTF16PtrFromString(password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
try := 0
|
||||
for {
|
||||
// Calculate a random suffix to append to the user name.
|
||||
var suffix [2]byte
|
||||
rand.Read(suffix[:])
|
||||
suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
|
||||
name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
|
||||
name16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create user.
|
||||
userInfo := windows.UserInfo1{
|
||||
Name: name16,
|
||||
Password: password16,
|
||||
Priv: windows.USER_PRIV_USER,
|
||||
}
|
||||
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
|
||||
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
||||
t.Skip("skipping test; don't have permission to create user")
|
||||
}
|
||||
// If the user already exists, try again with a different name.
|
||||
if errors.Is(err, windows.NERR_UserExists) {
|
||||
if try++; try < 1000 {
|
||||
t.Log("user already exists, trying again with a different name")
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("NetUserAdd failed: %v", err)
|
||||
}
|
||||
// Delete the user when the test is done.
|
||||
t.Cleanup(func() {
|
||||
if err := windows.NetUserDel(nil, name16); err != nil {
|
||||
if !errors.Is(err, windows.NERR_UserNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
return name, password
|
||||
}
|
||||
}
|
||||
|
||||
// windowsTestAccount creates a test user and returns a token for that user.
|
||||
// If the user already exists, it will be deleted and recreated.
|
||||
// The caller is responsible for closing the token.
|
||||
@@ -32,47 +114,15 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
|
||||
// See https://dev.go/issue/70396.
|
||||
t.Skip("skipping non-hermetic test outside of Go builders")
|
||||
}
|
||||
const testUserName = "GoStdTestUser01"
|
||||
var password [33]byte
|
||||
rand.Read(password[:])
|
||||
// Add special chars to ensure it satisfies password requirements.
|
||||
pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
|
||||
name, err := syscall.UTF16PtrFromString(testUserName)
|
||||
name, password := addUserAccount(t)
|
||||
name16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pwd16, err := syscall.UTF16PtrFromString(pwd)
|
||||
pwd16, err := syscall.UTF16PtrFromString(password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userInfo := windows.UserInfo1{
|
||||
Name: name,
|
||||
Password: pwd16,
|
||||
Priv: windows.USER_PRIV_USER,
|
||||
}
|
||||
// Create user.
|
||||
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
|
||||
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
||||
t.Skip("skipping test; don't have permission to create user")
|
||||
}
|
||||
if errors.Is(err, windows.NERR_UserExists) {
|
||||
// User already exists, delete and recreate.
|
||||
if err = windows.NetUserDel(nil, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err = windows.NetUserDel(nil, name); err != nil {
|
||||
if !errors.Is(err, windows.NERR_UserNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
domain, err := syscall.UTF16PtrFromString(".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -80,13 +130,13 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
|
||||
const LOGON32_PROVIDER_DEFAULT = 0
|
||||
const LOGON32_LOGON_INTERACTIVE = 2
|
||||
var token syscall.Token
|
||||
if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
|
||||
if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
token.Close()
|
||||
})
|
||||
usr, err := Lookup(testUserName)
|
||||
usr, err := Lookup(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
|
||||
}
|
||||
|
||||
if c.bubble != nil && getg().bubble != c.bubble {
|
||||
panic(plainError("send on synctest channel from outside bubble"))
|
||||
fatal("send on synctest channel from outside bubble")
|
||||
}
|
||||
|
||||
// Fast path: check for failed non-blocking operation without acquiring the lock.
|
||||
@@ -318,7 +318,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
|
||||
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
|
||||
if c.bubble != nil && getg().bubble != c.bubble {
|
||||
unlockf()
|
||||
panic(plainError("send on synctest channel from outside bubble"))
|
||||
fatal("send on synctest channel from outside bubble")
|
||||
}
|
||||
if raceenabled {
|
||||
if c.dataqsiz == 0 {
|
||||
@@ -416,7 +416,7 @@ func closechan(c *hchan) {
|
||||
panic(plainError("close of nil channel"))
|
||||
}
|
||||
if c.bubble != nil && getg().bubble != c.bubble {
|
||||
panic(plainError("close of synctest channel from outside bubble"))
|
||||
fatal("close of synctest channel from outside bubble")
|
||||
}
|
||||
|
||||
lock(&c.lock)
|
||||
@@ -538,7 +538,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
|
||||
}
|
||||
|
||||
if c.bubble != nil && getg().bubble != c.bubble {
|
||||
panic(plainError("receive on synctest channel from outside bubble"))
|
||||
fatal("receive on synctest channel from outside bubble")
|
||||
}
|
||||
|
||||
if c.timer != nil {
|
||||
@@ -702,7 +702,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
|
||||
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
|
||||
if c.bubble != nil && getg().bubble != c.bubble {
|
||||
unlockf()
|
||||
panic(plainError("receive on synctest channel from outside bubble"))
|
||||
fatal("receive on synctest channel from outside bubble")
|
||||
}
|
||||
if c.dataqsiz == 0 {
|
||||
if raceenabled {
|
||||
|
||||
@@ -178,7 +178,7 @@ func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, blo
|
||||
|
||||
if cas.c.bubble != nil {
|
||||
if getg().bubble != cas.c.bubble {
|
||||
panic(plainError("select on synctest channel from outside bubble"))
|
||||
fatal("select on synctest channel from outside bubble")
|
||||
}
|
||||
} else {
|
||||
allSynctest = false
|
||||
|
||||
@@ -415,7 +415,7 @@ func newTimer(when, period int64, f func(arg any, seq uintptr, delay int64), arg
|
||||
//go:linkname stopTimer time.stopTimer
|
||||
func stopTimer(t *timeTimer) bool {
|
||||
if t.isFake && getg().bubble == nil {
|
||||
panic("stop of synctest timer from outside bubble")
|
||||
fatal("stop of synctest timer from outside bubble")
|
||||
}
|
||||
return t.stop()
|
||||
}
|
||||
@@ -430,7 +430,7 @@ func resetTimer(t *timeTimer, when, period int64) bool {
|
||||
racerelease(unsafe.Pointer(&t.timer))
|
||||
}
|
||||
if t.isFake && getg().bubble == nil {
|
||||
panic("reset of synctest timer from outside bubble")
|
||||
fatal("reset of synctest timer from outside bubble")
|
||||
}
|
||||
return t.reset(when, period)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user