authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2025-01-01 03:47:17-08:00
committergravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2025-02-25 11:22:33-08:00
log931178494f4c77631e5eb9c567f64492d6592eeb
treecc20259219488748b3bbc48c6293f5a1dbfa951a
parent9432a9b6e1d2bd340afac6b29e49f1094cf1ba93

Compilation: correct when to include ubsan


6 files changed, 633 insertions(+), 631 deletions(-)

lib/ubsan.zig deleted-603
...@@ -1,603 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4
5const SourceLocation = extern struct {
6 file_name: ?[*:0]const u8,
7 line: u32,
8 col: u32,
9};
10
11const TypeDescriptor = extern struct {
12 kind: Kind,
13 info: Info,
14 // name: [?:0]u8
15
16 const Kind = enum(u16) {
17 integer = 0x0000,
18 float = 0x0001,
19 unknown = 0xFFFF,
20 };
21
22 const Info = extern union {
23 integer: packed struct(u16) {
24 signed: bool,
25 bit_width: u15,
26 },
27 float: u16,
28 };
29
30 fn getIntegerSize(desc: TypeDescriptor) u64 {
31 assert(desc.kind == .integer);
32 const bit_width = desc.info.integer.bit_width;
33 return @as(u64, 1) << @intCast(bit_width);
34 }
35
36 fn isSigned(desc: TypeDescriptor) bool {
37 return desc.kind == .integer and desc.info.integer.signed;
38 }
39
40 fn getName(desc: *const TypeDescriptor) [:0]const u8 {
41 return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor));
42 }
43};
44
45const ValueHandle = *const opaque {};
46
47const Value = extern struct {
48 td: *const TypeDescriptor,
49 handle: ValueHandle,
50
51 fn getUnsignedInteger(value: Value) u128 {
52 assert(!value.td.isSigned());
53 const size = value.td.getIntegerSize();
54 const max_inline_size = @bitSizeOf(ValueHandle);
55 if (size <= max_inline_size) {
56 return @intFromPtr(value.handle);
57 }
58
59 return switch (size) {
60 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*,
61 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*,
62 else => unreachable,
63 };
64 }
65
66 fn getSignedInteger(value: Value) i128 {
67 assert(value.td.isSigned());
68 const size = value.td.getIntegerSize();
69 const max_inline_size = @bitSizeOf(ValueHandle);
70 if (size <= max_inline_size) {
71 const extra_bits: std.math.Log2Int(usize) = @intCast(max_inline_size - size);
72 const handle: isize = @bitCast(@intFromPtr(value.handle));
73 return (handle << extra_bits) >> extra_bits;
74 }
75 return switch (size) {
76 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*,
77 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*,
78 else => @trap(),
79 };
80 }
81
82 fn getFloat(value: Value) c_longdouble {
83 assert(value.td.kind == .float);
84 const size = value.td.info.float;
85 const max_inline_size = @bitSizeOf(ValueHandle);
86 if (size <= max_inline_size) {
87 return @bitCast(@intFromPtr(value.handle));
88 }
89 return @floatCast(switch (size) {
90 64 => @as(*const f64, @alignCast(@ptrCast(value.handle))).*,
91 80 => @as(*const f80, @alignCast(@ptrCast(value.handle))).*,
92 128 => @as(*const f128, @alignCast(@ptrCast(value.handle))).*,
93 else => @trap(),
94 });
95 }
96
97 fn isMinusOne(value: Value) bool {
98 return value.td.isSigned() and
99 value.getSignedInteger() == -1;
100 }
101
102 fn isNegative(value: Value) bool {
103 return value.td.isSigned() and
104 value.getSignedInteger() < 0;
105 }
106
107 fn getPositiveInteger(value: Value) u128 {
108 if (value.td.isSigned()) {
109 const signed = value.getSignedInteger();
110 assert(signed >= 0);
111 return @intCast(signed);
112 } else {
113 return value.getUnsignedInteger();
114 }
115 }
116
117 pub fn format(
118 value: Value,
119 comptime fmt: []const u8,
120 _: std.fmt.FormatOptions,
121 writer: anytype,
122 ) !void {
123 comptime assert(fmt.len == 0);
124
125 switch (value.td.kind) {
126 .integer => {
127 if (value.td.isSigned()) {
128 try writer.print("{}", .{value.getSignedInteger()});
129 } else {
130 try writer.print("{}", .{value.getUnsignedInteger()});
131 }
132 },
133 .float => try writer.print("{}", .{value.getFloat()}),
134 .unknown => try writer.writeAll("(unknown)"),
135 }
136 }
137};
138
139const OverflowData = extern struct {
140 loc: SourceLocation,
141 td: *const TypeDescriptor,
142};
143
144fn overflowHandler(
145 comptime sym_name: []const u8,
146 comptime operator: []const u8,
147) void {
148 const S = struct {
149 fn handler(
150 data: *const OverflowData,
151 lhs_handle: ValueHandle,
152 rhs_handle: ValueHandle,
153 ) callconv(.c) noreturn {
154 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
155 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
156
157 const is_signed = data.td.isSigned();
158 const fmt = "{s} integer overflow: " ++ "{} " ++
159 operator ++ " {} cannot be represented in type {s}";
160
161 logMessage(fmt, .{
162 if (is_signed) "signed" else "unsigned",
163 lhs,
164 rhs,
165 data.td.getName(),
166 });
167 }
168 };
169
170 exportHandler(&S.handler, sym_name, true);
171}
172
173fn negationHandler(
174 data: *const OverflowData,
175 value_handle: ValueHandle,
176) callconv(.c) noreturn {
177 const value: Value = .{ .handle = value_handle, .td = data.td };
178 logMessage(
179 "negation of {} cannot be represented in type {s}",
180 .{ value, data.td.getName() },
181 );
182}
183
184fn divRemHandler(
185 data: *const OverflowData,
186 lhs_handle: ValueHandle,
187 rhs_handle: ValueHandle,
188) callconv(.c) noreturn {
189 const lhs: Value = .{ .handle = lhs_handle, .td = data.lhs_type };
190 const rhs: Value = .{ .handle = rhs_handle, .td = data.rhs_type };
191
192 if (rhs.isMinusOne()) {
193 logMessage(
194 "division of {} by -1 cannot be represented in type {s}",
195 .{ lhs, data.td.getName() },
196 );
197 } else logMessage("division by zero", .{});
198}
199
200const AlignmentAssumptionData = extern struct {
201 loc: SourceLocation,
202 assumption_loc: SourceLocation,
203 td: *const TypeDescriptor,
204};
205
206fn alignmentAssumptionHandler(
207 data: *const AlignmentAssumptionData,
208 pointer: ValueHandle,
209 alignment_handle: ValueHandle,
210 maybe_offset: ?ValueHandle,
211) callconv(.c) noreturn {
212 const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
213 const lsb = @ctz(real_pointer);
214 const actual_alignment = @as(u64, 1) << @intCast(lsb);
215 const mask = @intFromPtr(alignment_handle) - 1;
216 const misalignment_offset = real_pointer & mask;
217 const alignment: Value = .{ .handle = alignment_handle, .td = data.td };
218
219 if (maybe_offset) |offset| {
220 logMessage(
221 "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed\n" ++
222 "offset address is {} aligned, misalignment offset is {} bytes",
223 .{
224 alignment,
225 @intFromPtr(offset),
226 data.td.getName(),
227 actual_alignment,
228 misalignment_offset,
229 },
230 );
231 } else {
232 logMessage(
233 "assumption of {} byte alignment for pointer of type {s} failed\n" ++
234 "address is {} aligned, misalignment offset is {} bytes",
235 .{
236 alignment,
237 data.td.getName(),
238 actual_alignment,
239 misalignment_offset,
240 },
241 );
242 }
243}
244
245const ShiftOobData = extern struct {
246 loc: SourceLocation,
247 lhs_type: *const TypeDescriptor,
248 rhs_type: *const TypeDescriptor,
249};
250
251fn shiftOob(
252 data: *const ShiftOobData,
253 lhs_handle: ValueHandle,
254 rhs_handle: ValueHandle,
255) callconv(.c) noreturn {
256 const lhs: Value = .{ .handle = lhs_handle, .td = data.lhs_type };
257 const rhs: Value = .{ .handle = rhs_handle, .td = data.rhs_type };
258
259 if (rhs.isNegative() or
260 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
261 {
262 if (rhs.isNegative()) {
263 logMessage("shift exponent {} is negative", .{rhs});
264 } else {
265 logMessage(
266 "shift exponent {} is too large for {}-bit type {s}",
267 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
268 );
269 }
270 } else {
271 if (lhs.isNegative()) {
272 logMessage("left shift of negative value {}", .{lhs});
273 } else {
274 logMessage(
275 "left shift of {} by {} places cannot be represented in type {s}",
276 .{ lhs, rhs, data.lhs_type.getName() },
277 );
278 }
279 }
280}
281
282const OutOfBoundsData = extern struct {
283 loc: SourceLocation,
284 array_type: *const TypeDescriptor,
285 index_type: *const TypeDescriptor,
286};
287
288fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.c) noreturn {
289 const index: Value = .{ .handle = index_handle, .td = data.index_type };
290 logMessage(
291 "index {} out of bounds for type {s}",
292 .{ index, data.array_type.getName() },
293 );
294}
295
296const PointerOverflowData = extern struct {
297 loc: SourceLocation,
298};
299
300fn pointerOverflow(
301 _: *const PointerOverflowData,
302 base: usize,
303 result: usize,
304) callconv(.c) noreturn {
305 if (base == 0) {
306 if (result == 0) {
307 logMessage("applying zero offset to null pointer", .{});
308 } else {
309 logMessage("applying non-zero offset {} to null pointer", .{result});
310 }
311 } else {
312 if (result == 0) {
313 logMessage(
314 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
315 .{base},
316 );
317 } else {
318 const signed_base: isize = @bitCast(base);
319 const signed_result: isize = @bitCast(result);
320 if ((signed_base >= 0) == (signed_result >= 0)) {
321 if (base > result) {
322 logMessage(
323 "addition of unsigned offset to 0x{x} overflowed to 0x{x}",
324 .{ base, result },
325 );
326 } else {
327 logMessage(
328 "subtraction of unsigned offset to 0x{x} overflowed to 0x{x}",
329 .{ base, result },
330 );
331 }
332 } else {
333 logMessage(
334 "pointer index expression with base 0x{x} overflowed to 0x{x}",
335 .{ base, result },
336 );
337 }
338 }
339 }
340}
341
342const TypeMismatchData = extern struct {
343 loc: SourceLocation,
344 td: *const TypeDescriptor,
345 log_alignment: u8,
346 kind: enum(u8) {
347 load,
348 store,
349 reference_binding,
350 member_access,
351 member_call,
352 constructor_call,
353 downcast_pointer,
354 downcast_reference,
355 upcast,
356 upcast_to_virtual_base,
357 nonnull_assign,
358 dynamic_operation,
359
360 fn getName(kind: @This()) []const u8 {
361 return switch (kind) {
362 .load => "load of",
363 .store => "store of",
364 .reference_binding => "reference binding to",
365 .member_access => "member access within",
366 .member_call => "member call on",
367 .constructor_call => "constructor call on",
368 .downcast_pointer, .downcast_reference => "downcast of",
369 .upcast => "upcast of",
370 .upcast_to_virtual_base => "cast to virtual base of",
371 .nonnull_assign => "_Nonnull binding to",
372 .dynamic_operation => "dynamic operation on",
373 };
374 }
375 },
376};
377
378fn typeMismatch(
379 data: *const TypeMismatchData,
380 pointer: ?ValueHandle,
381) callconv(.c) noreturn {
382 const alignment = @as(usize, 1) << @intCast(data.log_alignment);
383 const handle: usize = @intFromPtr(pointer);
384
385 if (pointer == null) {
386 logMessage(
387 "{s} null pointer of type {s}",
388 .{ data.kind.getName(), data.td.getName() },
389 );
390 } else if (!std.mem.isAligned(handle, alignment)) {
391 logMessage(
392 "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment",
393 .{ data.kind.getName(), handle, data.td.getName(), alignment },
394 );
395 } else {
396 logMessage(
397 "{s} address 0x{x} with insufficient space for an object of type {s}",
398 .{ data.kind.getName(), handle, data.td.getName() },
399 );
400 }
401}
402
403const UnreachableData = extern struct {
404 loc: SourceLocation,
405};
406
407fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn {
408 logMessage("execution reached an unreachable program point", .{});
409}
410
411fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn {
412 logMessage("execution reached the end of a value-returning function without returning a value", .{});
413}
414
415const NonNullReturnData = extern struct {
416 attribute_loc: SourceLocation,
417};
418
419fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn {
420 logMessage("null pointer returned from function declared to never return null", .{});
421}
422
423const NonNullArgData = extern struct {
424 loc: SourceLocation,
425 attribute_loc: SourceLocation,
426 arg_index: i32,
427};
428
429fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn {
430 logMessage(
431 "null pointer passed as argument {}, which is declared to never be null",
432 .{data.arg_index},
433 );
434}
435
436const InvalidValueData = extern struct {
437 loc: SourceLocation,
438 td: *const TypeDescriptor,
439};
440
441fn loadInvalidValue(
442 data: *const InvalidValueData,
443 value_handle: ValueHandle,
444) callconv(.c) noreturn {
445 const value: Value = .{ .handle = value_handle, .td = data.td };
446 logMessage(
447 "load of value {}, which is not valid for type {s}",
448 .{ value, data.td.getName() },
449 );
450}
451
452const InvalidBuiltinData = extern struct {
453 loc: SourceLocation,
454 kind: enum(u8) {
455 ctz,
456 clz,
457 },
458};
459
460fn invalidBuiltin(data: *const InvalidBuiltinData) callconv(.c) noreturn {
461 logMessage(
462 "passing zero to {s}(), which is not a valid argument",
463 .{@tagName(data.kind)},
464 );
465}
466
467const VlaBoundNotPositive = extern struct {
468 loc: SourceLocation,
469 td: *const TypeDescriptor,
470};
471
472fn vlaBoundNotPositive(
473 data: *const VlaBoundNotPositive,
474 bound_handle: ValueHandle,
475) callconv(.c) noreturn {
476 const bound: Value = .{ .handle = bound_handle, .td = data.td };
477 logMessage(
478 "variable length array bound evaluates to non-positive value {}",
479 .{bound},
480 );
481}
482
483const FloatCastOverflowData = extern struct {
484 from: *const TypeDescriptor,
485 to: *const TypeDescriptor,
486};
487
488const FloatCastOverflowDataV2 = extern struct {
489 loc: SourceLocation,
490 from: *const TypeDescriptor,
491 to: *const TypeDescriptor,
492};
493
494fn floatCastOverflow(
495 data_handle: *align(8) const anyopaque,
496 from_handle: ValueHandle,
497) callconv(.c) noreturn {
498 // See: https://github.com/llvm/llvm-project/blob/release/19.x/compiler-rt/lib/ubsan/ubsan_handlers.cpp#L463
499 // for more information on this check.
500 const ptr: [*]const u8 = @ptrCast(data_handle);
501 if (@as(u16, ptr[0]) + @as(u16, ptr[1]) < 2 or ptr[0] == 0xFF or ptr[1] == 0xFF) {
502 const data: *const FloatCastOverflowData = @ptrCast(data_handle);
503 const from_value: Value = .{ .handle = from_handle, .td = data.from };
504 logMessage("{} is outside the range of representable values of type {s}", .{
505 from_value, data.to.getName(),
506 });
507 } else {
508 const data: *const FloatCastOverflowDataV2 = @ptrCast(data_handle);
509 const from_value: Value = .{ .handle = from_handle, .td = data.from };
510 logMessage("{} is outside the range of representable values of type {s}", .{
511 from_value, data.to.getName(),
512 });
513 }
514}
515
516inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
517 std.debug.panicExtra(null, @returnAddress(), fmt, args);
518}
519
520fn exportHandler(
521 handler: anytype,
522 comptime sym_name: []const u8,
523 comptime abort: bool,
524) void {
525 const linkage = if (builtin.is_test) .internal else .weak;
526 {
527 const N = "__ubsan_handle_" ++ sym_name;
528 @export(handler, .{ .name = N, .linkage = linkage });
529 }
530 if (abort) {
531 const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
532 @export(handler, .{ .name = N, .linkage = linkage });
533 }
534}
535
536fn exportMinimal(
537 comptime err_name: []const u8,
538 comptime sym_name: []const u8,
539 comptime abort: bool,
540) void {
541 const S = struct {
542 fn handler() callconv(.c) noreturn {
543 logMessage("{s}", .{err_name});
544 }
545 };
546 const linkage = if (builtin.is_test) .internal else .weak;
547 {
548 const N = "__ubsan_handle_" ++ sym_name ++ "_minimal";
549 @export(&S.handler, .{ .name = N, .linkage = linkage });
550 }
551 if (abort) {
552 const N = "__ubsan_handle_" ++ sym_name ++ "_minimal_abort";
553 @export(&S.handler, .{ .name = N, .linkage = linkage });
554 }
555}
556
557comptime {
558 overflowHandler("add_overflow", "+");
559 overflowHandler("mul_overflow", "*");
560 overflowHandler("sub_overflow", "-");
561 exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true);
562 exportHandler(&builtinUnreachable, "builtin_unreachable", false);
563 exportHandler(&divRemHandler, "divrem_overflow", true);
564 exportHandler(&floatCastOverflow, "float_cast_overflow", true);
565 exportHandler(&invalidBuiltin, "invalid_builtin", true);
566 exportHandler(&loadInvalidValue, "load_invalid_value", true);
567 exportHandler(&missingReturn, "missing_return", false);
568 exportHandler(&negationHandler, "negate_overflow", true);
569 exportHandler(&nonNullArg, "nonnull_arg", true);
570 exportHandler(&nonNullReturn, "nonnull_return_v1", true);
571 exportHandler(&outOfBounds, "out_of_bounds", true);
572 exportHandler(&pointerOverflow, "pointer_overflow", true);
573 exportHandler(&shiftOob, "shift_out_of_bounds", true);
574 exportHandler(&typeMismatch, "type_mismatch_v1", true);
575 exportHandler(&vlaBoundNotPositive, "vla_bound_not_positive", true);
576
577 exportMinimal("add-overflow", "add_overflow", true);
578 exportMinimal("sub-overflow", "sub_overflow", true);
579 exportMinimal("mul-overflow", "mul_overflow", true);
580 exportMinimal("alignment-assumption-handler", "alignment_assumption", true);
581 exportMinimal("builtin-unreachable", "builtin_unreachable", false);
582 exportMinimal("divrem-handler", "divrem_overflow", true);
583 exportMinimal("float-cast-overflow", "float_cast_overflow", true);
584 exportMinimal("invalid-builtin", "invalid_builtin", true);
585 exportMinimal("load-invalid-value", "load_invalid_value", true);
586 exportMinimal("missing-return", "missing_return", true);
587 exportMinimal("negation-handler", "negate_overflow", true);
588 exportMinimal("nonnull-arg", "nonnull_arg", true);
589 exportMinimal("out-of-bounds", "out_of_bounds", true);
590 exportMinimal("pointer-overflow", "pointer_overflow", true);
591 exportMinimal("shift-oob", "shift_out_of_bounds", true);
592 exportMinimal("type-mismatch", "type_mismatch", true);
593 exportMinimal("vla-bound-not-positive", "vla_bound_not_positive", true);
594
595 // these checks are nearly impossible to duplicate in zig, as they rely on nuances
596 // in the Itanium C++ ABI.
597 // exportHelper("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
598 // exportHelper("vptr_type_cache", "vptr-type-cache", true);
599
600 // we disable -fsanitize=function for reasons explained in src/Compilation.zig
601 // exportHelper("function-type-mismatch", "function_type_mismatch", true);
602 // exportHelper("function-type-mismatch-v1", "function_type_mismatch_v1", true);
603}
lib/ubsan_rt.zig created+569
...@@ -0,0 +1,569 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4
5const SourceLocation = extern struct {
6 file_name: ?[*:0]const u8,
7 line: u32,
8 col: u32,
9};
10
11const TypeDescriptor = extern struct {
12 kind: Kind,
13 info: Info,
14 // name: [?:0]u8
15
16 const Kind = enum(u16) {
17 integer = 0x0000,
18 float = 0x0001,
19 unknown = 0xFFFF,
20 };
21
22 const Info = extern union {
23 integer: packed struct(u16) {
24 signed: bool,
25 bit_width: u15,
26 },
27 float: u16,
28 };
29
30 fn getIntegerSize(desc: TypeDescriptor) u64 {
31 assert(desc.kind == .integer);
32 const bit_width = desc.info.integer.bit_width;
33 return @as(u64, 1) << @intCast(bit_width);
34 }
35
36 fn isSigned(desc: TypeDescriptor) bool {
37 return desc.kind == .integer and desc.info.integer.signed;
38 }
39
40 fn getName(desc: *const TypeDescriptor) [:0]const u8 {
41 return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor));
42 }
43};
44
45const ValueHandle = *const opaque {};
46
47const Value = extern struct {
48 td: *const TypeDescriptor,
49 handle: ValueHandle,
50
51 fn getUnsignedInteger(value: Value) u128 {
52 assert(!value.td.isSigned());
53 const size = value.td.getIntegerSize();
54 const max_inline_size = @bitSizeOf(ValueHandle);
55 if (size <= max_inline_size) {
56 return @intFromPtr(value.handle);
57 }
58
59 return switch (size) {
60 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*,
61 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*,
62 else => @trap(),
63 };
64 }
65
66 fn getSignedInteger(value: Value) i128 {
67 assert(value.td.isSigned());
68 const size = value.td.getIntegerSize();
69 const max_inline_size = @bitSizeOf(ValueHandle);
70 if (size <= max_inline_size) {
71 const extra_bits: std.math.Log2Int(usize) = @intCast(max_inline_size - size);
72 const handle: isize = @bitCast(@intFromPtr(value.handle));
73 return (handle << extra_bits) >> extra_bits;
74 }
75 return switch (size) {
76 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*,
77 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*,
78 else => @trap(),
79 };
80 }
81
82 fn getFloat(value: Value) c_longdouble {
83 assert(value.td.kind == .float);
84 const size = value.td.info.float;
85 const max_inline_size = @bitSizeOf(ValueHandle);
86 if (size <= max_inline_size) {
87 return @bitCast(@intFromPtr(value.handle));
88 }
89 return @floatCast(switch (size) {
90 64 => @as(*const f64, @alignCast(@ptrCast(value.handle))).*,
91 80 => @as(*const f80, @alignCast(@ptrCast(value.handle))).*,
92 128 => @as(*const f128, @alignCast(@ptrCast(value.handle))).*,
93 else => @trap(),
94 });
95 }
96
97 fn isMinusOne(value: Value) bool {
98 return value.td.isSigned() and
99 value.getSignedInteger() == -1;
100 }
101
102 fn isNegative(value: Value) bool {
103 return value.td.isSigned() and
104 value.getSignedInteger() < 0;
105 }
106
107 fn getPositiveInteger(value: Value) u128 {
108 if (value.td.isSigned()) {
109 const signed = value.getSignedInteger();
110 assert(signed >= 0);
111 return @intCast(signed);
112 } else {
113 return value.getUnsignedInteger();
114 }
115 }
116
117 pub fn format(
118 value: Value,
119 comptime fmt: []const u8,
120 _: std.fmt.FormatOptions,
121 writer: anytype,
122 ) !void {
123 comptime assert(fmt.len == 0);
124
125 switch (value.td.kind) {
126 .integer => {
127 if (value.td.isSigned()) {
128 try writer.print("{}", .{value.getSignedInteger()});
129 } else {
130 try writer.print("{}", .{value.getUnsignedInteger()});
131 }
132 },
133 .float => try writer.print("{}", .{value.getFloat()}),
134 .unknown => try writer.writeAll("(unknown)"),
135 }
136 }
137};
138
139const OverflowData = extern struct {
140 loc: SourceLocation,
141 td: *const TypeDescriptor,
142};
143
144fn overflowHandler(
145 comptime sym_name: []const u8,
146 comptime operator: []const u8,
147) void {
148 const S = struct {
149 fn handler(
150 data: *const OverflowData,
151 lhs_handle: ValueHandle,
152 rhs_handle: ValueHandle,
153 ) callconv(.c) noreturn {
154 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
155 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
156
157 const is_signed = data.td.isSigned();
158 const fmt = "{s} integer overflow: " ++ "{} " ++
159 operator ++ " {} cannot be represented in type {s}";
160
161 logMessage(fmt, .{
162 if (is_signed) "signed" else "unsigned",
163 lhs,
164 rhs,
165 data.td.getName(),
166 });
167 }
168 };
169
170 exportHandler(&S.handler, sym_name, true);
171}
172
173fn negationHandler(
174 data: *const OverflowData,
175 value_handle: ValueHandle,
176) callconv(.c) noreturn {
177 const value: Value = .{ .handle = value_handle, .td = data.td };
178 logMessage(
179 "negation of {} cannot be represented in type {s}",
180 .{ value, data.td.getName() },
181 );
182}
183
184fn divRemHandler(
185 data: *const OverflowData,
186 lhs_handle: ValueHandle,
187 rhs_handle: ValueHandle,
188) callconv(.c) noreturn {
189 const lhs: Value = .{ .handle = lhs_handle, .td = data.td };
190 const rhs: Value = .{ .handle = rhs_handle, .td = data.td };
191
192 if (rhs.isMinusOne()) {
193 logMessage(
194 "division of {} by -1 cannot be represented in type {s}",
195 .{ lhs, data.td.getName() },
196 );
197 } else logMessage("division by zero", .{});
198}
199
200const AlignmentAssumptionData = extern struct {
201 loc: SourceLocation,
202 assumption_loc: SourceLocation,
203 td: *const TypeDescriptor,
204};
205
206fn alignmentAssumptionHandler(
207 data: *const AlignmentAssumptionData,
208 pointer: ValueHandle,
209 alignment_handle: ValueHandle,
210 maybe_offset: ?ValueHandle,
211) callconv(.c) noreturn {
212 const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
213 const lsb = @ctz(real_pointer);
214 const actual_alignment = @as(u64, 1) << @intCast(lsb);
215 const mask = @intFromPtr(alignment_handle) - 1;
216 const misalignment_offset = real_pointer & mask;
217 const alignment: Value = .{ .handle = alignment_handle, .td = data.td };
218
219 if (maybe_offset) |offset| {
220 logMessage(
221 "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed\n" ++
222 "offset address is {} aligned, misalignment offset is {} bytes",
223 .{
224 alignment,
225 @intFromPtr(offset),
226 data.td.getName(),
227 actual_alignment,
228 misalignment_offset,
229 },
230 );
231 } else {
232 logMessage(
233 "assumption of {} byte alignment for pointer of type {s} failed\n" ++
234 "address is {} aligned, misalignment offset is {} bytes",
235 .{
236 alignment,
237 data.td.getName(),
238 actual_alignment,
239 misalignment_offset,
240 },
241 );
242 }
243}
244
245const ShiftOobData = extern struct {
246 loc: SourceLocation,
247 lhs_type: *const TypeDescriptor,
248 rhs_type: *const TypeDescriptor,
249};
250
251fn shiftOob(
252 data: *const ShiftOobData,
253 lhs_handle: ValueHandle,
254 rhs_handle: ValueHandle,
255) callconv(.c) noreturn {
256 const lhs: Value = .{ .handle = lhs_handle, .td = data.lhs_type };
257 const rhs: Value = .{ .handle = rhs_handle, .td = data.rhs_type };
258
259 if (rhs.isNegative() or
260 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
261 {
262 if (rhs.isNegative()) {
263 logMessage("shift exponent {} is negative", .{rhs});
264 } else {
265 logMessage(
266 "shift exponent {} is too large for {}-bit type {s}",
267 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
268 );
269 }
270 } else {
271 if (lhs.isNegative()) {
272 logMessage("left shift of negative value {}", .{lhs});
273 } else {
274 logMessage(
275 "left shift of {} by {} places cannot be represented in type {s}",
276 .{ lhs, rhs, data.lhs_type.getName() },
277 );
278 }
279 }
280}
281
282const OutOfBoundsData = extern struct {
283 loc: SourceLocation,
284 array_type: *const TypeDescriptor,
285 index_type: *const TypeDescriptor,
286};
287
288fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.c) noreturn {
289 const index: Value = .{ .handle = index_handle, .td = data.index_type };
290 logMessage(
291 "index {} out of bounds for type {s}",
292 .{ index, data.array_type.getName() },
293 );
294}
295
296const PointerOverflowData = extern struct {
297 loc: SourceLocation,
298};
299
300fn pointerOverflow(
301 _: *const PointerOverflowData,
302 base: usize,
303 result: usize,
304) callconv(.c) noreturn {
305 if (base == 0) {
306 if (result == 0) {
307 logMessage("applying zero offset to null pointer", .{});
308 } else {
309 logMessage("applying non-zero offset {} to null pointer", .{result});
310 }
311 } else {
312 if (result == 0) {
313 logMessage(
314 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
315 .{base},
316 );
317 } else {
318 const signed_base: isize = @bitCast(base);
319 const signed_result: isize = @bitCast(result);
320 if ((signed_base >= 0) == (signed_result >= 0)) {
321 if (base > result) {
322 logMessage(
323 "addition of unsigned offset to 0x{x} overflowed to 0x{x}",
324 .{ base, result },
325 );
326 } else {
327 logMessage(
328 "subtraction of unsigned offset to 0x{x} overflowed to 0x{x}",
329 .{ base, result },
330 );
331 }
332 } else {
333 logMessage(
334 "pointer index expression with base 0x{x} overflowed to 0x{x}",
335 .{ base, result },
336 );
337 }
338 }
339 }
340}
341
342const TypeMismatchData = extern struct {
343 loc: SourceLocation,
344 td: *const TypeDescriptor,
345 log_alignment: u8,
346 kind: enum(u8) {
347 load,
348 store,
349 reference_binding,
350 member_access,
351 member_call,
352 constructor_call,
353 downcast_pointer,
354 downcast_reference,
355 upcast,
356 upcast_to_virtual_base,
357 nonnull_assign,
358 dynamic_operation,
359
360 fn getName(kind: @This()) []const u8 {
361 return switch (kind) {
362 .load => "load of",
363 .store => "store of",
364 .reference_binding => "reference binding to",
365 .member_access => "member access within",
366 .member_call => "member call on",
367 .constructor_call => "constructor call on",
368 .downcast_pointer, .downcast_reference => "downcast of",
369 .upcast => "upcast of",
370 .upcast_to_virtual_base => "cast to virtual base of",
371 .nonnull_assign => "_Nonnull binding to",
372 .dynamic_operation => "dynamic operation on",
373 };
374 }
375 },
376};
377
378fn typeMismatch(
379 data: *const TypeMismatchData,
380 pointer: ?ValueHandle,
381) callconv(.c) noreturn {
382 const alignment = @as(usize, 1) << @intCast(data.log_alignment);
383 const handle: usize = @intFromPtr(pointer);
384
385 if (pointer == null) {
386 logMessage(
387 "{s} null pointer of type {s}",
388 .{ data.kind.getName(), data.td.getName() },
389 );
390 } else if (!std.mem.isAligned(handle, alignment)) {
391 logMessage(
392 "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment",
393 .{ data.kind.getName(), handle, data.td.getName(), alignment },
394 );
395 } else {
396 logMessage(
397 "{s} address 0x{x} with insufficient space for an object of type {s}",
398 .{ data.kind.getName(), handle, data.td.getName() },
399 );
400 }
401}
402
403const UnreachableData = extern struct {
404 loc: SourceLocation,
405};
406
407fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn {
408 logMessage("execution reached an unreachable program point", .{});
409}
410
411fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn {
412 logMessage("execution reached the end of a value-returning function without returning a value", .{});
413}
414
415const NonNullReturnData = extern struct {
416 attribute_loc: SourceLocation,
417};
418
419fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn {
420 logMessage("null pointer returned from function declared to never return null", .{});
421}
422
423const NonNullArgData = extern struct {
424 loc: SourceLocation,
425 attribute_loc: SourceLocation,
426 arg_index: i32,
427};
428
429fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn {
430 logMessage(
431 "null pointer passed as argument {}, which is declared to never be null",
432 .{data.arg_index},
433 );
434}
435
436const InvalidValueData = extern struct {
437 loc: SourceLocation,
438 td: *const TypeDescriptor,
439};
440
441fn loadInvalidValue(
442 data: *const InvalidValueData,
443 value_handle: ValueHandle,
444) callconv(.c) noreturn {
445 const value: Value = .{ .handle = value_handle, .td = data.td };
446 logMessage(
447 "load of value {}, which is not valid for type {s}",
448 .{ value, data.td.getName() },
449 );
450}
451
452const InvalidBuiltinData = extern struct {
453 loc: SourceLocation,
454 kind: enum(u8) {
455 ctz,
456 clz,
457 },
458};
459
460fn invalidBuiltin(data: *const InvalidBuiltinData) callconv(.c) noreturn {
461 logMessage(
462 "passing zero to {s}(), which is not a valid argument",
463 .{@tagName(data.kind)},
464 );
465}
466
467const VlaBoundNotPositive = extern struct {
468 loc: SourceLocation,
469 td: *const TypeDescriptor,
470};
471
472fn vlaBoundNotPositive(
473 data: *const VlaBoundNotPositive,
474 bound_handle: ValueHandle,
475) callconv(.c) noreturn {
476 const bound: Value = .{ .handle = bound_handle, .td = data.td };
477 logMessage(
478 "variable length array bound evaluates to non-positive value {}",
479 .{bound},
480 );
481}
482
483const FloatCastOverflowData = extern struct {
484 from: *const TypeDescriptor,
485 to: *const TypeDescriptor,
486};
487
488const FloatCastOverflowDataV2 = extern struct {
489 loc: SourceLocation,
490 from: *const TypeDescriptor,
491 to: *const TypeDescriptor,
492};
493
494fn floatCastOverflow(
495 data_handle: *align(8) const anyopaque,
496 from_handle: ValueHandle,
497) callconv(.c) noreturn {
498 // See: https://github.com/llvm/llvm-project/blob/release/19.x/compiler-rt/lib/ubsan/ubsan_handlers.cpp#L463
499 // for more information on this check.
500 const ptr: [*]const u8 = @ptrCast(data_handle);
501 if (@as(u16, ptr[0]) + @as(u16, ptr[1]) < 2 or ptr[0] == 0xFF or ptr[1] == 0xFF) {
502 const data: *const FloatCastOverflowData = @ptrCast(data_handle);
503 const from_value: Value = .{ .handle = from_handle, .td = data.from };
504 logMessage("{} is outside the range of representable values of type {s}", .{
505 from_value, data.to.getName(),
506 });
507 } else {
508 const data: *const FloatCastOverflowDataV2 = @ptrCast(data_handle);
509 const from_value: Value = .{ .handle = from_handle, .td = data.from };
510 logMessage("{} is outside the range of representable values of type {s}", .{
511 from_value, data.to.getName(),
512 });
513 }
514}
515
516inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
517 std.debug.panicExtra(null, @returnAddress(), fmt, args);
518}
519
520fn exportHandler(
521 handler: anytype,
522 comptime sym_name: []const u8,
523 comptime abort: bool,
524) void {
525 const linkage = if (builtin.is_test) .internal else .weak;
526 {
527 const N = "__ubsan_handle_" ++ sym_name;
528 @export(handler, .{ .name = N, .linkage = linkage });
529 }
530 if (abort) {
531 const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
532 @export(handler, .{ .name = N, .linkage = linkage });
533 }
534}
535
536const can_build_ubsan = switch (builtin.zig_backend) {
537 .stage2_riscv64 => false,
538 else => true,
539};
540
541comptime {
542 overflowHandler("add_overflow", "+");
543 overflowHandler("mul_overflow", "*");
544 overflowHandler("sub_overflow", "-");
545 exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true);
546 exportHandler(&builtinUnreachable, "builtin_unreachable", false);
547 exportHandler(&divRemHandler, "divrem_overflow", true);
548 exportHandler(&floatCastOverflow, "float_cast_overflow", true);
549 exportHandler(&invalidBuiltin, "invalid_builtin", true);
550 exportHandler(&loadInvalidValue, "load_invalid_value", true);
551 exportHandler(&missingReturn, "missing_return", false);
552 exportHandler(&negationHandler, "negate_overflow", true);
553 exportHandler(&nonNullArg, "nonnull_arg", true);
554 exportHandler(&nonNullReturn, "nonnull_return_v1", true);
555 exportHandler(&outOfBounds, "out_of_bounds", true);
556 exportHandler(&pointerOverflow, "pointer_overflow", true);
557 exportHandler(&shiftOob, "shift_out_of_bounds", true);
558 exportHandler(&typeMismatch, "type_mismatch_v1", true);
559 exportHandler(&vlaBoundNotPositive, "vla_bound_not_positive", true);
560
561 // these checks are nearly impossible to duplicate in zig, as they rely on nuances
562 // in the Itanium C++ ABI.
563 // exportHelper("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
564 // exportHelper("vptr_type_cache", "vptr-type-cache", true);
565
566 // we disable -fsanitize=function for reasons explained in src/Compilation.zig
567 // exportHelper("function-type-mismatch", "function_type_mismatch", true);
568 // exportHelper("function-type-mismatch-v1", "function_type_mismatch_v1", true);
569}
src/Compilation.zig+57-24
...@@ -78,8 +78,8 @@ implib_emit: ?Path,...@@ -78,8 +78,8 @@ implib_emit: ?Path,
78/// This is non-null when `-femit-docs` is provided.78/// This is non-null when `-femit-docs` is provided.
79docs_emit: ?Path,79docs_emit: ?Path,
80root_name: [:0]const u8,80root_name: [:0]const u8,
81include_compiler_rt: bool,81compiler_rt_strat: RtStrat,
82include_ubsan_rt: bool,82ubsan_rt_strat: RtStrat,
83/// Resolved into known paths, any GNU ld scripts already resolved.83/// Resolved into known paths, any GNU ld scripts already resolved.
84link_inputs: []const link.Input,84link_inputs: []const link.Input,
85/// Needed only for passing -F args to clang.85/// Needed only for passing -F args to clang.
...@@ -1256,6 +1256,8 @@ fn addModuleTableToCacheHash(...@@ -1256,6 +1256,8 @@ fn addModuleTableToCacheHash(
1256 }1256 }
1257}1257}
12581258
1259const RtStrat = enum { none, lib, obj, zcu };
1260
1259pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compilation {1261pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compilation {
1260 const output_mode = options.config.output_mode;1262 const output_mode = options.config.output_mode;
1261 const is_dyn_lib = switch (output_mode) {1263 const is_dyn_lib = switch (output_mode) {
...@@ -1287,6 +1289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1287,6 +1289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1287 const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;1289 const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;
1288 const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;1290 const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;
1289 const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread;1291 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;
1290 const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;1293 const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;
12911294
1292 const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables;1295 const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables;
...@@ -1305,13 +1308,25 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1305,13 +1308,25 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
13051308
1306 const sysroot = options.sysroot orelse libc_dirs.sysroot;1309 const sysroot = options.sysroot orelse libc_dirs.sysroot;
13071310
1308 const include_compiler_rt = options.want_compiler_rt orelse1311 const compiler_rt_strat: RtStrat = s: {
1309 (!options.skip_linker_dependencies and is_exe_or_dyn_lib);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 };
13101319
1311 const include_ubsan_rt = options.want_ubsan_rt orelse1320 const ubsan_rt_strat: RtStrat = s: {
1312 (!options.skip_linker_dependencies and is_exe_or_dyn_lib and !have_zcu);1321 const want_ubsan_rt = options.want_ubsan_rt orelse (any_sanitize_c and output_mode != .Obj);
1322 if (!want_ubsan_rt) break :s .none;
1323 if (options.skip_linker_dependencies) break :s .none;
1324 if (have_zcu) break :s .zcu;
1325 if (is_exe_or_dyn_lib) break :s .lib;
1326 break :s .obj;
1327 };
13131328
1314 if (include_compiler_rt and output_mode == .Obj) {1329 if (compiler_rt_strat == .zcu) {
1315 // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");`1330 // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");`
1316 // injected into the object.1331 // injected into the object.
1317 const compiler_rt_mod = try Package.Module.create(arena, .{1332 const compiler_rt_mod = try Package.Module.create(arena, .{
...@@ -1340,7 +1355,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1340,7 +1355,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1340 // unlike compiler_rt, we always want to go through the `_ = @import("ubsan-rt")`1355 // unlike compiler_rt, we always want to go through the `_ = @import("ubsan-rt")`
1341 // approach, since the ubsan runtime uses quite a lot of the standard library1356 // approach, since the ubsan runtime uses quite a lot of the standard library
1342 // and this reduces unnecessary bloat.1357 // and this reduces unnecessary bloat.
1343 if (!options.skip_linker_dependencies and have_zcu) {1358 if (ubsan_rt_strat == .zcu) {
1344 const ubsan_rt_mod = try Package.Module.create(arena, .{1359 const ubsan_rt_mod = try Package.Module.create(arena, .{
1345 .global_cache_directory = options.global_cache_directory,1360 .global_cache_directory = options.global_cache_directory,
1346 .paths = .{1361 .paths = .{
...@@ -1536,8 +1551,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1536,8 +1551,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1536 .windows_libs = windows_libs,1551 .windows_libs = windows_libs,
1537 .version = options.version,1552 .version = options.version,
1538 .libc_installation = libc_dirs.libc_installation,1553 .libc_installation = libc_dirs.libc_installation,
1539 .include_compiler_rt = include_compiler_rt,1554 .compiler_rt_strat = compiler_rt_strat,
1540 .include_ubsan_rt = include_ubsan_rt,1555 .ubsan_rt_strat = ubsan_rt_strat,
1541 .link_inputs = options.link_inputs,1556 .link_inputs = options.link_inputs,
1542 .framework_dirs = options.framework_dirs,1557 .framework_dirs = options.framework_dirs,
1543 .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit,1558 .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit,
...@@ -1563,6 +1578,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1563,6 +1578,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1563 comp.config.any_unwind_tables = any_unwind_tables;1578 comp.config.any_unwind_tables = any_unwind_tables;
1564 comp.config.any_non_single_threaded = any_non_single_threaded;1579 comp.config.any_non_single_threaded = any_non_single_threaded;
1565 comp.config.any_sanitize_thread = any_sanitize_thread;1580 comp.config.any_sanitize_thread = any_sanitize_thread;
1581 comp.config.any_sanitize_c = any_sanitize_c;
1566 comp.config.any_fuzz = any_fuzz;1582 comp.config.any_fuzz = any_fuzz;
15671583
1568 const lf_open_opts: link.File.OpenOptions = .{1584 const lf_open_opts: link.File.OpenOptions = .{
...@@ -1909,34 +1925,51 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1909,34 +1925,51 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1909 comp.remaining_prelink_tasks += 1;1925 comp.remaining_prelink_tasks += 1;
1910 }1926 }
19111927
1928<<<<<<< HEAD
1912 if (comp.include_compiler_rt and capable_of_building_compiler_rt) {1929 if (comp.include_compiler_rt and capable_of_building_compiler_rt) {
1913 if (is_exe_or_dyn_lib) {1930 if (is_exe_or_dyn_lib) {
1931=======
1932 if (target.isMinGW() and comp.config.any_non_single_threaded) {
1933 // LLD might drop some symbols as unused during LTO and GCing, therefore,
1934 // we force mark them for resolution here.
1935
1936 const tls_index_sym = switch (target.cpu.arch) {
1937 .x86 => "__tls_index",
1938 else => "_tls_index",
1939 };
1940
1941 try comp.force_undefined_symbols.put(comp.gpa, tls_index_sym, {});
1942 }
1943
1944 if (capable_of_building_compiler_rt) {
1945 if (comp.compiler_rt_strat == .lib) {
1946>>>>>>> 050e3e69ac (Compilation: correct when to include ubsan)
1914 log.debug("queuing a job to build compiler_rt_lib", .{});1947 log.debug("queuing a job to build compiler_rt_lib", .{});
1915 comp.queued_jobs.compiler_rt_lib = true;1948 comp.queued_jobs.compiler_rt_lib = true;
1916 comp.remaining_prelink_tasks += 1;1949 comp.remaining_prelink_tasks += 1;
1917 } else if (output_mode != .Obj) {1950 } else if (comp.compiler_rt_strat == .obj) {
1918 log.debug("queuing a job to build compiler_rt_obj", .{});1951 log.debug("queuing a job to build compiler_rt_obj", .{});
1919 // In this case we are making a static library, so we ask1952 // In this case we are making a static library, so we ask
1920 // for a compiler-rt object to put in it.1953 // for a compiler-rt object to put in it.
1921 comp.queued_jobs.compiler_rt_obj = true;1954 comp.queued_jobs.compiler_rt_obj = true;
1922 comp.remaining_prelink_tasks += 1;1955 comp.remaining_prelink_tasks += 1;
1923 }1956 }
1924 }
19251957
1926 if (comp.include_ubsan_rt and capable_of_building_compiler_rt) {1958 if (comp.ubsan_rt_strat == .lib) {
1927 if (is_exe_or_dyn_lib) {
1928 log.debug("queuing a job to build ubsan_rt_lib", .{});1959 log.debug("queuing a job to build ubsan_rt_lib", .{});
1929 comp.job_queued_ubsan_rt_lib = true;1960 comp.job_queued_ubsan_rt_lib = true;
1930 } else if (output_mode != .Obj) {1961 comp.remaining_prelink_tasks += 1;
1962 } else if (comp.ubsan_rt_strat == .obj) {
1931 log.debug("queuing a job to build ubsan_rt_obj", .{});1963 log.debug("queuing a job to build ubsan_rt_obj", .{});
1932 comp.job_queued_ubsan_rt_obj = true;1964 comp.job_queued_ubsan_rt_obj = true;
1965 comp.remaining_prelink_tasks += 1;
1933 }1966 }
1934 }
19351967
1936 if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) {1968 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
1937 log.debug("queuing a job to build libfuzzer", .{});1969 log.debug("queuing a job to build libfuzzer", .{});
1938 comp.queued_jobs.fuzzer_lib = true;1970 comp.queued_jobs.fuzzer_lib = true;
1939 comp.remaining_prelink_tasks += 1;1971 comp.remaining_prelink_tasks += 1;
1972 }
1940 }1973 }
1941 }1974 }
19421975
...@@ -2656,8 +2689,8 @@ fn addNonIncrementalStuffToCacheManifest(...@@ -2656,8 +2689,8 @@ fn addNonIncrementalStuffToCacheManifest(
2656 man.hash.addOptional(comp.version);2689 man.hash.addOptional(comp.version);
2657 man.hash.add(comp.link_eh_frame_hdr);2690 man.hash.add(comp.link_eh_frame_hdr);
2658 man.hash.add(comp.skip_linker_dependencies);2691 man.hash.add(comp.skip_linker_dependencies);
2659 man.hash.add(comp.include_compiler_rt);2692 man.hash.add(comp.compiler_rt_strat);
2660 man.hash.add(comp.include_ubsan_rt);2693 man.hash.add(comp.ubsan_rt_strat);
2661 man.hash.add(comp.rc_includes);2694 man.hash.add(comp.rc_includes);
2662 man.hash.addListOfBytes(comp.force_undefined_symbols.keys());2695 man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
2663 man.hash.addListOfBytes(comp.framework_dirs);2696 man.hash.addListOfBytes(comp.framework_dirs);
...@@ -3749,11 +3782,11 @@ fn performAllTheWorkInner(...@@ -3749,11 +3782,11 @@ fn performAllTheWorkInner(
3749 }3782 }
37503783
3751 if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {3784 if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {
3752 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan.zig", .libubsan, .Lib, &comp.ubsan_rt_lib, main_progress_node });3785 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Lib, &comp.ubsan_rt_lib, main_progress_node });
3753 }3786 }
37543787
3755 if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {3788 if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {
3756 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan.zig", .libubsan, .Obj, &comp.ubsan_rt_obj, main_progress_node });3789 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Obj, &comp.ubsan_rt_obj, main_progress_node });
3757 }3790 }
37583791
3759 if (comp.queued_jobs.glibc_shared_objects) {3792 if (comp.queued_jobs.glibc_shared_objects) {
src/Compilation/Config.zig+3
...@@ -32,6 +32,7 @@ any_non_single_threaded: bool,...@@ -32,6 +32,7 @@ any_non_single_threaded: bool,
32/// per-Module setting.32/// per-Module setting.
33any_error_tracing: bool,33any_error_tracing: bool,
34any_sanitize_thread: bool,34any_sanitize_thread: bool,
35any_sanitize_c: bool,
35any_fuzz: bool,36any_fuzz: bool,
36pie: bool,37pie: bool,
37/// If this is true then linker code is responsible for making an LLVM IR38/// If this is true then linker code is responsible for making an LLVM IR
...@@ -87,6 +88,7 @@ pub const Options = struct {...@@ -87,6 +88,7 @@ pub const Options = struct {
87 ensure_libcpp_on_non_freestanding: bool = false,88 ensure_libcpp_on_non_freestanding: bool = false,
88 any_non_single_threaded: bool = false,89 any_non_single_threaded: bool = false,
89 any_sanitize_thread: bool = false,90 any_sanitize_thread: bool = false,
91 any_sanitize_c: bool = false,
90 any_fuzz: bool = false,92 any_fuzz: bool = false,
91 any_unwind_tables: bool = false,93 any_unwind_tables: bool = false,
92 any_dyn_libs: bool = false,94 any_dyn_libs: bool = false,
...@@ -476,6 +478,7 @@ pub fn resolve(options: Options) ResolveError!Config {...@@ -476,6 +478,7 @@ pub fn resolve(options: Options) ResolveError!Config {
476 .any_non_single_threaded = options.any_non_single_threaded,478 .any_non_single_threaded = options.any_non_single_threaded,
477 .any_error_tracing = any_error_tracing,479 .any_error_tracing = any_error_tracing,
478 .any_sanitize_thread = options.any_sanitize_thread,480 .any_sanitize_thread = options.any_sanitize_thread,
481 .any_sanitize_c = options.any_sanitize_c,
479 .any_fuzz = options.any_fuzz,482 .any_fuzz = options.any_fuzz,
480 .san_cov_trace_pc_guard = options.san_cov_trace_pc_guard,483 .san_cov_trace_pc_guard = options.san_cov_trace_pc_guard,
481 .root_error_tracing = root_error_tracing,484 .root_error_tracing = root_error_tracing,
src/link.zig+2-2
...@@ -1102,12 +1102,12 @@ pub const File = struct {...@@ -1102,12 +1102,12 @@ pub const File = struct {
11021102
1103 log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"});1103 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)
1106 comp.compiler_rt_obj.?.full_object_path1106 comp.compiler_rt_obj.?.full_object_path
1107 else1107 else
1108 null;1108 null;
11091109
1110 const ubsan_rt_path: ?Path = if (comp.include_ubsan_rt)1110 const ubsan_rt_path: ?Path = if (comp.ubsan_rt_strat == .obj)
1111 comp.ubsan_rt_obj.?.full_object_path1111 comp.ubsan_rt_obj.?.full_object_path
1112 else1112 else
1113 null;1113 null;
src/link/MachO/relocatable.zig+2-2
...@@ -93,11 +93,11 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?...@@ -93,11 +93,11 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
9393
94 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));94 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) {
97 try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path));97 try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path));
98 }98 }
9999
100 if (comp.include_ubsan_rt) {100 if (comp.ubsan_rt_strat == .obj) {
101 try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path));101 try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path));
102 }102 }
103103