mirror of
https://github.com/swc-project/swc.git
synced 2024-11-23 17:54:15 +03:00
feat(plugin): Add PluginError
(#3300)
This commit is contained in:
parent
5a08327784
commit
c6ffdc8717
@ -8,6 +8,34 @@ use crate::{syntax_pos::Mark, SyntaxContext};
|
||||
use anyhow::Error;
|
||||
use std::any::type_name;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(
|
||||
feature = "plugin-base",
|
||||
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
|
||||
)]
|
||||
/// Enum for possible errors while running transform via plugin.
|
||||
/// Mostly this enum is being used to bubble up error occurred inside of plugin
|
||||
/// runtime which we can't transparently pass forward Error itself. Host (SWC)
|
||||
/// will try to attach context if possible then raise it as Error.
|
||||
pub enum PluginError {
|
||||
/// Occurs when failed to convert size passed from host / guest into usize
|
||||
/// or similar for the conversion. This is an internal error rasied via
|
||||
/// plugin_macro, noramlly plugin author should not raise this manually.
|
||||
SizeInteropFailure(String),
|
||||
/// Occurs when failed to reconstruct a struct from `Serialized`.
|
||||
Deserialize((String, Vec<u8>)),
|
||||
/// Occurs when failed to serialize a struct into `Serialized`.
|
||||
/// Unlike deserialize error, this error cannot forward any context for the
|
||||
/// raw bytes: when serialze failed, there's nothing we can pass between
|
||||
/// runtime.
|
||||
Serialize(String),
|
||||
/// When plugin fails to transform for some reason.
|
||||
/// This is incomplete yet as it is unclear what kind of data plugin want to
|
||||
/// forward into host.
|
||||
Transform,
|
||||
}
|
||||
|
||||
/// Wraps internal representation of serialized data. Consumers should not
|
||||
/// rely on specific details of byte format struct contains: it is
|
||||
/// strictly implementation detail which can change anytime.
|
||||
|
@ -1,5 +1,9 @@
|
||||
// Reexports
|
||||
pub use swc_common::{chain, plugin::Serialized, DUMMY_SP};
|
||||
pub use swc_common::{
|
||||
chain,
|
||||
plugin::{PluginError, Serialized},
|
||||
DUMMY_SP,
|
||||
};
|
||||
pub mod ast {
|
||||
pub use swc_atoms::*;
|
||||
pub use swc_ecma_ast::*;
|
||||
|
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
description = "Base for swc plugins"
|
||||
documentation = "https://rustdoc.swc.rs/swc_plugin_build/"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
name = "swc_plugin_build"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.1.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.41"
|
@ -1 +0,0 @@
|
||||
pub fn setup() {}
|
@ -25,41 +25,101 @@ fn handle_func(func: ItemFn) -> TokenStream {
|
||||
let ret = quote! {
|
||||
#func
|
||||
|
||||
use swc_plugin::Serialized;
|
||||
/// Internal function plugin_macro uses to create ptr to PluginError.
|
||||
fn construct_error_ptr(plugin_error: PluginError) -> (i32, i32, i32) {
|
||||
let ret = swc_plugin::Serialized::serialize(&plugin_error).expect("Should able to serialize PluginError");
|
||||
let ret_ref = ret.as_ref();
|
||||
|
||||
// TODO:
|
||||
// error handling & bubbling
|
||||
(
|
||||
1,
|
||||
ret_ref.as_ptr() as _,
|
||||
ret_ref.len().try_into().expect("Should able to convert size of PluginError")
|
||||
)
|
||||
}
|
||||
|
||||
// Macro to allow compose plugin's transform function without manual pointer operation.
|
||||
// Internally it wraps pointer operation also bubbles up error in forms of PluginError.
|
||||
// There are some cases error won't be wrapped up however - for example, we expect
|
||||
// serialization of PluginError itself should succeed.
|
||||
#[no_mangle]
|
||||
pub fn #process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32) -> (i32, i32) {
|
||||
pub fn #process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32) -> (i32, i32, i32) {
|
||||
let ast_ptr_len_usize: Result<usize, std::num::TryFromIntError> = ast_ptr_len.try_into();
|
||||
let config_str_ptr_len_usize: Result<usize, std::num::TryFromIntError> = config_str_ptr_len.try_into();
|
||||
|
||||
if ast_ptr_len_usize.is_err() {
|
||||
let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of AST pointer".to_string());
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
if config_str_ptr_len_usize.is_err() {
|
||||
let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of plugin config string pointer".to_string());
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
// Read raw serialized bytes from wasm's memory space. Host (SWC) should
|
||||
// allocate memory, copy bytes and pass ptr to plugin.
|
||||
let raw_ast_serialized_bytes =
|
||||
unsafe { std::slice::from_raw_parts(ast_ptr, ast_ptr_len.try_into().unwrap()) };
|
||||
|
||||
unsafe { std::slice::from_raw_parts(ast_ptr, ast_ptr_len_usize.unwrap()) };
|
||||
let raw_config_serialized_bytes =
|
||||
unsafe { std::slice::from_raw_parts(config_str_ptr, config_str_ptr_len.try_into().unwrap()) };
|
||||
unsafe { std::slice::from_raw_parts(config_str_ptr, config_str_ptr_len_usize.unwrap()) };
|
||||
|
||||
// Reconstruct SerializedProgram from raw bytes
|
||||
let serialized_program = Serialized::new_for_plugin(raw_ast_serialized_bytes, ast_ptr_len);
|
||||
let serialized_config = Serialized::new_for_plugin(raw_config_serialized_bytes, config_str_ptr_len);
|
||||
let serialized_program = swc_plugin::Serialized::new_for_plugin(raw_ast_serialized_bytes, ast_ptr_len);
|
||||
let serialized_config = swc_plugin::Serialized::new_for_plugin(raw_config_serialized_bytes, config_str_ptr_len);
|
||||
|
||||
// TODO: Returning error pointer instead of unwrap
|
||||
let program: Program =
|
||||
Serialized::deserialize(&serialized_program).expect("Should able to deserialize");
|
||||
let config: String = Serialized::deserialize(&serialized_config).expect("Should able to deserialize");
|
||||
// Reconstruct `Program` & config string from serialized program
|
||||
let program = swc_plugin::Serialized::deserialize(&serialized_program);
|
||||
let config = swc_plugin::Serialized::deserialize(&serialized_config);
|
||||
|
||||
// Take original plugin fn ident, then call it with interop-ed args
|
||||
let transformed_program = #ident(program, config).expect("Should return program for now");
|
||||
if program.is_err() {
|
||||
let err = swc_plugin::PluginError::Deserialize(
|
||||
("Failed to deserialize program received from host".to_string(),
|
||||
raw_ast_serialized_bytes.to_vec())
|
||||
);
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
let program = program.expect("Should be a program");
|
||||
|
||||
if config.is_err() {
|
||||
let err = swc_plugin::PluginError::Deserialize(
|
||||
("Failed to deserialize config string received from host".to_string(),
|
||||
raw_config_serialized_bytes.to_vec())
|
||||
);
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
let config = config.expect("Should be a string");
|
||||
|
||||
// Take original plugin fn ident, then call it with interop'ed args
|
||||
let transformed_program = #ident(program, config);
|
||||
if transformed_program.is_err() {
|
||||
let err = transformed_program.expect_err("Should be an error");
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
let transformed_program = transformed_program.expect("Should be a transformed program");
|
||||
|
||||
// Serialize transformed result, return back to the host.
|
||||
let serialized_result =
|
||||
Serialized::serialize(&transformed_program).expect("Should serializable");
|
||||
let serialized_result = swc_plugin::Serialized::serialize(&transformed_program);
|
||||
|
||||
if serialized_result.is_err() {
|
||||
let err = swc_plugin::PluginError::Serialize("Failed to serialize transformed program".to_string());
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
let serialized_result = serialized_result.expect("Should be a realized transformed program");
|
||||
let serialized_result = serialized_result.as_ref();
|
||||
|
||||
let serialized_result_len: Result<i32, std::num::TryFromIntError> = serialized_result.len().try_into();
|
||||
|
||||
if serialized_result_len.is_err() {
|
||||
let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of transformed AST pointer".to_string());
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
(
|
||||
0,
|
||||
serialized_result.as_ptr() as _,
|
||||
serialized_result.len().try_into().unwrap(),
|
||||
serialized_result_len.expect("Should be an i32"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
@ -7,7 +7,10 @@ use anyhow::{anyhow, Context, Error};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use resolve::PluginCache;
|
||||
use swc_common::{collections::AHashMap, plugin::Serialized};
|
||||
use swc_common::{
|
||||
collections::AHashMap,
|
||||
plugin::{PluginError, Serialized},
|
||||
};
|
||||
use wasmer::{imports, Array, Exports, Instance, Memory, MemoryType, Module, Store, WasmPtr};
|
||||
use wasmer_cache::{Cache, Hash};
|
||||
use wasmer_wasi::{get_wasi_version, WasiState};
|
||||
@ -140,7 +143,7 @@ fn load_plugin(plugin_path: &Path, cache: &mut Option<PluginCache>) -> Result<In
|
||||
/// teardown
|
||||
struct PluginTransformTracker {
|
||||
// Main transform interface plugin exports
|
||||
exported_plugin_transform: wasmer::NativeFunc<(i32, i32, i32, i32), (i32, i32)>,
|
||||
exported_plugin_transform: wasmer::NativeFunc<(i32, i32, i32, i32), (i32, i32, i32)>,
|
||||
// `__free` function automatically exported via swc_plugin sdk to allow deallocation in guest
|
||||
// memory space
|
||||
exported_plugin_free: wasmer::NativeFunc<(i32, i32), i32>,
|
||||
@ -159,7 +162,9 @@ impl PluginTransformTracker {
|
||||
let tracker = PluginTransformTracker {
|
||||
exported_plugin_transform: instance
|
||||
.exports
|
||||
.get_native_function::<(i32, i32, i32, i32), (i32, i32)>("__plugin_process_impl")?,
|
||||
.get_native_function::<(i32, i32, i32, i32), (i32, i32, i32)>(
|
||||
"__plugin_process_impl",
|
||||
)?,
|
||||
exported_plugin_free: instance
|
||||
.exports
|
||||
.get_native_function::<(i32, i32), i32>("__free")?,
|
||||
@ -209,7 +214,12 @@ impl PluginTransformTracker {
|
||||
|
||||
/// Copy guest's memory into host, construct serialized struct from raw
|
||||
/// bytes.
|
||||
fn read_bytes_from_guest(&mut self, returned_ptr: i32, len: i32) -> Result<Serialized, Error> {
|
||||
fn read_bytes_from_guest(
|
||||
&mut self,
|
||||
returned_ptr_result: i32,
|
||||
returned_ptr: i32,
|
||||
len: i32,
|
||||
) -> Result<Serialized, Error> {
|
||||
let memory = self.instance.exports.get_memory("memory")?;
|
||||
let ptr: WasmPtr<u8, Array> = WasmPtr::new(returned_ptr as _);
|
||||
|
||||
@ -225,7 +235,26 @@ impl PluginTransformTracker {
|
||||
.map(|(_size, cell)| cell.get())
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
Ok(Serialized::new_for_plugin(&transformed_raw_bytes[..], len))
|
||||
let ret = Serialized::new_for_plugin(&transformed_raw_bytes[..], len);
|
||||
|
||||
if returned_ptr_result == 0 {
|
||||
Ok(ret)
|
||||
} else {
|
||||
let err: PluginError = Serialized::deserialize(&ret)?;
|
||||
match err {
|
||||
PluginError::SizeInteropFailure(msg) => Err(anyhow!(
|
||||
"Failed to convert pointer size to calculate: {}",
|
||||
msg
|
||||
)),
|
||||
PluginError::Deserialize((msg, ..)) | PluginError::Serialize(msg) => {
|
||||
Err(anyhow!("{}", msg))
|
||||
}
|
||||
PluginError::Transform => Err(anyhow!("Failed to apply transform via plugin")),
|
||||
_ => Err(anyhow!(
|
||||
"Unexpected error occurred while running plugin transform"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform(
|
||||
@ -243,8 +272,10 @@ impl PluginTransformTracker {
|
||||
config_str_ptr.1,
|
||||
)?;
|
||||
|
||||
self.allocated_ptr_vec.push(returned_ptr);
|
||||
self.read_bytes_from_guest(returned_ptr.0, returned_ptr.1)
|
||||
self.allocated_ptr_vec
|
||||
.push((returned_ptr.1, returned_ptr.2));
|
||||
|
||||
self.read_bytes_from_guest(returned_ptr.0, returned_ptr.1, returned_ptr.2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,8 +289,6 @@ impl Drop for PluginTransformTracker {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// - error propagation from plugin
|
||||
pub fn apply_js_plugin(
|
||||
plugin_name: &str,
|
||||
path: &Path,
|
||||
|
@ -20,8 +20,11 @@ fn get_ids_by_name(module: &Module, function_name: &str) -> (ExportId, FunctionI
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// We'll polyfill return type of plugin's `process` interface to (i32, i32)
|
||||
let transformations = vec![("__plugin_process_impl", vec![ValType::I32, ValType::I32])];
|
||||
// We'll polyfill return type of plugin's `process` interface to (i32, i32, i32)
|
||||
let transformations = vec![(
|
||||
"__plugin_process_impl",
|
||||
vec![ValType::I32, ValType::I32, ValType::I32],
|
||||
)];
|
||||
|
||||
let target: Vec<String> = env::args().collect();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use swc_plugin::{ast::*, plugin_module, DUMMY_SP};
|
||||
use swc_plugin::{ast::*, plugin_module, PluginError, DUMMY_SP};
|
||||
|
||||
struct ConsoleOutputReplacer;
|
||||
|
||||
@ -28,13 +28,18 @@ impl VisitMut for ConsoleOutputReplacer {
|
||||
/// returning ptr back to host.
|
||||
///
|
||||
/// It is possible to opt out from macro by writing transform fn manually via
|
||||
/// `__plugin_process_impl(*const u8, i32, *const u8, i32) -> (i32,i32)`
|
||||
/// if plugin need to handle low-level ptr directly.
|
||||
/// `__plugin_process_impl(
|
||||
/// ast_ptr: *const u8,
|
||||
/// ast_ptr_len: i32,
|
||||
/// config_str_ptr: *const u8,
|
||||
/// config_str_ptr_len: i32) ->
|
||||
/// (is_ok: i32 /* 0 means success */,
|
||||
/// result_ptr: i32,
|
||||
/// result_ptr_length: i32)`
|
||||
///
|
||||
/// TODO:
|
||||
/// - better developer experience: println / dbg!() doesn't emit anything
|
||||
/// if plugin need to handle low-level ptr directly.
|
||||
#[plugin_module]
|
||||
pub fn process(program: Program, _plugin_config: String) -> Result<Program, std::fmt::Error> {
|
||||
pub fn process(program: Program, _plugin_config: String) -> Result<Program, PluginError> {
|
||||
let transformed_program = program.fold_with(&mut as_folder(ConsoleOutputReplacer));
|
||||
Ok(transformed_program)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user