fix(plugin): Make span_to_source use dedicated function (#6853)

**Related issue:**

 - Closes https://github.com/swc-project/swc/issues/6852.
 - Closes https://github.com/swc-project/swc/issues/6404.
This commit is contained in:
Donny/강동윤 2023-01-26 11:09:36 +09:00 committed by GitHub
parent 28be71dfaf
commit 99d48e305e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2207 additions and 91 deletions

12
.vscode/settings.json vendored
View File

@ -23,10 +23,14 @@
"crates/swc_html_parser/tests/html5lib-tests"
],
"eslint.enable": false,
"cSpell.allowCompoundWords": true,
"cSpell.caseSensitive": true,
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.checkOnSave.features": [
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.features": [
// We use this to make IDE faster
"rust-analyzer",
"rkyv-impl",
"debug"
],
"rust-analyzer.cargo.features": [
// We use this to make IDE faster
"rust-analyzer",
"rkyv-impl",

View File

@ -1267,6 +1267,10 @@ pub enum SpanLinesError {
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(
any(feature = "rkyv-impl", feature = "rkyv-bytecheck-impl"),
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub enum SpanSnippetError {
DummyBytePos,
IllFormedSpan(Span),
@ -1286,6 +1290,10 @@ pub struct DistinctSources {
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(
any(feature = "rkyv-impl", feature = "rkyv-bytecheck-impl"),
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct MalformedSourceMapPositions {
pub name: FileName,
pub source_len: usize,

View File

@ -45,6 +45,12 @@ extern "C" {
span_ctxt: u32,
allocated_ret_ptr: u32,
) -> u32;
fn __span_to_source_proxy(
span_lo: u32,
span_hi: u32,
span_ctxt: u32,
allocated_ret_ptr: u32,
) -> u32;
fn __span_to_lines_proxy(
span_lo: u32,
span_hi: u32,
@ -67,49 +73,6 @@ pub struct PluginSourceMapProxy {
#[cfg(all(feature = "__rkyv", feature = "__plugin_mode", target_arch = "wasm32"))]
#[swc_trace]
impl PluginSourceMapProxy {
/*
fn sss<F, Ret>(&self, sp: Span, extract_source: F) -> Result<Ret, Box<SpanSnippetError>>
where
F: FnOnce(&str, usize, usize) -> Ret,
{
if sp.lo() > sp.hi() {
return Err(SpanSnippetError::IllFormedSpan(sp));
}
if sp.lo.is_dummy() || sp.hi.is_dummy() {
return Err(SpanSnippetError::DummyBytePos);
}
let local_begin = self.lookup_byte_offset(sp.lo());
let local_end = self.lookup_byte_offset(sp.hi());
if local_begin.sf.start_pos != local_end.sf.start_pos {
Err(SpanSnippetError::DistinctSources(DistinctSources {
begin: (local_begin.sf.name.clone(), local_begin.sf.start_pos),
end: (local_end.sf.name.clone(), local_end.sf.start_pos),
}))
} else {
let pos: BytePos = BytePos(0);
let start_index = pos.to_usize();
let end_index = local_end.pos.to_usize();
let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
if start_index > end_index || end_index > source_len {
return Err(SpanSnippetError::MalformedForSourcemap(
MalformedSourceMapPositions {
name: local_begin.sf.name.clone(),
source_len,
begin_pos: local_begin.pos,
end_pos: local_end.pos,
},
));
}
let src = &local_begin.sf.src;
Ok(extract_source(src, start_index, end_index))
}
}
*/
pub fn span_to_source<F, Ret>(
&self,
sp: Span,
@ -120,50 +83,14 @@ impl PluginSourceMapProxy {
{
#[cfg(target_arch = "wasm32")]
{
if sp.lo() > sp.hi() {
return Err(Box::new(SpanSnippetError::IllFormedSpan(sp)));
}
if sp.lo.is_dummy() || sp.hi.is_dummy() {
return Err(Box::new(SpanSnippetError::DummyBytePos));
}
let local_begin: SourceFileAndBytePos =
let src: Result<String, Box<SpanSnippetError>> =
read_returned_result_from_host_fallible(|serialized_ptr| unsafe {
__lookup_byte_offset_proxy(sp.lo().0, serialized_ptr)
__span_to_source_proxy(sp.lo.0, sp.hi.0, sp.ctxt.as_u32(), serialized_ptr)
})
.expect("Should return begin offset");
let local_end: SourceFileAndBytePos =
read_returned_result_from_host_fallible(|serialized_ptr| unsafe {
__lookup_byte_offset_proxy(sp.hi().0, serialized_ptr)
})
.expect("Should return end offset");
.expect("Host should return source code");
if local_begin.sf.start_pos != local_end.sf.start_pos {
Err(Box::new(SpanSnippetError::DistinctSources(
DistinctSources {
begin: (local_begin.sf.name.clone(), local_begin.sf.start_pos),
end: (local_end.sf.name.clone(), local_end.sf.start_pos),
},
)))
} else {
let start_index = local_begin.pos.to_usize();
let end_index = local_end.pos.to_usize();
let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
if start_index > end_index || end_index > source_len {
return Err(Box::new(SpanSnippetError::MalformedForSourcemap(
MalformedSourceMapPositions {
name: local_begin.sf.name.clone(),
source_len,
begin_pos: local_begin.pos,
end_pos: local_end.pos,
},
)));
}
let src = &local_begin.sf.src;
return Ok(extract_source(src, start_index, end_index));
}
let src = src?;
return Ok(extract_source(&src, 0, src.len()));
}
#[cfg(not(target_arch = "wasm32"))]

View File

@ -83,8 +83,8 @@ use self::{
},
source_map::{
doctest_offset_line_proxy, lookup_byte_offset_proxy, lookup_char_pos_proxy,
merge_spans_proxy, span_to_filename_proxy, span_to_lines_proxy, span_to_string_proxy,
SourceMapHostEnvironment,
merge_spans_proxy, span_to_filename_proxy, span_to_lines_proxy, span_to_source_proxy,
span_to_string_proxy, SourceMapHostEnvironment,
},
};
@ -281,6 +281,12 @@ pub(crate) fn build_import_object(
span_to_filename_proxy,
);
let span_to_source_fn_decl = Function::new_native_with_env(
store,
SourceMapHostEnvironment::new(&source_map, &source_map_buffer),
span_to_source_proxy,
);
let span_to_lines_fn_decl = Function::new_native_with_env(
store,
SourceMapHostEnvironment::new(&source_map, &source_map_buffer),
@ -339,6 +345,7 @@ pub(crate) fn build_import_object(
"__merge_spans_proxy" => merge_spans_fn_decl,
"__span_to_string_proxy" => span_to_string_fn_decl,
"__span_to_filename_proxy" => span_to_filename_fn_decl,
"__span_to_source_proxy" => span_to_source_fn_decl,
"__span_to_lines_proxy" => span_to_lines_fn_decl,
"__lookup_byte_offset_proxy" => lookup_byte_offset_fn_decl
}

View File

@ -4,7 +4,7 @@ use parking_lot::Mutex;
use swc_common::{
plugin::serialized::PluginSerializedBytes,
source_map::{PartialFileLines, PartialLoc},
BytePos, SourceMap, Span, SyntaxContext,
BytePos, SourceMap, SourceMapper, Span, SyntaxContext,
};
use wasmer::{LazyInit, Memory, NativeFunc};
@ -267,3 +267,37 @@ pub fn span_to_filename_proxy(
0
}
}
#[tracing::instrument(level = "info", skip_all)]
pub fn span_to_source_proxy(
env: &SourceMapHostEnvironment,
span_lo: u32,
span_hi: u32,
span_ctxt: u32,
allocated_ret_ptr: u32,
) -> i32 {
if let Some(memory) = env.memory_ref() {
let span = Span {
lo: BytePos(span_lo),
hi: BytePos(span_hi),
ctxt: SyntaxContext::from_u32(span_ctxt),
};
let ret = (env.source_map.lock()).span_to_snippet(span);
let serialized_loc_bytes =
PluginSerializedBytes::try_serialize(&ret).expect("Should be serializable");
if let Some(alloc_guest_memory) = env.alloc_guest_memory_ref() {
allocate_return_values_into_guest(
memory,
alloc_guest_memory,
allocated_ret_ptr,
&serialized_loc_bytes,
);
1
} else {
0
}
} else {
0
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
[workspace]
[package]
edition = "2021"
name = "swc_issue_6404"
publish = false
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = "1"
swc_core = { path = "../../../../swc_core", features = [
"ecma_plugin_transform",
"ecma_quote",
] }

View File

@ -0,0 +1,21 @@
use swc_core::{
common::{BytePos, SourceMapper, Span, SyntaxContext},
ecma::ast::*,
plugin::{metadata::TransformPluginProgramMetadata, plugin_transform},
};
#[plugin_transform]
pub fn process_transform(program: Program, metadata: TransformPluginProgramMetadata) -> Program {
for i in 1..50000 {
let j: u32 = i;
println!("i {} j {}", i, j);
let res = metadata.source_map.span_to_snippet(Span::new(
BytePos(j),
BytePos(j + 1000),
SyntaxContext::empty(),
));
let _ = dbg!(res);
}
program
}

View File

@ -0,0 +1,146 @@
#![cfg_attr(not(feature = "__rkyv"), allow(warnings))]
use std::{
env, fs,
path::{Path, PathBuf},
process::{Command, Stdio},
sync::Arc,
};
use anyhow::{anyhow, Error};
use serde_json::json;
#[cfg(feature = "__rkyv")]
use swc_common::plugin::serialized::PluginSerializedBytes;
use swc_common::{
collections::AHashMap, plugin::metadata::TransformPluginMetadataContext, sync::Lazy, Mark,
};
use swc_ecma_ast::{CallExpr, Callee, EsVersion, Expr, Lit, MemberExpr, Program, Str};
use swc_ecma_parser::{parse_file_as_program, EsConfig, Syntax};
use swc_ecma_visit::Visit;
use swc_plugin_runner::cache::PluginModuleCache;
/// Returns the path to the built plugin
fn build_plugin(dir: &Path, crate_name: &str) -> Result<PathBuf, Error> {
{
let mut cmd = Command::new("cargo");
cmd.current_dir(dir);
cmd.args(["build", "--release", "--target=wasm32-wasi"])
.stderr(Stdio::inherit());
cmd.output()?;
if !cmd
.status()
.expect("Exit code should be available")
.success()
{
return Err(anyhow!("Failed to build plugin"));
}
}
for entry in fs::read_dir(&dir.join("target").join("wasm32-wasi").join("release"))? {
let entry = entry?;
let s = entry.file_name().to_string_lossy().into_owned();
if s.eq_ignore_ascii_case(&format!("{}.wasm", crate_name)) {
return Ok(entry.path());
}
}
Err(anyhow!("Could not find built plugin"))
}
struct TestVisitor {
pub plugin_transform_found: bool,
}
impl Visit for TestVisitor {
fn visit_call_expr(&mut self, call: &CallExpr) {
if let Callee::Expr(expr) = &call.callee {
if let Expr::Member(MemberExpr { obj, .. }) = &**expr {
if let Expr::Ident(ident) = &**obj {
if ident.sym == *"console" {
let args = &*(call.args[0].expr);
if let Expr::Lit(Lit::Str(Str { value, .. })) = args {
self.plugin_transform_found = value == "changed_via_plugin";
}
}
}
}
}
}
}
#[cfg(feature = "__rkyv")]
#[test]
fn issue_6404() -> Result<(), Error> {
let plugin_path = build_plugin(
&PathBuf::from(env::var("CARGO_MANIFEST_DIR")?)
.join("tests")
.join("fixture")
.join("issue_6404"),
"swc_issue_6404",
)?;
dbg!("Built!");
// run single plugin
testing::run_test(false, |cm, _handler| {
let fm = cm
.load_file("../swc_ecma_minifier/benches/full/typescript.js".as_ref())
.unwrap();
let program = parse_file_as_program(
&fm,
Syntax::Es(EsConfig {
..Default::default()
}),
EsVersion::latest(),
None,
&mut vec![],
)
.unwrap();
let program = PluginSerializedBytes::try_serialize(&program).expect("Should serializable");
let experimental_metadata: AHashMap<String, String> = [
(
"TestExperimental".to_string(),
"ExperimentalValue".to_string(),
),
("OtherTest".to_string(), "OtherVal".to_string()),
]
.into_iter()
.collect();
let cache: Lazy<PluginModuleCache> = Lazy::new(PluginModuleCache::new);
let mut plugin_transform_executor = swc_plugin_runner::create_plugin_transform_executor(
&plugin_path,
&cache,
&cm,
&Arc::new(TransformPluginMetadataContext::new(
None,
"development".to_string(),
Some(experimental_metadata),
)),
Some(json!({ "pluginConfig": "testValue" })),
)
.expect("Should load plugin");
assert!(!plugin_transform_executor
.plugin_core_diag
.pkg_version
.is_empty());
let program_bytes = plugin_transform_executor
.transform(&program, Mark::new(), false)
.expect("Plugin should apply transform");
let _: Program = program_bytes
.deserialize()
.expect("Should able to deserialize");
Ok(())
})
.expect("Should able to run single plugin transform");
Ok(())
}