1use super::{
2 NativeCall, NativeCancellationReason, NativeDeoptReason, NativeExit, NativeExitKind,
3 NativeFrame, NativeResumeKind, NativeRootSet, NativeSafepoint, NativeSourceMapView,
4 NATIVE_ABI_VERSION,
5};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct NativeAbiError {
9 pub field: &'static str,
10 pub message: &'static str,
11}
12
13impl NativeAbiError {
14 pub(super) const fn new(field: &'static str, message: &'static str) -> Self {
15 Self { field, message }
16 }
17}
18
19impl std::fmt::Display for NativeAbiError {
20 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(formatter, "invalid {}: {}", self.field, self.message)
22 }
23}
24
25impl std::error::Error for NativeAbiError {}
26
27impl NativeRootSet {
28 pub fn validate(self) -> Result<(), NativeAbiError> {
29 validate_slice("native.roots", self.roots, self.count)
30 }
31}
32
33impl NativeSourceMapView {
34 pub fn validate(self) -> Result<(), NativeAbiError> {
35 validate_slice("native.source_map", self.entries, self.count)
36 }
37}
38
39impl NativeFrame {
40 pub fn validate(&self) -> Result<(), NativeAbiError> {
41 validate_version(self.abi_version)?;
42 if !self.kind.is_known() {
43 return Err(NativeAbiError::new(
44 "native.frame.kind",
45 "frame kind is unknown",
46 ));
47 }
48 validate_zero("native.frame.flags", self.flags)?;
49 validate_zero("native.frame.reserved", self.reserved)?;
50 validate_slice("native.frame.locals", self.locals, self.local_count)?;
51 validate_slice(
52 "native.frame.operands",
53 self.operands,
54 self.operand_capacity,
55 )?;
56 self.roots.validate()?;
57 if self.resume.is_null() {
58 return Err(NativeAbiError::new(
59 "native.frame.resume",
60 "exact resume state is required",
61 ));
62 }
63 Ok(())
64 }
65}
66
67impl NativeCall {
68 pub fn validate(&self) -> Result<(), NativeAbiError> {
69 validate_version(self.abi_version)?;
70 if !self.kind.is_known() {
71 return Err(NativeAbiError::new(
72 "native.call.kind",
73 "call kind is unknown",
74 ));
75 }
76 validate_zero("native.call.flags", self.flags)?;
77 if self.host.is_null() {
78 return Err(NativeAbiError::new(
79 "native.call.host",
80 "host service table must not be null",
81 ));
82 }
83 if self.frame.is_null() {
84 return Err(NativeAbiError::new(
85 "native.call.frame",
86 "frame must not be null",
87 ));
88 }
89 validate_slice("native.call.arguments", self.arguments, self.argument_count)?;
90 validate_slice("native.call.results", self.results, self.result_capacity)?;
91 if self.requested_outputs as usize > self.result_capacity {
92 return Err(NativeAbiError::new(
93 "native.call.requested_outputs",
94 "requested outputs exceed result capacity",
95 ));
96 }
97 Ok(())
98 }
99}
100
101impl NativeExit {
102 pub fn validate(&self) -> Result<(), NativeAbiError> {
103 validate_version(self.abi_version)?;
104 validate_zero("native.exit.flags", self.flags)?;
105 match self.kind {
106 NativeExitKind::COMPLETED => Ok(()),
107 NativeExitKind::EXCEPTION
108 if self.exception.handle != 0 && self.exception.generation != 0 =>
109 {
110 self.exception.source.validate()
111 }
112 NativeExitKind::CANCELLED
113 if self.cancellation.reason.is_known()
114 && self.cancellation.reserved == 0
115 && self.cancellation.generation != 0 =>
116 {
117 Ok(())
118 }
119 NativeExitKind::SUSPENDED => {
120 if self.suspension.continuation == 0
121 || self.suspension.generation == 0
122 || self.suspension.resume.is_null()
123 {
124 return Err(NativeAbiError::new(
125 "native.exit.suspension",
126 "continuation and exact resume state are required",
127 ));
128 }
129 self.suspension.roots.validate()
130 }
131 NativeExitKind::DEOPTIMIZED
132 if self.deoptimization.reason.is_known()
133 && self.deoptimization.target.is_known()
134 && !self.deoptimization.resume.is_null() =>
135 {
136 Ok(())
137 }
138 NativeExitKind::EXCEPTION => Err(NativeAbiError::new(
139 "native.exit.exception",
140 "exception handle, generation, and source must be valid",
141 )),
142 NativeExitKind::CANCELLED => Err(NativeAbiError::new(
143 "native.exit.cancellation",
144 "cancellation reason, generation, and reserved fields must be valid",
145 )),
146 NativeExitKind::DEOPTIMIZED => Err(NativeAbiError::new(
147 "native.exit.deoptimization",
148 "reason, target, and resume state must be valid",
149 )),
150 _ => Err(NativeAbiError::new(
151 "native.exit.kind",
152 "exit kind is unknown",
153 )),
154 }
155 }
156}
157
158impl NativeSafepoint {
159 pub fn validate(&self) -> Result<(), NativeAbiError> {
160 if self.resume.is_null() {
161 return Err(NativeAbiError::new(
162 "native.safepoint.resume",
163 "exact resume state is required",
164 ));
165 }
166 self.roots.validate()
167 }
168}
169
170impl super::NativeResumeState {
171 pub fn validate(&self) -> Result<(), NativeAbiError> {
172 validate_zero("native.resume.flags", self.flags)?;
173 validate_zero("native.resume.reserved", self.reserved)?;
174 if !super::NativeSitePhase(self.phase).is_known() {
175 return Err(NativeAbiError::new(
176 "native.resume.phase",
177 "resume site phase is unknown",
178 ));
179 }
180 self.source.validate()
181 }
182}
183
184impl NativeCancellationReason {
185 pub const fn is_known(self) -> bool {
186 self.0 <= Self::SHUTDOWN.0
187 }
188}
189
190impl NativeResumeKind {
191 pub const fn is_known(self) -> bool {
192 self.0 <= Self::OPTIMIZED_NATIVE.0
193 }
194}
195
196impl NativeDeoptReason {
197 pub const fn is_known(self) -> bool {
198 self.0 <= Self::EXPLICIT_SLOW_PATH.0
199 }
200}
201
202fn validate_version(version: u32) -> Result<(), NativeAbiError> {
203 if version != NATIVE_ABI_VERSION.encoded() {
204 return Err(NativeAbiError::new(
205 "native.abi_version",
206 "unsupported native ABI version",
207 ));
208 }
209 Ok(())
210}
211
212fn validate_zero(field: &'static str, value: u32) -> Result<(), NativeAbiError> {
213 if value != 0 {
214 return Err(NativeAbiError::new(
215 field,
216 "reserved bits must be zero for this ABI revision",
217 ));
218 }
219 Ok(())
220}
221
222pub(super) fn validate_slice<T>(
223 field: &'static str,
224 pointer: *const T,
225 count: usize,
226) -> Result<(), NativeAbiError> {
227 if pointer.is_null() != (count == 0) {
228 return Err(NativeAbiError::new(
229 field,
230 "pointer must be null exactly when count is zero",
231 ));
232 }
233 Ok(())
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::native::{
240 NativeCallKind, NativeCancellation, NativeException, NativeFrameKind, NativeResumeState,
241 NativeRoot, NativeSourceLocation, NativeValueRef,
242 };
243
244 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
245 #[cfg_attr(not(target_arch = "wasm32"), test)]
246 fn call_and_frame_require_exact_borrowed_state() {
247 let mut resume = NativeResumeState::default();
248 let mut local = NativeValueRef::NULL;
249 let root = NativeRoot {
250 value: NativeValueRef::NULL,
251 kind: crate::native::NativeRootKind::LOCAL,
252 slot: 0,
253 };
254 let mut frame = NativeFrame {
255 kind: NativeFrameKind::FUNCTION,
256 locals: &mut local,
257 local_count: 1,
258 operands: std::ptr::null_mut(),
259 operand_capacity: 0,
260 roots: NativeRootSet {
261 roots: &root,
262 count: 1,
263 },
264 resume: &mut resume,
265 ..NativeFrame::default()
266 };
267 assert_eq!(frame.validate(), Ok(()));
268
269 let host = std::ptr::dangling::<crate::native::NativeHostVTable>();
270 let call = NativeCall {
271 kind: NativeCallKind::DIRECT,
272 host,
273 frame: &mut frame,
274 ..NativeCall::default()
275 };
276 assert_eq!(call.validate(), Ok(()));
277
278 let invalid = NativeCall {
279 requested_outputs: 1,
280 ..call
281 };
282 assert_eq!(
283 invalid.validate().unwrap_err().field,
284 "native.call.requested_outputs"
285 );
286
287 let invalid_kind = NativeCall {
288 kind: NativeCallKind(u32::MAX),
289 ..call
290 };
291 assert_eq!(
292 invalid_kind.validate().unwrap_err().field,
293 "native.call.kind"
294 );
295 }
296
297 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
298 #[cfg_attr(not(target_arch = "wasm32"), test)]
299 fn unknown_control_codes_fail_closed_without_invalid_rust_enums() {
300 assert!(!NativeCancellationReason(u32::MAX).is_known());
301 assert!(!NativeResumeKind(u32::MAX).is_known());
302 assert!(!NativeDeoptReason(u32::MAX).is_known());
303 assert!(!NativeValueRef {
304 handle: 1,
305 generation: 0
306 }
307 .is_valid());
308
309 let invalid_resume = NativeResumeState {
310 flags: 1,
311 ..NativeResumeState::default()
312 };
313 assert_eq!(
314 invalid_resume.validate().unwrap_err().field,
315 "native.resume.flags"
316 );
317
318 let invalid_source = NativeSourceLocation {
319 start: 2,
320 end: 1,
321 ..NativeSourceLocation::default()
322 };
323 assert_eq!(
324 invalid_source.validate().unwrap_err().field,
325 "native.source_location"
326 );
327 }
328
329 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
330 #[cfg_attr(not(target_arch = "wasm32"), test)]
331 fn transactional_exit_variants_validate_their_selected_payload() {
332 assert_eq!(NativeExit::completed(2).validate(), Ok(()));
333 assert_eq!(
334 NativeExit::exception(NativeException {
335 handle: 7,
336 generation: 1,
337 source: NativeSourceLocation::default(),
338 })
339 .validate(),
340 Ok(())
341 );
342 assert_eq!(
343 NativeExit::cancelled(NativeCancellation {
344 reason: NativeCancellationReason::DEADLINE,
345 reserved: 0,
346 generation: 2,
347 })
348 .validate(),
349 Ok(())
350 );
351 assert!(NativeExit::exception(NativeException::default())
352 .validate()
353 .is_err());
354
355 let mut resume = NativeResumeState::default();
356 let mut frame = NativeFrame {
357 resume: &mut resume,
358 ..NativeFrame::default()
359 };
360 let mut result = NativeValueRef::NULL;
361 let call = NativeCall {
362 requested_outputs: 1,
363 host: std::ptr::dangling::<crate::native::NativeHostVTable>(),
364 frame: &mut frame,
365 results: &mut result,
366 result_capacity: 1,
367 ..NativeCall::default()
368 };
369 assert_eq!(call.validate_exit(&NativeExit::completed(1)), Ok(()));
370 assert_eq!(
371 call.validate_exit(&NativeExit::completed(2))
372 .unwrap_err()
373 .field,
374 "native.exit.produced_outputs"
375 );
376
377 let mut non_transactional = NativeExit::cancelled(NativeCancellation {
378 reason: NativeCancellationReason::REQUESTED,
379 reserved: 0,
380 generation: 1,
381 });
382 non_transactional.produced_outputs = 1;
383 assert_eq!(
384 call.validate_exit(&non_transactional).unwrap_err().field,
385 "native.exit.produced_outputs"
386 );
387 }
388}