| author | |
| committer | |
| log | 95720f007bb0dacc8a7cecb621c322d574c61d00 |
| tree | e3746e6d83a709cd86f93eaf7e59578007466d88 |
| parent | babee5f73c01c220e3fb3901eb1a70149f94258b |
11 files changed, 655 insertions(+), 512 deletions(-)
lib/std/std.zig-1| ... | @@ -44,7 +44,6 @@ pub const Thread = @import("Thread.zig"); | ... | @@ -44,7 +44,6 @@ pub const Thread = @import("Thread.zig"); |
| 44 | pub const Treap = @import("treap.zig").Treap; | 44 | pub const Treap = @import("treap.zig").Treap; |
| 45 | pub const Tz = tz.Tz; | 45 | pub const Tz = tz.Tz; |
| 46 | pub const Uri = @import("Uri.zig"); | 46 | pub const Uri = @import("Uri.zig"); |
| 47 | pub const ubsan = @import("ubsan.zig"); | ||
| 48 | 47 | ||
| 49 | pub const array_hash_map = @import("array_hash_map.zig"); | 48 | pub const array_hash_map = @import("array_hash_map.zig"); |
| 50 | pub const atomic = @import("atomic.zig"); | 49 | pub const atomic = @import("atomic.zig"); |
lib/std/ubsan.zig deleted-509| ... | @@ -1,509 +0,0 @@ | ||
| 1 | //! Minimal UBSan Runtime | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const builtin = @import("builtin"); | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | |||
| 7 | const SourceLocation = extern struct { | ||
| 8 | file_name: ?[*:0]const u8, | ||
| 9 | line: u32, | ||
| 10 | col: u32, | ||
| 11 | }; | ||
| 12 | |||
| 13 | const 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 | |||
| 46 | const ValueHandle = *const opaque { | ||
| 47 | fn getValue(handle: ValueHandle, data: anytype) Value { | ||
| 48 | return .{ .handle = handle, .type_descriptor = data.type_descriptor }; | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | |||
| 52 | const 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 | |||
| 129 | const OverflowData = extern struct { | ||
| 130 | loc: SourceLocation, | ||
| 131 | type_descriptor: *const TypeDescriptor, | ||
| 132 | }; | ||
| 133 | |||
| 134 | fn 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 | |||
| 163 | fn 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 | |||
| 174 | fn 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 | |||
| 191 | const AlignmentAssumptionData = extern struct { | ||
| 192 | loc: SourceLocation, | ||
| 193 | assumption_loc: SourceLocation, | ||
| 194 | type_descriptor: *const TypeDescriptor, | ||
| 195 | }; | ||
| 196 | |||
| 197 | fn 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 | |||
| 226 | const ShiftOobData = extern struct { | ||
| 227 | loc: SourceLocation, | ||
| 228 | lhs_type: *const TypeDescriptor, | ||
| 229 | rhs_type: *const TypeDescriptor, | ||
| 230 | }; | ||
| 231 | |||
| 232 | fn 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 | |||
| 263 | const OutOfBoundsData = extern struct { | ||
| 264 | loc: SourceLocation, | ||
| 265 | array_type: *const TypeDescriptor, | ||
| 266 | index_type: *const TypeDescriptor, | ||
| 267 | }; | ||
| 268 | |||
| 269 | fn 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 | |||
| 277 | const PointerOverflowData = extern struct { | ||
| 278 | loc: SourceLocation, | ||
| 279 | }; | ||
| 280 | |||
| 281 | fn 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 | |||
| 304 | const 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 | |||
| 340 | fn 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 | |||
| 365 | const UnreachableData = extern struct { | ||
| 366 | loc: SourceLocation, | ||
| 367 | }; | ||
| 368 | |||
| 369 | fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn { | ||
| 370 | logMessage("execution reached an unreachable program point", .{}); | ||
| 371 | } | ||
| 372 | |||
| 373 | fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn { | ||
| 374 | logMessage("execution reached the end of a value-returning function without returning a value", .{}); | ||
| 375 | } | ||
| 376 | |||
| 377 | const NonNullReturnData = extern struct { | ||
| 378 | attribute_loc: SourceLocation, | ||
| 379 | }; | ||
| 380 | |||
| 381 | fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn { | ||
| 382 | logMessage("null pointer returned from function declared to never return null", .{}); | ||
| 383 | } | ||
| 384 | |||
| 385 | const NonNullArgData = extern struct { | ||
| 386 | loc: SourceLocation, | ||
| 387 | attribute_loc: SourceLocation, | ||
| 388 | arg_index: i32, | ||
| 389 | }; | ||
| 390 | |||
| 391 | fn 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 | |||
| 398 | const InvalidValueData = extern struct { | ||
| 399 | loc: SourceLocation, | ||
| 400 | type_descriptor: *const TypeDescriptor, | ||
| 401 | }; | ||
| 402 | |||
| 403 | fn 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 | |||
| 412 | fn 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 | |||
| 420 | inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn { | ||
| 421 | std.debug.panicExtra(null, @returnAddress(), fmt, args); | ||
| 422 | } | ||
| 423 | |||
| 424 | fn 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 | |||
| 440 | fn 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 | |||
| 457 | fn 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 | |||
| 466 | comptime { | ||
| 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 | |||
| 3 | const std = @import("std"); | ||
| 4 | const builtin = @import("builtin"); | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | |||
| 7 | const SourceLocation = extern struct { | ||
| 8 | file_name: ?[*:0]const u8, | ||
| 9 | line: u32, | ||
| 10 | col: u32, | ||
| 11 | }; | ||
| 12 | |||
| 13 | const 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 | |||
| 46 | const ValueHandle = *const opaque { | ||
| 47 | fn getValue(handle: ValueHandle, data: anytype) Value { | ||
| 48 | return .{ .handle = handle, .type_descriptor = data.type_descriptor }; | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | |||
| 52 | const 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 | |||
| 129 | const OverflowData = extern struct { | ||
| 130 | loc: SourceLocation, | ||
| 131 | type_descriptor: *const TypeDescriptor, | ||
| 132 | }; | ||
| 133 | |||
| 134 | fn 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 | |||
| 163 | fn 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 | |||
| 174 | fn 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 | |||
| 191 | const AlignmentAssumptionData = extern struct { | ||
| 192 | loc: SourceLocation, | ||
| 193 | assumption_loc: SourceLocation, | ||
| 194 | type_descriptor: *const TypeDescriptor, | ||
| 195 | }; | ||
| 196 | |||
| 197 | fn 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 | |||
| 226 | const ShiftOobData = extern struct { | ||
| 227 | loc: SourceLocation, | ||
| 228 | lhs_type: *const TypeDescriptor, | ||
| 229 | rhs_type: *const TypeDescriptor, | ||
| 230 | }; | ||
| 231 | |||
| 232 | fn 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 | |||
| 263 | const OutOfBoundsData = extern struct { | ||
| 264 | loc: SourceLocation, | ||
| 265 | array_type: *const TypeDescriptor, | ||
| 266 | index_type: *const TypeDescriptor, | ||
| 267 | }; | ||
| 268 | |||
| 269 | fn 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 | |||
| 277 | const PointerOverflowData = extern struct { | ||
| 278 | loc: SourceLocation, | ||
| 279 | }; | ||
| 280 | |||
| 281 | fn 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 | |||
| 304 | const 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 | |||
| 340 | fn 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 | |||
| 365 | const UnreachableData = extern struct { | ||
| 366 | loc: SourceLocation, | ||
| 367 | }; | ||
| 368 | |||
| 369 | fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn { | ||
| 370 | logMessage("execution reached an unreachable program point", .{}); | ||
| 371 | } | ||
| 372 | |||
| 373 | fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn { | ||
| 374 | logMessage("execution reached the end of a value-returning function without returning a value", .{}); | ||
| 375 | } | ||
| 376 | |||
| 377 | const NonNullReturnData = extern struct { | ||
| 378 | attribute_loc: SourceLocation, | ||
| 379 | }; | ||
| 380 | |||
| 381 | fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn { | ||
| 382 | logMessage("null pointer returned from function declared to never return null", .{}); | ||
| 383 | } | ||
| 384 | |||
| 385 | const NonNullArgData = extern struct { | ||
| 386 | loc: SourceLocation, | ||
| 387 | attribute_loc: SourceLocation, | ||
| 388 | arg_index: i32, | ||
| 389 | }; | ||
| 390 | |||
| 391 | fn 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 | |||
| 398 | const InvalidValueData = extern struct { | ||
| 399 | loc: SourceLocation, | ||
| 400 | type_descriptor: *const TypeDescriptor, | ||
| 401 | }; | ||
| 402 | |||
| 403 | fn 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 | |||
| 412 | fn 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 | |||
| 420 | inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn { | ||
| 421 | std.debug.panicExtra(null, @returnAddress(), fmt, args); | ||
| 422 | } | ||
| 423 | |||
| 424 | fn 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 | |||
| 440 | fn 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 | |||
| 457 | fn 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 | |||
| 466 | comptime { | ||
| 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, | ... | @@ -79,6 +79,7 @@ implib_emit: ?Path, |
| 79 | docs_emit: ?Path, | 79 | docs_emit: ?Path, |
| 80 | root_name: [:0]const u8, | 80 | root_name: [:0]const u8, |
| 81 | include_compiler_rt: bool, | 81 | include_compiler_rt: bool, |
| 82 | include_ubsan_rt: bool, | ||
| 82 | /// Resolved into known paths, any GNU ld scripts already resolved. | 83 | /// Resolved into known paths, any GNU ld scripts already resolved. |
| 83 | link_inputs: []const link.Input, | 84 | link_inputs: []const link.Input, |
| 84 | /// Needed only for passing -F args to clang. | 85 | /// Needed only for passing -F args to clang. |
| ... | @@ -226,6 +227,12 @@ libunwind_static_lib: ?CrtFile = null, | ... | @@ -226,6 +227,12 @@ libunwind_static_lib: ?CrtFile = null, |
| 226 | /// Populated when we build the TSAN library. A Job to build this is placed in the queue | 227 | /// Populated when we build the TSAN library. A Job to build this is placed in the queue |
| 227 | /// and resolved before calling linker.flush(). | 228 | /// and resolved before calling linker.flush(). |
| 228 | tsan_lib: ?CrtFile = null, | 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 | /// Populated when we build the libc static library. A Job to build this is placed in the queue | 236 | /// Populated when we build the libc static library. A Job to build this is placed in the queue |
| 230 | /// and resolved before calling linker.flush(). | 237 | /// and resolved before calling linker.flush(). |
| 231 | libc_static_lib: ?CrtFile = null, | 238 | libc_static_lib: ?CrtFile = null, |
| ... | @@ -283,6 +290,8 @@ digest: ?[Cache.bin_digest_len]u8 = null, | ... | @@ -283,6 +290,8 @@ digest: ?[Cache.bin_digest_len]u8 = null, |
| 283 | const QueuedJobs = struct { | 290 | const QueuedJobs = struct { |
| 284 | compiler_rt_lib: bool = false, | 291 | compiler_rt_lib: bool = false, |
| 285 | compiler_rt_obj: bool = false, | 292 | compiler_rt_obj: bool = false, |
| 293 | ubsan_rt_lib: bool = false, | ||
| 294 | ubsan_rt_obj: bool = false, | ||
| 286 | fuzzer_lib: bool = false, | 295 | fuzzer_lib: bool = false, |
| 287 | update_builtin_zig: bool, | 296 | update_builtin_zig: bool, |
| 288 | musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false), | 297 | musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false), |
| ... | @@ -789,6 +798,7 @@ pub const MiscTask = enum { | ... | @@ -789,6 +798,7 @@ pub const MiscTask = enum { |
| 789 | libcxx, | 798 | libcxx, |
| 790 | libcxxabi, | 799 | libcxxabi, |
| 791 | libtsan, | 800 | libtsan, |
| 801 | libubsan, | ||
| 792 | libfuzzer, | 802 | libfuzzer, |
| 793 | wasi_libc_crt_file, | 803 | wasi_libc_crt_file, |
| 794 | compiler_rt, | 804 | compiler_rt, |
| ... | @@ -1064,6 +1074,7 @@ pub const CreateOptions = struct { | ... | @@ -1064,6 +1074,7 @@ pub const CreateOptions = struct { |
| 1064 | /// Position Independent Executable. If the output mode is not an | 1074 | /// Position Independent Executable. If the output mode is not an |
| 1065 | /// executable this field is ignored. | 1075 | /// executable this field is ignored. |
| 1066 | want_compiler_rt: ?bool = null, | 1076 | want_compiler_rt: ?bool = null, |
| 1077 | want_ubsan_rt: ?bool = null, | ||
| 1067 | want_lto: ?bool = null, | 1078 | want_lto: ?bool = null, |
| 1068 | function_sections: bool = false, | 1079 | function_sections: bool = false, |
| 1069 | data_sections: bool = false, | 1080 | data_sections: bool = false, |
| ... | @@ -1297,6 +1308,9 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil | ... | @@ -1297,6 +1308,9 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1297 | const include_compiler_rt = options.want_compiler_rt orelse | 1308 | const include_compiler_rt = options.want_compiler_rt orelse |
| 1298 | (!options.skip_linker_dependencies and is_exe_or_dyn_lib); | 1309 | (!options.skip_linker_dependencies and is_exe_or_dyn_lib); |
| 1299 | 1310 | ||
| 1311 | const include_ubsan_rt = options.want_ubsan_rt orelse | ||
| 1312 | (!options.skip_linker_dependencies and is_exe_or_dyn_lib); | ||
| 1313 | |||
| 1300 | if (include_compiler_rt and output_mode == .Obj) { | 1314 | if (include_compiler_rt and output_mode == .Obj) { |
| 1301 | // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` | 1315 | // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` |
| 1302 | // injected into the object. | 1316 | // injected into the object. |
| ... | @@ -1323,6 +1337,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil | ... | @@ -1323,6 +1337,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1323 | try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod); | 1337 | try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod); |
| 1324 | } | 1338 | } |
| 1325 | 1339 | ||
| 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 | |||
| 1326 | if (options.verbose_llvm_cpu_features) { | 1360 | if (options.verbose_llvm_cpu_features) { |
| 1327 | if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { | 1361 | if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { |
| 1328 | const target = options.root_mod.resolved_target.result; | 1362 | const target = options.root_mod.resolved_target.result; |
| ... | @@ -1500,6 +1534,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil | ... | @@ -1500,6 +1534,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1500 | .version = options.version, | 1534 | .version = options.version, |
| 1501 | .libc_installation = libc_dirs.libc_installation, | 1535 | .libc_installation = libc_dirs.libc_installation, |
| 1502 | .include_compiler_rt = include_compiler_rt, | 1536 | .include_compiler_rt = include_compiler_rt, |
| 1537 | .include_ubsan_rt = include_ubsan_rt, | ||
| 1503 | .link_inputs = options.link_inputs, | 1538 | .link_inputs = options.link_inputs, |
| 1504 | .framework_dirs = options.framework_dirs, | 1539 | .framework_dirs = options.framework_dirs, |
| 1505 | .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, | 1540 | .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, |
| ... | @@ -1885,6 +1920,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil | ... | @@ -1885,6 +1920,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1885 | } | 1920 | } |
| 1886 | } | 1921 | } |
| 1887 | 1922 | ||
| 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 | |||
| 1888 | if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) { | 1933 | 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", .{}); | 1934 | log.debug("queuing a job to build libfuzzer", .{}); |
| 1890 | comp.queued_jobs.fuzzer_lib = true; | 1935 | comp.queued_jobs.fuzzer_lib = true; |
| ... | @@ -1937,9 +1982,16 @@ pub fn destroy(comp: *Compilation) void { | ... | @@ -1937,9 +1982,16 @@ pub fn destroy(comp: *Compilation) void { |
| 1937 | if (comp.compiler_rt_obj) |*crt_file| { | 1982 | if (comp.compiler_rt_obj) |*crt_file| { |
| 1938 | crt_file.deinit(gpa); | 1983 | crt_file.deinit(gpa); |
| 1939 | } | 1984 | } |
| 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 | } | ||
| 1940 | if (comp.fuzzer_lib) |*crt_file| { | 1991 | if (comp.fuzzer_lib) |*crt_file| { |
| 1941 | crt_file.deinit(gpa); | 1992 | crt_file.deinit(gpa); |
| 1942 | } | 1993 | } |
| 1994 | |||
| 1943 | if (comp.libc_static_lib) |*crt_file| { | 1995 | if (comp.libc_static_lib) |*crt_file| { |
| 1944 | crt_file.deinit(gpa); | 1996 | crt_file.deinit(gpa); |
| 1945 | } | 1997 | } |
| ... | @@ -2207,6 +2259,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { | ... | @@ -2207,6 +2259,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2207 | _ = try pt.importPkg(zcu.main_mod); | 2259 | _ = try pt.importPkg(zcu.main_mod); |
| 2208 | } | 2260 | } |
| 2209 | 2261 | ||
| 2262 | if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { | ||
| 2263 | _ = try pt.importPkg(ubsan_rt_mod); | ||
| 2264 | } | ||
| 2265 | |||
| 2210 | if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { | 2266 | if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { |
| 2211 | _ = try pt.importPkg(compiler_rt_mod); | 2267 | _ = try pt.importPkg(compiler_rt_mod); |
| 2212 | } | 2268 | } |
| ... | @@ -2248,6 +2304,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { | ... | @@ -2248,6 +2304,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2248 | try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); | 2304 | try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); |
| 2249 | zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod); | 2305 | zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod); |
| 2250 | } | 2306 | } |
| 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 | } | ||
| 2251 | } | 2312 | } |
| 2252 | 2313 | ||
| 2253 | try comp.performAllTheWork(main_progress_node); | 2314 | try comp.performAllTheWork(main_progress_node); |
| ... | @@ -2593,6 +2654,7 @@ fn addNonIncrementalStuffToCacheManifest( | ... | @@ -2593,6 +2654,7 @@ fn addNonIncrementalStuffToCacheManifest( |
| 2593 | man.hash.add(comp.link_eh_frame_hdr); | 2654 | man.hash.add(comp.link_eh_frame_hdr); |
| 2594 | man.hash.add(comp.skip_linker_dependencies); | 2655 | man.hash.add(comp.skip_linker_dependencies); |
| 2595 | man.hash.add(comp.include_compiler_rt); | 2656 | man.hash.add(comp.include_compiler_rt); |
| 2657 | man.hash.add(comp.include_ubsan_rt); | ||
| 2596 | man.hash.add(comp.rc_includes); | 2658 | man.hash.add(comp.rc_includes); |
| 2597 | man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); | 2659 | man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); |
| 2598 | man.hash.addListOfBytes(comp.framework_dirs); | 2660 | man.hash.addListOfBytes(comp.framework_dirs); |
| ... | @@ -3683,6 +3745,14 @@ fn performAllTheWorkInner( | ... | @@ -3683,6 +3745,14 @@ fn performAllTheWorkInner( |
| 3683 | comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node }); | 3745 | comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node }); |
| 3684 | } | 3746 | } |
| 3685 | 3747 | ||
| 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 | |||
| 3686 | if (comp.queued_jobs.glibc_shared_objects) { | 3756 | if (comp.queued_jobs.glibc_shared_objects) { |
| 3687 | comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node }); | 3757 | comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node }); |
| 3688 | } | 3758 | } |
| ... | @@ -5916,7 +5986,11 @@ pub fn addCCArgs( | ... | @@ -5916,7 +5986,11 @@ pub fn addCCArgs( |
| 5916 | // These args have to be added after the `-fsanitize` arg or | 5986 | // These args have to be added after the `-fsanitize` arg or |
| 5917 | // they won't take effect. | 5987 | // they won't take effect. |
| 5918 | if (mod.sanitize_c) { | 5988 | 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. | ||
| 5919 | try argv.append("-fno-sanitize=vptr"); | 5992 | try argv.append("-fno-sanitize=vptr"); |
| 5993 | |||
| 5920 | // It is very common, and well-defined, for a pointer on one side of a C ABI | 5994 | // It is very common, and well-defined, for a pointer on one side of a C ABI |
| 5921 | // to have a different but compatible element type. Examples include: | 5995 | // to have a different but compatible element type. Examples include: |
| 5922 | // `char*` vs `uint8_t*` on a system with 8-bit bytes | 5996 | // `char*` vs `uint8_t*` on a system with 8-bit bytes |
| ... | @@ -5926,6 +6000,8 @@ pub fn addCCArgs( | ... | @@ -5926,6 +6000,8 @@ pub fn addCCArgs( |
| 5926 | // function was called. | 6000 | // function was called. |
| 5927 | try argv.append("-fno-sanitize=function"); | 6001 | try argv.append("-fno-sanitize=function"); |
| 5928 | 6002 | ||
| 6003 | // It's recommended to use the minimal runtime in production environments | ||
| 6004 | // due to the security implications of the full runtime. | ||
| 5929 | if (mod.optimize_mode == .ReleaseSafe) { | 6005 | if (mod.optimize_mode == .ReleaseSafe) { |
| 5930 | try argv.append("-fsanitize-minimal-runtime"); | 6006 | try argv.append("-fsanitize-minimal-runtime"); |
| 5931 | } | 6007 | } |
src/link.zig+7| ... | @@ -1107,6 +1107,11 @@ pub const File = struct { | ... | @@ -1107,6 +1107,11 @@ pub const File = struct { |
| 1107 | else | 1107 | else |
| 1108 | null; | 1108 | null; |
| 1109 | 1109 | ||
| 1110 | const ubsan_rt_path: ?Path = if (comp.include_ubsan_rt) | ||
| 1111 | comp.ubsan_rt_obj.?.full_object_path | ||
| 1112 | else | ||
| 1113 | null; | ||
| 1114 | |||
| 1110 | // This function follows the same pattern as link.Elf.linkWithLLD so if you want some | 1115 | // This function follows the same pattern as link.Elf.linkWithLLD so if you want some |
| 1111 | // insight as to what's going on here you can read that function body which is more | 1116 | // insight as to what's going on here you can read that function body which is more |
| 1112 | // well-commented. | 1117 | // well-commented. |
| ... | @@ -1136,6 +1141,7 @@ pub const File = struct { | ... | @@ -1136,6 +1141,7 @@ pub const File = struct { |
| 1136 | } | 1141 | } |
| 1137 | try man.addOptionalFile(zcu_obj_path); | 1142 | try man.addOptionalFile(zcu_obj_path); |
| 1138 | try man.addOptionalFilePath(compiler_rt_path); | 1143 | try man.addOptionalFilePath(compiler_rt_path); |
| 1144 | try man.addOptionalFilePath(ubsan_rt_path); | ||
| 1139 | 1145 | ||
| 1140 | // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. | 1146 | // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. |
| 1141 | _ = try man.hit(); | 1147 | _ = try man.hit(); |
| ... | @@ -1181,6 +1187,7 @@ pub const File = struct { | ... | @@ -1181,6 +1187,7 @@ pub const File = struct { |
| 1181 | } | 1187 | } |
| 1182 | if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); | 1188 | if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); |
| 1183 | if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); | 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 | if (comp.verbose_link) { | 1192 | if (comp.verbose_link) { |
| 1186 | std.debug.print("ar rcs {s}", .{full_out_path_z}); | 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,6 +2162,15 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 2162 | try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); | 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 | if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { | 2174 | if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { |
| 2166 | if (!comp.config.link_libc) { | 2175 | if (!comp.config.link_libc) { |
| 2167 | if (comp.libc_static_lib) |lib| { | 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,6 +1541,11 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1541 | if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; | 1541 | if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; |
| 1542 | break :blk null; | 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 | // Here we want to determine whether we can save time by not invoking LLD when the | 1550 | // Here we want to determine whether we can save time by not invoking LLD when the |
| 1546 | // output is unchanged. None of the linker options or the object files that are being | 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,6 +1580,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1575 | } | 1580 | } |
| 1576 | try man.addOptionalFile(module_obj_path); | 1581 | try man.addOptionalFile(module_obj_path); |
| 1577 | try man.addOptionalFilePath(compiler_rt_path); | 1582 | try man.addOptionalFilePath(compiler_rt_path); |
| 1583 | try man.addOptionalFilePath(ubsan_rt_path); | ||
| 1578 | try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); | 1584 | try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); |
| 1579 | try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); | 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,6 +1980,10 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1974 | try argv.append(try lib.full_object_path.toString(arena)); | 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 | // libc | 1987 | // libc |
| 1978 | if (is_exe_or_dyn_lib and | 1988 | if (is_exe_or_dyn_lib and |
| 1979 | !comp.skip_linker_dependencies and | 1989 | !comp.skip_linker_dependencies and |
src/link/MachO.zig+24-2| ... | @@ -344,11 +344,21 @@ pub fn deinit(self: *MachO) void { | ... | @@ -344,11 +344,21 @@ pub fn deinit(self: *MachO) void { |
| 344 | self.thunks.deinit(gpa); | 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 | try self.flushModule(arena, tid, prog_node); | 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 | const tracy = trace(@src()); | 362 | const tracy = trace(@src()); |
| 353 | defer tracy.end(); | 363 | defer tracy.end(); |
| 354 | 364 | ||
| ... | @@ -409,6 +419,16 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n | ... | @@ -409,6 +419,16 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n |
| 409 | try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path)); | 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 | for (positionals.items) |link_input| { | 432 | for (positionals.items) |link_input| { |
| 413 | self.classifyInputFile(link_input) catch |err| | 433 | self.classifyInputFile(link_input) catch |err| |
| 414 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); | 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,6 +833,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { |
| 813 | 833 | ||
| 814 | if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); | 834 | if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); |
| 815 | if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); | 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 | Compilation.dump_argv(argv.items); | 840 | 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: ? | ... | @@ -97,6 +97,10 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? |
| 97 | try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path)); | 97 | try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path)); |
| 98 | } | 98 | } |
| 99 | 99 | ||
| 100 | if (comp.include_ubsan_rt) { | ||
| 101 | try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path)); | ||
| 102 | } | ||
| 103 | |||
| 100 | for (positionals.items) |link_input| { | 104 | for (positionals.items) |link_input| { |
| 101 | macho_file.classifyInputFile(link_input) catch |err| | 105 | macho_file.classifyInputFile(link_input) catch |err| |
| 102 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); | 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,6 +3879,11 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 3879 | if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; | 3879 | if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; |
| 3880 | break :blk null; | 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 | const id_symlink_basename = "lld.id"; | 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,6 +3906,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 3901 | } | 3906 | } |
| 3902 | try man.addOptionalFile(module_obj_path); | 3907 | try man.addOptionalFile(module_obj_path); |
| 3903 | try man.addOptionalFilePath(compiler_rt_path); | 3908 | try man.addOptionalFilePath(compiler_rt_path); |
| 3909 | try man.addOptionalFilePath(ubsan_rt_path); | ||
| 3904 | man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); | 3910 | man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); |
| 3905 | man.hash.add(wasm.base.stack_size); | 3911 | man.hash.add(wasm.base.stack_size); |
| 3906 | man.hash.add(wasm.base.build_id); | 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,6 +4154,10 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 4148 | try argv.append(try p.toString(arena)); | 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 | if (comp.verbose_link) { | 4161 | if (comp.verbose_link) { |
| 4152 | // Skip over our own name so that the LLD linker name is the first argv item. | 4162 | // Skip over our own name so that the LLD linker name is the first argv item. |
| 4153 | Compilation.dump_argv(argv.items[1..]); | 4163 | Compilation.dump_argv(argv.items[1..]); |
src/main.zig+6| ... | @@ -849,6 +849,7 @@ fn buildOutputType( | ... | @@ -849,6 +849,7 @@ fn buildOutputType( |
| 849 | var emit_h: Emit = .no; | 849 | var emit_h: Emit = .no; |
| 850 | var soname: SOName = undefined; | 850 | var soname: SOName = undefined; |
| 851 | var want_compiler_rt: ?bool = null; | 851 | var want_compiler_rt: ?bool = null; |
| 852 | var want_ubsan_rt: ?bool = null; | ||
| 852 | var linker_script: ?[]const u8 = null; | 853 | var linker_script: ?[]const u8 = null; |
| 853 | var version_script: ?[]const u8 = null; | 854 | var version_script: ?[]const u8 = null; |
| 854 | var linker_repro: ?bool = null; | 855 | var linker_repro: ?bool = null; |
| ... | @@ -1376,6 +1377,10 @@ fn buildOutputType( | ... | @@ -1376,6 +1377,10 @@ fn buildOutputType( |
| 1376 | want_compiler_rt = true; | 1377 | want_compiler_rt = true; |
| 1377 | } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { | 1378 | } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { |
| 1378 | want_compiler_rt = false; | 1379 | 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; | ||
| 1379 | } else if (mem.eql(u8, arg, "-feach-lib-rpath")) { | 1384 | } else if (mem.eql(u8, arg, "-feach-lib-rpath")) { |
| 1380 | create_module.each_lib_rpath = true; | 1385 | create_module.each_lib_rpath = true; |
| 1381 | } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { | 1386 | } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { |
| ... | @@ -3504,6 +3509,7 @@ fn buildOutputType( | ... | @@ -3504,6 +3509,7 @@ fn buildOutputType( |
| 3504 | .windows_lib_names = create_module.windows_libs.keys(), | 3509 | .windows_lib_names = create_module.windows_libs.keys(), |
| 3505 | .wasi_emulated_libs = create_module.wasi_emulated_libs.items, | 3510 | .wasi_emulated_libs = create_module.wasi_emulated_libs.items, |
| 3506 | .want_compiler_rt = want_compiler_rt, | 3511 | .want_compiler_rt = want_compiler_rt, |
| 3512 | .want_ubsan_rt = want_ubsan_rt, | ||
| 3507 | .hash_style = hash_style, | 3513 | .hash_style = hash_style, |
| 3508 | .linker_script = linker_script, | 3514 | .linker_script = linker_script, |
| 3509 | .version_script = version_script, | 3515 | .version_script = version_script, |