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 injector: 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 injector
20
21--- Wrap an allocator with a budget of `allowed_attempts` allocation calls
22--- that may reach it. An inner failure consumes one of them too; an injected
23--- failure never calls the inner provider and counts only in `attempts` and
24--- `injected_failures`.
25public new: (inner_provider: type is mem.allocator,
26 inner: ptr mut inner_provider,
27 allowed_attempts: usize)
28 -> (state: injector(inner_provider) from inner) =
29 state = (inner: inner, remaining: allowed_attempts,
30 attempts: 0, delegated: 0, successes: 0,
31 injected_failures: 0, inner_failures: 0,
32 frees: 0, live: 0)
33end new
34
35--- Replace the future delegation budget without resetting the counters, so
36--- failed and successful calls stay distinguishable across retries.
37public permit: (inner_provider: type,
38 inout state: injector(inner_provider),
39 allowed_attempts: usize) -> none =
40 state.remaining = allowed_attempts
41end permit
42
43injector_alloc: (inner_provider: type,
44 inout state: injector(inner_provider),
45 size: usize, alignment: usize)
46 -> (block: ptr mut u8) ! mem.out_of_memory =
47 inc state.attempts
48 if state.remaining == 0 then
49 inc state.injected_failures
50 fail mem.out_of_memory
51 end if
52
53 dec state.remaining
54 inc state.delegated
55 block = mem.allocate(state.inner.val, size, alignment)
56 else (problem)
57 _ = problem
58 inc state.inner_failures
59 fail mem.out_of_memory
60 end
61 inc state.successes
62 inc state.live
63end injector_alloc
64
65injector_free: (inner_provider: type,
66 inout state: injector(inner_provider),
67 block: ptr mut u8, size: usize) -> none =
68 mem.free(state.inner.val, block, size)
69 -- Allocator free has no result. These two counters are therefore exact
70 -- under the allocator contract: the caller frees each successful block
71 -- once with its original extent. A malformed or duplicate free is still
72 -- delegated, but an inner rejection cannot be reflected in live.
73 inc state.frees
74 if state.live > 0 then
75 dec state.live
76 end if
77end injector_free
78
79injector_grow: (inner_provider: type,
80 inout state: injector(inner_provider), block: ptr mut u8,
81 old_size: usize, new_size: usize, alignment: usize)
82 -> (grown: bool) =
83 _ = state.live
84 _ = block
85 _ = old_size
86 _ = new_size
87 _ = alignment
88 grown = false
89end injector_grow
90
91(inner_provider: type is mem.allocator) injector(inner_provider)
92 is mem.allocator (alloc: injector_alloc, grow: injector_grow,
93 free: injector_free)
94
95--- Return how many allocation attempts may still reach the inner provider.
96public remaining: (inner_provider: type,
97 state: injector(inner_provider)) -> (count: usize) =
98 count = state.remaining
99end remaining
100
101--- Return all allocation attempts, including injected and inner failures.
102public attempts: (inner_provider: type,
103 state: injector(inner_provider)) -> (count: usize) =
104 count = state.attempts
105end attempts
106
107--- Return allocation attempts passed through to the inner provider.
108public delegated: (inner_provider: type,
109 state: injector(inner_provider)) -> (count: usize) =
110 count = state.delegated
111end delegated
112
113--- Return the number of successful delegated allocations.
114public successes: (inner_provider: type,
115 state: injector(inner_provider)) -> (count: usize) =
116 count = state.successes
117end successes
118
119--- Return attempts rejected by the wrapper before calling the inner provider.
120public injected_failures: (inner_provider: type,
121 state: injector(inner_provider))
122 -> (count: usize) =
123 count = state.injected_failures
124end injected_failures
125
126--- Return allocation failures reported by the inner provider.
127public inner_failures: (inner_provider: type,
128 state: injector(inner_provider)) -> (count: usize) =
129 count = state.inner_failures
130end inner_failures
131
132--- Return the number of delegated free calls.
133public frees: (inner_provider: type,
134 state: injector(inner_provider)) -> (count: usize) =
135 count = state.frees
136end frees
137
138--- Return successful allocations minus free calls, bounded below by zero.
139--- Exact under the allocator contract; malformed or duplicate frees are not
140--- validated here.
141public live: (inner_provider: type,
142 state: injector(inner_provider)) -> (count: usize) =
143 -- Successful allocations less delegated frees under valid-free use.
144 count = state.live
145end live