1pub use super::modules::ModuleConcreteError;
4use crate::FastStaticString;
5pub use crate::io::ResourceError;
6pub use crate::modules::ModuleLoaderError;
7use crate::runtime::JsRealm;
8use crate::runtime::JsRuntime;
9use crate::runtime::v8_static_strings;
10use crate::source_map::SourceMapApplication;
11use crate::url::Url;
12use deno_error::JsErrorClass;
13use deno_error::builtin_classes::*;
14use std::any::Any;
15use std::borrow::Cow;
16use std::collections::HashSet;
17use std::error::Error;
18use std::fmt;
19use std::fmt::Debug;
20use std::fmt::Display;
21use std::fmt::Formatter;
22use std::fmt::Write as _;
23
24pub type AnyError = anyhow::Error;
27
28deno_error::js_error_wrapper!(v8::DataError, DataError, TYPE_ERROR);
29
30#[derive(Debug, thiserror::Error)]
31pub enum CoreError {
32 #[error("Top-level await is not allowed in synchronous evaluation")]
33 TLA,
34 #[error(transparent)]
35 Js(#[from] JsError),
36 #[error(transparent)]
37 Io(#[from] std::io::Error),
38 #[error(transparent)]
39 ExtensionTranspiler(deno_error::JsErrorBox),
40 #[error("Failed to parse {0}")]
41 Parse(FastStaticString),
42 #[error("Failed to execute {0}")]
43 Execute(FastStaticString),
44 #[error(
45 "Following modules were passed to ExtModuleLoader but never used:\n{}",
46 .0.iter().map(|s| format!(" - {}\n", s)).collect::<Vec<_>>().join("")
47 )]
48 UnusedModules(Vec<String>),
49 #[error(
50 "Following modules were not evaluated; make sure they are imported from other code:\n{}",
51 .0.iter().map(|s| format!(" - {}\n", s)).collect::<Vec<_>>().join("")
52 )]
53 NonEvaluatedModules(Vec<String>),
54 #[error("{0} not present in the module map")]
55 MissingFromModuleMap(String),
56 #[error(transparent)]
57 ModuleLoader(Box<ModuleLoaderError>),
58 #[error("Could not execute {specifier}")]
59 CouldNotExecute {
60 #[source]
61 error: Box<Self>,
62 specifier: String,
63 },
64 #[error(transparent)]
65 JsBox(#[from] deno_error::JsErrorBox),
66 #[error(transparent)]
67 Url(#[from] url::ParseError),
68 #[error(transparent)]
69 FutureCanceled(#[from] futures::channel::oneshot::Canceled),
70 #[error(
71 "Cannot evaluate module, because JavaScript execution has been terminated"
72 )]
73 ExecutionTerminated,
74 #[error(
75 "Promise resolution is still pending but the event loop has already resolved"
76 )]
77 PendingPromiseResolution,
78 #[error(
79 "Cannot evaluate dynamically imported module, because JavaScript execution has been terminated"
80 )]
81 EvaluateDynamicImportedModule,
82 #[error(transparent)]
83 Module(ModuleConcreteError),
84 #[error(transparent)]
85 DataError(DataError),
86 #[error("Unable to get code cache from unbound module script for {0}")]
87 CreateCodeCache(String),
88 #[error(
89 "Extensions from snapshot loaded in wrong order: expected {0} but got {1}"
90 )]
91 ExtensionSnapshotMismatch(&'static str, &'static str),
92 #[error(
93 "Number of lazy-initialized extensions ({0}) does not match number of arguments ({1})"
94 )]
95 ExtensionLazyInitCountMismatch(usize, usize),
96 #[error(
97 "Lazy-initialized extensions loaded in wrong order: expected {0} but got {1}"
98 )]
99 ExtensionLazyInitOrderMismatch(&'static str, &'static str),
100}
101
102impl CoreError {
103 pub fn print_with_cause(&self) -> String {
104 use std::error::Error;
105 let mut err_message = self.to_string();
106
107 if let Some(source) = self.source() {
108 err_message.push_str(&format!(
109 "\n\nCaused by:\n {}",
110 source.to_string().replace("\n", "\n ")
111 ));
112 }
113
114 err_message
115 }
116
117 pub fn to_v8_error(
118 &self,
119 scope: &mut v8::HandleScope,
120 ) -> v8::Global<v8::Value> {
121 let err_string = self.get_message().to_string();
122 let mut error_chain = vec![];
123 let mut intermediary_error: Option<&(dyn Error)> = Some(&self);
124
125 while let Some(err) = intermediary_error {
126 if let Some(source) = err.source() {
127 let source_str = source.to_string();
128 if source_str != err_string {
129 error_chain.push(source_str);
130 }
131
132 intermediary_error = Some(source);
133 } else {
134 intermediary_error = None;
135 }
136 }
137
138 let message = if !error_chain.is_empty() {
139 format!(
140 "{}\n Caused by:\n {}",
141 err_string,
142 error_chain.join("\n ")
143 )
144 } else {
145 err_string
146 };
147
148 let exception =
149 js_class_and_message_to_exception(scope, &self.get_class(), &message);
150 v8::Global::new(scope, exception)
151 }
152}
153
154impl From<v8::DataError> for CoreError {
155 fn from(err: v8::DataError) -> Self {
156 CoreError::DataError(DataError(err))
157 }
158}
159
160impl From<ModuleLoaderError> for CoreError {
161 fn from(err: ModuleLoaderError) -> Self {
162 CoreError::ModuleLoader(Box::new(err))
163 }
164}
165
166impl JsErrorClass for CoreError {
167 fn get_class(&self) -> Cow<'static, str> {
168 match self {
169 CoreError::Js(js_error) => {
170 if let Some(name) = &js_error.name {
171 Cow::Owned(name.clone())
172 } else {
173 Cow::Borrowed(GENERIC_ERROR)
174 }
175 }
176 CoreError::Io(err) => err.get_class(),
177 CoreError::ExtensionTranspiler(err) => err.get_class(),
178 CoreError::ModuleLoader(err) => err.get_class(),
179 CoreError::CouldNotExecute { error, .. } => error.get_class(),
180 CoreError::JsBox(err) => err.get_class(),
181 CoreError::Url(err) => err.get_class(),
182 CoreError::Module(err) => err.get_class(),
183 CoreError::DataError(err) => err.get_class(),
184 CoreError::FutureCanceled(_) => Cow::Borrowed("Interrupted"),
185 CoreError::TLA
186 | CoreError::Parse(_)
187 | CoreError::Execute(_)
188 | CoreError::UnusedModules(_)
189 | CoreError::NonEvaluatedModules(_)
190 | CoreError::MissingFromModuleMap(_)
191 | CoreError::ExecutionTerminated
192 | CoreError::PendingPromiseResolution
193 | CoreError::CreateCodeCache(_)
194 | CoreError::EvaluateDynamicImportedModule
195 | CoreError::ExtensionSnapshotMismatch(..)
196 | CoreError::ExtensionLazyInitCountMismatch(..)
197 | CoreError::ExtensionLazyInitOrderMismatch(..) => {
198 Cow::Borrowed(GENERIC_ERROR)
199 }
200 }
201 }
202
203 fn get_message(&self) -> Cow<'static, str> {
204 match self {
205 CoreError::Js(js_error) => {
206 if let Some(name) = &js_error.message {
207 Cow::Owned(name.clone())
208 } else {
209 Cow::Borrowed("")
210 }
211 }
212 CoreError::Io(err) => err.get_message(),
213 CoreError::ExtensionTranspiler(err) => err.get_message(),
214 CoreError::ModuleLoader(err) => err.get_message(),
215 CoreError::CouldNotExecute { error, .. } => error.get_message(),
216 CoreError::JsBox(err) => err.get_message(),
217 CoreError::Url(err) => err.get_message(),
218 CoreError::Module(err) => err.get_message(),
219 CoreError::DataError(err) => err.get_message(),
220 CoreError::TLA
221 | CoreError::Parse(_)
222 | CoreError::Execute(_)
223 | CoreError::UnusedModules(_)
224 | CoreError::NonEvaluatedModules(_)
225 | CoreError::MissingFromModuleMap(_)
226 | CoreError::FutureCanceled(_)
227 | CoreError::ExecutionTerminated
228 | CoreError::PendingPromiseResolution
229 | CoreError::EvaluateDynamicImportedModule
230 | CoreError::CreateCodeCache(_)
231 | CoreError::ExtensionSnapshotMismatch(..)
232 | CoreError::ExtensionLazyInitCountMismatch(..)
233 | CoreError::ExtensionLazyInitOrderMismatch(..) => {
234 self.to_string().into()
235 }
236 }
237 }
238
239 fn get_additional_properties(
240 &self,
241 ) -> Vec<(Cow<'static, str>, Cow<'static, str>)> {
242 vec![] }
244
245 fn as_any(&self) -> &dyn Any {
246 self
247 }
248}
249
250pub fn throw_js_error_class(
251 scope: &mut v8::HandleScope,
252 error: &dyn JsErrorClass,
253) {
254 let exception = js_class_and_message_to_exception(
255 scope,
256 &error.get_class(),
257 &error.get_message(),
258 );
259 scope.throw_exception(exception);
260}
261
262fn js_class_and_message_to_exception<'s>(
263 scope: &mut v8::HandleScope<'s>,
264 _class: &str,
265 message: &str,
266) -> v8::Local<'s, v8::Value> {
267 let message = v8::String::new(scope, message).unwrap();
268 v8::Exception::type_error(scope, message)
280}
281
282pub fn to_v8_error<'a>(
283 scope: &mut v8::HandleScope<'a>,
284 error: &dyn JsErrorClass,
285) -> v8::Local<'a, v8::Value> {
286 let tc_scope = &mut v8::TryCatch::new(scope);
287 let cb = JsRealm::exception_state_from_scope(tc_scope)
288 .js_build_custom_error_cb
289 .borrow()
290 .clone()
291 .expect("Custom error builder must be set");
292 let cb = cb.open(tc_scope);
293 let this = v8::undefined(tc_scope).into();
294 let class = v8::String::new(tc_scope, &error.get_class()).unwrap();
295 let message = v8::String::new(tc_scope, &error.get_message()).unwrap();
296 let mut args = vec![class.into(), message.into()];
297
298 let additional_properties = error
299 .get_additional_properties()
300 .into_iter()
301 .map(|(key, value)| {
302 let key = v8::String::new(tc_scope, &key).unwrap().into();
303 let value = v8::String::new(tc_scope, &value).unwrap().into();
304
305 v8::Array::new_with_elements(tc_scope, &[key, value]).into()
306 })
307 .collect::<Vec<_>>();
308
309 if !additional_properties.is_empty() {
310 args.push(
311 v8::Array::new_with_elements(tc_scope, &additional_properties).into(),
312 );
313 }
314
315 let maybe_exception = cb.call(tc_scope, this, &args);
316
317 match maybe_exception {
318 Some(exception) => exception,
319 None => {
320 let mut msg =
321 "Custom error class must have a builder registered".to_string();
322 if tc_scope.has_caught() {
323 let e = tc_scope.exception().unwrap();
324 let js_error = JsError::from_v8_exception(tc_scope, e);
325 msg = format!("{}: {}", msg, js_error.exception_message);
326 }
327 panic!("{}", msg);
328 }
329 }
330}
331
332#[inline(always)]
333pub(crate) fn call_site_evals_key<'a>(
334 scope: &mut v8::HandleScope<'a>,
335) -> v8::Local<'a, v8::Private> {
336 let name = v8_static_strings::CALL_SITE_EVALS.v8_string(scope).unwrap();
337 v8::Private::for_api(scope, Some(name))
338}
339
340#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
346#[serde(rename_all = "camelCase")]
347pub struct JsError {
348 pub name: Option<String>,
349 pub message: Option<String>,
350 pub stack: Option<String>,
351 pub cause: Option<Box<JsError>>,
352 pub exception_message: String,
353 pub frames: Vec<JsStackFrame>,
354 pub source_line: Option<String>,
355 pub source_line_frame_index: Option<usize>,
356 pub aggregated: Option<Vec<JsError>>,
357}
358
359#[derive(Debug, Eq, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
360#[serde(rename_all = "camelCase")]
361pub struct JsStackFrame {
362 pub type_name: Option<String>,
363 pub function_name: Option<String>,
364 pub method_name: Option<String>,
365 pub file_name: Option<String>,
366 pub line_number: Option<i64>,
367 pub column_number: Option<i64>,
368 pub eval_origin: Option<String>,
369 #[serde(rename = "isToplevel")]
372 pub is_top_level: Option<bool>,
373 pub is_eval: bool,
374 pub is_native: bool,
375 pub is_constructor: bool,
376 pub is_async: bool,
377 pub is_promise_all: bool,
378 pub promise_index: Option<i64>,
379}
380
381fn apply_source_map<'a>(
383 source_mapper: &mut crate::source_map::SourceMapper,
384 file_name: Cow<'a, str>,
385 line_number: i64,
386 column_number: i64,
387) -> (Cow<'a, str>, i64, i64) {
388 match source_mapper.apply_source_map(
389 &file_name,
390 line_number as u32,
391 column_number as u32,
392 ) {
393 SourceMapApplication::Unchanged => (file_name, line_number, column_number),
394 SourceMapApplication::LineAndColumn {
395 line_number,
396 column_number,
397 } => (file_name, line_number.into(), column_number.into()),
398 SourceMapApplication::LineAndColumnAndFileName {
399 file_name,
400 line_number,
401 column_number,
402 } => (file_name.into(), line_number.into(), column_number.into()),
403 }
404}
405
406fn parse_eval_origin(
420 eval_origin: &str,
421) -> Option<(&str, (&str, i64, i64), &str)> {
422 let eval_at = "eval at ";
430 let mut innermost_start = eval_origin.rfind(eval_at)? + eval_at.len();
433 innermost_start += eval_origin[innermost_start..].find('(')? + 1;
435 if innermost_start >= eval_origin.len() {
436 return None;
438 }
439
440 let mut parts = eval_origin[innermost_start..].rsplitn(3, ':');
445 let column_number_with_rest = parts.next()?;
448 let column_number_end = column_number_with_rest.find(')')?;
449 let column_number = column_number_with_rest[..column_number_end]
450 .parse::<i64>()
451 .ok()?;
452 let line_number = parts.next()?.parse::<i64>().ok()?;
453 let file_name = parts.next()?;
454 let column_start = eval_origin.rfind(':')? + 1;
456 let innermost_end = column_start + column_number_end;
458 Some((
459 &eval_origin[..innermost_start],
460 (file_name, line_number, column_number),
461 &eval_origin[innermost_end..],
462 ))
463}
464
465impl JsStackFrame {
466 pub fn from_location(
467 file_name: Option<String>,
468 line_number: Option<i64>,
469 column_number: Option<i64>,
470 ) -> Self {
471 Self {
472 type_name: None,
473 function_name: None,
474 method_name: None,
475 file_name,
476 line_number,
477 column_number,
478 eval_origin: None,
479 is_top_level: None,
480 is_eval: false,
481 is_native: false,
482 is_constructor: false,
483 is_async: false,
484 is_promise_all: false,
485 promise_index: None,
486 }
487 }
488
489 fn from_callsite_object<'s>(
492 scope: &mut v8::HandleScope<'s>,
493 callsite: v8::Local<'s, v8::Object>,
494 ) -> Option<Self> {
495 macro_rules! call {
496 ($key: ident : $t: ty) => {{
497 let res = call_method(scope, callsite, $key, &[])?;
498 let res: $t = match serde_v8::from_v8(scope, res) {
499 Ok(res) => res,
500 Err(err) => {
501 let message = format!(
502 "Failed to deserialize return value from callsite property '{}' to correct type: {err:?}.",
503 $key
504 );
505 let message = v8::String::new(scope, &message).unwrap();
506 let exception = v8::Exception::type_error(scope, message);
507 scope.throw_exception(exception);
508 return None;
509 }
510 };
511 res
512 }};
513 ($key: ident) => { call!($key : _) };
514 }
515
516 let state = JsRuntime::state_from(scope);
517 let mut source_mapper = state.source_mapper.borrow_mut();
518 let (file_name, line_number, column_number) = match (
520 call!(GET_FILE_NAME : Option<String>),
521 call!(GET_LINE_NUMBER),
522 call!(GET_COLUMN_NUMBER),
523 ) {
524 (Some(f), Some(l), Some(c)) => {
525 let (file_name, line_num, col_num) =
526 apply_source_map(&mut source_mapper, f.into(), l, c);
527 (Some(file_name.into_owned()), Some(line_num), Some(col_num))
528 }
529 (f, l, c) => (f, l, c),
530 };
531
532 let eval_origin = call!(GET_EVAL_ORIGIN: Option<String>).and_then(|o| {
534 let Some((before, (file, line, col), after)) = parse_eval_origin(&o)
535 else {
536 return Some(o);
537 };
538 let (file, line, col) =
539 apply_source_map(&mut source_mapper, file.into(), line, col);
540 Some(format!("{before}{file}:{line}:{col}{after}"))
541 });
542
543 Some(Self {
544 file_name,
545 line_number,
546 column_number,
547 eval_origin,
548 type_name: call!(GET_TYPE_NAME),
549 function_name: call!(GET_FUNCTION_NAME),
550 method_name: call!(GET_METHOD_NAME),
551 is_top_level: call!(IS_TOPLEVEL),
552 is_eval: call!(IS_EVAL),
553 is_native: call!(IS_NATIVE),
554 is_constructor: call!(IS_CONSTRUCTOR),
555 is_async: call!(IS_ASYNC),
556 is_promise_all: call!(IS_PROMISE_ALL),
557 promise_index: call!(GET_PROMISE_INDEX),
558 })
559 }
560
561 pub fn from_v8_message<'a>(
565 scope: &'a mut v8::HandleScope,
566 message: v8::Local<'a, v8::Message>,
567 ) -> Option<Self> {
568 let f = message.get_script_resource_name(scope)?;
569 let f: v8::Local<v8::String> = f.try_into().ok()?;
570 let f = f.to_rust_string_lossy(scope);
571 let l = message.get_line_number(scope)? as i64;
572 let c = message.get_start_column() as i64 + 1;
574 let state = JsRuntime::state_from(scope);
575 let mut source_mapper = state.source_mapper.borrow_mut();
576 let (file_name, line_num, col_num) =
577 apply_source_map(&mut source_mapper, f.into(), l, c);
578 Some(JsStackFrame::from_location(
579 Some(file_name.into_owned()),
580 Some(line_num),
581 Some(col_num),
582 ))
583 }
584
585 pub fn maybe_format_location(&self) -> Option<String> {
586 Some(format!(
587 "{}:{}:{}",
588 self.file_name.as_ref()?,
589 self.line_number?,
590 self.column_number?
591 ))
592 }
593}
594
595#[inline(always)]
596fn get_property<'a>(
597 scope: &mut v8::HandleScope<'a>,
598 object: v8::Local<v8::Object>,
599 key: FastStaticString,
600) -> Option<v8::Local<'a, v8::Value>> {
601 let key = key.v8_string(scope).unwrap();
602 object.get(scope, key.into())
603}
604
605fn call_method<'a, T>(
606 scope: &mut v8::HandleScope<'a>,
607 object: v8::Local<v8::Object>,
608 key: FastStaticString,
609 args: &[v8::Local<'a, v8::Value>],
610) -> Option<v8::Local<'a, T>>
611where
612 v8::Local<'a, T>: TryFrom<v8::Local<'a, v8::Value>, Error: Debug>,
613{
614 let func = match get_property(scope, object, key)?.try_cast::<v8::Function>()
615 {
616 Ok(func) => func,
617 Err(err) => {
618 let message =
619 format!("Callsite property '{key}' is not a function: {err}");
620 let message = v8::String::new(scope, &message).unwrap();
621 let exception = v8::Exception::type_error(scope, message);
622 scope.throw_exception(exception);
623 return None;
624 }
625 };
626
627 let res = func.call(scope, object.into(), args)?;
628
629 let result = match v8::Local::try_from(res) {
630 Ok(result) => result,
631 Err(err) => {
632 let message = format!(
633 "Failed to cast callsite method '{key}' return value to correct value: {err:?}."
634 );
635 let message = v8::String::new(scope, &message).unwrap();
636 let exception = v8::Exception::type_error(scope, message);
637 scope.throw_exception(exception);
638 return None;
639 }
640 };
641
642 Some(result)
643}
644
645#[derive(Debug, Default, serde::Deserialize)]
646pub(crate) struct NativeJsError {
647 pub name: Option<String>,
648 pub message: Option<String>,
649 }
652
653impl JsError {
654 pub fn is_same_error(&self, other: &JsError) -> bool {
660 let a = self;
661 let b = other;
662 a.name == b.name
665 && a.message == b.message
666 && a.stack == b.stack
667 && (a.exception_message == b.exception_message
670 || a.exception_message.replace(" (in promise) ", " ") == b.exception_message.replace(" (in promise) ", " "))
671 && a.frames == b.frames
672 && a.source_line == b.source_line
673 && a.source_line_frame_index == b.source_line_frame_index
674 && a.aggregated == b.aggregated
675 }
676
677 pub fn from_v8_exception(
678 scope: &mut v8::HandleScope,
679 exception: v8::Local<v8::Value>,
680 ) -> Self {
681 Self::inner_from_v8_exception(scope, exception, Default::default())
682 }
683
684 pub fn from_v8_message<'a>(
685 scope: &'a mut v8::HandleScope,
686 msg: v8::Local<'a, v8::Message>,
687 ) -> Self {
688 let scope = &mut v8::HandleScope::new(scope);
691
692 let exception_message = msg.get(scope).to_rust_string_lossy(scope);
693
694 let mut frames: Vec<JsStackFrame> = vec![];
696 let mut source_line = None;
697 let mut source_line_frame_index = None;
698
699 if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
700 frames = vec![stack_frame];
701 }
702 {
703 let state = JsRuntime::state_from(scope);
704 let mut source_mapper = state.source_mapper.borrow_mut();
705 for (i, frame) in frames.iter().enumerate() {
706 if let (Some(file_name), Some(line_number)) =
707 (&frame.file_name, frame.line_number)
708 {
709 if !file_name.trim_start_matches('[').starts_with("ext:") {
710 source_line = source_mapper.get_source_line(file_name, line_number);
711 source_line_frame_index = Some(i);
712 break;
713 }
714 }
715 }
716 }
717
718 Self {
719 name: None,
720 message: None,
721 exception_message,
722 cause: None,
723 source_line,
724 source_line_frame_index,
725 frames,
726 stack: None,
727 aggregated: None,
728 }
729 }
730
731 fn inner_from_v8_exception<'a>(
732 scope: &'a mut v8::HandleScope,
733 exception: v8::Local<'a, v8::Value>,
734 mut seen: HashSet<v8::Local<'a, v8::Object>>,
735 ) -> Self {
736 let scope = &mut v8::HandleScope::new(scope);
739
740 let msg = v8::Exception::create_message(scope, exception);
741
742 let mut exception_message = None;
743 let exception_state = JsRealm::exception_state_from_scope(scope);
744
745 let js_format_exception_cb =
746 exception_state.js_format_exception_cb.borrow().clone();
747 if let Some(format_exception_cb) = js_format_exception_cb {
748 let format_exception_cb = format_exception_cb.open(scope);
749 let this = v8::undefined(scope).into();
750 let formatted = format_exception_cb.call(scope, this, &[exception]);
751 if let Some(formatted) = formatted {
752 if formatted.is_string() {
753 exception_message = Some(formatted.to_rust_string_lossy(scope));
754 }
755 }
756 }
757
758 if is_instance_of_error(scope, exception) {
759 let v8_exception = exception;
760 let exception: v8::Local<v8::Object> = exception.try_into().unwrap();
762 let cause = get_property(scope, exception, v8_static_strings::CAUSE);
763 let e: NativeJsError =
764 serde_v8::from_v8(scope, exception.into()).unwrap_or_default();
765 let name = e.name.clone().unwrap_or_else(|| GENERIC_ERROR.to_string());
767 let message_prop = e.message.clone().unwrap_or_default();
768 let exception_message = exception_message.unwrap_or_else(|| {
769 if !name.is_empty() && !message_prop.is_empty() {
770 format!("Uncaught {name}: {message_prop}")
771 } else if !name.is_empty() {
772 format!("Uncaught {name}")
773 } else if !message_prop.is_empty() {
774 format!("Uncaught {message_prop}")
775 } else {
776 "Uncaught".to_string()
777 }
778 });
779 let cause = cause.and_then(|cause| {
780 if cause.is_undefined() || seen.contains(&exception) {
781 None
782 } else {
783 seen.insert(exception);
784 Some(Box::new(JsError::inner_from_v8_exception(
785 scope, cause, seen,
786 )))
787 }
788 });
789
790 let stack = get_property(scope, exception, v8_static_strings::STACK);
793 let stack: Option<v8::Local<v8::String>> =
794 stack.and_then(|s| s.try_into().ok());
795 let stack = stack.map(|s| s.to_rust_string_lossy(scope));
796
797 let frames_v8 = {
799 let key = call_site_evals_key(scope);
800 exception.get_private(scope, key)
801 };
802 let frames_v8: Option<v8::Local<v8::Array>> =
804 frames_v8.and_then(|a| a.try_into().ok());
805
806 let mut frames: Vec<JsStackFrame> = match frames_v8 {
808 Some(frames_v8) => {
809 let mut buf = Vec::with_capacity(frames_v8.length() as usize);
810 for i in 0..frames_v8.length() {
811 let callsite = frames_v8.get_index(scope, i).unwrap().cast();
812 let tc_scope = &mut v8::TryCatch::new(scope);
813 let Some(stack_frame) =
814 JsStackFrame::from_callsite_object(tc_scope, callsite)
815 else {
816 let message = tc_scope
817 .exception()
818 .expect(
819 "JsStackFrame::from_callsite_object raised an exception",
820 )
821 .to_rust_string_lossy(tc_scope);
822 #[allow(clippy::print_stderr)]
823 {
824 eprintln!(
825 "warning: Failed to create JsStackFrame from callsite object: {message}. This is a bug in deno"
826 );
827 }
828 break;
829 };
830 buf.push(stack_frame);
831 }
832 buf
833 }
834 None => vec![],
835 };
836 let mut source_line = None;
837 let mut source_line_frame_index = None;
838
839 if frames.is_empty() {
844 if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
845 frames = vec![stack_frame];
846 }
847 }
848 {
849 let state = JsRuntime::state_from(scope);
850 let mut source_mapper = state.source_mapper.borrow_mut();
851 for (i, frame) in frames.iter().enumerate() {
852 if let (Some(file_name), Some(line_number)) =
853 (&frame.file_name, frame.line_number)
854 {
855 if !file_name.trim_start_matches('[').starts_with("ext:") {
856 source_line =
857 source_mapper.get_source_line(file_name, line_number);
858 source_line_frame_index = Some(i);
859 break;
860 }
861 }
862 }
863 }
864
865 let mut aggregated: Option<Vec<JsError>> = None;
866 if is_aggregate_error(scope, v8_exception) {
867 let aggregated_errors =
869 get_property(scope, exception, v8_static_strings::ERRORS);
870 let aggregated_errors: Option<v8::Local<v8::Array>> =
871 aggregated_errors.and_then(|a| a.try_into().ok());
872
873 if let Some(errors) = aggregated_errors {
874 if errors.length() > 0 {
875 let mut agg = vec![];
876 for i in 0..errors.length() {
877 let error = errors.get_index(scope, i).unwrap();
878 let js_error = Self::from_v8_exception(scope, error);
879 agg.push(js_error);
880 }
881 aggregated = Some(agg);
882 }
883 }
884 };
885
886 Self {
887 name: e.name,
888 message: e.message,
889 exception_message,
890 cause,
891 source_line,
892 source_line_frame_index,
893 frames,
894 stack,
895 aggregated,
896 }
897 } else {
898 let exception_message = exception_message
899 .unwrap_or_else(|| msg.get(scope).to_rust_string_lossy(scope));
900 Self {
904 name: None,
905 message: None,
906 exception_message,
907 cause: None,
908 source_line: None,
909 source_line_frame_index: None,
910 frames: vec![],
911 stack: None,
912 aggregated: None,
913 }
914 }
915 }
916}
917
918impl std::error::Error for JsError {}
919
920impl Display for JsError {
921 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
922 if let Some(stack) = &self.stack {
923 let stack_lines = stack.lines();
924 if stack_lines.count() > 1 {
925 return write!(f, "{stack}");
926 }
927 }
928 write!(f, "{}", self.exception_message)?;
929 let location = self.frames.first().and_then(|f| f.maybe_format_location());
930 if let Some(location) = location {
931 write!(f, "\n at {location}")?;
932 }
933 Ok(())
934 }
935}
936
937pub(crate) fn is_instance_of_error(
943 scope: &mut v8::HandleScope,
944 value: v8::Local<v8::Value>,
945) -> bool {
946 if !value.is_object() {
947 return false;
948 }
949 let message = v8::String::empty(scope);
950 let error_prototype = v8::Exception::error(scope, message)
951 .to_object(scope)
952 .unwrap()
953 .get_prototype(scope)
954 .unwrap();
955 let mut maybe_prototype =
956 value.to_object(scope).unwrap().get_prototype(scope);
957 while let Some(prototype) = maybe_prototype {
958 if !prototype.is_object() {
959 return false;
960 }
961 if prototype.strict_equals(error_prototype) {
962 return true;
963 }
964 maybe_prototype = prototype
965 .to_object(scope)
966 .and_then(|o| o.get_prototype(scope));
967 }
968 false
969}
970
971pub(crate) fn is_aggregate_error(
978 scope: &mut v8::HandleScope,
979 value: v8::Local<v8::Value>,
980) -> bool {
981 let mut maybe_prototype = Some(value);
982 while let Some(prototype) = maybe_prototype {
983 if !prototype.is_object() {
984 return false;
985 }
986
987 let prototype = prototype.to_object(scope).unwrap();
988 let prototype_name =
989 match get_property(scope, prototype, v8_static_strings::CONSTRUCTOR) {
990 Some(constructor) => {
991 let ctor = constructor.to_object(scope).unwrap();
992 get_property(scope, ctor, v8_static_strings::NAME)
993 .map(|v| v.to_rust_string_lossy(scope))
994 }
995 None => return false,
996 };
997
998 if prototype_name == Some(String::from("AggregateError")) {
999 return true;
1000 }
1001
1002 maybe_prototype = prototype.get_prototype(scope);
1003 }
1004
1005 false
1006}
1007
1008pub(crate) fn has_call_site(
1011 scope: &mut v8::HandleScope,
1012 exception: v8::Local<v8::Value>,
1013) -> bool {
1014 if !exception.is_object() {
1015 return false;
1016 }
1017 let exception = exception.to_object(scope).unwrap();
1018 get_property(scope, exception, v8_static_strings::STACK);
1021 let frames_v8 = {
1022 let key = call_site_evals_key(scope);
1023 exception.get_private(scope, key)
1024 };
1025 let frames_v8: Option<v8::Local<v8::Array>> =
1026 frames_v8.and_then(|a| a.try_into().ok());
1027 if let Some(frames_v8) = frames_v8 {
1028 if frames_v8.length() > 0 {
1029 return true;
1030 }
1031 }
1032 false
1033}
1034
1035const DATA_URL_ABBREV_THRESHOLD: usize = 150;
1036
1037pub fn format_file_name(file_name: &str) -> String {
1038 abbrev_file_name(file_name).unwrap_or_else(|| {
1039 match percent_encoding::percent_decode_str(file_name).decode_utf8() {
1041 Ok(s) => s.to_string(),
1042 Err(_) => file_name.to_string(),
1044 }
1045 })
1046}
1047
1048fn abbrev_file_name(file_name: &str) -> Option<String> {
1049 if !file_name.starts_with("data:") {
1050 return None;
1051 }
1052 if file_name.len() <= DATA_URL_ABBREV_THRESHOLD {
1053 return Some(file_name.to_string());
1054 }
1055 let url = Url::parse(file_name).ok()?;
1056 let (head, tail) = url.path().split_once(',')?;
1057 let len = tail.len();
1058 let start = tail.get(0..20)?;
1059 let end = tail.get(len - 20..)?;
1060 Some(format!("{}:{},{}......{}", url.scheme(), head, start, end))
1061}
1062
1063pub(crate) fn exception_to_err_result<T>(
1064 scope: &mut v8::HandleScope,
1065 exception: v8::Local<v8::Value>,
1066 mut in_promise: bool,
1067 clear_error: bool,
1068) -> Result<T, CoreError> {
1069 let state = JsRealm::exception_state_from_scope(scope);
1070
1071 let mut was_terminating_execution = scope.is_execution_terminating();
1072
1073 scope.set_microtasks_policy(v8::MicrotasksPolicy::Explicit);
1077 scope.cancel_terminate_execution();
1082 let exception = match state.get_dispatched_exception_as_local(scope) {
1083 Some(dispatched_exception) => {
1084 in_promise = state.is_dispatched_exception_promise();
1088 if clear_error {
1089 state.clear_error();
1090 was_terminating_execution = false;
1091 }
1092 dispatched_exception
1093 }
1094 _ => {
1095 if was_terminating_execution && exception.is_null_or_undefined() {
1096 let message = v8::String::new(scope, "execution terminated").unwrap();
1098 v8::Exception::error(scope, message)
1099 } else {
1100 exception
1102 }
1103 }
1104 };
1105
1106 let mut js_error = JsError::from_v8_exception(scope, exception);
1107 if in_promise {
1108 js_error.exception_message = format!(
1109 "Uncaught (in promise) {}",
1110 js_error.exception_message.trim_start_matches("Uncaught ")
1111 );
1112 }
1113
1114 if was_terminating_execution {
1115 scope.terminate_execution();
1117 }
1118 scope.set_microtasks_policy(v8::MicrotasksPolicy::Auto);
1119
1120 Err(CoreError::Js(js_error))
1121}
1122
1123v8_static_strings::v8_static_strings! {
1124 ERROR = "Error",
1125 GET_FILE_NAME = "getFileName",
1126 GET_SCRIPT_NAME_OR_SOURCE_URL = "getScriptNameOrSourceURL",
1127 GET_THIS = "getThis",
1128 GET_TYPE_NAME = "getTypeName",
1129 GET_FUNCTION = "getFunction",
1130 GET_FUNCTION_NAME = "getFunctionName",
1131 GET_METHOD_NAME = "getMethodName",
1132 GET_LINE_NUMBER = "getLineNumber",
1133 GET_COLUMN_NUMBER = "getColumnNumber",
1134 GET_EVAL_ORIGIN = "getEvalOrigin",
1135 IS_TOPLEVEL = "isToplevel",
1136 IS_EVAL = "isEval",
1137 IS_NATIVE = "isNative",
1138 IS_CONSTRUCTOR = "isConstructor",
1139 IS_ASYNC = "isAsync",
1140 IS_PROMISE_ALL = "isPromiseAll",
1141 GET_PROMISE_INDEX = "getPromiseIndex",
1142 TO_STRING = "toString",
1143 PREPARE_STACK_TRACE = "prepareStackTrace",
1144 ORIGINAL = "deno_core::original_call_site",
1145 SOURCE_MAPPED_INFO = "deno_core::source_mapped_call_site_info",
1146 ERROR_RECEIVER_IS_NOT_VALID_CALLSITE_OBJECT = "The receiver is not a valid callsite object.",
1147}
1148
1149#[inline(always)]
1150pub(crate) fn original_call_site_key<'a>(
1151 scope: &mut v8::HandleScope<'a>,
1152) -> v8::Local<'a, v8::Private> {
1153 let name = ORIGINAL.v8_string(scope).unwrap();
1154 v8::Private::for_api(scope, Some(name))
1155}
1156
1157pub(crate) fn source_mapped_info_key<'a>(
1158 scope: &mut v8::HandleScope<'a>,
1159) -> v8::Local<'a, v8::Private> {
1160 let name = SOURCE_MAPPED_INFO.v8_string(scope).unwrap();
1161 v8::Private::for_api(scope, Some(name))
1162}
1163
1164fn make_patched_callsite<'s>(
1165 scope: &mut v8::HandleScope<'s>,
1166 callsite: v8::Local<'s, v8::Object>,
1167 prototype: v8::Local<'s, v8::Object>,
1168) -> v8::Local<'s, v8::Object> {
1169 let out_obj = v8::Object::with_prototype_and_properties(
1170 scope,
1171 prototype.into(),
1172 &[],
1173 &[],
1174 );
1175 let orig_key = original_call_site_key(scope);
1176 out_obj.set_private(scope, orig_key, callsite.into());
1177 out_obj
1178}
1179
1180fn original_call_site<'a>(
1181 scope: &mut v8::HandleScope<'a>,
1182 this: v8::Local<'_, v8::Object>,
1183) -> Option<v8::Local<'a, v8::Object>> {
1184 let orig_key = original_call_site_key(scope);
1185 let Some(orig) = this
1186 .get_private(scope, orig_key)
1187 .and_then(|v| v8::Local::<v8::Object>::try_from(v).ok())
1188 else {
1189 let message = ERROR_RECEIVER_IS_NOT_VALID_CALLSITE_OBJECT
1190 .v8_string(scope)
1191 .unwrap();
1192 let exception = v8::Exception::type_error(scope, message);
1193 scope.throw_exception(exception);
1194 return None;
1195 };
1196 Some(orig)
1197}
1198
1199macro_rules! make_callsite_fn {
1200 ($fn:ident, $field:ident) => {
1201 pub fn $fn(
1202 scope: &mut v8::HandleScope<'_>,
1203 args: v8::FunctionCallbackArguments<'_>,
1204 mut rv: v8::ReturnValue<'_>,
1205 ) {
1206 let Some(orig) = original_call_site(scope, args.this()) else {
1207 return;
1208 };
1209 let key = $field.v8_string(scope).unwrap().into();
1210 let orig_ret = orig
1211 .cast::<v8::Object>()
1212 .get(scope, key)
1213 .unwrap()
1214 .cast::<v8::Function>()
1215 .call(scope, orig.into(), &[]);
1216 rv.set(orig_ret.unwrap_or_else(|| v8::undefined(scope).into()));
1217 }
1218 };
1219}
1220
1221fn maybe_to_path_str(string: &str) -> Option<String> {
1222 if string.starts_with("file://") {
1223 Some(
1224 Url::parse(string)
1225 .unwrap()
1226 .to_file_path()
1227 .unwrap()
1228 .to_string_lossy()
1229 .into_owned(),
1230 )
1231 } else {
1232 None
1233 }
1234}
1235
1236pub mod callsite_fns {
1237 use capacity_builder::StringBuilder;
1238
1239 use crate::FromV8;
1240 use crate::ToV8;
1241 use crate::convert;
1242
1243 use super::*;
1244
1245 enum SourceMappedCallsiteInfo<'a> {
1246 Ref(v8::Local<'a, v8::Array>),
1247 Value {
1248 file_name: v8::Local<'a, v8::Value>,
1249 line_number: v8::Local<'a, v8::Value>,
1250 column_number: v8::Local<'a, v8::Value>,
1251 },
1252 }
1253 impl<'a> SourceMappedCallsiteInfo<'a> {
1254 #[inline]
1255 fn file_name(
1256 &self,
1257 scope: &mut v8::HandleScope<'a>,
1258 ) -> v8::Local<'a, v8::Value> {
1259 match self {
1260 Self::Ref(array) => array.get_index(scope, 0).unwrap(),
1261 Self::Value { file_name, .. } => *file_name,
1262 }
1263 }
1264 #[inline]
1265 fn line_number(
1266 &self,
1267 scope: &mut v8::HandleScope<'a>,
1268 ) -> v8::Local<'a, v8::Value> {
1269 match self {
1270 Self::Ref(array) => array.get_index(scope, 1).unwrap(),
1271 Self::Value { line_number, .. } => *line_number,
1272 }
1273 }
1274 #[inline]
1275 fn column_number(
1276 &self,
1277 scope: &mut v8::HandleScope<'a>,
1278 ) -> v8::Local<'a, v8::Value> {
1279 match self {
1280 Self::Ref(array) => array.get_index(scope, 2).unwrap(),
1281 Self::Value { column_number, .. } => *column_number,
1282 }
1283 }
1284 }
1285
1286 type MaybeValue<'a> = Option<v8::Local<'a, v8::Value>>;
1287
1288 fn maybe_apply_source_map<'a>(
1289 scope: &mut v8::HandleScope<'a>,
1290 file_name: MaybeValue<'a>,
1291 line_number: MaybeValue<'a>,
1292 column_number: MaybeValue<'a>,
1293 ) -> Option<(String, i64, i64)> {
1294 let file_name = serde_v8::to_utf8(file_name?.try_cast().ok()?, scope);
1295 let convert::Number(line_number) =
1296 FromV8::from_v8(scope, line_number?).ok()?;
1297 let convert::Number(column_number) =
1298 FromV8::from_v8(scope, column_number?).ok()?;
1299
1300 let state = JsRuntime::state_from(scope);
1301 let mut source_mapper = state.source_mapper.borrow_mut();
1302 let (mapped_file_name, mapped_line_number, mapped_column_number) =
1303 apply_source_map(
1304 &mut source_mapper,
1305 Cow::Owned(file_name),
1306 line_number,
1307 column_number,
1308 );
1309 Some((
1310 mapped_file_name.into_owned(),
1311 mapped_line_number,
1312 mapped_column_number,
1313 ))
1314 }
1315 fn source_mapped_call_site_info<'a>(
1316 scope: &mut v8::HandleScope<'a>,
1317 callsite: v8::Local<'a, v8::Object>,
1318 ) -> Option<SourceMappedCallsiteInfo<'a>> {
1319 let key = source_mapped_info_key(scope);
1320 if let Some(info) = callsite.get_private(scope, key) {
1322 if let Ok(array) = info.try_cast::<v8::Array>() {
1323 return Some(SourceMappedCallsiteInfo::Ref(array));
1324 }
1325 }
1326 let orig_callsite = original_call_site(scope, callsite)?;
1327
1328 let file_name =
1329 call_method::<v8::Value>(scope, orig_callsite, super::GET_FILE_NAME, &[]);
1330 let line_number = call_method::<v8::Value>(
1331 scope,
1332 orig_callsite,
1333 super::GET_LINE_NUMBER,
1334 &[],
1335 );
1336 let column_number = call_method::<v8::Value>(
1337 scope,
1338 orig_callsite,
1339 super::GET_COLUMN_NUMBER,
1340 &[],
1341 );
1342
1343 let info = v8::Array::new(scope, 3);
1344
1345 if let Some((mapped_file_name, mapped_line_number, mapped_column_number)) =
1347 maybe_apply_source_map(scope, file_name, line_number, column_number)
1348 {
1349 let mapped_file_name_trimmed =
1350 maybe_to_path_str(&mapped_file_name).unwrap_or(mapped_file_name);
1351 let mapped_file_name = crate::FastString::from(mapped_file_name_trimmed)
1352 .v8_string(scope)
1353 .unwrap();
1354 let Ok(mapped_line_number) =
1355 convert::Number(mapped_line_number).to_v8(scope);
1356 let Ok(mapped_column_number) =
1357 convert::Number(mapped_column_number).to_v8(scope);
1358 info.set_index(scope, 0, mapped_file_name.into());
1359 info.set_index(scope, 1, mapped_line_number);
1360 info.set_index(scope, 2, mapped_column_number);
1361 callsite.set_private(scope, key, info.into());
1362 Some(SourceMappedCallsiteInfo::Value {
1363 file_name: mapped_file_name.into(),
1364 line_number: mapped_line_number,
1365 column_number: mapped_column_number,
1366 })
1367 } else {
1368 let file_name = file_name.unwrap_or_else(|| v8::undefined(scope).into());
1369 let line_number =
1370 line_number.unwrap_or_else(|| v8::undefined(scope).into());
1371 let column_number =
1372 column_number.unwrap_or_else(|| v8::undefined(scope).into());
1373 info.set_index(scope, 0, file_name);
1374 info.set_index(scope, 1, line_number);
1375 info.set_index(scope, 2, column_number);
1376 callsite.set_private(scope, key, info.into());
1377 Some(SourceMappedCallsiteInfo::Ref(info))
1378 }
1379 }
1380
1381 make_callsite_fn!(get_this, GET_THIS);
1382 make_callsite_fn!(get_type_name, GET_TYPE_NAME);
1383 make_callsite_fn!(get_function, GET_FUNCTION);
1384 make_callsite_fn!(get_function_name, GET_FUNCTION_NAME);
1385 make_callsite_fn!(get_method_name, GET_METHOD_NAME);
1386
1387 pub fn get_file_name<'a>(
1388 scope: &mut v8::HandleScope<'a>,
1389 args: v8::FunctionCallbackArguments<'a>,
1390 mut rv: v8::ReturnValue<'_>,
1391 ) {
1392 if let Some(info) = source_mapped_call_site_info(scope, args.this()) {
1393 rv.set(info.file_name(scope));
1394 }
1395 }
1396
1397 pub fn get_line_number<'a>(
1398 scope: &mut v8::HandleScope<'a>,
1399 args: v8::FunctionCallbackArguments<'a>,
1400 mut rv: v8::ReturnValue<'_>,
1401 ) {
1402 if let Some(info) = source_mapped_call_site_info(scope, args.this()) {
1403 rv.set(info.line_number(scope));
1404 }
1405 }
1406
1407 pub fn get_column_number<'a>(
1408 scope: &mut v8::HandleScope<'a>,
1409 args: v8::FunctionCallbackArguments<'a>,
1410 mut rv: v8::ReturnValue<'_>,
1411 ) {
1412 if let Some(info) = source_mapped_call_site_info(scope, args.this()) {
1413 rv.set(info.column_number(scope));
1414 }
1415 }
1416
1417 make_callsite_fn!(get_eval_origin, GET_EVAL_ORIGIN);
1418 make_callsite_fn!(is_toplevel, IS_TOPLEVEL);
1419 make_callsite_fn!(is_eval, IS_EVAL);
1420 make_callsite_fn!(is_native, IS_NATIVE);
1421 make_callsite_fn!(is_constructor, IS_CONSTRUCTOR);
1422 make_callsite_fn!(is_async, IS_ASYNC);
1423 make_callsite_fn!(is_promise_all, IS_PROMISE_ALL);
1424 make_callsite_fn!(get_promise_index, GET_PROMISE_INDEX);
1425 make_callsite_fn!(
1426 get_script_name_or_source_url,
1427 GET_SCRIPT_NAME_OR_SOURCE_URL
1428 );
1429
1430 fn to_string_inner<'e>(
1432 scope: &mut v8::HandleScope<'e>,
1433 this: v8::Local<'e, v8::Object>,
1434 orig: v8::Local<'e, v8::Object>,
1435 orig_to_string_v8: v8::Local<'e, v8::String>,
1436 ) -> Option<v8::Local<'e, v8::String>> {
1437 let orig_to_string = serde_v8::to_utf8(orig_to_string_v8, scope);
1438 let orig_file_name =
1440 call_method::<v8::Value>(scope, orig, GET_FILE_NAME, &[])
1441 .and_then(|v| v.try_cast::<v8::String>().ok())?;
1442 let orig_line_number =
1443 call_method::<v8::Value>(scope, orig, GET_LINE_NUMBER, &[])
1444 .and_then(|v| v.try_cast::<v8::Number>().ok())?;
1445 let orig_column_number =
1446 call_method::<v8::Value>(scope, orig, GET_COLUMN_NUMBER, &[])
1447 .and_then(|v| v.try_cast::<v8::Number>().ok())?;
1448 let orig_file_name = serde_v8::to_utf8(orig_file_name, scope);
1449 let orig_line_number = orig_line_number.value() as i64;
1450 let orig_column_number = orig_column_number.value() as i64;
1451 let orig_file_name_line_col =
1452 fmt_file_line_col(&orig_file_name, orig_line_number, orig_column_number);
1453 let mapped = source_mapped_call_site_info(scope, this)?;
1454 let mapped_file_name = mapped.file_name(scope).to_rust_string_lossy(scope);
1455 let mapped_line_num = mapped
1456 .line_number(scope)
1457 .try_cast::<v8::Number>()
1458 .ok()
1459 .map(|n| n.value() as i64)?;
1460 let mapped_col_num =
1461 mapped.column_number(scope).cast::<v8::Number>().value() as i64;
1462 let file_name_line_col =
1463 fmt_file_line_col(&mapped_file_name, mapped_line_num, mapped_col_num);
1464 let to_string = orig_to_string
1466 .replace(&orig_file_name_line_col, &file_name_line_col)
1467 .replace(&orig_file_name, &mapped_file_name); Some(crate::FastString::from(to_string).v8_string(scope).unwrap())
1469 }
1470
1471 fn fmt_file_line_col(file: &str, line: i64, col: i64) -> String {
1472 StringBuilder::build(|builder| {
1473 builder.append(file);
1474 builder.append(':');
1475 builder.append(line);
1476 builder.append(':');
1477 builder.append(col);
1478 })
1479 .unwrap()
1480 }
1481
1482 pub fn to_string<'a>(
1483 scope: &mut v8::HandleScope<'a>,
1484 args: v8::FunctionCallbackArguments<'a>,
1485 mut rv: v8::ReturnValue<'_>,
1486 ) {
1487 let this = args.this();
1488 let Some(orig) = original_call_site(scope, this) else {
1489 return;
1490 };
1491 let Some(orig_to_string_v8) =
1493 call_method::<v8::String>(scope, orig, TO_STRING, &[])
1494 else {
1495 return;
1496 };
1497
1498 if let Some(v8_str) = to_string_inner(scope, this, orig, orig_to_string_v8)
1499 {
1500 rv.set(v8_str.into());
1501 } else {
1502 rv.set(orig_to_string_v8.into());
1503 }
1504 }
1505}
1506
1507pub(crate) fn make_callsite_prototype<'s>(
1525 scope: &mut v8::HandleScope<'s>,
1526) -> v8::Local<'s, v8::Object> {
1527 let template = v8::ObjectTemplate::new(scope);
1528
1529 macro_rules! set_attr {
1530 ($scope:ident, $template:ident, $fn:ident, $field:ident) => {
1531 let key = $field.v8_string($scope).unwrap().into();
1532 $template.set_with_attr(
1533 key,
1534 v8::FunctionBuilder::<v8::FunctionTemplate>::new(callsite_fns::$fn)
1535 .build($scope)
1536 .into(),
1537 v8::PropertyAttribute::DONT_DELETE
1538 | v8::PropertyAttribute::DONT_ENUM
1539 | v8::PropertyAttribute::READ_ONLY,
1540 );
1541 };
1542 }
1543
1544 set_attr!(scope, template, get_this, GET_THIS);
1545 set_attr!(scope, template, get_type_name, GET_TYPE_NAME);
1546 set_attr!(scope, template, get_function, GET_FUNCTION);
1547 set_attr!(scope, template, get_function_name, GET_FUNCTION_NAME);
1548 set_attr!(scope, template, get_method_name, GET_METHOD_NAME);
1549 set_attr!(scope, template, get_file_name, GET_FILE_NAME);
1550 set_attr!(scope, template, get_line_number, GET_LINE_NUMBER);
1551 set_attr!(scope, template, get_column_number, GET_COLUMN_NUMBER);
1552 set_attr!(scope, template, get_eval_origin, GET_EVAL_ORIGIN);
1553 set_attr!(scope, template, is_toplevel, IS_TOPLEVEL);
1554 set_attr!(scope, template, is_eval, IS_EVAL);
1555 set_attr!(scope, template, is_native, IS_NATIVE);
1556 set_attr!(scope, template, is_constructor, IS_CONSTRUCTOR);
1557 set_attr!(scope, template, is_async, IS_ASYNC);
1558 set_attr!(scope, template, is_promise_all, IS_PROMISE_ALL);
1559 set_attr!(scope, template, get_promise_index, GET_PROMISE_INDEX);
1560 set_attr!(
1561 scope,
1562 template,
1563 get_script_name_or_source_url,
1564 GET_SCRIPT_NAME_OR_SOURCE_URL
1565 );
1566 set_attr!(scope, template, to_string, TO_STRING);
1567
1568 template.new_instance(scope).unwrap()
1569}
1570
1571#[inline(always)]
1572fn prepare_stack_trace_inner<'s, const PATCH_CALLSITES: bool>(
1573 scope: &mut v8::HandleScope<'s>,
1574 error: v8::Local<'s, v8::Value>,
1575 callsites: v8::Local<'s, v8::Array>,
1576) -> v8::Local<'s, v8::Value> {
1577 if let Ok(obj) = error.try_cast::<v8::Object>() {
1580 let key = call_site_evals_key(scope);
1581 obj.set_private(scope, key, callsites.into());
1582 }
1583
1584 let global = scope.get_current_context().global(scope);
1586 let global_error =
1587 get_property(scope, global, ERROR).and_then(|g| g.try_cast().ok());
1588 let prepare_fn = global_error.and_then(|g| {
1589 get_property(scope, g, PREPARE_STACK_TRACE)
1590 .and_then(|f| f.try_cast::<v8::Function>().ok())
1591 });
1592
1593 if let Some(prepare_fn) = prepare_fn {
1596 let callsites = if PATCH_CALLSITES {
1597 let len = callsites.length();
1601 let mut patched = Vec::with_capacity(len as usize);
1602 let template = JsRuntime::state_from(scope)
1603 .callsite_prototype
1604 .borrow()
1605 .clone()
1606 .unwrap();
1607 let prototype = v8::Local::new(scope, template);
1608 for i in 0..len {
1609 let callsite =
1610 callsites.get_index(scope, i).unwrap().cast::<v8::Object>();
1611 patched.push(make_patched_callsite(scope, callsite, prototype).into());
1612 }
1613 v8::Array::new_with_elements(scope, &patched)
1614 } else {
1615 callsites
1616 };
1617
1618 let this = global_error.unwrap().into();
1620 let args = &[error, callsites.into()];
1621 return prepare_fn
1622 .call(scope, this, args)
1623 .unwrap_or_else(|| v8::undefined(scope).into());
1624 }
1625
1626 format_stack_trace(scope, error, callsites)
1628}
1629
1630pub fn prepare_stack_trace_callback_with_original_callsites<'s>(
1637 scope: &mut v8::HandleScope<'s>,
1638 error: v8::Local<'s, v8::Value>,
1639 callsites: v8::Local<'s, v8::Array>,
1640) -> v8::Local<'s, v8::Value> {
1641 prepare_stack_trace_inner::<false>(scope, error, callsites)
1642}
1643
1644pub fn prepare_stack_trace_callback<'s>(
1651 scope: &mut v8::HandleScope<'s>,
1652 error: v8::Local<'s, v8::Value>,
1653 callsites: v8::Local<'s, v8::Array>,
1654) -> v8::Local<'s, v8::Value> {
1655 prepare_stack_trace_inner::<true>(scope, error, callsites)
1656}
1657
1658pub fn format_stack_trace<'s>(
1659 scope: &mut v8::HandleScope<'s>,
1660 error: v8::Local<'s, v8::Value>,
1661 callsites: v8::Local<'s, v8::Array>,
1662) -> v8::Local<'s, v8::Value> {
1663 let mut result = String::new();
1664
1665 if let Ok(obj) = error.try_cast() {
1666 let msg = get_property(scope, obj, v8_static_strings::MESSAGE)
1668 .filter(|v| !v.is_undefined())
1669 .map(|v| v.to_rust_string_lossy(scope))
1670 .unwrap_or_default();
1671 let name = get_property(scope, obj, v8_static_strings::NAME)
1672 .filter(|v| !v.is_undefined())
1673 .map(|v| v.to_rust_string_lossy(scope))
1674 .unwrap_or_else(|| GENERIC_ERROR.to_string());
1675
1676 match (!msg.is_empty(), !name.is_empty()) {
1677 (true, true) => write!(result, "{}: {}", name, msg).unwrap(),
1678 (true, false) => write!(result, "{}", msg).unwrap(),
1679 (false, true) => write!(result, "{}", name).unwrap(),
1680 (false, false) => {}
1681 }
1682 }
1683
1684 for i in 0..callsites.length() {
1686 let callsite = callsites.get_index(scope, i).unwrap().cast::<v8::Object>();
1687 let tc_scope = &mut v8::TryCatch::new(scope);
1688 let Some(frame) = JsStackFrame::from_callsite_object(tc_scope, callsite)
1689 else {
1690 let message = tc_scope
1691 .exception()
1692 .expect("JsStackFrame::from_callsite_object raised an exception")
1693 .to_rust_string_lossy(tc_scope);
1694 #[allow(clippy::print_stderr)]
1695 {
1696 eprintln!(
1697 "warning: Failed to create JsStackFrame from callsite object: {message}; Result so far: {result}. This is a bug in deno"
1698 );
1699 }
1700 break;
1701 };
1702 write!(result, "\n at {}", format_frame::<NoAnsiColors>(&frame))
1703 .unwrap();
1704 }
1705
1706 let result = v8::String::new(scope, &result).unwrap();
1707 result.into()
1708}
1709
1710pub struct NoAnsiColors;
1712
1713#[derive(Debug, Clone, Copy)]
1714pub enum ErrorElement {
1716 Anonymous,
1718 NativeFrame,
1720 LineNumber,
1722 ColumnNumber,
1724 FunctionName,
1726 FileName,
1728 EvalOrigin,
1730 PromiseAll,
1732}
1733
1734pub trait ErrorFormat {
1740 fn fmt_element(element: ErrorElement, s: &str) -> Cow<'_, str>;
1741}
1742
1743impl ErrorFormat for NoAnsiColors {
1744 fn fmt_element(_element: ErrorElement, s: &str) -> Cow<'_, str> {
1745 s.into()
1746 }
1747}
1748
1749pub fn format_location<F: ErrorFormat>(frame: &JsStackFrame) -> String {
1750 use ErrorElement::*;
1751 let _internal = frame
1752 .file_name
1753 .as_ref()
1754 .map(|f| f.starts_with("ext:"))
1755 .unwrap_or(false);
1756 if frame.is_native {
1757 return F::fmt_element(NativeFrame, "native").to_string();
1758 }
1759 let mut result = String::new();
1760 let file_name = frame.file_name.clone().unwrap_or_default();
1761 if !file_name.is_empty() {
1762 result += &F::fmt_element(FileName, &format_file_name(&file_name))
1763 } else {
1764 if frame.is_eval {
1765 result += &(F::fmt_element(
1766 ErrorElement::EvalOrigin,
1767 frame.eval_origin.as_ref().unwrap(),
1768 )
1769 .to_string()
1770 + ", ");
1771 }
1772 result += &F::fmt_element(Anonymous, "<anonymous>");
1773 }
1774 if let Some(line_number) = frame.line_number {
1775 write!(
1776 result,
1777 ":{}",
1778 F::fmt_element(LineNumber, &line_number.to_string())
1779 )
1780 .unwrap();
1781 if let Some(column_number) = frame.column_number {
1782 write!(
1783 result,
1784 ":{}",
1785 F::fmt_element(ColumnNumber, &column_number.to_string())
1786 )
1787 .unwrap();
1788 }
1789 }
1790 result
1791}
1792
1793pub fn format_frame<F: ErrorFormat>(frame: &JsStackFrame) -> String {
1794 use ErrorElement::*;
1795 let _internal = frame
1796 .file_name
1797 .as_ref()
1798 .map(|f| f.starts_with("ext:"))
1799 .unwrap_or(false);
1800 let is_method_call =
1801 !(frame.is_top_level.unwrap_or_default() || frame.is_constructor);
1802 let mut result = String::new();
1803 if frame.is_async {
1804 result += "async ";
1805 }
1806 if frame.is_promise_all {
1807 result += &F::fmt_element(
1808 PromiseAll,
1809 &format!(
1810 "Promise.all (index {})",
1811 frame.promise_index.unwrap_or_default()
1812 ),
1813 );
1814 return result;
1815 }
1816 if is_method_call {
1817 let mut formatted_method = String::new();
1818 if let Some(function_name) = &frame.function_name {
1819 if let Some(type_name) = &frame.type_name {
1820 if !function_name.starts_with(type_name) {
1821 write!(formatted_method, "{type_name}.").unwrap();
1822 }
1823 }
1824 formatted_method += function_name;
1825 if let Some(method_name) = &frame.method_name {
1826 if !function_name.ends_with(method_name) {
1827 write!(formatted_method, " [as {method_name}]").unwrap();
1828 }
1829 }
1830 } else {
1831 if let Some(type_name) = &frame.type_name {
1832 write!(formatted_method, "{type_name}.").unwrap();
1833 }
1834 if let Some(method_name) = &frame.method_name {
1835 formatted_method += method_name
1836 } else {
1837 formatted_method += "<anonymous>";
1838 }
1839 }
1840 result += F::fmt_element(FunctionName, &formatted_method).as_ref();
1841 } else if frame.is_constructor {
1842 result += "new ";
1843 if let Some(function_name) = &frame.function_name {
1844 write!(result, "{}", F::fmt_element(FunctionName, function_name))
1845 .unwrap();
1846 } else {
1847 result += F::fmt_element(Anonymous, "<anonymous>").as_ref();
1848 }
1849 } else if let Some(function_name) = &frame.function_name {
1850 result += F::fmt_element(FunctionName, function_name).as_ref();
1851 } else {
1852 result += &format_location::<F>(frame);
1853 return result;
1854 }
1855 write!(result, " ({})", format_location::<F>(frame)).unwrap();
1856 result
1857}
1858
1859pub fn throw_error_one_byte_info(
1860 info: &v8::FunctionCallbackInfo,
1861 message: &str,
1862) {
1863 let mut scope = unsafe { v8::CallbackScope::new(info) };
1864 throw_error_one_byte(&mut scope, message);
1865}
1866
1867pub fn throw_error_js_error_class(
1868 scope: &mut v8::CallbackScope<'_>,
1869 err: &dyn JsErrorClass,
1870) {
1871 let exc = to_v8_error(scope, err);
1872 scope.throw_exception(exc);
1873}
1874
1875pub fn throw_error_one_byte(scope: &mut v8::CallbackScope, message: &str) {
1876 let msg = deno_core::v8::String::new_from_one_byte(
1877 scope,
1878 message.as_bytes(),
1879 deno_core::v8::NewStringType::Normal,
1880 )
1881 .unwrap();
1882 let exc = deno_core::v8::Exception::type_error(scope, msg);
1883 scope.throw_exception(exc);
1884}
1885
1886#[cfg(test)]
1887mod tests {
1888 use super::*;
1889
1890 #[test]
1891 fn test_format_file_name() {
1892 let file_name = format_file_name("data:,Hello%2C%20World%21");
1893 assert_eq!(file_name, "data:,Hello%2C%20World%21");
1894
1895 let too_long_name = "a".repeat(DATA_URL_ABBREV_THRESHOLD + 1);
1896 let file_name = format_file_name(&format!(
1897 "data:text/plain;base64,{too_long_name}_%F0%9F%A6%95"
1898 ));
1899 assert_eq!(
1900 file_name,
1901 "data:text/plain;base64,aaaaaaaaaaaaaaaaaaaa......aaaaaaa_%F0%9F%A6%95"
1902 );
1903
1904 let file_name = format_file_name("file:///foo/bar.ts");
1905 assert_eq!(file_name, "file:///foo/bar.ts");
1906
1907 let file_name =
1908 format_file_name("file:///%E6%9D%B1%E4%BA%AC/%F0%9F%A6%95.ts");
1909 assert_eq!(file_name, "file:///東京/🦕.ts");
1910 }
1911
1912 #[test]
1913 fn test_parse_eval_origin() {
1914 let cases = [
1915 (
1916 "eval at <anonymous> (file://path.ts:1:2)",
1917 Some(("eval at <anonymous> (", ("file://path.ts", 1, 2), ")")),
1918 ),
1919 (
1920 "eval at (s:1:2",
1922 None,
1923 ),
1924 (
1925 "at ()", None,
1927 ),
1928 (
1929 "eval at foo (http://website.zzz/my-script).ts:1:2)",
1931 Some((
1932 "eval at foo (",
1933 ("http://website.zzz/my-script).ts", 1, 2),
1934 ")",
1935 )),
1936 ),
1937 (
1938 "eval at foo (eval at bar (file://path.ts:1:2))",
1940 Some(("eval at foo (eval at bar (", ("file://path.ts", 1, 2), "))")),
1941 ),
1942 ];
1943 for (input, expect) in cases {
1944 match expect {
1945 Some((
1946 expect_before,
1947 (expect_file, expect_line, expect_col),
1948 expect_after,
1949 )) => {
1950 let (before, (file_name, line_number, column_number), after) =
1951 parse_eval_origin(input).unwrap();
1952 assert_eq!(before, expect_before);
1953 assert_eq!(file_name, expect_file);
1954 assert_eq!(line_number, expect_line);
1955 assert_eq!(column_number, expect_col);
1956 assert_eq!(after, expect_after);
1957 }
1958 None => {
1959 assert!(parse_eval_origin(input).is_none());
1960 }
1961 }
1962 }
1963 }
1964}