frust_hotpatch/lib.rs
1// Ported from subsecond 0.7.10 (DioxusLabs, MIT OR Apache-2.0),
2// https://github.com/DioxusLabs/dioxus/tree/main/packages/subsecond.
3//
4//! Frust's native-only hot-patch runtime: the in-app half of hot patching.
5//!
6//! Wrap a patch point in [`HotFn`] (or the free [`call`]). In a debug build every call does one
7//! lookup in the installed [`JumpTable`], keyed on the address of the function's
8//! [`HotFunction::call_it`] monomorphisation (or, for [`HotFn::from_fn_ptr`], the pointer's own
9//! value), and jumps to the patched version when one is mapped, else runs the original. There is
10//! no stale-call detection and no retry. A patch builder (today `dx` 0.7.10, whose wire format
11//! [`JumpTable`] matches, but whose tables are anchored on `main`, which [`apply_patch`] refuses
12//! with [`PatchError::AnchorMismatch`]: a table must be anchored on [`ANCHOR_SYMBOL`]) compiles the changed code into a library and sends the table;
13//! [`apply_patch`] loads that library, rebases the table onto this process, publishes it, and runs
14//! every [`register_handler`] handler. Patches are serialised, and a patch is refused before
15//! anything is loaded in a release build, while no anchor is set ([`set_anchor`]; the app defines
16//! the [`ANCHOR_SYMBOL`] function), and, in every apply entry ([`apply_patch`] and
17//! [`apply_from_devtools`]), while a layout-mismatch record is unreported
18//! ([`report_layout_mismatch`]; checked under the apply lock, [`PatchError::LayoutMismatchPending`]).
19//!
20//! The layout precondition: every type whose values cross images (a mapped function's arguments,
21//! return and closure captures, a component's state) must keep its layout between the running
22//! image and a patch. Symbol names do not encode layout, so nothing here can check it; the patch
23//! builder's L3 gate does, and the runtime reports a disagreement it meets anyway.
24//!
25//! The patch boundary: nothing rewrites process memory, so the table only redirects calls that
26//! enter through a `HotFn`. Patch code is also reached without one, though: a trait object, stored
27//! closure or fn pointer created while patched code ran points straight into the patch library,
28//! and calling it later runs that code with no `HotFn` in between (including after a newer patch).
29//! Likewise such values created before a patch keep running the old code until they are rebuilt.
30//!
31//! Lookups happen only under `cfg!(debug_assertions)`. In a release build every `HotFn` call is a
32//! direct call and the table is never read. See the crate README for scope and differences.
33
34#[cfg(target_arch = "wasm32")]
35compile_error!("frust-hotpatch is native-only: wasm32 patch loading is not ported");
36
37mod anchor;
38#[cfg(target_os = "android")]
39mod android;
40mod hot_fn;
41mod jump_table;
42mod patch;
43
44#[doc(hidden)]
45pub use anchor::aslr_reference;
46pub use anchor::{ANCHOR_SYMBOL, UNKNOWN_IMAGE, set_anchor};
47pub use hot_fn::{
48 FnPointer, HotFn, HotFnPtr, HotFunction, MissedKey, call, fall_through_count,
49 last_call_fell_through, missed_keys, seam_hits,
50};
51pub use jump_table::{AddressHasher, AddressMap, BuildAddressHasher, JumpTable};
52#[cfg(any(unix, windows))]
53pub use patch::{ApplyReport, apply_from_devtools, apply_patch, load_patch_library};
54pub use patch::{
55 LayoutMismatch, PatchError, get_jump_table, mark_layout_mismatches_reported,
56 pending_layout_mismatches, register_handler, report_layout_mismatch,
57};