This change adds support for weak handles that don't prevent GC of the
referenced objects, through the `v8::Weak<T>` API. A weak handle can
be empty (if it was created empty or its object was GC'd) or
non-empty, and if non-empty it allows getting its object as a global
or local.
When creating a `v8::Weak` you can also set a finalizer that will be
called at some point after the object is GC'd, as long as the weak
handle is still alive at that point. This finalization corresponds to
the second-pass callback in `kParameter` mode in the C++ API, so it
will only be called after the object is GC'd. The finalizer function
is a `FnOnce` that may close over data, and which takes a
`&mut Isolate` as an argument.
The C++ finalization API doesn't guarantee _when_ or even _if_ the
finalizer will ever be called, but in order to prevent memory leaks,
the rusty_v8 wrapper ensures that it will be called at some point,
even if it's just before the isolate gets dropped.
`v8::Weak<T>` implements `Clone`, but a finalizer is tied to a single
weak handle, so its clones won't be able to keep the finalizer alive.
And in fact, cloning will create a new weak handle that isn't tied to
a finalizer at all. `v8::Weak::clone_with_finalizer` can be used to
make a clone of a weak handle which has a finalizer tied to it.
Note that `v8::Weak<T>` doesn't implement `Hash`, because the hash
would have to change once the handle's object is GC'd, which is a big
gotcha and would break some of the algorithms that rely on hashes,
such as the Rust std's `HashMap`.
This fixes in a segmentation fault when dropping a `BackingStore`
constructed through `ArrayBuffer::new_backing_store_from_boxed_slice()`
from an empty slice, since zero length boxed slices are invalid
(dangling) pointers, while Rust expects a `Box<c_void>` to always be a
valid pointer.
Fixes: #849
The pointer returned by `BackingStore::data` might be null if the
backing store has zero length, but the return type `*mut c_void` does
not force the user to consider this case. This change makes the return
type `Option<NonNull<c_void>>`, which is semantically equivalent, but
which forces users of the API to handle the `None` case.
This is a breaking API change.
For zero-size `BackingStore`s, it seems like `BackingStore::data` always
returns a null pointer. The `Deref` impl for `BackingStore` called
`std::slice::from_raw_parts` on that pointer, even though it is UB to
call that function on a null pointer even for empty slices. This change
fixes that by obtaining a valid pointer from `NonNull::dangling()` if
the original is null.
Reported in
https://github.com/denoland/rusty_v8/issues/711#issuecomment-950637136.
Best case, it produces serialized output that cannot be deserialized.
Worst case, it hits this assert in V8:
# Fatal error in v8::FromJust
# Maybe value is Nothing.
Serializing `new String("")` requires that the wire format header is
written, otherwise V8 assumes an incompatible legacy wire format when
deserializing.
The test started a new thread that slept for a bit, then terminated the
isolate, assuming that the delay was long enough for the isolate and the
context to get fully initialized. It wasn't.
Fixes #710.
This commit adds the following methods:
* `FunctionTemplate::inherit()`
* `FunctionTemplate::prototype_template()`
* `FunctionTemplate::read_only_prototype()`
* `FunctionTemplate::remove_prototype()`
This commit adds support for import assertions.
Major changes include:
- removal of "ResolveCallback" (deprecated in V8)
in favor of "ModuleResolveCallback"
- removal of "HostImportModuleDynamicallyCallback" (deprecated in V8)
in favor of "HostImportModuleDynamicallyWithImportAssertionsCallback"
This commit removes the set_flags_from_command_line_with_usage and puts
it in an example code section instead.
The motivation for doing this is that the test output currently contains
the usage string and all the V8 options which creates a lot of output
when the tests is run regardless if --nocapture is used or not.
The blanket `std:#️⃣:Hash` impl for instances of `v8::Data` invokes
`v8::internal::Object::GetHash()` but that crashes for `v8::Module`
objects. Use a custom impl that calls `v8::Module::get_identity_hash()`.
Fixes the following runtime assertion:
# Fatal error in ../../../v8/src/objects/objects-inl.h, line 1043
# Debug check failed: object.IsJSReceiver().
Refs: https://github.com/denoland/deno/pull/8354#discussion_r522157813
After the upgrade some Deno tests started crashing somewhere deep inside
V8, and the cause of these crashes is unclear.
This reverts the following commits:
* 12334ffe Upgrade gn to 6f13aaac; make BUILD.gn compatible with it (#443)
* f53f10d4 Upgrade V8 to 8.7.25 (#443)
Isolate::run_microtasks() already exists but the microtasks queue
is also flushed on script entry and exit. Some embedders want strict
control over when microtasks run, which is set by the policy.
And deprecate Isolate::run_microtasks(). The corresponding V8 API is
tagged with V8_DEPRECATE_SOON.
After upgrading to V8 8.6.337, with a 20 MB heap limit, the
near-heap-limit callback never gets called before V8 runs out of memory.
It turns out that this test exhibits memory allocation behavior which
produces so little actual garbage that 'scavenge' type garbage
collections make memory usage go up rather than down. Because of this,
V8 runs out of memory in the middle of a garbage collection cycle, after
it has already decided that there's no need to run the near-heap-limit
callback.
The issue is fixed by making sure that some actual garbage is produced
alongside with the retained objects that will eventually fill up the
heap.
* Merged all handle type implementations into one file ('handle.h').
* Made it so that `Global` handles cannot be empty.
* Renamed the `AsHandle` trait to `Handle`, and made it more generally
useful.
* Simplified how `PartialEq` is implemented for V8 heap objects and/or
the `Local`/`Global` handles that reference them.
Local handles never need to be mutable. This patch also rounds up the
last few places where we were still asking the user to pass an `&mut T`
to an API method.
According to v8.h, "the returned handle is valid until this TryCatch
block has been destroyed". This is incorrect, as can be demonstrated
with the test below. In practice the return value lives no longer and
no shorter than the active HandleScope at the time these methods are
called. An issue has been opened about this in the V8 bug tracker:
https://bugs.chromium.org/p/v8/issues/detail?id=10537.
```rust
fn try_catch_bad_lifetimes() {
let _setup_guard = setup();
let mut isolate = v8::Isolate::new(Default::default());
let mut hs = v8::HandleScope::new(&mut isolate);
let scope = hs.enter();
let context = v8::Context::new(scope);
let mut cs = v8::ContextScope::new(scope, context);
let scope = cs.enter();
let caught_msg_2 = {
let mut try_catch = v8::TryCatch::new(scope);
let try_catch = try_catch.enter();
let caught_msg_1 = {
let mut hs = v8::HandleScope::new(scope);
let scope = hs.enter();
// Throw exception #1.
let msg_1 = v8::String::new(scope, "BOOM!").unwrap();
let exc_1 = v8::Exception::type_error(scope, msg_1);
scope.isolate().throw_exception(exc_1);
// Catch exception #1.
let caught_msg_1 = try_catch.message().unwrap();
let caught_str_1 =
caught_msg_1.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_1.contains("BOOM"));
// Move `caught_msg_1` out of the HandleScope it was created in.
// The borrow checker allows this because `caught_msg_1`'s
// lifetime is contrained to not outlive the TryCatch, but it is
// allowed to outlive the HandleScope that was active when the
// exception was caught.
caught_msg_1
};
// Next line crashes.
let caught_str_1 =
caught_msg_1.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_1.contains("BOOM"));
// Throws exception #2.
let msg_2 = v8::String::new(scope, "DANG!").unwrap();
let exc_2 = v8::Exception::type_error(scope, msg_2);
scope.isolate().throw_exception(exc_2);
// Catch exception #2.
let caught_msg_2 = try_catch.message().unwrap();
let caught_str_2 =
caught_msg_2.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_2.contains("DANG"));
// Move `caught_msg_2` out of the extent of the TryCatch, but still
// within the extent of its HandleScope. This is unnecessarily
// rejected at compile time.
caught_msg_2
};
let caught_str_2 =
caught_msg_2.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_2.contains("DANG"));
}
```
* Add `SharedPtr` as a nullable sibling to `SharedRef`.
* Add `Borrow`, `AsRef` and `AsMut` implementations as appropriate.
* `SharedRef<T>` now derefs to `T` rather than to `UnsafeCell<T>`.
* `array_buffer::BackingStore` now derefs to `[Cell<u8>]`.
This doesn't really follow the current V8 API (it's pretty close to how
V8 used to be back in 2012 though.) However:
1. The C++ API is very C++-y and doesn't carry over well to Rust, and
2. It addresses the immediate need of being able to take heap snapshots.
Refs #298