1-- Generic deterministic failure injection. The wrapper retains a mutable
2-- pointer to its supplied provider and delegates every successful free.
3
4import core/mem
5
6--- Allocator wrapper with deterministic failure injection and cumulative
7--- counters. Borrows its inner provider, delegates permitted allocations and
8--- frees, and refuses in-place growth.
9public counted: type (inner_provider: type) = struct
10 inner: ptr mut inner_provider
11 remaining: usize
12 attempts: usize
13 delegated: usize
14 successes: usize
15 injected_failures: usize
16 inner_failures: usize
17 frees: usize
18 live: usize
19end counted
20
21--- Wrap an allocator with an allocation-attempt budget. Inner failures
22--- consume the budget too; injected failures never call the inner provider.
23---
24--- allowed_attempts is the number of future allocation calls allowed to
25--- reach inner.alloc. An inner failure consumes one such attempt. Injected
26--- failures increment attempts/injected_failures only and never call inner.
27public new: (inner_provider: type is mem.allocator,
28 inner: ptr mut inner_provider,
29 allowed_attempts: usize)
30 -> (state: counted(inner_provider) from inner) =
31 state = (inner: inner, remaining: allowed_attempts,
32 attempts: 0, delegated: 0, successes: 0,
33 injected_failures: 0, inner_failures: 0,
34 frees: 0, live: 0)
35end new
36
37--- Replace the future delegation budget without resetting accumulated
38--- counters.
39---
40--- Replace only the future delegation budget. Evidence counters accumulate
41--- across retries so the failed and successful calls remain distinguishable.
42public permit: (inner_provider: type,
43 inout state: counted(inner_provider),
44 allowed_attempts: usize) -> none =
45 state.remaining = allowed_attempts
46end permit
47
48counted_alloc: (inner_provider: type,
49 inout state: counted(inner_provider),
50 size: usize, alignment: usize)
51 -> (block: ptr mut u8) ! mem.out_of_memory =
52 inc state.attempts
53 if state.remaining == 0 then
54 inc state.injected_failures
55 fail mem.out_of_memory
56 end if
57
58 dec state.remaining
59 inc state.delegated
60 block = mem.allocate(state.inner.val, size, alignment)
61 else (problem)
62 _ = problem
63 inc state.inner_failures
64 fail mem.out_of_memory
65 end
66 inc state.successes
67 inc state.live
68end counted_alloc
69
70counted_free: (inner_provider: type,
71 inout state: counted(inner_provider),
72 block: ptr mut u8, size: usize) -> none =
73 mem.free(state.inner.val, block, size)
74 -- Allocator free has no result. These two counters are therefore exact
75 -- under the allocator contract: the caller frees each successful block
76 -- once with its original extent. A malformed or duplicate free is still
77 -- delegated, but an inner rejection cannot be reflected in live.
78 inc state.frees
79 if state.live > 0 then
80 dec state.live
81 end if
82end counted_free
83
84counted_grow: (inner_provider: type,
85 inout state: counted(inner_provider), block: ptr mut u8,
86 old_size: usize, new_size: usize, alignment: usize)
87 -> (grown: bool) =
88 _ = state.live
89 _ = block
90 _ = old_size
91 _ = new_size
92 _ = alignment
93 grown = false
94end counted_grow
95
96(inner_provider: type is mem.allocator) counted(inner_provider)
97 is mem.allocator (alloc: counted_alloc, grow: counted_grow,
98 free: counted_free)
99
100--- Return how many allocation attempts may still reach the inner provider.
101public remaining: (inner_provider: type,
102 state: counted(inner_provider)) -> (count: usize) =
103 count = state.remaining
104end remaining
105
106--- Return all allocation attempts, including injected and inner failures.
107public attempts: (inner_provider: type,
108 state: counted(inner_provider)) -> (count: usize) =
109 count = state.attempts
110end attempts
111
112--- Return allocation attempts passed through to the inner provider.
113public delegated: (inner_provider: type,
114 state: counted(inner_provider)) -> (count: usize) =
115 count = state.delegated
116end delegated
117
118--- Return the number of successful delegated allocations.
119public successes: (inner_provider: type,
120 state: counted(inner_provider)) -> (count: usize) =
121 count = state.successes
122end successes
123
124--- Return attempts rejected by the wrapper before calling the inner provider.
125public injected_failures: (inner_provider: type,
126 state: counted(inner_provider))
127 -> (count: usize) =
128 count = state.injected_failures
129end injected_failures
130
131--- Return allocation failures reported by the inner provider.
132public inner_failures: (inner_provider: type,
133 state: counted(inner_provider)) -> (count: usize) =
134 count = state.inner_failures
135end inner_failures
136
137--- Return the number of delegated free calls.
138public frees: (inner_provider: type,
139 state: counted(inner_provider)) -> (count: usize) =
140 count = state.frees
141end frees
142
143--- Return successful allocations minus free calls, bounded below by zero.
144--- Exact under the allocator contract; malformed or duplicate frees are not
145--- validated here.
146public live: (inner_provider: type,
147 state: counted(inner_provider)) -> (count: usize) =
148 -- Successful allocations less delegated frees under valid-free use.
149 count = state.live
150end live