1use std::borrow::Cow;
4use std::collections::HashSet;
5use std::error::Error;
6use std::fmt;
7use std::fmt::Debug;
8use std::fmt::Display;
9use std::fmt::Formatter;
10use std::fmt::Write as _;
11use std::sync::Arc;
12
13use boxed_error::Boxed;
14use deno_error::JsError;
15use deno_error::JsErrorClass;
16use deno_error::PropertyValue;
17use deno_error::builtin_classes::*;
18use thiserror::Error;
19
20pub use super::modules::ModuleConcreteError;
21use crate::FastStaticString;
22pub use crate::io::ResourceError;
23pub use crate::modules::ModuleLoaderError;
24use crate::runtime::JsRealm;
25use crate::runtime::JsRuntime;
26use crate::runtime::v8_static_strings;
27use crate::source_map::SourceMapApplication;
28use crate::url::Url;
29
30pub type AnyError = anyhow::Error;
33
34deno_error::js_error_wrapper!(v8::DataError, DataError, TYPE_ERROR);
35
36impl PartialEq<DataError> for DataError {
37 fn eq(&self, other: &DataError) -> bool {
38 match (self.0, other.0) {
39 (
40 v8::DataError::BadType { actual, expected },
41 v8::DataError::BadType {
42 actual: other_actual,
43 expected: other_expected,
44 },
45 ) => actual == other_actual && expected == other_expected,
46 (
47 v8::DataError::NoData { expected },
48 v8::DataError::NoData {
49 expected: other_expected,
50 },
51 ) => expected == other_expected,
52 _ => false,
53 }
54 }
55}
56
57impl Eq for DataError {}
58
59#[derive(Debug, Error, JsError)]
60#[class(generic)]
61#[error("Failed to parse {0}")]
62pub struct CoreModuleParseError(pub FastStaticString);
63
64#[derive(Debug, Error, JsError)]
65#[class(generic)]
66#[error("Failed to execute {0}")]
67pub struct CoreModuleExecuteError(pub FastStaticString);
68
69#[derive(Debug, Error, JsError)]
70#[class(generic)]
71#[error("Unable to get code cache from unbound module script for {0}")]
72pub struct CreateCodeCacheError(pub Url);
73
74#[derive(Debug, Error, JsError)]
75#[class(generic)]
76#[error(
77 "Extensions from snapshot loaded in wrong order: expected {} but got {}", .expected, .actual
78)]
79pub struct ExtensionSnapshotMismatchError {
80 pub expected: &'static str,
81 pub actual: &'static str,
82}
83
84#[derive(Debug, Error, JsError)]
85#[class(generic)]
86#[error(
87 "Number of lazy-initialized extensions ({}) does not match number of arguments ({})", .lazy_init_extensions_len, .arguments_len
88)]
89pub struct ExtensionLazyInitCountMismatchError {
90 pub lazy_init_extensions_len: usize,
91 pub arguments_len: usize,
92}
93
94#[derive(Debug, Error, JsError)]
95#[class(generic)]
96#[error(
97 "Lazy-initialized extensions loaded in wrong order: expected {} but got {}", .expected, .actual
98)]
99pub struct ExtensionLazyInitOrderMismatchError {
100 pub expected: &'static str,
101 pub actual: &'static str,
102}
103
104#[derive(Debug, Boxed, JsError)]
105pub struct CoreError(pub Box<CoreErrorKind>);
106
107#[derive(Debug, thiserror::Error, JsError)]
108pub enum CoreErrorKind {
109 #[class(generic)]
110 #[error("Top-level await is not allowed in synchronous evaluation")]
111 TLA,
112 #[class(inherit)]
113 #[error(transparent)]
114 Js(#[from] Box<JsError>),
115 #[class(inherit)]
116 #[error(transparent)]
117 Io(#[from] std::io::Error),
118 #[class(inherit)]
119 #[error(transparent)]
120 ExtensionTranspiler(deno_error::JsErrorBox),
121 #[class(inherit)]
122 #[error(transparent)]
123 Parse(#[from] CoreModuleParseError),
124 #[class(inherit)]
125 #[error(transparent)]
126 Execute(#[from] CoreModuleExecuteError),
127 #[class(generic)]
128 #[error(
129 "Following modules were passed to ExtModuleLoader but never used:\n{}",
130 .0.iter().map(|s| format!(" - {}\n", s)).collect::<Vec<_>>().join("")
131 )]
132 UnusedModules(Vec<String>),
133 #[class(generic)]
134 #[error(
135 "Following modules were not evaluated; make sure they are imported from other code:\n{}",
136 .0.iter().map(|s| format!(" - {}\n", s)).collect::<Vec<_>>().join("")
137 )]
138 NonEvaluatedModules(Vec<String>),
139 #[class(generic)]
140 #[error("{0} not present in the module map")]
141 MissingFromModuleMap(String),
142 #[class(generic)]
143 #[error("Could not execute {specifier}")]
144 CouldNotExecute {
145 #[source]
146 error: Box<Self>,
147 specifier: String,
148 },
149 #[class(inherit)]
150 #[error(transparent)]
151 JsBox(#[from] deno_error::JsErrorBox),
152 #[class(inherit)]
153 #[error(transparent)]
154 Url(#[from] url::ParseError),
155 #[class(generic)]
156 #[error(
157 "Cannot evaluate module, because JavaScript execution has been terminated"
158 )]
159 ExecutionTerminated,
160 #[class(generic)]
161 #[error(
162 "Promise resolution is still pending but the event loop has already resolved"
163 )]
164 PendingPromiseResolution,
165 #[class(generic)]
166 #[error(
167 "Cannot evaluate dynamically imported module, because JavaScript execution has been terminated"
168 )]
169 EvaluateDynamicImportedModule,
170 #[class(inherit)]
171 #[error(transparent)]
172 Module(ModuleConcreteError),
173 #[class(inherit)]
174 #[error(transparent)]
175 Data(DataError),
176 #[class(inherit)]
177 #[error(transparent)]
178 CreateCodeCache(#[from] CreateCodeCacheError),
179 #[class(inherit)]
180 #[error(transparent)]
181 ExtensionSnapshotMismatch(ExtensionSnapshotMismatchError),
182 #[class(inherit)]
183 #[error(transparent)]
184 ExtensionLazyInitCountMismatch(ExtensionLazyInitCountMismatchError),
185 #[class(inherit)]
186 #[error(transparent)]
187 ExtensionLazyInitOrderMismatch(ExtensionLazyInitOrderMismatchError),
188}
189
190impl CoreError {
191 pub fn print_with_cause(&self) -> String {
192 use std::error::Error;
193 let mut err_message = self.to_string();
194
195 if let Some(source) = self.source() {
196 err_message.push_str(&format!(
197 "\n\nCaused by:\n {}",
198 source.to_string().replace("\n", "\n ")
199 ));
200 }
201
202 err_message
203 }
204
205 pub fn to_v8_error(&self, scope: &mut v8::PinScope) -> v8::Global<v8::Value> {
206 self.as_kind().to_v8_error(scope)
207 }
208}
209
210impl CoreErrorKind {
211 pub fn to_v8_error(&self, scope: &mut v8::PinScope) -> v8::Global<v8::Value> {
212 let err_string = self.get_message().to_string();
213 let mut error_chain = vec![];
214 let mut intermediary_error: Option<&dyn Error> = Some(&self);
215
216 while let Some(err) = intermediary_error {
217 if let Some(source) = err.source() {
218 let source_str = source.to_string();
219 if source_str != err_string {
220 error_chain.push(source_str);
221 }
222
223 intermediary_error = Some(source);
224 } else {
225 intermediary_error = None;
226 }
227 }
228
229 let message = if !error_chain.is_empty() {
230 format!(
231 "{}\n Caused by:\n {}",
232 err_string,
233 error_chain.join("\n ")
234 )
235 } else {
236 err_string
237 };
238
239 let exception =
240 js_class_and_message_to_exception(scope, &self.get_class(), &message);
241 v8::Global::new(scope, exception)
242 }
243}
244
245impl From<v8::DataError> for CoreError {
246 fn from(err: v8::DataError) -> Self {
247 CoreErrorKind::Data(DataError(err)).into_box()
248 }
249}
250
251#[inline]
255fn v8_to_rust_string(s: &v8::String, scope: &mut v8::Isolate) -> String {
256 let mut buf = String::new();
257 s.write_utf8_into(scope, &mut buf);
258 buf
259}
260
261pub fn throw_js_error_class(
262 scope: &mut v8::PinScope,
263 error: &dyn JsErrorClass,
264) {
265 let exception = js_class_and_message_to_exception(
266 scope,
267 &error.get_class(),
268 &error.get_message(),
269 );
270 scope.throw_exception(exception);
271}
272
273fn js_class_and_message_to_exception<'s, 'i>(
274 scope: &mut v8::PinScope<'s, 'i>,
275 _class: &str,
276 message: &str,
277) -> v8::Local<'s, v8::Value> {
278 let message = v8::String::new(scope, message).unwrap();
279 v8::Exception::type_error(scope, message)
291}
292
293pub fn to_v8_error<'s, 'i>(
294 scope: &mut v8::PinScope<'s, 'i>,
295 error: &dyn JsErrorClass,
296) -> v8::Local<'s, v8::Value> {
297 v8::tc_scope!(let tc_scope, scope);
298
299 let cb = JsRealm::exception_state_from_scope(tc_scope)
300 .js_build_custom_error_cb
301 .borrow()
302 .clone()
303 .expect("Custom error builder must be set");
304 let cb = cb.open(tc_scope);
305 let this = v8::undefined(tc_scope).into();
306 let class = v8::String::new(tc_scope, &error.get_class()).unwrap();
307 let message = v8::String::new(tc_scope, &error.get_message()).unwrap();
308 let mut args = vec![class.into(), message.into()];
309
310 let additional_properties = error
311 .get_additional_properties()
312 .map(|(key, value)| {
313 let key = v8::String::new(tc_scope, &key).unwrap().into();
314 let value = match value {
315 PropertyValue::String(value) => {
316 v8::String::new(tc_scope, &value).unwrap().into()
317 }
318 PropertyValue::Number(value) => v8::Number::new(tc_scope, value).into(),
319 };
320
321 v8::Array::new_with_elements(tc_scope, &[key, value]).into()
322 })
323 .collect::<Vec<_>>();
324
325 if !additional_properties.is_empty() {
326 args.push(
327 v8::Array::new_with_elements(tc_scope, &additional_properties).into(),
328 );
329 }
330
331 let maybe_exception = cb.call(tc_scope, this, &args);
332
333 match maybe_exception {
334 Some(exception) => exception,
335 None => {
336 if tc_scope.has_caught() {
341 tc_scope.reset();
342 }
343 message.into()
344 }
345 }
346}
347
348pub fn dispatch_exception<'s, 'i>(
352 scope: &mut v8::PinScope<'s, 'i>,
353 exception: v8::Local<'s, v8::Value>,
354 promise: bool,
355) {
356 let state = JsRuntime::state_from(scope);
357 if let Some(true) = state.with_inspector(|inspector| {
358 inspector.exception_thrown(scope, exception, false);
359 inspector.is_dispatching_message()
360 }) {
361 return;
363 }
364
365 JsRealm::exception_state_from_scope(scope)
366 .set_dispatched_exception(v8::Global::new(scope, exception), promise);
367 scope.terminate_execution();
368}
369
370#[inline(always)]
371pub(crate) fn call_site_evals_key<'s, 'i>(
372 scope: &mut v8::PinScope<'s, 'i>,
373) -> v8::Local<'s, v8::Private> {
374 let name = v8_static_strings::CALL_SITE_EVALS.v8_string(scope).unwrap();
375 v8::Private::for_api(scope, Some(name))
376}
377
378#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
384#[serde(rename_all = "camelCase")]
385pub struct JsError {
386 pub name: Option<String>,
387 pub message: Option<String>,
388 pub stack: Option<String>,
389 pub cause: Option<Box<JsError>>,
390 pub exception_message: String,
391 pub frames: Vec<JsStackFrame>,
392 pub source_line: Option<String>,
393 pub source_line_frame_index: Option<usize>,
394 pub aggregated: Option<Vec<JsError>>,
395 pub additional_properties: Vec<(String, String)>,
396}
397
398impl JsErrorClass for JsError {
399 fn get_class(&self) -> Cow<'static, str> {
400 if let Some(name) = &self.name {
401 Cow::Owned(name.clone())
402 } else {
403 Cow::Borrowed(GENERIC_ERROR)
404 }
405 }
406
407 fn get_message(&self) -> Cow<'static, str> {
408 if let Some(message) = &self.message {
409 Cow::Owned(message.clone())
410 } else {
411 Cow::Borrowed("")
412 }
413 }
414
415 fn get_additional_properties(&self) -> deno_error::AdditionalProperties {
416 Box::new(
417 self
418 .additional_properties
419 .clone()
421 .into_iter()
422 .map(|(k, v)| {
423 (
424 Cow::Owned(k.to_string()),
425 PropertyValue::String(Cow::Owned(v.to_string())),
426 )
427 }),
428 )
429 }
430
431 fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
432 self
433 }
434}
435
436#[derive(Debug, Eq, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
437#[serde(rename_all = "camelCase")]
438pub struct JsStackFrame {
439 pub type_name: Option<String>,
440 pub function_name: Option<String>,
441 pub method_name: Option<String>,
442 pub file_name: Option<String>,
443 pub line_number: Option<i64>,
444 pub column_number: Option<i64>,
445 pub eval_origin: Option<String>,
446 #[serde(rename = "isToplevel")]
449 pub is_top_level: Option<bool>,
450 pub is_eval: bool,
451 pub is_native: bool,
452 pub is_constructor: bool,
453 pub is_async: bool,
454 pub is_promise_all: bool,
455 pub is_wasm: bool,
456 pub promise_index: Option<i64>,
457}
458
459fn apply_source_map<'a>(
461 source_mapper: &mut crate::source_map::SourceMapper,
462 file_name: Cow<'a, str>,
463 line_number: i64,
464 column_number: i64,
465) -> (Cow<'a, str>, i64, i64) {
466 match source_mapper.apply_source_map(
467 &file_name,
468 line_number as u32,
469 column_number as u32,
470 ) {
471 SourceMapApplication::Unchanged => (file_name, line_number, column_number),
472 SourceMapApplication::LineAndColumn {
473 line_number,
474 column_number,
475 } => (file_name, line_number.into(), column_number.into()),
476 SourceMapApplication::LineAndColumnAndFileName {
477 file_name,
478 line_number,
479 column_number,
480 } => (file_name.into(), line_number.into(), column_number.into()),
481 }
482}
483
484fn parse_eval_origin(
498 eval_origin: &str,
499) -> Option<(&str, (&str, i64, i64), &str)> {
500 let eval_at = "eval at ";
508 let mut innermost_start = eval_origin.rfind(eval_at)? + eval_at.len();
511 innermost_start += eval_origin[innermost_start..].find('(')? + 1;
513 if innermost_start >= eval_origin.len() {
514 return None;
516 }
517
518 let mut parts = eval_origin[innermost_start..].rsplitn(3, ':');
523 let column_number_with_rest = parts.next()?;
526 let column_number_end = column_number_with_rest.find(')')?;
527 let column_number = column_number_with_rest[..column_number_end]
528 .parse::<i64>()
529 .ok()?;
530 let line_number = parts.next()?.parse::<i64>().ok()?;
531 let file_name = parts.next()?;
532 let column_start = eval_origin.rfind(':')? + 1;
534 let innermost_end = column_start + column_number_end;
536 Some((
537 &eval_origin[..innermost_start],
538 (file_name, line_number, column_number),
539 &eval_origin[innermost_end..],
540 ))
541}
542
543impl JsStackFrame {
544 pub fn from_location(
545 file_name: Option<String>,
546 line_number: Option<i64>,
547 column_number: Option<i64>,
548 ) -> Self {
549 Self {
550 type_name: None,
551 function_name: None,
552 method_name: None,
553 file_name,
554 line_number,
555 column_number,
556 eval_origin: None,
557 is_top_level: None,
558 is_eval: false,
559 is_native: false,
560 is_constructor: false,
561 is_async: false,
562 is_promise_all: false,
563 is_wasm: false,
564 promise_index: None,
565 }
566 }
567
568 fn from_callsite_object<'s, 'i>(
571 scope: &mut v8::PinScope<'s, 'i>,
572 callsite: v8::Local<'s, v8::Object>,
573 ) -> Option<Self> {
574 macro_rules! call {
575 ($key: ident : $t: ty) => {{
576 let res = call_method(scope, callsite, $key, &[])?;
577 let res: $t = match serde_v8::from_v8(scope, res) {
578 Ok(res) => res,
579 Err(err) => {
580 let message = format!(
581 "Failed to deserialize return value from callsite property '{}' to correct type: {err:?}.",
582 $key
583 );
584 let message = v8::String::new(scope, &message).unwrap();
585 let exception = v8::Exception::type_error(scope, message);
586 scope.throw_exception(exception);
587 return None;
588 }
589 };
590 res
591 }};
592 ($key: ident) => { call!($key : _) };
593 }
594
595 let raw_file_name = call!(GET_FILE_NAME : Option<String>);
596 let is_wasm = raw_file_name
597 .as_deref()
598 .map(|f| f.starts_with("wasm://"))
599 .unwrap_or(false);
600
601 let (file_name, line_number, column_number) = if is_wasm {
604 (
605 raw_file_name,
606 call!(GET_LINE_NUMBER),
607 call!(GET_COLUMN_NUMBER),
608 )
609 } else {
610 let state = JsRuntime::state_from(scope);
611 let mut source_mapper = state.source_mapper.borrow_mut();
612 match (
614 raw_file_name,
615 call!(GET_LINE_NUMBER),
616 call!(GET_COLUMN_NUMBER),
617 ) {
618 (Some(f), Some(l), Some(c)) => {
619 let (file_name, line_num, col_num) =
620 apply_source_map(&mut source_mapper, f.into(), l, c);
621 (Some(file_name.into_owned()), Some(line_num), Some(col_num))
622 }
623 (f, l, c) => (f, l, c),
624 }
625 };
626
627 let eval_origin = if is_wasm {
629 None
630 } else {
631 call!(GET_EVAL_ORIGIN: Option<String>).and_then(|o| {
632 let Some((before, (file, line, col), after)) = parse_eval_origin(&o)
633 else {
634 return Some(o);
635 };
636 let state = JsRuntime::state_from(scope);
637 let mut source_mapper = state.source_mapper.borrow_mut();
638 let (file, line, col) =
639 apply_source_map(&mut source_mapper, file.into(), line, col);
640 Some(format!("{before}{file}:{line}:{col}{after}"))
641 })
642 };
643
644 Some(Self {
645 file_name,
646 line_number,
647 column_number,
648 eval_origin,
649 type_name: call!(GET_TYPE_NAME),
650 function_name: call!(GET_FUNCTION_NAME),
651 method_name: call!(GET_METHOD_NAME),
652 is_top_level: call!(IS_TOPLEVEL),
653 is_eval: call!(IS_EVAL),
654 is_native: call!(IS_NATIVE),
655 is_constructor: call!(IS_CONSTRUCTOR),
656 is_async: call!(IS_ASYNC),
657 is_promise_all: call!(IS_PROMISE_ALL),
658 is_wasm,
659 promise_index: call!(GET_PROMISE_INDEX),
660 })
661 }
662
663 pub fn from_v8_message<'s, 'i>(
667 scope: &mut v8::PinScope<'s, 'i>,
668 message: v8::Local<'s, v8::Message>,
669 ) -> Option<Self> {
670 let f = message.get_script_resource_name(scope)?;
671 let f: v8::Local<v8::String> = f.try_into().ok()?;
672 let f = v8_to_rust_string(&f, scope);
673 let l = message.get_line_number(scope)? as i64;
674 let c = message.get_start_column() as i64 + 1;
676 let state = JsRuntime::state_from(scope);
677 let mut source_mapper = state.source_mapper.borrow_mut();
678 let (file_name, line_num, col_num) =
679 apply_source_map(&mut source_mapper, f.into(), l, c);
680 Some(JsStackFrame::from_location(
681 Some(file_name.into_owned()),
682 Some(line_num),
683 Some(col_num),
684 ))
685 }
686
687 pub fn maybe_format_location(&self) -> Option<String> {
688 Some(format!(
689 "{}:{}:{}",
690 self.file_name.as_ref()?,
691 self.line_number?,
692 self.column_number?
693 ))
694 }
695}
696
697#[inline(always)]
698fn get_property<'s, 'i>(
699 scope: &mut v8::PinScope<'s, 'i>,
700 object: v8::Local<'s, v8::Object>,
701 key: FastStaticString,
702) -> Option<v8::Local<'s, v8::Value>> {
703 let key = key.v8_string(scope).unwrap();
704 object.get(scope, key.into())
705}
706
707fn call_method<'s, 'i, T>(
708 scope: &mut v8::PinScope<'s, 'i>,
709 object: v8::Local<'s, v8::Object>,
710 key: FastStaticString,
711 args: &[v8::Local<'s, v8::Value>],
712) -> Option<v8::Local<'s, T>>
713where
714 v8::Local<'s, T>: TryFrom<v8::Local<'s, v8::Value>, Error: Debug>,
715{
716 let func = match get_property(scope, object, key)?.try_cast::<v8::Function>()
717 {
718 Ok(func) => func,
719 Err(err) => {
720 let message =
721 format!("Callsite property '{key}' is not a function: {err}");
722 let message = v8::String::new(scope, &message).unwrap();
723 let exception = v8::Exception::type_error(scope, message);
724 scope.throw_exception(exception);
725 return None;
726 }
727 };
728
729 let res = func.call(scope, object.into(), args)?;
730
731 let result = match v8::Local::try_from(res) {
732 Ok(result) => result,
733 Err(err) => {
734 let message = format!(
735 "Failed to cast callsite method '{key}' return value to correct value: {err:?}."
736 );
737 let message = v8::String::new(scope, &message).unwrap();
738 let exception = v8::Exception::type_error(scope, message);
739 scope.throw_exception(exception);
740 return None;
741 }
742 };
743
744 Some(result)
745}
746
747#[derive(Debug, Default, serde::Deserialize)]
748pub(crate) struct NativeJsError {
749 pub name: Option<String>,
750 pub message: Option<String>,
751 }
754
755impl JsError {
756 pub fn is_same_error(&self, other: &JsError) -> bool {
762 let a = self;
763 let b = other;
764 a.name == b.name
767 && a.message == b.message
768 && a.stack == b.stack
769 && (a.exception_message == b.exception_message
772 || a.exception_message.replace(" (in promise) ", " ") == b.exception_message.replace(" (in promise) ", " "))
773 && a.frames == b.frames
774 && a.source_line == b.source_line
775 && a.source_line_frame_index == b.source_line_frame_index
776 && a.aggregated == b.aggregated
777 }
778
779 pub fn from_v8_exception<'s, 'i>(
780 scope: &mut v8::PinScope<'s, 'i>,
781 exception: v8::Local<'s, v8::Value>,
782 ) -> Box<Self> {
783 Box::new(Self::inner_from_v8_exception(
784 scope,
785 exception,
786 Default::default(),
787 ))
788 }
789
790 pub fn from_v8_message<'s, 'i>(
791 scope: &mut v8::PinScope<'s, 'i>,
792 msg: v8::Local<'s, v8::Message>,
793 ) -> Box<Self> {
794 v8::scope!(let scope, scope);
797
798 let exception_message = v8_to_rust_string(&msg.get(scope), scope);
799
800 let mut frames: Vec<JsStackFrame> = vec![];
802 let mut source_line = None;
803 let mut source_line_frame_index = None;
804
805 if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
806 frames = vec![stack_frame];
807 }
808 {
809 let state = JsRuntime::state_from(scope);
810 let mut source_mapper = state.source_mapper.borrow_mut();
811 for (i, frame) in frames.iter().enumerate() {
812 if let (Some(file_name), Some(line_number)) =
813 (&frame.file_name, frame.line_number)
814 && !file_name.trim_start_matches('[').starts_with("ext:")
815 && !file_name.starts_with("node:")
816 {
817 source_line = source_mapper.get_source_line(file_name, line_number);
818 source_line_frame_index = Some(i);
819 break;
820 }
821 }
822 }
823
824 Box::new(Self {
825 name: None,
826 message: None,
827 exception_message,
828 cause: None,
829 source_line,
830 source_line_frame_index,
831 frames,
832 stack: None,
833 aggregated: None,
834 additional_properties: vec![],
835 })
836 }
837
838 fn inner_from_v8_exception<'s, 'i>(
839 scope: &mut v8::PinScope<'s, 'i>,
840 exception: v8::Local<'s, v8::Value>,
841 mut seen: HashSet<v8::Local<'s, v8::Object>>,
842 ) -> Self {
843 v8::scope!(let scope, scope);
846
847 let msg = v8::Exception::create_message(scope, exception);
848
849 let mut exception_message = None;
850 let exception_state = JsRealm::exception_state_from_scope(scope);
851
852 let js_format_exception_cb =
853 exception_state.js_format_exception_cb.borrow().clone();
854 if let Some(format_exception_cb) = js_format_exception_cb {
855 let format_exception_cb = format_exception_cb.open(scope);
856 let this = v8::undefined(scope).into();
857 let formatted = format_exception_cb.call(scope, this, &[exception]);
858 if let Some(formatted) = formatted
859 && let Ok(formatted) = formatted.try_cast::<v8::String>()
860 {
861 exception_message = Some(v8_to_rust_string(&formatted, scope));
862 }
863 }
864
865 if is_instance_of_error(scope, exception) {
866 let v8_exception = exception;
867 let exception: v8::Local<v8::Object> = exception.try_into().unwrap();
869 let cause = get_property(scope, exception, v8_static_strings::CAUSE);
870 let e: NativeJsError =
871 serde_v8::from_v8(scope, exception.into()).unwrap_or_default();
872 let name = e.name.clone().unwrap_or_else(|| GENERIC_ERROR.to_string());
874 let message_prop = e.message.clone().unwrap_or_default();
875 let exception_message = exception_message.unwrap_or_else(|| {
876 if !name.is_empty() && !message_prop.is_empty() {
877 format!("Uncaught {name}: {message_prop}")
878 } else if !name.is_empty() {
879 format!("Uncaught {name}")
880 } else if !message_prop.is_empty() {
881 format!("Uncaught {message_prop}")
882 } else {
883 "Uncaught".to_string()
884 }
885 });
886 let cause = cause.and_then(|cause| {
887 if cause.is_undefined() || seen.contains(&exception) {
888 None
889 } else {
890 seen.insert(exception);
891 Some(Box::new(JsError::inner_from_v8_exception(
892 scope, cause, seen,
893 )))
894 }
895 });
896
897 let stack = get_property(scope, exception, v8_static_strings::STACK);
900 let stack: Option<v8::Local<v8::String>> =
901 stack.and_then(|s| s.try_into().ok());
902 let stack = stack.map(|s| v8_to_rust_string(&s, scope));
903
904 let frames_v8 = {
906 let key = call_site_evals_key(scope);
907 exception.get_private(scope, key)
908 };
909 let frames_v8: Option<v8::Local<v8::Array>> =
911 frames_v8.and_then(|a| a.try_into().ok());
912
913 let mut frames: Vec<JsStackFrame> = match frames_v8 {
915 Some(frames_v8) => {
916 let mut buf = Vec::with_capacity(frames_v8.length() as usize);
917 for i in 0..frames_v8.length() {
918 let callsite = frames_v8.get_index(scope, i).unwrap().cast();
919 v8::tc_scope!(let tc_scope, scope);
920
921 let Some(stack_frame) =
922 JsStackFrame::from_callsite_object(tc_scope, callsite)
923 else {
924 let exc = tc_scope.exception().expect(
925 "JsStackFrame::from_callsite_object raised an exception",
926 );
927 let message = match exc.to_string(tc_scope) {
928 Some(s) => v8_to_rust_string(&s, tc_scope),
929 None => String::new(),
930 };
931 #[allow(
932 clippy::print_stderr,
933 reason = "intentional warning output"
934 )]
935 {
936 eprintln!(
937 "warning: Failed to create JsStackFrame from callsite object: {message}. This is a bug in deno"
938 );
939 }
940 break;
941 };
942 buf.push(stack_frame);
943 }
944 buf
945 }
946 None => vec![],
947 };
948 let mut source_line = None;
949 let mut source_line_frame_index = None;
950
951 if frames.is_empty()
956 && let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg)
957 {
958 frames = vec![stack_frame];
959 }
960 {
961 let state = JsRuntime::state_from(scope);
962 let mut source_mapper = state.source_mapper.borrow_mut();
963 for (i, frame) in frames.iter().enumerate() {
964 if let (Some(file_name), Some(line_number)) =
965 (&frame.file_name, frame.line_number)
966 && !file_name.trim_start_matches('[').starts_with("ext:")
967 && !file_name.starts_with("node:")
968 {
969 source_line = source_mapper.get_source_line(file_name, line_number);
970 source_line_frame_index = Some(i);
971 break;
972 }
973 }
974 }
975
976 let mut aggregated: Option<Vec<JsError>> = None;
977 if is_aggregate_error(scope, v8_exception) {
978 let aggregated_errors =
980 get_property(scope, exception, v8_static_strings::ERRORS);
981 let aggregated_errors: Option<v8::Local<v8::Array>> =
982 aggregated_errors.and_then(|a| a.try_into().ok());
983
984 if let Some(errors) = aggregated_errors
985 && errors.length() > 0
986 {
987 let mut agg = vec![];
988 for i in 0..errors.length() {
989 let error = errors.get_index(scope, i).unwrap();
990 let js_error = Self::from_v8_exception(scope, error);
991 agg.push(*js_error);
992 }
993 aggregated = Some(agg);
994 }
995 };
996
997 let additional_properties_string =
998 v8::String::new(scope, "errorAdditionalPropertyKeys").unwrap();
999 let additional_properties_key =
1000 v8::Symbol::for_key(scope, additional_properties_string);
1001 let additional_properties =
1002 exception.get(scope, additional_properties_key.into());
1003
1004 let additional_properties = if let Some(arr) =
1005 additional_properties.and_then(|keys| keys.try_cast::<v8::Array>().ok())
1006 {
1007 let mut out = Vec::with_capacity(arr.length() as usize);
1008
1009 for i in 0..arr.length() {
1010 let Some(key) = arr.get_index(scope, i) else {
1011 continue;
1012 };
1013 let key_name = match key.to_string(scope) {
1014 Some(s) => v8_to_rust_string(&s, scope),
1015 None => continue,
1016 };
1017
1018 let Some(val) = exception.get(scope, key) else {
1019 continue;
1020 };
1021 let val_str = match val.to_string(scope) {
1022 Some(s) => v8_to_rust_string(&s, scope),
1023 None => continue,
1024 };
1025 out.push((key_name, val_str));
1026 }
1027
1028 out
1029 } else {
1030 vec![]
1031 };
1032
1033 Self {
1034 name: e.name,
1035 message: e.message,
1036 exception_message,
1037 cause,
1038 source_line,
1039 source_line_frame_index,
1040 frames,
1041 stack,
1042 aggregated,
1043 additional_properties,
1044 }
1045 } else {
1046 let exception_message = exception_message
1047 .unwrap_or_else(|| v8_to_rust_string(&msg.get(scope), scope));
1048 Self {
1052 name: None,
1053 message: None,
1054 exception_message,
1055 cause: None,
1056 source_line: None,
1057 source_line_frame_index: None,
1058 frames: vec![],
1059 stack: None,
1060 aggregated: None,
1061 additional_properties: vec![],
1062 }
1063 }
1064 }
1065}
1066
1067impl std::error::Error for JsError {}
1068
1069impl Display for JsError {
1070 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1071 if let Some(stack) = &self.stack {
1072 let stack_lines = stack.lines();
1073 if stack_lines.count() > 1 {
1074 return write!(f, "{stack}");
1075 }
1076 }
1077 write!(f, "{}", self.exception_message)?;
1078 let location = self.frames.first().and_then(|f| f.maybe_format_location());
1079 if let Some(location) = location {
1080 write!(f, "\n at {location}")?;
1081 }
1082 Ok(())
1083 }
1084}
1085
1086pub(crate) fn is_instance_of_error<'s, 'i>(
1092 scope: &mut v8::PinScope<'s, 'i>,
1093 value: v8::Local<'s, v8::Value>,
1094) -> bool {
1095 if !value.is_object() {
1096 return false;
1097 }
1098 let message = v8::String::empty(scope);
1099 let error_prototype = v8::Exception::error(scope, message)
1100 .to_object(scope)
1101 .unwrap()
1102 .get_prototype(scope)
1103 .unwrap();
1104 let mut maybe_prototype =
1105 value.to_object(scope).unwrap().get_prototype(scope);
1106 while let Some(prototype) = maybe_prototype {
1107 if !prototype.is_object() {
1108 return false;
1109 }
1110 if prototype.strict_equals(error_prototype) {
1111 return true;
1112 }
1113 maybe_prototype = prototype
1114 .to_object(scope)
1115 .and_then(|o| o.get_prototype(scope));
1116 }
1117 false
1118}
1119
1120pub(crate) fn is_aggregate_error<'s, 'i>(
1127 scope: &mut v8::PinScope<'s, 'i>,
1128 value: v8::Local<'s, v8::Value>,
1129) -> bool {
1130 let mut maybe_prototype = Some(value);
1131 while let Some(prototype) = maybe_prototype {
1132 if !prototype.is_object() {
1133 return false;
1134 }
1135
1136 let prototype = prototype.to_object(scope).unwrap();
1137 let prototype_name =
1138 match get_property(scope, prototype, v8_static_strings::CONSTRUCTOR) {
1139 Some(constructor) => {
1140 let ctor = constructor.to_object(scope).unwrap();
1141 get_property(scope, ctor, v8_static_strings::NAME)
1142 .and_then(|v| v.to_string(scope))
1143 .map(|s| v8_to_rust_string(&s, scope))
1144 }
1145 None => return false,
1146 };
1147
1148 if prototype_name == Some(String::from("AggregateError")) {
1149 return true;
1150 }
1151
1152 maybe_prototype = prototype.get_prototype(scope);
1153 }
1154
1155 false
1156}
1157
1158pub(crate) fn has_call_site<'s, 'i>(
1161 scope: &mut v8::PinScope<'s, 'i>,
1162 exception: v8::Local<'s, v8::Value>,
1163) -> bool {
1164 if !exception.is_object() {
1165 return false;
1166 }
1167 let exception = exception.to_object(scope).unwrap();
1168 get_property(scope, exception, v8_static_strings::STACK);
1171 let frames_v8 = {
1172 let key = call_site_evals_key(scope);
1173 exception.get_private(scope, key)
1174 };
1175 let frames_v8: Option<v8::Local<v8::Array>> =
1176 frames_v8.and_then(|a| a.try_into().ok());
1177 if let Some(frames_v8) = frames_v8
1178 && frames_v8.length() > 0
1179 {
1180 return true;
1181 }
1182 false
1183}
1184
1185const DATA_URL_ABBREV_THRESHOLD: usize = 150;
1186
1187fn to_percent_decoded_str(s: &str) -> String {
1188 match percent_encoding::percent_decode_str(s).decode_utf8() {
1189 Ok(s) => s.to_string(),
1190 Err(_) => s.to_string(),
1192 }
1193}
1194
1195pub fn relative_specifier(from: &Url, to: &Url) -> Option<String> {
1196 let is_dir = to.path().ends_with('/');
1197
1198 if is_dir && from == to {
1199 return Some("./".to_string());
1200 }
1201
1202 let text = from.make_relative(to)?;
1203 let text = if text.starts_with("../") || text.starts_with("./") {
1204 text
1205 } else {
1206 format!("./{text}")
1207 };
1208 Some(to_percent_decoded_str(&text))
1209}
1210
1211fn relative_specifier_within(from: &Url, to: &Url) -> Option<String> {
1213 let relative = relative_specifier(from, to)?;
1214 if relative.starts_with("../") {
1215 return None;
1216 }
1217 Some(relative)
1218}
1219
1220pub struct FileNameParts<'a> {
1221 pub working_dir_path: Option<Cow<'a, str>>,
1222 pub file_name: Cow<'a, str>,
1223}
1224
1225impl<'a> FileNameParts<'a> {
1226 pub fn into_owned(self) -> FileNameParts<'static> {
1227 FileNameParts {
1228 working_dir_path: self.working_dir_path.map(|s| match s {
1229 Cow::Borrowed(s) => Cow::Owned(s.to_owned()),
1230 Cow::Owned(s) => Cow::Owned(s),
1231 }),
1232 file_name: match self.file_name {
1233 Cow::Borrowed(s) => Cow::Owned(s.to_owned()),
1234 Cow::Owned(s) => Cow::Owned(s),
1235 },
1236 }
1237 }
1238}
1239
1240pub fn format_file_name<'a>(
1241 file_name: &'a str,
1242 maybe_initial_cwd: Option<&Url>,
1243) -> FileNameParts<'a> {
1244 abbrev_file_name(file_name, maybe_initial_cwd).unwrap_or_else(|| {
1245 match percent_encoding::percent_decode_str(file_name).decode_utf8() {
1247 Ok(file_name) => FileNameParts {
1248 working_dir_path: None,
1249 file_name,
1250 },
1251 Err(_) => FileNameParts {
1253 working_dir_path: None,
1254 file_name: file_name.into(),
1255 },
1256 }
1257 })
1258}
1259
1260fn abbrev_file_name<'a>(
1261 file_name: &'a str,
1262 maybe_initial_cwd: Option<&Url>,
1263) -> Option<FileNameParts<'a>> {
1264 let Ok(url) = Url::parse(file_name) else {
1265 return None;
1266 };
1267
1268 if url.scheme() == "data" {
1270 if file_name.len() <= DATA_URL_ABBREV_THRESHOLD {
1271 return Some(FileNameParts {
1272 working_dir_path: None,
1273 file_name: file_name.into(),
1274 });
1275 }
1276 let (head, tail) = url.path().split_once(',')?;
1277 let len = tail.len();
1278 let start = tail.get(0..20)?;
1279 let end = tail.get(len - 20..)?;
1280 return Some(FileNameParts {
1281 working_dir_path: None,
1282 file_name: format!("{}:{},{}......{}", url.scheme(), head, start, end)
1283 .into(),
1284 });
1285 }
1286
1287 if let Some(initial_cwd) = maybe_initial_cwd
1289 && let Some(relative) = relative_specifier_within(initial_cwd, &url)
1290 {
1291 return Some(FileNameParts {
1292 working_dir_path: Some(initial_cwd.to_string().into()),
1293 file_name: relative.into(),
1294 });
1295 }
1296
1297 None
1299}
1300
1301fn format_eval_origin<'a>(
1302 eval_origin: &'a str,
1303 maybe_initial_cwd: Option<&Url>,
1304) -> Cow<'a, str> {
1305 let Some((before, (file, line, col), after)) = parse_eval_origin(eval_origin)
1306 else {
1307 return eval_origin.into();
1308 };
1309 let formatted_file = format_file_name(file, maybe_initial_cwd);
1310
1311 Cow::Owned(format!(
1312 "{before}{}:{line}:{col}{after}",
1313 if let Some(working_dir_path) = formatted_file.working_dir_path {
1314 Cow::Owned(format!(
1315 "{}{}",
1316 working_dir_path,
1317 formatted_file.file_name.trim_start_matches("./")
1318 ))
1319 } else {
1320 formatted_file.file_name
1321 }
1322 ))
1323}
1324
1325pub(crate) fn exception_to_err_result<'s, 'i, T>(
1326 scope: &mut v8::PinScope<'s, 'i>,
1327 exception: v8::Local<'s, v8::Value>,
1328 in_promise: bool,
1329 clear_error: bool,
1330) -> Result<T, Box<JsError>> {
1331 Err(exception_to_err(scope, exception, in_promise, clear_error))
1332}
1333
1334pub fn exception_to_err<'s, 'i>(
1335 scope: &mut v8::PinScope<'s, 'i>,
1336 exception: v8::Local<'s, v8::Value>,
1337 mut in_promise: bool,
1338 clear_error: bool,
1339) -> Box<JsError> {
1340 let state = JsRealm::exception_state_from_scope(scope);
1341
1342 let mut was_terminating_execution = scope.is_execution_terminating();
1343
1344 scope.cancel_terminate_execution();
1349 let exception = match state.get_dispatched_exception_as_local(scope) {
1350 Some(dispatched_exception) => {
1351 in_promise = state.is_dispatched_exception_promise();
1355 if clear_error {
1356 state.clear_error();
1357 was_terminating_execution = false;
1358 }
1359 dispatched_exception
1360 }
1361 _ => {
1362 if was_terminating_execution && exception.is_null_or_undefined() {
1363 let message = v8::String::new(scope, "execution terminated").unwrap();
1365 v8::Exception::error(scope, message)
1366 } else {
1367 exception
1369 }
1370 }
1371 };
1372
1373 let mut js_error = JsError::from_v8_exception(scope, exception);
1374 if in_promise {
1375 js_error.exception_message = format!(
1376 "Uncaught (in promise) {}",
1377 js_error.exception_message.trim_start_matches("Uncaught ")
1378 );
1379 }
1380
1381 if was_terminating_execution {
1382 scope.terminate_execution();
1384 }
1385
1386 js_error
1387}
1388
1389v8_static_strings::v8_static_strings! {
1390 ERROR = "Error",
1391 GET_FILE_NAME = "getFileName",
1392 GET_SCRIPT_NAME_OR_SOURCE_URL = "getScriptNameOrSourceURL",
1393 GET_THIS = "getThis",
1394 GET_TYPE_NAME = "getTypeName",
1395 GET_FUNCTION = "getFunction",
1396 GET_FUNCTION_NAME = "getFunctionName",
1397 GET_METHOD_NAME = "getMethodName",
1398 GET_LINE_NUMBER = "getLineNumber",
1399 GET_COLUMN_NUMBER = "getColumnNumber",
1400 GET_EVAL_ORIGIN = "getEvalOrigin",
1401 IS_TOPLEVEL = "isToplevel",
1402 IS_EVAL = "isEval",
1403 IS_NATIVE = "isNative",
1404 IS_CONSTRUCTOR = "isConstructor",
1405 IS_ASYNC = "isAsync",
1406 IS_PROMISE_ALL = "isPromiseAll",
1407 GET_PROMISE_INDEX = "getPromiseIndex",
1408 TO_STRING = "toString",
1409 PREPARE_STACK_TRACE = "prepareStackTrace",
1410 ORIGINAL = "deno_core::original_call_site",
1411 SOURCE_MAPPED_INFO = "deno_core::source_mapped_call_site_info",
1412 ERROR_RECEIVER_IS_NOT_VALID_CALLSITE_OBJECT = "The receiver is not a valid callsite object.",
1413}
1414
1415#[inline(always)]
1416pub(crate) fn original_call_site_key<'s, 'i>(
1417 scope: &mut v8::PinScope<'s, 'i>,
1418) -> v8::Local<'s, v8::Private> {
1419 let name = ORIGINAL.v8_string(scope).unwrap();
1420 v8::Private::for_api(scope, Some(name))
1421}
1422
1423pub(crate) fn source_mapped_info_key<'s, 'i>(
1424 scope: &mut v8::PinScope<'s, 'i>,
1425) -> v8::Local<'s, v8::Private> {
1426 let name = SOURCE_MAPPED_INFO.v8_string(scope).unwrap();
1427 v8::Private::for_api(scope, Some(name))
1428}
1429
1430fn make_patched_callsite<'s, 'i>(
1431 scope: &mut v8::PinScope<'s, 'i>,
1432 callsite: v8::Local<'s, v8::Object>,
1433 prototype: v8::Local<'s, v8::Object>,
1434) -> v8::Local<'s, v8::Object> {
1435 let out_obj = v8::Object::with_prototype_and_properties(
1436 scope,
1437 prototype.into(),
1438 &[],
1439 &[],
1440 );
1441 let orig_key = original_call_site_key(scope);
1442 out_obj.set_private(scope, orig_key, callsite.into());
1443 out_obj
1444}
1445
1446fn original_call_site<'s, 'i>(
1447 scope: &mut v8::PinScope<'s, 'i>,
1448 this: v8::Local<'s, v8::Object>,
1449) -> Option<v8::Local<'s, v8::Object>> {
1450 let orig_key = original_call_site_key(scope);
1451 let Some(orig) = this
1452 .get_private(scope, orig_key)
1453 .and_then(|v| v8::Local::<v8::Object>::try_from(v).ok())
1454 else {
1455 let message = ERROR_RECEIVER_IS_NOT_VALID_CALLSITE_OBJECT
1456 .v8_string(scope)
1457 .unwrap();
1458 let exception = v8::Exception::type_error(scope, message);
1459 scope.throw_exception(exception);
1460 return None;
1461 };
1462 Some(orig)
1463}
1464
1465macro_rules! make_callsite_fn {
1466 ($fn:ident, $field:ident) => {
1467 pub fn $fn<'s, 'i>(
1468 scope: &mut v8::PinScope<'s, 'i>,
1469 args: v8::FunctionCallbackArguments<'s>,
1470 mut rv: v8::ReturnValue<'_>,
1471 ) {
1472 let Some(orig) = original_call_site(scope, args.this()) else {
1473 return;
1474 };
1475 let key = $field.v8_string(scope).unwrap().into();
1476 let orig_ret = orig
1477 .cast::<v8::Object>()
1478 .get(scope, key)
1479 .unwrap()
1480 .cast::<v8::Function>()
1481 .call(scope, orig.into(), &[]);
1482 rv.set(orig_ret.unwrap_or_else(|| v8::undefined(scope).into()));
1483 }
1484 };
1485}
1486
1487fn maybe_to_path_str(string: &str) -> Option<String> {
1488 if string.starts_with("file://") {
1489 Some(
1490 deno_path_util::url_to_file_path(&Url::parse(string).ok()?)
1491 .ok()?
1492 .to_string_lossy()
1493 .into_owned(),
1494 )
1495 } else {
1496 None
1497 }
1498}
1499
1500pub mod callsite_fns {
1501 use capacity_builder::StringBuilder;
1502
1503 use super::*;
1504 use crate::FromV8;
1505 use crate::ToV8;
1506 use crate::convert;
1507
1508 enum SourceMappedCallsiteInfo<'a> {
1509 Ref(v8::Local<'a, v8::Array>),
1510 Value {
1511 file_name: v8::Local<'a, v8::Value>,
1512 line_number: v8::Local<'a, v8::Value>,
1513 column_number: v8::Local<'a, v8::Value>,
1514 },
1515 }
1516 impl<'s> SourceMappedCallsiteInfo<'s> {
1517 #[inline]
1518 fn file_name<'i>(
1519 &self,
1520 scope: &mut v8::PinScope<'s, 'i>,
1521 ) -> v8::Local<'s, v8::Value> {
1522 match self {
1523 Self::Ref(array) => array.get_index(scope, 0).unwrap(),
1524 Self::Value { file_name, .. } => *file_name,
1525 }
1526 }
1527 #[inline]
1528 fn line_number<'i>(
1529 &self,
1530 scope: &mut v8::PinScope<'s, 'i>,
1531 ) -> v8::Local<'s, v8::Value> {
1532 match self {
1533 Self::Ref(array) => array.get_index(scope, 1).unwrap(),
1534 Self::Value { line_number, .. } => *line_number,
1535 }
1536 }
1537 #[inline]
1538 fn column_number<'i>(
1539 &self,
1540 scope: &mut v8::PinScope<'s, 'i>,
1541 ) -> v8::Local<'s, v8::Value> {
1542 match self {
1543 Self::Ref(array) => array.get_index(scope, 2).unwrap(),
1544 Self::Value { column_number, .. } => *column_number,
1545 }
1546 }
1547 }
1548
1549 type MaybeValue<'a> = Option<v8::Local<'a, v8::Value>>;
1550
1551 fn maybe_apply_source_map<'s, 'i>(
1552 scope: &mut v8::PinScope<'s, 'i>,
1553 file_name: MaybeValue<'s>,
1554 line_number: MaybeValue<'s>,
1555 column_number: MaybeValue<'s>,
1556 ) -> Option<(String, i64, i64)> {
1557 let file_name = serde_v8::to_utf8(file_name?.try_cast().ok()?, scope);
1558 let convert::Number(line_number) =
1559 FromV8::from_v8(scope, line_number?).ok()?;
1560 let convert::Number(column_number) =
1561 FromV8::from_v8(scope, column_number?).ok()?;
1562
1563 let state = JsRuntime::state_from(scope);
1564 let mut source_mapper = state.source_mapper.borrow_mut();
1565 let (mapped_file_name, mapped_line_number, mapped_column_number) =
1566 apply_source_map(
1567 &mut source_mapper,
1568 Cow::Owned(file_name),
1569 line_number,
1570 column_number,
1571 );
1572 Some((
1573 mapped_file_name.into_owned(),
1574 mapped_line_number,
1575 mapped_column_number,
1576 ))
1577 }
1578 fn source_mapped_call_site_info<'s, 'i>(
1579 scope: &mut v8::PinScope<'s, 'i>,
1580 callsite: v8::Local<'s, v8::Object>,
1581 ) -> Option<SourceMappedCallsiteInfo<'s>> {
1582 let key = source_mapped_info_key(scope);
1583 if let Some(info) = callsite.get_private(scope, key)
1585 && let Ok(array) = info.try_cast::<v8::Array>()
1586 {
1587 return Some(SourceMappedCallsiteInfo::Ref(array));
1588 }
1589 let orig_callsite = original_call_site(scope, callsite)?;
1590
1591 let file_name =
1592 call_method::<v8::Value>(scope, orig_callsite, super::GET_FILE_NAME, &[]);
1593 let line_number = call_method::<v8::Value>(
1594 scope,
1595 orig_callsite,
1596 super::GET_LINE_NUMBER,
1597 &[],
1598 );
1599 let column_number = call_method::<v8::Value>(
1600 scope,
1601 orig_callsite,
1602 super::GET_COLUMN_NUMBER,
1603 &[],
1604 );
1605
1606 let is_wasm = file_name
1608 .and_then(|v| v.try_cast::<v8::String>().ok())
1609 .map(|s| serde_v8::to_utf8(s, scope).starts_with("wasm://"))
1610 .unwrap_or(false);
1611
1612 let info = v8::Array::new(scope, 3);
1613
1614 if !is_wasm
1616 && let Some((mapped_file_name, mapped_line_number, mapped_column_number)) =
1617 maybe_apply_source_map(scope, file_name, line_number, column_number)
1618 {
1619 let mapped_file_name_trimmed =
1620 maybe_to_path_str(&mapped_file_name).unwrap_or(mapped_file_name);
1621 let mapped_file_name = crate::FastString::from(mapped_file_name_trimmed)
1622 .v8_string(scope)
1623 .unwrap();
1624 let Ok(mapped_line_number) =
1625 convert::Number(mapped_line_number).to_v8(scope);
1626 let Ok(mapped_column_number) =
1627 convert::Number(mapped_column_number).to_v8(scope);
1628 info.set_index(scope, 0, mapped_file_name.into());
1629 info.set_index(scope, 1, mapped_line_number);
1630 info.set_index(scope, 2, mapped_column_number);
1631 callsite.set_private(scope, key, info.into());
1632 Some(SourceMappedCallsiteInfo::Value {
1633 file_name: mapped_file_name.into(),
1634 line_number: mapped_line_number,
1635 column_number: mapped_column_number,
1636 })
1637 } else {
1638 let file_name = file_name.unwrap_or_else(|| v8::undefined(scope).into());
1639 let line_number =
1640 line_number.unwrap_or_else(|| v8::undefined(scope).into());
1641 let column_number =
1642 column_number.unwrap_or_else(|| v8::undefined(scope).into());
1643 info.set_index(scope, 0, file_name);
1644 info.set_index(scope, 1, line_number);
1645 info.set_index(scope, 2, column_number);
1646 callsite.set_private(scope, key, info.into());
1647 Some(SourceMappedCallsiteInfo::Ref(info))
1648 }
1649 }
1650
1651 make_callsite_fn!(get_this, GET_THIS);
1652 make_callsite_fn!(get_type_name, GET_TYPE_NAME);
1653 make_callsite_fn!(get_function, GET_FUNCTION);
1654 make_callsite_fn!(get_function_name, GET_FUNCTION_NAME);
1655 make_callsite_fn!(get_method_name, GET_METHOD_NAME);
1656
1657 pub fn get_file_name<'s, 'i>(
1658 scope: &mut v8::PinScope<'s, 'i>,
1659 args: v8::FunctionCallbackArguments<'s>,
1660 mut rv: v8::ReturnValue<'_>,
1661 ) {
1662 if let Some(info) = source_mapped_call_site_info(scope, args.this()) {
1663 rv.set(info.file_name(scope));
1664 }
1665 }
1666
1667 pub fn get_line_number<'s, 'i>(
1668 scope: &mut v8::PinScope<'s, 'i>,
1669 args: v8::FunctionCallbackArguments<'s>,
1670 mut rv: v8::ReturnValue<'_>,
1671 ) {
1672 if let Some(info) = source_mapped_call_site_info(scope, args.this()) {
1673 rv.set(info.line_number(scope));
1674 }
1675 }
1676
1677 pub fn get_column_number<'s, 'i>(
1678 scope: &mut v8::PinScope<'s, 'i>,
1679 args: v8::FunctionCallbackArguments<'s>,
1680 mut rv: v8::ReturnValue<'_>,
1681 ) {
1682 if let Some(info) = source_mapped_call_site_info(scope, args.this()) {
1683 rv.set(info.column_number(scope));
1684 }
1685 }
1686
1687 make_callsite_fn!(get_eval_origin, GET_EVAL_ORIGIN);
1688 make_callsite_fn!(is_toplevel, IS_TOPLEVEL);
1689 make_callsite_fn!(is_eval, IS_EVAL);
1690 make_callsite_fn!(is_native, IS_NATIVE);
1691 make_callsite_fn!(is_constructor, IS_CONSTRUCTOR);
1692 make_callsite_fn!(is_async, IS_ASYNC);
1693 make_callsite_fn!(is_promise_all, IS_PROMISE_ALL);
1694 make_callsite_fn!(get_promise_index, GET_PROMISE_INDEX);
1695 make_callsite_fn!(
1696 get_script_name_or_source_url,
1697 GET_SCRIPT_NAME_OR_SOURCE_URL
1698 );
1699
1700 fn to_string_inner<'s, 'i>(
1702 scope: &mut v8::PinScope<'s, 'i>,
1703 this: v8::Local<'s, v8::Object>,
1704 orig: v8::Local<'s, v8::Object>,
1705 orig_to_string_v8: v8::Local<'s, v8::String>,
1706 ) -> Option<v8::Local<'s, v8::String>> {
1707 let orig_to_string = serde_v8::to_utf8(orig_to_string_v8, scope);
1708 let orig_file_name =
1710 call_method::<v8::Value>(scope, orig, GET_FILE_NAME, &[])
1711 .and_then(|v| v.try_cast::<v8::String>().ok())?;
1712 let orig_file_name = serde_v8::to_utf8(orig_file_name, scope);
1713
1714 if orig_file_name.starts_with("wasm://") {
1717 return Some(orig_to_string_v8);
1718 }
1719
1720 let orig_line_number =
1721 call_method::<v8::Value>(scope, orig, GET_LINE_NUMBER, &[])
1722 .and_then(|v| v.try_cast::<v8::Number>().ok())?;
1723 let orig_column_number =
1724 call_method::<v8::Value>(scope, orig, GET_COLUMN_NUMBER, &[])
1725 .and_then(|v| v.try_cast::<v8::Number>().ok())?;
1726 let orig_line_number = orig_line_number.value() as i64;
1727 let orig_column_number = orig_column_number.value() as i64;
1728 let orig_file_name_line_col =
1729 fmt_file_line_col(&orig_file_name, orig_line_number, orig_column_number);
1730 let mapped = source_mapped_call_site_info(scope, this)?;
1731 let mapped_file_name = match mapped.file_name(scope).to_string(scope) {
1732 Some(s) => v8_to_rust_string(&s, scope),
1733 None => String::new(),
1734 };
1735 let mapped_line_num = mapped
1736 .line_number(scope)
1737 .try_cast::<v8::Number>()
1738 .ok()
1739 .map(|n| n.value() as i64)?;
1740 let mapped_col_num =
1741 mapped.column_number(scope).cast::<v8::Number>().value() as i64;
1742 let file_name_line_col =
1743 fmt_file_line_col(&mapped_file_name, mapped_line_num, mapped_col_num);
1744 let to_string = orig_to_string
1746 .replace(&orig_file_name_line_col, &file_name_line_col)
1747 .replace(&orig_file_name, &mapped_file_name); Some(crate::FastString::from(to_string).v8_string(scope).unwrap())
1749 }
1750
1751 fn fmt_file_line_col(file: &str, line: i64, col: i64) -> String {
1752 StringBuilder::build(|builder| {
1753 builder.append(file);
1754 builder.append(':');
1755 builder.append(line);
1756 builder.append(':');
1757 builder.append(col);
1758 })
1759 .unwrap()
1760 }
1761
1762 pub fn to_string<'s, 'i>(
1763 scope: &mut v8::PinScope<'s, 'i>,
1764 args: v8::FunctionCallbackArguments<'s>,
1765 mut rv: v8::ReturnValue<'_>,
1766 ) {
1767 let this = args.this();
1768 let Some(orig) = original_call_site(scope, this) else {
1769 return;
1770 };
1771 let Some(orig_to_string_v8) =
1773 call_method::<v8::String>(scope, orig, TO_STRING, &[])
1774 else {
1775 return;
1776 };
1777
1778 if let Some(v8_str) = to_string_inner(scope, this, orig, orig_to_string_v8)
1779 {
1780 rv.set(v8_str.into());
1781 } else {
1782 rv.set(orig_to_string_v8.into());
1783 }
1784 }
1785}
1786
1787pub(crate) fn make_callsite_prototype<'s, 'i>(
1805 scope: &mut v8::PinScope<'s, 'i>,
1806) -> v8::Local<'s, v8::Object> {
1807 let template = v8::ObjectTemplate::new(scope);
1808
1809 macro_rules! set_attr {
1810 ($scope:ident, $template:ident, $fn:ident, $field:ident) => {
1811 let key = $field.v8_string($scope).unwrap().into();
1812 $template.set_with_attr(
1813 key,
1814 v8::FunctionBuilder::<v8::FunctionTemplate>::new(callsite_fns::$fn)
1815 .build($scope)
1816 .into(),
1817 v8::PropertyAttribute::DONT_DELETE
1818 | v8::PropertyAttribute::DONT_ENUM
1819 | v8::PropertyAttribute::READ_ONLY,
1820 );
1821 };
1822 }
1823
1824 set_attr!(scope, template, get_this, GET_THIS);
1825 set_attr!(scope, template, get_type_name, GET_TYPE_NAME);
1826 set_attr!(scope, template, get_function, GET_FUNCTION);
1827 set_attr!(scope, template, get_function_name, GET_FUNCTION_NAME);
1828 set_attr!(scope, template, get_method_name, GET_METHOD_NAME);
1829 set_attr!(scope, template, get_file_name, GET_FILE_NAME);
1830 set_attr!(scope, template, get_line_number, GET_LINE_NUMBER);
1831 set_attr!(scope, template, get_column_number, GET_COLUMN_NUMBER);
1832 set_attr!(scope, template, get_eval_origin, GET_EVAL_ORIGIN);
1833 set_attr!(scope, template, is_toplevel, IS_TOPLEVEL);
1834 set_attr!(scope, template, is_eval, IS_EVAL);
1835 set_attr!(scope, template, is_native, IS_NATIVE);
1836 set_attr!(scope, template, is_constructor, IS_CONSTRUCTOR);
1837 set_attr!(scope, template, is_async, IS_ASYNC);
1838 set_attr!(scope, template, is_promise_all, IS_PROMISE_ALL);
1839 set_attr!(scope, template, get_promise_index, GET_PROMISE_INDEX);
1840 set_attr!(
1841 scope,
1842 template,
1843 get_script_name_or_source_url,
1844 GET_SCRIPT_NAME_OR_SOURCE_URL
1845 );
1846 set_attr!(scope, template, to_string, TO_STRING);
1847
1848 template.new_instance(scope).unwrap()
1849}
1850
1851#[inline(always)]
1852fn prepare_stack_trace_inner<'s, 'i, const PATCH_CALLSITES: bool>(
1853 scope: &mut v8::PinScope<'s, 'i>,
1854 error: v8::Local<'s, v8::Value>,
1855 callsites: v8::Local<'s, v8::Array>,
1856) -> v8::Local<'s, v8::Value> {
1857 if let Ok(obj) = error.try_cast::<v8::Object>() {
1860 let key = call_site_evals_key(scope);
1861 obj.set_private(scope, key, callsites.into());
1862 }
1863
1864 let global = scope.get_current_context().global(scope);
1866 let global_error =
1867 get_property(scope, global, ERROR).and_then(|g| g.try_cast().ok());
1868 let prepare_fn = global_error.and_then(|g| {
1869 get_property(scope, g, PREPARE_STACK_TRACE)
1870 .and_then(|f| f.try_cast::<v8::Function>().ok())
1871 });
1872
1873 if let Some(prepare_fn) = prepare_fn {
1876 let callsites = if PATCH_CALLSITES {
1877 let len = callsites.length();
1881 let mut patched = Vec::with_capacity(len as usize);
1882 let template = JsRuntime::state_from(scope)
1883 .callsite_prototype
1884 .borrow()
1885 .clone()
1886 .unwrap();
1887 let prototype = v8::Local::new(scope, template);
1888 for i in 0..len {
1889 let callsite =
1890 callsites.get_index(scope, i).unwrap().cast::<v8::Object>();
1891 patched.push(make_patched_callsite(scope, callsite, prototype).into());
1892 }
1893 v8::Array::new_with_elements(scope, &patched)
1894 } else {
1895 callsites
1896 };
1897
1898 let this = global_error.unwrap().into();
1900 let args = &[error, callsites.into()];
1901 return prepare_fn
1902 .call(scope, this, args)
1903 .unwrap_or_else(|| v8::undefined(scope).into());
1904 }
1905
1906 format_stack_trace(scope, error, callsites)
1908}
1909
1910pub fn prepare_stack_trace_callback_with_original_callsites<'s, 'i>(
1917 scope: &mut v8::PinScope<'s, 'i>,
1918 error: v8::Local<'s, v8::Value>,
1919 callsites: v8::Local<'s, v8::Array>,
1920) -> v8::Local<'s, v8::Value> {
1921 prepare_stack_trace_inner::<false>(scope, error, callsites)
1922}
1923
1924pub fn prepare_stack_trace_callback<'s, 'i>(
1931 scope: &mut v8::PinScope<'s, 'i>,
1932 error: v8::Local<'s, v8::Value>,
1933 callsites: v8::Local<'s, v8::Array>,
1934) -> v8::Local<'s, v8::Value> {
1935 prepare_stack_trace_inner::<true>(scope, error, callsites)
1936}
1937
1938pub struct InitialCwd(pub Arc<Url>);
1939
1940pub fn format_stack_trace<'s, 'i>(
1941 scope: &mut v8::PinScope<'s, 'i>,
1942 error: v8::Local<'s, v8::Value>,
1943 callsites: v8::Local<'s, v8::Array>,
1944) -> v8::Local<'s, v8::Value> {
1945 let state = JsRuntime::state_from(scope);
1946 let maybe_initial_cwd = state
1947 .op_state
1948 .borrow()
1949 .try_borrow::<InitialCwd>()
1950 .map(|i| &i.0)
1951 .cloned();
1952 let mut result = String::new();
1953
1954 if let Ok(obj) = error.try_cast() {
1955 let msg = get_property(scope, obj, v8_static_strings::MESSAGE)
1957 .filter(|v| !v.is_undefined())
1958 .and_then(|v| v.to_string(scope))
1959 .map(|s| v8_to_rust_string(&s, scope))
1960 .unwrap_or_default();
1961 let name = get_property(scope, obj, v8_static_strings::NAME)
1962 .filter(|v| !v.is_undefined())
1963 .and_then(|v| v.to_string(scope))
1964 .map(|s| v8_to_rust_string(&s, scope))
1965 .unwrap_or_else(|| GENERIC_ERROR.to_string());
1966
1967 match (!msg.is_empty(), !name.is_empty()) {
1968 (true, true) => write!(result, "{}: {}", name, msg).unwrap(),
1969 (true, false) => write!(result, "{}", msg).unwrap(),
1970 (false, true) => write!(result, "{}", name).unwrap(),
1971 (false, false) => {}
1972 }
1973 }
1974
1975 for i in 0..callsites.length() {
1977 let callsite = callsites.get_index(scope, i).unwrap().cast::<v8::Object>();
1978 v8::tc_scope!(let tc_scope, scope);
1979
1980 let Some(frame) = JsStackFrame::from_callsite_object(tc_scope, callsite)
1981 else {
1982 let exc = tc_scope
1983 .exception()
1984 .expect("JsStackFrame::from_callsite_object raised an exception");
1985 let message = match exc.to_string(tc_scope) {
1986 Some(s) => v8_to_rust_string(&s, tc_scope),
1987 None => String::new(),
1988 };
1989 #[allow(clippy::print_stderr, reason = "intentional warning output")]
1990 {
1991 eprintln!(
1992 "warning: Failed to create JsStackFrame from callsite object: {message}; Result so far: {result}. This is a bug in deno"
1993 );
1994 }
1995 break;
1996 };
1997 write!(
1998 result,
1999 "\n at {}",
2000 format_frame::<NoAnsiColors>(&frame, maybe_initial_cwd.as_deref())
2001 )
2002 .unwrap();
2003 }
2004
2005 let result = v8::String::new(scope, &result).unwrap();
2006 result.into()
2007}
2008
2009pub struct NoAnsiColors;
2011
2012#[derive(Debug, Clone, Copy)]
2013pub enum ErrorElement {
2015 Anonymous,
2017 NativeFrame,
2019 LineNumber,
2021 ColumnNumber,
2023 FunctionName,
2025 FileName,
2027 WorkingDirPath,
2029 EvalOrigin,
2031 PromiseAll,
2033 PlainText,
2035}
2036
2037pub trait ErrorFormat {
2043 fn fmt_element(
2044 element: ErrorElement,
2045 in_extension_code: bool,
2046 s: &str,
2047 ) -> Cow<'_, str>;
2048}
2049
2050impl ErrorFormat for NoAnsiColors {
2051 fn fmt_element(
2052 _element: ErrorElement,
2053 _in_extension_code: bool,
2054 s: &str,
2055 ) -> Cow<'_, str> {
2056 s.into()
2057 }
2058}
2059
2060pub fn format_location<F: ErrorFormat>(
2061 frame: &JsStackFrame,
2062 maybe_initial_cwd: Option<&Url>,
2063) -> String {
2064 use ErrorElement::*;
2065 let in_extension_code = frame
2066 .file_name
2067 .as_ref()
2068 .map(|f| f.starts_with("ext:") || f.starts_with("node:"))
2069 .unwrap_or(false);
2070 if frame.is_native {
2071 return F::fmt_element(NativeFrame, in_extension_code, "native")
2072 .to_string();
2073 }
2074 let mut result = String::new();
2075 let file_name = frame.file_name.as_deref().unwrap_or("");
2076 if !file_name.is_empty() {
2077 let parts = format_file_name(file_name, maybe_initial_cwd);
2078 if let Some(working_dir_path) = &parts.working_dir_path {
2079 result +=
2080 &F::fmt_element(WorkingDirPath, in_extension_code, working_dir_path);
2081 result += &F::fmt_element(
2082 FileName,
2083 in_extension_code,
2084 parts.file_name.trim_start_matches("./"),
2085 )
2086 } else {
2087 result += &F::fmt_element(FileName, in_extension_code, &parts.file_name)
2088 }
2089 } else {
2090 if frame.is_eval {
2091 let eval_origin = frame.eval_origin.as_ref().unwrap();
2092 let formatted_eval_origin =
2093 format_eval_origin(eval_origin, maybe_initial_cwd);
2094 result +=
2095 &F::fmt_element(EvalOrigin, in_extension_code, &formatted_eval_origin);
2096 result += &F::fmt_element(PlainText, in_extension_code, ", ");
2097 }
2098 result += &F::fmt_element(Anonymous, in_extension_code, "<anonymous>");
2099 }
2100 if frame.is_wasm {
2101 if let Some(line_number) = frame.line_number {
2106 let func_index = line_number - 1;
2107 let wasm_func = format!("wasm-function[{func_index}]");
2108 result += &F::fmt_element(PlainText, in_extension_code, ":");
2109 result += &F::fmt_element(LineNumber, in_extension_code, &wasm_func);
2110 }
2111 if let Some(column_number) = frame.column_number {
2112 let pc_offset = format!("0x{:x}", column_number - 1);
2114 result += &F::fmt_element(PlainText, in_extension_code, ":");
2115 result += &F::fmt_element(ColumnNumber, in_extension_code, &pc_offset);
2116 }
2117 } else if let Some(line_number) = frame.line_number {
2118 result += &F::fmt_element(PlainText, in_extension_code, ":");
2119 result +=
2120 &F::fmt_element(LineNumber, in_extension_code, &line_number.to_string());
2121 if let Some(column_number) = frame.column_number {
2122 result += &F::fmt_element(PlainText, in_extension_code, ":");
2123 result += &F::fmt_element(
2124 ColumnNumber,
2125 in_extension_code,
2126 &column_number.to_string(),
2127 );
2128 }
2129 }
2130 result
2131}
2132
2133pub fn format_frame<F: ErrorFormat>(
2134 frame: &JsStackFrame,
2135 maybe_initial_cwd: Option<&Url>,
2136) -> String {
2137 use ErrorElement::*;
2138 let in_extension_code = frame
2139 .file_name
2140 .as_ref()
2141 .map(|f| f.starts_with("ext:") || f.starts_with("node:"))
2142 .unwrap_or(false);
2143 let is_method_call =
2144 !(frame.is_top_level.unwrap_or_default() || frame.is_constructor);
2145 let mut result = String::new();
2146 if frame.is_async {
2147 result += &F::fmt_element(PlainText, in_extension_code, "async ");
2148 }
2149 if frame.is_promise_all {
2150 result += &F::fmt_element(
2151 PromiseAll,
2152 in_extension_code,
2153 &format!(
2154 "Promise.all (index {})",
2155 frame.promise_index.unwrap_or_default()
2156 ),
2157 );
2158 return result;
2159 }
2160 if is_method_call {
2161 let mut formatted_method = String::new();
2162 if let Some(function_name) = &frame.function_name {
2163 if let Some(type_name) = &frame.type_name
2164 && !function_name.starts_with(type_name)
2165 {
2166 write!(formatted_method, "{type_name}.").unwrap();
2167 }
2168 formatted_method += function_name;
2169 if let Some(method_name) = &frame.method_name
2170 && !function_name.ends_with(method_name)
2171 {
2172 write!(formatted_method, " [as {method_name}]").unwrap();
2173 }
2174 } else {
2175 if let Some(type_name) = &frame.type_name {
2176 write!(formatted_method, "{type_name}.").unwrap();
2177 }
2178 if let Some(method_name) = &frame.method_name {
2179 formatted_method += method_name
2180 } else {
2181 formatted_method += "<anonymous>";
2182 }
2183 }
2184 result +=
2185 F::fmt_element(FunctionName, in_extension_code, &formatted_method)
2186 .as_ref();
2187 } else if frame.is_constructor {
2188 result += &F::fmt_element(PlainText, in_extension_code, "new ");
2189 if let Some(function_name) = &frame.function_name {
2190 write!(
2191 result,
2192 "{}",
2193 F::fmt_element(FunctionName, in_extension_code, function_name)
2194 )
2195 .unwrap();
2196 } else {
2197 result +=
2198 F::fmt_element(Anonymous, in_extension_code, "<anonymous>").as_ref();
2199 }
2200 } else if let Some(function_name) = &frame.function_name {
2201 result +=
2202 F::fmt_element(FunctionName, in_extension_code, function_name).as_ref();
2203 } else {
2204 result += &format_location::<F>(frame, maybe_initial_cwd);
2205 return result;
2206 }
2207 result += &F::fmt_element(PlainText, in_extension_code, " (");
2208 result += &format_location::<F>(frame, maybe_initial_cwd);
2209 result += &F::fmt_element(PlainText, in_extension_code, ")");
2210 result
2211}
2212
2213pub fn throw_error_one_byte_info(
2214 info: &v8::FunctionCallbackInfo,
2215 message: &str,
2216) {
2217 v8::callback_scope!(unsafe scope, info);
2218 throw_error_one_byte(scope, message);
2219}
2220
2221pub fn throw_error_js_error_class<'s, 'i>(
2222 scope: &mut v8::PinCallbackScope<'s, 'i>,
2223 err: &dyn JsErrorClass,
2224) {
2225 let exc = to_v8_error(scope, err);
2226 scope.throw_exception(exc);
2227}
2228
2229pub fn throw_error_one_byte<'s, 'i>(
2230 scope: &mut v8::PinCallbackScope<'s, 'i>,
2231 message: &str,
2232) {
2233 let msg = deno_core::v8::String::new_from_one_byte(
2234 scope,
2235 message.as_bytes(),
2236 deno_core::v8::NewStringType::Normal,
2237 )
2238 .unwrap();
2239 let exc = deno_core::v8::Exception::type_error(scope, msg);
2240 scope.throw_exception(exc);
2241}
2242
2243#[cfg(test)]
2244mod tests {
2245 use super::*;
2246
2247 #[test]
2248 fn test_format_file_name() {
2249 let file_name = format_file_name("data:,Hello%2C%20World%21", None);
2250 assert_eq!(file_name.file_name, "data:,Hello%2C%20World%21");
2251
2252 let too_long_name = "a".repeat(DATA_URL_ABBREV_THRESHOLD + 1);
2253 let file_name = format_file_name(
2254 &format!("data:text/plain;base64,{too_long_name}_%F0%9F%A6%95"),
2255 None,
2256 )
2257 .into_owned();
2258 let expected =
2259 "data:text/plain;base64,aaaaaaaaaaaaaaaaaaaa......aaaaaaa_%F0%9F%A6%95";
2260 assert_eq!(file_name.file_name, expected,);
2261 let file_name = format_file_name(
2262 &format!("data:text/plain;base64,{too_long_name}_%F0%9F%A6%95"),
2263 Some(&Url::parse("file:///foo").unwrap()),
2264 )
2265 .into_owned();
2266 assert_eq!(file_name.file_name, expected);
2267 let file_name = format_file_name("file:///foo/bar.ts", None);
2268 assert_eq!(file_name.file_name, "file:///foo/bar.ts");
2269
2270 let file_name = format_file_name(
2271 "file:///foo/bar.ts",
2272 Some(&Url::parse("file:///foo/").unwrap()),
2273 );
2274 assert_eq!(file_name.file_name, "./bar.ts");
2275
2276 let file_name =
2277 format_file_name("file:///%E6%9D%B1%E4%BA%AC/%F0%9F%A6%95.ts", None);
2278 assert_eq!(file_name.file_name, "file:///東京/🦕.ts");
2279 }
2280
2281 #[test]
2282 fn test_parse_eval_origin() {
2283 let cases = [
2284 (
2285 "eval at <anonymous> (file://path.ts:1:2)",
2286 Some(("eval at <anonymous> (", ("file://path.ts", 1, 2), ")")),
2287 ),
2288 (
2289 "eval at (s:1:2",
2291 None,
2292 ),
2293 (
2294 "at ()", None,
2296 ),
2297 (
2298 "eval at foo (http://website.zzz/my-script).ts:1:2)",
2300 Some((
2301 "eval at foo (",
2302 ("http://website.zzz/my-script).ts", 1, 2),
2303 ")",
2304 )),
2305 ),
2306 (
2307 "eval at foo (eval at bar (file://path.ts:1:2))",
2309 Some(("eval at foo (eval at bar (", ("file://path.ts", 1, 2), "))")),
2310 ),
2311 ];
2312 for (input, expect) in cases {
2313 match expect {
2314 Some((
2315 expect_before,
2316 (expect_file, expect_line, expect_col),
2317 expect_after,
2318 )) => {
2319 let (before, (file_name, line_number, column_number), after) =
2320 parse_eval_origin(input).unwrap();
2321 assert_eq!(before, expect_before);
2322 assert_eq!(file_name, expect_file);
2323 assert_eq!(line_number, expect_line);
2324 assert_eq!(column_number, expect_col);
2325 assert_eq!(after, expect_after);
2326 }
2327 None => {
2328 assert!(parse_eval_origin(input).is_none());
2329 }
2330 }
2331 }
2332 }
2333
2334 #[test]
2335 fn test_relative_specifier_within() {
2336 let from = Url::parse("file:///Users/dev/project/").unwrap();
2338 let to = Url::parse("file:///Users/dev/project/foo.ts").unwrap();
2339 let result = relative_specifier_within(&from, &to);
2340 assert_eq!(result, Some("./foo.ts".to_string()));
2341
2342 let from = Url::parse("file:///Users/dev/project/").unwrap();
2344 let to = Url::parse("file:///Users/dev/project/src/bar.ts").unwrap();
2345 let result = relative_specifier_within(&from, &to);
2346 assert_eq!(result, Some("./src/bar.ts".to_string()));
2347
2348 let from = Url::parse("file:///Users/dev/project/").unwrap();
2350 let to = Url::parse("file:///Users/dev/project/src/lib/utils.ts").unwrap();
2351 let result = relative_specifier_within(&from, &to);
2352 assert_eq!(result, Some("./src/lib/utils.ts".to_string()));
2353
2354 let from = Url::parse("file:///Users/dev/project/src/").unwrap();
2356 let to = Url::parse("file:///Users/dev/project/main.ts").unwrap();
2357 let result = relative_specifier_within(&from, &to);
2358 assert_eq!(result, None);
2359
2360 let from = Url::parse("file:///Users/dev/project/src/lib/").unwrap();
2362 let to = Url::parse("file:///Users/dev/other/file.ts").unwrap();
2363 let result = relative_specifier_within(&from, &to);
2364 assert_eq!(result, None);
2365
2366 let from = Url::parse("file:///Users/dev/workspace/deno/").unwrap();
2368 let to = Url::parse("file:///a.ts").unwrap();
2369 let result = relative_specifier_within(&from, &to);
2370 assert_eq!(result, None);
2371
2372 let from = Url::parse("file:///Users/dev/project/").unwrap();
2374 let to = Url::parse("file:///Users/dev/project/").unwrap();
2375 let result = relative_specifier_within(&from, &to);
2376 assert_eq!(result, Some("./".to_string()));
2377
2378 let from = Url::parse("file:///Users/dev/project1/src/").unwrap();
2380 let to = Url::parse("file:///Users/dev/project2/foo.ts").unwrap();
2381 let result = relative_specifier_within(&from, &to);
2382 assert_eq!(result, None);
2383
2384 let from = Url::parse("http://example.com/app/").unwrap();
2386 let to = Url::parse("http://example.com/app/main.js").unwrap();
2387 let result = relative_specifier_within(&from, &to);
2388 assert_eq!(result, Some("./main.js".to_string()));
2389
2390 let from = Url::parse("http://example.com/app/src/").unwrap();
2392 let to = Url::parse("http://example.com/app/main.js").unwrap();
2393 let result = relative_specifier_within(&from, &to);
2394 assert_eq!(result, None);
2395 }
2396
2397 #[cfg(not(miri))]
2398 #[test]
2399 fn test_to_v8_error_handles_null_builder_exception() {
2400 let mut runtime = JsRuntime::new(Default::default());
2401
2402 deno_core::scope!(scope, runtime);
2403
2404 let throw_null_fn: v8::Local<v8::Function> =
2405 JsRuntime::eval(scope, "(function () { throw null; })").unwrap();
2406
2407 let exception_state = JsRealm::exception_state_from_scope(scope);
2409 exception_state
2410 .js_build_custom_error_cb
2411 .borrow_mut()
2412 .replace(v8::Global::new(scope, throw_null_fn));
2413
2414 let err = CoreErrorKind::TLA;
2415 let expected_message = err.get_message();
2416
2417 let value = to_v8_error(scope, &err);
2418 let value: v8::Local<v8::String> = value
2419 .try_into()
2420 .expect("should fall back to message string");
2421
2422 assert_eq!(value.to_rust_string_lossy(scope), expected_message);
2423 }
2424
2425 #[cfg(not(miri))]
2426 #[test]
2427 fn test_to_v8_error_handles_stack_overflow_in_builder() {
2428 let mut runtime = JsRuntime::new(Default::default());
2429
2430 deno_core::scope!(scope, runtime);
2431
2432 let throw_range_error_fn: v8::Local<v8::Function> = JsRuntime::eval(
2436 scope,
2437 "(function () { throw new RangeError('Maximum call stack size exceeded'); })",
2438 )
2439 .unwrap();
2440
2441 let exception_state = JsRealm::exception_state_from_scope(scope);
2442 exception_state
2443 .js_build_custom_error_cb
2444 .borrow_mut()
2445 .replace(v8::Global::new(scope, throw_range_error_fn));
2446
2447 let err = CoreErrorKind::TLA;
2448 let expected_message = err.get_message();
2449
2450 let value = to_v8_error(scope, &err);
2452 let value: v8::Local<v8::String> = value
2453 .try_into()
2454 .expect("should fall back to message string");
2455
2456 assert_eq!(value.to_rust_string_lossy(scope), expected_message);
2457 }
2458}