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