authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2024-12-25 21:10:02-08:00
committergravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2025-02-25 11:22:33-08:00
log95720f007bb0dacc8a7cecb621c322d574c61d00
treee3746e6d83a709cd86f93eaf7e59578007466d88
parentbabee5f73c01c220e3fb3901eb1a70149f94258b

move libubsan to `lib/` and integrate it into `-fubsan-rt`


11 files changed, 655 insertions(+), 512 deletions(-)

lib/std/std.zig-1
......@@ -44,7 +44,6 @@ pub const Thread = @import("Thread.zig");
4444pub const Treap = @import("treap.zig").Treap;
4545pub const Tz = tz.Tz;
4646pub const Uri = @import("Uri.zig");
47pub const ubsan = @import("ubsan.zig");
4847
4948pub const array_hash_map = @import("array_hash_map.zig");
5049pub const atomic = @import("atomic.zig");
lib/std/ubsan.zig deleted-509
......@@ -1,509 +0,0 @@
1//! Minimal UBSan Runtime
2
3const std = @import("std");
4const builtin = @import("builtin");
5const assert = std.debug.assert;
6
7const SourceLocation = extern struct {
8 file_name: ?[*:0]const u8,
9 line: u32,
10 col: u32,
11};
12
13const TypeDescriptor = extern struct {
14 kind: Kind,
15 info: Info,
16 // name: [?:0]u8
17
18 const Kind = enum(u16) {
19 integer = 0x0000,
20 float = 0x0001,
21 unknown = 0xFFFF,
22 };
23
24 const Info = extern union {
25 integer: packed struct(u16) {
26 signed: bool,
27 bit_width: u15,
28 },
29 };
30
31 fn getIntegerSize(desc: TypeDescriptor) u64 {
32 assert(desc.kind == .integer);
33 const bit_width = desc.info.integer.bit_width;
34 return @as(u64, 1) << @intCast(bit_width);
35 }
36
37 fn isSigned(desc: TypeDescriptor) bool {
38 return desc.kind == .integer and desc.info.integer.signed;
39 }
40
41 fn getName(desc: *const TypeDescriptor) [:0]const u8 {
42 return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor));
43 }
44};
45
46const ValueHandle = *const opaque {
47 fn getValue(handle: ValueHandle, data: anytype) Value {
48 return .{ .handle = handle, .type_descriptor = data.type_descriptor };
49 }
50};
51
52const Value = extern struct {
53 type_descriptor: *const TypeDescriptor,
54 handle: ValueHandle,
55
56 fn getUnsignedInteger(value: Value) u128 {
57 assert(!value.type_descriptor.isSigned());
58 const size = value.type_descriptor.getIntegerSize();
59 const max_inline_size = @bitSizeOf(ValueHandle);
60 if (size <= max_inline_size) {
61 return @intFromPtr(value.handle);
62 }
63
64 return switch (size) {
65 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*,
66 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*,
67 else => unreachable,
68 };
69 }
70
71 fn getSignedInteger(value: Value) i128 {
72 assert(value.type_descriptor.isSigned());
73 const size = value.type_descriptor.getIntegerSize();
74 const max_inline_size = @bitSizeOf(ValueHandle);
75 if (size <= max_inline_size) {
76 const extra_bits: u6 = @intCast(max_inline_size - size);
77 const handle: i64 = @bitCast(@intFromPtr(value.handle));
78 return (handle << extra_bits) >> extra_bits;
79 }
80 return switch (size) {
81 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*,
82 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*,
83 else => unreachable,
84 };
85 }
86
87 fn isMinusOne(value: Value) bool {
88 return value.type_descriptor.isSigned() and
89 value.getSignedInteger() == -1;
90 }
91
92 fn isNegative(value: Value) bool {
93 return value.type_descriptor.isSigned() and
94 value.getSignedInteger() < 0;
95 }
96
97 fn getPositiveInteger(value: Value) u128 {
98 if (value.type_descriptor.isSigned()) {
99 const signed = value.getSignedInteger();
100 assert(signed >= 0);
101 return @intCast(signed);
102 } else {
103 return value.getUnsignedInteger();
104 }
105 }
106
107 pub fn format(
108 value: Value,
109 comptime fmt: []const u8,
110 _: std.fmt.FormatOptions,
111 writer: anytype,
112 ) !void {
113 comptime assert(fmt.len == 0);
114
115 switch (value.type_descriptor.kind) {
116 .integer => {
117 if (value.type_descriptor.isSigned()) {
118 try writer.print("{}", .{value.getSignedInteger()});
119 } else {
120 try writer.print("{}", .{value.getUnsignedInteger()});
121 }
122 },
123 .float => @panic("TODO: write float"),
124 .unknown => try writer.writeAll("(unknown)"),
125 }
126 }
127};
128
129const OverflowData = extern struct {
130 loc: SourceLocation,
131 type_descriptor: *const TypeDescriptor,
132};
133
134fn overflowHandler(
135 comptime sym_name: []const u8,
136 comptime operator: []const u8,
137) void {
138 const S = struct {
139 fn handler(
140 data: *OverflowData,
141 lhs_handle: ValueHandle,
142 rhs_handle: ValueHandle,
143 ) callconv(.c) noreturn {
144 const lhs = lhs_handle.getValue(data);
145 const rhs = rhs_handle.getValue(data);
146
147 const is_signed = data.type_descriptor.isSigned();
148 const fmt = "{s} integer overflow: " ++ "{} " ++
149 operator ++ " {} cannot be represented in type {s}";
150
151 logMessage(fmt, .{
152 if (is_signed) "signed" else "unsigned",
153 lhs,
154 rhs,
155 data.type_descriptor.getName(),
156 });
157 }
158 };
159
160 exportHandler(&S.handler, sym_name, true);
161}
162
163fn negationHandler(
164 data: *const OverflowData,
165 old_value_handle: ValueHandle,
166) callconv(.c) noreturn {
167 const old_value = old_value_handle.getValue(data);
168 logMessage(
169 "negation of {} cannot be represented in type {s}",
170 .{ old_value, data.type_descriptor.getName() },
171 );
172}
173
174fn divRemHandler(
175 data: *const OverflowData,
176 lhs_handle: ValueHandle,
177 rhs_handle: ValueHandle,
178) callconv(.c) noreturn {
179 const is_signed = data.type_descriptor.isSigned();
180 const lhs = lhs_handle.getValue(data);
181 const rhs = rhs_handle.getValue(data);
182
183 if (is_signed and rhs.getSignedInteger() == -1) {
184 logMessage(
185 "division of {} by -1 cannot be represented in type {s}",
186 .{ lhs, data.type_descriptor.getName() },
187 );
188 } else logMessage("division by zero", .{});
189}
190
191const AlignmentAssumptionData = extern struct {
192 loc: SourceLocation,
193 assumption_loc: SourceLocation,
194 type_descriptor: *const TypeDescriptor,
195};
196
197fn alignmentAssumptionHandler(
198 data: *const AlignmentAssumptionData,
199 pointer: ValueHandle,
200 alignment: ValueHandle,
201 maybe_offset: ?ValueHandle,
202) callconv(.c) noreturn {
203 _ = pointer;
204 // TODO: add the hint here?
205 // const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
206 // const lsb = @ctz(real_pointer);
207 // const actual_alignment = @as(u64, 1) << @intCast(lsb);
208 // const mask = @intFromPtr(alignment) - 1;
209 // const misalignment_offset = real_pointer & mask;
210 // _ = actual_alignment;
211 // _ = misalignment_offset;
212
213 if (maybe_offset) |offset| {
214 logMessage(
215 "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed",
216 .{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() },
217 );
218 } else {
219 logMessage(
220 "assumption of {} byte alignment for pointer of type {s} failed",
221 .{ alignment.getValue(data), data.type_descriptor.getName() },
222 );
223 }
224}
225
226const ShiftOobData = extern struct {
227 loc: SourceLocation,
228 lhs_type: *const TypeDescriptor,
229 rhs_type: *const TypeDescriptor,
230};
231
232fn shiftOob(
233 data: *const ShiftOobData,
234 lhs_handle: ValueHandle,
235 rhs_handle: ValueHandle,
236) callconv(.c) noreturn {
237 const lhs: Value = .{ .handle = lhs_handle, .type_descriptor = data.lhs_type };
238 const rhs: Value = .{ .handle = rhs_handle, .type_descriptor = data.rhs_type };
239
240 if (rhs.isNegative() or
241 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
242 {
243 if (rhs.isNegative()) {
244 logMessage("shift exponent {} is negative", .{rhs});
245 } else {
246 logMessage(
247 "shift exponent {} is too large for {}-bit type {s}",
248 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
249 );
250 }
251 } else {
252 if (lhs.isNegative()) {
253 logMessage("left shift of negative value {}", .{lhs});
254 } else {
255 logMessage(
256 "left shift of {} by {} places cannot be represented in type {s}",
257 .{ lhs, rhs, data.lhs_type.getName() },
258 );
259 }
260 }
261}
262
263const OutOfBoundsData = extern struct {
264 loc: SourceLocation,
265 array_type: *const TypeDescriptor,
266 index_type: *const TypeDescriptor,
267};
268
269fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.c) noreturn {
270 const index: Value = .{ .handle = index_handle, .type_descriptor = data.index_type };
271 logMessage(
272 "index {} out of bounds for type {s}",
273 .{ index, data.array_type.getName() },
274 );
275}
276
277const PointerOverflowData = extern struct {
278 loc: SourceLocation,
279};
280
281fn pointerOverflow(
282 _: *const PointerOverflowData,
283 base: usize,
284 result: usize,
285) callconv(.c) noreturn {
286 if (base == 0) {
287 if (result == 0) {
288 logMessage("applying zero offset to null pointer", .{});
289 } else {
290 logMessage("applying non-zero offset {} to null pointer", .{result});
291 }
292 } else {
293 if (result == 0) {
294 logMessage(
295 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
296 .{base},
297 );
298 } else {
299 @panic("TODO");
300 }
301 }
302}
303
304const TypeMismatchData = extern struct {
305 loc: SourceLocation,
306 type_descriptor: *const TypeDescriptor,
307 log_alignment: u8,
308 kind: enum(u8) {
309 load,
310 store,
311 reference_binding,
312 member_access,
313 member_call,
314 constructor_call,
315 downcast_pointer,
316 downcast_reference,
317 upcast,
318 upcast_to_virtual_base,
319 nonnull_assign,
320 dynamic_operation,
321
322 fn getName(kind: @This()) []const u8 {
323 return switch (kind) {
324 .load => "load of",
325 .store => "store of",
326 .reference_binding => "reference binding to",
327 .member_access => "member access within",
328 .member_call => "member call on",
329 .constructor_call => "constructor call on",
330 .downcast_pointer, .downcast_reference => "downcast of",
331 .upcast => "upcast of",
332 .upcast_to_virtual_base => "cast to virtual base of",
333 .nonnull_assign => "_Nonnull binding to",
334 .dynamic_operation => "dynamic operation on",
335 };
336 }
337 },
338};
339
340fn typeMismatch(
341 data: *const TypeMismatchData,
342 pointer: ?ValueHandle,
343) callconv(.c) noreturn {
344 const alignment = @as(usize, 1) << @intCast(data.log_alignment);
345 const handle: usize = @intFromPtr(pointer);
346
347 if (pointer == null) {
348 logMessage(
349 "{s} null pointer of type {s}",
350 .{ data.kind.getName(), data.type_descriptor.getName() },
351 );
352 } else if (!std.mem.isAligned(handle, alignment)) {
353 logMessage(
354 "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment",
355 .{ data.kind.getName(), handle, data.type_descriptor.getName(), alignment },
356 );
357 } else {
358 logMessage(
359 "{s} address 0x{x} with insufficient space for an object of type {s}",
360 .{ data.kind.getName(), handle, data.type_descriptor.getName() },
361 );
362 }
363}
364
365const UnreachableData = extern struct {
366 loc: SourceLocation,
367};
368
369fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn {
370 logMessage("execution reached an unreachable program point", .{});
371}
372
373fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn {
374 logMessage("execution reached the end of a value-returning function without returning a value", .{});
375}
376
377const NonNullReturnData = extern struct {
378 attribute_loc: SourceLocation,
379};
380
381fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn {
382 logMessage("null pointer returned from function declared to never return null", .{});
383}
384
385const NonNullArgData = extern struct {
386 loc: SourceLocation,
387 attribute_loc: SourceLocation,
388 arg_index: i32,
389};
390
391fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn {
392 logMessage(
393 "null pointer passed as argument {}, which is declared to never be null",
394 .{data.arg_index},
395 );
396}
397
398const InvalidValueData = extern struct {
399 loc: SourceLocation,
400 type_descriptor: *const TypeDescriptor,
401};
402
403fn loadInvalidValue(
404 data: *const InvalidValueData,
405 value_handle: ValueHandle,
406) callconv(.c) noreturn {
407 logMessage("load of value {}, which is not valid for type {s}", .{
408 value_handle.getValue(data), data.type_descriptor.getName(),
409 });
410}
411
412fn SimpleHandler(comptime error_name: []const u8) type {
413 return struct {
414 fn handler() callconv(.c) noreturn {
415 logMessage("{s}", .{error_name});
416 }
417 };
418}
419
420inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
421 std.debug.panicExtra(null, @returnAddress(), fmt, args);
422}
423
424fn exportHandler(
425 handler: anytype,
426 comptime sym_name: []const u8,
427 comptime abort: bool,
428) void {
429 const linkage = if (builtin.is_test) .internal else .weak;
430 {
431 const N = "__ubsan_handle_" ++ sym_name;
432 @export(handler, .{ .name = N, .linkage = linkage });
433 }
434 if (abort) {
435 const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
436 @export(handler, .{ .name = N, .linkage = linkage });
437 }
438}
439
440fn exportMinimal(
441 err_name: anytype,
442 comptime sym_name: []const u8,
443 comptime abort: bool,
444) void {
445 const handler = &SimpleHandler(err_name).handler;
446 const linkage = if (builtin.is_test) .internal else .weak;
447 {
448 const N = "__ubsan_handle_" ++ sym_name ++ "_minimal";
449 @export(handler, .{ .name = N, .linkage = linkage });
450 }
451 if (abort) {
452 const N = "__ubsan_handle_" ++ sym_name ++ "_minimal_abort";
453 @export(handler, .{ .name = N, .linkage = linkage });
454 }
455}
456
457fn exportHelper(
458 comptime err_name: []const u8,
459 comptime sym_name: []const u8,
460 comptime abort: bool,
461) void {
462 exportHandler(&SimpleHandler(err_name).handler, sym_name, abort);
463 exportMinimal(err_name, sym_name, abort);
464}
465
466comptime {
467 overflowHandler("add_overflow", "+");
468 overflowHandler("sub_overflow", "-");
469 overflowHandler("mul_overflow", "*");
470 exportHandler(&negationHandler, "negate_overflow", true);
471 exportHandler(&divRemHandler, "divrem_overflow", true);
472 exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true);
473 exportHandler(&shiftOob, "shift_out_of_bounds", true);
474 exportHandler(&outOfBounds, "out_of_bounds", true);
475 exportHandler(&pointerOverflow, "pointer_overflow", true);
476 exportHandler(&typeMismatch, "type_mismatch_v1", true);
477 exportHandler(&builtinUnreachable, "builtin_unreachable", false);
478 exportHandler(&missingReturn, "missing_return", false);
479 exportHandler(&nonNullReturn, "nonnull_return_v1", true);
480 exportHandler(&nonNullArg, "nonnull_arg", true);
481 exportHandler(&loadInvalidValue, "load_invalid_value", true);
482
483 exportHelper("vla-bound-not-positive", "vla_bound_not_positive", true);
484 exportHelper("float-cast-overflow", "float_cast_overflow", true);
485 exportHelper("invalid-builtin", "invalid_builtin", true);
486 exportHelper("function-type-mismatch", "function_type_mismatch", true);
487 exportHelper("implicit-conversion", "implicit_conversion", true);
488 exportHelper("nullability-arg", "nullability_arg", true);
489 exportHelper("nullability-return", "nullability_return", true);
490 exportHelper("cfi-check-fail", "cfi_check_fail", true);
491 exportHelper("function-type-mismatch-v1", "function_type_mismatch_v1", true);
492
493 exportMinimal("builtin-unreachable", "builtin_unreachable", false);
494 exportMinimal("add-overflow", "add_overflow", true);
495 exportMinimal("sub-overflow", "sub_overflow", true);
496 exportMinimal("mul-overflow", "mul_overflow", true);
497 exportMinimal("negation-handler", "negate_overflow", true);
498 exportMinimal("divrem-handler", "divrem_overflow", true);
499 exportMinimal("alignment-assumption-handler", "alignment_assumption", true);
500 exportMinimal("shift-oob", "shift_out_of_bounds", true);
501 exportMinimal("out-of-bounds", "out_of_bounds", true);
502 exportMinimal("pointer-overflow", "pointer_overflow", true);
503 exportMinimal("type-mismatch", "type_mismatch", true);
504
505 // these checks are nearly impossible to duplicate in zig, as they rely on nuances
506 // in the Itanium C++ ABI.
507 // exportHelper("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
508 // exportHelper("vptr_type_cache", "vptr-type-cache", true);
509}
lib/ubsan.zig created+509
......@@ -0,0 +1,509 @@
1//! Minimal UBSan Runtime
2
3const std = @import("std");
4const builtin = @import("builtin");
5const assert = std.debug.assert;
6
7const SourceLocation = extern struct {
8 file_name: ?[*:0]const u8,
9 line: u32,
10 col: u32,
11};
12
13const TypeDescriptor = extern struct {
14 kind: Kind,
15 info: Info,
16 // name: [?:0]u8
17
18 const Kind = enum(u16) {
19 integer = 0x0000,
20 float = 0x0001,
21 unknown = 0xFFFF,
22 };
23
24 const Info = extern union {
25 integer: packed struct(u16) {
26 signed: bool,
27 bit_width: u15,
28 },
29 };
30
31 fn getIntegerSize(desc: TypeDescriptor) u64 {
32 assert(desc.kind == .integer);
33 const bit_width = desc.info.integer.bit_width;
34 return @as(u64, 1) << @intCast(bit_width);
35 }
36
37 fn isSigned(desc: TypeDescriptor) bool {
38 return desc.kind == .integer and desc.info.integer.signed;
39 }
40
41 fn getName(desc: *const TypeDescriptor) [:0]const u8 {
42 return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor));
43 }
44};
45
46const ValueHandle = *const opaque {
47 fn getValue(handle: ValueHandle, data: anytype) Value {
48 return .{ .handle = handle, .type_descriptor = data.type_descriptor };
49 }
50};
51
52const Value = extern struct {
53 type_descriptor: *const TypeDescriptor,
54 handle: ValueHandle,
55
56 fn getUnsignedInteger(value: Value) u128 {
57 assert(!value.type_descriptor.isSigned());
58 const size = value.type_descriptor.getIntegerSize();
59 const max_inline_size = @bitSizeOf(ValueHandle);
60 if (size <= max_inline_size) {
61 return @intFromPtr(value.handle);
62 }
63
64 return switch (size) {
65 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*,
66 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*,
67 else => unreachable,
68 };
69 }
70
71 fn getSignedInteger(value: Value) i128 {
72 assert(value.type_descriptor.isSigned());
73 const size = value.type_descriptor.getIntegerSize();
74 const max_inline_size = @bitSizeOf(ValueHandle);
75 if (size <= max_inline_size) {
76 const extra_bits: std.math.Log2Int(usize) = @intCast(max_inline_size - size);
77 const handle: isize = @bitCast(@intFromPtr(value.handle));
78 return (handle << extra_bits) >> extra_bits;
79 }
80 return switch (size) {
81 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*,
82 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*,
83 else => unreachable,
84 };
85 }
86
87 fn isMinusOne(value: Value) bool {
88 return value.type_descriptor.isSigned() and
89 value.getSignedInteger() == -1;
90 }
91
92 fn isNegative(value: Value) bool {
93 return value.type_descriptor.isSigned() and
94 value.getSignedInteger() < 0;
95 }
96
97 fn getPositiveInteger(value: Value) u128 {
98 if (value.type_descriptor.isSigned()) {
99 const signed = value.getSignedInteger();
100 assert(signed >= 0);
101 return @intCast(signed);
102 } else {
103 return value.getUnsignedInteger();
104 }
105 }
106
107 pub fn format(
108 value: Value,
109 comptime fmt: []const u8,
110 _: std.fmt.FormatOptions,
111 writer: anytype,
112 ) !void {
113 comptime assert(fmt.len == 0);
114
115 switch (value.type_descriptor.kind) {
116 .integer => {
117 if (value.type_descriptor.isSigned()) {
118 try writer.print("{}", .{value.getSignedInteger()});
119 } else {
120 try writer.print("{}", .{value.getUnsignedInteger()});
121 }
122 },
123 .float => @panic("TODO: write float"),
124 .unknown => try writer.writeAll("(unknown)"),
125 }
126 }
127};
128
129const OverflowData = extern struct {
130 loc: SourceLocation,
131 type_descriptor: *const TypeDescriptor,
132};
133
134fn overflowHandler(
135 comptime sym_name: []const u8,
136 comptime operator: []const u8,
137) void {
138 const S = struct {
139 fn handler(
140 data: *const OverflowData,
141 lhs_handle: ValueHandle,
142 rhs_handle: ValueHandle,
143 ) callconv(.c) noreturn {
144 const lhs = lhs_handle.getValue(data);
145 const rhs = rhs_handle.getValue(data);
146
147 const is_signed = data.type_descriptor.isSigned();
148 const fmt = "{s} integer overflow: " ++ "{} " ++
149 operator ++ " {} cannot be represented in type {s}";
150
151 logMessage(fmt, .{
152 if (is_signed) "signed" else "unsigned",
153 lhs,
154 rhs,
155 data.type_descriptor.getName(),
156 });
157 }
158 };
159
160 exportHandler(&S.handler, sym_name, true);
161}
162
163fn negationHandler(
164 data: *const OverflowData,
165 old_value_handle: ValueHandle,
166) callconv(.c) noreturn {
167 const old_value = old_value_handle.getValue(data);
168 logMessage(
169 "negation of {} cannot be represented in type {s}",
170 .{ old_value, data.type_descriptor.getName() },
171 );
172}
173
174fn divRemHandler(
175 data: *const OverflowData,
176 lhs_handle: ValueHandle,
177 rhs_handle: ValueHandle,
178) callconv(.c) noreturn {
179 const is_signed = data.type_descriptor.isSigned();
180 const lhs = lhs_handle.getValue(data);
181 const rhs = rhs_handle.getValue(data);
182
183 if (is_signed and rhs.getSignedInteger() == -1) {
184 logMessage(
185 "division of {} by -1 cannot be represented in type {s}",
186 .{ lhs, data.type_descriptor.getName() },
187 );
188 } else logMessage("division by zero", .{});
189}
190
191const AlignmentAssumptionData = extern struct {
192 loc: SourceLocation,
193 assumption_loc: SourceLocation,
194 type_descriptor: *const TypeDescriptor,
195};
196
197fn alignmentAssumptionHandler(
198 data: *const AlignmentAssumptionData,
199 pointer: ValueHandle,
200 alignment: ValueHandle,
201 maybe_offset: ?ValueHandle,
202) callconv(.c) noreturn {
203 _ = pointer;
204 // TODO: add the hint here?
205 // const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
206 // const lsb = @ctz(real_pointer);
207 // const actual_alignment = @as(u64, 1) << @intCast(lsb);
208 // const mask = @intFromPtr(alignment) - 1;
209 // const misalignment_offset = real_pointer & mask;
210 // _ = actual_alignment;
211 // _ = misalignment_offset;
212
213 if (maybe_offset) |offset| {
214 logMessage(
215 "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed",
216 .{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() },
217 );
218 } else {
219 logMessage(
220 "assumption of {} byte alignment for pointer of type {s} failed",
221 .{ alignment.getValue(data), data.type_descriptor.getName() },
222 );
223 }
224}
225
226const ShiftOobData = extern struct {
227 loc: SourceLocation,
228 lhs_type: *const TypeDescriptor,
229 rhs_type: *const TypeDescriptor,
230};
231
232fn shiftOob(
233 data: *const ShiftOobData,
234 lhs_handle: ValueHandle,
235 rhs_handle: ValueHandle,
236) callconv(.c) noreturn {
237 const lhs: Value = .{ .handle = lhs_handle, .type_descriptor = data.lhs_type };
238 const rhs: Value = .{ .handle = rhs_handle, .type_descriptor = data.rhs_type };
239
240 if (rhs.isNegative() or
241 rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
242 {
243 if (rhs.isNegative()) {
244 logMessage("shift exponent {} is negative", .{rhs});
245 } else {
246 logMessage(
247 "shift exponent {} is too large for {}-bit type {s}",
248 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
249 );
250 }
251 } else {
252 if (lhs.isNegative()) {
253 logMessage("left shift of negative value {}", .{lhs});
254 } else {
255 logMessage(
256 "left shift of {} by {} places cannot be represented in type {s}",
257 .{ lhs, rhs, data.lhs_type.getName() },
258 );
259 }
260 }
261}
262
263const OutOfBoundsData = extern struct {
264 loc: SourceLocation,
265 array_type: *const TypeDescriptor,
266 index_type: *const TypeDescriptor,
267};
268
269fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.c) noreturn {
270 const index: Value = .{ .handle = index_handle, .type_descriptor = data.index_type };
271 logMessage(
272 "index {} out of bounds for type {s}",
273 .{ index, data.array_type.getName() },
274 );
275}
276
277const PointerOverflowData = extern struct {
278 loc: SourceLocation,
279};
280
281fn pointerOverflow(
282 _: *const PointerOverflowData,
283 base: usize,
284 result: usize,
285) callconv(.c) noreturn {
286 if (base == 0) {
287 if (result == 0) {
288 logMessage("applying zero offset to null pointer", .{});
289 } else {
290 logMessage("applying non-zero offset {} to null pointer", .{result});
291 }
292 } else {
293 if (result == 0) {
294 logMessage(
295 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
296 .{base},
297 );
298 } else {
299 @panic("TODO");
300 }
301 }
302}
303
304const TypeMismatchData = extern struct {
305 loc: SourceLocation,
306 type_descriptor: *const TypeDescriptor,
307 log_alignment: u8,
308 kind: enum(u8) {
309 load,
310 store,
311 reference_binding,
312 member_access,
313 member_call,
314 constructor_call,
315 downcast_pointer,
316 downcast_reference,
317 upcast,
318 upcast_to_virtual_base,
319 nonnull_assign,
320 dynamic_operation,
321
322 fn getName(kind: @This()) []const u8 {
323 return switch (kind) {
324 .load => "load of",
325 .store => "store of",
326 .reference_binding => "reference binding to",
327 .member_access => "member access within",
328 .member_call => "member call on",
329 .constructor_call => "constructor call on",
330 .downcast_pointer, .downcast_reference => "downcast of",
331 .upcast => "upcast of",
332 .upcast_to_virtual_base => "cast to virtual base of",
333 .nonnull_assign => "_Nonnull binding to",
334 .dynamic_operation => "dynamic operation on",
335 };
336 }
337 },
338};
339
340fn typeMismatch(
341 data: *const TypeMismatchData,
342 pointer: ?ValueHandle,
343) callconv(.c) noreturn {
344 const alignment = @as(usize, 1) << @intCast(data.log_alignment);
345 const handle: usize = @intFromPtr(pointer);
346
347 if (pointer == null) {
348 logMessage(
349 "{s} null pointer of type {s}",
350 .{ data.kind.getName(), data.type_descriptor.getName() },
351 );
352 } else if (!std.mem.isAligned(handle, alignment)) {
353 logMessage(
354 "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment",
355 .{ data.kind.getName(), handle, data.type_descriptor.getName(), alignment },
356 );
357 } else {
358 logMessage(
359 "{s} address 0x{x} with insufficient space for an object of type {s}",
360 .{ data.kind.getName(), handle, data.type_descriptor.getName() },
361 );
362 }
363}
364
365const UnreachableData = extern struct {
366 loc: SourceLocation,
367};
368
369fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn {
370 logMessage("execution reached an unreachable program point", .{});
371}
372
373fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn {
374 logMessage("execution reached the end of a value-returning function without returning a value", .{});
375}
376
377const NonNullReturnData = extern struct {
378 attribute_loc: SourceLocation,
379};
380
381fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn {
382 logMessage("null pointer returned from function declared to never return null", .{});
383}
384
385const NonNullArgData = extern struct {
386 loc: SourceLocation,
387 attribute_loc: SourceLocation,
388 arg_index: i32,
389};
390
391fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn {
392 logMessage(
393 "null pointer passed as argument {}, which is declared to never be null",
394 .{data.arg_index},
395 );
396}
397
398const InvalidValueData = extern struct {
399 loc: SourceLocation,
400 type_descriptor: *const TypeDescriptor,
401};
402
403fn loadInvalidValue(
404 data: *const InvalidValueData,
405 value_handle: ValueHandle,
406) callconv(.c) noreturn {
407 logMessage("load of value {}, which is not valid for type {s}", .{
408 value_handle.getValue(data), data.type_descriptor.getName(),
409 });
410}
411
412fn SimpleHandler(comptime error_name: []const u8) type {
413 return struct {
414 fn handler() callconv(.c) noreturn {
415 logMessage("{s}", .{error_name});
416 }
417 };
418}
419
420inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
421 std.debug.panicExtra(null, @returnAddress(), fmt, args);
422}
423
424fn exportHandler(
425 handler: anytype,
426 comptime sym_name: []const u8,
427 comptime abort: bool,
428) void {
429 const linkage = if (builtin.is_test) .internal else .weak;
430 {
431 const N = "__ubsan_handle_" ++ sym_name;
432 @export(handler, .{ .name = N, .linkage = linkage });
433 }
434 if (abort) {
435 const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
436 @export(handler, .{ .name = N, .linkage = linkage });
437 }
438}
439
440fn exportMinimal(
441 err_name: anytype,
442 comptime sym_name: []const u8,
443 comptime abort: bool,
444) void {
445 const handler = &SimpleHandler(err_name).handler;
446 const linkage = if (builtin.is_test) .internal else .weak;
447 {
448 const N = "__ubsan_handle_" ++ sym_name ++ "_minimal";
449 @export(handler, .{ .name = N, .linkage = linkage });
450 }
451 if (abort) {
452 const N = "__ubsan_handle_" ++ sym_name ++ "_minimal_abort";
453 @export(handler, .{ .name = N, .linkage = linkage });
454 }
455}
456
457fn exportHelper(
458 comptime err_name: []const u8,
459 comptime sym_name: []const u8,
460 comptime abort: bool,
461) void {
462 exportHandler(&SimpleHandler(err_name).handler, sym_name, abort);
463 exportMinimal(err_name, sym_name, abort);
464}
465
466comptime {
467 overflowHandler("add_overflow", "+");
468 overflowHandler("sub_overflow", "-");
469 overflowHandler("mul_overflow", "*");
470 exportHandler(&negationHandler, "negate_overflow", true);
471 exportHandler(&divRemHandler, "divrem_overflow", true);
472 exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true);
473 exportHandler(&shiftOob, "shift_out_of_bounds", true);
474 exportHandler(&outOfBounds, "out_of_bounds", true);
475 exportHandler(&pointerOverflow, "pointer_overflow", true);
476 exportHandler(&typeMismatch, "type_mismatch_v1", true);
477 exportHandler(&builtinUnreachable, "builtin_unreachable", false);
478 exportHandler(&missingReturn, "missing_return", false);
479 exportHandler(&nonNullReturn, "nonnull_return_v1", true);
480 exportHandler(&nonNullArg, "nonnull_arg", true);
481 exportHandler(&loadInvalidValue, "load_invalid_value", true);
482
483 exportHelper("vla-bound-not-positive", "vla_bound_not_positive", true);
484 exportHelper("float-cast-overflow", "float_cast_overflow", true);
485 exportHelper("invalid-builtin", "invalid_builtin", true);
486 exportHelper("function-type-mismatch", "function_type_mismatch", true);
487 exportHelper("implicit-conversion", "implicit_conversion", true);
488 exportHelper("nullability-arg", "nullability_arg", true);
489 exportHelper("nullability-return", "nullability_return", true);
490 exportHelper("cfi-check-fail", "cfi_check_fail", true);
491 exportHelper("function-type-mismatch-v1", "function_type_mismatch_v1", true);
492
493 exportMinimal("builtin-unreachable", "builtin_unreachable", false);
494 exportMinimal("add-overflow", "add_overflow", true);
495 exportMinimal("sub-overflow", "sub_overflow", true);
496 exportMinimal("mul-overflow", "mul_overflow", true);
497 exportMinimal("negation-handler", "negate_overflow", true);
498 exportMinimal("divrem-handler", "divrem_overflow", true);
499 exportMinimal("alignment-assumption-handler", "alignment_assumption", true);
500 exportMinimal("shift-oob", "shift_out_of_bounds", true);
501 exportMinimal("out-of-bounds", "out_of_bounds", true);
502 exportMinimal("pointer-overflow", "pointer_overflow", true);
503 exportMinimal("type-mismatch", "type_mismatch", true);
504
505 // these checks are nearly impossible to duplicate in zig, as they rely on nuances
506 // in the Itanium C++ ABI.
507 // exportHelper("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
508 // exportHelper("vptr_type_cache", "vptr-type-cache", true);
509}
src/Compilation.zig+76
......@@ -79,6 +79,7 @@ implib_emit: ?Path,
7979docs_emit: ?Path,
8080root_name: [:0]const u8,
8181include_compiler_rt: bool,
82include_ubsan_rt: bool,
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,
......@@ -1297,6 +1308,9 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
12971308 const include_compiler_rt = options.want_compiler_rt orelse
12981309 (!options.skip_linker_dependencies and is_exe_or_dyn_lib);
12991310
1311 const include_ubsan_rt = options.want_ubsan_rt orelse
1312 (!options.skip_linker_dependencies and is_exe_or_dyn_lib);
1313
13001314 if (include_compiler_rt and output_mode == .Obj) {
13011315 // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");`
13021316 // injected into the object.
......@@ -1323,6 +1337,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
13231337 try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod);
13241338 }
13251339
1340 if (include_ubsan_rt and output_mode == .Obj) {
1341 const ubsan_rt_mod = try Package.Module.create(arena, .{
1342 .global_cache_directory = options.global_cache_directory,
1343 .paths = .{
1344 .root = .{
1345 .root_dir = options.zig_lib_directory,
1346 },
1347 .root_src_path = "ubsan.zig",
1348 },
1349 .fully_qualified_name = "ubsan_rt",
1350 .cc_argv = &.{},
1351 .inherited = .{},
1352 .global = options.config,
1353 .parent = options.root_mod,
1354 .builtin_mod = options.root_mod.getBuiltinDependency(),
1355 .builtin_modules = null, // `builtin_mod` is set
1356 });
1357 try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod);
1358 }
1359
13261360 if (options.verbose_llvm_cpu_features) {
13271361 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: {
13281362 const target = options.root_mod.resolved_target.result;
......@@ -1500,6 +1534,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
15001534 .version = options.version,
15011535 .libc_installation = libc_dirs.libc_installation,
15021536 .include_compiler_rt = include_compiler_rt,
1537 .include_ubsan_rt = include_ubsan_rt,
15031538 .link_inputs = options.link_inputs,
15041539 .framework_dirs = options.framework_dirs,
15051540 .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit,
......@@ -1885,6 +1920,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18851920 }
18861921 }
18871922
1923 if (comp.include_ubsan_rt and capable_of_building_compiler_rt) {
1924 if (is_exe_or_dyn_lib) {
1925 log.debug("queuing a job to build ubsan_rt_lib", .{});
1926 comp.job_queued_ubsan_rt_lib = true;
1927 } else if (output_mode != .Obj) {
1928 log.debug("queuing a job to build ubsan_rt_obj", .{});
1929 comp.job_queued_ubsan_rt_obj = true;
1930 }
1931 }
1932
18881933 if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) {
18891934 log.debug("queuing a job to build libfuzzer", .{});
18901935 comp.queued_jobs.fuzzer_lib = true;
......@@ -1937,9 +1982,16 @@ pub fn destroy(comp: *Compilation) void {
19371982 if (comp.compiler_rt_obj) |*crt_file| {
19381983 crt_file.deinit(gpa);
19391984 }
1985 if (comp.ubsan_rt_lib) |*crt_file| {
1986 crt_file.deinit(gpa);
1987 }
1988 if (comp.ubsan_rt_obj) |*crt_file| {
1989 crt_file.deinit(gpa);
1990 }
19401991 if (comp.fuzzer_lib) |*crt_file| {
19411992 crt_file.deinit(gpa);
19421993 }
1994
19431995 if (comp.libc_static_lib) |*crt_file| {
19441996 crt_file.deinit(gpa);
19451997 }
......@@ -2207,6 +2259,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22072259 _ = try pt.importPkg(zcu.main_mod);
22082260 }
22092261
2262 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {
2263 _ = try pt.importPkg(ubsan_rt_mod);
2264 }
2265
22102266 if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
22112267 _ = try pt.importPkg(compiler_rt_mod);
22122268 }
......@@ -2248,6 +2304,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22482304 try comp.queueJob(.{ .analyze_mod = compiler_rt_mod });
22492305 zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod);
22502306 }
2307
2308 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {
2309 try comp.queueJob(.{ .analyze_mod = ubsan_rt_mod });
2310 zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod);
2311 }
22512312 }
22522313
22532314 try comp.performAllTheWork(main_progress_node);
......@@ -2593,6 +2654,7 @@ fn addNonIncrementalStuffToCacheManifest(
25932654 man.hash.add(comp.link_eh_frame_hdr);
25942655 man.hash.add(comp.skip_linker_dependencies);
25952656 man.hash.add(comp.include_compiler_rt);
2657 man.hash.add(comp.include_ubsan_rt);
25962658 man.hash.add(comp.rc_includes);
25972659 man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
25982660 man.hash.addListOfBytes(comp.framework_dirs);
......@@ -3683,6 +3745,14 @@ fn performAllTheWorkInner(
36833745 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node });
36843746 }
36853747
3748 if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {
3749 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan.zig", .libubsan, .Lib, &comp.ubsan_rt_lib, main_progress_node });
3750 }
3751
3752 if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {
3753 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan.zig", .libubsan, .Obj, &comp.ubsan_rt_obj, main_progress_node });
3754 }
3755
36863756 if (comp.queued_jobs.glibc_shared_objects) {
36873757 comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node });
36883758 }
......@@ -5916,7 +5986,11 @@ pub fn addCCArgs(
59165986 // These args have to be added after the `-fsanitize` arg or
59175987 // they won't take effect.
59185988 if (mod.sanitize_c) {
5989 // This check requires implementing the Itanium C++ ABI.
5990 // We would make it `-fsanitize-trap=vptr`, however this check requires
5991 // a full runtime due to the type hashing involved.
59195992 try argv.append("-fno-sanitize=vptr");
5993
59205994 // It is very common, and well-defined, for a pointer on one side of a C ABI
59215995 // to have a different but compatible element type. Examples include:
59225996 // `char*` vs `uint8_t*` on a system with 8-bit bytes
......@@ -5926,6 +6000,8 @@ pub fn addCCArgs(
59266000 // function was called.
59276001 try argv.append("-fno-sanitize=function");
59286002
6003 // It's recommended to use the minimal runtime in production environments
6004 // due to the security implications of the full runtime.
59296005 if (mod.optimize_mode == .ReleaseSafe) {
59306006 try argv.append("-fsanitize-minimal-runtime");
59316007 }
src/link.zig+7
......@@ -1107,6 +1107,11 @@ pub const File = struct {
11071107 else
11081108 null;
11091109
1110 const ubsan_rt_path: ?Path = if (comp.include_ubsan_rt)
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+4
......@@ -97,6 +97,10 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
9797 try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path));
9898 }
9999
100 if (comp.include_ubsan_rt) {
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+6
......@@ -849,6 +849,7 @@ fn buildOutputType(
849849 var emit_h: Emit = .no;
850850 var soname: SOName = undefined;
851851 var want_compiler_rt: ?bool = null;
852 var want_ubsan_rt: ?bool = null;
852853 var linker_script: ?[]const u8 = null;
853854 var version_script: ?[]const u8 = null;
854855 var linker_repro: ?bool = null;
......@@ -1376,6 +1377,10 @@ fn buildOutputType(
13761377 want_compiler_rt = true;
13771378 } else if (mem.eql(u8, arg, "-fno-compiler-rt")) {
13781379 want_compiler_rt = false;
1380 } else if (mem.eql(u8, arg, "-fubsan-rt")) {
1381 want_ubsan_rt = true;
1382 } else if (mem.eql(u8, arg, "-fno-ubsan-rt")) {
1383 want_ubsan_rt = false;
13791384 } else if (mem.eql(u8, arg, "-feach-lib-rpath")) {
13801385 create_module.each_lib_rpath = true;
13811386 } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) {
......@@ -3504,6 +3509,7 @@ fn buildOutputType(
35043509 .windows_lib_names = create_module.windows_libs.keys(),
35053510 .wasi_emulated_libs = create_module.wasi_emulated_libs.items,
35063511 .want_compiler_rt = want_compiler_rt,
3512 .want_ubsan_rt = want_ubsan_rt,
35073513 .hash_style = hash_style,
35083514 .linker_script = linker_script,
35093515 .version_script = version_script,