polyplug_js 0.1.1

QuickJS loader for polyplug - loads JavaScript plugins via QuickJS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
//! JsHostBridge — Bridge for JavaScript hosts implementing host contracts.
//!
//! This module provides the bridge that allows JavaScript hosts to implement
//! host contracts. The bridge stores JavaScript callable functions and dispatches
//! calls through the QuickJS VM.
//!
//! # Architecture
//!
//! When a JavaScript host registers a host contract implementation:
//! 1. The JS callable is stored in a HashMap keyed by contract_id
//! 2. When a plugin calls a host contract function, the bridge:
//!    - Looks up the JS callable
//!    - Invokes it with converted arguments
//!    - Converts the result back to ABI format
//!
//! # Thread Safety
//!
//! The bridge uses `RwLock` to protect the contracts HashMap because:
//! - Registration happens during initialization (write lock)
//! - Calls happen during plugin execution (read lock)
//! - rquickjs's `parallel` feature makes `Runtime`, `Context`, and `Persistent<Function>` Send/Sync safe
//!
//! # QuickJS VM Ownership
//!
//! The bridge owns its own QuickJS Runtime and Context for complete isolation from
//! plugin QuickJS VMs. This ensures:
//! - Host contract implementations don't interfere with plugin state
//! - Multiple Runtime instances can have isolated JS host bridges

use std::collections::HashMap;
use std::sync::RwLock;

use rquickjs::Context;
use rquickjs::Function;
use rquickjs::Persistent;
use rquickjs::Runtime;

use polyplug::host_bridge::BridgeError;
use polyplug::host_bridge::RuntimeLanguageBridge;
use polyplug_abi::AbiError;
use polyplug_abi::AbiErrorCode;
use polyplug_abi::StringView;
use polyplug_abi::SupportedLanguage;

/// Errors that can occur when creating a JsHostBridge.
#[derive(Debug, thiserror::Error)]
pub enum JsBridgeError {
    /// Failed to create the QuickJS runtime.
    #[error("QuickJS runtime creation failed: {0}")]
    RuntimeCreationFailed(String),

    /// Failed to create the QuickJS context.
    #[error("QuickJS context creation failed: {0}")]
    ContextCreationFailed(String),
}

/// Bridge for JavaScript hosts implementing host contracts.
///
/// This bridge stores JavaScript callable functions and dispatches calls through
/// the QuickJS VM. The bridge handles:
/// - Thread-safe storage of registered implementations
/// - JavaScript error handling and conversion to AbiError
/// - Per-bridge QuickJS VM isolation
///
/// # Example
///
/// ```rust,ignore
/// use polyplug_js::bridge::JsHostBridge;
/// use polyplug::host_bridge::RuntimeLanguageBridge;
///
/// let bridge = JsHostBridge::new();
///
/// // Register a JS implementation
/// let ctx = bridge.context();
/// ctx.with(|ctx| {
///     let callable = ctx.eval::<Function>("function(fn_id, args, out) { return fn_id; }").unwrap();
///     let persistent = Persistent::save(&ctx, callable);
///     bridge.register_host_contract(1234, Box::new(persistent));
/// });
///
/// // Call through the bridge
/// let result = bridge.call_host_contract(1234, 0, args_ptr, out_ptr);
/// ```
pub struct JsHostBridge {
    /// The QuickJS Runtime owned by this bridge.
    /// SAFETY: rquickjs's `parallel` feature makes Runtime Send + Sync.
    runtime: Runtime,

    /// The QuickJS Context owned by this bridge.
    /// SAFETY: rquickjs's `parallel` feature makes Context Send + Sync.
    context: Context,

    /// Registered host contract implementations.
    /// Key: contract_id (FNV-1a hash of "host_contract:name@major")
    /// Value: JavaScript callable function (Persistent<Function<'static>>)
    contracts: RwLock<HashMap<u64, Persistent<Function<'static>>>>,
}

impl JsHostBridge {
    /// Create a new JsHostBridge with a fresh QuickJS VM.
    ///
    /// # Errors
    ///
    /// Returns `JsBridgeError::RuntimeCreationFailed` if the QuickJS runtime
    /// cannot be created.
    ///
    /// Returns `JsBridgeError::ContextCreationFailed` if the QuickJS context
    /// cannot be created.
    pub fn new() -> Result<JsHostBridge, JsBridgeError> {
        let runtime: Runtime = Runtime::new()
            .map_err(|e: rquickjs::Error| JsBridgeError::RuntimeCreationFailed(e.to_string()))?;
        let context: Context = Context::full(&runtime)
            .map_err(|e: rquickjs::Error| JsBridgeError::ContextCreationFailed(e.to_string()))?;

        Ok(JsHostBridge {
            runtime,
            context,
            contracts: RwLock::new(HashMap::new()),
        })
    }

    /// Create a new JsHostBridge with pre-allocated capacity.
    ///
    /// # Errors
    ///
    /// Returns `JsBridgeError::RuntimeCreationFailed` if the QuickJS runtime
    /// cannot be created.
    ///
    /// Returns `JsBridgeError::ContextCreationFailed` if the QuickJS context
    /// cannot be created.
    pub fn with_capacity(capacity: usize) -> Result<JsHostBridge, JsBridgeError> {
        let runtime: Runtime = Runtime::new()
            .map_err(|e: rquickjs::Error| JsBridgeError::RuntimeCreationFailed(e.to_string()))?;
        let context: Context = Context::full(&runtime)
            .map_err(|e: rquickjs::Error| JsBridgeError::ContextCreationFailed(e.to_string()))?;

        Ok(JsHostBridge {
            runtime,
            context,
            contracts: RwLock::new(HashMap::with_capacity(capacity)),
        })
    }

    /// Get a reference to the underlying QuickJS Context.
    ///
    /// This allows host code to create JS functions for registration.
    pub fn context(&self) -> &Context {
        &self.context
    }

    /// Get a reference to the underlying QuickJS Runtime.
    ///
    /// This allows host code to access runtime-level features.
    pub fn runtime(&self) -> &Runtime {
        &self.runtime
    }
}

impl Drop for JsHostBridge {
    fn drop(&mut self) {
        // Clear the contracts HashMap before dropping the Runtime.
        // This ensures all Persistent<Function<'static>> references are released
        // before the QuickJS Runtime is destroyed, preventing the GC assertion failure.
        if let Ok(mut contracts) = self.contracts.write() {
            contracts.clear();
        }
    }
}

impl RuntimeLanguageBridge for JsHostBridge {
    /// Returns `SupportedLanguage::JavaScript` to identify this as a JavaScript bridge.
    fn runtime_type(&self) -> SupportedLanguage {
        SupportedLanguage::JavaScript
    }

    /// Register a JavaScript callable as a host contract implementation.
    ///
    /// The `implementation` must be a `Box<Persistent<Function<'static>>>` containing a
    /// JavaScript callable function. The bridge stores this function for later dispatch.
    ///
    /// # Arguments
    ///
    /// - `contract_id`: The FNV-1a hash of `"host_contract:name@major"`
    /// - `implementation`: A boxed persistent JS function (`Persistent<Function<'static>>`)
    ///
    /// # Errors
    ///
    /// - `BridgeError::DuplicateContract`: Contract already registered
    /// - `BridgeError::TypeMismatch`: Implementation is not a `Persistent<Function<'static>>`
    fn register_host_contract(
        &mut self,
        contract_id: u64,
        implementation: Box<dyn core::any::Any>,
    ) -> Result<(), BridgeError> {
        // Attempt to downcast to Persistent<Function<'static>>
        let callable: Persistent<Function<'static>> = implementation
            .downcast::<Persistent<Function<'static>>>()
            .map_err(|_| BridgeError::TypeMismatch {
                contract_id,
                expected: "Persistent<Function<'static>>".to_owned(),
                got: "unknown type".to_owned(),
            })
            .map(|boxed| *boxed)?;

        // Acquire write lock and insert
        let mut contracts: std::sync::RwLockWriteGuard<
            '_,
            HashMap<u64, Persistent<Function<'static>>>,
        > = self
            .contracts
            .write()
            .map_err(|_| BridgeError::VmRegistrationFailed {
                contract_id,
                reason: "failed to acquire write lock on contracts map".to_owned(),
            })?;

        if contracts.contains_key(&contract_id) {
            return Err(BridgeError::DuplicateContract { contract_id });
        }

        contracts.insert(contract_id, callable);
        Ok(())
    }

    /// Call a host contract function through JavaScript dispatch.
    ///
    /// This method:
    /// 1. Looks up the registered JavaScript callable
    /// 2. Calls the function with converted arguments
    /// 3. Returns the result or an error
    ///
    /// # Arguments
    ///
    /// - `contract_id`: The contract ID to look up
    /// - `fn_id`: Function index within the contract (0-based)
    /// - `args`: Pointer to packed ABI arguments (layout defined by contract)
    /// - `out`: Pointer to output buffer for return value
    ///
    /// # Returns
    ///
    /// - `AbiError::ok()` on success
    /// - `AbiError { code: AbiErrorCode::HostContractNotFound, ... }` if contract not found
    /// - `AbiError { code: AbiErrorCode::HostContractCallFailed, ... }` if dispatch failed
    ///
    /// # Safety
    ///
    /// This method is inherently unsafe because it deals with raw pointers:
    /// - `args` must point to valid ABI-packed arguments for the contract
    /// - `out` must point to a valid buffer sized for the return type
    /// - The caller must ensure proper alignment of both pointers
    ///
    /// # Note
    ///
    /// For MVP, this implementation provides basic dispatch functionality.
    /// Full type marshaling for all primitive types will be added in future tasks.
    unsafe fn call_host_contract(
        &self,
        contract_id: u64,
        fn_id: u32,
        args: *const (),
        out: *mut (),
    ) -> AbiError {
        // Step 1: Look up the registered callable
        let contracts_guard: std::sync::RwLockReadGuard<
            '_,
            HashMap<u64, Persistent<Function<'static>>>,
        > = match self.contracts.read() {
            Ok(guard) => guard,
            Err(_) => {
                return AbiError {
                    code: AbiErrorCode::HostContractCallFailed as u32,
                    message: StringView::from_static(
                        b"failed to acquire read lock on contracts map",
                    ),
                };
            }
        };

        let callable: &Persistent<Function<'static>> = match contracts_guard.get(&contract_id) {
            Some(f) => f,
            None => {
                return AbiError {
                    code: AbiErrorCode::HostContractNotFound as u32,
                    message: StringView::from_static(b"host contract not found"),
                };
            }
        };

        // Step 2: Call the function within the QuickJS context
        // For MVP, we pass fn_id as the first argument and args/out as opaque pointers
        // Full type marshaling will be implemented in future tasks
        //
        // Pass pointers as BigInt to preserve full 64-bit precision.
        // QuickJS BigInt can hold full 64-bit integers.
        let fn_id_arg: u32 = fn_id;
        let args_ptr: i64 = args as usize as i64;
        let out_ptr: i64 = out as usize as i64;

        let call_result: Result<i32, rquickjs::Error> = self.context.with(|ctx| {
            // Restore the persistent function to a usable Function<'_> in this context
            let js_fn: Function<'_> = callable.clone().restore(&ctx)?;

            // Create BigInt values for pointer arguments
            let args_bigint: rquickjs::BigInt<'_> =
                rquickjs::BigInt::from_i64(ctx.clone(), args_ptr)?;
            let out_bigint: rquickjs::BigInt<'_> =
                rquickjs::BigInt::from_i64(ctx.clone(), out_ptr)?;

            // Call the JS function with (fn_id, args_ptr, out_ptr)
            let result: i32 = js_fn
                .call::<(u32, rquickjs::BigInt<'_>, rquickjs::BigInt<'_>), i32>((
                    fn_id_arg,
                    args_bigint,
                    out_bigint,
                ))?;

            Ok(result)
        });

        match call_result {
            Ok(0) => AbiError::ok(),
            Ok(code) => AbiError {
                // AbiError.code is a raw u32, so the plugin-provided code is stored
                // verbatim — no enum materialization, hence no UB on unknown values.
                code: code as u32,
                message: StringView::null(),
            },
            Err(e) => {
                // The full JS error detail propagates to the caller in the
                // AbiError message below; no side-channel print.
                let message: String = format!("JavaScript exception: {}", e);
                // SAFETY: We leak the message string to create a 'static StringView.
                // This is acceptable because:
                // 1. Error messages are small and short-lived
                // 2. The alternative would require host_alloc which we don't have here
                // 3. This matches the pattern used in other loaders
                let message_static: &'static str = Box::leak(message.into_boxed_str());
                AbiError {
                    code: AbiErrorCode::HostContractCallFailed as u32,
                    message: StringView {
                        ptr: message_static.as_ptr(),
                        len: message_static.len(),
                    },
                }
            }
        }
    }
}

// SAFETY: JsHostBridge is Send because:
// - RwLock<HashMap<u64, Persistent<Function<'static>>>> is Send (RwLock is Send, HashMap is Send)
// - Runtime is Send when compiled with rquickjs's `parallel` feature (which we use)
// - Context is Send when compiled with rquickjs's `parallel` feature
// - Persistent<Function<'static>> is Send when compiled with rquickjs's `parallel` feature
// - All operations that access QuickJS objects are thread-safe due to rquickjs's internal synchronization
unsafe impl Send for JsHostBridge {}

// SAFETY: JsHostBridge is Sync because:
// - RwLock provides synchronization for the contracts HashMap
// - Runtime is Sync when compiled with rquickjs's `parallel` feature (which we use)
// - Context is Sync when compiled with rquickjs's `parallel` feature
// - rquickjs's internal mutex provides synchronization for QuickJS state access
// - Concurrent reads are safe (read lock + rquickjs's internal sync)
// - Concurrent writes are serialized (write lock + rquickjs's internal sync)
unsafe impl Sync for JsHostBridge {}

#[cfg(test)]
mod tests {
    #![allow(clippy::expect_used)]

    use super::*;

    #[test]
    fn bridge_new_creates_empty_bridge() {
        let bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");
        let contracts: std::sync::RwLockReadGuard<'_, HashMap<u64, Persistent<Function<'static>>>> =
            bridge.contracts.read().expect("read lock");
        assert!(contracts.is_empty());
    }

    #[test]
    fn bridge_with_capacity_creates_empty_bridge() {
        let bridge: JsHostBridge = JsHostBridge::with_capacity(10).expect("bridge creation");
        let contracts: std::sync::RwLockReadGuard<'_, HashMap<u64, Persistent<Function<'static>>>> =
            bridge.contracts.read().expect("read lock");
        assert!(contracts.is_empty());
    }

    #[test]
    fn bridge_runtime_type_returns_javascript() {
        let bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");
        assert_eq!(bridge.runtime_type(), SupportedLanguage::JavaScript);
    }

    #[test]
    fn bridge_context_returns_reference() {
        let bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");
        let ctx: &Context = bridge.context();
        let result: i32 = ctx.with(|ctx| ctx.eval::<i32, _>("42")).expect("eval");
        assert_eq!(result, 42);
    }

    #[test]
    fn bridge_runtime_returns_reference() {
        let bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");
        let _runtime: &Runtime = bridge.runtime();
    }

    #[test]
    fn bridge_register_host_contract_success() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Create a simple JS callable
        let persistent: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>("(function(fn_id, args, out) { return 0; })")
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        // Register it
        let result: Result<(), BridgeError> =
            bridge.register_host_contract(1234, Box::new(persistent));
        assert!(result.is_ok());

        // Verify it's stored
        let contracts: std::sync::RwLockReadGuard<'_, HashMap<u64, Persistent<Function<'static>>>> =
            bridge.contracts.read().expect("read lock");
        assert!(contracts.contains_key(&1234));
    }

    #[test]
    fn bridge_register_host_contract_duplicate_fails() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Create two JS callables
        let persistent1: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>("(function(fn_id, args, out) { return 0; })")
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        let persistent2: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>("(function(fn_id, args, out) { return 1; })")
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        // Register first one
        let result1: Result<(), BridgeError> =
            bridge.register_host_contract(1234, Box::new(persistent1));
        assert!(result1.is_ok());

        // Try to register second one with same ID
        let result2: Result<(), BridgeError> =
            bridge.register_host_contract(1234, Box::new(persistent2));
        assert!(result2.is_err());
        let err: BridgeError = result2.expect_err("should fail");
        assert!(matches!(
            err,
            BridgeError::DuplicateContract { contract_id: 1234 }
        ));
    }

    #[test]
    fn bridge_register_host_contract_type_mismatch_fails() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Try to register a non-Persistent<Function<'static>>
        let result: Result<(), BridgeError> = bridge.register_host_contract(1234, Box::new(42i32));
        assert!(result.is_err());
        let err: BridgeError = result.expect_err("should fail");
        assert!(matches!(
            err,
            BridgeError::TypeMismatch {
                contract_id: 1234,
                ..
            }
        ));
    }

    #[test]
    fn bridge_call_host_contract_not_found() {
        let bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // SAFETY: null pointers are valid here — the lookup fails before any
        // pointer is dereferenced (contract 9999 is not registered).
        let result: AbiError =
            unsafe { bridge.call_host_contract(9999, 0, core::ptr::null(), core::ptr::null_mut()) };
        assert_eq!(result.code, AbiErrorCode::HostContractNotFound as u32);
    }

    #[test]
    fn bridge_call_host_contract_success() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Create a simple JS callable that returns 0 (success)
        let persistent: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>("(function(fn_id, args, out) { return 0; })")
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        // Register it
        bridge
            .register_host_contract(1234, Box::new(persistent))
            .expect("register");

        // Call it
        // SAFETY: the registered JS function never dereferences args/out, so passing
        // null pointers is sound here.
        let result: AbiError =
            unsafe { bridge.call_host_contract(1234, 5, core::ptr::null(), core::ptr::null_mut()) };
        assert!(result.is_ok());
    }

    #[test]
    fn bridge_call_host_contract_returns_error_code() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Create a JS callable that returns a non-zero error code
        let persistent: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>("(function(fn_id, args, out) { return 42; })")
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        // Register it
        bridge
            .register_host_contract(1234, Box::new(persistent))
            .expect("register");

        // Call it - should return error code 42 (user-defined error)
        // SAFETY: the registered JS function never dereferences args/out, so passing
        // null pointers is sound here.
        let result: AbiError =
            unsafe { bridge.call_host_contract(1234, 0, core::ptr::null(), core::ptr::null_mut()) };
        assert_eq!(result.code, 42_u32);
    }

    #[test]
    fn bridge_call_host_contract_exception() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Create a JS callable that throws an exception
        let persistent: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>(
                    "(function(fn_id, args, out) { throw new Error('test error'); })",
                )
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        // Register it
        bridge
            .register_host_contract(1234, Box::new(persistent))
            .expect("register");

        // Call it - should return error
        // SAFETY: the registered JS function never dereferences args/out, so passing
        // null pointers is sound here.
        let result: AbiError =
            unsafe { bridge.call_host_contract(1234, 0, core::ptr::null(), core::ptr::null_mut()) };
        assert_eq!(result.code, AbiErrorCode::HostContractCallFailed as u32);
    }

    #[test]
    fn bridge_call_host_contract_with_fn_id() {
        let mut bridge: JsHostBridge = JsHostBridge::new().expect("bridge creation");

        // Create a JS callable that returns fn_id * 2 as error code
        let persistent: Persistent<Function<'static>> = bridge.context().with(|ctx| {
            let callable: Function<'_> = ctx
                .eval::<Function<'_>, _>("(function(fn_id, args, out) { return fn_id * 2; })")
                .expect("eval function");
            Persistent::save(&ctx, callable)
        });

        // Register it
        bridge
            .register_host_contract(1234, Box::new(persistent))
            .expect("register");

        // Call with fn_id=5, expect error code 10 (user-defined error)
        // SAFETY: the registered JS function never dereferences args/out, so passing
        // null pointers is sound here.
        let result: AbiError =
            unsafe { bridge.call_host_contract(1234, 5, core::ptr::null(), core::ptr::null_mut()) };
        assert_eq!(result.code, 10_u32);
    }
}