swc_plugin_runner/
transform_executor.rs

1use std::{env, sync::Arc};
2
3use anyhow::{anyhow, Context, Error};
4use parking_lot::Mutex;
5#[cfg(feature = "__rkyv")]
6use swc_common::plugin::serialized::{PluginError, PluginSerializedBytes};
7#[cfg(any(
8    feature = "plugin_transform_schema_v1",
9    feature = "plugin_transform_schema_vtest"
10))]
11use swc_common::plugin::PLUGIN_TRANSFORM_AST_SCHEMA_VERSION;
12use swc_common::{
13    plugin::{diagnostics::PluginCorePkgDiagnostics, metadata::TransformPluginMetadataContext},
14    SourceMap,
15};
16
17#[cfg(feature = "__rkyv")]
18use crate::{
19    host_environment::BaseHostEnvironment,
20    imported_fn::{
21        build_import_object, comments::CommentHostEnvironment,
22        diagnostics::DiagnosticContextHostEnvironment,
23        metadata_context::MetadataContextHostEnvironment,
24        set_transform_result::TransformResultHostEnvironment, source_map::SourceMapHostEnvironment,
25    },
26    memory_interop::write_into_memory_view,
27};
28use crate::{plugin_module_bytes::PluginModuleBytes, runtime};
29
30/// An internal state to the plugin transform.
31struct PluginTransformState {
32    instance: Box<dyn runtime::Instance>,
33    transform_result: Arc<Mutex<Vec<u8>>>,
34    #[allow(unused)]
35    plugin_core_diag: PluginCorePkgDiagnostics,
36}
37
38#[cfg(feature = "__rkyv")]
39impl PluginTransformState {
40    fn run(
41        &mut self,
42        program: &PluginSerializedBytes,
43        unresolved_mark: swc_common::Mark,
44        should_enable_comments_proxy: Option<bool>,
45    ) -> Result<PluginSerializedBytes, Error> {
46        let should_enable_comments_proxy =
47            u32::from(should_enable_comments_proxy.unwrap_or_default());
48
49        // Copy host's serialized bytes into guest (plugin)'s allocated memory.
50        let guest_program_ptr = write_into_memory_view(
51            &mut *self.instance.caller()?,
52            program,
53            |caller, serialized_len| {
54                let serialized_len = serialized_len
55                    .try_into()
56                    .expect("Should able to convert size");
57                caller.alloc(serialized_len).unwrap_or_else(|_| {
58                    panic!("Should able to allocate memory for the size of {serialized_len}")
59                })
60            },
61        );
62
63        let returned_ptr_result = self.instance.transform(
64            guest_program_ptr.0,
65            guest_program_ptr.1,
66            unresolved_mark.as_u32(),
67            should_enable_comments_proxy,
68        )?;
69
70        // Copy guest's memory into host, construct serialized struct from raw
71        // bytes.
72        let transformed_result = &(*self.transform_result.lock());
73        let ret = PluginSerializedBytes::from_slice(&transformed_result[..]);
74
75        let ret = if returned_ptr_result == 0 {
76            Ok(ret)
77        } else {
78            let err: PluginError = ret.deserialize()?.into_inner();
79            match err {
80                PluginError::SizeInteropFailure(msg) => Err(anyhow!(
81                    "Failed to convert pointer size to calculate: {}",
82                    msg
83                )),
84                PluginError::Deserialize(msg) | PluginError::Serialize(msg) => {
85                    Err(anyhow!("{}", msg))
86                }
87                _ => Err(anyhow!(
88                    "Unexpected error occurred while running plugin transform"
89                )),
90            }
91        };
92
93        self.instance
94            .caller()?
95            .free(guest_program_ptr.0, guest_program_ptr.1)?;
96        self.instance.cleanup()?;
97
98        ret
99    }
100
101    /**
102     * Check compile-time version of AST schema between the plugin and
103     * the host. Returns true if it's compatible, false otherwise.
104     *
105     * Host should appropriately handle if plugin is not compatible to the
106     * current runtime.
107     */
108    #[allow(unreachable_code)]
109    pub fn is_transform_schema_compatible(&mut self) -> Result<(), Error> {
110        #[cfg(any(
111            feature = "plugin_transform_schema_v1",
112            feature = "plugin_transform_schema_vtest"
113        ))]
114        return {
115            let host_schema_version = PLUGIN_TRANSFORM_AST_SCHEMA_VERSION;
116
117            // TODO: this is incomplete
118            if host_schema_version >= self.plugin_core_diag.ast_schema_version {
119                Ok(())
120            } else {
121                anyhow::bail!(
122                    "Plugin's AST schema version is not compatible with host's. Host: {}, Plugin: \
123                     {}",
124                    host_schema_version,
125                    self.plugin_core_diag.ast_schema_version
126                )
127            }
128        };
129
130        #[cfg(not(all(
131            feature = "plugin_transform_schema_v1",
132            feature = "plugin_transform_schema_vtest"
133        )))]
134        anyhow::bail!(
135            "Plugin runner cannot detect plugin's schema version. Ensure host is compiled with \
136             proper versions"
137        )
138    }
139}
140
141/// A struct encapsule executing a plugin's transform.
142pub struct TransformExecutor {
143    source_map: Arc<SourceMap>,
144    unresolved_mark: swc_common::Mark,
145    metadata_context: Arc<TransformPluginMetadataContext>,
146    plugin_env_vars: Option<Arc<Vec<swc_atoms::Atom>>>,
147    plugin_config: Option<serde_json::Value>,
148    module_bytes: Box<dyn PluginModuleBytes>,
149    runtime: Arc<dyn runtime::Runtime>,
150}
151
152#[cfg(feature = "__rkyv")]
153impl TransformExecutor {
154    #[tracing::instrument(
155        level = "info",
156        skip(source_map, metadata_context, plugin_config, module_bytes, runtime)
157    )]
158    pub fn new(
159        module_bytes: Box<dyn PluginModuleBytes>,
160        source_map: &Arc<SourceMap>,
161        unresolved_mark: &swc_common::Mark,
162        metadata_context: &Arc<TransformPluginMetadataContext>,
163        plugin_env_vars: Option<Arc<Vec<swc_atoms::Atom>>>,
164        plugin_config: Option<serde_json::Value>,
165        runtime: Arc<dyn runtime::Runtime>,
166    ) -> Self {
167        Self {
168            source_map: source_map.clone(),
169            unresolved_mark: *unresolved_mark,
170            metadata_context: metadata_context.clone(),
171            plugin_env_vars,
172            plugin_config,
173            module_bytes,
174            runtime,
175        }
176    }
177
178    // Import, export, and create memory for the plugin to communicate between host
179    // and guest then acquire necessary exports from the plugin.
180    fn setup_plugin_env_exports(&mut self) -> Result<PluginTransformState, Error> {
181        // First, compile plugin module bytes into wasmer::Module and get the
182        // corresponding store
183        let module_name = self.module_bytes.get_module_name();
184        let module = self.module_bytes.compile_module(&*self.runtime);
185
186        let context_key_buffer = Arc::new(Mutex::new(Vec::new()));
187        let metadata_env = Arc::new(MetadataContextHostEnvironment::new(
188            &self.metadata_context,
189            &self.plugin_config,
190            &context_key_buffer,
191        ));
192
193        let transform_result: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
194        let transform_env = Arc::new(TransformResultHostEnvironment::new(&transform_result));
195
196        let base_env = Arc::new(BaseHostEnvironment::new());
197
198        let comment_buffer = Arc::new(Mutex::new(Vec::new()));
199        let comments_env = Arc::new(CommentHostEnvironment::new(&comment_buffer));
200
201        let source_map_buffer = Arc::new(Mutex::new(Vec::new()));
202        let source_map = Arc::new(Mutex::new(self.source_map.clone()));
203        let source_map_host_env = Arc::new(SourceMapHostEnvironment::new(
204            &source_map,
205            &source_map_buffer,
206        ));
207
208        let diagnostics_buffer: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
209        let diagnostics_env = Arc::new(DiagnosticContextHostEnvironment::new(&diagnostics_buffer));
210
211        let import_object = build_import_object(
212            metadata_env,
213            transform_env,
214            base_env,
215            comments_env,
216            source_map_host_env,
217            diagnostics_env,
218        );
219        let envs = self
220            .plugin_env_vars
221            .iter()
222            .flat_map(|list| list.iter())
223            .filter_map(|name| {
224                std::env::var(name.as_str())
225                    .ok()
226                    .map(|value| (name.as_str().into(), value))
227            })
228            .collect::<Vec<_>>();
229        let instance = self
230            .runtime
231            .init(module_name, import_object, envs, module)?;
232
233        let diag_result: PluginCorePkgDiagnostics =
234            PluginSerializedBytes::from_slice(&(&(*diagnostics_buffer.lock()))[..])
235                .deserialize()?
236                .into_inner();
237
238        Ok(PluginTransformState {
239            instance,
240            transform_result,
241            plugin_core_diag: diag_result,
242        })
243    }
244
245    #[tracing::instrument(level = "info", skip_all)]
246    pub fn transform(
247        &mut self,
248        program: &PluginSerializedBytes,
249        should_enable_comments_proxy: Option<bool>,
250    ) -> Result<PluginSerializedBytes, Error> {
251        let mut transform_state = self.setup_plugin_env_exports()?;
252        transform_state.is_transform_schema_compatible()?;
253        transform_state
254            .run(program, self.unresolved_mark, should_enable_comments_proxy)
255            .with_context(|| {
256                format!(
257                    "failed to run Wasm plugin transform. Please ensure the version of `swc_core` \
258                     used by the plugin is compatible with the host runtime. See the \
259                     documentation for compatibility information. If you are an author of the \
260                     plugin, please update `swc_core` to the compatible version.
261
262                Note that if you want to use the os features like filesystem, you need to use \
263                     `wasi`. Wasm itself does not have concept of filesystem.
264
265                https://swc.rs/docs/plugin/selecting-swc-core
266
267                See https://plugins.swc.rs/versions/from-plugin-runner/{PKG_VERSION} for the list of the compatible versions.
268
269                Build info:
270                    Date: {BUILD_DATE}
271                    Timestamp: {BUILD_TIMESTAMP}
272
273                Version info:
274                    swc_plugin_runner: {PKG_VERSION}
275                    Dependencies: {PKG_DEPS}
276                "
277                )
278            })
279    }
280}
281
282const BUILD_DATE: &str = env!("VERGEN_BUILD_DATE");
283const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP");
284const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
285const PKG_DEPS: &str = env!("VERGEN_CARGO_DEPENDENCIES");