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 }