]> Cypherpunks.ru repositories - gostls13.git/commitdiff
math/rand: auto-seed global source
authorRuss Cox <rsc@golang.org>
Tue, 4 Oct 2022 16:20:18 +0000 (12:20 -0400)
committerGopher Robot <gobot@golang.org>
Tue, 25 Oct 2022 16:49:48 +0000 (16:49 +0000)
Implement proposal #54880, to automatically seed the global source.

The justification for this not being a breaking change is that any
use of the global source in a package's init function or exported API
clearly must be valid - that is, if a package changes how much
randomness it consumes at init time or in an exported API, that
clearly isn't the kind of breaking change that requires issuing a v2
of that package. That kind of per-package change in the position
of the global source is indistinguishable from seeding the global
source differently. So if the per-package change is valid, so is auto-seeding.

And then, of course, auto-seeding means that packages will be
far less likely to depend on the specific results of the global source
and therefore not break when those kinds of per-package changes
happen in the future.

Seed(1) can be called in programs that need the old sequence from
the global source and want to restore the old behavior.
Of course, those programs will still be broken by the per-package
changes just described, and it would be better for them to allocate
local sources rather than continue to use the global one.

Fixes #54880.

Change-Id: Ib9dc3307b97f7a45587a9cc50d81f919d3edc7ae
Reviewed-on: https://go-review.googlesource.com/c/go/+/443058
Reviewed-by: Austin Clements <austin@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>

src/math/rand/auto_test.go [new file with mode: 0644]
src/math/rand/rand.go
src/runtime/stubs.go

diff --git a/src/math/rand/auto_test.go b/src/math/rand/auto_test.go
new file mode 100644 (file)
index 0000000..b057370
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright 2022 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 rand_test
+
+import (
+       . "math/rand"
+       "testing"
+)
+
+// This test is first, in its own file with an alphabetically early name,
+// to try to make sure that it runs early. It has the best chance of
+// detecting deterministic seeding if it's the first test that runs.
+
+func TestAuto(t *testing.T) {
+       // Pull out 10 int64s from the global source
+       // and then check that they don't appear in that
+       // order in the deterministic Seed(1) result.
+       var out []int64
+       for i := 0; i < 10; i++ {
+               out = append(out, Int63())
+       }
+
+       // Look for out in Seed(1)'s output.
+       // Strictly speaking, we should look for them in order,
+       // but this is good enough and not significantly more
+       // likely to have a false positive.
+       Seed(1)
+       found := 0
+       for i := 0; i < 1000; i++ {
+               x := Int63()
+               if x == out[found] {
+                       found++
+                       if found == len(out) {
+                               t.Fatalf("found unseeded output in Seed(1) output")
+                       }
+               }
+       }
+}
index 4627d4515fb391cd9109995609d36f4120226f33..2d45a28280b122b981301b7ce3dea96c5258f7e4 100644 (file)
@@ -5,22 +5,28 @@
 // Package rand implements pseudo-random number generators unsuitable for
 // security-sensitive work.
 //
-// Random numbers are generated by a Source. Top-level functions, such as
-// Float64 and Int, use a default shared Source that produces a deterministic
-// sequence of values each time a program is run. Use the Seed function to
-// initialize the default Source if different behavior is required for each run.
-// The default Source is safe for concurrent use by multiple goroutines, but
-// Sources created by NewSource are not.
+// Random numbers are generated by a [Source], usually wrapped in a [Rand].
+// Both types should be used by a single goroutine at a time: sharing among
+// multiple goroutines requires some kind of synchronization.
+//
+// Top-level functions, such as [Float64] and [Int],
+// are safe for concurrent use by multiple goroutines.
 //
 // This package's outputs might be easily predictable regardless of how it's
 // seeded. For random numbers suitable for security-sensitive work, see the
 // crypto/rand package.
 package rand
 
-import "sync"
+import (
+       "internal/godebug"
+       "sync"
+       _ "unsafe" // for go:linkname
+)
 
 // A Source represents a source of uniformly-distributed
 // pseudo-random int64 values in the range [0, 1<<63).
+//
+// A Source is not safe for concurrent use by multiple goroutines.
 type Source interface {
        Int63() int64
        Seed(seed int64)
@@ -298,10 +304,23 @@ func read(p []byte, src Source, readVal *int64, readPos *int8) (n int, err error
 var globalRand = New(new(lockedSource))
 
 // Seed uses the provided seed value to initialize the default Source to a
-// deterministic state. If Seed is not called, the generator behaves as
-// if seeded by Seed(1). Seed values that have the same remainder when
+// deterministic state. Seed values that have the same remainder when
 // divided by 2³¹-1 generate the same pseudo-random sequence.
 // Seed, unlike the Rand.Seed method, is safe for concurrent use.
+//
+// If Seed is not called, the generator is seeded randomly at program startup.
+//
+// Prior to Go 1.20, the generator was seeded like Seed(1) at program startup.
+// To force the old behavior, call Seed(1) at program startup.
+// Alternately, set GODEBUG=randautoseed=0 in the environment
+// before making any calls to functions in this package.
+//
+// Note: Programs that call Seed and then expect a specific sequence
+// of results from the global random source (using functions such as Int)
+// can be broken when a dependency changes how much it consumes
+// from the global random source. To avoid such breakages, programs
+// that need a specific result sequence should use NewRand(NewSource(seed))
+// to obtain a random generator that other packages cannot access.
 func Seed(seed int64) { globalRand.Seed(seed) }
 
 // Int63 returns a non-negative pseudo-random 63-bit integer as an int64
@@ -384,11 +403,20 @@ type lockedSource struct {
        s  *rngSource // nil if not yet allocated
 }
 
+//go:linkname fastrand64
+func fastrand64() uint64
+
 // source returns r.s, allocating and seeding it if needed.
 // The caller must have locked r.
 func (r *lockedSource) source() *rngSource {
        if r.s == nil {
-               r.s = newSource(1)
+               var seed int64
+               if godebug.Get("randautoseed") == "0" {
+                       seed = 1
+               } else {
+                       seed = int64(fastrand64())
+               }
+               r.s = newSource(seed)
        }
        return r.s
 }
index 929f8fadca81aa065ca81d2c9b406113512e23d0..20487fdf883b5d215504658e50a25bc5c65babff 100644 (file)
@@ -196,6 +196,9 @@ func fastrandu() uint {
        return uint(fastrand64())
 }
 
+//go:linkname rand_fastrand64 math/rand.fastrand64
+func rand_fastrand64() uint64 { return fastrand64() }
+
 //go:linkname sync_fastrandn sync.fastrandn
 func sync_fastrandn(n uint32) uint32 { return fastrandn(n) }