source_map_mappings_wasm_api/
lib.rs

1//! The public JS API to the `source-map-mappings` crate.
2//!
3//! ## Usage
4//!
5//! 1. Instantiate the WebAssembly module, supplying a JS implementation of
6//! `mapping_callback`.
7//!
8//! 2. Allocate space for the mappings string with `allocate_mappings`.
9//!
10//! 3. Initialize the mappings string by copying the JS `String`'s data into it.
11//!
12//! 4. Parse the mappings with `parse_mappings`. Handle errors, if any.
13//!
14//! 5. Query the resulting `Mappings` structure as needed with
15//! `by_generated_location`, `by_original_location`, `compute_column_spans`,
16//! `original_location_for`, `generated_location_for`, and
17//! `all_generated_locations_for` as needed.
18//!
19//! 6. When finished with `Mappings` structure, dispose of it with
20//! `free_mappings`.
21
22// NB: every exported function must be `#[no_mangle]` and `pub extern "C"`.
23
24#![deny(missing_docs)]
25
26extern crate source_map_mappings;
27
28use source_map_mappings::{Bias, Error, Mapping, Mappings};
29use std::mem;
30use std::ptr;
31use std::process;
32use std::slice;
33
34#[cfg(feature = "profiling")]
35mod observer {
36    use source_map_mappings;
37
38    macro_rules! define_raii_observer {
39        ( $name:ident , $ctor:ident , $dtor:ident ) => {
40            #[derive(Debug)]
41            pub struct $name;
42
43            impl Default for $name {
44                #[inline]
45                fn default() -> $name {
46                    extern "C" {
47                        fn $ctor();
48                    }
49                    unsafe {
50                        $ctor();
51                    }
52                    $name
53                }
54            }
55
56            impl Drop for $name {
57                #[inline]
58                fn drop(&mut self) {
59                    extern "C" {
60                        fn $dtor();
61                    }
62                    unsafe {
63                        $dtor();
64                    }
65                }
66            }
67        }
68    }
69
70    define_raii_observer!(ParseMappings, start_parse_mappings, end_parse_mappings);
71    define_raii_observer!(
72        SortByOriginalLocation,
73        start_sort_by_original_location,
74        end_sort_by_original_location
75    );
76    define_raii_observer!(
77        SortByGeneratedLocation,
78        start_sort_by_generated_location,
79        end_sort_by_generated_location
80    );
81    define_raii_observer!(
82        ComputeColumnSpans,
83        start_compute_column_spans,
84        end_compute_column_spans
85    );
86    define_raii_observer!(
87        OriginalLocationFor,
88        start_original_location_for,
89        end_original_location_for
90    );
91    define_raii_observer!(
92        GeneratedLocationFor,
93        start_generated_location_for,
94        end_generated_location_for
95    );
96    define_raii_observer!(
97        AllGeneratedLocationsFor,
98        start_all_generated_locations_for,
99        end_all_generated_locations_for
100    );
101
102    #[derive(Debug, Default)]
103    pub struct Observer;
104
105    impl source_map_mappings::Observer for Observer {
106        type ParseMappings = ParseMappings;
107        type SortByOriginalLocation = SortByOriginalLocation;
108        type SortByGeneratedLocation = SortByGeneratedLocation;
109        type ComputeColumnSpans = ComputeColumnSpans;
110        type OriginalLocationFor = OriginalLocationFor;
111        type GeneratedLocationFor = GeneratedLocationFor;
112        type AllGeneratedLocationsFor = AllGeneratedLocationsFor;
113    }
114}
115
116#[cfg(not(feature = "profiling"))]
117mod observer {
118    pub type Observer = ();
119}
120
121use observer::Observer;
122
123static mut LAST_ERROR: Option<Error> = None;
124
125/// Get the last error's error code, or 0 if there was none.
126///
127/// See `source_map_mappings::Error` for the error code definitions.
128#[no_mangle]
129pub extern "C" fn get_last_error() -> u32 {
130    unsafe {
131        match LAST_ERROR {
132            None => 0,
133            Some(e) => e as u32,
134        }
135    }
136}
137
138#[inline]
139fn assert_pointer_is_word_aligned(p: *mut u8) {
140    debug_assert_eq!(p as usize & (mem::size_of::<usize>() - 1), 0);
141}
142
143/// Allocate space for a mappings string of the given size (in bytes).
144///
145/// It is the JS callers responsibility to initialize the resulting buffer by
146/// copying the JS `String` holding the source map's "mappings" into it (encoded
147/// in ascii).
148#[no_mangle]
149pub extern "C" fn allocate_mappings(size: usize) -> *mut u8 {
150    // Make sure that we don't lose any bytes from size in the remainder.
151    let size_in_units_of_usize = (size + mem::size_of::<usize>() - 1) / mem::size_of::<usize>();
152
153    // Make room for two additional `usize`s: we'll stuff capacity and length in
154    // there.
155    let mut vec: Vec<usize> = Vec::with_capacity(size_in_units_of_usize + 2);
156
157    // And do the stuffing.
158    let capacity = vec.capacity();
159    vec.push(capacity);
160    vec.push(size);
161
162    // Leak the vec's elements and get a pointer to them.
163    let ptr = vec.as_mut_ptr();
164    debug_assert!(!ptr.is_null());
165    mem::forget(vec);
166
167    // Advance the pointer past our stuffed data and return it to JS, so that JS
168    // can write the mappings string into it.
169    let ptr = ptr.wrapping_offset(2) as *mut u8;
170    assert_pointer_is_word_aligned(ptr);
171    ptr
172}
173
174#[inline]
175fn constrain<'a, T>(_scope: &'a (), reference: &'a T) -> &'a T
176where
177    T: ?Sized,
178{
179    reference
180}
181
182/// Parse the given initialized mappings string into a `Mappings` structure.
183///
184/// Returns `NULL` on failure, or a pointer to the parsed `Mappings` structure
185/// on success.
186///
187/// In the case of failure, the error can be retrieved with `get_last_error`.
188///
189/// In the case of success, the caller takes ownership of the result, and must
190/// call `free_mappings` to destroy it when finished.
191///
192/// In both the success or failure cases, the caller gives up ownership of the
193/// input mappings string and must not use it again.
194#[no_mangle]
195pub extern "C" fn parse_mappings(mappings: *mut u8) -> *mut Mappings<Observer> {
196    assert_pointer_is_word_aligned(mappings);
197    let mappings = mappings as *mut usize;
198
199    // Unstuff the data we put just before the pointer to the mappings
200    // string.
201    let capacity_ptr = mappings.wrapping_offset(-2);
202    debug_assert!(!capacity_ptr.is_null());
203    let capacity = unsafe { *capacity_ptr };
204
205    let size_ptr = mappings.wrapping_offset(-1);
206    debug_assert!(!size_ptr.is_null());
207    let size = unsafe { *size_ptr };
208
209    // Construct the input slice from the pointer and parse the mappings.
210    let result = unsafe {
211        let input = slice::from_raw_parts(mappings as *const u8, size);
212        let this_scope = ();
213        let input = constrain(&this_scope, input);
214        source_map_mappings::parse_mappings(input)
215    };
216
217    // Deallocate the mappings string and its two prefix words.
218    let size_in_usizes = (size + mem::size_of::<usize>() - 1) / mem::size_of::<usize>();
219    unsafe {
220        Vec::<usize>::from_raw_parts(capacity_ptr, size_in_usizes + 2, capacity);
221    }
222
223    // Return the result, saving any errors on the side for later inspection by
224    // JS if required.
225    match result {
226        Ok(mappings) => Box::into_raw(Box::new(mappings)),
227        Err(e) => {
228            unsafe {
229                LAST_ERROR = Some(e);
230            }
231            ptr::null_mut()
232        }
233    }
234}
235
236/// Destroy the given `Mappings` structure.
237///
238/// The caller gives up ownership of the mappings and must not use them again.
239#[no_mangle]
240pub extern "C" fn free_mappings(mappings: *mut Mappings<Observer>) {
241    unsafe {
242        Box::from_raw(mappings);
243    }
244}
245
246#[inline]
247unsafe fn mappings_mut<'a>(
248    _scope: &'a (),
249    mappings: *mut Mappings<Observer>,
250) -> &'a mut Mappings<Observer> {
251    mappings.as_mut().unwrap()
252}
253
254extern "C" {
255    fn mapping_callback(
256        // These two parameters are always valid.
257        generated_line: u32,
258        generated_column: u32,
259
260        // The `last_generated_column` parameter is only valid if
261        // `has_last_generated_column` is `true`.
262        has_last_generated_column: bool,
263        last_generated_column: u32,
264
265        // The `source`, `original_line`, and `original_column` parameters are
266        // only valid if `has_original` is `true`.
267        has_original: bool,
268        source: u32,
269        original_line: u32,
270        original_column: u32,
271
272        // The `name` parameter is only valid if `has_name` is `true`.
273        has_name: bool,
274        name: u32,
275    );
276}
277
278#[inline]
279unsafe fn invoke_mapping_callback(mapping: &Mapping) {
280    let generated_line = mapping.generated_line;
281    let generated_column = mapping.generated_column;
282
283    let (has_last_generated_column, last_generated_column) =
284        if let Some(last_generated_column) = mapping.last_generated_column {
285            (true, last_generated_column)
286        } else {
287            (false, 0)
288        };
289
290    let (has_original, source, original_line, original_column, has_name, name) =
291        if let Some(original) = mapping.original.as_ref() {
292            let (has_name, name) = if let Some(name) = original.name {
293                (true, name)
294            } else {
295                (false, 0)
296            };
297
298            (
299                true,
300                original.source,
301                original.original_line,
302                original.original_column,
303                has_name,
304                name,
305            )
306        } else {
307            (false, 0, 0, 0, false, 0)
308        };
309
310    mapping_callback(
311        generated_line,
312        generated_column,
313        has_last_generated_column,
314        last_generated_column,
315        has_original,
316        source,
317        original_line,
318        original_column,
319        has_name,
320        name,
321    );
322}
323
324/// Invoke the `mapping_callback` on each mapping in the given `Mappings`
325/// structure, in order of generated location.
326#[no_mangle]
327pub extern "C" fn by_generated_location(mappings: *mut Mappings<Observer>) {
328    let this_scope = ();
329    let mappings = unsafe { mappings_mut(&this_scope, mappings) };
330
331    mappings
332        .by_generated_location()
333        .iter()
334        .for_each(|m| unsafe {
335            invoke_mapping_callback(m);
336        });
337}
338
339/// Compute column spans for the given mappings.
340#[no_mangle]
341pub extern "C" fn compute_column_spans(mappings: *mut Mappings<Observer>) {
342    let this_scope = ();
343    let mappings = unsafe { mappings_mut(&this_scope, mappings) };
344
345    mappings.compute_column_spans();
346}
347
348/// Invoke the `mapping_callback` on each mapping in the given `Mappings`
349/// structure that has original location information, in order of original
350/// location.
351#[no_mangle]
352pub extern "C" fn by_original_location(mappings: *mut Mappings<Observer>) {
353    let this_scope = ();
354    let mappings = unsafe { mappings_mut(&this_scope, mappings) };
355
356    mappings.by_original_location().for_each(|m| unsafe {
357        invoke_mapping_callback(m);
358    });
359}
360
361#[inline]
362fn u32_to_bias(bias: u32) -> Bias {
363    match bias {
364        1 => Bias::GreatestLowerBound,
365        2 => Bias::LeastUpperBound,
366        otherwise => if cfg!(debug_assertions) {
367            panic!(
368                "Invalid `Bias = {}`; must be `Bias::GreatestLowerBound = {}` or \
369                 `Bias::LeastUpperBound = {}`",
370                otherwise,
371                Bias::GreatestLowerBound as u32,
372                Bias::LeastUpperBound as u32,
373            )
374        } else {
375            process::abort()
376        },
377    }
378}
379
380/// Find the mapping for the given generated location, if any exists.
381///
382/// If a mapping is found, the `mapping_callback` is invoked with it
383/// once. Otherwise, the `mapping_callback` is not invoked at all.
384#[no_mangle]
385pub extern "C" fn original_location_for(
386    mappings: *mut Mappings<Observer>,
387    generated_line: u32,
388    generated_column: u32,
389    bias: u32,
390) {
391    let this_scope = ();
392    let mappings = unsafe { mappings_mut(&this_scope, mappings) };
393    let bias = u32_to_bias(bias);
394
395    if let Some(m) = mappings.original_location_for(generated_line, generated_column, bias) {
396        unsafe {
397            invoke_mapping_callback(m);
398        }
399    }
400}
401
402/// Find the mapping for the given original location, if any exists.
403///
404/// If a mapping is found, the `mapping_callback` is invoked with it
405/// once. Otherwise, the `mapping_callback` is not invoked at all.
406#[no_mangle]
407pub extern "C" fn generated_location_for(
408    mappings: *mut Mappings<Observer>,
409    source: u32,
410    original_line: u32,
411    original_column: u32,
412    bias: u32,
413) {
414    let this_scope = ();
415    let mappings = unsafe { mappings_mut(&this_scope, mappings) };
416    let bias = u32_to_bias(bias);
417
418    if let Some(m) = mappings.generated_location_for(source, original_line, original_column, bias) {
419        unsafe {
420            invoke_mapping_callback(m);
421        }
422    }
423}
424
425/// Find all mappings for the given original location, and invoke the
426/// `mapping_callback` on each of them.
427///
428/// If `has_original_column` is `true`, then the `mapping_callback` is only
429/// invoked with mappings with matching source and original line **and**
430/// original column is equal to `original_column`. If `has_original_column` is
431/// `false`, then the `original_column` argument is ignored, and the
432/// `mapping_callback` is invoked on all mappings with matching source and
433/// original line.
434#[no_mangle]
435pub extern "C" fn all_generated_locations_for(
436    mappings: *mut Mappings<Observer>,
437    source: u32,
438    original_line: u32,
439    has_original_column: bool,
440    original_column: u32,
441) {
442    let this_scope = ();
443    let mappings = unsafe { mappings_mut(&this_scope, mappings) };
444
445    let original_column = if has_original_column {
446        Some(original_column)
447    } else {
448        None
449    };
450
451    for m in mappings.all_generated_locations_for(source, original_line, original_column) {
452        unsafe {
453            invoke_mapping_callback(m);
454        }
455    }
456}