swc_common/plugin/
serialized.rs

1use std::any::type_name;
2
3use anyhow::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6#[non_exhaustive]
7#[cfg_attr(
8    feature = "__plugin",
9    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
10)]
11#[cfg_attr(feature = "__plugin", derive(bytecheck::CheckBytes))]
12#[cfg_attr(feature = "__plugin", repr(u32))]
13/// Enum for possible errors while running transform via plugin.
14///
15/// This error indicates internal operation failure either in plugin_runner
16/// or plugin_macro. Plugin's transform fn itself does not allow to return
17/// error - instead it should use provided `handler` to emit corresponding error
18/// to the host.
19pub enum PluginError {
20    /// Occurs when failed to convert size passed from host / guest into usize
21    /// or similar for the conversion. This is an internal error rasied via
22    /// plugin_macro, normally plugin author should not raise this manually.
23    SizeInteropFailure(String),
24    /// Occurs when failed to reconstruct a struct from `Serialized`.
25    Deserialize(String),
26    /// Occurs when failed to serialize a struct into `Serialized`.
27    /// Unlike deserialize error, this error cannot forward any context for the
28    /// raw bytes: when serialize failed, there's nothing we can pass between
29    /// runtime.
30    Serialize(String),
31}
32
33/// A wrapper type for the internal representation of serialized data.
34///
35/// Wraps internal representation of serialized data for exchanging data between
36/// plugin to the host. Consumers should not rely on specific details of byte
37/// format struct contains: it is strict implementation detail which can
38/// change anytime.
39pub struct PluginSerializedBytes {
40    pub(crate) field: rkyv::util::AlignedVec,
41}
42
43#[cfg(feature = "__plugin")]
44impl PluginSerializedBytes {
45    /**
46     * Constructs an instance from already serialized byte
47     * slices.
48     */
49    #[tracing::instrument(level = "info", skip_all)]
50    pub fn from_slice(bytes: &[u8]) -> PluginSerializedBytes {
51        let mut field = rkyv::util::AlignedVec::new();
52        field.extend_from_slice(bytes);
53        PluginSerializedBytes { field }
54    }
55
56    /**
57     * Constructs an instance from versioned struct by serializing it.
58     *
59     * This is sort of mimic TryFrom behavior, since we can't use generic
60     * to implement TryFrom trait
61     */
62    /*
63    #[tracing::instrument(level = "info", skip_all)]
64    pub fn try_serialize<W>(t: &VersionedSerializable<W>) -> Result<Self, Error>
65    where
66        W: rkyv::Serialize<rkyv::ser::serializers::AllocSerializer<512>>,
67    {
68        rkyv::to_bytes::<_, 512>(t)
69            .map(|field| PluginSerializedBytes { field })
70            .map_err(|err| match err {
71                rkyv::ser::serializers::CompositeSerializerError::SerializerError(e) => e.into(),
72                rkyv::ser::serializers::CompositeSerializerError::ScratchSpaceError(_e) => {
73                    Error::msg("AllocScratchError")
74                }
75                rkyv::ser::serializers::CompositeSerializerError::SharedError(_e) => {
76                    Error::msg("SharedSerializeMapError")
77                }
78            })
79    }
80     */
81
82    #[tracing::instrument(level = "info", skip_all)]
83    pub fn try_serialize<W>(t: &VersionedSerializable<W>) -> Result<Self, Error>
84    where
85        W: for<'a> rkyv::Serialize<
86            rancor::Strategy<
87                rkyv::ser::Serializer<
88                    rkyv::util::AlignedVec,
89                    rkyv::ser::allocator::ArenaHandle<'a>,
90                    rkyv::ser::sharing::Share,
91                >,
92                rancor::Error,
93            >,
94        >,
95    {
96        rkyv::to_bytes::<rancor::Error>(t)
97            .map(|field| PluginSerializedBytes { field })
98            .map_err(|err| err.into())
99    }
100
101    /*
102     * Internal fn to constructs an instance from raw bytes ptr.
103     */
104    #[tracing::instrument(level = "info", skip_all)]
105    #[allow(clippy::not_unsafe_ptr_arg_deref)]
106    pub fn from_raw_ptr(
107        raw_allocated_ptr: *const u8,
108        raw_allocated_ptr_len: usize,
109    ) -> PluginSerializedBytes {
110        let raw_ptr_bytes =
111            unsafe { std::slice::from_raw_parts(raw_allocated_ptr, raw_allocated_ptr_len) };
112
113        PluginSerializedBytes::from_slice(raw_ptr_bytes)
114    }
115
116    pub fn as_slice(&self) -> &[u8] {
117        self.field.as_slice()
118    }
119
120    pub fn as_ptr(&self) -> (*const u8, usize) {
121        (self.field.as_ptr(), self.field.len())
122    }
123
124    #[tracing::instrument(level = "info", skip_all)]
125    pub fn deserialize<W>(&self) -> Result<VersionedSerializable<W>, Error>
126    where
127        W: rkyv::Archive,
128        W::Archived: rkyv::Deserialize<W, rancor::Strategy<rkyv::de::Pool, rancor::Error>>,
129    {
130        use anyhow::Context;
131
132        let archived = unsafe {
133            // Safety: We intentionally skip bytecheck because it makes Wasm plugins not
134            // backward compatible.
135            rkyv::access_unchecked::<W::Archived>(&self.field[..])
136        };
137
138        let deserialized = rkyv::deserialize(archived)
139            .with_context(|| format!("failed to deserialize `{}`", type_name::<W>()))?;
140
141        Ok(VersionedSerializable(deserialized))
142    }
143
144    /*
145    #[tracing::instrument(level = "info", skip_all)]
146    pub fn deserialize<W>(&self) -> Result<VersionedSerializable<W>, Error>
147    where
148        W: rkyv::Archive,
149        W::Archived: rkyv::Deserialize<W, rkyv::de::deserializers::SharedDeserializeMap>
150            + for<'a> rkyv::CheckBytes<rkyv::validation::validators::DefaultValidator<'a>>,
151    {
152        use anyhow::Context;
153
154        let archived = rkyv::check_archived_root::<VersionedSerializable<W>>(&self.field[..])
155            .map_err(|err| {
156                anyhow::format_err!("wasm plugin bytecheck failed {:?}", err.to_string())
157            })?;
158
159        archived
160            .deserialize(&mut rkyv::de::deserializers::SharedDeserializeMap::new())
161            .with_context(|| format!("failed to deserialize `{}`", type_name::<W>()))
162    } */
163}
164
165/// A wrapper type for the structures to be passed into plugins
166/// serializes the contained value out-of-line so that newer
167/// versions can be viewed as the older version.
168///
169/// First field indicate version of struct type (schema). Any consumers like
170/// swc_plugin_macro can use this to validate compatiblility before attempt to
171/// serialize.
172#[cfg_attr(
173    feature = "__plugin",
174    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
175)]
176#[repr(transparent)]
177#[cfg_attr(feature = "__plugin", derive(bytecheck::CheckBytes))]
178#[derive(Debug)]
179pub struct VersionedSerializable<T>(
180    // [NOTE]: https://github.com/rkyv/rkyv/issues/373#issuecomment-1546360897
181    //#[cfg_attr(feature = "__plugin", with(rkyv::with::AsBox))]
182    pub T,
183);
184
185impl<T> VersionedSerializable<T> {
186    pub fn new(value: T) -> Self {
187        Self(value)
188    }
189
190    pub fn inner(&self) -> &T {
191        &self.0
192    }
193
194    pub fn into_inner(self) -> T {
195        self.0
196    }
197}