Repository: denoland/deno_core
Pull request #853 (2024-07-30 01:07:39)

feat: Add ImportAssertionsSupport
Needed for https://github.com/denoland/deno/pull/24743.

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/denoland/deno_core/pull/853?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) Report
Attention: Patch coverage is `35.16484%` with `59 lines` in your changes missing coverage. Please review.
> Project coverage is 81.56%. Comparing base [(`0c7f83e`)](https://app.codecov.io/gh/denoland/deno_core/commit/0c7f83ea8daabe8f541fc93ae97529dc446f5f24?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) to head [(`e3712a1`)](https://app.codecov.io/gh/denoland/deno_core/commit/e3712a16d2389393d83d86fdbfe1e27e6937bfc8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).
> Report is 84 commits behind head on main.

| [Files](https://app.codecov.io/gh/denoland/deno_core/pull/853?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) | Patch % | Lines |
|---|---|---|
| [core/runtime/jsruntime.rs](https://app.codecov.io/gh/denoland/deno_core/pull/853?src=pr&el=tree&filepath=core%2Fruntime%2Fjsruntime.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS9ydW50aW1lL2pzcnVudGltZS5ycw==) | 23.07% | [50 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/853?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |
| [core/runtime/setup.rs](https://app.codecov.io/gh/denoland/deno_core/pull/853?src=pr&el=tree&filepath=core%2Fruntime%2Fsetup.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland#diff-Y29yZS9ydW50aW1lL3NldHVwLnJz) | 64.00% | [9 Missing :warning: ](https://app.codecov.io/gh/denoland/deno_core/pull/853?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #853      +/-   ##
==========================================
+ Coverage   81.43%   81.56%   +0.12%     
==========================================
  Files          97       98       +1     
  Lines       23877    24910    +1033     
==========================================
+ Hits        19445    20318     +873     
- Misses       4432     4592     +160     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/denoland/deno_core/pull/853?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).


Patch:
diff --git a/core/error.rs b/core/error.rs
index 79988fd..fd83744 100644
--- a/core/error.rs
+++ b/core/error.rs
@@ -352,7 +352,10 @@ impl JsStackFrame {
 
     // apply source map to the eval origin, if the error originates from `eval`ed code
     let eval_origin = call!(GET_EVAL_ORIGIN: Option<String>).and_then(|o| {
-      let (before, (file, line, col), after) = parse_eval_origin(&o)?;
+      let Some((before, (file, line, col), after)) = parse_eval_origin(&o)
+      else {
+        return Some(o);
+      };
       let (file, line, col) =
         apply_source_map(&mut source_mapper, file.into(), line, col);
       Some(format!("{before}{file}:{line}:{col}{after}"))
@@ -950,6 +953,7 @@ v8_static_strings::v8_static_strings! {
   GET_PROMISE_INDEX = "getPromiseIndex",
   PREPARE_STACK_TRACE = "prepareStackTrace",
   ORIGINAL = "_orig",
+  TO_STRING = "toString",
 }
 
 #[inline(always)]
@@ -971,6 +975,51 @@ fn make_patched_callsite<'s>(
   out_obj
 }
 
+fn make_fn_template<'a, F>(
+  scope: &mut v8::HandleScope<'a>,
+  f: F,
+) -> v8::Local<'a, v8::FunctionTemplate>
+where
+  F: for<'s, 't> FnOnce(
+      &mut v8::HandleScope<'s>,
+      v8::FunctionCallbackArguments<'t>,
+      v8::ReturnValue<'t>,
+    ) + Copy,
+{
+  v8::FunctionBuilder::<v8::FunctionTemplate>::new(
+    move |scope: &mut v8::HandleScope<'_>,
+          args: v8::FunctionCallbackArguments<'_>,
+          rv: v8::ReturnValue<'_>| {
+      f(scope, args, rv);
+    },
+  )
+  .build(scope)
+}
+
+fn maybe_to_path_str(s: &str) -> Option<String> {
+  if s.starts_with("file://") {
+    Some(
+      Url::parse(s)
+        .unwrap()
+        .to_file_path()
+        .unwrap()
+        .to_string_lossy()
+        .into_owned(),
+    )
+  } else {
+    None
+  }
+}
+
+fn original_call_site<'s>(
+  scope: &mut v8::HandleScope<'s>,
+  object: v8::Local<v8::Object>,
+) -> v8::Local<'s, v8::Object> {
+  // lookup the original call site object
+  let orig_key = original_call_site_key(scope);
+  object.get_private(scope, orig_key).unwrap().cast()
+}
+
 /// Creates a template for a `Callsite`-like object, with
 /// a patched `getFileName`.
 /// Effectively:
@@ -996,29 +1045,25 @@ pub(crate) fn make_callsite_template<'s>(
   // effectively
   // `$field() { return this[kOriginalCallsite].$field() }`
   macro_rules! make_delegate {
-    ($scope: ident, $template: ident, [$($field: ident),+ $(,)?]) => {
+    ($($field: ident),+ $(,)?) => {
       $(
         {
-          let key = $field.v8_string($scope).into();
-          $template.set_with_attr(
+          let key = $field.v8_string(scope).into();
+          template.set_with_attr(
             key,
-            v8::FunctionBuilder::<v8::FunctionTemplate>::new(
-              |scope: &mut v8::HandleScope<'_>,
-              args: v8::FunctionCallbackArguments<'_>,
-              mut rv: v8::ReturnValue<'_>| {
-                let orig_key = original_call_site_key(scope);
-                let orig = args.this().get_private(scope, orig_key).unwrap();
+            make_fn_template(
+              scope,
+              |scope, args, mut rv| {
+                let orig = original_call_site(scope, args.this());
                 let key = $field.v8_string(scope).into();
                 let orig_ret = orig
-                  .cast::<v8::Object>()
                   .get(scope, key)
                   .unwrap()
                   .cast::<v8::Function>()
-                  .call(scope, orig, &[]);
+                  .call(scope, orig.into(), &[]);
                 rv.set(orig_ret.unwrap_or_else(|| v8::undefined(scope).into()));
               },
             )
-            .build($scope)
             .into(),
             v8::PropertyAttribute::DONT_DELETE
             | v8::PropertyAttribute::DONT_ENUM
@@ -1031,67 +1076,76 @@ pub(crate) fn make_callsite_template<'s>(
 
   // delegate to the original callsite object
   make_delegate!(
-    scope,
-    template,
-    [
-      // excludes getFileName, which we'll override below
-      GET_THIS,
-      GET_TYPE_NAME,
-      GET_FUNCTION,
-      GET_FUNCTION_NAME,
-      GET_METHOD_NAME,
-      GET_LINE_NUMBER,
-      GET_COLUMN_NUMBER,
-      GET_EVAL_ORIGIN,
-      IS_TOPLEVEL,
-      IS_EVAL,
-      IS_NATIVE,
-      IS_CONSTRUCTOR,
-      IS_ASYNC,
-      IS_PROMISE_ALL,
-      GET_PROMISE_INDEX,
-    ]
+    // excludes getFileName and toString, which we'll override below
+    GET_THIS,
+    GET_TYPE_NAME,
+    GET_FUNCTION,
+    GET_FUNCTION_NAME,
+    GET_METHOD_NAME,
+    GET_LINE_NUMBER,
+    GET_COLUMN_NUMBER,
+    GET_EVAL_ORIGIN,
+    IS_TOPLEVEL,
+    IS_EVAL,
+    IS_NATIVE,
+    IS_CONSTRUCTOR,
+    IS_ASYNC,
+    IS_PROMISE_ALL,
+    GET_PROMISE_INDEX,
   );
 
   let get_file_name_key = GET_FILE_NAME.v8_string(scope).into();
   template.set_with_attr(
     get_file_name_key,
-    v8::FunctionBuilder::<v8::FunctionTemplate>::new(
-      |scope: &mut v8::HandleScope<'_>,
-       args: v8::FunctionCallbackArguments<'_>,
-       mut rv: v8::ReturnValue<'_>| {
-        // lookup the original call site object
-        let orig_key = original_call_site_key(scope);
-        let orig = args.this().get_private(scope, orig_key).unwrap().cast();
-        // call getFileName
-        let orig_ret =
-          call_method::<v8::String>(scope, orig, GET_FILE_NAME, &[]);
-        if let Some(ret_val) = orig_ret {
-          // strip off `file://`
-          let string = ret_val.to_rust_string_lossy(scope);
-          let file_name = if string.starts_with("file://") {
-            Url::parse(&string)
-              .unwrap()
-              .to_file_path()
-              .unwrap()
-              .to_string_lossy()
-              .into_owned()
-          } else {
-            string
-          };
+    make_fn_template(scope, |scope, args, mut rv| {
+      // lookup the original call site object
+      let orig = original_call_site(scope, args.this());
+      // call getFileName
+      let orig_ret = call_method::<v8::String>(scope, orig, GET_FILE_NAME, &[]);
+      if let Some(ret_val) = orig_ret {
+        // strip off `file://`
+        let string = serde_v8::to_utf8(ret_val, scope);
+        let file_name = maybe_to_path_str(&string).unwrap_or(string);
+        let v8_str = crate::FastString::from(file_name).v8_string(scope).into();
+        rv.set(v8_str);
+      }
+    })
+    .into(),
+    v8::PropertyAttribute::DONT_DELETE
+      | v8::PropertyAttribute::DONT_ENUM
+      | v8::PropertyAttribute::READ_ONLY,
+  );
+
+  let to_string_key = TO_STRING.v8_string(scope).into();
+  template.set_with_attr(
+    to_string_key,
+    make_fn_template(scope, |scope, args, mut rv| {
+      let orig = original_call_site(scope, args.this());
+      // `this[kOriginalCallsite].toString()`
+      let orig_to_string_v8 =
+        call_method::<v8::String>(scope, orig, TO_STRING, &[]).unwrap();
+      let orig_to_string = serde_v8::to_utf8(orig_to_string_v8, scope);
+      // `this[kOriginalCallsite].getFileName()`
+      if let Some(orig_file_name) =
+        call_method::<v8::String>(scope, orig, GET_FILE_NAME, &[])
+      {
+        // replace file URL with file path in original `toString`
+        let orig_file_name = serde_v8::to_utf8(orig_file_name, scope);
+        if let Some(file_name) = maybe_to_path_str(&orig_file_name) {
+          let to_string = orig_to_string.replace(&orig_file_name, &file_name);
           let v8_str =
-            crate::FastString::from(file_name).v8_string(scope).into();
+            crate::FastString::from(to_string).v8_string(scope).into();
           rv.set(v8_str);
+          return;
         }
-      },
-    )
-    .build(scope)
+      }
+      rv.set(orig_to_string_v8.into());
+    })
     .into(),
     v8::PropertyAttribute::DONT_DELETE
       | v8::PropertyAttribute::DONT_ENUM
       | v8::PropertyAttribute::READ_ONLY,
   );
-
   template
 }
 
diff --git a/core/lib.rs b/core/lib.rs
index 5d246f3..977fa13 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -151,6 +151,8 @@ pub use crate::runtime::CompiledWasmModuleStore;
 pub use crate::runtime::ContextState;
 pub use crate::runtime::CreateRealmOptions;
 pub use crate::runtime::CrossIsolateStore;
+pub use crate::runtime::ImportAssertionsSupport;
+pub use crate::runtime::ImportAssertionsSupportCustomCallbackArgs;
 pub use crate::runtime::JsRuntime;
 pub use crate::runtime::JsRuntimeForSnapshot;
 pub use crate::runtime::PollEventLoopOptions;
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
index 4bcbbbf..a0cf375 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -69,6 +69,7 @@ use futures::Future;
 use futures::FutureExt;
 use smallvec::SmallVec;
 use std::any::Any;
+use v8::MessageErrorLevel;
 
 use std::cell::Cell;
 use std::cell::RefCell;
@@ -438,6 +439,7 @@ pub struct JsRuntimeState {
   /// Accessed through [`JsRuntimeState::with_inspector`].
   inspector: RefCell<Option<Rc<RefCell<JsRuntimeInspector>>>>,
   has_inspector: Cell<bool>,
+  import_assertions_support: ImportAssertionsSupport,
 }
 
 #[derive(Default)]
@@ -550,6 +552,104 @@ pub struct RuntimeOptions {
   /// through evalContext.
   pub eval_context_code_cache_cbs:
     Option<(EvalContextGetCodeCacheCb, EvalContextCodeCacheReadyCb)>,
+
+  pub import_assertions_support: ImportAssertionsSupport,
+}
+
+pub struct ImportAssertionsSupportCustomCallbackArgs {
+  pub maybe_specifier: Option<String>,
+  pub maybe_line_number: Option<usize>,
+  pub column_number: usize,
+  pub maybe_source_line: Option<String>,
+}
+
+#[derive(Default)]
+pub enum ImportAssertionsSupport {
+  /// `assert` keyword is no longer supported and causes a SyntaxError.
+  /// This is the default setting, because V8 unshipped import assertions
+  /// support in v12.6. To enable import assertion one must pass
+  /// `--harmony-import-assertions` V8 flag when initializing the runtime.
+  #[default]
+  Error,
+
+  /// `assert` keyword is supported.
+  Yes,
+
+  /// `assert` keyword is supported, but prints a warning message on occurence.
+  Warning,
+
+  /// `assert` keyword is supported, provided callback is called on each occurence.
+  /// Callback receives optional specifier, optional line number, column number and optional
+  /// source line.
+  CustomCallback(Box<dyn Fn(ImportAssertionsSupportCustomCallbackArgs)>),
+}
+
+impl ImportAssertionsSupport {
+  fn is_enabled(&self) -> bool {
+    matches!(self, Self::Yes | Self::Warning | Self::CustomCallback(_))
+  }
+
+  fn has_warning(&self) -> bool {
+    matches!(self, Self::Warning | Self::CustomCallback(_))
+  }
+}
+
+/// Currently only handles warnings for "import assertions" deprecation
+extern "C" fn isolate_message_listener(
+  message: v8::Local<v8::Message>,
+  _exception: v8::Local<v8::Value>,
+) {
+  let scope = &mut unsafe { v8::CallbackScope::new(message) };
+  let scope = &mut v8::HandleScope::new(scope);
+  let message_v8_str = message.get(scope);
+  let message_str = message_v8_str.to_rust_string_lossy(scope);
+
+  if !message_str.starts_with("'assert' is deprecated") {
+    return;
+  }
+
+  let maybe_script_resource_name = message
+    .get_script_resource_name(scope)
+    .map(|s| s.to_rust_string_lossy(scope));
+  let maybe_source_line = message
+    .get_source_line(scope)
+    .map(|s| s.to_rust_string_lossy(scope));
+  let maybe_line_number = message.get_line_number(scope);
+  let start_column = message.get_start_column();
+
+  let js_runtime_state = JsRuntime::state_from(scope);
+  match &js_runtime_state.import_assertions_support {
+    ImportAssertionsSupport::Warning => {
+      let mut msg = "⚠️  Import assertions are deprecated. Use `with` keyword, instead of 'assert' keyword.".to_string();
+      if let Some(specifier) = maybe_script_resource_name {
+        if let Some(source_line) = maybe_source_line {
+          msg.push('\n');
+          msg.push_str(&source_line);
+          msg.push('\n');
+          msg.push_str(&format!("{:0width$}^", " ", width = start_column));
+        }
+        msg.push_str(&format!(
+          "  at {}:{}:{}",
+          specifier,
+          maybe_line_number.unwrap(),
+          start_column
+        ));
+        #[allow(clippy::print_stderr)]
+        {
+          eprintln!("{}", msg);
+        }
+      }
+    }
+    ImportAssertionsSupport::CustomCallback(cb) => {
+      cb(ImportAssertionsSupportCustomCallbackArgs {
+        maybe_specifier: maybe_script_resource_name,
+        maybe_line_number,
+        column_number: start_column,
+        maybe_source_line,
+      });
+    }
+    _ => unreachable!(),
+  }
 }
 
 impl RuntimeOptions {
@@ -594,8 +694,11 @@ impl JsRuntime {
   /// should only be called once per process. Further calls will be silently
   /// ignored.
   #[cfg(not(any(test, feature = "unsafe_runtime_options")))]
-  pub fn init_platform(v8_platform: Option<v8::SharedRef<v8::Platform>>) {
-    setup::init_v8(v8_platform, cfg!(test), false);
+  pub fn init_platform(
+    v8_platform: Option<v8::SharedRef<v8::Platform>>,
+    import_assertions_enabled: bool,
+  ) {
+    setup::init_v8(v8_platform, cfg!(test), false, import_assertions_enabled);
   }
 
   /// Explicitly initalizes the V8 platform using the passed platform. This
@@ -610,8 +713,14 @@ impl JsRuntime {
   pub fn init_platform(
     v8_platform: Option<v8::SharedRef<v8::Platform>>,
     expose_natives: bool,
+    import_assertions_enabled: bool,
   ) {
-    setup::init_v8(v8_platform, cfg!(test), expose_natives);
+    setup::init_v8(
+      v8_platform,
+      cfg!(test),
+      expose_natives,
+      import_assertions_enabled,
+    );
   }
 
   /// Only constructor, configuration is done through `options`.
@@ -632,6 +741,7 @@ impl JsRuntime {
       options.v8_platform.take(),
       cfg!(test),
       options.unsafe_expose_natives_and_gc(),
+      options.import_assertions_support.is_enabled(),
     );
     JsRuntime::new_inner(options, false)
   }
@@ -737,6 +847,7 @@ impl JsRuntime {
       has_inspector: false.into(),
       cppgc_template: None.into(),
       callsite_template: None.into(),
+      import_assertions_support: options.import_assertions_support,
     });
 
     // ...now we're moving on to ops; set them up, create `OpCtx` for each op
@@ -816,6 +927,13 @@ impl JsRuntime {
       external_refs_static,
     );
 
+    if state_rc.import_assertions_support.has_warning() {
+      isolate.add_message_listener_with_error_level(
+        isolate_message_listener,
+        MessageErrorLevel::ALL,
+      );
+    }
+
     // ...isolate is fully set up, we can forward its pointer to the ops to finish
     // their' setup...
     for op_ctx in op_ctxs.iter_mut() {
@@ -1956,6 +2074,7 @@ impl JsRuntimeForSnapshot {
       options.v8_platform.take(),
       true,
       options.unsafe_expose_natives_and_gc(),
+      options.import_assertions_support.is_enabled(),
     );
 
     let runtime = JsRuntime::new_inner(options, true)?;
diff --git a/core/runtime/mod.rs b/core/runtime/mod.rs
index 8bae616..c01bdc8 100644
--- a/core/runtime/mod.rs
+++ b/core/runtime/mod.rs
@@ -27,6 +27,8 @@ pub use jsruntime::CompiledWasmModuleStore;
 pub use jsruntime::CreateRealmOptions;
 pub use jsruntime::CrossIsolateStore;
 pub use jsruntime::ExtensionTranspiler;
+pub use jsruntime::ImportAssertionsSupport;
+pub use jsruntime::ImportAssertionsSupportCustomCallbackArgs;
 pub(crate) use jsruntime::InitMode;
 pub use jsruntime::JsRuntime;
 pub use jsruntime::JsRuntimeForSnapshot;
diff --git a/core/runtime/setup.rs b/core/runtime/setup.rs
index dd44e4a..15e0b00 100644
--- a/core/runtime/setup.rs
+++ b/core/runtime/setup.rs
@@ -15,6 +15,7 @@ fn v8_init(
   v8_platform: Option<v8::SharedRef<v8::Platform>>,
   snapshot: bool,
   expose_natives: bool,
+  import_assertions_enabled: bool,
 ) {
   #[cfg(feature = "include_icu_data")]
   {
@@ -35,16 +36,36 @@ fn v8_init(
   } else {
     ""
   };
+  let import_assertions_flag = if import_assertions_enabled {
+    "--harmony-import-assertions"
+  } else {
+    "--no-harmony-import-assertions"
+  };
+  // TODO(bartlomieju): this is ridiculous, rewrite this
   #[allow(clippy::useless_format)]
-  let flags = match (snapshot, expose_natives) {
-    (false, false) => format!("{base_flags}"),
-    (true, false) => format!("{base_flags} {snapshot_flags} {lazy_flags}"),
-    (false, true) => format!("{base_flags} {expose_natives_flags}"),
-    (true, true) => {
+  let flags = match (snapshot, expose_natives, import_assertions_enabled) {
+    (false, false, false) => format!("{base_flags}"),
+    (false, false, true) => format!("{base_flags} {import_assertions_flag}"),
+    (true, false, false) => {
+      format!("{base_flags} {snapshot_flags} {lazy_flags}")
+    }
+    (true, false, true) => format!(
+      "{base_flags} {snapshot_flags} {lazy_flags} {import_assertions_flag}"
+    ),
+    (false, true, false) => format!("{base_flags} {expose_natives_flags}"),
+    (false, true, true) => {
+      format!("{base_flags} {expose_natives_flags} {import_assertions_flag}")
+    }
+    (true, true, false) => {
       format!(
         "{base_flags} {snapshot_flags} {lazy_flags} {expose_natives_flags}"
       )
     }
+    (true, true, true) => {
+      format!(
+        "{base_flags} {snapshot_flags} {lazy_flags} {expose_natives_flags} {import_assertions_flag}"
+      )
+    }
   };
   v8::V8::set_flags_from_string(&flags);
 
@@ -67,6 +88,7 @@ pub fn init_v8(
   v8_platform: Option<v8::SharedRef<v8::Platform>>,
   snapshot: bool,
   expose_natives: bool,
+  import_assertions_enabled: bool,
 ) {
   static DENO_INIT: Once = Once::new();
   static DENO_SNAPSHOT: AtomicBool = AtomicBool::new(false);
@@ -79,7 +101,14 @@ pub fn init_v8(
     DENO_SNAPSHOT.store(snapshot, Ordering::SeqCst);
   }
 
-  DENO_INIT.call_once(move || v8_init(v8_platform, snapshot, expose_natives));
+  DENO_INIT.call_once(move || {
+    v8_init(
+      v8_platform,
+      snapshot,
+      expose_natives,
+      import_assertions_enabled,
+    )
+  });
 }
 
 fn create_cpp_heap() -> v8::UniqueRef<v8::cppgc::Heap> {


Test patch:
diff --git a/testing/checkin/runner/mod.rs b/testing/checkin/runner/mod.rs
index 98dc529..37c5ae2 100644
--- a/testing/checkin/runner/mod.rs
+++ b/testing/checkin/runner/mod.rs
@@ -11,6 +11,7 @@ use deno_core::CrossIsolateStore;
 use deno_core::CustomModuleEvaluationKind;
 use deno_core::Extension;
 use deno_core::FastString;
+use deno_core::ImportAssertionsSupport;
 use deno_core::JsRuntime;
 use deno_core::ModuleSourceCode;
 use deno_core::RuntimeOptions;
@@ -127,6 +128,7 @@ pub fn create_runtime_from_snapshot(
     shared_array_buffer_store: Some(CrossIsolateStore::default()),
     custom_module_evaluation_cb: Some(Box::new(custom_module_evaluation_cb)),
     inspector,
+    import_assertions_support: ImportAssertionsSupport::Warning,
     ..Default::default()
   });
 
diff --git a/testing/integration/error_callsite/error_callsite.out b/testing/integration/error_callsite/error_callsite.out
new file mode 100644
index 0000000..183e4a1
--- /dev/null
+++ b/testing/integration/error_callsite/error_callsite.out
@@ -0,0 +1,32 @@
+{
+  "getTypeName": null,
+  "getFunctionName": "Foo",
+  "getMethodName": null,
+  "getFileName": "test:///integration/error_callsite/error_callsite.ts",
+  "getLineNumber": 36,
+  "getColumnNumber": 5,
+  "isToplevel": false,
+  "isEval": false,
+  "isNative": false,
+  "isConstructor": true,
+  "isAsync": false,
+  "isPromiseAll": false,
+  "getPromiseIndex": null
+}
+new Foo (test:///integration/error_callsite/error_callsite.ts:36:5)
+{
+  "getTypeName": null,
+  "getFunctionName": null,
+  "getMethodName": null,
+  "getFileName": "test:///integration/error_callsite/error_callsite.ts",
+  "getLineNumber": 39,
+  "getColumnNumber": 1,
+  "isToplevel": true,
+  "isEval": false,
+  "isNative": false,
+  "isConstructor": false,
+  "isAsync": false,
+  "isPromiseAll": false,
+  "getPromiseIndex": null
+}
+test:///integration/error_callsite/error_callsite.ts:39:1
diff --git a/testing/integration/error_callsite/error_callsite.ts b/testing/integration/error_callsite/error_callsite.ts
new file mode 100644
index 0000000..9477f86
--- /dev/null
+++ b/testing/integration/error_callsite/error_callsite.ts
@@ -0,0 +1,38 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file no-explicit-any
+function toObj(callsite: any) {
+  const keys = [
+    "getThis",
+    "getTypeName",
+    "getFunction",
+    "getFunctionName",
+    "getMethodName",
+    "getFileName",
+    "getLineNumber",
+    "getColumnNumber",
+    "getEvalOrigin",
+    "isToplevel",
+    "isEval",
+    "isNative",
+    "isConstructor",
+    "isAsync",
+    "isPromiseAll",
+    "getPromiseIndex",
+  ];
+  return Object.fromEntries(keys.map((key) => [key, callsite[key]()]));
+}
+(Error as any).prepareStackTrace = function (_: any, callsites: any) {
+  callsites.forEach((callsite: any) => {
+    console.log(toObj(callsite));
+    console.log(callsite.toString());
+  });
+  return callsites;
+};
+
+class Foo {
+  constructor() {
+    new Error().stack;
+  }
+}
+
+new Foo();
diff --git a/testing/integration/error_non_existent_eval_source/error_non_existent_eval_source.out b/testing/integration/error_non_existent_eval_source/error_non_existent_eval_source.out
new file mode 100644
index 0000000..c11aac7
--- /dev/null
+++ b/testing/integration/error_non_existent_eval_source/error_non_existent_eval_source.out
@@ -0,0 +1,3 @@
+[ERR] ReferenceError: doesNotExist is not defined
+[ERR]     at Object.eval (empty.eval, <anonymous>:3:1)
+[ERR]     at test:///integration/error_non_existent_eval_source/error_non_existent_eval_source.ts:11:6
diff --git a/testing/integration/error_non_existent_eval_source/error_non_existent_eval_source.ts b/testing/integration/error_non_existent_eval_source/error_non_existent_eval_source.ts
new file mode 100644
index 0000000..07b30ab
--- /dev/null
+++ b/testing/integration/error_non_existent_eval_source/error_non_existent_eval_source.ts
@@ -0,0 +1,11 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+const AsyncFunction = Object.getPrototypeOf(async function () {
+  // empty
+}).constructor;
+
+const func = new AsyncFunction(
+  `return doesNotExist();
+    //# sourceURL=empty.eval`,
+);
+
+func.call({});
diff --git a/testing/lib.rs b/testing/lib.rs
index 6314e80..e97df85 100644
--- a/testing/lib.rs
+++ b/testing/lib.rs
@@ -57,6 +57,8 @@ integration_test!(
   dyn_import_op,
   dyn_import_no_hang,
   error_async_stack,
+  error_callsite,
+  error_non_existent_eval_source,
   error_rejection_catch,
   error_rejection_order,
   error_eval_stack,