Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,300 @@
#![cfg_attr(not(any(unix, windows, target_os = "redox")), allow(unused_imports))]
use std::collections::HashMap;
use std::convert::AsRef;
use std::env;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use debugid::DebugId;
use url::Url;
use crate::errors::Result;
use crate::types::{RawToken, SourceMap, Token};
/// Helper for sourcemap generation
///
/// This helper exists because generating and modifying `SourceMap`
/// objects is generally not very comfortable. As a general aid this
/// type can help.
pub struct SourceMapBuilder {
file: Option<String>,
name_map: HashMap<String, u32>,
names: Vec<String>,
tokens: Vec<RawToken>,
source_map: HashMap<String, u32>,
source_root: Option<String>,
sources: Vec<String>,
source_contents: Vec<Option<String>>,
sources_mapping: Vec<u32>,
debug_id: Option<DebugId>,
}
#[cfg(any(unix, windows, target_os = "redox"))]
fn resolve_local_reference(base: &Url, reference: &str) -> Option<PathBuf> {
let url = match base.join(reference) {
Ok(url) => {
if url.scheme() != "file" {
return None;
}
url
}
Err(_) => {
return None;
}
};
url.to_file_path().ok()
}
impl SourceMapBuilder {
/// Creates a new source map builder and sets the file.
pub fn new(file: Option<&str>) -> SourceMapBuilder {
SourceMapBuilder {
file: file.map(str::to_owned),
name_map: HashMap::new(),
names: vec![],
tokens: vec![],
source_map: HashMap::new(),
source_root: None,
sources: vec![],
source_contents: vec![],
sources_mapping: vec![],
debug_id: None,
}
}
/// Sets the debug id for the sourcemap (optional)
pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
self.debug_id = debug_id;
}
/// Sets the file for the sourcemap (optional)
pub fn set_file<T: Into<String>>(&mut self, value: Option<T>) {
self.file = value.map(Into::into);
}
/// Returns the currently set file.
pub fn get_file(&self) -> Option<&str> {
self.file.as_deref()
}
/// Sets a new value for the source_root.
pub fn set_source_root<T: Into<String>>(&mut self, value: Option<T>) {
self.source_root = value.map(Into::into);
}
/// Returns the embedded source_root in case there is one.
pub fn get_source_root(&self) -> Option<&str> {
self.source_root.as_deref()
}
/// Registers a new source with the builder and returns the source ID.
pub fn add_source(&mut self, src: &str) -> u32 {
self.add_source_with_id(src, !0)
}
fn add_source_with_id(&mut self, src: &str, old_id: u32) -> u32 {
let count = self.sources.len() as u32;
let id = *self.source_map.entry(src.into()).or_insert(count);
if id == count {
self.sources.push(src.into());
self.sources_mapping.push(old_id);
}
id
}
/// Changes the source name for an already set source.
pub fn set_source(&mut self, src_id: u32, src: &str) {
assert!(src_id != !0, "Cannot set sources for tombstone source id");
self.sources[src_id as usize] = src.to_string();
}
/// Looks up a source name for an ID.
pub fn get_source(&self, src_id: u32) -> Option<&str> {
self.sources.get(src_id as usize).map(|x| &x[..])
}
/// Sets the source contents for an already existing source.
pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&str>) {
assert!(src_id != !0, "Cannot set sources for tombstone source id");
if self.sources.len() > self.source_contents.len() {
self.source_contents.resize(self.sources.len(), None);
}
self.source_contents[src_id as usize] = contents.map(str::to_owned);
}
/// Returns the current source contents for a source.
pub fn get_source_contents(&self, src_id: u32) -> Option<&str> {
self.source_contents
.get(src_id as usize)
.and_then(|x| x.as_ref().map(|x| &x[..]))
}
/// Checks if a given source ID has source contents available.
pub fn has_source_contents(&self, src_id: u32) -> bool {
self.get_source_contents(src_id).is_some()
}
/// Loads source contents from locally accessible files if referenced
/// accordingly. Returns the number of loaded source contents
#[cfg(any(unix, windows, target_os = "redox"))]
pub fn load_local_source_contents(&mut self, base_path: Option<&Path>) -> Result<usize> {
let mut abs_path = env::current_dir()?;
if let Some(path) = base_path {
abs_path.push(path);
}
let base_url = Url::from_directory_path(&abs_path).unwrap();
let mut to_read = vec![];
for (source, &src_id) in self.source_map.iter() {
if self.has_source_contents(src_id) {
continue;
}
if let Some(path) = resolve_local_reference(&base_url, source) {
to_read.push((src_id, path));
}
}
let rv = to_read.len();
for (src_id, path) in to_read {
if let Ok(mut f) = fs::File::open(path) {
let mut contents = String::new();
if f.read_to_string(&mut contents).is_ok() {
self.set_source_contents(src_id, Some(&contents));
}
}
}
Ok(rv)
}
/// Registers a name with the builder and returns the name ID.
pub fn add_name(&mut self, name: &str) -> u32 {
let count = self.names.len() as u32;
let id = *self.name_map.entry(name.into()).or_insert(count);
if id == count {
self.names.push(name.into());
}
id
}
/// Adds a new mapping to the builder.
pub fn add(
&mut self,
dst_line: u32,
dst_col: u32,
src_line: u32,
src_col: u32,
source: Option<&str>,
name: Option<&str>,
) -> RawToken {
self.add_with_id(dst_line, dst_col, src_line, src_col, source, !0, name)
}
#[allow(clippy::too_many_arguments)]
fn add_with_id(
&mut self,
dst_line: u32,
dst_col: u32,
src_line: u32,
src_col: u32,
source: Option<&str>,
source_id: u32,
name: Option<&str>,
) -> RawToken {
let src_id = match source {
Some(source) => self.add_source_with_id(source, source_id),
None => !0,
};
let name_id = match name {
Some(name) => self.add_name(name),
None => !0,
};
let raw = RawToken {
dst_line,
dst_col,
src_line,
src_col,
src_id,
name_id,
};
self.tokens.push(raw);
raw
}
/// Adds a new mapping to the builder.
pub fn add_raw(
&mut self,
dst_line: u32,
dst_col: u32,
src_line: u32,
src_col: u32,
source: Option<u32>,
name: Option<u32>,
) -> RawToken {
let src_id = source.unwrap_or(!0);
let name_id = name.unwrap_or(!0);
let raw = RawToken {
dst_line,
dst_col,
src_line,
src_col,
src_id,
name_id,
};
self.tokens.push(raw);
raw
}
/// Shortcut for adding a new mapping based of an already existing token,
/// optionally removing the name.
pub fn add_token(&mut self, token: &Token<'_>, with_name: bool) -> RawToken {
let name = if with_name { token.get_name() } else { None };
self.add_with_id(
token.get_dst_line(),
token.get_dst_col(),
token.get_src_line(),
token.get_src_col(),
token.get_source(),
token.get_src_id(),
name,
)
}
/// Strips common prefixes from the sources in the builder
pub fn strip_prefixes<S: AsRef<str>>(&mut self, prefixes: &[S]) {
for source in self.sources.iter_mut() {
for prefix in prefixes {
let mut prefix = prefix.as_ref().to_string();
if !prefix.ends_with('/') {
prefix.push('/');
}
if source.starts_with(&prefix) {
*source = source[prefix.len()..].to_string();
break;
}
}
}
}
pub(crate) fn take_mapping(&mut self) -> Vec<u32> {
std::mem::take(&mut self.sources_mapping)
}
/// Converts the builder into a sourcemap.
pub fn into_sourcemap(self) -> SourceMap {
let contents = if !self.source_contents.is_empty() {
Some(self.source_contents)
} else {
None
};
let mut sm = SourceMap::new(self.file, self.tokens, self.names, self.sources, contents);
sm.set_source_root(self.source_root);
sm.set_debug_id(self.debug_id);
sm
}
}

View file

@ -0,0 +1,313 @@
use std::io;
use std::io::{BufReader, Read};
use serde_json::Value;
use crate::errors::{Error, Result};
use crate::hermes::decode_hermes;
use crate::jsontypes::RawSourceMap;
use crate::types::{DecodedMap, RawToken, SourceMap, SourceMapIndex, SourceMapSection};
use crate::vlq::parse_vlq_segment_into;
const DATA_PREAMBLE: &str = "data:application/json;base64,";
#[derive(PartialEq, Eq)]
enum HeaderState {
Undecided,
Junk,
AwaitingNewline,
PastHeader,
}
pub struct StripHeaderReader<R: Read> {
r: R,
header_state: HeaderState,
}
impl<R: Read> StripHeaderReader<R> {
pub fn new(reader: R) -> StripHeaderReader<R> {
StripHeaderReader {
r: reader,
header_state: HeaderState::Undecided,
}
}
}
fn is_junk_json(byte: u8) -> bool {
byte == b')' || byte == b']' || byte == b'}' || byte == b'\''
}
impl<R: Read> Read for StripHeaderReader<R> {
#[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.header_state == HeaderState::PastHeader {
return self.r.read(buf);
}
self.strip_head_read(buf)
}
}
impl<R: Read> StripHeaderReader<R> {
fn strip_head_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut backing = vec![0; buf.len()];
let local_buf: &mut [u8] = &mut backing;
loop {
let read = self.r.read(local_buf)?;
if read == 0 {
return Ok(0);
}
for (offset, &byte) in local_buf[0..read].iter().enumerate() {
self.header_state = match self.header_state {
HeaderState::Undecided => {
if is_junk_json(byte) {
HeaderState::Junk
} else {
buf[..read].copy_from_slice(&local_buf[..read]);
self.header_state = HeaderState::PastHeader;
return Ok(read);
}
}
HeaderState::Junk => {
if byte == b'\r' {
HeaderState::AwaitingNewline
} else if byte == b'\n' {
HeaderState::PastHeader
} else {
HeaderState::Junk
}
}
HeaderState::AwaitingNewline => {
if byte == b'\n' {
HeaderState::PastHeader
} else {
fail!(io::Error::new(
io::ErrorKind::InvalidData,
"expected newline"
));
}
}
HeaderState::PastHeader => {
let rem = read - offset;
buf[..rem].copy_from_slice(&local_buf[offset..read]);
return Ok(rem);
}
};
}
}
}
}
pub fn strip_junk_header(slice: &[u8]) -> io::Result<&[u8]> {
if slice.is_empty() || !is_junk_json(slice[0]) {
return Ok(slice);
}
let mut need_newline = false;
for (idx, &byte) in slice.iter().enumerate() {
if need_newline && byte != b'\n' {
fail!(io::Error::new(
io::ErrorKind::InvalidData,
"expected newline"
));
} else if is_junk_json(byte) {
continue;
} else if byte == b'\r' {
need_newline = true;
} else if byte == b'\n' {
return Ok(&slice[idx..]);
}
}
Ok(&slice[slice.len()..])
}
pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
let mut dst_col;
let mut src_id = 0;
let mut src_line = 0;
let mut src_col = 0;
let mut name_id = 0;
let names = rsm.names.unwrap_or_default();
let sources = rsm.sources.unwrap_or_default();
let mappings = rsm.mappings.unwrap_or_default();
let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10;
let mut tokens = Vec::with_capacity(allocation_size);
let mut nums = Vec::with_capacity(6);
for (dst_line, line) in mappings.split(';').enumerate() {
if line.is_empty() {
continue;
}
dst_col = 0;
for segment in line.split(',') {
if segment.is_empty() {
continue;
}
nums.clear();
parse_vlq_segment_into(segment, &mut nums)?;
dst_col = (i64::from(dst_col) + nums[0]) as u32;
let mut src = !0;
let mut name = !0;
if nums.len() > 1 {
if nums.len() != 4 && nums.len() != 5 {
fail!(Error::BadSegmentSize(nums.len() as u32));
}
src_id = (i64::from(src_id) + nums[1]) as u32;
if src_id >= sources.len() as u32 {
fail!(Error::BadSourceReference(src_id));
}
src = src_id;
src_line = (i64::from(src_line) + nums[2]) as u32;
src_col = (i64::from(src_col) + nums[3]) as u32;
if nums.len() > 4 {
name_id = (i64::from(name_id) + nums[4]) as u32;
if name_id >= names.len() as u32 {
fail!(Error::BadNameReference(name_id));
}
name = name_id;
}
}
tokens.push(RawToken {
dst_line: dst_line as u32,
dst_col,
src_line,
src_col,
src_id: src,
name_id: name,
});
}
}
let sources = sources.into_iter().map(Option::unwrap_or_default).collect();
// apparently we can encounter some non string types in real world
// sourcemaps :(
let names = names
.into_iter()
.map(|val| match val {
Value::String(s) => s,
Value::Number(num) => num.to_string(),
_ => "".into(),
})
.collect::<Vec<String>>();
// file sometimes is not a string for unexplicable reasons
let file = rsm.file.map(|val| match val {
Value::String(s) => s,
_ => "<invalid>".into(),
});
let mut sm = SourceMap::new(file, tokens, names, sources, rsm.sources_content);
sm.set_source_root(rsm.source_root);
sm.set_debug_id(rsm.debug_id);
Ok(sm)
}
fn decode_index(rsm: RawSourceMap) -> Result<SourceMapIndex> {
let mut sections = vec![];
for mut raw_section in rsm.sections.unwrap_or_default() {
sections.push(SourceMapSection::new(
(raw_section.offset.line, raw_section.offset.column),
raw_section.url,
match raw_section.map.take() {
Some(map) => Some(decode_common(*map)?),
None => None,
},
));
}
sections.sort_by_key(SourceMapSection::get_offset);
// file sometimes is not a string for unexplicable reasons
let file = rsm.file.map(|val| match val {
Value::String(s) => s,
_ => "<invalid>".into(),
});
Ok(SourceMapIndex::new_ram_bundle_compatible(
file,
sections,
rsm.x_facebook_offsets,
rsm.x_metro_module_paths,
))
}
fn decode_common(rsm: RawSourceMap) -> Result<DecodedMap> {
Ok(if rsm.sections.is_some() {
DecodedMap::Index(decode_index(rsm)?)
} else if rsm.x_facebook_sources.is_some() {
DecodedMap::Hermes(decode_hermes(rsm)?)
} else {
DecodedMap::Regular(decode_regular(rsm)?)
})
}
/// Decodes a sourcemap or sourcemap index from a reader
///
/// This supports both sourcemaps and sourcemap indexes unless the
/// specialized methods on the individual types.
pub fn decode<R: Read>(rdr: R) -> Result<DecodedMap> {
let mut rdr = StripHeaderReader::new(rdr);
let mut rdr = BufReader::new(&mut rdr);
let rsm: RawSourceMap = serde_json::from_reader(&mut rdr)?;
decode_common(rsm)
}
/// Decodes a sourcemap or sourcemap index from a byte slice
///
/// This supports both sourcemaps and sourcemap indexes unless the
/// specialized methods on the individual types.
pub fn decode_slice(slice: &[u8]) -> Result<DecodedMap> {
let content = strip_junk_header(slice)?;
let rsm: RawSourceMap = serde_json::from_slice(content)?;
decode_common(rsm)
}
/// Loads a sourcemap from a data URL
pub fn decode_data_url(url: &str) -> Result<DecodedMap> {
if !url.starts_with(DATA_PREAMBLE) {
fail!(Error::InvalidDataUrl);
}
let data_b64 = &url[DATA_PREAMBLE.len()..];
let data = data_encoding::BASE64
.decode(data_b64.as_bytes())
.map_err(|_| Error::InvalidDataUrl)?;
decode_slice(&data[..])
}
#[test]
fn test_strip_header() {
use std::io::BufRead;
let input: &[_] = b")]}garbage\r\n[1, 2, 3]";
let mut reader = io::BufReader::new(StripHeaderReader::new(input));
let mut text = String::new();
reader.read_line(&mut text).ok();
assert_eq!(text, "[1, 2, 3]");
}
#[test]
fn test_bad_newline() {
use std::io::BufRead;
let input: &[_] = b")]}'\r[1, 2, 3]";
let mut reader = io::BufReader::new(StripHeaderReader::new(input));
let mut text = String::new();
match reader.read_line(&mut text) {
Err(err) => {
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
}
Ok(_) => {
panic!("Expected failure");
}
}
}

View file

@ -0,0 +1,127 @@
use std::io::{BufRead, BufReader, Read};
use std::str;
use crate::decoder::{decode_data_url, strip_junk_header, StripHeaderReader};
use crate::errors::Result;
use crate::jsontypes::MinimalRawSourceMap;
use crate::types::DecodedMap;
use url::Url;
/// Represents a reference to a sourcemap
#[derive(PartialEq, Eq, Debug)]
pub enum SourceMapRef {
/// A regular URL reference
Ref(String),
/// A legacy URL reference
LegacyRef(String),
}
fn resolve_url(ref_url: &str, minified_url: &Url) -> Option<Url> {
minified_url.join(ref_url).ok()
}
impl SourceMapRef {
/// Return the URL of the reference
pub fn get_url(&self) -> &str {
match *self {
SourceMapRef::Ref(ref u) => u.as_str(),
SourceMapRef::LegacyRef(ref u) => u.as_str(),
}
}
/// Resolves the reference.
///
/// The given minified URL needs to be the URL of the minified file. The
/// result is the fully resolved URL of where the source map can be located.
pub fn resolve(&self, minified_url: &str) -> Option<String> {
let url = self.get_url();
if url.starts_with("data:") {
return None;
}
resolve_url(url, &Url::parse(minified_url).ok()?).map(|x| x.to_string())
}
/// Resolves the reference against a local file path
///
/// This is similar to `resolve` but operates on file paths.
#[cfg(any(unix, windows, target_os = "redox"))]
pub fn resolve_path(&self, minified_path: &std::path::Path) -> Option<std::path::PathBuf> {
let url = self.get_url();
if url.starts_with("data:") {
return None;
}
resolve_url(url, &Url::from_file_path(minified_path).ok()?)
.and_then(|x| x.to_file_path().ok())
}
/// Load an embedded sourcemap if there is a data URL.
pub fn get_embedded_sourcemap(&self) -> Result<Option<DecodedMap>> {
let url = self.get_url();
if url.starts_with("data:") {
Ok(Some(decode_data_url(url)?))
} else {
Ok(None)
}
}
}
/// Locates a sourcemap reference
///
/// Given a reader to a JavaScript file this tries to find the correct
/// sourcemap reference comment and return it.
pub fn locate_sourcemap_reference<R: Read>(rdr: R) -> Result<Option<SourceMapRef>> {
for line in BufReader::new(rdr).lines() {
let line = line?;
if line.starts_with("//# sourceMappingURL=") || line.starts_with("//@ sourceMappingURL=") {
let url = str::from_utf8(&line.as_bytes()[21..])?.trim().to_owned();
if line.starts_with("//@") {
return Ok(Some(SourceMapRef::LegacyRef(url)));
} else {
return Ok(Some(SourceMapRef::Ref(url)));
}
}
}
Ok(None)
}
/// Locates a sourcemap reference in a slice
///
/// This is an alternative to `locate_sourcemap_reference` that operates
/// on slices.
pub fn locate_sourcemap_reference_slice(slice: &[u8]) -> Result<Option<SourceMapRef>> {
locate_sourcemap_reference(slice)
}
fn is_sourcemap_common(rsm: MinimalRawSourceMap) -> bool {
(rsm.version.is_some() || rsm.file.is_some())
&& ((rsm.sources.is_some()
|| rsm.source_root.is_some()
|| rsm.sources_content.is_some()
|| rsm.names.is_some())
&& rsm.mappings.is_some())
|| rsm.sections.is_some()
}
fn is_sourcemap_impl<R: Read>(rdr: R) -> Result<bool> {
let mut rdr = StripHeaderReader::new(rdr);
let mut rdr = BufReader::new(&mut rdr);
let rsm: MinimalRawSourceMap = serde_json::from_reader(&mut rdr)?;
Ok(is_sourcemap_common(rsm))
}
fn is_sourcemap_slice_impl(slice: &[u8]) -> Result<bool> {
let content = strip_junk_header(slice)?;
let rsm: MinimalRawSourceMap = serde_json::from_slice(content)?;
Ok(is_sourcemap_common(rsm))
}
/// Checks if a valid sourcemap can be read from the given reader
pub fn is_sourcemap<R: Read>(rdr: R) -> bool {
is_sourcemap_impl(rdr).unwrap_or(false)
}
/// Checks if the given byte slice contains a sourcemap
pub fn is_sourcemap_slice(slice: &[u8]) -> bool {
is_sourcemap_slice_impl(slice).unwrap_or(false)
}

View file

@ -0,0 +1,141 @@
use std::io::Write;
use serde_json::Value;
use crate::errors::Result;
use crate::jsontypes::{RawSection, RawSectionOffset, RawSourceMap};
use crate::types::{DecodedMap, SourceMap, SourceMapIndex};
use crate::vlq::encode_vlq;
pub trait Encodable {
fn as_raw_sourcemap(&self) -> RawSourceMap;
}
pub fn encode<M: Encodable, W: Write>(sm: &M, mut w: W) -> Result<()> {
let ty = sm.as_raw_sourcemap();
serde_json::to_writer(&mut w, &ty)?;
Ok(())
}
fn encode_vlq_diff(out: &mut String, a: u32, b: u32) {
encode_vlq(out, i64::from(a) - i64::from(b))
}
fn serialize_mappings(sm: &SourceMap) -> String {
let mut rv = String::new();
// dst == minified == generated
let mut prev_dst_line = 0;
let mut prev_dst_col = 0;
let mut prev_src_line = 0;
let mut prev_src_col = 0;
let mut prev_name_id = 0;
let mut prev_src_id = 0;
for (idx, token) in sm.tokens().enumerate() {
let idx = idx as u32;
if token.get_dst_line() != prev_dst_line {
prev_dst_col = 0;
while token.get_dst_line() != prev_dst_line {
rv.push(';');
prev_dst_line += 1;
}
} else if idx > 0 {
if Some(&token) == sm.get_token(idx - 1).as_ref() {
continue;
}
rv.push(',');
}
encode_vlq_diff(&mut rv, token.get_dst_col(), prev_dst_col);
prev_dst_col = token.get_dst_col();
if token.has_source() {
encode_vlq_diff(&mut rv, token.get_src_id(), prev_src_id);
prev_src_id = token.get_src_id();
encode_vlq_diff(&mut rv, token.get_src_line(), prev_src_line);
prev_src_line = token.get_src_line();
encode_vlq_diff(&mut rv, token.get_src_col(), prev_src_col);
prev_src_col = token.get_src_col();
if token.has_name() {
encode_vlq_diff(&mut rv, token.get_name_id(), prev_name_id);
prev_name_id = token.get_name_id();
}
}
}
rv
}
impl Encodable for SourceMap {
fn as_raw_sourcemap(&self) -> RawSourceMap {
let mut have_contents = false;
let contents = self
.source_contents()
.map(|contents| {
if let Some(contents) = contents {
have_contents = true;
Some(contents.to_string())
} else {
None
}
})
.collect();
RawSourceMap {
version: Some(3),
file: self.get_file().map(|x| Value::String(x.to_string())),
sources: Some(self.sources.iter().map(|x| Some(x.to_string())).collect()),
source_root: self.get_source_root().map(|x| x.to_string()),
sources_content: if have_contents { Some(contents) } else { None },
sections: None,
names: Some(self.names().map(|x| Value::String(x.to_string())).collect()),
mappings: Some(serialize_mappings(self)),
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: self.get_debug_id(),
}
}
}
impl Encodable for SourceMapIndex {
fn as_raw_sourcemap(&self) -> RawSourceMap {
RawSourceMap {
version: Some(3),
file: self.get_file().map(|x| Value::String(x.to_string())),
sources: None,
source_root: None,
sources_content: None,
sections: Some(
self.sections()
.map(|section| RawSection {
offset: RawSectionOffset {
line: section.get_offset_line(),
column: section.get_offset_col(),
},
url: section.get_url().map(str::to_owned),
map: section
.get_sourcemap()
.map(|sm| Box::new(sm.as_raw_sourcemap())),
})
.collect(),
),
names: None,
mappings: None,
x_facebook_offsets: None,
x_metro_module_paths: None,
x_facebook_sources: None,
debug_id: None,
}
}
}
impl Encodable for DecodedMap {
fn as_raw_sourcemap(&self) -> RawSourceMap {
match *self {
DecodedMap::Regular(ref sm) => sm.as_raw_sourcemap(),
DecodedMap::Index(ref smi) => smi.as_raw_sourcemap(),
DecodedMap::Hermes(ref smh) => smh.as_raw_sourcemap(),
}
}
}

View file

@ -0,0 +1,119 @@
use std::error;
use std::fmt;
use std::io;
use std::str;
use std::string;
/// Represents results from this library
pub type Result<T> = std::result::Result<T, Error>;
/// Represents different failure cases
#[derive(Debug)]
pub enum Error {
/// a std::io error
Io(io::Error),
#[cfg(feature = "ram_bundle")]
/// a scroll error
Scroll(scroll::Error),
/// a std::str::Utf8Error
Utf8(str::Utf8Error),
/// a JSON parsing related failure
BadJson(serde_json::Error),
/// a VLQ string was malformed and data was left over
VlqLeftover,
/// a VLQ string was empty and no values could be decoded.
VlqNoValues,
/// Overflow in Vlq handling
VlqOverflow,
/// a mapping segment had an unsupported size
BadSegmentSize(u32),
/// a reference to a non existing source was encountered
BadSourceReference(u32),
/// a reference to a non existing name was encountered
BadNameReference(u32),
/// Indicates that an incompatible sourcemap format was encountered
IncompatibleSourceMap,
/// Indicates an invalid data URL
InvalidDataUrl,
/// Flatten failed
CannotFlatten(String),
/// The magic of a RAM bundle did not match
InvalidRamBundleMagic,
/// The RAM bundle index was malformed
InvalidRamBundleIndex,
/// A RAM bundle entry was invalid
InvalidRamBundleEntry,
/// Tried to operate on a non RAM bundle file
NotARamBundle,
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err)
}
}
#[cfg(feature = "ram_bundle")]
impl From<scroll::Error> for Error {
fn from(err: scroll::Error) -> Self {
Error::Scroll(err)
}
}
impl From<string::FromUtf8Error> for Error {
fn from(err: string::FromUtf8Error) -> Error {
From::from(err.utf8_error())
}
}
impl From<str::Utf8Error> for Error {
fn from(err: str::Utf8Error) -> Error {
Error::Utf8(err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Error {
Error::BadJson(err)
}
}
impl error::Error for Error {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
Error::Io(ref err) => Some(err),
#[cfg(feature = "ram_bundle")]
Error::Scroll(ref err) => Some(err),
Error::Utf8(ref err) => Some(err),
Error::BadJson(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Io(ref msg) => write!(f, "{msg}"),
Error::Utf8(ref msg) => write!(f, "{msg}"),
Error::BadJson(ref err) => write!(f, "bad json: {err}"),
#[cfg(feature = "ram_bundle")]
Error::Scroll(ref err) => write!(f, "parse error: {err}"),
Error::VlqLeftover => write!(f, "leftover cur/shift in vlq decode"),
Error::VlqNoValues => write!(f, "vlq decode did not produce any values"),
Error::VlqOverflow => write!(f, "vlq decode caused an overflow"),
Error::BadSegmentSize(size) => write!(f, "got {size} segments, expected 4 or 5"),
Error::BadSourceReference(id) => write!(f, "bad reference to source #{id}"),
Error::BadNameReference(id) => write!(f, "bad reference to name #{id}"),
Error::IncompatibleSourceMap => write!(f, "encountered incompatible sourcemap format"),
Error::InvalidDataUrl => write!(f, "the provided data URL is invalid"),
Error::CannotFlatten(ref msg) => {
write!(f, "cannot flatten the indexed sourcemap: {msg}")
}
Error::InvalidRamBundleMagic => write!(f, "invalid magic number for ram bundle"),
Error::InvalidRamBundleIndex => write!(f, "invalid module index in ram bundle"),
Error::InvalidRamBundleEntry => write!(f, "invalid ram bundle module entry"),
Error::NotARamBundle => write!(f, "not a ram bundle"),
}
}
}

View file

@ -0,0 +1,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),
})
}

View file

@ -0,0 +1,84 @@
use unicode_id::UnicodeID;
/// Returns true if `c` is a valid character for an identifier start.
fn is_valid_start(c: char) -> bool {
c == '$' || c == '_' || c.is_ascii_alphabetic() || {
if c.is_ascii() {
false
} else {
UnicodeID::is_id_start(c)
}
}
}
/// Returns true if `c` is a valid character for an identifier part after start.
fn is_valid_continue(c: char) -> bool {
// As specified by the ECMA-262 spec, U+200C (ZERO WIDTH NON-JOINER) and U+200D
// (ZERO WIDTH JOINER) are format-control characters that are used to make necessary
// distinctions when forming words or phrases in certain languages. They are however
// not considered by UnicodeID to be universally valid identifier characters.
c == '$' || c == '_' || c == '\u{200c}' || c == '\u{200d}' || c.is_ascii_alphanumeric() || {
if c.is_ascii() {
false
} else {
UnicodeID::is_id_continue(c)
}
}
}
fn strip_identifier(s: &str) -> Option<&str> {
let mut iter = s.char_indices();
// Is the first character a valid starting character
match iter.next() {
Some((_, c)) => {
if !is_valid_start(c) {
return None;
}
}
None => {
return None;
}
};
// Slice up to the last valid continuation character
let mut end_idx = 0;
for (i, c) in iter {
if is_valid_continue(c) {
end_idx = i;
} else {
break;
}
}
Some(&s[..=end_idx])
}
pub fn is_valid_javascript_identifier(s: &str) -> bool {
// check stripping does not reduce the length of the token
strip_identifier(s).map_or(0, |t| t.len()) == s.len()
}
/// Finds the first valid identifier in the JS Source string given, provided
/// the string begins with the identifier or whitespace.
pub fn get_javascript_token(source_line: &str) -> Option<&str> {
match source_line.split_whitespace().next() {
Some(s) => strip_identifier(s),
None => None,
}
}
#[test]
fn test_is_valid_javascript_identifier() {
// assert_eq!(is_valid_javascript_identifier("foo 123"));
assert!(is_valid_javascript_identifier("foo_$123"));
assert!(!is_valid_javascript_identifier(" foo"));
assert!(!is_valid_javascript_identifier("foo "));
assert!(!is_valid_javascript_identifier("[123]"));
assert!(!is_valid_javascript_identifier("foo.bar"));
// Should these pass?
// assert!(is_valid_javascript_identifier("foo [bar]"));
// assert!(is_valid_javascript_identifier("foo[bar]"));
assert_eq!(get_javascript_token("foo "), Some("foo"));
assert_eq!(get_javascript_token("f _hi"), Some("f"));
assert_eq!(get_javascript_token("foo.bar"), Some("foo"));
assert_eq!(get_javascript_token("[foo,bar]"), None);
}

View file

@ -0,0 +1,69 @@
use debugid::DebugId;
use serde::de::IgnoredAny;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Serialize, Deserialize)]
pub struct RawSectionOffset {
pub line: u32,
pub column: u32,
}
#[derive(Serialize, Deserialize)]
pub struct RawSection {
pub offset: RawSectionOffset,
pub url: Option<String>,
pub map: Option<Box<RawSourceMap>>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FacebookScopeMapping {
pub names: Vec<String>,
pub mappings: String,
}
// Each element here is matching the `sources` of the outer SourceMap.
// It has a list of metadata, the first one of which is a *function map*,
// containing scope information as a nested source map.
// See the decoder in `hermes.rs` for details.
pub type FacebookSources = Option<Vec<Option<Vec<FacebookScopeMapping>>>>;
#[derive(Serialize, Deserialize)]
pub struct RawSourceMap {
pub version: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file: Option<Value>,
pub sources: Option<Vec<Option<String>>>,
#[serde(rename = "sourceRoot", skip_serializing_if = "Option::is_none")]
pub source_root: Option<String>,
#[serde(rename = "sourcesContent", skip_serializing_if = "Option::is_none")]
pub sources_content: Option<Vec<Option<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sections: Option<Vec<RawSection>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub names: Option<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mappings: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x_facebook_offsets: Option<Vec<Option<u32>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x_metro_module_paths: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x_facebook_sources: FacebookSources,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_id: Option<DebugId>,
}
#[derive(Deserialize)]
pub struct MinimalRawSourceMap {
pub version: Option<u32>,
pub file: Option<IgnoredAny>,
pub sources: Option<IgnoredAny>,
#[serde(rename = "sourceRoot")]
pub source_root: Option<IgnoredAny>,
#[serde(rename = "sourcesContent")]
pub sources_content: Option<IgnoredAny>,
pub sections: Option<IgnoredAny>,
pub names: Option<IgnoredAny>,
pub mappings: Option<IgnoredAny>,
}

80
third-party/vendor/sourcemap/src/lib.rs vendored Normal file
View file

@ -0,0 +1,80 @@
//! This library implements basic processing of JavaScript sourcemaps.
//!
//! # Installation
//!
//! The crate is called sourcemap and you can depend on it via cargo:
//!
//! ```toml
//! [dependencies]
//! sourcemap = "*"
//! ```
//!
//! If you want to use the git version:
//!
//! ```toml
//! [dependencies.sourcemap]
//! git = "https://github.com/getsentry/rust-sourcemap.git"
//! ```
//!
//! # Basic Operation
//!
//! This crate can load JavaScript sourcemaps from JSON files. It uses
//! `serde` for parsing of the JSON data. Due to the nature of sourcemaps
//! the entirety of the file must be loaded into memory which can be quite
//! memory intensive.
//!
//! Usage:
//!
//! ```rust
//! use sourcemap::SourceMap;
//! let input: &[_] = b"{
//! \"version\":3,
//! \"sources\":[\"coolstuff.js\"],
//! \"names\":[\"x\",\"alert\"],
//! \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\"
//! }";
//! let sm = SourceMap::from_reader(input).unwrap();
//! let token = sm.lookup_token(0, 0).unwrap(); // line-number and column
//! println!("token: {}", token);
//! ```
//!
//! # Features
//!
//! Functionality of the crate can be turned on and off by feature flags. This is the
//! current list of feature flags:
//!
//! * `ram_bundle`: turns on RAM bundle support
//!
#[warn(missing_docs)]
mod macros;
pub use crate::builder::SourceMapBuilder;
pub use crate::decoder::{decode, decode_data_url, decode_slice};
pub use crate::detector::{
is_sourcemap, is_sourcemap_slice, locate_sourcemap_reference, locate_sourcemap_reference_slice,
SourceMapRef,
};
pub use crate::errors::{Error, Result};
pub use crate::hermes::SourceMapHermes;
pub use crate::sourceview::SourceView;
pub use crate::types::{
DecodedMap, IndexIter, NameIter, RawToken, RewriteOptions, SourceContentsIter, SourceIter,
SourceMap, SourceMapIndex, SourceMapSection, SourceMapSectionIter, Token, TokenIter,
};
pub use crate::utils::make_relative_path;
mod builder;
mod decoder;
mod detector;
mod encoder;
mod errors;
mod hermes;
mod js_identifiers;
mod jsontypes;
mod sourceview;
mod types;
mod utils;
#[cfg(feature = "ram_bundle")]
pub mod ram_bundle;
pub mod vlq;

View file

@ -0,0 +1,7 @@
#![macro_use]
macro_rules! fail {
($expr:expr) => {
return Err(::std::convert::From::from($expr));
};
}

View file

@ -0,0 +1,614 @@
//! RAM bundle operations
use scroll::Pread;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::ops::Range;
use std::path::Path;
use crate::builder::SourceMapBuilder;
use crate::errors::{Error, Result};
use crate::sourceview::SourceView;
use crate::types::{SourceMap, SourceMapIndex};
/// Magic number for RAM bundles
pub const RAM_BUNDLE_MAGIC: u32 = 0xFB0B_D1E5;
const JS_MODULES_DIR_NAME: &str = "js-modules";
/// Represents a RAM bundle header
#[derive(Debug, Pread, Clone, Copy)]
#[repr(C, packed)]
pub struct RamBundleHeader {
magic: u32,
module_count: u32,
startup_code_size: u32,
}
impl RamBundleHeader {
/// Checks if the magic matches.
pub fn is_valid_magic(&self) -> bool {
self.magic == RAM_BUNDLE_MAGIC
}
}
#[derive(Debug, Pread, Clone, Copy)]
#[repr(C, packed)]
struct ModuleEntry {
offset: u32,
length: u32,
}
impl ModuleEntry {
pub fn is_empty(self) -> bool {
self.offset == 0 && self.length == 0
}
}
/// Represents an indexed RAM bundle module
///
/// This type is used on iOS by default.
#[derive(Debug)]
pub struct RamBundleModule<'a> {
id: usize,
data: &'a [u8],
}
impl<'a> RamBundleModule<'a> {
/// Returns the integer ID of the module.
pub fn id(&self) -> usize {
self.id
}
/// Returns a slice to the data in the module.
pub fn data(&self) -> &'a [u8] {
self.data
}
/// Returns a source view of the data.
///
/// This operation fails if the source code is not valid UTF-8.
pub fn source_view(&self) -> Result<SourceView<'a>> {
match std::str::from_utf8(self.data) {
Ok(s) => Ok(SourceView::new(s)),
Err(e) => Err(Error::Utf8(e)),
}
}
}
/// An iterator over modules in a RAM bundle
pub struct RamBundleModuleIter<'a> {
range: Range<usize>,
ram_bundle: &'a RamBundle<'a>,
}
impl<'a> Iterator for RamBundleModuleIter<'a> {
type Item = Result<RamBundleModule<'a>>;
fn next(&mut self) -> Option<Self::Item> {
for next_id in self.range.by_ref() {
match self.ram_bundle.get_module(next_id) {
Ok(None) => continue,
Ok(Some(module)) => return Some(Ok(module)),
Err(e) => return Some(Err(e)),
}
}
None
}
}
/// The type of ram bundle.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum RamBundleType {
Indexed,
Unbundle,
}
#[derive(Debug, Clone)]
enum RamBundleImpl<'a> {
/// Indexed RAM bundle
Indexed(IndexedRamBundle<'a>),
/// File (unbundle) RAM bundle
Unbundle(UnbundleRamBundle),
}
/// The main RAM bundle interface
#[derive(Debug, Clone)]
pub struct RamBundle<'a> {
repr: RamBundleImpl<'a>,
}
impl<'a> RamBundle<'a> {
/// Parses an indexed RAM bundle from the given slice
pub fn parse_indexed_from_slice(bytes: &'a [u8]) -> Result<Self> {
Ok(RamBundle {
repr: RamBundleImpl::Indexed(IndexedRamBundle::parse(Cow::Borrowed(bytes))?),
})
}
/// Parses an indexed RAM bundle from the given vector
pub fn parse_indexed_from_vec(bytes: Vec<u8>) -> Result<Self> {
Ok(RamBundle {
repr: RamBundleImpl::Indexed(IndexedRamBundle::parse(Cow::Owned(bytes))?),
})
}
/// Creates a new indexed RAM bundle from the file path
pub fn parse_indexed_from_path(path: &Path) -> Result<Self> {
RamBundle::parse_indexed_from_vec(fs::read(path)?)
}
/// Creates a file (unbundle) RAM bundle from the path
///
/// The provided path should point to a javascript file, that serves
/// as an entry point (startup code) for the app. The modules are stored in js-modules/
/// directory, next to the entry point. The js-modules/ directory must ONLY contain
/// files with integer names and the ".js" file suffix, along with the UNBUNDLE magic file.
pub fn parse_unbundle_from_path(bundle_path: &Path) -> Result<Self> {
Ok(RamBundle {
repr: RamBundleImpl::Unbundle(UnbundleRamBundle::parse(bundle_path)?),
})
}
/// Returns the type of the RAM bundle.
pub fn bundle_type(&self) -> RamBundleType {
match self.repr {
RamBundleImpl::Indexed(..) => RamBundleType::Indexed,
RamBundleImpl::Unbundle(..) => RamBundleType::Unbundle,
}
}
/// Looks up a module by ID in the bundle
pub fn get_module(&self, id: usize) -> Result<Option<RamBundleModule>> {
match self.repr {
RamBundleImpl::Indexed(ref indexed) => indexed.get_module(id),
RamBundleImpl::Unbundle(ref file) => file.get_module(id),
}
}
/// Returns the number of modules in the bundle
pub fn module_count(&self) -> usize {
match self.repr {
RamBundleImpl::Indexed(ref indexed) => indexed.module_count(),
RamBundleImpl::Unbundle(ref file) => file.module_count(),
}
}
/// Returns the startup code
pub fn startup_code(&self) -> Result<&[u8]> {
match self.repr {
RamBundleImpl::Indexed(ref indexed) => indexed.startup_code(),
RamBundleImpl::Unbundle(ref file) => file.startup_code(),
}
}
/// Returns an iterator over all modules in the bundle
pub fn iter_modules(&self) -> RamBundleModuleIter {
RamBundleModuleIter {
range: 0..self.module_count(),
ram_bundle: self,
}
}
}
/// Filename must be made of ascii-only digits and the .js extension
/// Anything else errors with `Error::InvalidRamBundleIndex`
fn js_filename_to_index_strict(filename: &str) -> Result<usize> {
match filename.strip_suffix(".js") {
Some(basename) => basename
.parse::<usize>()
.or(Err(Error::InvalidRamBundleIndex)),
None => Err(Error::InvalidRamBundleIndex),
}
}
/// Represents a file RAM bundle
///
/// This RAM bundle type is mostly used on Android.
#[derive(Debug, Clone)]
struct UnbundleRamBundle {
startup_code: Vec<u8>,
module_count: usize,
modules: BTreeMap<usize, Vec<u8>>,
}
impl UnbundleRamBundle {
pub fn parse(bundle_path: &Path) -> Result<Self> {
if !is_unbundle_path(bundle_path) {
return Err(Error::NotARamBundle);
}
let bundle_dir = match bundle_path.parent() {
Some(dir) => dir,
None => return Err(Error::NotARamBundle),
};
let startup_code = fs::read(bundle_path)?;
let mut max_module_id = 0;
let mut modules: BTreeMap<usize, Vec<u8>> = Default::default();
let js_modules_dir = bundle_dir.join(JS_MODULES_DIR_NAME);
for entry in js_modules_dir.read_dir()? {
let entry = entry?;
if !entry.file_type()?.is_file() {
continue;
}
let path = entry.path();
let filename_os = path.file_name().unwrap();
let filename: &str = &filename_os.to_string_lossy();
if filename == "UNBUNDLE" {
continue;
}
let module_id = js_filename_to_index_strict(filename)?;
if module_id > max_module_id {
max_module_id = module_id;
}
modules.insert(module_id, fs::read(path)?);
}
Ok(UnbundleRamBundle {
startup_code,
modules,
module_count: max_module_id + 1,
})
}
/// Returns the number of modules in the bundle
pub fn module_count(&self) -> usize {
self.module_count
}
/// Returns the startup code
pub fn startup_code(&self) -> Result<&[u8]> {
Ok(&self.startup_code)
}
/// Looks up a module by ID in the bundle
pub fn get_module(&self, id: usize) -> Result<Option<RamBundleModule>> {
match self.modules.get(&id) {
Some(data) => Ok(Some(RamBundleModule { id, data })),
None => Ok(None),
}
}
}
/// Represents an indexed RAM bundle
///
/// Provides access to a react-native metro
/// [RAM bundle](https://facebook.github.io/metro/docs/en/bundling).
#[derive(Debug, Clone)]
struct IndexedRamBundle<'a> {
bytes: Cow<'a, [u8]>,
module_count: usize,
startup_code_size: usize,
startup_code_offset: usize,
}
impl<'a> IndexedRamBundle<'a> {
/// Parses a RAM bundle from a given slice of bytes.
pub fn parse(bytes: Cow<'a, [u8]>) -> Result<Self> {
let header = bytes.pread_with::<RamBundleHeader>(0, scroll::LE)?;
if !header.is_valid_magic() {
return Err(Error::InvalidRamBundleMagic);
}
let module_count = header.module_count as usize;
let startup_code_offset = std::mem::size_of::<RamBundleHeader>()
+ module_count * std::mem::size_of::<ModuleEntry>();
Ok(IndexedRamBundle {
bytes,
module_count,
startup_code_size: header.startup_code_size as usize,
startup_code_offset,
})
}
/// Returns the number of modules in the bundle
pub fn module_count(&self) -> usize {
self.module_count
}
/// Returns the startup code
pub fn startup_code(&self) -> Result<&[u8]> {
self.bytes
.pread_with(self.startup_code_offset, self.startup_code_size)
.map_err(Error::Scroll)
}
/// Looks up a module by ID in the bundle
pub fn get_module(&self, id: usize) -> Result<Option<RamBundleModule>> {
if id >= self.module_count {
return Err(Error::InvalidRamBundleIndex);
}
let entry_offset =
std::mem::size_of::<RamBundleHeader>() + id * std::mem::size_of::<ModuleEntry>();
let module_entry = self
.bytes
.pread_with::<ModuleEntry>(entry_offset, scroll::LE)?;
if module_entry.is_empty() {
return Ok(None);
}
let module_global_offset = self.startup_code_offset + module_entry.offset as usize;
if module_entry.length == 0 {
return Err(Error::InvalidRamBundleEntry);
}
// Strip the trailing NULL byte
let module_length = (module_entry.length - 1) as usize;
let data = self.bytes.pread_with(module_global_offset, module_length)?;
Ok(Some(RamBundleModule { id, data }))
}
}
/// An iterator over deconstructed RAM bundle sources and sourcemaps
pub struct SplitRamBundleModuleIter<'a> {
ram_bundle_iter: RamBundleModuleIter<'a>,
sm: SourceMap,
offsets: Vec<Option<u32>>,
}
impl<'a> SplitRamBundleModuleIter<'a> {
fn split_module(
&self,
module: RamBundleModule<'a>,
) -> Result<Option<(String, SourceView<'a>, SourceMap)>> {
let module_offset = self
.offsets
.get(module.id())
.ok_or(Error::InvalidRamBundleIndex)?;
let starting_line = match *module_offset {
Some(offset) => offset,
None => return Ok(None),
};
let mut token_iter = self.sm.tokens();
if !token_iter.seek(starting_line, 0) {
return Err(Error::InvalidRamBundleEntry);
}
let source: SourceView<'a> = module.source_view()?;
let line_count = source.line_count() as u32;
let ending_line = starting_line + line_count;
let last_line_len = source
.get_line(line_count - 1)
.map_or(0, |line| line.chars().map(char::len_utf16).sum())
as u32;
let filename = format!("{}.js", module.id);
let mut builder = SourceMapBuilder::new(Some(&filename));
for token in token_iter {
let dst_line = token.get_dst_line();
let dst_col = token.get_dst_col();
if dst_line >= ending_line || dst_col >= last_line_len {
break;
}
let raw = builder.add(
dst_line - starting_line,
dst_col,
token.get_src_line(),
token.get_src_col(),
token.get_source(),
token.get_name(),
);
if token.get_source().is_some() && !builder.has_source_contents(raw.src_id) {
builder.set_source_contents(
raw.src_id,
self.sm.get_source_contents(token.get_src_id()),
);
}
}
let sourcemap = builder.into_sourcemap();
Ok(Some((filename, source, sourcemap)))
}
}
impl<'a> Iterator for SplitRamBundleModuleIter<'a> {
type Item = Result<(String, SourceView<'a>, SourceMap)>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(module_result) = self.ram_bundle_iter.next() {
match module_result {
Ok(module) => match self.split_module(module) {
Ok(None) => continue,
Ok(Some(result_tuple)) => return Some(Ok(result_tuple)),
Err(_) => return Some(Err(Error::InvalidRamBundleEntry)),
},
Err(_) => return Some(Err(Error::InvalidRamBundleEntry)),
}
}
None
}
}
/// Deconstructs a RAM bundle into a sequence of sources and their sourcemaps
///
/// With the help of the RAM bundle's indexed sourcemap, the bundle is split into modules,
/// where each module is represented by its minified source and the corresponding sourcemap that
/// we recover from the initial indexed sourcemap.
pub fn split_ram_bundle<'a>(
ram_bundle: &'a RamBundle,
smi: &SourceMapIndex,
) -> Result<SplitRamBundleModuleIter<'a>> {
Ok(SplitRamBundleModuleIter {
ram_bundle_iter: ram_bundle.iter_modules(),
sm: smi.flatten()?,
offsets: smi
.x_facebook_offsets()
.map(|v| v.to_vec())
.ok_or(Error::NotARamBundle)?,
})
}
/// Checks if the given byte slice contains an indexed RAM bundle
pub fn is_ram_bundle_slice(slice: &[u8]) -> bool {
slice
.pread_with::<RamBundleHeader>(0, scroll::LE)
.ok()
.map_or(false, |x| x.is_valid_magic())
}
/// Returns "true" if the given path points to the startup file of a file RAM bundle
///
/// The method checks the directory structure and the magic number in UNBUNDLE file.
pub fn is_unbundle_path(bundle_path: &Path) -> bool {
if !bundle_path.is_file() {
return false;
}
let bundle_dir = match bundle_path.parent() {
Some(dir) => dir,
None => return false,
};
let unbundle_file_path = bundle_dir.join(JS_MODULES_DIR_NAME).join("UNBUNDLE");
if !unbundle_file_path.is_file() {
return false;
}
let mut unbundle_file = match File::open(unbundle_file_path) {
Ok(file) => file,
Err(_) => return false,
};
let mut bundle_magic = [0; 4];
if unbundle_file.read_exact(&mut bundle_magic).is_err() {
return false;
}
bundle_magic == RAM_BUNDLE_MAGIC.to_le_bytes()
}
#[test]
fn test_indexed_ram_bundle_parse() -> std::result::Result<(), Box<dyn std::error::Error>> {
let mut bundle_file =
File::open("./tests/fixtures/ram_bundle/indexed_bundle_1/basic.jsbundle")?;
let mut bundle_data = Vec::new();
bundle_file.read_to_end(&mut bundle_data)?;
assert!(is_ram_bundle_slice(&bundle_data));
let ram_bundle = RamBundle::parse_indexed_from_slice(&bundle_data)?;
let indexed_ram_bundle = match ram_bundle.repr.clone() {
RamBundleImpl::Indexed(bundle) => bundle,
_ => {
panic!("Invalid RamBundleImpl type");
}
};
// Header checks
assert_eq!(indexed_ram_bundle.startup_code_size, 0x7192);
assert_eq!(indexed_ram_bundle.startup_code_offset, 0x34);
assert_eq!(ram_bundle.module_count(), 5);
// Check first modules
let mut module_iter = ram_bundle.iter_modules();
let module_0 = module_iter.next().unwrap()?;
let module_0_data = module_0.data();
assert_eq!(module_0.id(), 0);
assert_eq!(module_0_data.len(), 0xa8 - 1);
assert_eq!(
&module_0_data[0..60],
"__d(function(g,r,i,a,m,e,d){\"use strict\";const o=r(d[0]),s=r".as_bytes()
);
let module_3 = module_iter.next().unwrap()?;
let module_3_data = module_3.data();
assert_eq!(module_3.id(), 3);
assert_eq!(module_3_data.len(), 0x6b - 1);
assert_eq!(
&module_3_data[0..60],
"__d(function(g,r,i,a,m,e,d){\"use strict\";console.log('inside".as_bytes()
);
let module_1 = ram_bundle.get_module(1)?;
assert!(module_1.is_none());
Ok(())
}
#[test]
fn test_indexed_ram_bundle_split() -> std::result::Result<(), Box<dyn std::error::Error>> {
let ram_bundle = RamBundle::parse_indexed_from_path(Path::new(
"./tests/fixtures/ram_bundle/indexed_bundle_1/basic.jsbundle",
))?;
let sourcemap_file =
File::open("./tests/fixtures/ram_bundle/indexed_bundle_1/basic.jsbundle.map")?;
let ism = SourceMapIndex::from_reader(sourcemap_file)?;
assert!(ism.is_for_ram_bundle());
let x_facebook_offsets = ism.x_facebook_offsets().unwrap();
assert_eq!(x_facebook_offsets.len(), 5);
let x_metro_module_paths = ism.x_metro_module_paths().unwrap();
assert_eq!(x_metro_module_paths.len(), 7);
// Modules 0, 3, 4
assert_eq!(split_ram_bundle(&ram_bundle, &ism)?.count(), 3);
let mut ram_bundle_iter = split_ram_bundle(&ram_bundle, &ism)?;
let (name, sourceview, sourcemap) = ram_bundle_iter.next().unwrap()?;
assert_eq!(name, "0.js");
assert_eq!(
&sourceview.source()[0..60],
"__d(function(g,r,i,a,m,e,d){\"use strict\";const o=r(d[0]),s=r"
);
assert_eq!(
&sourcemap.get_source_contents(0).unwrap()[0..60],
"const f = require(\"./other\");\nconst isWindows = require(\"is-"
);
Ok(())
}
#[test]
fn test_file_ram_bundle_parse() -> std::result::Result<(), Box<dyn std::error::Error>> {
let valid_bundle_path = Path::new("./tests/fixtures/ram_bundle/file_bundle_1/basic.bundle");
assert!(is_unbundle_path(valid_bundle_path));
assert!(!is_unbundle_path(Path::new("./tmp/invalid/bundle/path")));
let ram_bundle = RamBundle::parse_unbundle_from_path(valid_bundle_path)?;
match ram_bundle.repr {
RamBundleImpl::Unbundle(_) => (),
_ => {
panic!("Invalid RamBundleImpl type");
}
};
assert_eq!(ram_bundle.module_count(), 4);
let startup_code = ram_bundle.startup_code()?;
assert_eq!(
startup_code[0..60].to_vec(),
b"var __DEV__=false,__BUNDLE_START_TIME__=this.nativePerforman".to_vec()
);
let module_0 = ram_bundle.get_module(0)?.unwrap();
let module_0_data = module_0.data();
assert_eq!(
module_0_data[0..60].to_vec(),
b"__d(function(g,r,i,a,m,e,d){'use strict';var t=Date.now();r(".to_vec()
);
let module_1 = ram_bundle.get_module(1)?;
assert!(module_1.is_none());
Ok(())
}

View file

@ -0,0 +1,371 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
use std::slice;
use std::str;
use if_chain::if_chain;
use crate::detector::{locate_sourcemap_reference_slice, SourceMapRef};
use crate::errors::Result;
use crate::js_identifiers::{get_javascript_token, is_valid_javascript_identifier};
use crate::types::{idx_from_token, sourcemap_from_token, Token};
/// An iterator that iterates over tokens in reverse.
pub struct RevTokenIter<'view, 'viewbase, 'map>
where
'viewbase: 'view,
{
sv: &'view SourceView<'viewbase>,
token: Option<Token<'map>>,
source_line: Option<(&'view str, usize, usize, usize)>,
}
impl<'view, 'viewbase, 'map> Iterator for RevTokenIter<'view, 'viewbase, 'map>
where
'viewbase: 'view,
{
type Item = (Token<'map>, Option<&'view str>);
fn next(&mut self) -> Option<(Token<'map>, Option<&'view str>)> {
let token = match self.token.take() {
None => {
return None;
}
Some(token) => token,
};
let idx = idx_from_token(&token);
if idx > 0 {
let sm = sourcemap_from_token(&token);
self.token = sm.get_token(idx - 1);
}
// if we are going to the same line as we did last iteration, we don't have to scan
// up to it again. For normal sourcemaps this should mean we only ever go to the
// line once.
let (source_line, last_char_offset, last_byte_offset) = if_chain! {
if let Some((source_line, dst_line, last_char_offset,
last_byte_offset)) = self.source_line;
if dst_line == token.get_dst_line() as usize;
then {
(source_line, last_char_offset, last_byte_offset)
} else {
if let Some(source_line) = self.sv.get_line(token.get_dst_line()) {
(source_line, !0, !0)
} else {
// if we can't find the line, return am empty one
("", !0, !0)
}
}
};
// find the byte offset where our token starts
let byte_offset = if last_byte_offset == !0 {
let mut off = 0;
let mut idx = 0;
for c in source_line.chars() {
if idx >= token.get_dst_col() as usize {
break;
}
off += c.len_utf8();
idx += c.len_utf16();
}
off
} else {
let chars_to_move = last_char_offset - token.get_dst_col() as usize;
let mut new_offset = last_byte_offset;
let mut idx = 0;
for c in source_line
.get(..last_byte_offset)
.unwrap_or("")
.chars()
.rev()
{
if idx >= chars_to_move {
break;
}
new_offset -= c.len_utf8();
idx += c.len_utf16();
}
new_offset
};
// remember where we were
self.source_line = Some((
source_line,
token.get_dst_line() as usize,
token.get_dst_col() as usize,
byte_offset,
));
// in case we run out of bounds here we reset the cache
if byte_offset >= source_line.len() {
self.source_line = None;
Some((token, None))
} else {
Some((
token,
source_line
.get(byte_offset..)
.and_then(get_javascript_token),
))
}
}
}
pub struct Lines<'a> {
sv: &'a SourceView<'a>,
idx: u32,
}
impl<'a> Iterator for Lines<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
if let Some(line) = self.sv.get_line(self.idx) {
self.idx += 1;
Some(line)
} else {
None
}
}
}
/// Provides efficient access to minified sources.
///
/// This type is used to implement fairly efficient source mapping
/// operations.
pub struct SourceView<'a> {
source: Cow<'a, str>,
processed_until: RefCell<usize>,
lines: RefCell<Vec<(*const u8, usize)>>,
}
impl<'a> Clone for SourceView<'a> {
fn clone(&self) -> SourceView<'a> {
SourceView {
source: self.source.clone(),
processed_until: RefCell::new(0),
lines: RefCell::new(vec![]),
}
}
}
impl<'a> fmt::Debug for SourceView<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SourceView")
.field("source", &self.source())
.finish()
}
}
unsafe fn make_str<'a>(tup: (*const u8, usize)) -> &'a str {
let (data, len) = tup;
str::from_utf8_unchecked(slice::from_raw_parts(data, len))
}
impl<'a> SourceView<'a> {
/// Creates an optimized view of a given source.
pub fn new(source: &'a str) -> SourceView<'a> {
SourceView {
source: Cow::Borrowed(source),
processed_until: RefCell::new(0),
lines: RefCell::new(vec![]),
}
}
/// Creates an optimized view from a given source string
pub fn from_string(source: String) -> SourceView<'static> {
SourceView {
source: Cow::Owned(source),
processed_until: RefCell::new(0),
lines: RefCell::new(vec![]),
}
}
/// Returns a requested minified line.
pub fn get_line(&self, idx: u32) -> Option<&str> {
let idx = idx as usize;
{
let lines = self.lines.borrow();
if idx < lines.len() {
return Some(unsafe { make_str(lines[idx]) });
}
}
// fetched everything
if *self.processed_until.borrow() > self.source.len() {
return None;
}
let mut processed_until = self.processed_until.borrow_mut();
let mut lines = self.lines.borrow_mut();
let mut done = false;
while !done {
let rest = &self.source.as_bytes()[*processed_until..];
let rv = if let Some(mut idx) = rest.iter().position(|&x| x == b'\n' || x == b'\r') {
let rv = &rest[..idx];
if rest[idx] == b'\r' && rest.get(idx + 1) == Some(&b'\n') {
idx += 1;
}
*processed_until += idx + 1;
rv
} else {
*processed_until += rest.len() + 1;
done = true;
rest
};
lines.push((rv.as_ptr(), rv.len()));
if let Some(&line) = lines.get(idx) {
return Some(unsafe { make_str(line) });
}
}
None
}
/// Returns a line slice.
///
/// Note that columns are indexed as JavaScript WTF-16 columns.
pub fn get_line_slice(&self, line: u32, col: u32, span: u32) -> Option<&str> {
self.get_line(line).and_then(|line| {
let mut off = 0;
let mut idx = 0;
let mut char_iter = line.chars().peekable();
while let Some(&c) = char_iter.peek() {
if idx >= col as usize {
break;
}
char_iter.next();
off += c.len_utf8();
idx += c.len_utf16();
}
let mut off_end = off;
for c in char_iter {
if idx >= (col + span) as usize {
break;
}
off_end += c.len_utf8();
idx += c.len_utf16();
}
if idx < ((col + span) as usize) {
None
} else {
line.get(off..off_end)
}
})
}
/// Returns an iterator over all lines.
pub fn lines(&'a self) -> Lines<'a> {
Lines { sv: self, idx: 0 }
}
/// Returns the source.
pub fn source(&self) -> &str {
&self.source
}
fn rev_token_iter<'this, 'map>(
&'this self,
token: Token<'map>,
) -> RevTokenIter<'this, 'a, 'map> {
RevTokenIter {
sv: self,
token: Some(token),
source_line: None,
}
}
/// Given a token and minified function name this attemps to resolve the
/// name to an original function name.
///
/// This invokes some guesswork and requires access to the original minified
/// source. This will not yield proper results for anonymous functions or
/// functions that do not have clear function names. (For instance it's
/// recommended that dotted function names are not passed to this
/// function).
pub fn get_original_function_name<'map>(
&self,
token: Token<'map>,
minified_name: &str,
) -> Option<&'map str> {
if !is_valid_javascript_identifier(minified_name) {
return None;
}
let mut iter = self.rev_token_iter(token).take(128).peekable();
while let Some((token, original_identifier)) = iter.next() {
if_chain! {
if original_identifier == Some(minified_name);
if let Some(item) = iter.peek();
if item.1 == Some("function");
then {
return token.get_name();
}
}
}
None
}
/// Returns the number of lines.
pub fn line_count(&self) -> usize {
self.get_line(!0);
self.lines.borrow().len()
}
/// Returns the source map reference in the source view.
pub fn sourcemap_reference(&self) -> Result<Option<SourceMapRef>> {
locate_sourcemap_reference_slice(self.source.as_bytes())
}
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_minified_source_view() {
let view = SourceView::new("a\nb\nc");
assert_eq!(view.get_line(0), Some("a"));
assert_eq!(view.get_line(0), Some("a"));
assert_eq!(view.get_line(2), Some("c"));
assert_eq!(view.get_line(1), Some("b"));
assert_eq!(view.get_line(3), None);
assert_eq!(view.line_count(), 3);
let view = SourceView::new("a\r\nb\r\nc");
assert_eq!(view.get_line(0), Some("a"));
assert_eq!(view.get_line(0), Some("a"));
assert_eq!(view.get_line(2), Some("c"));
assert_eq!(view.get_line(1), Some("b"));
assert_eq!(view.get_line(3), None);
assert_eq!(view.line_count(), 3);
let view = SourceView::new("abc👌def\nblah");
assert_eq!(view.get_line_slice(0, 0, 3), Some("abc"));
assert_eq!(view.get_line_slice(0, 3, 1), Some("👌"));
assert_eq!(view.get_line_slice(0, 3, 2), Some("👌"));
assert_eq!(view.get_line_slice(0, 3, 3), Some("👌d"));
assert_eq!(view.get_line_slice(0, 0, 4), Some("abc👌"));
assert_eq!(view.get_line_slice(0, 0, 5), Some("abc👌"));
assert_eq!(view.get_line_slice(0, 0, 6), Some("abc👌d"));
assert_eq!(view.get_line_slice(1, 0, 4), Some("blah"));
assert_eq!(view.get_line_slice(1, 0, 5), None);
assert_eq!(view.get_line_slice(1, 0, 12), None);
let view = SourceView::new("a\nb\nc\n");
assert_eq!(view.get_line(0), Some("a"));
assert_eq!(view.get_line(1), Some("b"));
assert_eq!(view.get_line(2), Some("c"));
assert_eq!(view.get_line(3), Some(""));
assert_eq!(view.get_line(4), None);
}

1581
third-party/vendor/sourcemap/src/types.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,227 @@
use std::borrow::Cow;
use std::iter::repeat;
fn split_path(path: &str) -> Vec<&str> {
let mut last_idx = 0;
let mut rv = vec![];
for (idx, _) in path.match_indices(&['/', '\\'][..]) {
rv.push(&path[last_idx..idx]);
last_idx = idx;
}
if last_idx < path.len() {
rv.push(&path[last_idx..]);
}
rv
}
fn is_abs_path(s: &str) -> bool {
if s.starts_with('/') {
return true;
} else if s.len() > 3 {
let b = s.as_bytes();
if b[1] == b':'
&& (b[2] == b'/' || b[2] == b'\\')
&& ((b[0] >= b'a' && b[0] <= b'z') || (b[0] >= b'A' && b[0] <= b'Z'))
{
return true;
}
}
false
}
fn find_common_prefix_of_sorted_vec<'a>(items: &'a [Cow<'a, [&'a str]>]) -> Option<&'a [&'a str]> {
if items.is_empty() {
return None;
}
let shortest = &items[0];
let mut max_idx = None;
for seq in items.iter() {
let mut seq_max_idx = None;
for (idx, &comp) in shortest.iter().enumerate() {
if seq.get(idx) != Some(&comp) {
break;
}
seq_max_idx = Some(idx);
}
if max_idx.is_none() || seq_max_idx < max_idx {
max_idx = seq_max_idx;
}
}
if let Some(max_idx) = max_idx {
Some(&shortest[..=max_idx])
} else {
None
}
}
pub fn find_common_prefix<'a, I: Iterator<Item = &'a str>>(iter: I) -> Option<String> {
let mut items: Vec<Cow<'_, [&str]>> = iter
.filter(|x| is_abs_path(x))
.map(|x| Cow::Owned(split_path(x)))
.collect();
items.sort_by_key(|x| x.len());
if let Some(slice) = find_common_prefix_of_sorted_vec(&items) {
let rv = slice.join("");
if !rv.is_empty() && &rv != "/" {
return Some(rv);
}
}
None
}
/// Helper function to calculate the path from a base file to a target file.
///
/// This is intended to caculate the path from a minified JavaScript file
/// to a sourcemap if they are both on the same server.
///
/// Example:
///
/// ```
/// # use sourcemap::make_relative_path;
/// assert_eq!(&make_relative_path(
/// "/foo/bar/baz.js", "/foo/baz.map"), "../baz.map");
/// ```
pub fn make_relative_path(base: &str, target: &str) -> String {
let target_path: Vec<_> = target
.split(&['/', '\\'][..])
.filter(|x| !x.is_empty())
.collect();
let mut base_path: Vec<_> = base
.split(&['/', '\\'][..])
.filter(|x| !x.is_empty())
.collect();
base_path.pop();
let mut items = vec![
Cow::Borrowed(target_path.as_slice()),
Cow::Borrowed(base_path.as_slice()),
];
items.sort_by_key(|x| x.len());
let prefix = find_common_prefix_of_sorted_vec(&items)
.map(|x| x.len())
.unwrap_or(0);
let mut rel_list: Vec<_> = repeat("../").take(base_path.len() - prefix).collect();
rel_list.extend_from_slice(&target_path[prefix..]);
if rel_list.is_empty() {
".".into()
} else {
rel_list.join("")
}
}
pub fn greatest_lower_bound<'a, T, K: Ord, F: Fn(&'a T) -> K>(
slice: &'a [T],
key: &K,
map: F,
) -> Option<&'a T> {
let mut idx = match slice.binary_search_by_key(key, &map) {
Ok(index) => index,
Err(index) => {
// If there is no match, then we know for certain that the index is where we should
// insert a new token, and that the token directly before is the greatest lower bound.
return slice.get(index.checked_sub(1)?);
}
};
// If we get an exact match, then we need to continue looking at previous tokens to see if
// they also match. We use a linear search because the number of exact matches is generally
// very small, and almost certainly smaller than the number of tokens before the index.
for i in (0..idx).rev() {
if map(&slice[i]) == *key {
idx = i;
} else {
break;
}
}
slice.get(idx)
}
#[test]
fn test_is_abs_path() {
assert!(is_abs_path("C:\\foo.txt"));
assert!(is_abs_path("d:/foo.txt"));
assert!(!is_abs_path("foo.txt"));
assert!(is_abs_path("/foo.txt"));
assert!(is_abs_path("/"));
}
#[test]
fn test_split_path() {
assert_eq!(split_path("/foo/bar/baz"), &["", "/foo", "/bar", "/baz"]);
}
#[test]
fn test_find_common_prefix() {
let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah"].into_iter());
assert_eq!(rv, Some("/foo/bar/baz".into()));
let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/meh"].into_iter());
assert_eq!(rv, None);
let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/foo"].into_iter());
assert_eq!(rv, Some("/foo".into()));
let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "foo"].into_iter());
assert_eq!(rv, Some("/foo/bar/baz".into()));
let rv =
find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/blah", "foo"].into_iter());
assert_eq!(rv, None);
let rv =
find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/blah", "foo"].into_iter());
assert_eq!(rv, None);
}
#[test]
fn test_make_relative_path() {
assert_eq!(
&make_relative_path("/foo/bar/baz.js", "/foo/bar/baz.map"),
"baz.map"
);
assert_eq!(
&make_relative_path("/foo/bar/.", "/foo/bar/baz.map"),
"baz.map"
);
assert_eq!(
&make_relative_path("/foo/bar/baz.js", "/foo/baz.map"),
"../baz.map"
);
assert_eq!(&make_relative_path("foo.txt", "foo.js"), "foo.js");
assert_eq!(&make_relative_path("blah/foo.txt", "foo.js"), "../foo.js");
}
#[test]
fn test_greatest_lower_bound() {
let cmp = |&(i, _id)| i;
let haystack = vec![(1, 1)];
assert_eq!(greatest_lower_bound(&haystack, &1, cmp), Some(&(1, 1)));
assert_eq!(greatest_lower_bound(&haystack, &2, cmp), Some(&(1, 1)));
assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
let haystack = vec![(1, 1), (1, 2)];
assert_eq!(greatest_lower_bound(&haystack, &1, cmp), Some(&(1, 1)));
assert_eq!(greatest_lower_bound(&haystack, &2, cmp), Some(&(1, 2)));
assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
let haystack = vec![(1, 1), (1, 2), (1, 3)];
assert_eq!(greatest_lower_bound(&haystack, &1, cmp), Some(&(1, 1)));
assert_eq!(greatest_lower_bound(&haystack, &2, cmp), Some(&(1, 3)));
assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
let haystack = vec![(1, 1), (1, 2), (1, 3), (1, 4)];
assert_eq!(greatest_lower_bound(&haystack, &1, cmp), Some(&(1, 1)));
assert_eq!(greatest_lower_bound(&haystack, &2, cmp), Some(&(1, 4)));
assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
let haystack = vec![(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)];
assert_eq!(greatest_lower_bound(&haystack, &1, cmp), Some(&(1, 1)));
assert_eq!(greatest_lower_bound(&haystack, &2, cmp), Some(&(1, 5)));
assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None);
}

355
third-party/vendor/sourcemap/src/vlq.rs vendored Normal file
View file

@ -0,0 +1,355 @@
//! Implements utilities for dealing with the sourcemap vlq encoding.
use crate::errors::{Error, Result};
const B64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const B64: [i8; 256] = [
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
62,
-1,
-1,
-1,
63,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
0,
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,
-1,
-1,
-1,
-1,
-1,
-1,
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,
-1,
-1,
-1,
-1,
-1 - 1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
];
/// Parses a VLQ segment into a vector.
pub fn parse_vlq_segment(segment: &str) -> Result<Vec<i64>> {
let mut rv = vec![];
parse_vlq_segment_into(segment, &mut rv)?;
Ok(rv)
}
/// Parses a VLQ segment into a pre-allocated `Vec` instead of returning a new allocation.
pub(crate) fn parse_vlq_segment_into(segment: &str, rv: &mut Vec<i64>) -> Result<()> {
let mut cur = 0;
let mut shift = 0;
for c in segment.bytes() {
let enc = i64::from(B64[c as usize]);
let val = enc & 0b11111;
let cont = enc >> 5;
cur += val.checked_shl(shift).ok_or(Error::VlqOverflow)?;
shift += 5;
if cont == 0 {
let sign = cur & 1;
cur >>= 1;
if sign != 0 {
cur = -cur;
}
rv.push(cur);
cur = 0;
shift = 0;
}
}
if cur != 0 || shift != 0 {
Err(Error::VlqLeftover)
} else if rv.is_empty() {
Err(Error::VlqNoValues)
} else {
Ok(())
}
}
/// Encodes a VLQ segment from a slice.
pub fn generate_vlq_segment(nums: &[i64]) -> Result<String> {
let mut rv = String::new();
for &num in nums {
encode_vlq(&mut rv, num);
}
Ok(rv)
}
pub(crate) fn encode_vlq(out: &mut String, num: i64) {
let mut num = if num < 0 { ((-num) << 1) + 1 } else { num << 1 };
loop {
let mut digit = num & 0b11111;
num >>= 5;
if num > 0 {
digit |= 1 << 5;
}
out.push(B64_CHARS[digit as usize] as char);
if num == 0 {
break;
}
}
}
#[test]
fn test_vlq_decode() {
let rv = parse_vlq_segment("AAAA").unwrap();
assert_eq!(rv, vec![0, 0, 0, 0]);
let rv = parse_vlq_segment("GAAIA").unwrap();
assert_eq!(rv, vec![3, 0, 0, 4, 0]);
}
#[test]
fn test_vlq_encode() {
let rv = generate_vlq_segment(&[0, 0, 0, 0]).unwrap();
assert_eq!(rv.as_str(), "AAAA");
let rv = generate_vlq_segment(&[3, 0, 0, 4, 0]).unwrap();
assert_eq!(rv.as_str(), "GAAIA");
}
#[test]
fn test_overflow() {
match parse_vlq_segment("00000000000000") {
Err(Error::VlqOverflow) => {}
e => {
panic!("Unexpeted result: {:?}", e);
}
}
}