diff --git a/assemblyscript/assemblyscript_test.go b/assemblyscript/assemblyscript_test.go index a23895fc..7f10dcfe 100644 --- a/assemblyscript/assemblyscript_test.go +++ b/assemblyscript/assemblyscript_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" _ "embed" + "encoding/hex" "errors" "io" "strings" @@ -123,8 +124,7 @@ func TestAbort_Error(t *testing.T) { } func TestSeed(t *testing.T) { - b := []byte{0, 1, 2, 3, 4, 5, 6, 7} - mod, r, log := requireProxyModule(t, NewFunctionExporter(), wazero.NewModuleConfig().WithRandSource(bytes.NewReader(b))) + mod, r, log := requireProxyModule(t, NewFunctionExporter(), wazero.NewModuleConfig()) defer r.Close(testCtx) ret, err := mod.ExportedFunction(functionSeed).Call(testCtx) @@ -132,11 +132,11 @@ func TestSeed(t *testing.T) { require.Equal(t, ` --> proxy.seed() ==> env.~lib/builtins/seed() - <== (7.949928895127363e-275) -<-- (7.949928895127363e-275) + <== (4.958153677776298e-175) +<-- (4.958153677776298e-175) `, "\n"+log.String()) - require.Equal(t, b, u64.LeBytes(ret[0])) + require.Equal(t, "538c7f96b164bf1b", hex.EncodeToString(u64.LeBytes(ret[0]))) } func TestSeed_error(t *testing.T) { diff --git a/config.go b/config.go index 99bef862..cee38053 100644 --- a/config.go +++ b/config.go @@ -565,12 +565,15 @@ type ModuleConfig interface { // See WithNanosleep WithSysNanosleep() ModuleConfig - // WithRandSource configures a source of random bytes. Defaults to crypto/rand.Reader. + // WithRandSource configures a source of random bytes. Defaults to return a + // deterministic source. You might override this with crypto/rand.Reader // - // This reader is most commonly used by the functions like "random_get" in "wasi_snapshot_preview1" or "seed" in - // AssemblyScript standard "env" although it could be used by functions imported from other modules. + // This reader is most commonly used by the functions like "random_get" in + // "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and + // "getRandomData" when runtime.GOOS is "js". // - // Note: The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close. + // Note: The caller is responsible to close any io.Reader they supply: It + // is not closed on api.Module Close. WithRandSource(io.Reader) ModuleConfig } diff --git a/config_test.go b/config_test.go index a17b2711..729450fe 100644 --- a/config_test.go +++ b/config_test.go @@ -2,6 +2,7 @@ package wazero import ( "context" + "crypto/rand" "io" "io/fs" "math" @@ -479,6 +480,23 @@ func TestModuleConfig_toSysContext(t *testing.T) { testFS2, // fs ), }, + { + name: "WithRandSource", + input: base.WithRandSource(rand.Reader), + expected: requireSysContext(t, + math.MaxUint32, // max + nil, // args + nil, // environ + nil, // stdin + nil, // stdout + nil, // stderr + rand.Reader, // randSource + &wt, 1, // walltime, walltimeResolution + &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep + nil, // fs + ), + }, } for _, tt := range tests { diff --git a/internal/platform/crypto.go b/internal/platform/crypto.go new file mode 100644 index 00000000..c141f00f --- /dev/null +++ b/internal/platform/crypto.go @@ -0,0 +1,17 @@ +package platform + +import ( + "io" + "math/rand" +) + +// seed is a fixed seed value for NewFakeRandSource. +// +// Trivia: While arbitrary, 42 was chosen as it is the "Ultimate Answer" in +// the Douglas Adams novel "The Hitchhiker's Guide to the Galaxy." +const seed = int64(42) + +// NewFakeRandSource returns a deterministic source of random values. +func NewFakeRandSource() io.Reader { + return rand.New(rand.NewSource(seed)) +} diff --git a/internal/sys/sys.go b/internal/sys/sys.go index 663773e0..7b1e405c 100644 --- a/internal/sys/sys.go +++ b/internal/sys/sys.go @@ -2,7 +2,6 @@ package sys import ( "context" - "crypto/rand" "errors" "fmt" "io" @@ -118,7 +117,7 @@ func (c *Context) FS(ctx context.Context) *FSContext { return c.fsc } -// RandSource is a source of random bytes and defaults to crypto/rand.Reader. +// RandSource is a source of random bytes and defaults to a deterministic source. // see wazero.ModuleConfig WithRandSource func (c *Context) RandSource() io.Reader { return c.randSource @@ -153,8 +152,10 @@ func NewContext( stdin io.Reader, stdout, stderr io.Writer, randSource io.Reader, - walltime *sys.Walltime, walltimeResolution sys.ClockResolution, - nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution, + walltime *sys.Walltime, + walltimeResolution sys.ClockResolution, + nanotime *sys.Nanotime, + nanotimeResolution sys.ClockResolution, nanosleep *sys.Nanosleep, fs fs.FS, ) (sysCtx *Context, err error) { @@ -187,7 +188,7 @@ func NewContext( } if randSource == nil { - sysCtx.randSource = rand.Reader + sysCtx.randSource = platform.NewFakeRandSource() } else { sysCtx.randSource = randSource } diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go index b4fc8b5c..1779dd36 100644 --- a/internal/sys/sys_test.go +++ b/internal/sys/sys_test.go @@ -3,7 +3,6 @@ package sys import ( "bytes" "context" - "crypto/rand" "io" "testing" "time" @@ -55,7 +54,7 @@ func TestDefaultSysContext(t *testing.T) { require.Zero(t, sysCtx.Nanotime(testCtx)) // See above on functions. require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution()) require.Equal(t, &ns, sysCtx.nanosleep) - require.Equal(t, rand.Reader, sysCtx.RandSource()) + require.Equal(t, platform.NewFakeRandSource(), sysCtx.RandSource()) require.Equal(t, NewFSContext(testfs.FS{}), sysCtx.FS(testCtx)) } diff --git a/wasi_snapshot_preview1/random_test.go b/wasi_snapshot_preview1/random_test.go index ee778461..53b42147 100644 --- a/wasi_snapshot_preview1/random_test.go +++ b/wasi_snapshot_preview1/random_test.go @@ -12,8 +12,7 @@ import ( ) func Test_randomGet(t *testing.T) { - mod, r, log := requireProxyModule(t, wazero.NewModuleConfig(). - WithRandSource(deterministicRandomSource())) + mod, r, log := requireProxyModule(t, wazero.NewModuleConfig()) defer r.Close(testCtx) expectedMemory := []byte{ @@ -42,8 +41,7 @@ func Test_randomGet(t *testing.T) { } func Test_randomGet_Errors(t *testing.T) { - mod, r, log := requireProxyModule(t, wazero.NewModuleConfig(). - WithRandSource(deterministicRandomSource())) + mod, r, log := requireProxyModule(t, wazero.NewModuleConfig()) defer r.Close(testCtx) memorySize := mod.Memory().Size(testCtx) diff --git a/wasi_snapshot_preview1/wasi_test.go b/wasi_snapshot_preview1/wasi_test.go index 7f990edb..02d1843c 100644 --- a/wasi_snapshot_preview1/wasi_test.go +++ b/wasi_snapshot_preview1/wasi_test.go @@ -4,8 +4,6 @@ import ( "bytes" "context" _ "embed" - "io" - "math/rand" "testing" "github.com/tetratelabs/wazero" @@ -16,12 +14,6 @@ import ( "github.com/tetratelabs/wazero/internal/testing/require" ) -const seed = int64(42) // fixed seed value - -var deterministicRandomSource = func() io.Reader { - return rand.New(rand.NewSource(seed)) -} - // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")