| author | |
| committer | |
| log | c45dcd013bfe9de1c739a88203349603c0682fd9 |
| tree | b1b5c1949b227e67238ef88b7a083d480388224c |
| parent | e0a955afb3c8768fb52b56c342f171bc1d0d6066 |
| parent | ca83f52fd95fa6ebf28b7d51d4ef396a2ccf4be4 |
| signature |
implement a ubsan runtime for better error messages23 files changed, 958 insertions(+), 28 deletions(-)
lib/std/Build/Step/Compile.zig+2| ... | ... | @@ -40,6 +40,7 @@ compress_debug_sections: enum { none, zlib, zstd } = .none, |
| 40 | 40 | verbose_link: bool, |
| 41 | 41 | verbose_cc: bool, |
| 42 | 42 | bundle_compiler_rt: ?bool = null, |
| 43 | bundle_ubsan_rt: ?bool = null, | |
| 43 | 44 | rdynamic: bool, |
| 44 | 45 | import_memory: bool = false, |
| 45 | 46 | export_memory: bool = false, |
| ... | ... | @@ -1563,6 +1564,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1563 | 1564 | } |
| 1564 | 1565 | |
| 1565 | 1566 | try addFlag(&zig_args, "compiler-rt", compile.bundle_compiler_rt); |
| 1567 | try addFlag(&zig_args, "ubsan-rt", compile.bundle_ubsan_rt); | |
| 1566 | 1568 | try addFlag(&zig_args, "dll-export-fns", compile.dll_export_fns); |
| 1567 | 1569 | if (compile.rdynamic) { |
| 1568 | 1570 | try zig_args.append("-rdynamic"); |
lib/std/heap.zig+5-5| ... | ... | @@ -42,10 +42,8 @@ pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null; |
| 42 | 42 | /// |
| 43 | 43 | /// On many systems, the actual page size can only be determined at runtime |
| 44 | 44 | /// with `pageSize`. |
| 45 | pub const page_size_min: usize = std.options.page_size_min orelse (page_size_min_default orelse if (builtin.os.tag == .freestanding or builtin.os.tag == .other) | |
| 46 | @compileError("freestanding/other page_size_min must provided with std.options.page_size_min") | |
| 47 | else | |
| 48 | @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min")); | |
| 45 | pub const page_size_min: usize = std.options.page_size_min orelse page_size_min_default orelse | |
| 46 | @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min"); | |
| 49 | 47 | |
| 50 | 48 | /// comptime-known maximum page size of the target. |
| 51 | 49 | /// |
| ... | ... | @@ -831,8 +829,10 @@ const page_size_min_default: ?usize = switch (builtin.os.tag) { |
| 831 | 829 | .xtensa => 4 << 10, |
| 832 | 830 | else => null, |
| 833 | 831 | }, |
| 834 | .freestanding => switch (builtin.cpu.arch) { | |
| 832 | .freestanding, .other => switch (builtin.cpu.arch) { | |
| 835 | 833 | .wasm32, .wasm64 => 64 << 10, |
| 834 | .x86, .x86_64 => 4 << 10, | |
| 835 | .aarch64, .aarch64_be => 4 << 10, | |
| 836 | 836 | else => null, |
| 837 | 837 | }, |
| 838 | 838 | else => null, |
lib/std/mem.zig+4-3| ... | ... | @@ -1098,12 +1098,12 @@ pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]co |
| 1098 | 1098 | // as we don't read into a new page. This should be the case for most architectures |
| 1099 | 1099 | // which use paged memory, however should be confirmed before adding a new arch below. |
| 1100 | 1100 | .aarch64, .x86, .x86_64 => if (std.simd.suggestVectorLength(T)) |block_len| { |
| 1101 | const page_size = std.heap.pageSize(); | |
| 1101 | const page_size = std.heap.page_size_min; | |
| 1102 | 1102 | const block_size = @sizeOf(T) * block_len; |
| 1103 | 1103 | const Block = @Vector(block_len, T); |
| 1104 | 1104 | const mask: Block = @splat(sentinel); |
| 1105 | 1105 | |
| 1106 | comptime assert(std.heap.page_size_max % @sizeOf(Block) == 0); | |
| 1106 | comptime assert(std.heap.page_size_min % @sizeOf(Block) == 0); | |
| 1107 | 1107 | assert(page_size % @sizeOf(Block) == 0); |
| 1108 | 1108 | |
| 1109 | 1109 | // First block may be unaligned |
| ... | ... | @@ -1119,6 +1119,7 @@ pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]co |
| 1119 | 1119 | |
| 1120 | 1120 | i += @divExact(std.mem.alignForward(usize, start_addr, block_size) - start_addr, @sizeOf(T)); |
| 1121 | 1121 | } else { |
| 1122 | @branchHint(.unlikely); | |
| 1122 | 1123 | // Would read over a page boundary. Per-byte at a time until aligned or found. |
| 1123 | 1124 | // 0.39% chance this branch is taken for 4K pages at 16b block length. |
| 1124 | 1125 | // |
| ... | ... | @@ -1152,7 +1153,7 @@ pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]co |
| 1152 | 1153 | test "indexOfSentinel vector paths" { |
| 1153 | 1154 | const Types = [_]type{ u8, u16, u32, u64 }; |
| 1154 | 1155 | const allocator = std.testing.allocator; |
| 1155 | const page_size = std.heap.pageSize(); | |
| 1156 | const page_size = std.heap.page_size_min; | |
| 1156 | 1157 | |
| 1157 | 1158 | inline for (Types) |T| { |
| 1158 | 1159 | const block_len = std.simd.suggestVectorLength(T) orelse continue; |
lib/ubsan_rt.zig created+711| ... | ... | @@ -0,0 +1,711 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const assert = std.debug.assert; | |
| 4 | const panic = std.debug.panicExtra; | |
| 5 | ||
| 6 | const SourceLocation = extern struct { | |
| 7 | file_name: ?[*:0]const u8, | |
| 8 | line: u32, | |
| 9 | col: u32, | |
| 10 | }; | |
| 11 | ||
| 12 | const TypeDescriptor = extern struct { | |
| 13 | kind: Kind, | |
| 14 | info: Info, | |
| 15 | // name: [?:0]u8 | |
| 16 | ||
| 17 | const Kind = enum(u16) { | |
| 18 | integer = 0x0000, | |
| 19 | float = 0x0001, | |
| 20 | unknown = 0xFFFF, | |
| 21 | }; | |
| 22 | ||
| 23 | const Info = extern union { | |
| 24 | integer: packed struct(u16) { | |
| 25 | signed: bool, | |
| 26 | bit_width: u15, | |
| 27 | }, | |
| 28 | float: u16, | |
| 29 | }; | |
| 30 | ||
| 31 | fn getIntegerSize(desc: TypeDescriptor) u64 { | |
| 32 | assert(desc.kind == .integer); | |
| 33 | const bit_width = desc.info.integer.bit_width; | |
| 34 | return @as(u64, 1) << @intCast(bit_width); | |
| 35 | } | |
| 36 | ||
| 37 | fn isSigned(desc: TypeDescriptor) bool { | |
| 38 | return desc.kind == .integer and desc.info.integer.signed; | |
| 39 | } | |
| 40 | ||
| 41 | fn getName(desc: *const TypeDescriptor) [:0]const u8 { | |
| 42 | return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor)); | |
| 43 | } | |
| 44 | }; | |
| 45 | ||
| 46 | const ValueHandle = *const opaque {}; | |
| 47 | ||
| 48 | const Value = extern struct { | |
| 49 | td: *const TypeDescriptor, | |
| 50 | handle: ValueHandle, | |
| 51 | ||
| 52 | fn getUnsignedInteger(value: Value) u128 { | |
| 53 | assert(!value.td.isSigned()); | |
| 54 | const size = value.td.getIntegerSize(); | |
| 55 | const max_inline_size = @bitSizeOf(ValueHandle); | |
| 56 | if (size <= max_inline_size) { | |
| 57 | return @intFromPtr(value.handle); | |
| 58 | } | |
| 59 | ||
| 60 | return switch (size) { | |
| 61 | 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*, | |
| 62 | 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*, | |
| 63 | else => @trap(), | |
| 64 | }; | |
| 65 | } | |
| 66 | ||
| 67 | fn getSignedInteger(value: Value) i128 { | |
| 68 | assert(value.td.isSigned()); | |
| 69 | const size = value.td.getIntegerSize(); | |
| 70 | const max_inline_size = @bitSizeOf(ValueHandle); | |
| 71 | if (size <= max_inline_size) { | |
| 72 | const extra_bits: std.math.Log2Int(usize) = @intCast(max_inline_size - size); | |
| 73 | const handle: isize = @bitCast(@intFromPtr(value.handle)); | |
| 74 | return (handle << extra_bits) >> extra_bits; | |
| 75 | } | |
| 76 | return switch (size) { | |
| 77 | 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*, | |
| 78 | 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*, | |
| 79 | else => @trap(), | |
| 80 | }; | |
| 81 | } | |
| 82 | ||
| 83 | fn getFloat(value: Value) f128 { | |
| 84 | assert(value.td.kind == .float); | |
| 85 | const size = value.td.info.float; | |
| 86 | const max_inline_size = @bitSizeOf(ValueHandle); | |
| 87 | if (size <= max_inline_size) { | |
| 88 | return @as(switch (@bitSizeOf(usize)) { | |
| 89 | 32 => f32, | |
| 90 | 64 => f64, | |
| 91 | else => @compileError("unsupported target"), | |
| 92 | }, @bitCast(@intFromPtr(value.handle))); | |
| 93 | } | |
| 94 | return @floatCast(switch (size) { | |
| 95 | 64 => @as(*const f64, @alignCast(@ptrCast(value.handle))).*, | |
| 96 | 80 => @as(*const f80, @alignCast(@ptrCast(value.handle))).*, | |
| 97 | 128 => @as(*const f128, @alignCast(@ptrCast(value.handle))).*, | |
| 98 | else => @trap(), | |
| 99 | }); | |
| 100 | } | |
| 101 | ||
| 102 | fn isMinusOne(value: Value) bool { | |
| 103 | return value.td.isSigned() and | |
| 104 | value.getSignedInteger() == -1; | |
| 105 | } | |
| 106 | ||
| 107 | fn isNegative(value: Value) bool { | |
| 108 | return value.td.isSigned() and | |
| 109 | value.getSignedInteger() < 0; | |
| 110 | } | |
| 111 | ||
| 112 | fn getPositiveInteger(value: Value) u128 { | |
| 113 | if (value.td.isSigned()) { | |
| 114 | const signed = value.getSignedInteger(); | |
| 115 | assert(signed >= 0); | |
| 116 | return @intCast(signed); | |
| 117 | } else { | |
| 118 | return value.getUnsignedInteger(); | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | pub fn format( | |
| 123 | value: Value, | |
| 124 | comptime fmt: []const u8, | |
| 125 | _: std.fmt.FormatOptions, | |
| 126 | writer: anytype, | |
| 127 | ) !void { | |
| 128 | comptime assert(fmt.len == 0); | |
| 129 | ||
| 130 | // Work around x86_64 backend limitation. | |
| 131 | if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag == .windows) { | |
| 132 | try writer.writeAll("(unknown)"); | |
| 133 | return; | |
| 134 | } | |
| 135 | ||
| 136 | switch (value.td.kind) { | |
| 137 | .integer => { | |
| 138 | if (value.td.isSigned()) { | |
| 139 | try writer.print("{}", .{value.getSignedInteger()}); | |
| 140 | } else { | |
| 141 | try writer.print("{}", .{value.getUnsignedInteger()}); | |
| 142 | } | |
| 143 | }, | |
| 144 | .float => try writer.print("{}", .{value.getFloat()}), | |
| 145 | .unknown => try writer.writeAll("(unknown)"), | |
| 146 | } | |
| 147 | } | |
| 148 | }; | |
| 149 | ||
| 150 | const OverflowData = extern struct { | |
| 151 | loc: SourceLocation, | |
| 152 | td: *const TypeDescriptor, | |
| 153 | }; | |
| 154 | ||
| 155 | fn overflowHandler( | |
| 156 | comptime sym_name: []const u8, | |
| 157 | comptime operator: []const u8, | |
| 158 | ) void { | |
| 159 | const S = struct { | |
| 160 | fn abort( | |
| 161 | data: *const OverflowData, | |
| 162 | lhs_handle: ValueHandle, | |
| 163 | rhs_handle: ValueHandle, | |
| 164 | ) callconv(.c) noreturn { | |
| 165 | handler(data, lhs_handle, rhs_handle); | |
| 166 | } | |
| 167 | ||
| 168 | fn handler( | |
| 169 | data: *const OverflowData, | |
| 170 | lhs_handle: ValueHandle, | |
| 171 | rhs_handle: ValueHandle, | |
| 172 | ) callconv(.c) noreturn { | |
| 173 | const lhs: Value = .{ .handle = lhs_handle, .td = data.td }; | |
| 174 | const rhs: Value = .{ .handle = rhs_handle, .td = data.td }; | |
| 175 | ||
| 176 | const is_signed = data.td.isSigned(); | |
| 177 | const fmt = "{s} integer overflow: " ++ "{} " ++ | |
| 178 | operator ++ " {} cannot be represented in type {s}"; | |
| 179 | ||
| 180 | panic(@returnAddress(), fmt, .{ | |
| 181 | if (is_signed) "signed" else "unsigned", | |
| 182 | lhs, | |
| 183 | rhs, | |
| 184 | data.td.getName(), | |
| 185 | }); | |
| 186 | } | |
| 187 | }; | |
| 188 | ||
| 189 | exportHandlerWithAbort(&S.handler, &S.abort, sym_name); | |
| 190 | } | |
| 191 | ||
| 192 | fn negationHandlerAbort( | |
| 193 | data: *const OverflowData, | |
| 194 | value_handle: ValueHandle, | |
| 195 | ) callconv(.c) noreturn { | |
| 196 | negationHandler(data, value_handle); | |
| 197 | } | |
| 198 | ||
| 199 | fn negationHandler( | |
| 200 | data: *const OverflowData, | |
| 201 | value_handle: ValueHandle, | |
| 202 | ) callconv(.c) noreturn { | |
| 203 | const value: Value = .{ .handle = value_handle, .td = data.td }; | |
| 204 | panic( | |
| 205 | @returnAddress(), | |
| 206 | "negation of {} cannot be represented in type {s}", | |
| 207 | .{ value, data.td.getName() }, | |
| 208 | ); | |
| 209 | } | |
| 210 | ||
| 211 | fn divRemHandlerAbort( | |
| 212 | data: *const OverflowData, | |
| 213 | lhs_handle: ValueHandle, | |
| 214 | rhs_handle: ValueHandle, | |
| 215 | ) callconv(.c) noreturn { | |
| 216 | divRemHandler(data, lhs_handle, rhs_handle); | |
| 217 | } | |
| 218 | ||
| 219 | fn divRemHandler( | |
| 220 | data: *const OverflowData, | |
| 221 | lhs_handle: ValueHandle, | |
| 222 | rhs_handle: ValueHandle, | |
| 223 | ) callconv(.c) noreturn { | |
| 224 | const lhs: Value = .{ .handle = lhs_handle, .td = data.td }; | |
| 225 | const rhs: Value = .{ .handle = rhs_handle, .td = data.td }; | |
| 226 | ||
| 227 | if (rhs.isMinusOne()) { | |
| 228 | panic( | |
| 229 | @returnAddress(), | |
| 230 | "division of {} by -1 cannot be represented in type {s}", | |
| 231 | .{ lhs, data.td.getName() }, | |
| 232 | ); | |
| 233 | } else panic(@returnAddress(), "division by zero", .{}); | |
| 234 | } | |
| 235 | ||
| 236 | const AlignmentAssumptionData = extern struct { | |
| 237 | loc: SourceLocation, | |
| 238 | assumption_loc: SourceLocation, | |
| 239 | td: *const TypeDescriptor, | |
| 240 | }; | |
| 241 | ||
| 242 | fn alignmentAssumptionHandlerAbort( | |
| 243 | data: *const AlignmentAssumptionData, | |
| 244 | pointer: ValueHandle, | |
| 245 | alignment_handle: ValueHandle, | |
| 246 | maybe_offset: ?ValueHandle, | |
| 247 | ) callconv(.c) noreturn { | |
| 248 | alignmentAssumptionHandler( | |
| 249 | data, | |
| 250 | pointer, | |
| 251 | alignment_handle, | |
| 252 | maybe_offset, | |
| 253 | ); | |
| 254 | } | |
| 255 | ||
| 256 | fn alignmentAssumptionHandler( | |
| 257 | data: *const AlignmentAssumptionData, | |
| 258 | pointer: ValueHandle, | |
| 259 | alignment_handle: ValueHandle, | |
| 260 | maybe_offset: ?ValueHandle, | |
| 261 | ) callconv(.c) noreturn { | |
| 262 | const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset); | |
| 263 | const lsb = @ctz(real_pointer); | |
| 264 | const actual_alignment = @as(u64, 1) << @intCast(lsb); | |
| 265 | const mask = @intFromPtr(alignment_handle) - 1; | |
| 266 | const misalignment_offset = real_pointer & mask; | |
| 267 | const alignment: Value = .{ .handle = alignment_handle, .td = data.td }; | |
| 268 | ||
| 269 | if (maybe_offset) |offset| { | |
| 270 | panic( | |
| 271 | @returnAddress(), | |
| 272 | "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed\n" ++ | |
| 273 | "offset address is {} aligned, misalignment offset is {} bytes", | |
| 274 | .{ | |
| 275 | alignment, | |
| 276 | @intFromPtr(offset), | |
| 277 | data.td.getName(), | |
| 278 | actual_alignment, | |
| 279 | misalignment_offset, | |
| 280 | }, | |
| 281 | ); | |
| 282 | } else { | |
| 283 | panic( | |
| 284 | @returnAddress(), | |
| 285 | "assumption of {} byte alignment for pointer of type {s} failed\n" ++ | |
| 286 | "address is {} aligned, misalignment offset is {} bytes", | |
| 287 | .{ | |
| 288 | alignment, | |
| 289 | data.td.getName(), | |
| 290 | actual_alignment, | |
| 291 | misalignment_offset, | |
| 292 | }, | |
| 293 | ); | |
| 294 | } | |
| 295 | } | |
| 296 | ||
| 297 | const ShiftOobData = extern struct { | |
| 298 | loc: SourceLocation, | |
| 299 | lhs_type: *const TypeDescriptor, | |
| 300 | rhs_type: *const TypeDescriptor, | |
| 301 | }; | |
| 302 | ||
| 303 | fn shiftOobAbort( | |
| 304 | data: *const ShiftOobData, | |
| 305 | lhs_handle: ValueHandle, | |
| 306 | rhs_handle: ValueHandle, | |
| 307 | ) callconv(.c) noreturn { | |
| 308 | shiftOob(data, lhs_handle, rhs_handle); | |
| 309 | } | |
| 310 | ||
| 311 | fn shiftOob( | |
| 312 | data: *const ShiftOobData, | |
| 313 | lhs_handle: ValueHandle, | |
| 314 | rhs_handle: ValueHandle, | |
| 315 | ) callconv(.c) noreturn { | |
| 316 | const lhs: Value = .{ .handle = lhs_handle, .td = data.lhs_type }; | |
| 317 | const rhs: Value = .{ .handle = rhs_handle, .td = data.rhs_type }; | |
| 318 | ||
| 319 | if (rhs.isNegative() or | |
| 320 | rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize()) | |
| 321 | { | |
| 322 | if (rhs.isNegative()) { | |
| 323 | panic(@returnAddress(), "shift exponent {} is negative", .{rhs}); | |
| 324 | } else { | |
| 325 | panic( | |
| 326 | @returnAddress(), | |
| 327 | "shift exponent {} is too large for {}-bit type {s}", | |
| 328 | .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() }, | |
| 329 | ); | |
| 330 | } | |
| 331 | } else { | |
| 332 | if (lhs.isNegative()) { | |
| 333 | panic(@returnAddress(), "left shift of negative value {}", .{lhs}); | |
| 334 | } else { | |
| 335 | panic( | |
| 336 | @returnAddress(), | |
| 337 | "left shift of {} by {} places cannot be represented in type {s}", | |
| 338 | .{ lhs, rhs, data.lhs_type.getName() }, | |
| 339 | ); | |
| 340 | } | |
| 341 | } | |
| 342 | } | |
| 343 | ||
| 344 | const OutOfBoundsData = extern struct { | |
| 345 | loc: SourceLocation, | |
| 346 | array_type: *const TypeDescriptor, | |
| 347 | index_type: *const TypeDescriptor, | |
| 348 | }; | |
| 349 | ||
| 350 | fn outOfBoundsAbort( | |
| 351 | data: *const OutOfBoundsData, | |
| 352 | index_handle: ValueHandle, | |
| 353 | ) callconv(.c) noreturn { | |
| 354 | outOfBounds(data, index_handle); | |
| 355 | } | |
| 356 | ||
| 357 | fn outOfBounds( | |
| 358 | data: *const OutOfBoundsData, | |
| 359 | index_handle: ValueHandle, | |
| 360 | ) callconv(.c) noreturn { | |
| 361 | const index: Value = .{ .handle = index_handle, .td = data.index_type }; | |
| 362 | panic( | |
| 363 | @returnAddress(), | |
| 364 | "index {} out of bounds for type {s}", | |
| 365 | .{ index, data.array_type.getName() }, | |
| 366 | ); | |
| 367 | } | |
| 368 | ||
| 369 | const PointerOverflowData = extern struct { | |
| 370 | loc: SourceLocation, | |
| 371 | }; | |
| 372 | ||
| 373 | fn pointerOverflowAbort( | |
| 374 | data: *const PointerOverflowData, | |
| 375 | base: usize, | |
| 376 | result: usize, | |
| 377 | ) callconv(.c) noreturn { | |
| 378 | pointerOverflow(data, base, result); | |
| 379 | } | |
| 380 | ||
| 381 | fn pointerOverflow( | |
| 382 | _: *const PointerOverflowData, | |
| 383 | base: usize, | |
| 384 | result: usize, | |
| 385 | ) callconv(.c) noreturn { | |
| 386 | if (base == 0) { | |
| 387 | if (result == 0) { | |
| 388 | panic(@returnAddress(), "applying zero offset to null pointer", .{}); | |
| 389 | } else { | |
| 390 | panic(@returnAddress(), "applying non-zero offset {} to null pointer", .{result}); | |
| 391 | } | |
| 392 | } else { | |
| 393 | if (result == 0) { | |
| 394 | panic( | |
| 395 | @returnAddress(), | |
| 396 | "applying non-zero offset to non-null pointer 0x{x} produced null pointer", | |
| 397 | .{base}, | |
| 398 | ); | |
| 399 | } else { | |
| 400 | const signed_base: isize = @bitCast(base); | |
| 401 | const signed_result: isize = @bitCast(result); | |
| 402 | if ((signed_base >= 0) == (signed_result >= 0)) { | |
| 403 | if (base > result) { | |
| 404 | panic( | |
| 405 | @returnAddress(), | |
| 406 | "addition of unsigned offset to 0x{x} overflowed to 0x{x}", | |
| 407 | .{ base, result }, | |
| 408 | ); | |
| 409 | } else { | |
| 410 | panic( | |
| 411 | @returnAddress(), | |
| 412 | "subtraction of unsigned offset to 0x{x} overflowed to 0x{x}", | |
| 413 | .{ base, result }, | |
| 414 | ); | |
| 415 | } | |
| 416 | } else { | |
| 417 | panic( | |
| 418 | @returnAddress(), | |
| 419 | "pointer index expression with base 0x{x} overflowed to 0x{x}", | |
| 420 | .{ base, result }, | |
| 421 | ); | |
| 422 | } | |
| 423 | } | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | const TypeMismatchData = extern struct { | |
| 428 | loc: SourceLocation, | |
| 429 | td: *const TypeDescriptor, | |
| 430 | log_alignment: u8, | |
| 431 | kind: enum(u8) { | |
| 432 | load, | |
| 433 | store, | |
| 434 | reference_binding, | |
| 435 | member_access, | |
| 436 | member_call, | |
| 437 | constructor_call, | |
| 438 | downcast_pointer, | |
| 439 | downcast_reference, | |
| 440 | upcast, | |
| 441 | upcast_to_virtual_base, | |
| 442 | nonnull_assign, | |
| 443 | dynamic_operation, | |
| 444 | ||
| 445 | fn getName(kind: @This()) []const u8 { | |
| 446 | return switch (kind) { | |
| 447 | .load => "load of", | |
| 448 | .store => "store of", | |
| 449 | .reference_binding => "reference binding to", | |
| 450 | .member_access => "member access within", | |
| 451 | .member_call => "member call on", | |
| 452 | .constructor_call => "constructor call on", | |
| 453 | .downcast_pointer, .downcast_reference => "downcast of", | |
| 454 | .upcast => "upcast of", | |
| 455 | .upcast_to_virtual_base => "cast to virtual base of", | |
| 456 | .nonnull_assign => "_Nonnull binding to", | |
| 457 | .dynamic_operation => "dynamic operation on", | |
| 458 | }; | |
| 459 | } | |
| 460 | }, | |
| 461 | }; | |
| 462 | ||
| 463 | fn typeMismatchAbort( | |
| 464 | data: *const TypeMismatchData, | |
| 465 | pointer: ?ValueHandle, | |
| 466 | ) callconv(.c) noreturn { | |
| 467 | typeMismatch(data, pointer); | |
| 468 | } | |
| 469 | ||
| 470 | fn typeMismatch( | |
| 471 | data: *const TypeMismatchData, | |
| 472 | pointer: ?ValueHandle, | |
| 473 | ) callconv(.c) noreturn { | |
| 474 | const alignment = @as(usize, 1) << @intCast(data.log_alignment); | |
| 475 | const handle: usize = @intFromPtr(pointer); | |
| 476 | ||
| 477 | if (pointer == null) { | |
| 478 | panic( | |
| 479 | @returnAddress(), | |
| 480 | "{s} null pointer of type {s}", | |
| 481 | .{ data.kind.getName(), data.td.getName() }, | |
| 482 | ); | |
| 483 | } else if (!std.mem.isAligned(handle, alignment)) { | |
| 484 | panic( | |
| 485 | @returnAddress(), | |
| 486 | "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment", | |
| 487 | .{ data.kind.getName(), handle, data.td.getName(), alignment }, | |
| 488 | ); | |
| 489 | } else { | |
| 490 | panic( | |
| 491 | @returnAddress(), | |
| 492 | "{s} address 0x{x} with insufficient space for an object of type {s}", | |
| 493 | .{ data.kind.getName(), handle, data.td.getName() }, | |
| 494 | ); | |
| 495 | } | |
| 496 | } | |
| 497 | ||
| 498 | const UnreachableData = extern struct { | |
| 499 | loc: SourceLocation, | |
| 500 | }; | |
| 501 | ||
| 502 | fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn { | |
| 503 | panic(@returnAddress(), "execution reached an unreachable program point", .{}); | |
| 504 | } | |
| 505 | ||
| 506 | fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn { | |
| 507 | panic(@returnAddress(), "execution reached the end of a value-returning function without returning a value", .{}); | |
| 508 | } | |
| 509 | ||
| 510 | const NonNullReturnData = extern struct { | |
| 511 | attribute_loc: SourceLocation, | |
| 512 | }; | |
| 513 | ||
| 514 | fn nonNullReturnAbort(data: *const NonNullReturnData) callconv(.c) noreturn { | |
| 515 | nonNullReturn(data); | |
| 516 | } | |
| 517 | fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn { | |
| 518 | panic(@returnAddress(), "null pointer returned from function declared to never return null", .{}); | |
| 519 | } | |
| 520 | ||
| 521 | const NonNullArgData = extern struct { | |
| 522 | loc: SourceLocation, | |
| 523 | attribute_loc: SourceLocation, | |
| 524 | arg_index: i32, | |
| 525 | }; | |
| 526 | ||
| 527 | fn nonNullArgAbort(data: *const NonNullArgData) callconv(.c) noreturn { | |
| 528 | nonNullArg(data); | |
| 529 | } | |
| 530 | ||
| 531 | fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn { | |
| 532 | panic( | |
| 533 | @returnAddress(), | |
| 534 | "null pointer passed as argument {}, which is declared to never be null", | |
| 535 | .{data.arg_index}, | |
| 536 | ); | |
| 537 | } | |
| 538 | ||
| 539 | const InvalidValueData = extern struct { | |
| 540 | loc: SourceLocation, | |
| 541 | td: *const TypeDescriptor, | |
| 542 | }; | |
| 543 | ||
| 544 | fn loadInvalidValueAbort( | |
| 545 | data: *const InvalidValueData, | |
| 546 | value_handle: ValueHandle, | |
| 547 | ) callconv(.c) noreturn { | |
| 548 | loadInvalidValue(data, value_handle); | |
| 549 | } | |
| 550 | ||
| 551 | fn loadInvalidValue( | |
| 552 | data: *const InvalidValueData, | |
| 553 | value_handle: ValueHandle, | |
| 554 | ) callconv(.c) noreturn { | |
| 555 | const value: Value = .{ .handle = value_handle, .td = data.td }; | |
| 556 | panic( | |
| 557 | @returnAddress(), | |
| 558 | "load of value {}, which is not valid for type {s}", | |
| 559 | .{ value, data.td.getName() }, | |
| 560 | ); | |
| 561 | } | |
| 562 | ||
| 563 | const InvalidBuiltinData = extern struct { | |
| 564 | loc: SourceLocation, | |
| 565 | kind: enum(u8) { | |
| 566 | ctz, | |
| 567 | clz, | |
| 568 | }, | |
| 569 | }; | |
| 570 | fn invalidBuiltinAbort(data: *const InvalidBuiltinData) callconv(.c) noreturn { | |
| 571 | invalidBuiltin(data); | |
| 572 | } | |
| 573 | ||
| 574 | fn invalidBuiltin(data: *const InvalidBuiltinData) callconv(.c) noreturn { | |
| 575 | panic( | |
| 576 | @returnAddress(), | |
| 577 | "passing zero to {s}(), which is not a valid argument", | |
| 578 | .{@tagName(data.kind)}, | |
| 579 | ); | |
| 580 | } | |
| 581 | ||
| 582 | const VlaBoundNotPositive = extern struct { | |
| 583 | loc: SourceLocation, | |
| 584 | td: *const TypeDescriptor, | |
| 585 | }; | |
| 586 | ||
| 587 | fn vlaBoundNotPositiveAbort( | |
| 588 | data: *const VlaBoundNotPositive, | |
| 589 | bound_handle: ValueHandle, | |
| 590 | ) callconv(.c) noreturn { | |
| 591 | vlaBoundNotPositive(data, bound_handle); | |
| 592 | } | |
| 593 | ||
| 594 | fn vlaBoundNotPositive( | |
| 595 | data: *const VlaBoundNotPositive, | |
| 596 | bound_handle: ValueHandle, | |
| 597 | ) callconv(.c) noreturn { | |
| 598 | const bound: Value = .{ .handle = bound_handle, .td = data.td }; | |
| 599 | panic( | |
| 600 | @returnAddress(), | |
| 601 | "variable length array bound evaluates to non-positive value {}", | |
| 602 | .{bound}, | |
| 603 | ); | |
| 604 | } | |
| 605 | ||
| 606 | const FloatCastOverflowData = extern struct { | |
| 607 | from: *const TypeDescriptor, | |
| 608 | to: *const TypeDescriptor, | |
| 609 | }; | |
| 610 | ||
| 611 | const FloatCastOverflowDataV2 = extern struct { | |
| 612 | loc: SourceLocation, | |
| 613 | from: *const TypeDescriptor, | |
| 614 | to: *const TypeDescriptor, | |
| 615 | }; | |
| 616 | ||
| 617 | fn floatCastOverflowAbort( | |
| 618 | data_handle: *align(8) const anyopaque, | |
| 619 | from_handle: ValueHandle, | |
| 620 | ) callconv(.c) noreturn { | |
| 621 | floatCastOverflow(data_handle, from_handle); | |
| 622 | } | |
| 623 | ||
| 624 | fn floatCastOverflow( | |
| 625 | data_handle: *align(8) const anyopaque, | |
| 626 | from_handle: ValueHandle, | |
| 627 | ) callconv(.c) noreturn { | |
| 628 | // See: https://github.com/llvm/llvm-project/blob/release/19.x/compiler-rt/lib/ubsan/ubsan_handlers.cpp#L463 | |
| 629 | // for more information on this check. | |
| 630 | const ptr: [*]const u8 = @ptrCast(data_handle); | |
| 631 | if (@as(u16, ptr[0]) + @as(u16, ptr[1]) < 2 or ptr[0] == 0xFF or ptr[1] == 0xFF) { | |
| 632 | const data: *const FloatCastOverflowData = @ptrCast(data_handle); | |
| 633 | const from_value: Value = .{ .handle = from_handle, .td = data.from }; | |
| 634 | panic(@returnAddress(), "{} is outside the range of representable values of type {s}", .{ | |
| 635 | from_value, data.to.getName(), | |
| 636 | }); | |
| 637 | } else { | |
| 638 | const data: *const FloatCastOverflowDataV2 = @ptrCast(data_handle); | |
| 639 | const from_value: Value = .{ .handle = from_handle, .td = data.from }; | |
| 640 | panic(@returnAddress(), "{} is outside the range of representable values of type {s}", .{ | |
| 641 | from_value, data.to.getName(), | |
| 642 | }); | |
| 643 | } | |
| 644 | } | |
| 645 | ||
| 646 | fn exportHandler( | |
| 647 | handler: anytype, | |
| 648 | comptime sym_name: []const u8, | |
| 649 | ) void { | |
| 650 | // Work around x86_64 backend limitation. | |
| 651 | const linkage = if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag == .windows) .internal else .weak; | |
| 652 | const N = "__ubsan_handle_" ++ sym_name; | |
| 653 | @export(handler, .{ .name = N, .linkage = linkage }); | |
| 654 | } | |
| 655 | ||
| 656 | fn exportHandlerWithAbort( | |
| 657 | handler: anytype, | |
| 658 | abort_handler: anytype, | |
| 659 | comptime sym_name: []const u8, | |
| 660 | ) void { | |
| 661 | // Work around x86_64 backend limitation. | |
| 662 | const linkage = if (builtin.zig_backend == .stage2_x86_64 and builtin.os.tag == .windows) .internal else .weak; | |
| 663 | { | |
| 664 | const N = "__ubsan_handle_" ++ sym_name; | |
| 665 | @export(handler, .{ .name = N, .linkage = linkage }); | |
| 666 | } | |
| 667 | { | |
| 668 | const N = "__ubsan_handle_" ++ sym_name ++ "_abort"; | |
| 669 | @export(abort_handler, .{ .name = N, .linkage = linkage }); | |
| 670 | } | |
| 671 | } | |
| 672 | ||
| 673 | const can_build_ubsan = switch (builtin.zig_backend) { | |
| 674 | .stage2_riscv64 => false, | |
| 675 | else => true, | |
| 676 | }; | |
| 677 | ||
| 678 | comptime { | |
| 679 | if (can_build_ubsan) { | |
| 680 | overflowHandler("add_overflow", "+"); | |
| 681 | overflowHandler("mul_overflow", "*"); | |
| 682 | overflowHandler("sub_overflow", "-"); | |
| 683 | exportHandlerWithAbort(&alignmentAssumptionHandler, &alignmentAssumptionHandlerAbort, "alignment_assumption"); | |
| 684 | ||
| 685 | exportHandlerWithAbort(&divRemHandler, &divRemHandlerAbort, "divrem_overflow"); | |
| 686 | exportHandlerWithAbort(&floatCastOverflow, &floatCastOverflowAbort, "float_cast_overflow"); | |
| 687 | exportHandlerWithAbort(&invalidBuiltin, &invalidBuiltinAbort, "invalid_builtin"); | |
| 688 | exportHandlerWithAbort(&loadInvalidValue, &loadInvalidValueAbort, "load_invalid_value"); | |
| 689 | ||
| 690 | exportHandlerWithAbort(&negationHandler, &negationHandlerAbort, "negate_overflow"); | |
| 691 | exportHandlerWithAbort(&nonNullArg, &nonNullArgAbort, "nonnull_arg"); | |
| 692 | exportHandlerWithAbort(&nonNullReturn, &nonNullReturnAbort, "nonnull_return_v1"); | |
| 693 | exportHandlerWithAbort(&outOfBounds, &outOfBoundsAbort, "out_of_bounds"); | |
| 694 | exportHandlerWithAbort(&pointerOverflow, &pointerOverflowAbort, "pointer_overflow"); | |
| 695 | exportHandlerWithAbort(&shiftOob, &shiftOobAbort, "shift_out_of_bounds"); | |
| 696 | exportHandlerWithAbort(&typeMismatch, &typeMismatchAbort, "type_mismatch_v1"); | |
| 697 | exportHandlerWithAbort(&vlaBoundNotPositive, &vlaBoundNotPositiveAbort, "vla_bound_not_positive"); | |
| 698 | ||
| 699 | exportHandler(&builtinUnreachable, "builtin_unreachable"); | |
| 700 | exportHandler(&missingReturn, "missing_return"); | |
| 701 | } | |
| 702 | ||
| 703 | // these checks are nearly impossible to replicate in zig, as they rely on nuances | |
| 704 | // in the Itanium C++ ABI. | |
| 705 | // exportHandlerWithAbort(&dynamicTypeCacheMiss, &dynamicTypeCacheMissAbort, "dynamic-type-cache-miss"); | |
| 706 | // exportHandlerWithAbort(&vptrTypeCache, &vptrTypeCacheAbort, "vptr-type-cache"); | |
| 707 | ||
| 708 | // we disable -fsanitize=function for reasons explained in src/Compilation.zig | |
| 709 | // exportHandlerWithAbort(&functionTypeMismatch, &functionTypeMismatchAbort, "function-type-mismatch"); | |
| 710 | // exportHandlerWithAbort(&functionTypeMismatchV1, &functionTypeMismatchV1Abort, "function-type-mismatch-v1"); | |
| 711 | } |
src/Compilation.zig+121-15| ... | ... | @@ -78,7 +78,8 @@ implib_emit: ?Path, |
| 78 | 78 | /// This is non-null when `-femit-docs` is provided. |
| 79 | 79 | docs_emit: ?Path, |
| 80 | 80 | root_name: [:0]const u8, |
| 81 | include_compiler_rt: bool, | |
| 81 | compiler_rt_strat: RtStrat, | |
| 82 | ubsan_rt_strat: RtStrat, | |
| 82 | 83 | /// Resolved into known paths, any GNU ld scripts already resolved. |
| 83 | 84 | link_inputs: []const link.Input, |
| 84 | 85 | /// Needed only for passing -F args to clang. |
| ... | ... | @@ -226,6 +227,12 @@ libunwind_static_lib: ?CrtFile = null, |
| 226 | 227 | /// Populated when we build the TSAN library. A Job to build this is placed in the queue |
| 227 | 228 | /// and resolved before calling linker.flush(). |
| 228 | 229 | tsan_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(). | |
| 232 | ubsan_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(). | |
| 235 | ubsan_rt_obj: ?CrtFile = null, | |
| 229 | 236 | /// Populated when we build the libc static library. A Job to build this is placed in the queue |
| 230 | 237 | /// and resolved before calling linker.flush(). |
| 231 | 238 | libc_static_lib: ?CrtFile = null, |
| ... | ... | @@ -283,6 +290,8 @@ digest: ?[Cache.bin_digest_len]u8 = null, |
| 283 | 290 | const QueuedJobs = struct { |
| 284 | 291 | compiler_rt_lib: bool = false, |
| 285 | 292 | compiler_rt_obj: bool = false, |
| 293 | ubsan_rt_lib: bool = false, | |
| 294 | ubsan_rt_obj: bool = false, | |
| 286 | 295 | fuzzer_lib: bool = false, |
| 287 | 296 | update_builtin_zig: bool, |
| 288 | 297 | musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false), |
| ... | ... | @@ -789,6 +798,7 @@ pub const MiscTask = enum { |
| 789 | 798 | libcxx, |
| 790 | 799 | libcxxabi, |
| 791 | 800 | libtsan, |
| 801 | libubsan, | |
| 792 | 802 | libfuzzer, |
| 793 | 803 | wasi_libc_crt_file, |
| 794 | 804 | compiler_rt, |
| ... | ... | @@ -1064,6 +1074,7 @@ pub const CreateOptions = struct { |
| 1064 | 1074 | /// Position Independent Executable. If the output mode is not an |
| 1065 | 1075 | /// executable this field is ignored. |
| 1066 | 1076 | want_compiler_rt: ?bool = null, |
| 1077 | want_ubsan_rt: ?bool = null, | |
| 1067 | 1078 | want_lto: ?bool = null, |
| 1068 | 1079 | function_sections: bool = false, |
| 1069 | 1080 | data_sections: bool = false, |
| ... | ... | @@ -1245,6 +1256,8 @@ fn addModuleTableToCacheHash( |
| 1245 | 1256 | } |
| 1246 | 1257 | } |
| 1247 | 1258 | |
| 1259 | const RtStrat = enum { none, lib, obj, zcu }; | |
| 1260 | ||
| 1248 | 1261 | pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compilation { |
| 1249 | 1262 | const output_mode = options.config.output_mode; |
| 1250 | 1263 | const is_dyn_lib = switch (output_mode) { |
| ... | ... | @@ -1276,6 +1289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1276 | 1289 | const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none; |
| 1277 | 1290 | const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded; |
| 1278 | 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; | |
| 1279 | 1293 | const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz; |
| 1280 | 1294 | |
| 1281 | 1295 | const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables; |
| ... | ... | @@ -1294,10 +1308,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1294 | 1308 | |
| 1295 | 1309 | const sysroot = options.sysroot orelse libc_dirs.sysroot; |
| 1296 | 1310 | |
| 1297 | const include_compiler_rt = options.want_compiler_rt orelse | |
| 1298 | (!options.skip_linker_dependencies and is_exe_or_dyn_lib); | |
| 1311 | const compiler_rt_strat: RtStrat = s: { | |
| 1312 | if (options.skip_linker_dependencies) break :s .none; | |
| 1313 | const want = options.want_compiler_rt orelse is_exe_or_dyn_lib; | |
| 1314 | if (!want) break :s .none; | |
| 1315 | if (have_zcu and output_mode == .Obj) break :s .zcu; | |
| 1316 | if (is_exe_or_dyn_lib) break :s .lib; | |
| 1317 | break :s .obj; | |
| 1318 | }; | |
| 1299 | 1319 | |
| 1300 | if (include_compiler_rt and output_mode == .Obj) { | |
| 1320 | if (compiler_rt_strat == .zcu) { | |
| 1301 | 1321 | // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` |
| 1302 | 1322 | // injected into the object. |
| 1303 | 1323 | const compiler_rt_mod = try Package.Module.create(arena, .{ |
| ... | ... | @@ -1323,6 +1343,38 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1323 | 1343 | try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod); |
| 1324 | 1344 | } |
| 1325 | 1345 | |
| 1346 | // unlike compiler_rt, we always want to go through the `_ = @import("ubsan-rt")` | |
| 1347 | // approach, since the ubsan runtime uses quite a lot of the standard library | |
| 1348 | // and this reduces unnecessary bloat. | |
| 1349 | const ubsan_rt_strat: RtStrat = s: { | |
| 1350 | const want_ubsan_rt = options.want_ubsan_rt orelse (any_sanitize_c and is_exe_or_dyn_lib); | |
| 1351 | if (!want_ubsan_rt) break :s .none; | |
| 1352 | if (options.skip_linker_dependencies) break :s .none; | |
| 1353 | if (have_zcu) break :s .zcu; | |
| 1354 | if (is_exe_or_dyn_lib) break :s .lib; | |
| 1355 | break :s .obj; | |
| 1356 | }; | |
| 1357 | ||
| 1358 | if (ubsan_rt_strat == .zcu) { | |
| 1359 | const ubsan_rt_mod = try Package.Module.create(arena, .{ | |
| 1360 | .global_cache_directory = options.global_cache_directory, | |
| 1361 | .paths = .{ | |
| 1362 | .root = .{ | |
| 1363 | .root_dir = options.zig_lib_directory, | |
| 1364 | }, | |
| 1365 | .root_src_path = "ubsan_rt.zig", | |
| 1366 | }, | |
| 1367 | .fully_qualified_name = "ubsan_rt", | |
| 1368 | .cc_argv = &.{}, | |
| 1369 | .inherited = .{}, | |
| 1370 | .global = options.config, | |
| 1371 | .parent = options.root_mod, | |
| 1372 | .builtin_mod = options.root_mod.getBuiltinDependency(), | |
| 1373 | .builtin_modules = null, // `builtin_mod` is set | |
| 1374 | }); | |
| 1375 | try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod); | |
| 1376 | } | |
| 1377 | ||
| 1326 | 1378 | if (options.verbose_llvm_cpu_features) { |
| 1327 | 1379 | if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { |
| 1328 | 1380 | const target = options.root_mod.resolved_target.result; |
| ... | ... | @@ -1499,7 +1551,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1499 | 1551 | .windows_libs = windows_libs, |
| 1500 | 1552 | .version = options.version, |
| 1501 | 1553 | .libc_installation = libc_dirs.libc_installation, |
| 1502 | .include_compiler_rt = include_compiler_rt, | |
| 1554 | .compiler_rt_strat = compiler_rt_strat, | |
| 1555 | .ubsan_rt_strat = ubsan_rt_strat, | |
| 1503 | 1556 | .link_inputs = options.link_inputs, |
| 1504 | 1557 | .framework_dirs = options.framework_dirs, |
| 1505 | 1558 | .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, |
| ... | ... | @@ -1525,6 +1578,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1525 | 1578 | comp.config.any_unwind_tables = any_unwind_tables; |
| 1526 | 1579 | comp.config.any_non_single_threaded = any_non_single_threaded; |
| 1527 | 1580 | comp.config.any_sanitize_thread = any_sanitize_thread; |
| 1581 | comp.config.any_sanitize_c = any_sanitize_c; | |
| 1528 | 1582 | comp.config.any_fuzz = any_fuzz; |
| 1529 | 1583 | |
| 1530 | 1584 | const lf_open_opts: link.File.OpenOptions = .{ |
| ... | ... | @@ -1871,24 +1925,34 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1871 | 1925 | comp.remaining_prelink_tasks += 1; |
| 1872 | 1926 | } |
| 1873 | 1927 | |
| 1874 | if (comp.include_compiler_rt and capable_of_building_compiler_rt) { | |
| 1875 | if (is_exe_or_dyn_lib) { | |
| 1928 | if (capable_of_building_compiler_rt) { | |
| 1929 | if (comp.compiler_rt_strat == .lib) { | |
| 1876 | 1930 | log.debug("queuing a job to build compiler_rt_lib", .{}); |
| 1877 | 1931 | comp.queued_jobs.compiler_rt_lib = true; |
| 1878 | 1932 | comp.remaining_prelink_tasks += 1; |
| 1879 | } else if (output_mode != .Obj) { | |
| 1933 | } else if (comp.compiler_rt_strat == .obj) { | |
| 1880 | 1934 | log.debug("queuing a job to build compiler_rt_obj", .{}); |
| 1881 | 1935 | // In this case we are making a static library, so we ask |
| 1882 | 1936 | // for a compiler-rt object to put in it. |
| 1883 | 1937 | comp.queued_jobs.compiler_rt_obj = true; |
| 1884 | 1938 | comp.remaining_prelink_tasks += 1; |
| 1885 | 1939 | } |
| 1886 | } | |
| 1887 | 1940 | |
| 1888 | if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) { | |
| 1889 | log.debug("queuing a job to build libfuzzer", .{}); | |
| 1890 | comp.queued_jobs.fuzzer_lib = true; | |
| 1891 | comp.remaining_prelink_tasks += 1; | |
| 1941 | if (comp.ubsan_rt_strat == .lib) { | |
| 1942 | log.debug("queuing a job to build ubsan_rt_lib", .{}); | |
| 1943 | comp.queued_jobs.ubsan_rt_lib = true; | |
| 1944 | comp.remaining_prelink_tasks += 1; | |
| 1945 | } else if (comp.ubsan_rt_strat == .obj) { | |
| 1946 | log.debug("queuing a job to build ubsan_rt_obj", .{}); | |
| 1947 | comp.queued_jobs.ubsan_rt_obj = true; | |
| 1948 | comp.remaining_prelink_tasks += 1; | |
| 1949 | } | |
| 1950 | ||
| 1951 | if (is_exe_or_dyn_lib and comp.config.any_fuzz) { | |
| 1952 | log.debug("queuing a job to build libfuzzer", .{}); | |
| 1953 | comp.queued_jobs.fuzzer_lib = true; | |
| 1954 | comp.remaining_prelink_tasks += 1; | |
| 1955 | } | |
| 1892 | 1956 | } |
| 1893 | 1957 | } |
| 1894 | 1958 | |
| ... | ... | @@ -1937,9 +2001,16 @@ pub fn destroy(comp: *Compilation) void { |
| 1937 | 2001 | if (comp.compiler_rt_obj) |*crt_file| { |
| 1938 | 2002 | crt_file.deinit(gpa); |
| 1939 | 2003 | } |
| 2004 | if (comp.ubsan_rt_lib) |*crt_file| { | |
| 2005 | crt_file.deinit(gpa); | |
| 2006 | } | |
| 2007 | if (comp.ubsan_rt_obj) |*crt_file| { | |
| 2008 | crt_file.deinit(gpa); | |
| 2009 | } | |
| 1940 | 2010 | if (comp.fuzzer_lib) |*crt_file| { |
| 1941 | 2011 | crt_file.deinit(gpa); |
| 1942 | 2012 | } |
| 2013 | ||
| 1943 | 2014 | if (comp.libc_static_lib) |*crt_file| { |
| 1944 | 2015 | crt_file.deinit(gpa); |
| 1945 | 2016 | } |
| ... | ... | @@ -2207,6 +2278,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2207 | 2278 | _ = try pt.importPkg(zcu.main_mod); |
| 2208 | 2279 | } |
| 2209 | 2280 | |
| 2281 | if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { | |
| 2282 | _ = try pt.importPkg(ubsan_rt_mod); | |
| 2283 | } | |
| 2284 | ||
| 2210 | 2285 | if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { |
| 2211 | 2286 | _ = try pt.importPkg(compiler_rt_mod); |
| 2212 | 2287 | } |
| ... | ... | @@ -2248,6 +2323,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2248 | 2323 | try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); |
| 2249 | 2324 | zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod); |
| 2250 | 2325 | } |
| 2326 | ||
| 2327 | if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { | |
| 2328 | try comp.queueJob(.{ .analyze_mod = ubsan_rt_mod }); | |
| 2329 | zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod); | |
| 2330 | } | |
| 2251 | 2331 | } |
| 2252 | 2332 | |
| 2253 | 2333 | try comp.performAllTheWork(main_progress_node); |
| ... | ... | @@ -2592,7 +2672,8 @@ fn addNonIncrementalStuffToCacheManifest( |
| 2592 | 2672 | man.hash.addOptional(comp.version); |
| 2593 | 2673 | man.hash.add(comp.link_eh_frame_hdr); |
| 2594 | 2674 | man.hash.add(comp.skip_linker_dependencies); |
| 2595 | man.hash.add(comp.include_compiler_rt); | |
| 2675 | man.hash.add(comp.compiler_rt_strat); | |
| 2676 | man.hash.add(comp.ubsan_rt_strat); | |
| 2596 | 2677 | man.hash.add(comp.rc_includes); |
| 2597 | 2678 | man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); |
| 2598 | 2679 | man.hash.addListOfBytes(comp.framework_dirs); |
| ... | ... | @@ -3683,6 +3764,14 @@ fn performAllTheWorkInner( |
| 3683 | 3764 | comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node }); |
| 3684 | 3765 | } |
| 3685 | 3766 | |
| 3767 | if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) { | |
| 3768 | comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Lib, false, &comp.ubsan_rt_lib, main_progress_node }); | |
| 3769 | } | |
| 3770 | ||
| 3771 | if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) { | |
| 3772 | comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Obj, false, &comp.ubsan_rt_obj, main_progress_node }); | |
| 3773 | } | |
| 3774 | ||
| 3686 | 3775 | if (comp.queued_jobs.glibc_shared_objects) { |
| 3687 | 3776 | comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node }); |
| 3688 | 3777 | } |
| ... | ... | @@ -5916,7 +6005,11 @@ pub fn addCCArgs( |
| 5916 | 6005 | // These args have to be added after the `-fsanitize` arg or |
| 5917 | 6006 | // they won't take effect. |
| 5918 | 6007 | if (mod.sanitize_c) { |
| 5919 | try argv.append("-fsanitize-trap=undefined"); | |
| 6008 | // This check requires implementing the Itanium C++ ABI. | |
| 6009 | // We would make it `-fsanitize-trap=vptr`, however this check requires | |
| 6010 | // a full runtime due to the type hashing involved. | |
| 6011 | try argv.append("-fno-sanitize=vptr"); | |
| 6012 | ||
| 5920 | 6013 | // It is very common, and well-defined, for a pointer on one side of a C ABI |
| 5921 | 6014 | // to have a different but compatible element type. Examples include: |
| 5922 | 6015 | // `char*` vs `uint8_t*` on a system with 8-bit bytes |
| ... | ... | @@ -5925,6 +6018,19 @@ pub fn addCCArgs( |
| 5925 | 6018 | // Without this flag, Clang would invoke UBSAN when such an extern |
| 5926 | 6019 | // function was called. |
| 5927 | 6020 | try argv.append("-fno-sanitize=function"); |
| 6021 | ||
| 6022 | // It's recommended to use the minimal runtime in production environments | |
| 6023 | // due to the security implications of the full runtime. The minimal runtime | |
| 6024 | // doesn't provide much benefit over simply trapping. | |
| 6025 | if (mod.optimize_mode == .ReleaseSafe) { | |
| 6026 | try argv.append("-fsanitize-trap=undefined"); | |
| 6027 | } | |
| 6028 | ||
| 6029 | // This is necessary because, by default, Clang instructs LLVM to embed a COFF link | |
| 6030 | // dependency on `libclang_rt.ubsan_standalone.a` when the UBSan runtime is used. | |
| 6031 | if (target.os.tag == .windows) { | |
| 6032 | try argv.append("-fno-rtlib-defaultlib"); | |
| 6033 | } | |
| 5928 | 6034 | } |
| 5929 | 6035 | } |
| 5930 | 6036 |
src/Compilation/Config.zig+3| ... | ... | @@ -32,6 +32,7 @@ any_non_single_threaded: bool, |
| 32 | 32 | /// per-Module setting. |
| 33 | 33 | any_error_tracing: bool, |
| 34 | 34 | any_sanitize_thread: bool, |
| 35 | any_sanitize_c: bool, | |
| 35 | 36 | any_fuzz: bool, |
| 36 | 37 | pie: bool, |
| 37 | 38 | /// If this is true then linker code is responsible for making an LLVM IR |
| ... | ... | @@ -87,6 +88,7 @@ pub const Options = struct { |
| 87 | 88 | ensure_libcpp_on_non_freestanding: bool = false, |
| 88 | 89 | any_non_single_threaded: bool = false, |
| 89 | 90 | any_sanitize_thread: bool = false, |
| 91 | any_sanitize_c: bool = false, | |
| 90 | 92 | any_fuzz: bool = false, |
| 91 | 93 | any_unwind_tables: bool = false, |
| 92 | 94 | any_dyn_libs: bool = false, |
| ... | ... | @@ -476,6 +478,7 @@ pub fn resolve(options: Options) ResolveError!Config { |
| 476 | 478 | .any_non_single_threaded = options.any_non_single_threaded, |
| 477 | 479 | .any_error_tracing = any_error_tracing, |
| 478 | 480 | .any_sanitize_thread = options.any_sanitize_thread, |
| 481 | .any_sanitize_c = options.any_sanitize_c, | |
| 479 | 482 | .any_fuzz = options.any_fuzz, |
| 480 | 483 | .san_cov_trace_pc_guard = options.san_cov_trace_pc_guard, |
| 481 | 484 | .root_error_tracing = root_error_tracing, |
src/Zcu.zig+1-1| ... | ... | @@ -175,7 +175,7 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi |
| 175 | 175 | |
| 176 | 176 | /// These are the modules which we initially queue for analysis in `Compilation.update`. |
| 177 | 177 | /// `resolveReferences` will use these as the root of its reachability traversal. |
| 178 | analysis_roots: std.BoundedArray(*Package.Module, 3) = .{}, | |
| 178 | analysis_roots: std.BoundedArray(*Package.Module, 4) = .{}, | |
| 179 | 179 | /// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and |
| 180 | 180 | /// reset to `null` when any semantic analysis occurs (since this invalidates the data). |
| 181 | 181 | /// Allocated into `gpa`. |
src/link.zig+8-1| ... | ... | @@ -1102,11 +1102,16 @@ pub const File = struct { |
| 1102 | 1102 | |
| 1103 | 1103 | log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"}); |
| 1104 | 1104 | |
| 1105 | const compiler_rt_path: ?Path = if (comp.include_compiler_rt) | |
| 1105 | const compiler_rt_path: ?Path = if (comp.compiler_rt_strat == .obj) | |
| 1106 | 1106 | comp.compiler_rt_obj.?.full_object_path |
| 1107 | 1107 | else |
| 1108 | 1108 | null; |
| 1109 | 1109 | |
| 1110 | const ubsan_rt_path: ?Path = if (comp.ubsan_rt_strat == .obj) | |
| 1111 | comp.ubsan_rt_obj.?.full_object_path | |
| 1112 | else | |
| 1113 | null; | |
| 1114 | ||
| 1110 | 1115 | // This function follows the same pattern as link.Elf.linkWithLLD so if you want some |
| 1111 | 1116 | // insight as to what's going on here you can read that function body which is more |
| 1112 | 1117 | // well-commented. |
| ... | ... | @@ -1136,6 +1141,7 @@ pub const File = struct { |
| 1136 | 1141 | } |
| 1137 | 1142 | try man.addOptionalFile(zcu_obj_path); |
| 1138 | 1143 | try man.addOptionalFilePath(compiler_rt_path); |
| 1144 | try man.addOptionalFilePath(ubsan_rt_path); | |
| 1139 | 1145 | |
| 1140 | 1146 | // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. |
| 1141 | 1147 | _ = try man.hit(); |
| ... | ... | @@ -1181,6 +1187,7 @@ pub const File = struct { |
| 1181 | 1187 | } |
| 1182 | 1188 | if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); |
| 1183 | 1189 | 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)); | |
| 1184 | 1191 | |
| 1185 | 1192 | if (comp.verbose_link) { |
| 1186 | 1193 | 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: |
| 2162 | 2162 | try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); |
| 2163 | 2163 | } |
| 2164 | 2164 | |
| 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 | ||
| 2165 | 2174 | if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { |
| 2166 | 2175 | if (!comp.config.link_libc) { |
| 2167 | 2176 | 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 |
| 1541 | 1541 | if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; |
| 1542 | 1542 | break :blk null; |
| 1543 | 1543 | }; |
| 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 | }; | |
| 1544 | 1549 | |
| 1545 | 1550 | // Here we want to determine whether we can save time by not invoking LLD when the |
| 1546 | 1551 | // 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 |
| 1575 | 1580 | } |
| 1576 | 1581 | try man.addOptionalFile(module_obj_path); |
| 1577 | 1582 | try man.addOptionalFilePath(compiler_rt_path); |
| 1583 | try man.addOptionalFilePath(ubsan_rt_path); | |
| 1578 | 1584 | try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); |
| 1579 | 1585 | try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); |
| 1580 | 1586 | |
| ... | ... | @@ -1974,6 +1980,10 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1974 | 1980 | try argv.append(try lib.full_object_path.toString(arena)); |
| 1975 | 1981 | } |
| 1976 | 1982 | |
| 1983 | if (ubsan_rt_path) |p| { | |
| 1984 | try argv.append(try p.toString(arena)); | |
| 1985 | } | |
| 1986 | ||
| 1977 | 1987 | // libc |
| 1978 | 1988 | if (is_exe_or_dyn_lib and |
| 1979 | 1989 | !comp.skip_linker_dependencies and |
src/link/MachO.zig+24-2| ... | ... | @@ -344,11 +344,21 @@ pub fn deinit(self: *MachO) void { |
| 344 | 344 | self.thunks.deinit(gpa); |
| 345 | 345 | } |
| 346 | 346 | |
| 347 | pub fn flush(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { | |
| 347 | pub fn flush( | |
| 348 | self: *MachO, | |
| 349 | arena: Allocator, | |
| 350 | tid: Zcu.PerThread.Id, | |
| 351 | prog_node: std.Progress.Node, | |
| 352 | ) link.File.FlushError!void { | |
| 348 | 353 | try self.flushModule(arena, tid, prog_node); |
| 349 | 354 | } |
| 350 | 355 | |
| 351 | pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { | |
| 356 | pub fn flushModule( | |
| 357 | self: *MachO, | |
| 358 | arena: Allocator, | |
| 359 | tid: Zcu.PerThread.Id, | |
| 360 | prog_node: std.Progress.Node, | |
| 361 | ) link.File.FlushError!void { | |
| 352 | 362 | const tracy = trace(@src()); |
| 353 | 363 | defer tracy.end(); |
| 354 | 364 | |
| ... | ... | @@ -409,6 +419,16 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n |
| 409 | 419 | try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path)); |
| 410 | 420 | } |
| 411 | 421 | |
| 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 | ||
| 412 | 432 | for (positionals.items) |link_input| { |
| 413 | 433 | self.classifyInputFile(link_input) catch |err| |
| 414 | 434 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); |
| ... | ... | @@ -813,6 +833,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { |
| 813 | 833 | |
| 814 | 834 | if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); |
| 815 | 835 | 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)); | |
| 816 | 838 | } |
| 817 | 839 | |
| 818 | 840 | Compilation.dump_argv(argv.items); |
src/link/MachO/relocatable.zig+5-1| ... | ... | @@ -93,10 +93,14 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? |
| 93 | 93 | |
| 94 | 94 | if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); |
| 95 | 95 | |
| 96 | if (comp.include_compiler_rt) { | |
| 96 | if (comp.compiler_rt_strat == .obj) { | |
| 97 | 97 | try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path)); |
| 98 | 98 | } |
| 99 | 99 | |
| 100 | if (comp.ubsan_rt_strat == .obj) { | |
| 101 | try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path)); | |
| 102 | } | |
| 103 | ||
| 100 | 104 | for (positionals.items) |link_input| { |
| 101 | 105 | macho_file.classifyInputFile(link_input) catch |err| |
| 102 | 106 | 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: |
| 3879 | 3879 | if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; |
| 3880 | 3880 | break :blk null; |
| 3881 | 3881 | }; |
| 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 | }; | |
| 3882 | 3887 | |
| 3883 | 3888 | const id_symlink_basename = "lld.id"; |
| 3884 | 3889 | |
| ... | ... | @@ -3901,6 +3906,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 3901 | 3906 | } |
| 3902 | 3907 | try man.addOptionalFile(module_obj_path); |
| 3903 | 3908 | try man.addOptionalFilePath(compiler_rt_path); |
| 3909 | try man.addOptionalFilePath(ubsan_rt_path); | |
| 3904 | 3910 | man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); |
| 3905 | 3911 | man.hash.add(wasm.base.stack_size); |
| 3906 | 3912 | man.hash.add(wasm.base.build_id); |
| ... | ... | @@ -4148,6 +4154,10 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 4148 | 4154 | try argv.append(try p.toString(arena)); |
| 4149 | 4155 | } |
| 4150 | 4156 | |
| 4157 | if (ubsan_rt_path) |p| { | |
| 4158 | try argv.append(try p.toStringZ(arena)); | |
| 4159 | } | |
| 4160 | ||
| 4151 | 4161 | if (comp.verbose_link) { |
| 4152 | 4162 | // Skip over our own name so that the LLD linker name is the first argv item. |
| 4153 | 4163 | Compilation.dump_argv(argv.items[1..]); |
src/main.zig+8| ... | ... | @@ -561,6 +561,8 @@ const usage_build_generic = |
| 561 | 561 | \\ -fno-lld Prevent using LLD as the linker |
| 562 | 562 | \\ -fcompiler-rt Always include compiler-rt symbols in output |
| 563 | 563 | \\ -fno-compiler-rt Prevent including compiler-rt symbols in output |
| 564 | \\ -fubsan-rt Always include ubsan-rt symbols in the output | |
| 565 | \\ -fno-ubsan-rt Prevent including ubsan-rt symbols in the output | |
| 564 | 566 | \\ -rdynamic Add all symbols to the dynamic symbol table |
| 565 | 567 | \\ -feach-lib-rpath Ensure adding rpath for each used dynamic library |
| 566 | 568 | \\ -fno-each-lib-rpath Prevent adding rpath for each used dynamic library |
| ... | ... | @@ -849,6 +851,7 @@ fn buildOutputType( |
| 849 | 851 | var emit_h: Emit = .no; |
| 850 | 852 | var soname: SOName = undefined; |
| 851 | 853 | var want_compiler_rt: ?bool = null; |
| 854 | var want_ubsan_rt: ?bool = null; | |
| 852 | 855 | var linker_script: ?[]const u8 = null; |
| 853 | 856 | var version_script: ?[]const u8 = null; |
| 854 | 857 | var linker_repro: ?bool = null; |
| ... | ... | @@ -1376,6 +1379,10 @@ fn buildOutputType( |
| 1376 | 1379 | want_compiler_rt = true; |
| 1377 | 1380 | } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { |
| 1378 | 1381 | want_compiler_rt = false; |
| 1382 | } else if (mem.eql(u8, arg, "-fubsan-rt")) { | |
| 1383 | want_ubsan_rt = true; | |
| 1384 | } else if (mem.eql(u8, arg, "-fno-ubsan-rt")) { | |
| 1385 | want_ubsan_rt = false; | |
| 1379 | 1386 | } else if (mem.eql(u8, arg, "-feach-lib-rpath")) { |
| 1380 | 1387 | create_module.each_lib_rpath = true; |
| 1381 | 1388 | } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { |
| ... | ... | @@ -3504,6 +3511,7 @@ fn buildOutputType( |
| 3504 | 3511 | .windows_lib_names = create_module.windows_libs.keys(), |
| 3505 | 3512 | .wasi_emulated_libs = create_module.wasi_emulated_libs.items, |
| 3506 | 3513 | .want_compiler_rt = want_compiler_rt, |
| 3514 | .want_ubsan_rt = want_ubsan_rt, | |
| 3507 | 3515 | .hash_style = hash_style, |
| 3508 | 3516 | .linker_script = linker_script, |
| 3509 | 3517 | .version_script = version_script, |
test/link/elf.zig+6| ... | ... | @@ -2049,6 +2049,9 @@ fn testLargeBss(b: *Build, opts: Options) *Step { |
| 2049 | 2049 | \\} |
| 2050 | 2050 | , &.{}); |
| 2051 | 2051 | exe.linkLibC(); |
| 2052 | // Disabled to work around the ELF linker crashing. | |
| 2053 | // Can be reproduced on a x86_64-linux host by commenting out the line below. | |
| 2054 | exe.root_module.sanitize_c = false; | |
| 2052 | 2055 | |
| 2053 | 2056 | const run = addRunArtifact(exe); |
| 2054 | 2057 | run.expectExitCode(0); |
| ... | ... | @@ -3552,6 +3555,9 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step { |
| 3552 | 3555 | \\} |
| 3553 | 3556 | , &.{}); |
| 3554 | 3557 | exe.linkLibC(); |
| 3558 | // Disabled to work around the ELF linker crashing. | |
| 3559 | // Can be reproduced on a x86_64-linux host by commenting out the line below. | |
| 3560 | exe.root_module.sanitize_c = false; | |
| 3555 | 3561 | |
| 3556 | 3562 | const run = addRunArtifact(exe); |
| 3557 | 3563 | run.expectStdOutEqual("3 0 5 0 0 0\n"); |
test/link/glibc_compat/build.zig+12| ... | ... | @@ -22,6 +22,10 @@ pub fn build(b: *std.Build) void { |
| 22 | 22 | .link_libc = true, |
| 23 | 23 | }), |
| 24 | 24 | }); |
| 25 | // We disable UBSAN for these tests as the libc being tested here is | |
| 26 | // so old, it doesn't even support compiling our UBSAN implementation. | |
| 27 | exe.bundle_ubsan_rt = false; | |
| 28 | exe.root_module.sanitize_c = false; | |
| 25 | 29 | exe.root_module.addCSourceFile(.{ .file = b.path("main.c") }); |
| 26 | 30 | // TODO: actually test the output |
| 27 | 31 | _ = exe.getEmittedBin(); |
| ... | ... | @@ -62,6 +66,10 @@ pub fn build(b: *std.Build) void { |
| 62 | 66 | .link_libc = true, |
| 63 | 67 | }), |
| 64 | 68 | }); |
| 69 | // We disable UBSAN for these tests as the libc being tested here is | |
| 70 | // so old, it doesn't even support compiling our UBSAN implementation. | |
| 71 | exe.bundle_ubsan_rt = false; | |
| 72 | exe.root_module.sanitize_c = false; | |
| 65 | 73 | exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") }); |
| 66 | 74 | |
| 67 | 75 | // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig |
| ... | ... | @@ -161,6 +169,10 @@ pub fn build(b: *std.Build) void { |
| 161 | 169 | .link_libc = true, |
| 162 | 170 | }), |
| 163 | 171 | }); |
| 172 | // We disable UBSAN for these tests as the libc being tested here is | |
| 173 | // so old, it doesn't even support compiling our UBSAN implementation. | |
| 174 | exe.bundle_ubsan_rt = false; | |
| 175 | exe.root_module.sanitize_c = false; | |
| 164 | 176 | |
| 165 | 177 | // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig |
| 166 | 178 | // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453 |
test/link/wasm/export-data/build.zig+3| ... | ... | @@ -13,6 +13,9 @@ pub fn build(b: *std.Build) void { |
| 13 | 13 | }), |
| 14 | 14 | }); |
| 15 | 15 | lib.entry = .disabled; |
| 16 | // Disabled to work around the Wasm linker crashing. | |
| 17 | // Can be reproduced by commenting out the line below. | |
| 18 | lib.bundle_ubsan_rt = false; | |
| 16 | 19 | lib.use_lld = false; |
| 17 | 20 | lib.root_module.export_symbol_names = &.{ "foo", "bar" }; |
| 18 | 21 | // Object being linked has neither functions nor globals named "foo" or "bar" and |
test/link/wasm/export/build.zig+6| ... | ... | @@ -19,6 +19,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize |
| 19 | 19 | no_export.entry = .disabled; |
| 20 | 20 | no_export.use_llvm = false; |
| 21 | 21 | no_export.use_lld = false; |
| 22 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 23 | no_export.bundle_ubsan_rt = false; | |
| 22 | 24 | |
| 23 | 25 | const dynamic_export = b.addExecutable(.{ |
| 24 | 26 | .name = "dynamic", |
| ... | ... | @@ -32,6 +34,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize |
| 32 | 34 | dynamic_export.rdynamic = true; |
| 33 | 35 | dynamic_export.use_llvm = false; |
| 34 | 36 | dynamic_export.use_lld = false; |
| 37 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 38 | dynamic_export.bundle_ubsan_rt = false; | |
| 35 | 39 | |
| 36 | 40 | const force_export = b.addExecutable(.{ |
| 37 | 41 | .name = "force", |
| ... | ... | @@ -45,6 +49,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize |
| 45 | 49 | force_export.root_module.export_symbol_names = &.{"foo"}; |
| 46 | 50 | force_export.use_llvm = false; |
| 47 | 51 | force_export.use_lld = false; |
| 52 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 53 | force_export.bundle_ubsan_rt = false; | |
| 48 | 54 | |
| 49 | 55 | const check_no_export = no_export.checkObject(); |
| 50 | 56 | check_no_export.checkInHeaders(); |
test/link/wasm/function-table/build.zig+4| ... | ... | @@ -21,6 +21,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize |
| 21 | 21 | export_table.use_lld = false; |
| 22 | 22 | export_table.export_table = true; |
| 23 | 23 | export_table.link_gc_sections = false; |
| 24 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 25 | export_table.bundle_ubsan_rt = false; | |
| 24 | 26 | |
| 25 | 27 | const regular_table = b.addExecutable(.{ |
| 26 | 28 | .name = "regular_table", |
| ... | ... | @@ -34,6 +36,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize |
| 34 | 36 | regular_table.use_llvm = false; |
| 35 | 37 | regular_table.use_lld = false; |
| 36 | 38 | regular_table.link_gc_sections = false; // Ensure function table is not empty |
| 39 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 40 | regular_table.bundle_ubsan_rt = false; | |
| 37 | 41 | |
| 38 | 42 | const check_export = export_table.checkObject(); |
| 39 | 43 | const check_regular = regular_table.checkObject(); |
test/link/wasm/shared-memory/build.zig+2| ... | ... | @@ -31,6 +31,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt |
| 31 | 31 | exe.shared_memory = true; |
| 32 | 32 | exe.max_memory = 67108864; |
| 33 | 33 | exe.root_module.export_symbol_names = &.{"foo"}; |
| 34 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 35 | exe.bundle_ubsan_rt = false; | |
| 34 | 36 | |
| 35 | 37 | const check_exe = exe.checkObject(); |
| 36 | 38 |
test/link/wasm/type/build.zig+2| ... | ... | @@ -21,6 +21,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize |
| 21 | 21 | exe.use_llvm = false; |
| 22 | 22 | exe.use_lld = false; |
| 23 | 23 | exe.root_module.export_symbol_names = &.{"foo"}; |
| 24 | // Don't pull in ubsan, since we're just expecting a very minimal executable. | |
| 25 | exe.bundle_ubsan_rt = false; | |
| 24 | 26 | b.installArtifact(exe); |
| 25 | 27 | |
| 26 | 28 | const check_exe = exe.checkObject(); |
test/src/StackTrace.zig+1| ... | ... | @@ -81,6 +81,7 @@ fn addExpect( |
| 81 | 81 | }), |
| 82 | 82 | .use_llvm = use_llvm, |
| 83 | 83 | }); |
| 84 | exe.bundle_ubsan_rt = false; | |
| 84 | 85 | |
| 85 | 86 | const run = b.addRunArtifact(exe); |
| 86 | 87 | run.removeEnvironmentVariable("CLICOLOR_FORCE"); |
tools/incr-check.zig+1| ... | ... | @@ -108,6 +108,7 @@ pub fn main() !void { |
| 108 | 108 | "build-exe", |
| 109 | 109 | case.root_source_file, |
| 110 | 110 | "-fincremental", |
| 111 | "-fno-ubsan-rt", | |
| 111 | 112 | "-target", |
| 112 | 113 | target.query, |
| 113 | 114 | "--cache-dir", |