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
//! Ground-truth effect-atom recorder (issue #870, T2 effects epic,
//! `docs/effects-spec.md`). The effects analogue of the oracle: this module
//! records, per **executing definition scope**, the atomic effects the VM
//! actually performs — cells read, cells written, external kinds called —
//! so `brink-test-harness` can assert the statically-inferred `effects(def)`
//! row (`brink-db::ProjectDb::effects`) covers every one of them for every
//! def a real run executed. A purely structural inter-row consistency check
//! (caller's row ⊇ callee's row) cannot catch an under-report where *both*
//! rows silently agree on the wrong (too-small) answer — exactly the #866
//! ref-param-write regression this issue is named for. This is the
//! independent, run-the-bytecode-and-look check that closes that gap.
//!
//! **Attribution mirrors the static analyzer's own model exactly**
//! (`brink_analyzer::infer::body::record_ref_param_writes`), not naively
//! "whichever def's bytecode happens to be executing": a `ref` argument's
//! pointer/projection is constructed exactly once, at the call site, inside
//! the *caller's* own bytecode (`Opcode::PushVarPointer`/`Opcode::
//! MakeProjection` — both are emitted *only* there, never for a plain read,
//! confirmed against `brink-codegen-inkb`'s `expr.rs`). The eventual
//! dereference deep inside the callee's frame (`SetTemp`/`GetTemp`/
//! `TakeTemp`'s pointer/projection arms, `ProjRead`/`ProjWrite`) is
//! deliberately **not** re-recorded — the callee's own row is generic over
//! whichever concrete cell a caller bound its `ref` parameter to, so the
//! static model charges the write to the call site that names the concrete
//! global, never to the callee. Recording at construction time reproduces
//! that attribution for free: whichever def's bytecode is running when the
//! pointer/projection value is built is, by construction, the def the
//! static analyzer also charges. See the call sites in `vm.rs`'s
//! `note_effect_*` helpers for the exact opcodes instrumented.
//!
//! Feature-gated exactly like the `bench-counters` module (issue #821):
//! this module and every call site are compiled out entirely unless
//! `effect-trace` is enabled (not part of `default` — no released consumer
//! should ever turn it on), so an ordinary build pays exactly zero cost.
use ;
use String;
use Mutex;
use DefinitionId;
/// Atoms observed for one executed definition scope (`docs/effects-spec.md`
/// §2) — the runtime counterpart of `brink_analyzer::EffectRow`'s
/// `{reads, writes, calls}` (this module never constructs an opaque row:
/// every atom the VM performs is concrete).
static OBSERVED: = new;
/// Run `f` against the map, recovering from lock poisoning rather than
/// panicking (`unwrap`/`expect` on a `PoisonError` are denied outside tests
/// by workspace lint policy) — a panicking test elsewhere in the same
/// process must never wedge every subsequent recorder call.
/// Record a cell read, attributed to `def` (the definition scope executing
/// when the read happened — see the module docs for what "attributed to"
/// means for a pointer/projection-mediated access).
/// Record a cell write, attributed to `def`.
/// Record an external-kind call, attributed to `def`.
/// NS-A2 (issue #1108): record a visible content emission, attributed to
/// `def`.
/// NS-A2: record a tag-channel touch, attributed to `def`.
/// NS-A2: record a tracked turn-terminating fault, attributed to `def` (the
/// definition scope executing when `vm::step` returned the fault).
/// NS-A2 (issue #1108, from #1097): is this error one of the **designed
/// domain faults** the `faults` row dimension tracks? The inventory mirrors
/// the static harvest in `brink-analyzer::infer::body` exactly — every
/// variant listed here must be raisable only by a construct that sets the
/// static `faults` bit (indexing, `/`/`mod`, the faulting stdlib
/// intrinsics, conversions, `ref` projections, value calls), or the
/// ground-truth harness would report a false under-report.
///
/// F34 note: `ComparatorWroteState` (dev-mode-only, like
/// `UnorderedComparand`) is raisable only inside a pure-callback frame — a
/// frame reachable only through `sort_by`/`sorted_by`'s, or the fn-value
/// verb trio's (`map`/`filter`/`fold`, issue #1679), value-call dispatch,
/// whose call sites the static harvest conservatively marks as faulting
/// (`check_value_call`'s dispatch-faults rule). The observation attributes
/// the fault to the *callee's* def (the scope executing at the write
/// opcode), whose own static row need not carry a fault construct —
/// acceptable because the write construct that triggers it is exactly what
/// E119 rejects wherever the callee's origin is provable, and no
/// ground-truth corpus case runs an opaque writing comparator/callback in
/// dev mode.
///
/// `CallbackNotAFunction`/`CallbackReturnType` (issue #1679's dispatch
/// faults, the trio's counterparts to `ComparatorNotAFunction`) sit beside
/// the NS-A4 pair below for the same reason: `intrinsics.rs` declares
/// `map`/`filter`/`fold` as never-fault-discharged (`may_fault`), so this
/// ground-truth recorder must actually observe the trio's dispatch faults
/// or it would silently under-report exactly the class it exists to catch.
///
/// Deliberately NOT tracked (not part of the dimension v1):
/// - gradual-mode type errors (`TypeError`, `NotARecord`,
/// `RecordFieldNotFound`, …) — the strict-mode-eliminated species;
/// - infrastructure/malformed-bytecode errors (stack underflows, invalid
/// ids, decode errors, step/line limits, `RanOutOfContent`);
/// - host-surface errors (`ArgCountMismatch`, `UnknownPath`,
/// `PrivateAccess`, external-resolution errors).
/// Clear every recorded atom. Call before each measured run — the recorder
/// is a single process-wide map, so a caller driving multiple programs (or
/// multiple explored episodes of one program) in the same process must
/// reset between the units it wants to compare independently.
/// Snapshot every def's observed atoms recorded since the last [`reset`].