deno_core/
error.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3pub 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
24/// A generic wrapper that can encapsulate any concrete error type.
25// TODO(ry) Deprecate AnyError and encourage deno_core::anyhow::Error instead.
26pub 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![] // TODO
243  }
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  /*
269  commented out since this was previously only handling type errors, but this
270  change is breaking CLI, so visiting on a later date
271
272  match class {
273    TYPE_ERROR => v8::Exception::type_error(scope, message),
274    RANGE_ERROR => v8::Exception::range_error(scope, message),
275    REFERENCE_ERROR => v8::Exception::reference_error(scope, message),
276    SYNTAX_ERROR => v8::Exception::syntax_error(scope, message),
277    _ => v8::Exception::error(scope, message),
278  }*/
279  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/// A `JsError` represents an exception coming from V8, with stack frames and
341/// line numbers. The deno_cli crate defines another `JsError` type, which wraps
342/// the one defined here, that adds source map support and colorful formatting.
343/// When updating this struct, also update errors_are_equal_without_cause() in
344/// fmt_error.rs.
345#[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  // Warning! isToplevel has inconsistent snake<>camel case, "typo" originates in v8:
370  // https://source.chromium.org/search?q=isToplevel&sq=&ss=chromium%2Fchromium%2Fsrc:v8%2F
371  #[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
381/// Applies source map to the given location
382fn 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
406/// Parses an eval origin string from V8, returning
407/// the contents before the location,
408/// (the file name, line number, and column number), and
409/// the contents after the location.
410///
411/// # Example
412/// ```ignore
413/// assert_eq!(
414///   parse_eval_origin("eval at foo (bar at (file://a.ts:1:2))"),
415///   Some(("eval at foo (bar at (", ("file://a.ts", 1, 2), "))")),
416/// );
417/// ```
418///
419fn parse_eval_origin(
420  eval_origin: &str,
421) -> Option<(&str, (&str, i64, i64), &str)> {
422  // The eval origin string we get from V8 looks like
423  // `eval at ${function_name} (${origin})`
424  // where origin can be either a file name, like
425  // "eval at foo (file:///path/to/script.ts:1:2)"
426  // or a nested eval origin, like
427  // "eval at foo (eval at bar (file:///path/to/script.ts:1:2))"
428  //
429  let eval_at = "eval at ";
430  // only the innermost eval origin can have location info, so find the last
431  // "eval at", then continue parsing the rest of the string
432  let mut innermost_start = eval_origin.rfind(eval_at)? + eval_at.len();
433  // skip over the function name
434  innermost_start += eval_origin[innermost_start..].find('(')? + 1;
435  if innermost_start >= eval_origin.len() {
436    // malformed
437    return None;
438  }
439
440  // from the right, split by ":" to get the column number, line number, file name
441  // (in that order, since we're iterating from the right). e.g.
442  // eval at foo (eval at bar (file://foo.ts:1:2))
443  //                           ^^^^^^^^^^^^^ ^ ^^^
444  let mut parts = eval_origin[innermost_start..].rsplitn(3, ':');
445  // the part with the column number will include extra stuff, the actual number ends at
446  // the closing paren
447  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  // The column number starts after the last occurring ":".
455  let column_start = eval_origin.rfind(':')? + 1;
456  // the innermost origin ends at the end of the column number
457  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  /// Creates a `JsStackFrame` from a `CallSite`` JS object,
490  /// provided by V8.
491  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    // apply source map
519    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    // apply source map to the eval origin, if the error originates from `eval`ed code
533    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  /// Gets the source mapped stack frame corresponding to the
562  /// (script_resource_name, line_number, column_number) from a v8 message.
563  /// For non-syntax errors, it should also correspond to the first stack frame.
564  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    // V8's column numbers are 0-based, we want 1-based.
573    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  // Warning! .stack is special so handled by itself
650  // stack: Option<String>,
651}
652
653impl JsError {
654  /// Compares all properties of JsError, except for JsError::cause. This function is used to
655  /// detect that 2 JsError objects in a JsError::cause chain are identical, ie. there is a recursive cause.
656  ///
657  /// We don't have access to object identity here, so we do it via field comparison. Ideally this should
658  /// be able to maintain object identity somehow.
659  pub fn is_same_error(&self, other: &JsError) -> bool {
660    let a = self;
661    let b = other;
662    // `a.cause == b.cause` omitted, because it is absent in recursive errors,
663    // despite the error being identical to a previously seen one.
664    a.name == b.name
665      && a.message == b.message
666      && a.stack == b.stack
667      // TODO(mmastrac): we need consistency around when we insert "in promise" and when we don't. For now, we
668      // are going to manually replace this part of the string.
669      && (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    // Create a new HandleScope because we're creating a lot of new local
689    // handles below.
690    let scope = &mut v8::HandleScope::new(scope);
691
692    let exception_message = msg.get(scope).to_rust_string_lossy(scope);
693
694    // Convert them into Vec<JsStackFrame>
695    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    // Create a new HandleScope because we're creating a lot of new local
737    // handles below.
738    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      // The exception is a JS Error object.
761      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      // Get the message by formatting error.name and error.message.
766      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      // Access error.stack to ensure that prepareStackTrace() has been called.
791      // This should populate error.#callSiteEvals.
792      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      // Read an array of structured frames from error.#callSiteEvals.
798      let frames_v8 = {
799        let key = call_site_evals_key(scope);
800        exception.get_private(scope, key)
801      };
802      // Ignore non-array values
803      let frames_v8: Option<v8::Local<v8::Array>> =
804        frames_v8.and_then(|a| a.try_into().ok());
805
806      // Convert them into Vec<JsStackFrame>
807      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      // When the stack frame array is empty, but the source location given by
840      // (script_resource_name, line_number, start_column + 1) exists, this is
841      // likely a syntax error. For the sake of formatting we treat it like it
842      // was given as a single stack frame.
843      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        // Read an array of stored errors, this is only defined for `AggregateError`
868        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      // The exception is not a JS Error object.
901      // Get the message given by V8::Exception::create_message(), and provide
902      // empty frames.
903      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
937/// Implements `value instanceof primordials.Error` in JS. Similar to
938/// `Value::is_native_error()` but more closely matches the semantics
939/// of `instanceof`. `Value::is_native_error()` also checks for static class
940/// inheritance rather than just scanning the prototype chain, which doesn't
941/// work with our WebIDL implementation of `DOMException`.
942pub(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
971/// Implements `value instanceof primordials.AggregateError` in JS,
972/// by walking the prototype chain, and comparing each links constructor `name` property.
973///
974/// NOTE: There is currently no way to detect `AggregateError` via `rusty_v8`,
975/// as v8 itself doesn't expose `v8__Exception__AggregateError`,
976/// and we cannot create bindings for it. This forces us to rely on `name` inference.
977pub(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
1008/// Check if the error has a proper stack trace. The stack trace checked is the
1009/// one passed to `prepareStackTrace()`, not `msg.get_stack_trace()`.
1010pub(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  // Access error.stack to ensure that prepareStackTrace() has been called.
1019  // This should populate error.#callSiteEvals.
1020  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    // same as to_percent_decoded_str() in cli/util/path.rs
1040    match percent_encoding::percent_decode_str(file_name).decode_utf8() {
1041      Ok(s) => s.to_string(),
1042      // when failing as utf-8, just return the original string
1043      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  // Disable running microtasks for a moment. When upgrading to V8 v11.4
1074  // we discovered that canceling termination here will cause the queued
1075  // microtasks to run which breaks some tests.
1076  scope.set_microtasks_policy(v8::MicrotasksPolicy::Explicit);
1077  // If TerminateExecution was called, cancel isolate termination so that the
1078  // exception can be created. Note that `scope.is_execution_terminating()` may
1079  // have returned false if TerminateExecution was indeed called but there was
1080  // no JS to execute after the call.
1081  scope.cancel_terminate_execution();
1082  let exception = match state.get_dispatched_exception_as_local(scope) {
1083    Some(dispatched_exception) => {
1084      // If termination is the result of a `reportUnhandledException` call, we want
1085      // to use the exception that was passed to it rather than the exception that
1086      // was passed to this function.
1087      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        // If we are terminating and there is no exception, throw `new Error("execution terminated")``.
1097        let message = v8::String::new(scope, "execution terminated").unwrap();
1098        v8::Exception::error(scope, message)
1099      } else {
1100        // Otherwise re-use the exception
1101        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    // Resume exception termination.
1116    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    // return the cached value if it exists
1321    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 the types are right, apply the source map, otherwise just take them as is
1346    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  // the bulk of the to_string logic
1431  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    // `this[kOriginalCallsite].getFileName()`
1439    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    // replace file URL with file path, and source map in original `toString`
1465    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); // maybe unnecessary?
1468    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    // `this[kOriginalCallsite].toString()`
1492    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
1507/// Creates a template for a `Callsite`-like object, with
1508/// a patched `getFileName`.
1509/// Effectively:
1510/// ```js
1511/// const kOriginalCallsite = Symbol("_original");
1512/// {
1513///   [kOriginalCallsite]: originalCallSite,
1514///   getLineNumber() {
1515///     return this[kOriginalCallsite].getLineNumber();
1516///   },
1517///   // etc
1518///   getFileName() {
1519///     const fileName = this[kOriginalCallsite].getFileName();
1520///     return fileUrlToPath(fileName);
1521///   }
1522/// }
1523/// ```
1524pub(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  // stash the callsites on the error object. this is used by `JsError::from_exception_inner`
1578  // to get more details on an error, because the v8 StackFrame API is missing some info.
1579  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  // `globalThis.Error.prepareStackTrace`
1585  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  // Note that the callback is called _instead_ of `Error.prepareStackTrace`
1594  // so we have to explicitly call the global function
1595  if let Some(prepare_fn) = prepare_fn {
1596    let callsites = if PATCH_CALLSITES {
1597      // User defined `Error.prepareStackTrace`.
1598      // Patch the callsites to have file paths, then call
1599      // the user's function
1600      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    // call the user's `prepareStackTrace` with our "callsite" objects
1619    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  // no user defined `prepareStackTrace`, just call our default
1627  format_stack_trace(scope, error, callsites)
1628}
1629
1630/// Callback to prepare an error stack trace. This callback is invoked by V8 whenever a stack trace is created.
1631/// This will patch callsite objects to translate file URLs to file paths before they're
1632/// exposed to user-defined `prepareStackTrace` implementations.
1633///
1634/// This function is not used by default, but it can
1635/// be set directly on the [v8 isolate][v8::Isolate::set_prepare_stack_trace_callback]
1636pub 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
1644/// Callback to prepare an error stack trace. This callback is invoked by V8 whenever a stack trace is created.
1645/// This will patch callsite objects to translate file URLs to file paths before they're
1646/// exposed to user-defined `prepareStackTrace` implementations.
1647///
1648/// This function is the default callback, set on creating a `JsRuntime`, but the callback can also
1649/// be set directly on the [v8 isolate][v8::Isolate::set_prepare_stack_trace_callback]
1650pub 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    // Write out the error name + message, if any
1667    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  // format each stack frame
1685  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
1710/// Formats an error without using ansi colors.
1711pub struct NoAnsiColors;
1712
1713#[derive(Debug, Clone, Copy)]
1714/// Part of an error stack trace
1715pub enum ErrorElement {
1716  /// An anonymous file or method name
1717  Anonymous,
1718  /// Text signifying a native stack frame
1719  NativeFrame,
1720  /// A source line number
1721  LineNumber,
1722  /// A source column number
1723  ColumnNumber,
1724  /// The name of a function (or method)
1725  FunctionName,
1726  /// The name of a source file
1727  FileName,
1728  /// The origin of an error coming from `eval`ed code
1729  EvalOrigin,
1730  /// Text signifying a call to `Promise.all`
1731  PromiseAll,
1732}
1733
1734/// Applies formatting to various parts of error stack traces.
1735///
1736/// This is to allow the `deno` crate to reuse `format_frame` and
1737/// `format_location` but apply ANSI colors for terminal output,
1738/// without adding extra dependencies to `deno_core`.
1739pub 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        // malformed
1921        "eval at (s:1:2",
1922        None,
1923      ),
1924      (
1925        // malformed
1926        "at ()", None,
1927      ),
1928      (
1929        // url with parens
1930        "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        // nested
1939        "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}