Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): error registration could pollute constructors #10422

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions core/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
const { opcall } = window.Deno.core;

let opsCache = {};
const errorMap = {
// Builtin v8 / JS errors
Error,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError,
};
const errorMap = {};
// Builtin v8 / JS errors
registerErrorClass("Error", Error);
registerErrorClass("RangeError", RangeError);
registerErrorClass("ReferenceError", ReferenceError);
registerErrorClass("SyntaxError", SyntaxError);
registerErrorClass("TypeError", TypeError);
registerErrorClass("URIError", URIError);

let nextPromiseId = 1;
const promiseMap = new Map();
const RING_SIZE = 4 * 1024;
Expand Down Expand Up @@ -83,23 +83,27 @@
}

function registerErrorClass(className, errorClass) {
registerErrorBuilder(className, (msg) => new errorClass(msg));
}

function registerErrorBuilder(className, errorBuilder) {
if (typeof errorMap[className] !== "undefined") {
throw new TypeError(`Error class for "${className}" already registered`);
}
errorMap[className] = errorClass;
errorMap[className] = errorBuilder;
}

function unwrapOpResult(res) {
// .$err_class_name is a special key that should only exist on errors
if (res?.$err_class_name) {
const className = res.$err_class_name;
const ErrorClass = errorMap[className];
if (!ErrorClass) {
const errorBuilder = errorMap[className];
if (!errorBuilder) {
throw new Error(
`Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
);
}
throw new ErrorClass(res.message);
throw errorBuilder(res.message);
}
return res;
}
Expand Down Expand Up @@ -138,6 +142,7 @@
close,
print,
resources,
registerErrorBuilder,
registerErrorClass,
handleAsyncMsgFromRust,
syncOpsCache,
Expand Down
30 changes: 30 additions & 0 deletions core/error_builder_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { core } = Deno;

class DOMException {
constructor(message, code) {
this.msg = message;
this.code = code;
}
}

core.registerErrorBuilder(
"DOMExceptionOperationError",
function DOMExceptionOperationError(msg) {
return new DOMException(msg, "OperationError");
},
);

try {
core.opSync("op_err", undefined, null);
throw new Error("op_err didn't throw!");
} catch (err) {
if (!(err instanceof DOMException)) {
throw new Error("err not DOMException");
}
if (err.msg !== "abc") {
throw new Error("err.message is incorrect");
}
if (err.code !== "OperationError") {
throw new Error("err.code is incorrect");
}
}
35 changes: 35 additions & 0 deletions core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1521,7 +1521,9 @@ impl JsRuntime {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::error::custom_error;
use crate::modules::ModuleSourceFuture;
use crate::op_sync;
use futures::future::lazy;
use futures::FutureExt;
use std::io;
Expand Down Expand Up @@ -1768,6 +1770,39 @@ pub mod tests {
});
}

#[test]
fn test_error_builder() {
fn op_err(
_: &mut OpState,
_: (),
_: Option<ZeroCopyBuf>,
) -> Result<(), AnyError> {
Err(custom_error("DOMExceptionOperationError", "abc"))
}

pub fn get_error_class_name(_: &AnyError) -> &'static str {
"DOMExceptionOperationError"
}

run_in_task(|mut cx| {
let mut runtime = JsRuntime::new(RuntimeOptions {
get_error_class_fn: Some(&get_error_class_name),
..Default::default()
});
runtime.register_op("op_err", op_sync(op_err));
runtime.sync_ops_cache();
runtime
.execute(
"error_builder_test.js",
include_str!("error_builder_test.js"),
)
.unwrap();
if let Poll::Ready(Err(_)) = runtime.poll_event_loop(&mut cx) {
unreachable!();
}
});
}

#[test]
fn will_snapshot() {
let snapshot = {
Expand Down
4 changes: 2 additions & 2 deletions runtime/js/99_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,10 @@ delete Object.prototype.__proto__;
core.registerErrorClass("Http", errors.Http);
core.registerErrorClass("Busy", errors.Busy);
core.registerErrorClass("NotSupported", errors.NotSupported);
core.registerErrorClass(
core.registerErrorBuilder(
"DOMExceptionOperationError",
function DOMExceptionOperationError(msg) {
DOMException.prototype.constructor.call(this, msg, "OperationError");
return new DOMException(msg, "OperationError");
},
);
}
Expand Down