authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2024-12-17 03:46:56-08:00
committergravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2025-02-25 11:22:33-08:00
log5e0073c898b74f03f0c0e7f8cb859b9ac719bf43
treec93c341b84541d5a1d379bd479991b2cba11cf23
parente902c231c85890fdba486db3469e1aaf0de83da5

ubsan: add a basic runtime


3 files changed, 392 insertions(+), 1 deletions(-)

lib/std/std.zig+1
...@@ -44,6 +44,7 @@ pub const Thread = @import("Thread.zig");...@@ -44,6 +44,7 @@ pub const Thread = @import("Thread.zig");
44pub const Treap = @import("treap.zig").Treap;44pub const Treap = @import("treap.zig").Treap;
45pub const Tz = tz.Tz;45pub const Tz = tz.Tz;
46pub const Uri = @import("Uri.zig");46pub const Uri = @import("Uri.zig");
47pub const ubsan = @import("ubsan.zig");
4748
48pub const array_hash_map = @import("array_hash_map.zig");49pub const array_hash_map = @import("array_hash_map.zig");
49pub const atomic = @import("atomic.zig");50pub const atomic = @import("atomic.zig");
lib/std/ubsan.zig created+390
...@@ -0,0 +1,390 @@
1//! Minimal UBSan Runtime
2
3const std = @import("std");
4const builtin = @import("builtin");
5const assert = std.debug.assert;
6
7const SourceLocation = extern struct {
8 file_name: ?[*:0]const u8,
9 line: u32,
10 col: u32,
11};
12
13const TypeDescriptor = extern struct {
14 kind: Kind,
15 info: Info,
16 // name: [?:0]u8
17
18 const Kind = enum(u16) {
19 integer = 0x0000,
20 float = 0x0001,
21 unknown = 0xFFFF,
22 };
23
24 const Info = extern union {
25 integer: packed struct(u16) {
26 signed: bool,
27 bit_width: u15,
28 },
29 };
30
31 fn getIntegerSize(desc: TypeDescriptor) u64 {
32 assert(desc.kind == .integer);
33 const bit_width = desc.info.integer.bit_width;
34 return @as(u64, 1) << @intCast(bit_width);
35 }
36
37 fn isSigned(desc: TypeDescriptor) bool {
38 return desc.kind == .integer and desc.info.integer.signed;
39 }
40
41 fn getName(desc: *const TypeDescriptor) [:0]const u8 {
42 return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor));
43 }
44};
45
46const ValueHandle = *const opaque {
47 fn getValue(handle: ValueHandle, data: anytype) Value {
48 return .{ .handle = handle, .type_descriptor = data.type_descriptor };
49 }
50};
51
52const Value = extern struct {
53 type_descriptor: *const TypeDescriptor,
54 handle: ValueHandle,
55
56 fn getUnsignedInteger(value: Value) u128 {
57 assert(!value.type_descriptor.isSigned());
58 const size = value.type_descriptor.getIntegerSize();
59 const max_inline_size = @bitSizeOf(ValueHandle);
60 if (size <= max_inline_size) {
61 return @intFromPtr(value.handle);
62 }
63
64 return switch (size) {
65 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*,
66 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*,
67 else => unreachable,
68 };
69 }
70
71 fn getSignedInteger(value: Value) i128 {
72 assert(value.type_descriptor.isSigned());
73 const size = value.type_descriptor.getIntegerSize();
74 const max_inline_size = @bitSizeOf(ValueHandle);
75 if (size <= max_inline_size) {
76 const extra_bits: u6 = @intCast(max_inline_size - size);
77 const handle: i64 = @bitCast(@intFromPtr(value.handle));
78 return (handle << extra_bits) >> extra_bits;
79 }
80 return switch (size) {
81 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*,
82 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*,
83 else => unreachable,
84 };
85 }
86
87 fn isMinusOne(value: Value) bool {
88 return value.type_descriptor.isSigned() and
89 value.getSignedInteger() == -1;
90 }
91
92 fn isNegative(value: Value) bool {
93 return value.type_descriptor.isSigned() and
94 value.getSignedInteger() < 0;
95 }
96
97 fn getPositiveInteger(value: Value) u128 {
98 if (value.type_descriptor.isSigned()) {
99 const signed = value.getSignedInteger();
100 assert(signed >= 0);
101 return @intCast(signed);
102 } else {
103 return value.getUnsignedInteger();
104 }
105 }
106
107 pub fn format(
108 value: Value,
109 comptime fmt: []const u8,
110 _: std.fmt.FormatOptions,
111 writer: anytype,
112 ) !void {
113 comptime assert(fmt.len == 0);
114
115 switch (value.type_descriptor.kind) {
116 .integer => {
117 if (value.type_descriptor.isSigned()) {
118 try writer.print("{}", .{value.getSignedInteger()});
119 } else {
120 try writer.print("{}", .{value.getUnsignedInteger()});
121 }
122 },
123 .float => @panic("TODO: write float"),
124 .unknown => try writer.writeAll("(unknown)"),
125 }
126 }
127};
128
129const OverflowData = extern struct {
130 loc: SourceLocation,
131 type_descriptor: *const TypeDescriptor,
132};
133
134fn overflowHandler(
135 comptime sym_name: []const u8,
136 comptime operator: []const u8,
137) void {
138 const S = struct {
139 fn handler(
140 data: *OverflowData,
141 lhs_handle: ValueHandle,
142 rhs_handle: ValueHandle,
143 ) callconv(.C) noreturn {
144 const lhs = lhs_handle.getValue(data);
145 const rhs = rhs_handle.getValue(data);
146
147 const is_signed = data.type_descriptor.isSigned();
148 const fmt = "{s} integer overflow: " ++ "{} " ++
149 operator ++ " {} cannot be represented in type {s}\n";
150
151 logMessage(fmt, .{
152 if (is_signed) "signed" else "unsigned",
153 lhs,
154 rhs,
155 data.type_descriptor.getName(),
156 });
157 }
158 };
159
160 exportHandler(&S.handler, sym_name, true);
161}
162
163fn negationHandler(
164 data: *const OverflowData,
165 old_value_handle: ValueHandle,
166) callconv(.C) noreturn {
167 const old_value = old_value_handle.getValue(data);
168 logMessage(
169 "negation of {} cannot be represented in type {s}\n",
170 .{ old_value, data.type_descriptor.getName() },
171 );
172}
173
174fn divRemHandler(
175 data: *const OverflowData,
176 lhs_handle: ValueHandle,
177 rhs_handle: ValueHandle,
178) callconv(.C) noreturn {
179 const is_signed = data.type_descriptor.isSigned();
180 const lhs = lhs_handle.getValue(data);
181 const rhs = rhs_handle.getValue(data);
182
183 if (is_signed and rhs.getSignedInteger() == -1) {
184 logMessage(
185 "division of {} by -1 cannot be represented in type {s}\n",
186 .{ lhs, data.type_descriptor.getName() },
187 );
188 } else logMessage("division by zero\n", .{});
189}
190
191const AlignmentAssumptionData = extern struct {
192 loc: SourceLocation,
193 assumption_loc: SourceLocation,
194 type_descriptor: *const TypeDescriptor,
195};
196
197fn alignmentAssumptionHandler(
198 data: *const AlignmentAssumptionData,
199 pointer: ValueHandle,
200 alignment: ValueHandle,
201 maybe_offset: ?ValueHandle,
202) callconv(.C) noreturn {
203 _ = pointer;
204 // TODO: add the hint here?
205 // const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
206 // const lsb = @ctz(real_pointer);
207 // const actual_alignment = @as(u64, 1) << @intCast(lsb);
208 // const mask = @intFromPtr(alignment) - 1;
209 // const misalignment_offset = real_pointer & mask;
210 // _ = actual_alignment;
211 // _ = misalignment_offset;
212
213 if (maybe_offset) |offset| {
214 logMessage(
215 "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed\n",
216 .{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() },
217 );
218 } else {
219 logMessage(
220 "assumption of {} byte alignment for pointer of type {s} failed\n",
221 .{ alignment.getValue(data), data.type_descriptor.getName() },
222 );
223 }
224}
225
226const ShiftOobData = extern struct {
227 loc: SourceLocation,
228 lhs_type: *const TypeDescriptor,
229 rhs_type: *const TypeDescriptor,
230};
231
232fn shiftOob(
233 data: *const ShiftOobData,
234 lhs_handle: ValueHandle,
235 rhs_handle: ValueHandle,
236) callconv(.C) noreturn {
237 const lhs: Value = .{ .handle = lhs_handle, .type_descriptor = data.lhs_type };
238 const rhs: Value = .{ .handle = rhs_handle, .type_descriptor = data.rhs_type };
239
240 if (rhs.isNegative() or
241 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
242 {
243 if (rhs.isNegative()) {
244 logMessage("shift exponent {} is negative\n", .{rhs});
245 } else {
246 logMessage(
247 "shift exponent {} is too large for {}-bit type {s}\n",
248 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
249 );
250 }
251 } else {
252 if (lhs.isNegative()) {
253 logMessage("left shift of negative value {}\n", .{lhs});
254 } else {
255 logMessage(
256 "left shift of {} by {} places cannot be represented in type {s}\n",
257 .{ lhs, rhs, data.lhs_type.getName() },
258 );
259 }
260 }
261}
262
263const OutOfBoundsData = extern struct {
264 loc: SourceLocation,
265 array_type: *const TypeDescriptor,
266 index_type: *const TypeDescriptor,
267};
268
269fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.C) noreturn {
270 const index: Value = .{ .handle = index_handle, .type_descriptor = data.index_type };
271 logMessage(
272 "index {} out of bounds for type {s}\n",
273 .{ index, data.array_type.getName() },
274 );
275}
276
277const PointerOverflowData = extern struct {
278 loc: SourceLocation,
279};
280
281fn pointerOverflow(
282 _: *const PointerOverflowData,
283 base: usize,
284 result: usize,
285) callconv(.C) noreturn {
286 if (base == 0) {
287 if (result == 0) {
288 logMessage("applying zero offset to null pointer\n", .{});
289 } else {
290 logMessage("applying non-zero offset {} to null pointer\n", .{result});
291 }
292 } else {
293 if (result == 0) {
294 logMessage(
295 "applying non-zero offset to non-null pointer 0x{x} produced null pointer\n",
296 .{base},
297 );
298 } else {
299 @panic("TODO");
300 }
301 }
302}
303
304const TypeMismatchData = extern struct {
305 loc: SourceLocation,
306 type_descriptor: *const TypeDescriptor,
307 log_alignment: u8,
308 kind: enum(u8) {
309 load,
310 store,
311 reference_binding,
312 member_access,
313 member_call,
314 constructor_call,
315 downcast_pointer,
316 downcast_reference,
317 upcast,
318 upcast_to_virtual_base,
319 nonnull_assign,
320 dynamic_operation,
321 },
322};
323
324fn simpleHandler(
325 comptime sym_name: []const u8,
326 comptime error_name: []const u8,
327 comptime abort: bool,
328) void {
329 const S = struct {
330 fn handler() callconv(.C) noreturn {
331 logMessage("{s}", .{error_name});
332 }
333 };
334 exportHandler(&S.handler, sym_name, abort);
335}
336
337inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
338 std.debug.print(fmt, args);
339 std.debug.dumpCurrentStackTrace(@returnAddress());
340 std.posix.abort();
341}
342
343fn exportHandler(
344 handler: anytype,
345 comptime sym_name: []const u8,
346 comptime abort: bool,
347) void {
348 const linkage = if (builtin.is_test) .internal else .weak;
349 {
350 const N = "__ubsan_handle_" ++ sym_name;
351 @export(handler, .{ .name = N, .linkage = linkage });
352 }
353 if (abort) {
354 const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
355 @export(handler, .{ .name = N, .linkage = linkage });
356 }
357}
358
359comptime {
360 overflowHandler("add_overflow", "+");
361 overflowHandler("sub_overflow", "-");
362 overflowHandler("mul_overflow", "*");
363 exportHandler(&negationHandler, "negate_overflow", true);
364 exportHandler(&divRemHandler, "divrem_overflow", true);
365 exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true);
366 exportHandler(&shiftOob, "shift_out_of_bounds", true);
367 exportHandler(&outOfBounds, "out_of_bounds", true);
368 exportHandler(&pointerOverflow, "pointer_overflow", true);
369
370 simpleHandler("type_mismatch_v1", "type-mismatch-v1", true);
371 simpleHandler("builtin_unreachable", "builtin-unreachable", false);
372 simpleHandler("missing_return", "missing-return", false);
373 simpleHandler("vla_bound_not_positive", "vla-bound-not-positive", true);
374 simpleHandler("float_cast_overflow", "float-cast-overflow", true);
375 simpleHandler("load_invalid_value", "load-invalid-value", true);
376 simpleHandler("invalid_builtin", "invalid-builtin", true);
377 simpleHandler("function_type_mismatch", "function-type-mismatch", true);
378 simpleHandler("implicit_conversion", "implicit-conversion", true);
379 simpleHandler("nonnull_arg", "nonnull-arg", true);
380 simpleHandler("nonnull_return", "nonnull-return", true);
381 simpleHandler("nullability_arg", "nullability-arg", true);
382 simpleHandler("nullability_return", "nullability-return", true);
383 simpleHandler("cfi_check_fail", "cfi-check-fail", true);
384 simpleHandler("function_type_mismatch_v1", "function-type-mismatch-v1", true);
385
386 // these checks are nearly impossible to duplicate in zig, as they rely on nuances
387 // in the Itanium C++ ABI.
388 simpleHandler("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
389 simpleHandler("vptr_type_cache", "vptr-type-cache", true);
390}
src/Compilation.zig+1-1
...@@ -5916,7 +5916,7 @@ pub fn addCCArgs(...@@ -5916,7 +5916,7 @@ pub fn addCCArgs(
5916 // These args have to be added after the `-fsanitize` arg or5916 // These args have to be added after the `-fsanitize` arg or
5917 // they won't take effect.5917 // they won't take effect.
5918 if (mod.sanitize_c) {5918 if (mod.sanitize_c) {
5919 try argv.append("-fsanitize-trap=undefined");5919 try argv.append("-fno-sanitize=vptr");
5920 // It is very common, and well-defined, for a pointer on one side of a C ABI5920 // It is very common, and well-defined, for a pointer on one side of a C ABI
5921 // to have a different but compatible element type. Examples include:5921 // to have a different but compatible element type. Examples include:
5922 // `char*` vs `uint8_t*` on a system with 8-bit bytes5922 // `char*` vs `uint8_t*` on a system with 8-bit bytes