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
use *;
use Cell;
use panic;
use Rc;
/// Regression test: `Signal::clear_listeners` must be idempotent so that
/// `cleanup_subtree` can be safely invoked multiple times on the same element
/// (e.g. via re-patching during a re-render before the element is actually
/// detached). Without the `is_alive` guard, the second call would re-enter
/// `inner_mut` on a deactivated-but-still-alive `SignalInner` and run
/// `set_value(String::new())` and `cleanup_attr_slot` a second time — at
/// best wasteful, at worst racy with `Registry` mutation.
///
/// The contract is: after `clear_listeners(addr)`, a second call is a safe
/// no-op (verified indirectly: if the first call leaves the signal in an
/// inconsistent state, the second call would either panic, double-free, or
/// trigger the guard's branch here).
///
/// `clear_listeners` does NOT free the bridge signal's heap allocation. The
/// bridge is only freed when *every* `subscribe`d source signal still holding
/// a closure that captures the bridge has been deactivated (see
/// `clear_listeners_then_source_deactivate_frees_bridge`). This ordering
/// keeps `bridge.get()` / `bridge.set()` from a stale listener safe, because
/// the address either still points at a valid `SignalInner` (with
/// `alive == false`, so calls are no-ops), or it has been reclaimed after
/// the last subscriber was torn down.
/// Verifies that an async-style stale listener registered on the source
/// signal is safe to fire after the bridge `Signal<String>` was deactivated
/// via `clear_listeners`.
///
/// Scenarios like `use_window_event` or `use_interval` keep the source
/// signal alive (and its listener closure) past the moment when the bound
/// DOM element is detached and its bridge signal is cleaned up. The
/// bridge's `Signal<T>` handle is `Copy`, so a stale closure holds only
/// the raw address — dereferencing it after the bridge's heap allocation
/// would be undefined behaviour.
///
/// `Signal::clear_listeners` deactivates the bridge (sets `alive = false`)
/// and removes the bridge from the global address registry, but the heap
/// allocation is only freed once `source.deactivate()` runs. Until then,
/// the stale listener's `bridge.get()` dereferences a still-allocated
/// `SignalInner` whose `alive == false`, returning the empty-string default
/// — safe, not UB.
/// Verifies that the bridge signal's heap allocation IS reclaimed when
/// the source signal is deactivated after the bound element was detached.
///
/// This is the second half of the safe reclamation contract: once `source`
/// has been deactivated, its listener closure (which captured the bridge
/// address) has been dropped, so no stale dereference can occur. The
/// reverse-index kept in `BRIDGE_REFS` notices that the bridge now has
/// zero subscribers AND the bridge is no longer in `SIGNAL_INNER_REGISTRY`
/// (i.e., `clear_listeners` already ran on it), and frees the
/// `Box<SignalInner<String>>`.
///
/// We assert this indirectly by checking that after `clear_listeners` +
/// `source.deactivate()`, the bridge address is NOT in
/// `SIGNAL_INNER_REGISTRY`. With the fix, `clear_listeners` itself removes
/// the bridge from the registry. The interesting assertion is that the
/// `deactivate()` call did not panic / double-free anything — which would
/// happen if the bridge had been freed before its last subscriber was
/// dropped.