master
1const std = @import("std");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const panic = std.debug.panicExtra;
5
6const SourceLocation = extern struct {
7 file_name: ?[*:0]const u8,
8 line: u32,
9 col: u32,
10};
11
12const TypeDescriptor = extern struct {
13 kind: Kind,
14 info: Info,
15 // name: [?:0]u8
16
17 const Kind = enum(u16) {
18 integer = 0x0000,
19 float = 0x0001,
20 unknown = 0xFFFF,
21 };
22
23 const Info = extern union {
24 integer: packed struct(u16) {
25 signed: bool,
26 bit_width: u15,
27 },
28 float: u16,
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
48const Value = extern struct {
49 td: *const TypeDescriptor,
50 handle: ValueHandle,
51
52 fn getUnsignedInteger(value: Value) u128 {
53 assert(!value.td.isSigned());
54 const size = value.td.getIntegerSize();
55 const max_inline_size = @bitSizeOf(ValueHandle);
56 if (size <= max_inline_size) {
57 return @intFromPtr(value.handle);
58 }
59
60 return switch (size) {
61 64 => @as(*const u64, @ptrCast(@alignCast(value.handle))).*,
62 128 => @as(*const u128, @ptrCast(@alignCast(value.handle))).*,
63 else => @trap(),
64 };
65 }
66
67 fn getSignedInteger(value: Value) i128 {
68 assert(value.td.isSigned());
69 const size = value.td.getIntegerSize();
70 const max_inline_size = @bitSizeOf(ValueHandle);
71 if (size <= max_inline_size) {
72 const extra_bits: std.math.Log2Int(usize) = @intCast(max_inline_size - size);
73 const handle: isize = @bitCast(@intFromPtr(value.handle));
74 return (handle << extra_bits) >> extra_bits;
75 }
76 return switch (size) {
77 64 => @as(*const i64, @ptrCast(@alignCast(value.handle))).*,
78 128 => @as(*const i128, @ptrCast(@alignCast(value.handle))).*,
79 else => @trap(),
80 };
81 }
82
83 fn getFloat(value: Value) f128 {
84 assert(value.td.kind == .float);
85 const size = value.td.info.float;
86 const max_inline_size = @bitSizeOf(ValueHandle);
87 if (size <= max_inline_size) {
88 return @as(switch (@bitSizeOf(usize)) {
89 32 => f32,
90 64 => f64,
91 else => @compileError("unsupported target"),
92 }, @bitCast(@intFromPtr(value.handle)));
93 }
94 return @floatCast(switch (size) {
95 64 => @as(*const f64, @ptrCast(@alignCast(value.handle))).*,
96 80 => @as(*const f80, @ptrCast(@alignCast(value.handle))).*,
97 128 => @as(*const f128, @ptrCast(@alignCast(value.handle))).*,
98 else => @trap(),
99 });
100 }
101
102 fn isMinusOne(value: Value) bool {
103 return value.td.isSigned() and
104 value.getSignedInteger() == -1;
105 }
106
107 fn isNegative(value: Value) bool {
108 return value.td.isSigned() and
109 value.getSignedInteger() < 0;
110 }
111
112 fn getPositiveInteger(value: Value) u128 {
113 if (value.td.isSigned()) {
114 const signed = value.getSignedInteger();
115 assert(signed >= 0);
116 return @intCast(signed);
117 } else {
118 return value.getUnsignedInteger();
119 }
120 }
121
122 pub fn format(value: Value, writer: *std.Io.Writer) std.Io.Writer.Error!void {
123 switch (value.td.kind) {
124 .integer => {
125 if (value.td.isSigned()) {
126 try writer.print("{d}", .{value.getSignedInteger()});
127 } else {
128 try writer.print("{d}", .{value.getUnsignedInteger()});
129 }
130 },
131 .float => try writer.print("{d}", .{value.getFloat()}),
132 .unknown => try writer.writeAll("(unknown)"),
133 }
134 }
135};
136
137const OverflowData = extern struct {
138 loc: SourceLocation,
139 td: *const TypeDescriptor,
140};
141
142fn overflowHandler(
143 comptime sym_name: []const u8,
144 comptime operator: []const u8,
145) void {
146 const S = struct {
147 fn abort(
148 data: *const OverflowData,
149 lhs_handle: ValueHandle,
150 rhs_handle: ValueHandle,
151 ) callconv(.c) noreturn {
152 handler(data, lhs_handle, rhs_handle);
153 }
154
155 fn handler(
156 data: *const OverflowData,
157 lhs_handle: ValueHandle,
158 rhs_handle: ValueHandle,
159 ) callconv(.c) noreturn {
160 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
161 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
162 const signed_str = if (data.td.isSigned()) "signed" else "unsigned";
163 panic(
164 @returnAddress(),
165 "{s} integer overflow: {f} " ++ operator ++ " {f} cannot be represented in type {s}",
166 .{ signed_str, lhs, rhs, data.td.getName() },
167 );
168 }
169 };
170
171 exportHandlerWithAbort(&S.handler, &S.abort, sym_name);
172}
173
174fn negationHandlerAbort(
175 data: *const OverflowData,
176 value_handle: ValueHandle,
177) callconv(.c) noreturn {
178 negationHandler(data, value_handle);
179}
180
181fn negationHandler(
182 data: *const OverflowData,
183 value_handle: ValueHandle,
184) callconv(.c) noreturn {
185 const value: Value = .{ .handle = value_handle, .td = data.td };
186 panic(@returnAddress(), "negation of {f} cannot be represented in type {s}", .{
187 value, data.td.getName(),
188 });
189}
190
191fn divRemHandlerAbort(
192 data: *const OverflowData,
193 lhs_handle: ValueHandle,
194 rhs_handle: ValueHandle,
195) callconv(.c) noreturn {
196 divRemHandler(data, lhs_handle, rhs_handle);
197}
198
199fn divRemHandler(
200 data: *const OverflowData,
201 lhs_handle: ValueHandle,
202 rhs_handle: ValueHandle,
203) callconv(.c) noreturn {
204 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
205 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
206
207 if (rhs.isMinusOne()) {
208 panic(@returnAddress(), "division of {f} by -1 cannot be represented in type {s}", .{
209 lhs, data.td.getName(),
210 });
211 } else panic(@returnAddress(), "division by zero", .{});
212}
213
214const AlignmentAssumptionData = extern struct {
215 loc: SourceLocation,
216 assumption_loc: SourceLocation,
217 td: *const TypeDescriptor,
218};
219
220fn alignmentAssumptionHandlerAbort(
221 data: *const AlignmentAssumptionData,
222 pointer: ValueHandle,
223 alignment_handle: ValueHandle,
224 maybe_offset: ?ValueHandle,
225) callconv(.c) noreturn {
226 alignmentAssumptionHandler(
227 data,
228 pointer,
229 alignment_handle,
230 maybe_offset,
231 );
232}
233
234fn alignmentAssumptionHandler(
235 data: *const AlignmentAssumptionData,
236 pointer: ValueHandle,
237 alignment_handle: ValueHandle,
238 maybe_offset: ?ValueHandle,
239) callconv(.c) noreturn {
240 const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
241 const lsb = @ctz(real_pointer);
242 const actual_alignment = @as(u64, 1) << @intCast(lsb);
243 const mask = @intFromPtr(alignment_handle) - 1;
244 const misalignment_offset = real_pointer & mask;
245 const alignment: Value = .{ .handle = alignment_handle, .td = data.td };
246
247 if (maybe_offset) |offset| {
248 panic(
249 @returnAddress(),
250 "assumption of {f} byte alignment (with offset of {d} byte) for pointer of type {s} failed\n" ++
251 "offset address is {d} aligned, misalignment offset is {d} bytes",
252 .{
253 alignment,
254 @intFromPtr(offset),
255 data.td.getName(),
256 actual_alignment,
257 misalignment_offset,
258 },
259 );
260 } else {
261 panic(
262 @returnAddress(),
263 "assumption of {f} byte alignment for pointer of type {s} failed\n" ++
264 "address is {d} aligned, misalignment offset is {d} bytes",
265 .{
266 alignment,
267 data.td.getName(),
268 actual_alignment,
269 misalignment_offset,
270 },
271 );
272 }
273}
274
275const ShiftOobData = extern struct {
276 loc: SourceLocation,
277 lhs_type: *const TypeDescriptor,
278 rhs_type: *const TypeDescriptor,
279};
280
281fn shiftOobAbort(
282 data: *const ShiftOobData,
283 lhs_handle: ValueHandle,
284 rhs_handle: ValueHandle,
285) callconv(.c) noreturn {
286 shiftOob(data, lhs_handle, rhs_handle);
287}
288
289fn shiftOob(
290 data: *const ShiftOobData,
291 lhs_handle: ValueHandle,
292 rhs_handle: ValueHandle,
293) callconv(.c) noreturn {
294 const lhs: Value = .{ .handle = lhs_handle, .td = data.lhs_type };
295 const rhs: Value = .{ .handle = rhs_handle, .td = data.rhs_type };
296
297 if (rhs.isNegative() or
298 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
299 {
300 if (rhs.isNegative()) {
301 panic(@returnAddress(), "shift exponent {f} is negative", .{rhs});
302 } else {
303 panic(
304 @returnAddress(),
305 "shift exponent {f} is too large for {d}-bit type {s}",
306 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
307 );
308 }
309 } else {
310 if (lhs.isNegative()) {
311 panic(@returnAddress(), "left shift of negative value {f}", .{lhs});
312 } else {
313 panic(
314 @returnAddress(),
315 "left shift of {f} by {f} places cannot be represented in type {s}",
316 .{ lhs, rhs, data.lhs_type.getName() },
317 );
318 }
319 }
320}
321
322const OutOfBoundsData = extern struct {
323 loc: SourceLocation,
324 array_type: *const TypeDescriptor,
325 index_type: *const TypeDescriptor,
326};
327
328fn outOfBoundsAbort(
329 data: *const OutOfBoundsData,
330 index_handle: ValueHandle,
331) callconv(.c) noreturn {
332 outOfBounds(data, index_handle);
333}
334
335fn outOfBounds(
336 data: *const OutOfBoundsData,
337 index_handle: ValueHandle,
338) callconv(.c) noreturn {
339 const index: Value = .{ .handle = index_handle, .td = data.index_type };
340 panic(@returnAddress(), "index {f} out of bounds for type {s}", .{
341 index,
342 data.array_type.getName(),
343 });
344}
345
346const PointerOverflowData = extern struct {
347 loc: SourceLocation,
348};
349
350fn pointerOverflowAbort(
351 data: *const PointerOverflowData,
352 base: usize,
353 result: usize,
354) callconv(.c) noreturn {
355 pointerOverflow(data, base, result);
356}
357
358fn pointerOverflow(
359 _: *const PointerOverflowData,
360 base: usize,
361 result: usize,
362) callconv(.c) noreturn {
363 if (base == 0) {
364 if (result == 0) {
365 panic(@returnAddress(), "applying zero offset to null pointer", .{});
366 } else {
367 panic(@returnAddress(), "applying non-zero offset {d} to null pointer", .{result});
368 }
369 } else {
370 if (result == 0) {
371 panic(
372 @returnAddress(),
373 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
374 .{base},
375 );
376 } else {
377 const signed_base: isize = @bitCast(base);
378 const signed_result: isize = @bitCast(result);
379 if ((signed_base >= 0) == (signed_result >= 0)) {
380 if (base > result) {
381 panic(
382 @returnAddress(),
383 "addition of unsigned offset to 0x{x} overflowed to 0x{x}",
384 .{ base, result },
385 );
386 } else {
387 panic(
388 @returnAddress(),
389 "subtraction of unsigned offset to 0x{x} overflowed to 0x{x}",
390 .{ base, result },
391 );
392 }
393 } else {
394 panic(
395 @returnAddress(),
396 "pointer index expression with base 0x{x} overflowed to 0x{x}",
397 .{ base, result },
398 );
399 }
400 }
401 }
402}
403
404const TypeMismatchData = extern struct {
405 loc: SourceLocation,
406 td: *const TypeDescriptor,
407 log_alignment: u8,
408 kind: enum(u8) {
409 load,
410 store,
411 reference_binding,
412 member_access,
413 member_call,
414 constructor_call,
415 downcast_pointer,
416 downcast_reference,
417 upcast,
418 upcast_to_virtual_base,
419 nonnull_assign,
420 dynamic_operation,
421
422 fn getName(kind: @This()) []const u8 {
423 return switch (kind) {
424 .load => "load of",
425 .store => "store of",
426 .reference_binding => "reference binding to",
427 .member_access => "member access within",
428 .member_call => "member call on",
429 .constructor_call => "constructor call on",
430 .downcast_pointer, .downcast_reference => "downcast of",
431 .upcast => "upcast of",
432 .upcast_to_virtual_base => "cast to virtual base of",
433 .nonnull_assign => "_Nonnull binding to",
434 .dynamic_operation => "dynamic operation on",
435 };
436 }
437 },
438};
439
440fn typeMismatchAbort(
441 data: *const TypeMismatchData,
442 pointer: ?ValueHandle,
443) callconv(.c) noreturn {
444 typeMismatch(data, pointer);
445}
446
447fn typeMismatch(
448 data: *const TypeMismatchData,
449 pointer: ?ValueHandle,
450) callconv(.c) noreturn {
451 const alignment = @as(usize, 1) << @intCast(data.log_alignment);
452 const handle: usize = @intFromPtr(pointer);
453
454 if (pointer == null) {
455 panic(
456 @returnAddress(),
457 "{s} null pointer of type {s}",
458 .{ data.kind.getName(), data.td.getName() },
459 );
460 } else if (!std.mem.isAligned(handle, alignment)) {
461 panic(
462 @returnAddress(),
463 "{s} misaligned address 0x{x} for type {s}, which requires {d} byte alignment",
464 .{ data.kind.getName(), handle, data.td.getName(), alignment },
465 );
466 } else {
467 panic(
468 @returnAddress(),
469 "{s} address 0x{x} with insufficient space for an object of type {s}",
470 .{ data.kind.getName(), handle, data.td.getName() },
471 );
472 }
473}
474
475const UnreachableData = extern struct {
476 loc: SourceLocation,
477};
478
479fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn {
480 panic(@returnAddress(), "execution reached an unreachable program point", .{});
481}
482
483fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn {
484 panic(@returnAddress(), "execution reached the end of a value-returning function without returning a value", .{});
485}
486
487const NonNullReturnData = extern struct {
488 attribute_loc: SourceLocation,
489};
490
491fn nonNullReturnAbort(data: *const NonNullReturnData) callconv(.c) noreturn {
492 nonNullReturn(data);
493}
494fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn {
495 panic(@returnAddress(), "null pointer returned from function declared to never return null", .{});
496}
497
498const NonNullArgData = extern struct {
499 loc: SourceLocation,
500 attribute_loc: SourceLocation,
501 arg_index: i32,
502};
503
504fn nonNullArgAbort(data: *const NonNullArgData) callconv(.c) noreturn {
505 nonNullArg(data);
506}
507
508fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn {
509 panic(
510 @returnAddress(),
511 "null pointer passed as argument {d}, which is declared to never be null",
512 .{data.arg_index},
513 );
514}
515
516const InvalidValueData = extern struct {
517 loc: SourceLocation,
518 td: *const TypeDescriptor,
519};
520
521fn loadInvalidValueAbort(
522 data: *const InvalidValueData,
523 value_handle: ValueHandle,
524) callconv(.c) noreturn {
525 loadInvalidValue(data, value_handle);
526}
527
528fn loadInvalidValue(
529 data: *const InvalidValueData,
530 value_handle: ValueHandle,
531) callconv(.c) noreturn {
532 const value: Value = .{ .handle = value_handle, .td = data.td };
533 panic(@returnAddress(), "load of value {f}, which is not valid for type {s}", .{
534 value, data.td.getName(),
535 });
536}
537
538const InvalidBuiltinData = extern struct {
539 loc: SourceLocation,
540 kind: enum(u8) {
541 ctz,
542 clz,
543 },
544};
545fn invalidBuiltinAbort(data: *const InvalidBuiltinData) callconv(.c) noreturn {
546 invalidBuiltin(data);
547}
548
549fn invalidBuiltin(data: *const InvalidBuiltinData) callconv(.c) noreturn {
550 panic(
551 @returnAddress(),
552 "passing zero to {s}(), which is not a valid argument",
553 .{@tagName(data.kind)},
554 );
555}
556
557const VlaBoundNotPositive = extern struct {
558 loc: SourceLocation,
559 td: *const TypeDescriptor,
560};
561
562fn vlaBoundNotPositiveAbort(
563 data: *const VlaBoundNotPositive,
564 bound_handle: ValueHandle,
565) callconv(.c) noreturn {
566 vlaBoundNotPositive(data, bound_handle);
567}
568
569fn vlaBoundNotPositive(
570 data: *const VlaBoundNotPositive,
571 bound_handle: ValueHandle,
572) callconv(.c) noreturn {
573 const bound: Value = .{ .handle = bound_handle, .td = data.td };
574 panic(@returnAddress(), "variable length array bound evaluates to non-positive value {f}", .{bound});
575}
576
577const FloatCastOverflowData = extern struct {
578 from: *const TypeDescriptor,
579 to: *const TypeDescriptor,
580};
581
582const FloatCastOverflowDataV2 = extern struct {
583 loc: SourceLocation,
584 from: *const TypeDescriptor,
585 to: *const TypeDescriptor,
586};
587
588fn floatCastOverflowAbort(
589 data_handle: *align(8) const anyopaque,
590 from_handle: ValueHandle,
591) callconv(.c) noreturn {
592 floatCastOverflow(data_handle, from_handle);
593}
594
595fn floatCastOverflow(
596 data_handle: *align(8) const anyopaque,
597 from_handle: ValueHandle,
598) callconv(.c) noreturn {
599 // See: https://github.com/llvm/llvm-project/blob/release/19.x/compiler-rt/lib/ubsan/ubsan_handlers.cpp#L463
600 // for more information on this check.
601 const ptr: [*]const u8 = @ptrCast(data_handle);
602 if (@as(u16, ptr[0]) + @as(u16, ptr[1]) < 2 or ptr[0] == 0xFF or ptr[1] == 0xFF) {
603 const data: *const FloatCastOverflowData = @ptrCast(data_handle);
604 const from_value: Value = .{ .handle = from_handle, .td = data.from };
605 panic(@returnAddress(), "{f} is outside the range of representable values of type {s}", .{
606 from_value, data.to.getName(),
607 });
608 } else {
609 const data: *const FloatCastOverflowDataV2 = @ptrCast(data_handle);
610 const from_value: Value = .{ .handle = from_handle, .td = data.from };
611 panic(@returnAddress(), "{f} is outside the range of representable values of type {s}", .{
612 from_value, data.to.getName(),
613 });
614 }
615}
616
617fn exportHandler(
618 handler: anytype,
619 comptime sym_name: []const u8,
620) void {
621 @export(handler, .{
622 .name = "__ubsan_handle_" ++ sym_name,
623 .linkage = .weak,
624 .visibility = .hidden,
625 });
626}
627
628fn exportHandlerWithAbort(
629 handler: anytype,
630 abort_handler: anytype,
631 comptime sym_name: []const u8,
632) void {
633 @export(handler, .{
634 .name = "__ubsan_handle_" ++ sym_name,
635 .linkage = .weak,
636 .visibility = .hidden,
637 });
638 @export(abort_handler, .{
639 .name = "__ubsan_handle_" ++ sym_name ++ "_abort",
640 .linkage = .weak,
641 .visibility = .hidden,
642 });
643}
644
645const can_build_ubsan = switch (builtin.zig_backend) {
646 .stage2_powerpc,
647 .stage2_riscv64,
648 => false,
649 else => true,
650};
651
652comptime {
653 if (can_build_ubsan) {
654 overflowHandler("add_overflow", "+");
655 overflowHandler("mul_overflow", "*");
656 overflowHandler("sub_overflow", "-");
657 exportHandlerWithAbort(&alignmentAssumptionHandler, &alignmentAssumptionHandlerAbort, "alignment_assumption");
658
659 exportHandlerWithAbort(&divRemHandler, &divRemHandlerAbort, "divrem_overflow");
660 exportHandlerWithAbort(&floatCastOverflow, &floatCastOverflowAbort, "float_cast_overflow");
661 exportHandlerWithAbort(&invalidBuiltin, &invalidBuiltinAbort, "invalid_builtin");
662 exportHandlerWithAbort(&loadInvalidValue, &loadInvalidValueAbort, "load_invalid_value");
663
664 exportHandlerWithAbort(&negationHandler, &negationHandlerAbort, "negate_overflow");
665 exportHandlerWithAbort(&nonNullArg, &nonNullArgAbort, "nonnull_arg");
666 exportHandlerWithAbort(&nonNullReturn, &nonNullReturnAbort, "nonnull_return_v1");
667 exportHandlerWithAbort(&outOfBounds, &outOfBoundsAbort, "out_of_bounds");
668 exportHandlerWithAbort(&pointerOverflow, &pointerOverflowAbort, "pointer_overflow");
669 exportHandlerWithAbort(&shiftOob, &shiftOobAbort, "shift_out_of_bounds");
670 exportHandlerWithAbort(&typeMismatch, &typeMismatchAbort, "type_mismatch_v1");
671 exportHandlerWithAbort(&vlaBoundNotPositive, &vlaBoundNotPositiveAbort, "vla_bound_not_positive");
672
673 exportHandler(&builtinUnreachable, "builtin_unreachable");
674 exportHandler(&missingReturn, "missing_return");
675 }
676
677 // these checks are nearly impossible to replicate in zig, as they rely on nuances
678 // in the Itanium C++ ABI.
679 // exportHandlerWithAbort(&dynamicTypeCacheMiss, &dynamicTypeCacheMissAbort, "dynamic-type-cache-miss");
680 // exportHandlerWithAbort(&vptrTypeCache, &vptrTypeCacheAbort, "vptr-type-cache");
681
682 // we disable -fsanitize=function for reasons explained in src/Compilation.zig
683 // exportHandlerWithAbort(&functionTypeMismatch, &functionTypeMismatchAbort, "function-type-mismatch");
684 // exportHandlerWithAbort(&functionTypeMismatchV1, &functionTypeMismatchV1Abort, "function-type-mismatch-v1");
685}