mirror of
https://github.com/lexmount/moli.git
synced 2026-10-31 00:06:19 +00:00
fix(streams): mark internal rejections handled without author catch hooks
Use the native Promise handled flag for internal Streams promises instead of reading author-controlled catch methods. Cover locked reader release and writer abort with a poisoned Promise.prototype.catch getter.
This commit is contained in:
@@ -269,22 +269,16 @@ pub(in crate::context_bootstrap) fn suppress_pending_read_unhandled_rejection<'s
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn suppress_promise_unhandled_rejection<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_: &mut v8::PinScope<'s, '_>,
|
||||
promise: v8::Local<'s, v8::Value>,
|
||||
) {
|
||||
let Ok(promise) = v8::Local::<v8::Promise>::try_from(promise) else {
|
||||
return;
|
||||
};
|
||||
let Some(noop) = v8::Function::builder(promise_return_undefined_callback).build(scope) else {
|
||||
return;
|
||||
};
|
||||
let Some(catch) = promise
|
||||
.get(scope, v8str(scope, "catch").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = catch.call(scope, promise.into(), &[noop.into()]);
|
||||
// Streams algorithms mark their internal promises handled directly. An
|
||||
// author can replace catch, its getter, or the Promise species constructor.
|
||||
// Looking them up here would add observable script to the native algorithm.
|
||||
promise.mark_as_handled();
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn value_buffer_source_bytes(
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn stream_internal_rejection_handling_does_not_read_author_promise_catch() {
|
||||
let mut vm = stream_test_vm();
|
||||
vm.eval(r#"
|
||||
globalThis.__handlingResult='pending';
|
||||
(async()=>{
|
||||
let hooks=0;
|
||||
const saved=Object.getOwnPropertyDescriptor(Promise.prototype,'catch');
|
||||
Object.defineProperty(Promise.prototype,'catch',{configurable:true,get(){hooks++;throw Error('author catch getter');}});
|
||||
try {
|
||||
const reader=new ReadableStream().getReader();
|
||||
const pending=reader.read().then(()=>false,error=>error instanceof TypeError);
|
||||
const readerClosed=reader.closed.then(()=>false,error=>error instanceof TypeError);
|
||||
reader.releaseLock();
|
||||
const writer=new WritableStream().getWriter();
|
||||
const writerClosed=writer.closed.then(()=>false,error=>error==='stop');
|
||||
await writer.abort('stop');
|
||||
const writerReady=writer.ready.then(()=>false,error=>error==='stop');
|
||||
const results=await Promise.all([pending,readerClosed,writerClosed,writerReady]);
|
||||
writer.releaseLock();
|
||||
return JSON.stringify({hooks,results});
|
||||
} finally {Object.defineProperty(Promise.prototype,'catch',saved);}
|
||||
})().then(value=>globalThis.__handlingResult=value,error=>globalThis.__handlingResult='error:'+error.name+':'+error.message);
|
||||
"#).unwrap();
|
||||
let mut result = String::new();
|
||||
for _ in 0..16 {
|
||||
result = vm.eval("__handlingResult").unwrap();
|
||||
if result != "pending" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(result, r#"{"hooks":0,"results":[true,true,true,true]}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writable_stream_writer_promises_survive_missing_or_poisoned_global_promise() {
|
||||
for (label, replacement) in [
|
||||
|
||||
@@ -134,7 +134,7 @@ const DIRECT_V8_CALL_ALLOWLIST: &[AllowedDirectCallFile] = &[
|
||||
"context_bootstrap/stream_adapter/utils.rs",
|
||||
// Pending promise settlement and cancellation continuations use the
|
||||
// native Promise API; the remaining calls run stream algorithms.
|
||||
5,
|
||||
4,
|
||||
DirectCallOwner::BrowserAlgorithm,
|
||||
),
|
||||
allowed(
|
||||
|
||||
Reference in New Issue
Block a user