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
//! The teardown proof that gates generation reclamation.
//!
//! JIT metadata — record schemas, tuple schemas, field-name strings, debug
//! local metadata — is reachable from *live heap objects*, not only from
//! generated code. A [`RecordPayload`](crate::RecordPayload) holds a
//! `*const RecordSchema`; a [`TuplePayload`](crate::TuplePayload) holds a
//! `*const TupleSchema`. While those objects are alive, freeing the arena the
//! schemas live in is a use-after-free waiting for the next `==`, `format` or
//! `hash`.
//!
//! The metadata is reclaimable, so the ordering is *encoded* rather than
//! documented: a generation is reclaimed by handing it a [`HeapDrained`], and
//! the only way to obtain one is [`Runtime::teardown`], which consumes the
//! runtime and drops the heap — running every finalizer first.
//!
//! [`Runtime::teardown`]: crate::Runtime::teardown
/// Proof that a heap has been dropped, and with it every object that could
/// hold a pointer into a JIT generation's arena.
///
/// Minted only by [`Runtime::teardown`](crate::Runtime::teardown). It carries
/// no data; its whole purpose is that a function requiring one cannot be called
/// before the heap is gone.
///
/// **What it does and does not prove.** It proves *a* runtime was torn down. A
/// process that builds two runtimes could tear down the first and retire a
/// generation the second still refers into — the token is a guard rail against
/// the ordering mistake, not a theorem about aliasing. The CLI and the debugger
/// each own exactly one `Runtime`, which is the configuration it is written
/// for. It is `Clone` because one teardown can legitimately retire several
/// generations (the debugger has a main generation and an evaluation
/// generation).
/// Drop every registered parser plan and every schema the parser interpreter
/// built from one.
///
/// These two have to go together. A named-capture template's `RecordSchema`
/// borrows its field names straight out of plan storage, so retiring the plans
/// while the schema cache still holds them leaves dangling `&'static str`s;
/// retiring the schemas while a live `RecordPayload` still points at one is a
/// use-after-free at the next `==`. The [`HeapDrained`] argument rules out the
/// second, and doing both here rules out the first.
///
/// This lives in `praxis-runtime` rather than in `praxis-input-parser` because
/// the proof does: `praxis-input-parser` cannot depend on this crate (the
/// interpreter makes the arrow point the other way).