authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-26 03:08:36-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-02-26 03:08:36-05:00
logc45dcd013bfe9de1c739a88203349603c0682fd9
treeb1b5c1949b227e67238ef88b7a083d480388224c
parente0a955afb3c8768fb52b56c342f171bc1d0d6066
parentca83f52fd95fa6ebf28b7d51d4ef396a2ccf4be4
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #22488 from Rexicon226/ubsan-rt

implement a ubsan runtime for better error messages

23 files changed, 958 insertions(+), 28 deletions(-)

lib/std/Build/Step/Compile.zig+2
......@@ -40,6 +40,7 @@ compress_debug_sections: enum { none, zlib, zstd } = .none,
4040verbose_link: bool,
4141verbose_cc: bool,
4242bundle_compiler_rt: ?bool = null,
43bundle_ubsan_rt: ?bool = null,
4344rdynamic: bool,
4445import_memory: bool = false,
4546export_memory: bool = false,
......@@ -1563,6 +1564,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
15631564 }
15641565
15651566 try addFlag(&zig_args, "compiler-rt", compile.bundle_compiler_rt);
1567 try addFlag(&zig_args, "ubsan-rt", compile.bundle_ubsan_rt);
15661568 try addFlag(&zig_args, "dll-export-fns", compile.dll_export_fns);
15671569 if (compile.rdynamic) {
15681570 try zig_args.append("-rdynamic");
lib/std/heap.zig+5-5
......@@ -42,10 +42,8 @@ pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null;
4242///
4343/// On many systems, the actual page size can only be determined at runtime
4444/// with `pageSize`.
45pub const page_size_min: usize = std.options.page_size_min orelse (page_size_min_default orelse if (builtin.os.tag == .freestanding or builtin.os.tag == .other)
46 @compileError("freestanding/other page_size_min must provided with std.options.page_size_min")
47else
48 @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min"));
45pub const page_size_min: usize = std.options.page_size_min orelse page_size_min_default orelse
46 @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min");
4947
5048/// comptime-known maximum page size of the target.
5149///
......@@ -831,8 +829,10 @@ const page_size_min_default: ?usize = switch (builtin.os.tag) {
831829 .xtensa => 4 << 10,
832830 else => null,
833831 },
834 .freestanding => switch (builtin.cpu.arch) {
832 .freestanding, .other => switch (builtin.cpu.arch) {
835833 .wasm32, .wasm64 => 64 << 10,
834 .x86, .x86_64 => 4 << 10,
835 .aarch64, .aarch64_be => 4 << 10,
836836 else => null,
837837 },
838838 else => null,
lib/std/mem.zig+4-3
......@@ -1098,12 +1098,12 @@ pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]co
10981098 // as we don't read into a new page. This should be the case for most architectures
10991099 // which use paged memory, however should be confirmed before adding a new arch below.
11001100 .aarch64, .x86, .x86_64 => if (std.simd.suggestVectorLength(T)) |block_len| {
1101 const page_size = std.heap.pageSize();
1101 const page_size = std.heap.page_size_min;
11021102 const block_size = @sizeOf(T) * block_len;
11031103 const Block = @Vector(block_len, T);
11041104 const mask: Block = @splat(sentinel);
11051105
1106 comptime assert(std.heap.page_size_max % @sizeOf(Block) == 0);
1106 comptime assert(std.heap.page_size_min % @sizeOf(Block) == 0);
11071107 assert(page_size % @sizeOf(Block) == 0);
11081108
11091109 // First block may be unaligned
......@@ -1119,6 +1119,7 @@ pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]co
11191119
11201120 i += @divExact(std.mem.alignForward(usize, start_addr, block_size) - start_addr, @sizeOf(T));
11211121 } else {
1122 @branchHint(.unlikely);
11221123 // Would read over a page boundary. Per-byte at a time until aligned or found.
11231124 // 0.39% chance this branch is taken for 4K pages at 16b block length.
11241125 //
......@@ -1152,7 +1153,7 @@ pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]co
11521153test "indexOfSentinel vector paths" {
11531154 const Types = [_]type{ u8, u16, u32, u64 };
11541155 const allocator = std.testing.allocator;
1155 const page_size = std.heap.pageSize();
1156 const page_size = std.heap.page_size_min;
11561157
11571158 inline for (Types) |T| {
11581159 const block_len = std.simd.suggestVectorLength(T) orelse continue;
lib/ubsan_rt.zig created+711
......@@ -0,0 +1,711 @@
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, @alignCast(@ptrCast(value.handle))).*,
62 128 => @as(*const u128, @alignCast(@ptrCast(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, @alignCast(@ptrCast(value.handle))).*,
78 128 => @as(*const i128, @alignCast(@ptrCast(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, @alignCast(@ptrCast(value.handle))).*,
96 80 => @as(*const f80, @alignCast(@ptrCast(value.handle))).*,
97 128 => @as(*const f128, @alignCast(@ptrCast(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(
123 value: Value,
124 comptime fmt: []const u8,
125 _: std.fmt.FormatOptions,
126 writer: anytype,
127 ) !void {
128 comptime assert(fmt.len == 0);
129
130 // Work around x86_64 backend limitation.
131 if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag == .windows) {
132 try writer.writeAll("(unknown)");
133 return;
134 }
135
136 switch (value.td.kind) {
137 .integer => {
138 if (value.td.isSigned()) {
139 try writer.print("{}", .{value.getSignedInteger()});
140 } else {
141 try writer.print("{}", .{value.getUnsignedInteger()});
142 }
143 },
144 .float => try writer.print("{}", .{value.getFloat()}),
145 .unknown => try writer.writeAll("(unknown)"),
146 }
147 }
148};
149
150const OverflowData = extern struct {
151 loc: SourceLocation,
152 td: *const TypeDescriptor,
153};
154
155fn overflowHandler(
156 comptime sym_name: []const u8,
157 comptime operator: []const u8,
158) void {
159 const S = struct {
160 fn abort(
161 data: *const OverflowData,
162 lhs_handle: ValueHandle,
163 rhs_handle: ValueHandle,
164 ) callconv(.c) noreturn {
165 handler(data, lhs_handle, rhs_handle);
166 }
167
168 fn handler(
169 data: *const OverflowData,
170 lhs_handle: ValueHandle,
171 rhs_handle: ValueHandle,
172 ) callconv(.c) noreturn {
173 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
174 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
175
176 const is_signed = data.td.isSigned();
177 const fmt = "{s} integer overflow: " ++ "{} " ++
178 operator ++ " {} cannot be represented in type {s}";
179
180 panic(@returnAddress(), fmt, .{
181 if (is_signed) "signed" else "unsigned",
182 lhs,
183 rhs,
184 data.td.getName(),
185 });
186 }
187 };
188
189 exportHandlerWithAbort(&S.handler, &S.abort, sym_name);
190}
191
192fn negationHandlerAbort(
193 data: *const OverflowData,
194 value_handle: ValueHandle,
195) callconv(.c) noreturn {
196 negationHandler(data, value_handle);
197}
198
199fn negationHandler(
200 data: *const OverflowData,
201 value_handle: ValueHandle,
202) callconv(.c) noreturn {
203 const value: Value = .{ .handle = value_handle, .td = data.td };
204 panic(
205 @returnAddress(),
206 "negation of {} cannot be represented in type {s}",
207 .{ value, data.td.getName() },
208 );
209}
210
211fn divRemHandlerAbort(
212 data: *const OverflowData,
213 lhs_handle: ValueHandle,
214 rhs_handle: ValueHandle,
215) callconv(.c) noreturn {
216 divRemHandler(data, lhs_handle, rhs_handle);
217}
218
219fn divRemHandler(
220 data: *const OverflowData,
221 lhs_handle: ValueHandle,
222 rhs_handle: ValueHandle,
223) callconv(.c) noreturn {
224 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
225 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
226
227 if (rhs.isMinusOne()) {
228 panic(
229 @returnAddress(),
230 "division of {} by -1 cannot be represented in type {s}",
231 .{ lhs, data.td.getName() },
232 );
233 } else panic(@returnAddress(), "division by zero", .{});
234}
235
236const AlignmentAssumptionData = extern struct {
237 loc: SourceLocation,
238 assumption_loc: SourceLocation,
239 td: *const TypeDescriptor,
240};
241
242fn alignmentAssumptionHandlerAbort(
243 data: *const AlignmentAssumptionData,
244 pointer: ValueHandle,
245 alignment_handle: ValueHandle,
246 maybe_offset: ?ValueHandle,
247) callconv(.c) noreturn {
248 alignmentAssumptionHandler(
249 data,
250 pointer,
251 alignment_handle,
252 maybe_offset,
253 );
254}
255
256fn alignmentAssumptionHandler(
257 data: *const AlignmentAssumptionData,
258 pointer: ValueHandle,
259 alignment_handle: ValueHandle,
260 maybe_offset: ?ValueHandle,
261) callconv(.c) noreturn {
262 const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
263 const lsb = @ctz(real_pointer);
264 const actual_alignment = @as(u64, 1) << @intCast(lsb);
265 const mask = @intFromPtr(alignment_handle) - 1;
266 const misalignment_offset = real_pointer & mask;
267 const alignment: Value = .{ .handle = alignment_handle, .td = data.td };
268
269 if (maybe_offset) |offset| {
270 panic(
271 @returnAddress(),
272 "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed\n" ++
273 "offset address is {} aligned, misalignment offset is {} bytes",
274 .{
275 alignment,
276 @intFromPtr(offset),
277 data.td.getName(),
278 actual_alignment,
279 misalignment_offset,
280 },
281 );
282 } else {
283 panic(
284 @returnAddress(),
285 "assumption of {} byte alignment for pointer of type {s} failed\n" ++
286 "address is {} aligned, misalignment offset is {} bytes",
287 .{
288 alignment,
289 data.td.getName(),
290 actual_alignment,
291 misalignment_offset,
292 },
293 );
294 }
295}
296
297const ShiftOobData = extern struct {
298 loc: SourceLocation,
299 lhs_type: *const TypeDescriptor,
300 rhs_type: *const TypeDescriptor,
301};
302
303fn shiftOobAbort(
304 data: *const ShiftOobData,
305 lhs_handle: ValueHandle,
306 rhs_handle: ValueHandle,
307) callconv(.c) noreturn {
308 shiftOob(data, lhs_handle, rhs_handle);
309}
310
311fn shiftOob(
312 data: *const ShiftOobData,
313 lhs_handle: ValueHandle,
314 rhs_handle: ValueHandle,
315) callconv(.c) noreturn {
316 const lhs: Value = .{ .handle = lhs_handle, .td = data.lhs_type };
317 const rhs: Value = .{ .handle = rhs_handle, .td = data.rhs_type };
318
319 if (rhs.isNegative() or
320 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
321 {
322 if (rhs.isNegative()) {
323 panic(@returnAddress(), "shift exponent {} is negative", .{rhs});
324 } else {
325 panic(
326 @returnAddress(),
327 "shift exponent {} is too large for {}-bit type {s}",
328 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
329 );
330 }
331 } else {
332 if (lhs.isNegative()) {
333 panic(@returnAddress(), "left shift of negative value {}", .{lhs});
334 } else {
335 panic(
336 @returnAddress(),
337 "left shift of {} by {} places cannot be represented in type {s}",
338 .{ lhs, rhs, data.lhs_type.getName() },
339 );
340 }
341 }
342}
343
344const OutOfBoundsData = extern struct {
345 loc: SourceLocation,
346 array_type: *const TypeDescriptor,
347 index_type: *const TypeDescriptor,
348};
349
350fn outOfBoundsAbort(
351 data: *const OutOfBoundsData,
352 index_handle: ValueHandle,
353) callconv(.c) noreturn {
354 outOfBounds(data, index_handle);
355}
356
357fn outOfBounds(
358 data: *const OutOfBoundsData,
359 index_handle: ValueHandle,
360) callconv(.c) noreturn {
361 const index: Value = .{ .handle = index_handle, .td = data.index_type };
362 panic(
363 @returnAddress(),
364 "index {} out of bounds for type {s}",
365 .{ index, data.array_type.getName() },
366 );
367}
368
369const PointerOverflowData = extern struct {
370 loc: SourceLocation,
371};
372
373fn pointerOverflowAbort(
374 data: *const PointerOverflowData,
375 base: usize,
376 result: usize,
377) callconv(.c) noreturn {
378 pointerOverflow(data, base, result);
379}
380
381fn pointerOverflow(
382 _: *const PointerOverflowData,
383 base: usize,
384 result: usize,
385) callconv(.c) noreturn {
386 if (base == 0) {
387 if (result == 0) {
388 panic(@returnAddress(), "applying zero offset to null pointer", .{});
389 } else {
390 panic(@returnAddress(), "applying non-zero offset {} to null pointer", .{result});
391 }
392 } else {
393 if (result == 0) {
394 panic(
395 @returnAddress(),
396 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
397 .{base},
398 );
399 } else {
400 const signed_base: isize = @bitCast(base);
401 const signed_result: isize = @bitCast(result);
402 if ((signed_base >= 0) == (signed_result >= 0)) {
403 if (base > result) {
404 panic(
405 @returnAddress(),
406 "addition of unsigned offset to 0x{x} overflowed to 0x{x}",
407 .{ base, result },
408 );
409 } else {
410 panic(
411 @returnAddress(),
412 "subtraction of unsigned offset to 0x{x} overflowed to 0x{x}",
413 .{ base, result },
414 );
415 }
416 } else {
417 panic(
418 @returnAddress(),
419 "pointer index expression with base 0x{x} overflowed to 0x{x}",
420 .{ base, result },
421 );
422 }
423 }
424 }
425}
426
427const TypeMismatchData = extern struct {
428 loc: SourceLocation,
429 td: *const TypeDescriptor,
430 log_alignment: u8,
431 kind: enum(u8) {
432 load,
433 store,
434 reference_binding,
435 member_access,
436 member_call,
437 constructor_call,
438 downcast_pointer,
439 downcast_reference,
440 upcast,
441 upcast_to_virtual_base,
442 nonnull_assign,
443 dynamic_operation,
444
445 fn getName(kind: @This()) []const u8 {
446 return switch (kind) {
447 .load => "load of",
448 .store => "store of",
449 .reference_binding => "reference binding to",
450 .member_access => "member access within",
451 .member_call => "member call on",
452 .constructor_call => "constructor call on",
453 .downcast_pointer, .downcast_reference => "downcast of",
454 .upcast => "upcast of",
455 .upcast_to_virtual_base => "cast to virtual base of",
456 .nonnull_assign => "_Nonnull binding to",
457 .dynamic_operation => "dynamic operation on",
458 };
459 }
460 },
461};
462
463fn typeMismatchAbort(
464 data: *const TypeMismatchData,
465 pointer: ?ValueHandle,
466) callconv(.c) noreturn {
467 typeMismatch(data, pointer);
468}
469
470fn typeMismatch(
471 data: *const TypeMismatchData,
472 pointer: ?ValueHandle,
473) callconv(.c) noreturn {
474 const alignment = @as(usize, 1) << @intCast(data.log_alignment);
475 const handle: usize = @intFromPtr(pointer);
476
477 if (pointer == null) {
478 panic(
479 @returnAddress(),
480 "{s} null pointer of type {s}",
481 .{ data.kind.getName(), data.td.getName() },
482 );
483 } else if (!std.mem.isAligned(handle, alignment)) {
484 panic(
485 @returnAddress(),
486 "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment",
487 .{ data.kind.getName(), handle, data.td.getName(), alignment },
488 );
489 } else {
490 panic(
491 @returnAddress(),
492 "{s} address 0x{x} with insufficient space for an object of type {s}",
493 .{ data.kind.getName(), handle, data.td.getName() },
494 );
495 }
496}
497
498const UnreachableData = extern struct {
499 loc: SourceLocation,
500};
501
502fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn {
503 panic(@returnAddress(), "execution reached an unreachable program point", .{});
504}
505
506fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn {
507 panic(@returnAddress(), "execution reached the end of a value-returning function without returning a value", .{});
508}
509
510const NonNullReturnData = extern struct {
511 attribute_loc: SourceLocation,
512};
513
514fn nonNullReturnAbort(data: *const NonNullReturnData) callconv(.c) noreturn {
515 nonNullReturn(data);
516}
517fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn {
518 panic(@returnAddress(), "null pointer returned from function declared to never return null", .{});
519}
520
521const NonNullArgData = extern struct {
522 loc: SourceLocation,
523 attribute_loc: SourceLocation,
524 arg_index: i32,
525};
526
527fn nonNullArgAbort(data: *const NonNullArgData) callconv(.c) noreturn {
528 nonNullArg(data);
529}
530
531fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn {
532 panic(
533 @returnAddress(),
534 "null pointer passed as argument {}, which is declared to never be null",
535 .{data.arg_index},
536 );
537}
538
539const InvalidValueData = extern struct {
540 loc: SourceLocation,
541 td: *const TypeDescriptor,
542};
543
544fn loadInvalidValueAbort(
545 data: *const InvalidValueData,
546 value_handle: ValueHandle,
547) callconv(.c) noreturn {
548 loadInvalidValue(data, value_handle);
549}
550
551fn loadInvalidValue(
552 data: *const InvalidValueData,
553 value_handle: ValueHandle,
554) callconv(.c) noreturn {
555 const value: Value = .{ .handle = value_handle, .td = data.td };
556 panic(
557 @returnAddress(),
558 "load of value {}, which is not valid for type {s}",
559 .{ value, data.td.getName() },
560 );
561}
562
563const InvalidBuiltinData = extern struct {
564 loc: SourceLocation,
565 kind: enum(u8) {
566 ctz,
567 clz,
568 },
569};
570fn invalidBuiltinAbort(data: *const InvalidBuiltinData) callconv(.c) noreturn {
571 invalidBuiltin(data);
572}
573
574fn invalidBuiltin(data: *const InvalidBuiltinData) callconv(.c) noreturn {
575 panic(
576 @returnAddress(),
577 "passing zero to {s}(), which is not a valid argument",
578 .{@tagName(data.kind)},
579 );
580}
581
582const VlaBoundNotPositive = extern struct {
583 loc: SourceLocation,
584 td: *const TypeDescriptor,
585};
586
587fn vlaBoundNotPositiveAbort(
588 data: *const VlaBoundNotPositive,
589 bound_handle: ValueHandle,
590) callconv(.c) noreturn {
591 vlaBoundNotPositive(data, bound_handle);
592}
593
594fn vlaBoundNotPositive(
595 data: *const VlaBoundNotPositive,
596 bound_handle: ValueHandle,
597) callconv(.c) noreturn {
598 const bound: Value = .{ .handle = bound_handle, .td = data.td };
599 panic(
600 @returnAddress(),
601 "variable length array bound evaluates to non-positive value {}",
602 .{bound},
603 );
604}
605
606const FloatCastOverflowData = extern struct {
607 from: *const TypeDescriptor,
608 to: *const TypeDescriptor,
609};
610
611const FloatCastOverflowDataV2 = extern struct {
612 loc: SourceLocation,
613 from: *const TypeDescriptor,
614 to: *const TypeDescriptor,
615};
616
617fn floatCastOverflowAbort(
618 data_handle: *align(8) const anyopaque,
619 from_handle: ValueHandle,
620) callconv(.c) noreturn {
621 floatCastOverflow(data_handle, from_handle);
622}
623
624fn floatCastOverflow(
625 data_handle: *align(8) const anyopaque,
626 from_handle: ValueHandle,
627) callconv(.c) noreturn {
628 // See: https://github.com/llvm/llvm-project/blob/release/19.x/compiler-rt/lib/ubsan/ubsan_handlers.cpp#L463
629 // for more information on this check.
630 const ptr: [*]const u8 = @ptrCast(data_handle);
631 if (@as(u16, ptr[0]) + @as(u16, ptr[1]) < 2 or ptr[0] == 0xFF or ptr[1] == 0xFF) {
632 const data: *const FloatCastOverflowData = @ptrCast(data_handle);
633 const from_value: Value = .{ .handle = from_handle, .td = data.from };
634 panic(@returnAddress(), "{} is outside the range of representable values of type {s}", .{
635 from_value, data.to.getName(),
636 });
637 } else {
638 const data: *const FloatCastOverflowDataV2 = @ptrCast(data_handle);
639 const from_value: Value = .{ .handle = from_handle, .td = data.from };
640 panic(@returnAddress(), "{} is outside the range of representable values of type {s}", .{
641 from_value, data.to.getName(),
642 });
643 }
644}
645
646fn exportHandler(
647 handler: anytype,
648 comptime sym_name: []const u8,
649) void {
650 // Work around x86_64 backend limitation.
651 const linkage = if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag == .windows) .internal else .weak;
652 const N = "__ubsan_handle_" ++ sym_name;
653 @export(handler, .{ .name = N, .linkage = linkage });
654}
655
656fn exportHandlerWithAbort(
657 handler: anytype,
658 abort_handler: anytype,
659 comptime sym_name: []const u8,
660) void {
661 // Work around x86_64 backend limitation.
662 const linkage = if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag == .windows) .internal else .weak;
663 {
664 const N = "__ubsan_handle_" ++ sym_name;
665 @export(handler, .{ .name = N, .linkage = linkage });
666 }
667 {
668 const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
669 @export(abort_handler, .{ .name = N, .linkage = linkage });
670 }
671}
672
673const can_build_ubsan = switch (builtin.zig_backend) {
674 .stage2_riscv64 => false,
675 else => true,
676};
677
678comptime {
679 if (can_build_ubsan) {
680 overflowHandler("add_overflow", "+");
681 overflowHandler("mul_overflow", "*");
682 overflowHandler("sub_overflow", "-");
683 exportHandlerWithAbort(&alignmentAssumptionHandler, &alignmentAssumptionHandlerAbort, "alignment_assumption");
684
685 exportHandlerWithAbort(&divRemHandler, &divRemHandlerAbort, "divrem_overflow");
686 exportHandlerWithAbort(&floatCastOverflow, &floatCastOverflowAbort, "float_cast_overflow");
687 exportHandlerWithAbort(&invalidBuiltin, &invalidBuiltinAbort, "invalid_builtin");
688 exportHandlerWithAbort(&loadInvalidValue, &loadInvalidValueAbort, "load_invalid_value");
689
690 exportHandlerWithAbort(&negationHandler, &negationHandlerAbort, "negate_overflow");
691 exportHandlerWithAbort(&nonNullArg, &nonNullArgAbort, "nonnull_arg");
692 exportHandlerWithAbort(&nonNullReturn, &nonNullReturnAbort, "nonnull_return_v1");
693 exportHandlerWithAbort(&outOfBounds, &outOfBoundsAbort, "out_of_bounds");
694 exportHandlerWithAbort(&pointerOverflow, &pointerOverflowAbort, "pointer_overflow");
695 exportHandlerWithAbort(&shiftOob, &shiftOobAbort, "shift_out_of_bounds");
696 exportHandlerWithAbort(&typeMismatch, &typeMismatchAbort, "type_mismatch_v1");
697 exportHandlerWithAbort(&vlaBoundNotPositive, &vlaBoundNotPositiveAbort, "vla_bound_not_positive");
698
699 exportHandler(&builtinUnreachable, "builtin_unreachable");
700 exportHandler(&missingReturn, "missing_return");
701 }
702
703 // these checks are nearly impossible to replicate in zig, as they rely on nuances
704 // in the Itanium C++ ABI.
705 // exportHandlerWithAbort(&dynamicTypeCacheMiss, &dynamicTypeCacheMissAbort, "dynamic-type-cache-miss");
706 // exportHandlerWithAbort(&vptrTypeCache, &vptrTypeCacheAbort, "vptr-type-cache");
707
708 // we disable -fsanitize=function for reasons explained in src/Compilation.zig
709 // exportHandlerWithAbort(&functionTypeMismatch, &functionTypeMismatchAbort, "function-type-mismatch");
710 // exportHandlerWithAbort(&functionTypeMismatchV1, &functionTypeMismatchV1Abort, "function-type-mismatch-v1");
711}
src/Compilation.zig+121-15
......@@ -78,7 +78,8 @@ implib_emit: ?Path,
7878/// This is non-null when `-femit-docs` is provided.
7979docs_emit: ?Path,
8080root_name: [:0]const u8,
81include_compiler_rt: bool,
81compiler_rt_strat: RtStrat,
82ubsan_rt_strat: RtStrat,
8283/// Resolved into known paths, any GNU ld scripts already resolved.
8384link_inputs: []const link.Input,
8485/// Needed only for passing -F args to clang.
......@@ -226,6 +227,12 @@ libunwind_static_lib: ?CrtFile = null,
226227/// Populated when we build the TSAN library. A Job to build this is placed in the queue
227228/// and resolved before calling linker.flush().
228229tsan_lib: ?CrtFile = null,
230/// Populated when we build the UBSAN library. A Job to build this is placed in the queue
231/// and resolved before calling linker.flush().
232ubsan_rt_lib: ?CrtFile = null,
233/// Populated when we build the UBSAN object. A Job to build this is placed in the queue
234/// and resolved before calling linker.flush().
235ubsan_rt_obj: ?CrtFile = null,
229236/// Populated when we build the libc static library. A Job to build this is placed in the queue
230237/// and resolved before calling linker.flush().
231238libc_static_lib: ?CrtFile = null,
......@@ -283,6 +290,8 @@ digest: ?[Cache.bin_digest_len]u8 = null,
283290const QueuedJobs = struct {
284291 compiler_rt_lib: bool = false,
285292 compiler_rt_obj: bool = false,
293 ubsan_rt_lib: bool = false,
294 ubsan_rt_obj: bool = false,
286295 fuzzer_lib: bool = false,
287296 update_builtin_zig: bool,
288297 musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false),
......@@ -789,6 +798,7 @@ pub const MiscTask = enum {
789798 libcxx,
790799 libcxxabi,
791800 libtsan,
801 libubsan,
792802 libfuzzer,
793803 wasi_libc_crt_file,
794804 compiler_rt,
......@@ -1064,6 +1074,7 @@ pub const CreateOptions = struct {
10641074 /// Position Independent Executable. If the output mode is not an
10651075 /// executable this field is ignored.
10661076 want_compiler_rt: ?bool = null,
1077 want_ubsan_rt: ?bool = null,
10671078 want_lto: ?bool = null,
10681079 function_sections: bool = false,
10691080 data_sections: bool = false,
......@@ -1245,6 +1256,8 @@ fn addModuleTableToCacheHash(
12451256 }
12461257}
12471258
1259const RtStrat = enum { none, lib, obj, zcu };
1260
12481261pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compilation {
12491262 const output_mode = options.config.output_mode;
12501263 const is_dyn_lib = switch (output_mode) {
......@@ -1276,6 +1289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
12761289 const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;
12771290 const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;
12781291 const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread;
1292 const any_sanitize_c = options.config.any_sanitize_c or options.root_mod.sanitize_c;
12791293 const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;
12801294
12811295 const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables;
......@@ -1294,10 +1308,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
12941308
12951309 const sysroot = options.sysroot orelse libc_dirs.sysroot;
12961310
1297 const include_compiler_rt = options.want_compiler_rt orelse
1298 (!options.skip_linker_dependencies and is_exe_or_dyn_lib);
1311 const compiler_rt_strat: RtStrat = s: {
1312 if (options.skip_linker_dependencies) break :s .none;
1313 const want = options.want_compiler_rt orelse is_exe_or_dyn_lib;
1314 if (!want) break :s .none;
1315 if (have_zcu and output_mode == .Obj) break :s .zcu;
1316 if (is_exe_or_dyn_lib) break :s .lib;
1317 break :s .obj;
1318 };
12991319
1300 if (include_compiler_rt and output_mode == .Obj) {
1320 if (compiler_rt_strat == .zcu) {
13011321 // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");`
13021322 // injected into the object.
13031323 const compiler_rt_mod = try Package.Module.create(arena, .{
......@@ -1323,6 +1343,38 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
13231343 try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod);
13241344 }
13251345
1346 // unlike compiler_rt, we always want to go through the `_ = @import("ubsan-rt")`
1347 // approach, since the ubsan runtime uses quite a lot of the standard library
1348 // and this reduces unnecessary bloat.
1349 const ubsan_rt_strat: RtStrat = s: {
1350 const want_ubsan_rt = options.want_ubsan_rt orelse (any_sanitize_c and is_exe_or_dyn_lib);
1351 if (!want_ubsan_rt) break :s .none;
1352 if (options.skip_linker_dependencies) break :s .none;
1353 if (have_zcu) break :s .zcu;
1354 if (is_exe_or_dyn_lib) break :s .lib;
1355 break :s .obj;
1356 };
1357
1358 if (ubsan_rt_strat == .zcu) {
1359 const ubsan_rt_mod = try Package.Module.create(arena, .{
1360 .global_cache_directory = options.global_cache_directory,
1361 .paths = .{
1362 .root = .{
1363 .root_dir = options.zig_lib_directory,
1364 },
1365 .root_src_path = "ubsan_rt.zig",
1366 },
1367 .fully_qualified_name = "ubsan_rt",
1368 .cc_argv = &.{},
1369 .inherited = .{},
1370 .global = options.config,
1371 .parent = options.root_mod,
1372 .builtin_mod = options.root_mod.getBuiltinDependency(),
1373 .builtin_modules = null, // `builtin_mod` is set
1374 });
1375 try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod);
1376 }
1377
13261378 if (options.verbose_llvm_cpu_features) {
13271379 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: {
13281380 const target = options.root_mod.resolved_target.result;
......@@ -1499,7 +1551,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
14991551 .windows_libs = windows_libs,
15001552 .version = options.version,
15011553 .libc_installation = libc_dirs.libc_installation,
1502 .include_compiler_rt = include_compiler_rt,
1554 .compiler_rt_strat = compiler_rt_strat,
1555 .ubsan_rt_strat = ubsan_rt_strat,
15031556 .link_inputs = options.link_inputs,
15041557 .framework_dirs = options.framework_dirs,
15051558 .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit,
......@@ -1525,6 +1578,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
15251578 comp.config.any_unwind_tables = any_unwind_tables;
15261579 comp.config.any_non_single_threaded = any_non_single_threaded;
15271580 comp.config.any_sanitize_thread = any_sanitize_thread;
1581 comp.config.any_sanitize_c = any_sanitize_c;
15281582 comp.config.any_fuzz = any_fuzz;
15291583
15301584 const lf_open_opts: link.File.OpenOptions = .{
......@@ -1871,24 +1925,34 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18711925 comp.remaining_prelink_tasks += 1;
18721926 }
18731927
1874 if (comp.include_compiler_rt and capable_of_building_compiler_rt) {
1875 if (is_exe_or_dyn_lib) {
1928 if (capable_of_building_compiler_rt) {
1929 if (comp.compiler_rt_strat == .lib) {
18761930 log.debug("queuing a job to build compiler_rt_lib", .{});
18771931 comp.queued_jobs.compiler_rt_lib = true;
18781932 comp.remaining_prelink_tasks += 1;
1879 } else if (output_mode != .Obj) {
1933 } else if (comp.compiler_rt_strat == .obj) {
18801934 log.debug("queuing a job to build compiler_rt_obj", .{});
18811935 // In this case we are making a static library, so we ask
18821936 // for a compiler-rt object to put in it.
18831937 comp.queued_jobs.compiler_rt_obj = true;
18841938 comp.remaining_prelink_tasks += 1;
18851939 }
1886 }
18871940
1888 if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) {
1889 log.debug("queuing a job to build libfuzzer", .{});
1890 comp.queued_jobs.fuzzer_lib = true;
1891 comp.remaining_prelink_tasks += 1;
1941 if (comp.ubsan_rt_strat == .lib) {
1942 log.debug("queuing a job to build ubsan_rt_lib", .{});
1943 comp.queued_jobs.ubsan_rt_lib = true;
1944 comp.remaining_prelink_tasks += 1;
1945 } else if (comp.ubsan_rt_strat == .obj) {
1946 log.debug("queuing a job to build ubsan_rt_obj", .{});
1947 comp.queued_jobs.ubsan_rt_obj = true;
1948 comp.remaining_prelink_tasks += 1;
1949 }
1950
1951 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
1952 log.debug("queuing a job to build libfuzzer", .{});
1953 comp.queued_jobs.fuzzer_lib = true;
1954 comp.remaining_prelink_tasks += 1;
1955 }
18921956 }
18931957 }
18941958
......@@ -1937,9 +2001,16 @@ pub fn destroy(comp: *Compilation) void {
19372001 if (comp.compiler_rt_obj) |*crt_file| {
19382002 crt_file.deinit(gpa);
19392003 }
2004 if (comp.ubsan_rt_lib) |*crt_file| {
2005 crt_file.deinit(gpa);
2006 }
2007 if (comp.ubsan_rt_obj) |*crt_file| {
2008 crt_file.deinit(gpa);
2009 }
19402010 if (comp.fuzzer_lib) |*crt_file| {
19412011 crt_file.deinit(gpa);
19422012 }
2013
19432014 if (comp.libc_static_lib) |*crt_file| {
19442015 crt_file.deinit(gpa);
19452016 }
......@@ -2207,6 +2278,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22072278 _ = try pt.importPkg(zcu.main_mod);
22082279 }
22092280
2281 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {
2282 _ = try pt.importPkg(ubsan_rt_mod);
2283 }
2284
22102285 if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
22112286 _ = try pt.importPkg(compiler_rt_mod);
22122287 }
......@@ -2248,6 +2323,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22482323 try comp.queueJob(.{ .analyze_mod = compiler_rt_mod });
22492324 zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod);
22502325 }
2326
2327 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {
2328 try comp.queueJob(.{ .analyze_mod = ubsan_rt_mod });
2329 zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod);
2330 }
22512331 }
22522332
22532333 try comp.performAllTheWork(main_progress_node);
......@@ -2592,7 +2672,8 @@ fn addNonIncrementalStuffToCacheManifest(
25922672 man.hash.addOptional(comp.version);
25932673 man.hash.add(comp.link_eh_frame_hdr);
25942674 man.hash.add(comp.skip_linker_dependencies);
2595 man.hash.add(comp.include_compiler_rt);
2675 man.hash.add(comp.compiler_rt_strat);
2676 man.hash.add(comp.ubsan_rt_strat);
25962677 man.hash.add(comp.rc_includes);
25972678 man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
25982679 man.hash.addListOfBytes(comp.framework_dirs);
......@@ -3683,6 +3764,14 @@ fn performAllTheWorkInner(
36833764 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node });
36843765 }
36853766
3767 if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {
3768 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Lib, false, &comp.ubsan_rt_lib, main_progress_node });
3769 }
3770
3771 if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {
3772 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Obj, false, &comp.ubsan_rt_obj, main_progress_node });
3773 }
3774
36863775 if (comp.queued_jobs.glibc_shared_objects) {
36873776 comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node });
36883777 }
......@@ -5916,7 +6005,11 @@ pub fn addCCArgs(
59166005 // These args have to be added after the `-fsanitize` arg or
59176006 // they won't take effect.
59186007 if (mod.sanitize_c) {
5919 try argv.append("-fsanitize-trap=undefined");
6008 // This check requires implementing the Itanium C++ ABI.
6009 // We would make it `-fsanitize-trap=vptr`, however this check requires
6010 // a full runtime due to the type hashing involved.
6011 try argv.append("-fno-sanitize=vptr");
6012
59206013 // It is very common, and well-defined, for a pointer on one side of a C ABI
59216014 // to have a different but compatible element type. Examples include:
59226015 // `char*` vs `uint8_t*` on a system with 8-bit bytes
......@@ -5925,6 +6018,19 @@ pub fn addCCArgs(
59256018 // Without this flag, Clang would invoke UBSAN when such an extern
59266019 // function was called.
59276020 try argv.append("-fno-sanitize=function");
6021
6022 // It's recommended to use the minimal runtime in production environments
6023 // due to the security implications of the full runtime. The minimal runtime
6024 // doesn't provide much benefit over simply trapping.
6025 if (mod.optimize_mode == .ReleaseSafe) {
6026 try argv.append("-fsanitize-trap=undefined");
6027 }
6028
6029 // This is necessary because, by default, Clang instructs LLVM to embed a COFF link
6030 // dependency on `libclang_rt.ubsan_standalone.a` when the UBSan runtime is used.
6031 if (target.os.tag == .windows) {
6032 try argv.append("-fno-rtlib-defaultlib");
6033 }
59286034 }
59296035 }
59306036
src/Compilation/Config.zig+3
......@@ -32,6 +32,7 @@ any_non_single_threaded: bool,
3232/// per-Module setting.
3333any_error_tracing: bool,
3434any_sanitize_thread: bool,
35any_sanitize_c: bool,
3536any_fuzz: bool,
3637pie: bool,
3738/// If this is true then linker code is responsible for making an LLVM IR
......@@ -87,6 +88,7 @@ pub const Options = struct {
8788 ensure_libcpp_on_non_freestanding: bool = false,
8889 any_non_single_threaded: bool = false,
8990 any_sanitize_thread: bool = false,
91 any_sanitize_c: bool = false,
9092 any_fuzz: bool = false,
9193 any_unwind_tables: bool = false,
9294 any_dyn_libs: bool = false,
......@@ -476,6 +478,7 @@ pub fn resolve(options: Options) ResolveError!Config {
476478 .any_non_single_threaded = options.any_non_single_threaded,
477479 .any_error_tracing = any_error_tracing,
478480 .any_sanitize_thread = options.any_sanitize_thread,
481 .any_sanitize_c = options.any_sanitize_c,
479482 .any_fuzz = options.any_fuzz,
480483 .san_cov_trace_pc_guard = options.san_cov_trace_pc_guard,
481484 .root_error_tracing = root_error_tracing,
src/Zcu.zig+1-1
......@@ -175,7 +175,7 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi
175175
176176/// These are the modules which we initially queue for analysis in `Compilation.update`.
177177/// `resolveReferences` will use these as the root of its reachability traversal.
178analysis_roots: std.BoundedArray(*Package.Module, 3) = .{},
178analysis_roots: std.BoundedArray(*Package.Module, 4) = .{},
179179/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and
180180/// reset to `null` when any semantic analysis occurs (since this invalidates the data).
181181/// Allocated into `gpa`.
src/link.zig+8-1
......@@ -1102,11 +1102,16 @@ pub const File = struct {
11021102
11031103 log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"});
11041104
1105 const compiler_rt_path: ?Path = if (comp.include_compiler_rt)
1105 const compiler_rt_path: ?Path = if (comp.compiler_rt_strat == .obj)
11061106 comp.compiler_rt_obj.?.full_object_path
11071107 else
11081108 null;
11091109
1110 const ubsan_rt_path: ?Path = if (comp.ubsan_rt_strat == .obj)
1111 comp.ubsan_rt_obj.?.full_object_path
1112 else
1113 null;
1114
11101115 // This function follows the same pattern as link.Elf.linkWithLLD so if you want some
11111116 // insight as to what's going on here you can read that function body which is more
11121117 // well-commented.
......@@ -1136,6 +1141,7 @@ pub const File = struct {
11361141 }
11371142 try man.addOptionalFile(zcu_obj_path);
11381143 try man.addOptionalFilePath(compiler_rt_path);
1144 try man.addOptionalFilePath(ubsan_rt_path);
11391145
11401146 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
11411147 _ = try man.hit();
......@@ -1181,6 +1187,7 @@ pub const File = struct {
11811187 }
11821188 if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
11831189 if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
1190 if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
11841191
11851192 if (comp.verbose_link) {
11861193 std.debug.print("ar rcs {s}", .{full_out_path_z});
src/link/Coff.zig+9
......@@ -2162,6 +2162,15 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
21622162 try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena));
21632163 }
21642164
2165 const ubsan_rt_path: ?Path = blk: {
2166 if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path;
2167 if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path;
2168 break :blk null;
2169 };
2170 if (ubsan_rt_path) |path| {
2171 try argv.append(try path.toString(arena));
2172 }
2173
21652174 if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) {
21662175 if (!comp.config.link_libc) {
21672176 if (comp.libc_static_lib) |lib| {
src/link/Elf.zig+10
......@@ -1541,6 +1541,11 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
15411541 if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
15421542 break :blk null;
15431543 };
1544 const ubsan_rt_path: ?Path = blk: {
1545 if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path;
1546 if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path;
1547 break :blk null;
1548 };
15441549
15451550 // Here we want to determine whether we can save time by not invoking LLD when the
15461551 // output is unchanged. None of the linker options or the object files that are being
......@@ -1575,6 +1580,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
15751580 }
15761581 try man.addOptionalFile(module_obj_path);
15771582 try man.addOptionalFilePath(compiler_rt_path);
1583 try man.addOptionalFilePath(ubsan_rt_path);
15781584 try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null);
15791585 try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null);
15801586
......@@ -1974,6 +1980,10 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
19741980 try argv.append(try lib.full_object_path.toString(arena));
19751981 }
19761982
1983 if (ubsan_rt_path) |p| {
1984 try argv.append(try p.toString(arena));
1985 }
1986
19771987 // libc
19781988 if (is_exe_or_dyn_lib and
19791989 !comp.skip_linker_dependencies and
src/link/MachO.zig+24-2
......@@ -344,11 +344,21 @@ pub fn deinit(self: *MachO) void {
344344 self.thunks.deinit(gpa);
345345}
346346
347pub fn flush(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
347pub fn flush(
348 self: *MachO,
349 arena: Allocator,
350 tid: Zcu.PerThread.Id,
351 prog_node: std.Progress.Node,
352) link.File.FlushError!void {
348353 try self.flushModule(arena, tid, prog_node);
349354}
350355
351pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
356pub fn flushModule(
357 self: *MachO,
358 arena: Allocator,
359 tid: Zcu.PerThread.Id,
360 prog_node: std.Progress.Node,
361) link.File.FlushError!void {
352362 const tracy = trace(@src());
353363 defer tracy.end();
354364
......@@ -409,6 +419,16 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
409419 try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path));
410420 }
411421
422 if (comp.ubsan_rt_lib) |crt_file| {
423 const path = crt_file.full_object_path;
424 self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err|
425 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
426 } else if (comp.ubsan_rt_obj) |crt_file| {
427 const path = crt_file.full_object_path;
428 self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err|
429 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
430 }
431
412432 for (positionals.items) |link_input| {
413433 self.classifyInputFile(link_input) catch |err|
414434 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
......@@ -813,6 +833,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
813833
814834 if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
815835 if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
836 if (comp.ubsan_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
837 if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
816838 }
817839
818840 Compilation.dump_argv(argv.items);
src/link/MachO/relocatable.zig+5-1
......@@ -93,10 +93,14 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
9393
9494 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
9595
96 if (comp.include_compiler_rt) {
96 if (comp.compiler_rt_strat == .obj) {
9797 try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path));
9898 }
9999
100 if (comp.ubsan_rt_strat == .obj) {
101 try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path));
102 }
103
100104 for (positionals.items) |link_input| {
101105 macho_file.classifyInputFile(link_input) catch |err|
102106 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
src/link/Wasm.zig+10
......@@ -3879,6 +3879,11 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
38793879 if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path;
38803880 break :blk null;
38813881 };
3882 const ubsan_rt_path: ?Path = blk: {
3883 if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path;
3884 if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path;
3885 break :blk null;
3886 };
38823887
38833888 const id_symlink_basename = "lld.id";
38843889
......@@ -3901,6 +3906,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
39013906 }
39023907 try man.addOptionalFile(module_obj_path);
39033908 try man.addOptionalFilePath(compiler_rt_path);
3909 try man.addOptionalFilePath(ubsan_rt_path);
39043910 man.hash.addOptionalBytes(wasm.entry_name.slice(wasm));
39053911 man.hash.add(wasm.base.stack_size);
39063912 man.hash.add(wasm.base.build_id);
......@@ -4148,6 +4154,10 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
41484154 try argv.append(try p.toString(arena));
41494155 }
41504156
4157 if (ubsan_rt_path) |p| {
4158 try argv.append(try p.toStringZ(arena));
4159 }
4160
41514161 if (comp.verbose_link) {
41524162 // Skip over our own name so that the LLD linker name is the first argv item.
41534163 Compilation.dump_argv(argv.items[1..]);
src/main.zig+8
......@@ -561,6 +561,8 @@ const usage_build_generic =
561561 \\ -fno-lld Prevent using LLD as the linker
562562 \\ -fcompiler-rt Always include compiler-rt symbols in output
563563 \\ -fno-compiler-rt Prevent including compiler-rt symbols in output
564 \\ -fubsan-rt Always include ubsan-rt symbols in the output
565 \\ -fno-ubsan-rt Prevent including ubsan-rt symbols in the output
564566 \\ -rdynamic Add all symbols to the dynamic symbol table
565567 \\ -feach-lib-rpath Ensure adding rpath for each used dynamic library
566568 \\ -fno-each-lib-rpath Prevent adding rpath for each used dynamic library
......@@ -849,6 +851,7 @@ fn buildOutputType(
849851 var emit_h: Emit = .no;
850852 var soname: SOName = undefined;
851853 var want_compiler_rt: ?bool = null;
854 var want_ubsan_rt: ?bool = null;
852855 var linker_script: ?[]const u8 = null;
853856 var version_script: ?[]const u8 = null;
854857 var linker_repro: ?bool = null;
......@@ -1376,6 +1379,10 @@ fn buildOutputType(
13761379 want_compiler_rt = true;
13771380 } else if (mem.eql(u8, arg, "-fno-compiler-rt")) {
13781381 want_compiler_rt = false;
1382 } else if (mem.eql(u8, arg, "-fubsan-rt")) {
1383 want_ubsan_rt = true;
1384 } else if (mem.eql(u8, arg, "-fno-ubsan-rt")) {
1385 want_ubsan_rt = false;
13791386 } else if (mem.eql(u8, arg, "-feach-lib-rpath")) {
13801387 create_module.each_lib_rpath = true;
13811388 } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) {
......@@ -3504,6 +3511,7 @@ fn buildOutputType(
35043511 .windows_lib_names = create_module.windows_libs.keys(),
35053512 .wasi_emulated_libs = create_module.wasi_emulated_libs.items,
35063513 .want_compiler_rt = want_compiler_rt,
3514 .want_ubsan_rt = want_ubsan_rt,
35073515 .hash_style = hash_style,
35083516 .linker_script = linker_script,
35093517 .version_script = version_script,
test/link/elf.zig+6
......@@ -2049,6 +2049,9 @@ fn testLargeBss(b: *Build, opts: Options) *Step {
20492049 \\}
20502050 , &.{});
20512051 exe.linkLibC();
2052 // Disabled to work around the ELF linker crashing.
2053 // Can be reproduced on a x86_64-linux host by commenting out the line below.
2054 exe.root_module.sanitize_c = false;
20522055
20532056 const run = addRunArtifact(exe);
20542057 run.expectExitCode(0);
......@@ -3552,6 +3555,9 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
35523555 \\}
35533556 , &.{});
35543557 exe.linkLibC();
3558 // Disabled to work around the ELF linker crashing.
3559 // Can be reproduced on a x86_64-linux host by commenting out the line below.
3560 exe.root_module.sanitize_c = false;
35553561
35563562 const run = addRunArtifact(exe);
35573563 run.expectStdOutEqual("3 0 5 0 0 0\n");
test/link/glibc_compat/build.zig+12
......@@ -22,6 +22,10 @@ pub fn build(b: *std.Build) void {
2222 .link_libc = true,
2323 }),
2424 });
25 // We disable UBSAN for these tests as the libc being tested here is
26 // so old, it doesn't even support compiling our UBSAN implementation.
27 exe.bundle_ubsan_rt = false;
28 exe.root_module.sanitize_c = false;
2529 exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });
2630 // TODO: actually test the output
2731 _ = exe.getEmittedBin();
......@@ -62,6 +66,10 @@ pub fn build(b: *std.Build) void {
6266 .link_libc = true,
6367 }),
6468 });
69 // We disable UBSAN for these tests as the libc being tested here is
70 // so old, it doesn't even support compiling our UBSAN implementation.
71 exe.bundle_ubsan_rt = false;
72 exe.root_module.sanitize_c = false;
6573 exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
6674
6775 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
......@@ -161,6 +169,10 @@ pub fn build(b: *std.Build) void {
161169 .link_libc = true,
162170 }),
163171 });
172 // We disable UBSAN for these tests as the libc being tested here is
173 // so old, it doesn't even support compiling our UBSAN implementation.
174 exe.bundle_ubsan_rt = false;
175 exe.root_module.sanitize_c = false;
164176
165177 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
166178 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
test/link/wasm/export-data/build.zig+3
......@@ -13,6 +13,9 @@ pub fn build(b: *std.Build) void {
1313 }),
1414 });
1515 lib.entry = .disabled;
16 // Disabled to work around the Wasm linker crashing.
17 // Can be reproduced by commenting out the line below.
18 lib.bundle_ubsan_rt = false;
1619 lib.use_lld = false;
1720 lib.root_module.export_symbol_names = &.{ "foo", "bar" };
1821 // Object being linked has neither functions nor globals named "foo" or "bar" and
test/link/wasm/export/build.zig+6
......@@ -19,6 +19,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1919 no_export.entry = .disabled;
2020 no_export.use_llvm = false;
2121 no_export.use_lld = false;
22 // Don't pull in ubsan, since we're just expecting a very minimal executable.
23 no_export.bundle_ubsan_rt = false;
2224
2325 const dynamic_export = b.addExecutable(.{
2426 .name = "dynamic",
......@@ -32,6 +34,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3234 dynamic_export.rdynamic = true;
3335 dynamic_export.use_llvm = false;
3436 dynamic_export.use_lld = false;
37 // Don't pull in ubsan, since we're just expecting a very minimal executable.
38 dynamic_export.bundle_ubsan_rt = false;
3539
3640 const force_export = b.addExecutable(.{
3741 .name = "force",
......@@ -45,6 +49,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4549 force_export.root_module.export_symbol_names = &.{"foo"};
4650 force_export.use_llvm = false;
4751 force_export.use_lld = false;
52 // Don't pull in ubsan, since we're just expecting a very minimal executable.
53 force_export.bundle_ubsan_rt = false;
4854
4955 const check_no_export = no_export.checkObject();
5056 check_no_export.checkInHeaders();
test/link/wasm/function-table/build.zig+4
......@@ -21,6 +21,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 export_table.use_lld = false;
2222 export_table.export_table = true;
2323 export_table.link_gc_sections = false;
24 // Don't pull in ubsan, since we're just expecting a very minimal executable.
25 export_table.bundle_ubsan_rt = false;
2426
2527 const regular_table = b.addExecutable(.{
2628 .name = "regular_table",
......@@ -34,6 +36,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3436 regular_table.use_llvm = false;
3537 regular_table.use_lld = false;
3638 regular_table.link_gc_sections = false; // Ensure function table is not empty
39 // Don't pull in ubsan, since we're just expecting a very minimal executable.
40 regular_table.bundle_ubsan_rt = false;
3741
3842 const check_export = export_table.checkObject();
3943 const check_regular = regular_table.checkObject();
test/link/wasm/shared-memory/build.zig+2
......@@ -31,6 +31,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt
3131 exe.shared_memory = true;
3232 exe.max_memory = 67108864;
3333 exe.root_module.export_symbol_names = &.{"foo"};
34 // Don't pull in ubsan, since we're just expecting a very minimal executable.
35 exe.bundle_ubsan_rt = false;
3436
3537 const check_exe = exe.checkObject();
3638
test/link/wasm/type/build.zig+2
......@@ -21,6 +21,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 exe.use_llvm = false;
2222 exe.use_lld = false;
2323 exe.root_module.export_symbol_names = &.{"foo"};
24 // Don't pull in ubsan, since we're just expecting a very minimal executable.
25 exe.bundle_ubsan_rt = false;
2426 b.installArtifact(exe);
2527
2628 const check_exe = exe.checkObject();
test/src/StackTrace.zig+1
......@@ -81,6 +81,7 @@ fn addExpect(
8181 }),
8282 .use_llvm = use_llvm,
8383 });
84 exe.bundle_ubsan_rt = false;
8485
8586 const run = b.addRunArtifact(exe);
8687 run.removeEnvironmentVariable("CLICOLOR_FORCE");
tools/incr-check.zig+1
......@@ -108,6 +108,7 @@ pub fn main() !void {
108108 "build-exe",
109109 case.root_source_file,
110110 "-fincremental",
111 "-fno-ubsan-rt",
111112 "-target",
112113 target.query,
113114 "--cache-dir",