summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/random.nim74
-rw-r--r--lib/std/tempfiles.nim7
-rw-r--r--tests/stdlib/trandom.nim32
-rw-r--r--tests/test_nimscript.nims2
4 files changed, 95 insertions, 20 deletions
diff --git a/lib/pure/random.nim b/lib/pure/random.nim
index a292386af..b8aeb86e0 100644
--- a/lib/pure/random.nim
+++ b/lib/pure/random.nim
@@ -108,10 +108,19 @@ when defined(js):
     a0: 0x69B4C98Cu32,
     a1: 0xFED1DD30u32) # global for backwards compatibility
 else:
-  # racy for multi-threading but good enough for now:
-  var state = Rand(
+  const DefaultRandSeed = Rand(
     a0: 0x69B4C98CB8530805u64,
-    a1: 0xFED1DD3004688D67CAu64) # global for backwards compatibility
+    a1: 0xFED1DD3004688D67CAu64)
+
+  # racy for multi-threading but good enough for now:
+  var state = DefaultRandSeed # global for backwards compatibility
+
+func isValid(r: Rand): bool {.inline.} =
+  ## Check whether state of `r` is valid.
+  ##
+  ## In `xoroshiro128+`, if all bits of `a0` and `a1` are zero,
+  ## they are always zero after calling `next(r: var Rand)`.
+  not (r.a0 == 0 and r.a1 == 0)
 
 since (1, 5):
   template randState*(): untyped =
@@ -617,15 +626,28 @@ proc shuffle*[T](x: var openArray[T]) =
 
   shuffle(state, x)
 
-when not defined(nimscript) and not defined(standalone):
-  import times
+when not defined(standalone):
+  when defined(js):
+    import std/times
+  else:
+    when defined(nimscript):
+      import std/hashes
+    else:
+      import std/[hashes, os, sysrand, monotimes]
+
+      when compileOption("threads"):
+        import locks
+        var baseSeedLock: Lock
+        baseSeedLock.initLock
+
+    var baseState: Rand
 
   proc initRand(): Rand =
-    ## Initializes a new Rand state with a seed based on the current time.
+    ## Initializes a new Rand state.
     ##
     ## The resulting state is independent of the default RNG's state.
     ##
-    ## **Note:** Does not work for NimScript or the compile-time VM.
+    ## **Note:** Does not work for the compile-time VM.
     ##
     ## See also:
     ## * `initRand proc<#initRand,int64>`_ that accepts a seed for a new Rand state
@@ -635,20 +657,50 @@ when not defined(nimscript) and not defined(standalone):
       let time = int64(times.epochTime() * 1000) and 0x7fff_ffff
       result = initRand(time)
     else:
-      let now = times.getTime()
-      result = initRand(convert(Seconds, Nanoseconds, now.toUnix) + now.nanosecond)
+      proc getRandomState(): Rand =
+        when defined(nimscript):
+          result = Rand(
+            a0: CompileTime.hash.Ui,
+            a1: CompileDate.hash.Ui)
+          if not result.isValid:
+            result = DefaultRandSeed
+        else:
+          var urand: array[sizeof(Rand), byte]
+
+          for i in 0 .. 7:
+            if sysrand.urandom(urand):
+              copyMem(result.addr, urand[0].addr, sizeof(Rand))
+              if result.isValid:
+                break
+
+          if not result.isValid:
+            # Don't try to get alternative random values from other source like time or process/thread id,
+            # because such code would be never tested and is a liability for security.
+            quit("Failed to initializes baseState in random module as sysrand.urandom doesn't work.")
+
+      when compileOption("threads"):
+        baseSeedLock.withLock:
+          if not baseState.isValid:
+            baseState = getRandomState()
+          result = baseState
+          baseState.skipRandomNumbers
+      else:
+        if not baseState.isValid:
+          baseState = getRandomState()
+        result = baseState
+        baseState.skipRandomNumbers
 
   since (1, 5, 1):
     export initRand
 
   proc randomize*() {.benign.} =
     ## Initializes the default random number generator with a seed based on
-    ## the current time.
+    ## random number source.
     ##
     ## This proc only needs to be called once, and it should be called before
     ## the first usage of procs from this module that use the default RNG.
     ##
-    ## **Note:** Does not work for NimScript or the compile-time VM.
+    ## **Note:** Does not work for the compile-time VM.
     ##
     ## **See also:**
     ## * `randomize proc<#randomize,int64>`_ that accepts a seed
diff --git a/lib/std/tempfiles.nim b/lib/std/tempfiles.nim
index f1cfd5885..26786d463 100644
--- a/lib/std/tempfiles.nim
+++ b/lib/std/tempfiles.nim
@@ -17,7 +17,7 @@ See also:
 * `mkstemp` (posix), refs https://man7.org/linux/man-pages/man3/mkstemp.3.html
 ]#
 
-import os, random, std/monotimes
+import os, random
 
 
 const
@@ -107,11 +107,8 @@ var nimTempPathState {.threadvar.}: NimTempPathState
 template randomPathName(length: Natural): string =
   var res = newString(length)
   if not nimTempPathState.isInit:
-    var time = getMonoTime().ticks
-    when compileOption("threads"):
-      time = time xor int64(getThreadId())
     nimTempPathState.isInit = true
-    nimTempPathState.state = initRand(time)
+    nimTempPathState.state = initRand()
 
   for i in 0 ..< length:
     res[i] = nimTempPathState.state.sample(letters)
diff --git a/tests/stdlib/trandom.nim b/tests/stdlib/trandom.nim
index 39ccca85b..61e858f86 100644
--- a/tests/stdlib/trandom.nim
+++ b/tests/stdlib/trandom.nim
@@ -23,9 +23,10 @@ proc main() =
   doAssert a in [[0,1], [1,0]]
 
   doAssert rand(0) == 0
-  doAssert sample("a") == 'a'
+  when not defined(nimscript):
+    doAssert sample("a") == 'a'
 
-  when compileOption("rangeChecks"):
+  when compileOption("rangeChecks") and not defined(nimscript):
     doAssertRaises(RangeDefect):
       discard rand(-1)
 
@@ -92,7 +93,7 @@ block: # random int
 
   block: # again gives new numbers
     var rand1 = rand(1000000)
-    when not defined(js):
+    when not (defined(js) or defined(nimscript)):
       os.sleep(200)
 
     var rand2 = rand(1000000)
@@ -122,7 +123,7 @@ block: # random float
 
   block: # again gives new numbers
     var rand1: float = rand(1000000.0)
-    when not defined(js):
+    when not (defined(js) or defined(nimscript)):
       os.sleep(200)
 
     var rand2: float = rand(1000000.0)
@@ -248,3 +249,26 @@ block: # bug #17670
     type UInt48 = range[0'u64..2'u64^48-1]
     let x = rand(UInt48)
     doAssert x is UInt48
+
+block: # bug #17898
+  # Checks whether `initRand()` generates unique states.
+  # size should be 2^64, but we don't have time and space.
+
+  # Disable this test for js until js gets proper skipRandomNumbers.
+  when not defined(js):
+    const size = 1000
+    var
+      rands: array[size, Rand]
+      randSet: HashSet[Rand]
+    for i in 0..<size:
+      rands[i] = initRand()
+      randSet.incl rands[i]
+
+    doAssert randSet.len == size
+
+    # Checks random number sequences overlapping.
+    const numRepeat = 100
+    for i in 0..<size:
+      for j in 0..<numRepeat:
+        discard rands[i].next
+        doAssert rands[i] notin randSet
diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims
index 2468699a4..90b6181aa 100644
--- a/tests/test_nimscript.nims
+++ b/tests/test_nimscript.nims
@@ -71,6 +71,8 @@ import std/[
   decls, compilesettings, with, wrapnils
 ]
 
+import stdlib/trandom
+
 echo "Nimscript imports are successful."
 
 block: