sourcemap/hermes.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
use crate::decoder::{decode, decode_regular, decode_slice};
use crate::encoder::{encode, Encodable};
use crate::errors::{Error, Result};
use crate::jsontypes::{FacebookScopeMapping, FacebookSources, RawSourceMap};
use crate::types::{DecodedMap, RewriteOptions, SourceMap};
use crate::utils::greatest_lower_bound;
use crate::vlq::parse_vlq_segment_into;
use crate::Token;
use std::io::{Read, Write};
use std::ops::{Deref, DerefMut};
/// These are starting locations of scopes.
/// The `name_index` represents the index into the `HermesFunctionMap.names` vec,
/// which represents the function names/scopes.
#[derive(Debug, Clone)]
pub struct HermesScopeOffset {
line: u32,
column: u32,
name_index: u32,
}
#[derive(Debug, Clone)]
pub struct HermesFunctionMap {
names: Vec<String>,
mappings: Vec<HermesScopeOffset>,
}
/// Represents a `react-native`-style SourceMap, which has additional scope
/// information embedded.
#[derive(Debug, Clone)]
pub struct SourceMapHermes {
pub(crate) sm: SourceMap,
// There should be one `HermesFunctionMap` per each `sources` entry in the main SourceMap.
function_maps: Vec<Option<HermesFunctionMap>>,
// XXX: right now, I am too lazy to actually serialize the above `function_maps`
// back into json types, so just keep the original json. Might be a bit inefficient, but meh.
raw_facebook_sources: FacebookSources,
}
impl Deref for SourceMapHermes {
type Target = SourceMap;
fn deref(&self) -> &Self::Target {
&self.sm
}
}
impl DerefMut for SourceMapHermes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.sm
}
}
impl Encodable for SourceMapHermes {
fn as_raw_sourcemap(&self) -> RawSourceMap {
// TODO: need to serialize the `HermesFunctionMap` mappings
let mut rsm = self.sm.as_raw_sourcemap();
rsm.x_facebook_sources = self.raw_facebook_sources.clone();
rsm
}
}
impl SourceMapHermes {
/// Creates a sourcemap from a reader over a JSON stream in UTF-8
/// format.
///
/// See [`SourceMap::from_reader`](struct.SourceMap.html#method.from_reader)
pub fn from_reader<R: Read>(rdr: R) -> Result<Self> {
match decode(rdr)? {
DecodedMap::Hermes(sm) => Ok(sm),
_ => Err(Error::IncompatibleSourceMap),
}
}
/// Creates a sourcemap from a reader over a JSON byte slice in UTF-8
/// format.
///
/// See [`SourceMap::from_slice`](struct.SourceMap.html#method.from_slice)
pub fn from_slice(slice: &[u8]) -> Result<Self> {
match decode_slice(slice)? {
DecodedMap::Hermes(sm) => Ok(sm),
_ => Err(Error::IncompatibleSourceMap),
}
}
/// Writes a sourcemap into a writer.
///
/// See [`SourceMap::to_writer`](struct.SourceMap.html#method.to_writer)
pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
encode(self, w)
}
/// Given a bytecode offset, this will find the enclosing scopes function
/// name.
pub fn get_original_function_name(&self, bytecode_offset: u32) -> Option<&str> {
let token = self.sm.lookup_token(0, bytecode_offset)?;
self.get_scope_for_token(token)
}
/// Resolves the name of the enclosing function for the given [`Token`].
pub fn get_scope_for_token(&self, token: Token) -> Option<&str> {
let function_map = self
.function_maps
.get(token.get_src_id() as usize)?
.as_ref()?;
// Find the closest mapping, just like here:
// https://github.com/facebook/metro/blob/63b523eb20e7bdf62018aeaf195bb5a3a1a67f36/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L204-L231
let mapping = greatest_lower_bound(&function_map.mappings, &token.get_src(), |o| {
(o.line, o.column)
})?;
function_map
.names
.get(mapping.name_index as usize)
.map(|n| n.as_str())
}
/// This rewrites the sourcemap according to the provided rewrite
/// options.
///
/// See [`SourceMap::rewrite`](struct.SourceMap.html#method.rewrite)
pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result<Self> {
let Self {
sm,
mut function_maps,
mut raw_facebook_sources,
} = self;
let (sm, mapping) = sm.rewrite_with_mapping(options)?;
if function_maps.len() >= mapping.len() {
function_maps = mapping
.iter()
.map(|idx| function_maps[*idx as usize].take())
.collect();
raw_facebook_sources = raw_facebook_sources.map(|mut sources| {
mapping
.into_iter()
.map(|idx| sources[idx as usize].take())
.collect()
});
}
Ok(Self {
sm,
function_maps,
raw_facebook_sources,
})
}
}
pub fn decode_hermes(mut rsm: RawSourceMap) -> Result<SourceMapHermes> {
let x_facebook_sources = rsm
.x_facebook_sources
.take()
.ok_or(Error::IncompatibleSourceMap)?;
// This is basically the logic from here:
// https://github.com/facebook/metro/blob/63b523eb20e7bdf62018aeaf195bb5a3a1a67f36/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L182-L202
let mut nums = Vec::with_capacity(4);
let function_maps = x_facebook_sources
.iter()
.map(|v| {
let FacebookScopeMapping {
names,
mappings: raw_mappings,
} = v.as_ref()?.iter().next()?;
let mut mappings = vec![];
let mut line = 1;
let mut name_index = 0;
for line_mapping in raw_mappings.split(';') {
if line_mapping.is_empty() {
continue;
}
let mut column = 0;
for mapping in line_mapping.split(',') {
if mapping.is_empty() {
continue;
}
nums.clear();
parse_vlq_segment_into(mapping, &mut nums).ok()?;
let mut nums = nums.iter().copied();
column = (i64::from(column) + nums.next()?) as u32;
name_index = (i64::from(name_index) + nums.next().unwrap_or(0)) as u32;
line = (i64::from(line) + nums.next().unwrap_or(0)) as u32;
mappings.push(HermesScopeOffset {
column,
line,
name_index,
});
}
}
Some(HermesFunctionMap {
names: names.clone(),
mappings,
})
})
.collect();
let sm = decode_regular(rsm)?;
Ok(SourceMapHermes {
sm,
function_maps,
raw_facebook_sources: Some(x_facebook_sources),
})
}