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