| 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 | 44 | pub const Treap = @import("treap.zig").Treap; |
| 45 | 45 | pub const Tz = tz.Tz; |
| 46 | 46 | pub const Uri = @import("Uri.zig"); |
| 47 | pub const ubsan = @import("ubsan.zig"); | |
| 48 | 47 | |
| 49 | 48 | pub const array_hash_map = @import("array_hash_map.zig"); |
| 50 | 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 | 79 | docs_emit: ?Path, |
| 80 | 80 | root_name: [:0]const u8, |
| 81 | 81 | include_compiler_rt: bool, |
| 82 | include_ubsan_rt: bool, | |
| 82 | 83 | /// Resolved into known paths, any GNU ld scripts already resolved. |
| 83 | 84 | link_inputs: []const link.Input, |
| 84 | 85 | /// Needed only for passing -F args to clang. |
| ... | ... | @@ -226,6 +227,12 @@ libunwind_static_lib: ?CrtFile = null, |
| 226 | 227 | /// Populated when we build the TSAN library. A Job to build this is placed in the queue |
| 227 | 228 | /// and resolved before calling linker.flush(). |
| 228 | 229 | tsan_lib: ?CrtFile = null, |
| 230 | /// Populated when we build the UBSAN library. A Job to build this is placed in the queue | |
| 231 | /// and resolved before calling linker.flush(). | |
| 232 | ubsan_rt_lib: ?CrtFile = null, | |
| 233 | /// Populated when we build the UBSAN object. A Job to build this is placed in the queue | |
| 234 | /// and resolved before calling linker.flush(). | |
| 235 | ubsan_rt_obj: ?CrtFile = null, | |
| 229 | 236 | /// Populated when we build the libc static library. A Job to build this is placed in the queue |
| 230 | 237 | /// and resolved before calling linker.flush(). |
| 231 | 238 | libc_static_lib: ?CrtFile = null, |
| ... | ... | @@ -283,6 +290,8 @@ digest: ?[Cache.bin_digest_len]u8 = null, |
| 283 | 290 | const QueuedJobs = struct { |
| 284 | 291 | compiler_rt_lib: bool = false, |
| 285 | 292 | compiler_rt_obj: bool = false, |
| 293 | ubsan_rt_lib: bool = false, | |
| 294 | ubsan_rt_obj: bool = false, | |
| 286 | 295 | fuzzer_lib: bool = false, |
| 287 | 296 | update_builtin_zig: bool, |
| 288 | 297 | musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false), |
| ... | ... | @@ -789,6 +798,7 @@ pub const MiscTask = enum { |
| 789 | 798 | libcxx, |
| 790 | 799 | libcxxabi, |
| 791 | 800 | libtsan, |
| 801 | libubsan, | |
| 792 | 802 | libfuzzer, |
| 793 | 803 | wasi_libc_crt_file, |
| 794 | 804 | compiler_rt, |
| ... | ... | @@ -1064,6 +1074,7 @@ pub const CreateOptions = struct { |
| 1064 | 1074 | /// Position Independent Executable. If the output mode is not an |
| 1065 | 1075 | /// executable this field is ignored. |
| 1066 | 1076 | want_compiler_rt: ?bool = null, |
| 1077 | want_ubsan_rt: ?bool = null, | |
| 1067 | 1078 | want_lto: ?bool = null, |
| 1068 | 1079 | function_sections: bool = false, |
| 1069 | 1080 | data_sections: bool = false, |
| ... | ... | @@ -1297,6 +1308,9 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1297 | 1308 | const include_compiler_rt = options.want_compiler_rt orelse |
| 1298 | 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 | 1314 | if (include_compiler_rt and output_mode == .Obj) { |
| 1301 | 1315 | // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` |
| 1302 | 1316 | // injected into the object. |
| ... | ... | @@ -1323,6 +1337,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1323 | 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 | 1360 | if (options.verbose_llvm_cpu_features) { |
| 1327 | 1361 | if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { |
| 1328 | 1362 | const target = options.root_mod.resolved_target.result; |
| ... | ... | @@ -1500,6 +1534,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1500 | 1534 | .version = options.version, |
| 1501 | 1535 | .libc_installation = libc_dirs.libc_installation, |
| 1502 | 1536 | .include_compiler_rt = include_compiler_rt, |
| 1537 | .include_ubsan_rt = include_ubsan_rt, | |
| 1503 | 1538 | .link_inputs = options.link_inputs, |
| 1504 | 1539 | .framework_dirs = options.framework_dirs, |
| 1505 | 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 | 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 | 1933 | if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) { |
| 1889 | 1934 | log.debug("queuing a job to build libfuzzer", .{}); |
| 1890 | 1935 | comp.queued_jobs.fuzzer_lib = true; |
| ... | ... | @@ -1937,9 +1982,16 @@ pub fn destroy(comp: *Compilation) void { |
| 1937 | 1982 | if (comp.compiler_rt_obj) |*crt_file| { |
| 1938 | 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 | 1991 | if (comp.fuzzer_lib) |*crt_file| { |
| 1941 | 1992 | crt_file.deinit(gpa); |
| 1942 | 1993 | } |
| 1994 | ||
| 1943 | 1995 | if (comp.libc_static_lib) |*crt_file| { |
| 1944 | 1996 | crt_file.deinit(gpa); |
| 1945 | 1997 | } |
| ... | ... | @@ -2207,6 +2259,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2207 | 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 | 2266 | if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { |
| 2211 | 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 | 2304 | try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); |
| 2249 | 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 | 2314 | try comp.performAllTheWork(main_progress_node); |
| ... | ... | @@ -2593,6 +2654,7 @@ fn addNonIncrementalStuffToCacheManifest( |
| 2593 | 2654 | man.hash.add(comp.link_eh_frame_hdr); |
| 2594 | 2655 | man.hash.add(comp.skip_linker_dependencies); |
| 2595 | 2656 | man.hash.add(comp.include_compiler_rt); |
| 2657 | man.hash.add(comp.include_ubsan_rt); | |
| 2596 | 2658 | man.hash.add(comp.rc_includes); |
| 2597 | 2659 | man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); |
| 2598 | 2660 | man.hash.addListOfBytes(comp.framework_dirs); |
| ... | ... | @@ -3683,6 +3745,14 @@ fn performAllTheWorkInner( |
| 3683 | 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 | 3756 | if (comp.queued_jobs.glibc_shared_objects) { |
| 3687 | 3757 | comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node }); |
| 3688 | 3758 | } |
| ... | ... | @@ -5916,7 +5986,11 @@ pub fn addCCArgs( |
| 5916 | 5986 | // These args have to be added after the `-fsanitize` arg or |
| 5917 | 5987 | // they won't take effect. |
| 5918 | 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 | 5992 | try argv.append("-fno-sanitize=vptr"); |
| 5993 | ||
| 5920 | 5994 | // It is very common, and well-defined, for a pointer on one side of a C ABI |
| 5921 | 5995 | // to have a different but compatible element type. Examples include: |
| 5922 | 5996 | // `char*` vs `uint8_t*` on a system with 8-bit bytes |
| ... | ... | @@ -5926,6 +6000,8 @@ pub fn addCCArgs( |
| 5926 | 6000 | // function was called. |
| 5927 | 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 | 6005 | if (mod.optimize_mode == .ReleaseSafe) { |
| 5930 | 6006 | try argv.append("-fsanitize-minimal-runtime"); |
| 5931 | 6007 | } |
src/link.zig+7| ... | ... | @@ -1107,6 +1107,11 @@ pub const File = struct { |
| 1107 | 1107 | else |
| 1108 | 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 | 1115 | // This function follows the same pattern as link.Elf.linkWithLLD so if you want some |
| 1111 | 1116 | // insight as to what's going on here you can read that function body which is more |
| 1112 | 1117 | // well-commented. |
| ... | ... | @@ -1136,6 +1141,7 @@ pub const File = struct { |
| 1136 | 1141 | } |
| 1137 | 1142 | try man.addOptionalFile(zcu_obj_path); |
| 1138 | 1143 | try man.addOptionalFilePath(compiler_rt_path); |
| 1144 | try man.addOptionalFilePath(ubsan_rt_path); | |
| 1139 | 1145 | |
| 1140 | 1146 | // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. |
| 1141 | 1147 | _ = try man.hit(); |
| ... | ... | @@ -1181,6 +1187,7 @@ pub const File = struct { |
| 1181 | 1187 | } |
| 1182 | 1188 | if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); |
| 1183 | 1189 | if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); |
| 1190 | if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); | |
| 1184 | 1191 | |
| 1185 | 1192 | if (comp.verbose_link) { |
| 1186 | 1193 | std.debug.print("ar rcs {s}", .{full_out_path_z}); |
src/link/Coff.zig+9| ... | ... | @@ -2162,6 +2162,15 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 2162 | 2162 | try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); |
| 2163 | 2163 | } |
| 2164 | 2164 | |
| 2165 | const ubsan_rt_path: ?Path = blk: { | |
| 2166 | if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; | |
| 2167 | if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; | |
| 2168 | break :blk null; | |
| 2169 | }; | |
| 2170 | if (ubsan_rt_path) |path| { | |
| 2171 | try argv.append(try path.toString(arena)); | |
| 2172 | } | |
| 2173 | ||
| 2165 | 2174 | if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { |
| 2166 | 2175 | if (!comp.config.link_libc) { |
| 2167 | 2176 | if (comp.libc_static_lib) |lib| { |
src/link/Elf.zig+10| ... | ... | @@ -1541,6 +1541,11 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1541 | 1541 | if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; |
| 1542 | 1542 | break :blk null; |
| 1543 | 1543 | }; |
| 1544 | const ubsan_rt_path: ?Path = blk: { | |
| 1545 | if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; | |
| 1546 | if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; | |
| 1547 | break :blk null; | |
| 1548 | }; | |
| 1544 | 1549 | |
| 1545 | 1550 | // Here we want to determine whether we can save time by not invoking LLD when the |
| 1546 | 1551 | // output is unchanged. None of the linker options or the object files that are being |
| ... | ... | @@ -1575,6 +1580,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1575 | 1580 | } |
| 1576 | 1581 | try man.addOptionalFile(module_obj_path); |
| 1577 | 1582 | try man.addOptionalFilePath(compiler_rt_path); |
| 1583 | try man.addOptionalFilePath(ubsan_rt_path); | |
| 1578 | 1584 | try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); |
| 1579 | 1585 | try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); |
| 1580 | 1586 | |
| ... | ... | @@ -1974,6 +1980,10 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s |
| 1974 | 1980 | try argv.append(try lib.full_object_path.toString(arena)); |
| 1975 | 1981 | } |
| 1976 | 1982 | |
| 1983 | if (ubsan_rt_path) |p| { | |
| 1984 | try argv.append(try p.toString(arena)); | |
| 1985 | } | |
| 1986 | ||
| 1977 | 1987 | // libc |
| 1978 | 1988 | if (is_exe_or_dyn_lib and |
| 1979 | 1989 | !comp.skip_linker_dependencies and |
src/link/MachO.zig+24-2| ... | ... | @@ -344,11 +344,21 @@ pub fn deinit(self: *MachO) void { |
| 344 | 344 | self.thunks.deinit(gpa); |
| 345 | 345 | } |
| 346 | 346 | |
| 347 | pub fn flush(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { | |
| 347 | pub fn flush( | |
| 348 | self: *MachO, | |
| 349 | arena: Allocator, | |
| 350 | tid: Zcu.PerThread.Id, | |
| 351 | prog_node: std.Progress.Node, | |
| 352 | ) link.File.FlushError!void { | |
| 348 | 353 | try self.flushModule(arena, tid, prog_node); |
| 349 | 354 | } |
| 350 | 355 | |
| 351 | pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { | |
| 356 | pub fn flushModule( | |
| 357 | self: *MachO, | |
| 358 | arena: Allocator, | |
| 359 | tid: Zcu.PerThread.Id, | |
| 360 | prog_node: std.Progress.Node, | |
| 361 | ) link.File.FlushError!void { | |
| 352 | 362 | const tracy = trace(@src()); |
| 353 | 363 | defer tracy.end(); |
| 354 | 364 | |
| ... | ... | @@ -409,6 +419,16 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n |
| 409 | 419 | try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path)); |
| 410 | 420 | } |
| 411 | 421 | |
| 422 | if (comp.ubsan_rt_lib) |crt_file| { | |
| 423 | const path = crt_file.full_object_path; | |
| 424 | self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| | |
| 425 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); | |
| 426 | } else if (comp.ubsan_rt_obj) |crt_file| { | |
| 427 | const path = crt_file.full_object_path; | |
| 428 | self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| | |
| 429 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); | |
| 430 | } | |
| 431 | ||
| 412 | 432 | for (positionals.items) |link_input| { |
| 413 | 433 | self.classifyInputFile(link_input) catch |err| |
| 414 | 434 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); |
| ... | ... | @@ -813,6 +833,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { |
| 813 | 833 | |
| 814 | 834 | if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); |
| 815 | 835 | if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); |
| 836 | if (comp.ubsan_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); | |
| 837 | if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); | |
| 816 | 838 | } |
| 817 | 839 | |
| 818 | 840 | Compilation.dump_argv(argv.items); |
src/link/MachO/relocatable.zig+4| ... | ... | @@ -97,6 +97,10 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? |
| 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) { | |
| 101 | try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path)); | |
| 102 | } | |
| 103 | ||
| 100 | 104 | for (positionals.items) |link_input| { |
| 101 | 105 | macho_file.classifyInputFile(link_input) catch |err| |
| 102 | 106 | diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); |
src/link/Wasm.zig+10| ... | ... | @@ -3879,6 +3879,11 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 3879 | 3879 | if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; |
| 3880 | 3880 | break :blk null; |
| 3881 | 3881 | }; |
| 3882 | const ubsan_rt_path: ?Path = blk: { | |
| 3883 | if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; | |
| 3884 | if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; | |
| 3885 | break :blk null; | |
| 3886 | }; | |
| 3882 | 3887 | |
| 3883 | 3888 | const id_symlink_basename = "lld.id"; |
| 3884 | 3889 | |
| ... | ... | @@ -3901,6 +3906,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 3901 | 3906 | } |
| 3902 | 3907 | try man.addOptionalFile(module_obj_path); |
| 3903 | 3908 | try man.addOptionalFilePath(compiler_rt_path); |
| 3909 | try man.addOptionalFilePath(ubsan_rt_path); | |
| 3904 | 3910 | man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); |
| 3905 | 3911 | man.hash.add(wasm.base.stack_size); |
| 3906 | 3912 | man.hash.add(wasm.base.build_id); |
| ... | ... | @@ -4148,6 +4154,10 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: |
| 4148 | 4154 | try argv.append(try p.toString(arena)); |
| 4149 | 4155 | } |
| 4150 | 4156 | |
| 4157 | if (ubsan_rt_path) |p| { | |
| 4158 | try argv.append(try p.toStringZ(arena)); | |
| 4159 | } | |
| 4160 | ||
| 4151 | 4161 | if (comp.verbose_link) { |
| 4152 | 4162 | // Skip over our own name so that the LLD linker name is the first argv item. |
| 4153 | 4163 | Compilation.dump_argv(argv.items[1..]); |
src/main.zig+6| ... | ... | @@ -849,6 +849,7 @@ fn buildOutputType( |
| 849 | 849 | var emit_h: Emit = .no; |
| 850 | 850 | var soname: SOName = undefined; |
| 851 | 851 | var want_compiler_rt: ?bool = null; |
| 852 | var want_ubsan_rt: ?bool = null; | |
| 852 | 853 | var linker_script: ?[]const u8 = null; |
| 853 | 854 | var version_script: ?[]const u8 = null; |
| 854 | 855 | var linker_repro: ?bool = null; |
| ... | ... | @@ -1376,6 +1377,10 @@ fn buildOutputType( |
| 1376 | 1377 | want_compiler_rt = true; |
| 1377 | 1378 | } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { |
| 1378 | 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 | 1384 | } else if (mem.eql(u8, arg, "-feach-lib-rpath")) { |
| 1380 | 1385 | create_module.each_lib_rpath = true; |
| 1381 | 1386 | } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { |
| ... | ... | @@ -3504,6 +3509,7 @@ fn buildOutputType( |
| 3504 | 3509 | .windows_lib_names = create_module.windows_libs.keys(), |
| 3505 | 3510 | .wasi_emulated_libs = create_module.wasi_emulated_libs.items, |
| 3506 | 3511 | .want_compiler_rt = want_compiler_rt, |
| 3512 | .want_ubsan_rt = want_ubsan_rt, | |
| 3507 | 3513 | .hash_style = hash_style, |
| 3508 | 3514 | .linker_script = linker_script, |
| 3509 | 3515 | .version_script = version_script, |