rusty_v8/
module.rs

1use std::hash::Hash;
2use std::hash::Hasher;
3use std::mem::MaybeUninit;
4use std::ptr::null;
5
6use crate::support::int;
7use crate::support::MapFnFrom;
8use crate::support::MapFnTo;
9use crate::support::MaybeBool;
10use crate::support::ToCFn;
11use crate::support::UnitType;
12use crate::Context;
13use crate::FixedArray;
14use crate::HandleScope;
15use crate::Isolate;
16use crate::Local;
17use crate::Module;
18use crate::ModuleRequest;
19use crate::String;
20use crate::UnboundModuleScript;
21use crate::Value;
22
23/// Called during Module::instantiate_module. Provided with arguments:
24/// (context, specifier, import_assertions, referrer). Return None on error.
25///
26/// Note: this callback has an unusual signature due to ABI incompatibilities
27/// between Rust and C++. However end users can implement the callback as
28/// follows; it'll be automatically converted.
29///
30/// ```rust,ignore
31///   fn my_resolve_callback<'a>(
32///      context: v8::Local<'a, v8::Context>,
33///      specifier: v8::Local<'a, v8::String>,
34///      import_assertions: v8::Local<'a, v8::FixedArray>,
35///      referrer: v8::Local<'a, v8::Module>,
36///   ) -> Option<v8::Local<'a, v8::Module>> {
37///      // ...
38///      Some(resolved_module)
39///   }
40/// ```
41
42// System V AMD64 ABI: Local<Module> returned in a register.
43#[cfg(not(target_os = "windows"))]
44pub type ResolveModuleCallback<'a> = extern "C" fn(
45  Local<'a, Context>,
46  Local<'a, String>,
47  Local<'a, FixedArray>,
48  Local<'a, Module>,
49) -> *const Module;
50
51// Windows x64 ABI: Local<Module> returned on the stack.
52#[cfg(target_os = "windows")]
53pub type ResolveModuleCallback<'a> = extern "C" fn(
54  *mut *const Module,
55  Local<'a, Context>,
56  Local<'a, String>,
57  Local<'a, FixedArray>,
58  Local<'a, Module>,
59) -> *mut *const Module;
60
61impl<'a, F> MapFnFrom<F> for ResolveModuleCallback<'a>
62where
63  F: UnitType
64    + Fn(
65      Local<'a, Context>,
66      Local<'a, String>,
67      Local<'a, FixedArray>,
68      Local<'a, Module>,
69    ) -> Option<Local<'a, Module>>,
70{
71  #[cfg(not(target_os = "windows"))]
72  fn mapping() -> Self {
73    let f = |context, specifier, import_assertions, referrer| {
74      (F::get())(context, specifier, import_assertions, referrer)
75        .map(|r| -> *const Module { &*r })
76        .unwrap_or(null())
77    };
78    f.to_c_fn()
79  }
80
81  #[cfg(target_os = "windows")]
82  fn mapping() -> Self {
83    let f = |ret_ptr, context, specifier, import_assertions, referrer| {
84      let r = (F::get())(context, specifier, import_assertions, referrer)
85        .map(|r| -> *const Module { &*r })
86        .unwrap_or(null());
87      unsafe { std::ptr::write(ret_ptr, r) }; // Write result to stack.
88      ret_ptr // Return stack pointer to the return value.
89    };
90    f.to_c_fn()
91  }
92}
93
94// System V AMD64 ABI: Local<Value> returned in a register.
95#[cfg(not(target_os = "windows"))]
96pub type SyntheticModuleEvaluationSteps<'a> =
97  extern "C" fn(Local<'a, Context>, Local<'a, Module>) -> *const Value;
98
99// Windows x64 ABI: Local<Value> returned on the stack.
100#[cfg(target_os = "windows")]
101pub type SyntheticModuleEvaluationSteps<'a> =
102  extern "C" fn(
103    *mut *const Value,
104    Local<'a, Context>,
105    Local<'a, Module>,
106  ) -> *mut *const Value;
107
108impl<'a, F> MapFnFrom<F> for SyntheticModuleEvaluationSteps<'a>
109where
110  F: UnitType
111    + Fn(Local<'a, Context>, Local<'a, Module>) -> Option<Local<'a, Value>>,
112{
113  #[cfg(not(target_os = "windows"))]
114  fn mapping() -> Self {
115    let f = |context, module| {
116      (F::get())(context, module)
117        .map(|r| -> *const Value { &*r })
118        .unwrap_or(null())
119    };
120    f.to_c_fn()
121  }
122
123  #[cfg(target_os = "windows")]
124  fn mapping() -> Self {
125    let f = |ret_ptr, context, module| {
126      let r = (F::get())(context, module)
127        .map(|r| -> *const Value { &*r })
128        .unwrap_or(null());
129      unsafe { std::ptr::write(ret_ptr, r) }; // Write result to stack.
130      ret_ptr // Return stack pointer to the return value.
131    };
132    f.to_c_fn()
133  }
134}
135
136extern "C" {
137  fn v8__Module__GetStatus(this: *const Module) -> ModuleStatus;
138  fn v8__Module__GetException(this: *const Module) -> *const Value;
139  fn v8__Module__GetModuleRequests(this: *const Module) -> *const FixedArray;
140  fn v8__Module__SourceOffsetToLocation(
141    this: *const Module,
142    offset: int,
143    out: *mut MaybeUninit<Location>,
144  ) -> Location;
145  fn v8__Module__GetModuleNamespace(this: *const Module) -> *const Value;
146  fn v8__Module__GetIdentityHash(this: *const Module) -> int;
147  fn v8__Module__ScriptId(this: *const Module) -> int;
148  fn v8__Module__InstantiateModule(
149    this: *const Module,
150    context: *const Context,
151    cb: ResolveModuleCallback,
152  ) -> MaybeBool;
153  fn v8__Module__Evaluate(
154    this: *const Module,
155    context: *const Context,
156  ) -> *const Value;
157  fn v8__Module__IsSourceTextModule(this: *const Module) -> bool;
158  fn v8__Module__IsSyntheticModule(this: *const Module) -> bool;
159  fn v8__Module__CreateSyntheticModule(
160    isolate: *const Isolate,
161    module_name: *const String,
162    export_names_len: usize,
163    export_names_raw: *const *const String,
164    evaluation_steps: SyntheticModuleEvaluationSteps,
165  ) -> *const Module;
166  fn v8__Module__SetSyntheticModuleExport(
167    this: *const Module,
168    isolate: *const Isolate,
169    export_name: *const String,
170    export_value: *const Value,
171  ) -> MaybeBool;
172  fn v8__Module__GetUnboundModuleScript(
173    this: *const Module,
174  ) -> *const UnboundModuleScript;
175  fn v8__Location__GetLineNumber(this: *const Location) -> int;
176  fn v8__Location__GetColumnNumber(this: *const Location) -> int;
177  fn v8__ModuleRequest__GetSpecifier(
178    this: *const ModuleRequest,
179  ) -> *const String;
180  fn v8__ModuleRequest__GetSourceOffset(this: *const ModuleRequest) -> int;
181  fn v8__ModuleRequest__GetImportAssertions(
182    this: *const ModuleRequest,
183  ) -> *const FixedArray;
184}
185
186/// A location in JavaScript source.
187#[repr(C)]
188#[derive(Debug)]
189pub struct Location([i32; 2]);
190
191impl Location {
192  pub fn get_line_number(&self) -> int {
193    unsafe { v8__Location__GetLineNumber(self) }
194  }
195
196  pub fn get_column_number(&self) -> int {
197    unsafe { v8__Location__GetColumnNumber(self) }
198  }
199}
200
201/// The different states a module can be in.
202///
203/// This corresponds to the states used in ECMAScript except that "evaluated"
204/// is split into kEvaluated and kErrored, indicating success and failure,
205/// respectively.
206#[derive(Debug, PartialEq)]
207#[repr(C)]
208pub enum ModuleStatus {
209  Uninstantiated,
210  Instantiating,
211  Instantiated,
212  Evaluating,
213  Evaluated,
214  Errored,
215}
216
217impl Module {
218  /// Returns the module's current status.
219  pub fn get_status(&self) -> ModuleStatus {
220    unsafe { v8__Module__GetStatus(self) }
221  }
222
223  /// For a module in kErrored status, this returns the corresponding exception.
224  pub fn get_exception(&self) -> Local<Value> {
225    // Note: the returned value is not actually stored in a HandleScope,
226    // therefore we don't need a scope object here.
227    unsafe { Local::from_raw(v8__Module__GetException(self)) }.unwrap()
228  }
229
230  /// Returns the ModuleRequests for this module.
231  pub fn get_module_requests(&self) -> Local<FixedArray> {
232    unsafe { Local::from_raw(v8__Module__GetModuleRequests(self)) }.unwrap()
233  }
234
235  /// For the given source text offset in this module, returns the corresponding
236  /// Location with line and column numbers.
237  pub fn source_offset_to_location(&self, offset: int) -> Location {
238    let mut out = MaybeUninit::<Location>::uninit();
239    unsafe {
240      v8__Module__SourceOffsetToLocation(self, offset, &mut out);
241      out.assume_init()
242    }
243  }
244
245  /// The `Module` specific equivalent of `Data::get_hash()`.
246  /// This function is kept around for testing purposes only.
247  #[doc(hidden)]
248  pub fn get_identity_hash(&self) -> int {
249    unsafe { v8__Module__GetIdentityHash(self) }
250  }
251
252  /// Returns the underlying script's id.
253  ///
254  /// The module must be a SourceTextModule and must not have an Errored status.
255  pub fn script_id(&self) -> Option<int> {
256    if !self.is_source_text_module() {
257      return None;
258    }
259    if self.get_status() == ModuleStatus::Errored {
260      return None;
261    }
262    Some(unsafe { v8__Module__ScriptId(self) })
263  }
264
265  /// Returns the namespace object of this module.
266  ///
267  /// The module's status must be at least kInstantiated.
268  pub fn get_module_namespace(&self) -> Local<Value> {
269    // Note: the returned value is not actually stored in a HandleScope,
270    // therefore we don't need a scope object here.
271    unsafe { Local::from_raw(v8__Module__GetModuleNamespace(self)).unwrap() }
272  }
273
274  /// Instantiates the module and its dependencies.
275  ///
276  /// Returns an empty Maybe<bool> if an exception occurred during
277  /// instantiation. (In the case where the callback throws an exception, that
278  /// exception is propagated.)
279  ///
280  /// NOTE: requires to set `--harmony-import-assertions` V8 flag.
281  #[must_use]
282  pub fn instantiate_module<'a>(
283    &self,
284    scope: &mut HandleScope,
285    callback: impl MapFnTo<ResolveModuleCallback<'a>>,
286  ) -> Option<bool> {
287    unsafe {
288      v8__Module__InstantiateModule(
289        self,
290        &*scope.get_current_context(),
291        callback.map_fn_to(),
292      )
293    }
294    .into()
295  }
296
297  /// Evaluates the module and its dependencies.
298  ///
299  /// If status is kInstantiated, run the module's code. On success, set status
300  /// to kEvaluated and return the completion value; on failure, set status to
301  /// kErrored and propagate the thrown exception (which is then also available
302  /// via |GetException|).
303  #[must_use]
304  pub fn evaluate<'s>(
305    &self,
306    scope: &mut HandleScope<'s>,
307  ) -> Option<Local<'s, Value>> {
308    unsafe {
309      scope
310        .cast_local(|sd| v8__Module__Evaluate(&*self, sd.get_current_context()))
311    }
312  }
313
314  /// Returns whether the module is a SourceTextModule.
315  pub fn is_source_text_module(&self) -> bool {
316    unsafe { v8__Module__IsSourceTextModule(&*self) }
317  }
318
319  /// Returns whether the module is a SyntheticModule.
320  pub fn is_synthetic_module(&self) -> bool {
321    unsafe { v8__Module__IsSyntheticModule(&*self) }
322  }
323
324  /// Creates a new SyntheticModule with the specified export names, where
325  /// evaluation_steps will be executed upon module evaluation.
326  /// export_names must not contain duplicates.
327  /// module_name is used solely for logging/debugging and doesn't affect module
328  /// behavior.
329  pub fn create_synthetic_module<'s, 'a>(
330    scope: &mut HandleScope<'s>,
331    module_name: Local<String>,
332    export_names: &[Local<String>],
333    evaluation_steps: impl MapFnTo<SyntheticModuleEvaluationSteps<'a>>,
334  ) -> Local<'s, Module> {
335    let export_names = Local::slice_into_raw(export_names);
336    let export_names_len = export_names.len();
337    let export_names = export_names.as_ptr();
338    unsafe {
339      scope
340        .cast_local(|sd| {
341          v8__Module__CreateSyntheticModule(
342            sd.get_isolate_ptr(),
343            &*module_name,
344            export_names_len,
345            export_names,
346            evaluation_steps.map_fn_to(),
347          )
348        })
349        .unwrap()
350    }
351  }
352
353  /// Set this module's exported value for the name export_name to the specified
354  /// export_value. This method must be called only on Modules created via
355  /// create_synthetic_module.  An error will be thrown if export_name is not one
356  /// of the export_names that were passed in that create_synthetic_module call.
357  /// Returns Some(true) on success, None if an error was thrown.
358  #[must_use]
359  pub fn set_synthetic_module_export(
360    &self,
361    scope: &mut HandleScope,
362    export_name: Local<String>,
363    export_value: Local<Value>,
364  ) -> Option<bool> {
365    unsafe {
366      v8__Module__SetSyntheticModuleExport(
367        &*self,
368        scope.get_isolate_ptr(),
369        &*export_name,
370        &*export_value,
371      )
372    }
373    .into()
374  }
375
376  pub fn get_unbound_module_script<'s>(
377    &self,
378    scope: &mut HandleScope<'s>,
379  ) -> Local<'s, UnboundModuleScript> {
380    unsafe {
381      scope
382        .cast_local(|_| v8__Module__GetUnboundModuleScript(self))
383        .unwrap()
384    }
385  }
386}
387
388impl Hash for Module {
389  fn hash<H: Hasher>(&self, state: &mut H) {
390    state.write_i32(self.get_identity_hash());
391  }
392}
393
394impl ModuleRequest {
395  /// Returns the module specifier for this ModuleRequest.
396  pub fn get_specifier(&self) -> Local<String> {
397    unsafe { Local::from_raw(v8__ModuleRequest__GetSpecifier(self)) }.unwrap()
398  }
399
400  /// Returns the source code offset of this module request.
401  /// Use Module::source_offset_to_location to convert this to line/column numbers.
402  pub fn get_source_offset(&self) -> int {
403    unsafe { v8__ModuleRequest__GetSourceOffset(self) }
404  }
405
406  /// Contains the import assertions for this request in the form:
407  /// [key1, value1, source_offset1, key2, value2, source_offset2, ...].
408  /// The keys and values are of type v8::String, and the source offsets are of
409  /// type Int32. Use Module::source_offset_to_location to convert the source
410  /// offsets to Locations with line/column numbers.
411  ///
412  /// All assertions present in the module request will be supplied in this
413  /// list, regardless of whether they are supported by the host. Per
414  /// https://tc39.es/proposal-import-assertions/#sec-hostgetsupportedimportassertions,
415  /// hosts are expected to ignore assertions that they do not support (as
416  /// opposed to, for example, triggering an error if an unsupported assertion is
417  /// present).
418  pub fn get_import_assertions(&self) -> Local<FixedArray> {
419    unsafe { Local::from_raw(v8__ModuleRequest__GetImportAssertions(self)) }
420      .unwrap()
421  }
422}