Files
rusty_v8/tests/test_api_entropy_source.rs
Nathan Whitaker 65fb5c1683 rework scopes to make them stack allocated and remove runtime bookkeeping of scopes (#1830)
Before this change, `HandleScope`s (and other scope types) were stored in the heap and we did bookkeeping at runtime of the current stack of scopes. The reasons were primarily:
- raw handle scopes need to be pinned in memory once initialized
- we couldn't enforce at compile that a `Local` didn't outlive the scope it was created from
The latter lead us to need a workaround where scopes actually were not exited when they were dropped, rather they were exited the next time we activated another scope.

This change refactors the scope implementation to:
- make scopes stack allocated, to reduce overhead of using them
- remove the runtime bookkeeping
- use lifetimes to prevent scopes from being dropped, so we can remove the `zombie` scope workaround

The practical effect is that creating a handle scope changes from:

```rust
let mut scope = v8::HandleScope::new(&mut isolate);
let local = v8::Integer::new(&mut scope, 123);
```

to:

```rust
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
let scope = scope.init();
let local = v8::Integer::new(&scope, 123);

// or shorter
v8::scope!(let scope, &mut isolate);
let local = v8::Integer::new(scope, 123);
```

If you accept a `HandleScope` as a parameter in a function, you would change it from:

```rust
fn takes_scope(s: &mut v8::HandleScope) {}
```

to

```rust
fn takes_scope(s: &mut v8::PinScope) {}
```

(`v8::PinScope` is a type alias for a pinned handle scope).
2025-09-22 21:24:22 +00:00

56 lines
1.6 KiB
Rust

// Tests from the same file run in a single process. That's why this test
// is in its own file, because changing the entropy source affects the
// whole process.
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
static CALLS: AtomicUsize = AtomicUsize::new(0);
#[test]
fn set_entropy_source() {
const N: usize = 3;
v8::V8::set_entropy_source(|buf| {
CALLS.fetch_add(1, Ordering::SeqCst);
for c in buf {
*c = 42;
}
true
});
v8::V8::initialize_platform(
v8::new_unprotected_default_platform(0, false).make_shared(),
);
v8::V8::initialize();
// Assumes that every isolate creates a PRNG from scratch, which is currently true.
let mut results = vec![];
for _ in 0..N {
let isolate = &mut v8::Isolate::new(Default::default());
v8::scope!(let scope, isolate);
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
let source = v8::String::new(scope, "Math.random()").unwrap();
let script = v8::Script::compile(scope, source, None).unwrap();
let result = script.run(scope).unwrap();
let result = result.to_string(scope).unwrap();
let result = result.to_rust_string_lossy(scope);
results.push(result);
}
unsafe {
v8::V8::dispose();
}
v8::V8::dispose_platform();
// All runs should have produced the same value.
assert_eq!(results.len(), N);
results.dedup();
assert_eq!(results.len(), 1);
// Doesn't have to be exactly N because V8 also calls
// the EntropySource for things like hash seeds and ASLR.
assert!(CALLS.load(Ordering::SeqCst) >= N);
}