lib/memtrace/src/coverage.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3
4 const metadata_tag = "\"meta\":\"memtrace.coverage\"";
5
6 pub const Universe = enum {
7 registered_allocator_handles,
8 owned_allocator_producers_and_registered_boundaries,
9 owned_allocator_producers_registered_boundaries_and_sys_memory_operations,
10 process_memory_operations_during_trace_epoch,
11
12 pub fn tag(self: Universe) []const u8 {
13 return switch (self) {
14 .registered_allocator_handles => "registered_allocator_handles",
15 .owned_allocator_producers_and_registered_boundaries => "owned_allocator_producers_and_registered_boundaries",
16 .owned_allocator_producers_registered_boundaries_and_sys_memory_operations => "owned_allocator_producers_registered_boundaries_and_sys_memory_operations",
17 .process_memory_operations_during_trace_epoch => "process_memory_operations_during_trace_epoch",
18 };
19 }
20
21 fn fromTag(text: []const u8) ?Universe {
22 if (std.mem.eql(u8, text, "registered_allocator_handles")) {
23 return .registered_allocator_handles;
24 }
25 if (std.mem.eql(
26 u8,
27 text,
28 "owned_allocator_producers_and_registered_boundaries",
29 )) {
30 return .owned_allocator_producers_and_registered_boundaries;
31 }
32 if (std.mem.eql(
33 u8,
34 text,
35 "owned_allocator_producers_registered_boundaries_and_sys_memory_operations",
36 )) {
37 return .owned_allocator_producers_registered_boundaries_and_sys_memory_operations;
38 }
39 if (std.mem.eql(
40 u8,
41 text,
42 "process_memory_operations_during_trace_epoch",
43 )) {
44 return .process_memory_operations_during_trace_epoch;
45 }
46 return null;
47 }
48 };
49
50 pub const Inclusion = enum {
51 observed,
52 proved_absent,
53 isolated,
54 no_memory_effect,
55 unobserved,
56
57 pub fn tag(self: Inclusion) []const u8 {
58 return switch (self) {
59 .observed => "observed",
60 .proved_absent => "proved_absent",
61 .isolated => "isolated",
62 .no_memory_effect => "no_memory_effect",
63 .unobserved => "opaque",
64 };
65 }
66
67 fn fromTag(text: []const u8) ?Inclusion {
68 if (std.mem.eql(u8, text, "observed")) return .observed;
69 if (std.mem.eql(u8, text, "proved_absent")) return .proved_absent;
70 if (std.mem.eql(u8, text, "isolated")) return .isolated;
71 if (std.mem.eql(u8, text, "no_memory_effect")) {
72 return .no_memory_effect;
73 }
74 if (std.mem.eql(u8, text, "opaque")) return .unobserved;
75 return null;
76 }
77 };
78
79 pub const StaticEvidence = struct {
80 direct_os_memory_operations_absent: bool = false,
81 unowned_allocator_producers_absent: bool = false,
82 foreign_allocations_absent: bool = false,
83 };
84
85 pub const Manifest = struct {
86 universe: Universe = .registered_allocator_handles,
87 child_allocator_fast_paths: Inclusion = .unobserved,
88 sys_memory_operations: Inclusion = .unobserved,
89 direct_os_memory_operations: Inclusion = .unobserved,
90 unowned_allocator_producers: Inclusion = .unobserved,
91 foreign_allocations: Inclusion = .unobserved,
92 observer_control: Inclusion = .isolated,
93 zero_length_operations: Inclusion = .no_memory_effect,
94 predispatch_failures: Inclusion = .no_memory_effect,
95 observer_control_operations: u64 = 0,
96
97 pub fn processComplete(self: Manifest) bool {
98 return self.universe ==
99 .process_memory_operations_during_trace_epoch and
100 self.child_allocator_fast_paths == .observed and
101 self.sys_memory_operations == .observed and
102 observedOrAbsent(self.direct_os_memory_operations) and
103 observedOrAbsent(self.unowned_allocator_producers) and
104 observedOrAbsent(self.foreign_allocations) and
105 observedOrIsolated(self.observer_control) and
106 observedOrNoEffect(self.zero_length_operations) and
107 observedOrNoEffect(self.predispatch_failures);
108 }
109
110 pub fn statusTag(self: Manifest) []const u8 {
111 if (self.processComplete()) return "process_memory_operation_complete";
112 return switch (self.universe) {
113 .registered_allocator_handles => "boundary_complete",
114 .owned_allocator_producers_and_registered_boundaries => "owned_producer_complete",
115 .owned_allocator_producers_registered_boundaries_and_sys_memory_operations => "owned_producer_and_sys_memory_operation_complete",
116 .process_memory_operations_during_trace_epoch => "process_memory_operation_incomplete",
117 };
118 }
119 };
120
121 pub fn boundaryManifest() Manifest {
122 return .{};
123 }
124
125 pub fn ownedProducerManifest() Manifest {
126 return .{
127 .universe = .owned_allocator_producers_registered_boundaries_and_sys_memory_operations,
128 .child_allocator_fast_paths = .observed,
129 .sys_memory_operations = .observed,
130 };
131 }
132
133 pub fn processManifest(evidence: StaticEvidence) Manifest {
134 var manifest = ownedProducerManifest();
135 manifest.universe = .process_memory_operations_during_trace_epoch;
136 manifest.direct_os_memory_operations = if (evidence.direct_os_memory_operations_absent)
137 .proved_absent
138 else
139 .unobserved;
140 manifest.unowned_allocator_producers = if (evidence.unowned_allocator_producers_absent)
141 .proved_absent
142 else
143 .unobserved;
144 manifest.foreign_allocations = if (evidence.foreign_allocations_absent)
145 .proved_absent
146 else
147 .unobserved;
148 return manifest;
149 }
150
151 fn observedOrAbsent(state: Inclusion) bool {
152 return state == .observed or state == .proved_absent;
153 }
154
155 fn observedOrIsolated(state: Inclusion) bool {
156 return state == .observed or state == .isolated;
157 }
158
159 fn observedOrNoEffect(state: Inclusion) bool {
160 return state == .observed or state == .no_memory_effect;
161 }
162
163 pub fn isMetadataLine(line: []const u8) bool {
164 return std.mem.indexOf(u8, line, metadata_tag) != null;
165 }
166
167 pub fn writeMetadata(writer: *std.Io.Writer, manifest: Manifest) !void {
168 var stream = pretty_json.Writer.init(writer, .minified);
169 const object = try stream.object();
170 try object.field("v", 3);
171 try object.field("meta", "memtrace.coverage");
172 try object.field("universe", manifest.universe.tag());
173 try object.field("child_allocator_fast_paths", manifest.child_allocator_fast_paths.tag());
174 try object.field("sys_memory_operations", manifest.sys_memory_operations.tag());
175 try object.field("direct_os_memory_operations", manifest.direct_os_memory_operations.tag());
176 try object.field("unowned_allocator_producers", manifest.unowned_allocator_producers.tag());
177 try object.field("foreign_allocations", manifest.foreign_allocations.tag());
178 try object.field("observer_control", manifest.observer_control.tag());
179 try object.field("zero_length_operations", manifest.zero_length_operations.tag());
180 try object.field("predispatch_failures", manifest.predispatch_failures.tag());
181 try object.field("observer_control_operations", manifest.observer_control_operations);
182 try object.endLine();
183 }
184
185 pub fn parseMetadata(allocator: std.mem.Allocator, line: []const u8) !Manifest {
186 var parsed = std.json.parseFromSlice(std.json.Value, allocator, line, .{}) catch
187 return error.InvalidCoverageMetadata;
188 defer parsed.deinit();
189 const object = switch (parsed.value) {
190 .object => |value| value,
191 else => return error.InvalidCoverageMetadata,
192 };
193 if (try unsigned(object, "v") != 3) return error.UnsupportedCoverageVersion;
194 if (!std.mem.eql(u8, try string(object, "meta"), "memtrace.coverage")) {
195 return error.InvalidCoverageMetadata;
196 }
197 return .{
198 .universe = Universe.fromTag(try string(object, "universe")) orelse
199 return error.InvalidCoverageMetadata,
200 .child_allocator_fast_paths = try inclusion(object, "child_allocator_fast_paths"),
201 .sys_memory_operations = try inclusion(object, "sys_memory_operations"),
202 .direct_os_memory_operations = try inclusion(
203 object,
204 "direct_os_memory_operations",
205 ),
206 .unowned_allocator_producers = try inclusion(
207 object,
208 "unowned_allocator_producers",
209 ),
210 .foreign_allocations = try inclusion(object, "foreign_allocations"),
211 .observer_control = try inclusion(object, "observer_control"),
212 .zero_length_operations = try inclusion(object, "zero_length_operations"),
213 .predispatch_failures = try inclusion(object, "predispatch_failures"),
214 .observer_control_operations = try unsigned(
215 object,
216 "observer_control_operations",
217 ),
218 };
219 }
220
221 fn inclusion(object: std.json.ObjectMap, key: []const u8) !Inclusion {
222 return Inclusion.fromTag(try string(object, key)) orelse
223 error.InvalidCoverageMetadata;
224 }
225
226 fn string(object: std.json.ObjectMap, key: []const u8) ![]const u8 {
227 const value = object.get(key) orelse return error.InvalidCoverageMetadata;
228 return switch (value) {
229 .string => |text| text,
230 else => error.InvalidCoverageMetadata,
231 };
232 }
233
234 fn unsigned(object: std.json.ObjectMap, key: []const u8) !u64 {
235 const value = object.get(key) orelse return error.InvalidCoverageMetadata;
236 return switch (value) {
237 .integer => |number| if (number >= 0)
238 @intCast(number)
239 else
240 error.InvalidCoverageMetadata,
241 else => error.InvalidCoverageMetadata,
242 };
243 }
244
245 test "coverage metadata preserves the allocator boundary contract" {
246 const expected = boundaryManifest();
247 var output = std.Io.Writer.Allocating.init(std.testing.allocator);
248 defer output.deinit();
249 try writeMetadata(&output.writer, expected);
250 const actual = try parseMetadata(
251 std.testing.allocator,
252 std.mem.trimEnd(u8, output.written(), "\n"),
253 );
254 try std.testing.expectEqualDeep(expected, actual);
255 try std.testing.expect(!actual.processComplete());
256 }
257
258 test "coverage metadata preserves the owned producer contract" {
259 const expected = ownedProducerManifest();
260 var output = std.Io.Writer.Allocating.init(std.testing.allocator);
261 defer output.deinit();
262 try writeMetadata(&output.writer, expected);
263 const actual = try parseMetadata(
264 std.testing.allocator,
265 std.mem.trimEnd(u8, output.written(), "\n"),
266 );
267 try std.testing.expectEqualDeep(expected, actual);
268 try std.testing.expectEqualStrings(
269 "owned_producer_and_sys_memory_operation_complete",
270 actual.statusTag(),
271 );
272 try std.testing.expect(!actual.processComplete());
273 }
274
275 test "coverage metadata preserves the certified process contract" {
276 const expected = processManifest(.{
277 .direct_os_memory_operations_absent = true,
278 .unowned_allocator_producers_absent = true,
279 .foreign_allocations_absent = true,
280 });
281 var output = std.Io.Writer.Allocating.init(std.testing.allocator);
282 defer output.deinit();
283 try writeMetadata(&output.writer, expected);
284 const actual = try parseMetadata(
285 std.testing.allocator,
286 std.mem.trimEnd(u8, output.written(), "\n"),
287 );
288 try std.testing.expectEqualDeep(expected, actual);
289 try std.testing.expect(actual.processComplete());
290 try std.testing.expectEqualStrings(
291 "process_memory_operation_complete",
292 actual.statusTag(),
293 );
294 }
295
296 test "process coverage rejects category states with the wrong meaning" {
297 var manifest = processManifest(.{
298 .direct_os_memory_operations_absent = true,
299 .unowned_allocator_producers_absent = true,
300 .foreign_allocations_absent = true,
301 });
302 manifest.direct_os_memory_operations = .isolated;
303 try std.testing.expect(!manifest.processComplete());
304 manifest.direct_os_memory_operations = .proved_absent;
305 manifest.observer_control = .no_memory_effect;
306 try std.testing.expect(!manifest.processComplete());
307 manifest.observer_control = .isolated;
308 manifest.zero_length_operations = .proved_absent;
309 try std.testing.expect(!manifest.processComplete());
310 }