Skip to main content

pmpx_plugin/
export.rs

1//! `export!`: the whole C shell, generated.
2//!
3//! A plugin author writes one line:
4//!
5//! ```ignore
6//! pub fn create() -> Box<dyn PackageManager> { Box::new(CargoPlugin) }
7//! pmpx_plugin::export!(create);
8//! ```
9//!
10//! and this macro produces everything the host looks for: the `identity`, `command` and `attach`
11//! capability tables, the root table, the capability lookup, and the one exported symbol. None of it
12//! is something an author should have to see, and none of it is a place for a plugin to make a
13//! decision -- it is marshalling, panic containment and ownership, exactly as the ABI describes.
14//!
15//! # What the generated code promises the host
16//!
17//! - **A panic never crosses.** Every entry point is wrapped in `catch_unwind`; a panic in `name`,
18//!   `family` or `command` becomes the contract's marker or an error code, never an abort.
19//! - **Memory is freed by the side that allocated it.** What the shells lease out is reclaimed by
20//!   the `free_str` / `free_command` shims, and never by the host.
21//! - **A verb this build does not know is `unsupported`**, not "invalid arguments": that is what
22//!   keeps a new verb additive for the host's degradation path.
23
24/// Generate the C shell for one [`PackageManager`](crate::PackageManager) implementation.
25#[macro_export]
26macro_rules! export {
27    ($create:expr) => {
28        /// The plugin's factory, wrapped.
29        fn __pmpx_instance() -> ::std::boxed::Box<dyn $crate::PackageManager> {
30            let create: fn() -> ::std::boxed::Box<dyn $crate::PackageManager> = $create;
31            create()
32        }
33
34        #[doc(hidden)]
35        unsafe extern "C" fn __pmpx_name() -> $crate::abi::PmpxStr {
36            $crate::shell::guard_str(|| $crate::shell::leak_str(__pmpx_instance().name()))
37        }
38
39        #[doc(hidden)]
40        unsafe extern "C" fn __pmpx_family() -> $crate::abi::PmpxStr {
41            $crate::shell::guard_str(|| {
42                $crate::shell::leak_str(__pmpx_instance().family().as_str())
43            })
44        }
45
46        #[doc(hidden)]
47        unsafe extern "C" fn __pmpx_run(
48            context: *const $crate::abi::PmpxContext,
49            out: *mut $crate::abi::PmpxCommand,
50        ) -> ::std::primitive::u32 {
51            // `guard` is not optional: a panic crossing the `extern "C"` boundary aborts the whole
52            // process, and the host cannot save it.
53            $crate::shell::guard(move || {
54                let plugin = __pmpx_instance();
55                // Remember the plugin's own name for the case where no host installed hooks (a
56                // plugin's own tests); the host path never needs it, since the host knows its id.
57                $crate::shell::remember_name(plugin.name());
58                // SAFETY: the host promises the context and `out` under the `command` contract.
59                unsafe { $crate::shell::dispatch(&*plugin, context, out) }
60            })
61        }
62
63        #[doc(hidden)]
64        unsafe extern "C" fn __pmpx_attach(host: *const $crate::abi::PmpxHost) {
65            if host.is_null() {
66                return;
67            }
68
69            // SAFETY: the host hands over its own table, valid for the process.
70            let lookup = unsafe { (*host).capability };
71            let key = $crate::abi::PmpxStr::new(
72                $crate::abi::PMPX_CAP_LOG.as_ptr(),
73                $crate::abi::PMPX_CAP_LOG.len(),
74            );
75            // SAFETY: a borrow that outlives the call.
76            let table = unsafe { lookup(key) };
77            if table.is_null() {
78                return;
79            }
80
81            let table = table as *const $crate::abi::PmpxLog;
82            // Read no further than the host says it built: a host with an older, smaller table is
83            // "no hooks" rather than a read past its end.
84            // SAFETY: the pointer is non-null and belongs to the host.
85            if unsafe { (*table).size } < ::std::mem::size_of::<$crate::abi::PmpxLog>() {
86                return;
87            }
88
89            // SAFETY: checked above, and the host keeps it alive.
90            unsafe { $crate::shell::install_log_table(table) };
91        }
92
93        #[doc(hidden)]
94        unsafe extern "C" fn __pmpx_free_str(s: $crate::abi::PmpxStr) {
95            // SAFETY: the host only passes back what this side leased out.
96            unsafe { $crate::shell::dispatch_free_str(s) }
97        }
98
99        #[doc(hidden)]
100        unsafe extern "C" fn __pmpx_free_command(command: *mut $crate::abi::PmpxCommand) {
101            // SAFETY: as above.
102            unsafe { $crate::shell::dispatch_free_command(command) }
103        }
104
105        #[doc(hidden)]
106        unsafe extern "C" fn __pmpx_capability(
107            name: $crate::abi::PmpxStr,
108        ) -> *const ::std::ffi::c_void {
109            // SAFETY: the host passes a borrow that outlives the call.
110            let Some(name) = (unsafe { $crate::shell::read_str(name) }) else {
111                return ::std::ptr::null();
112            };
113
114            match name {
115                $crate::abi::PMPX_CAP_IDENTITY => ::std::ptr::from_ref(&__PMPX_IDENTITY).cast(),
116                $crate::abi::PMPX_CAP_COMMAND => ::std::ptr::from_ref(&__PMPX_COMMAND).cast(),
117                $crate::abi::PMPX_CAP_ATTACH => ::std::ptr::from_ref(&__PMPX_ATTACH).cast(),
118                // A capability this build does not have is "not here", which is what lets a host
119                // ask for one it knows and this plugin keep working.
120                _ => ::std::ptr::null(),
121            }
122        }
123
124        #[doc(hidden)]
125        static __PMPX_IDENTITY: $crate::abi::PmpxIdentity = $crate::abi::PmpxIdentity {
126            size: ::std::mem::size_of::<$crate::abi::PmpxIdentity>(),
127            name: __pmpx_name,
128            family: __pmpx_family,
129            free_str: __pmpx_free_str,
130        };
131
132        #[doc(hidden)]
133        static __PMPX_COMMAND: $crate::abi::PmpxCommandCap = $crate::abi::PmpxCommandCap {
134            size: ::std::mem::size_of::<$crate::abi::PmpxCommandCap>(),
135            run: __pmpx_run,
136            free_command: __pmpx_free_command,
137        };
138
139        #[doc(hidden)]
140        static __PMPX_ATTACH: $crate::abi::PmpxAttach = $crate::abi::PmpxAttach {
141            size: ::std::mem::size_of::<$crate::abi::PmpxAttach>(),
142            attach: __pmpx_attach,
143        };
144
145        #[doc(hidden)]
146        static __PMPX_PLUGIN: $crate::abi::PmpxPlugin = $crate::abi::PmpxPlugin {
147            abi_major: $crate::abi::PMPX_ABI_MAJOR,
148            rustc_version: $crate::abi::PmpxStr::new(
149                $crate::shell::BUILD_RUSTC.as_ptr(),
150                $crate::shell::BUILD_RUSTC.len(),
151            ),
152            target: $crate::abi::PmpxStr::new(
153                $crate::shell::BUILD_TARGET.as_ptr(),
154                $crate::shell::BUILD_TARGET.len(),
155            ),
156            capability: __pmpx_capability,
157        };
158
159        // Compile-time proof that the shell's shape is the shape the table declares: if the two ever
160        // drift, this stops compiling instead of becoming a call through the wrong function type --
161        // which the ABI's major version could not catch, because it would not change.
162        const _: $crate::abi::RunFn = __pmpx_run;
163
164        /// The one symbol a host looks for.
165        ///
166        /// The version in the name tracks the *root table's layout*, not the keys: a host that finds
167        /// no such symbol says "built against another contract" instead of reading fields that
168        /// moved.
169        #[unsafe(no_mangle)]
170        pub extern "C" fn pmpx_plugin_entry_v3() -> *const $crate::abi::PmpxPlugin {
171            &__PMPX_PLUGIN
172        }
173    };
174}