1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::error::exception_to_err_result;
use anyhow::Error;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
#[derive(Default)]
pub(crate) struct ExceptionState {
// TODO(nayeemrmn): This is polled in `exception_to_err_result()` which is
// flimsy. Try to poll it similarly to `pending_promise_rejections`.
dispatched_exception: Cell<Option<v8::Global<v8::Value>>>,
dispatched_exception_is_promise: Cell<bool>,
pub(crate) pending_promise_rejections:
RefCell<VecDeque<(v8::Global<v8::Promise>, v8::Global<v8::Value>)>>,
pub(crate) pending_handled_promise_rejections:
RefCell<VecDeque<(v8::Global<v8::Promise>, v8::Global<v8::Value>)>>,
pub(crate) js_build_custom_error_cb:
RefCell<Option<Rc<v8::Global<v8::Function>>>>,
pub(crate) js_handled_promise_rejection_cb:
RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) js_format_exception_cb:
RefCell<Option<Rc<v8::Global<v8::Function>>>>,
}
impl ExceptionState {
/// Clear all the associated v8 objects to prepare for this isolate to be torn down, either for
/// a snapshot or for process termination purposes.
///
/// The [`ExceptionState`] is not considered valid after this operation and should not be used.
/// It generally will not live long after this, however.
pub(crate) fn prepare_to_destroy(&self) {
// TODO(mmastrac): we can probably move this to Drop eventually
self.js_build_custom_error_cb.borrow_mut().take();
self.js_handled_promise_rejection_cb.borrow_mut().take();
self.js_format_exception_cb.borrow_mut().take();
self.pending_promise_rejections.borrow_mut().clear();
self.dispatched_exception.set(None);
}
pub(crate) fn clear_error(&self) {
self.dispatched_exception_is_promise.set(false);
self.dispatched_exception.set(None);
}
pub(crate) fn has_dispatched_exception(&self) -> bool {
// SAFETY: we limit access to this cell to this method only
unsafe {
self
.dispatched_exception
.as_ptr()
.as_ref()
.unwrap_unchecked()
.is_some()
}
}
pub(crate) fn set_dispatched_exception(
&self,
exception: v8::Global<v8::Value>,
promise: bool,
) {
self.dispatched_exception.set(Some(exception));
self.dispatched_exception_is_promise.set(promise);
}
/// If there is an exception condition (ie: an unhandled promise rejection or exception, or
/// the runtime is shut down), returns it from here. If not, returns `Ok`.
pub(crate) fn check_exception_condition(
&self,
scope: &mut v8::HandleScope,
) -> Result<(), Error> {
if self.has_dispatched_exception() {
let undefined = v8::undefined(scope);
exception_to_err_result(
scope,
undefined.into(),
self.dispatched_exception_is_promise.get(),
true,
)
} else {
Ok(())
}
}
pub(crate) fn is_dispatched_exception_promise(&self) -> bool {
self.dispatched_exception_is_promise.get()
}
pub(crate) fn get_dispatched_exception_as_local<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
) -> Option<v8::Local<'s, v8::Value>> {
// SAFETY: we limit access to this cell to this method only
unsafe {
self
.dispatched_exception
.as_ptr()
.as_ref()
.unwrap_unchecked()
}
.as_ref()
.map(|global| v8::Local::new(scope, global))
}
/// Tracks this promise rejection until we have a chance to give it to the unhandled promise rejection handler.
/// This performs the role of `HostPromiseRejectionTracker` from https://262.ecma-international.org/14.0/#sec-host-promise-rejection-tracker.
///
/// Notes from ECMAScript's `HostPromiseRejectionTracker` operation:
///
/// - HostPromiseRejectionTracker is called with the operation argument set to "reject" when a promise is rejected
/// without any handlers, or "handle" when a handler is added to a previously rejected promise for the first time.
/// - Host environments can use this operation to track promise rejections without causing abrupt completion.
/// - Implementations may notify developers of unhandled rejections and invalidate notifications if new handlers are attached.
/// - If operation is "handle", an implementation should not hold a reference to promise in a way that would
/// interfere with garbage collection.
/// - An implementation may hold a reference to promise if operation is "reject", since it is expected that rejections
/// will be rare and not on hot code paths.
pub fn track_promise_rejection(
&self,
scope: &mut v8::HandleScope,
promise: v8::Local<v8::Promise>,
event: v8::PromiseRejectEvent,
rejection_value: Option<v8::Local<v8::Value>>,
) {
use v8::PromiseRejectEvent::*;
let promise_global = v8::Global::new(scope, promise);
match event {
PromiseRejectWithNoHandler => {
let error = rejection_value.unwrap();
let error_global = v8::Global::new(scope, error);
self
.pending_promise_rejections
.borrow_mut()
.push_back((promise_global, error_global));
}
PromiseHandlerAddedAfterReject => {
// The code has until the event loop yields to attach a handler and avoid an unhandled rejection
// event. If we haven't delivered an unhandled exception event yet, we search for the old promise
// in this list and remove it. If it doesn't exist, that means it was already "handled as unhandled"
// and we need to fire a rejectionhandled event.
let mut rejections = self.pending_promise_rejections.borrow_mut();
let previous_len = rejections.len();
rejections.retain(|(key, _)| key != &promise_global);
if rejections.len() == previous_len {
// Don't hold the lock while we go back into v8
drop(rejections);
// The unhandled rejection was already delivered, so this means we need to deliver a
// "rejectionhandled" event if anyone cares.
if self.js_handled_promise_rejection_cb.borrow().is_some() {
let error = promise.result(scope);
let error_global = v8::Global::new(scope, error);
self
.pending_handled_promise_rejections
.borrow_mut()
.push_back((promise_global, error_global));
}
}
}
PromiseRejectAfterResolved => {}
PromiseResolveAfterResolved => {
// Should not warn. See #1272
}
}
}
}