zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 95720f007bb0dacc8a7cecb621c322d574c61d00 (tree)
parent babee5f73c01c220e3fb3901eb1a70149f94258b
Author: David Rubin <daviru007@icloud.com>
Date:   Wed, 25 Dec 2024 21:10:02 -0800

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

Diffstat:
Mlib/std/std.zig | 1-
Dlib/std/ubsan.zig | 509-------------------------------------------------------------------------------
Alib/ubsan.zig | 509+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Compilation.zig | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/link.zig | 7+++++++
Msrc/link/Coff.zig | 9+++++++++
Msrc/link/Elf.zig | 10++++++++++
Msrc/link/MachO.zig | 26++++++++++++++++++++++++--
Msrc/link/MachO/relocatable.zig | 4++++
Msrc/link/Wasm.zig | 10++++++++++
Msrc/main.zig | 6++++++
11 files changed, 655 insertions(+), 512 deletions(-)

diff --git a/lib/std/std.zig b/lib/std/std.zig @@ -44,7 +44,6 @@ pub const Thread = @import("Thread.zig"); pub const Treap = @import("treap.zig").Treap; pub const Tz = tz.Tz; pub const Uri = @import("Uri.zig"); -pub const ubsan = @import("ubsan.zig"); pub const array_hash_map = @import("array_hash_map.zig"); pub const atomic = @import("atomic.zig"); diff --git a/lib/std/ubsan.zig b/lib/std/ubsan.zig @@ -1,509 +0,0 @@ -//! Minimal UBSan Runtime - -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; - -const SourceLocation = extern struct { - file_name: ?[*:0]const u8, - line: u32, - col: u32, -}; - -const TypeDescriptor = extern struct { - kind: Kind, - info: Info, - // name: [?:0]u8 - - const Kind = enum(u16) { - integer = 0x0000, - float = 0x0001, - unknown = 0xFFFF, - }; - - const Info = extern union { - integer: packed struct(u16) { - signed: bool, - bit_width: u15, - }, - }; - - fn getIntegerSize(desc: TypeDescriptor) u64 { - assert(desc.kind == .integer); - const bit_width = desc.info.integer.bit_width; - return @as(u64, 1) << @intCast(bit_width); - } - - fn isSigned(desc: TypeDescriptor) bool { - return desc.kind == .integer and desc.info.integer.signed; - } - - fn getName(desc: *const TypeDescriptor) [:0]const u8 { - return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor)); - } -}; - -const ValueHandle = *const opaque { - fn getValue(handle: ValueHandle, data: anytype) Value { - return .{ .handle = handle, .type_descriptor = data.type_descriptor }; - } -}; - -const Value = extern struct { - type_descriptor: *const TypeDescriptor, - handle: ValueHandle, - - fn getUnsignedInteger(value: Value) u128 { - assert(!value.type_descriptor.isSigned()); - const size = value.type_descriptor.getIntegerSize(); - const max_inline_size = @bitSizeOf(ValueHandle); - if (size <= max_inline_size) { - return @intFromPtr(value.handle); - } - - return switch (size) { - 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*, - 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*, - else => unreachable, - }; - } - - fn getSignedInteger(value: Value) i128 { - assert(value.type_descriptor.isSigned()); - const size = value.type_descriptor.getIntegerSize(); - const max_inline_size = @bitSizeOf(ValueHandle); - if (size <= max_inline_size) { - const extra_bits: u6 = @intCast(max_inline_size - size); - const handle: i64 = @bitCast(@intFromPtr(value.handle)); - return (handle << extra_bits) >> extra_bits; - } - return switch (size) { - 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*, - 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*, - else => unreachable, - }; - } - - fn isMinusOne(value: Value) bool { - return value.type_descriptor.isSigned() and - value.getSignedInteger() == -1; - } - - fn isNegative(value: Value) bool { - return value.type_descriptor.isSigned() and - value.getSignedInteger() < 0; - } - - fn getPositiveInteger(value: Value) u128 { - if (value.type_descriptor.isSigned()) { - const signed = value.getSignedInteger(); - assert(signed >= 0); - return @intCast(signed); - } else { - return value.getUnsignedInteger(); - } - } - - pub fn format( - value: Value, - comptime fmt: []const u8, - _: std.fmt.FormatOptions, - writer: anytype, - ) !void { - comptime assert(fmt.len == 0); - - switch (value.type_descriptor.kind) { - .integer => { - if (value.type_descriptor.isSigned()) { - try writer.print("{}", .{value.getSignedInteger()}); - } else { - try writer.print("{}", .{value.getUnsignedInteger()}); - } - }, - .float => @panic("TODO: write float"), - .unknown => try writer.writeAll("(unknown)"), - } - } -}; - -const OverflowData = extern struct { - loc: SourceLocation, - type_descriptor: *const TypeDescriptor, -}; - -fn overflowHandler( - comptime sym_name: []const u8, - comptime operator: []const u8, -) void { - const S = struct { - fn handler( - data: *OverflowData, - lhs_handle: ValueHandle, - rhs_handle: ValueHandle, - ) callconv(.c) noreturn { - const lhs = lhs_handle.getValue(data); - const rhs = rhs_handle.getValue(data); - - const is_signed = data.type_descriptor.isSigned(); - const fmt = "{s} integer overflow: " ++ "{} " ++ - operator ++ " {} cannot be represented in type {s}"; - - logMessage(fmt, .{ - if (is_signed) "signed" else "unsigned", - lhs, - rhs, - data.type_descriptor.getName(), - }); - } - }; - - exportHandler(&S.handler, sym_name, true); -} - -fn negationHandler( - data: *const OverflowData, - old_value_handle: ValueHandle, -) callconv(.c) noreturn { - const old_value = old_value_handle.getValue(data); - logMessage( - "negation of {} cannot be represented in type {s}", - .{ old_value, data.type_descriptor.getName() }, - ); -} - -fn divRemHandler( - data: *const OverflowData, - lhs_handle: ValueHandle, - rhs_handle: ValueHandle, -) callconv(.c) noreturn { - const is_signed = data.type_descriptor.isSigned(); - const lhs = lhs_handle.getValue(data); - const rhs = rhs_handle.getValue(data); - - if (is_signed and rhs.getSignedInteger() == -1) { - logMessage( - "division of {} by -1 cannot be represented in type {s}", - .{ lhs, data.type_descriptor.getName() }, - ); - } else logMessage("division by zero", .{}); -} - -const AlignmentAssumptionData = extern struct { - loc: SourceLocation, - assumption_loc: SourceLocation, - type_descriptor: *const TypeDescriptor, -}; - -fn alignmentAssumptionHandler( - data: *const AlignmentAssumptionData, - pointer: ValueHandle, - alignment: ValueHandle, - maybe_offset: ?ValueHandle, -) callconv(.c) noreturn { - _ = pointer; - // TODO: add the hint here? - // const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset); - // const lsb = @ctz(real_pointer); - // const actual_alignment = @as(u64, 1) << @intCast(lsb); - // const mask = @intFromPtr(alignment) - 1; - // const misalignment_offset = real_pointer & mask; - // _ = actual_alignment; - // _ = misalignment_offset; - - if (maybe_offset) |offset| { - logMessage( - "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed", - .{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() }, - ); - } else { - logMessage( - "assumption of {} byte alignment for pointer of type {s} failed", - .{ alignment.getValue(data), data.type_descriptor.getName() }, - ); - } -} - -const ShiftOobData = extern struct { - loc: SourceLocation, - lhs_type: *const TypeDescriptor, - rhs_type: *const TypeDescriptor, -}; - -fn shiftOob( - data: *const ShiftOobData, - lhs_handle: ValueHandle, - rhs_handle: ValueHandle, -) callconv(.c) noreturn { - const lhs: Value = .{ .handle = lhs_handle, .type_descriptor = data.lhs_type }; - const rhs: Value = .{ .handle = rhs_handle, .type_descriptor = data.rhs_type }; - - if (rhs.isNegative() or - rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize()) - { - if (rhs.isNegative()) { - logMessage("shift exponent {} is negative", .{rhs}); - } else { - logMessage( - "shift exponent {} is too large for {}-bit type {s}", - .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() }, - ); - } - } else { - if (lhs.isNegative()) { - logMessage("left shift of negative value {}", .{lhs}); - } else { - logMessage( - "left shift of {} by {} places cannot be represented in type {s}", - .{ lhs, rhs, data.lhs_type.getName() }, - ); - } - } -} - -const OutOfBoundsData = extern struct { - loc: SourceLocation, - array_type: *const TypeDescriptor, - index_type: *const TypeDescriptor, -}; - -fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.c) noreturn { - const index: Value = .{ .handle = index_handle, .type_descriptor = data.index_type }; - logMessage( - "index {} out of bounds for type {s}", - .{ index, data.array_type.getName() }, - ); -} - -const PointerOverflowData = extern struct { - loc: SourceLocation, -}; - -fn pointerOverflow( - _: *const PointerOverflowData, - base: usize, - result: usize, -) callconv(.c) noreturn { - if (base == 0) { - if (result == 0) { - logMessage("applying zero offset to null pointer", .{}); - } else { - logMessage("applying non-zero offset {} to null pointer", .{result}); - } - } else { - if (result == 0) { - logMessage( - "applying non-zero offset to non-null pointer 0x{x} produced null pointer", - .{base}, - ); - } else { - @panic("TODO"); - } - } -} - -const TypeMismatchData = extern struct { - loc: SourceLocation, - type_descriptor: *const TypeDescriptor, - log_alignment: u8, - kind: enum(u8) { - load, - store, - reference_binding, - member_access, - member_call, - constructor_call, - downcast_pointer, - downcast_reference, - upcast, - upcast_to_virtual_base, - nonnull_assign, - dynamic_operation, - - fn getName(kind: @This()) []const u8 { - return switch (kind) { - .load => "load of", - .store => "store of", - .reference_binding => "reference binding to", - .member_access => "member access within", - .member_call => "member call on", - .constructor_call => "constructor call on", - .downcast_pointer, .downcast_reference => "downcast of", - .upcast => "upcast of", - .upcast_to_virtual_base => "cast to virtual base of", - .nonnull_assign => "_Nonnull binding to", - .dynamic_operation => "dynamic operation on", - }; - } - }, -}; - -fn typeMismatch( - data: *const TypeMismatchData, - pointer: ?ValueHandle, -) callconv(.c) noreturn { - const alignment = @as(usize, 1) << @intCast(data.log_alignment); - const handle: usize = @intFromPtr(pointer); - - if (pointer == null) { - logMessage( - "{s} null pointer of type {s}", - .{ data.kind.getName(), data.type_descriptor.getName() }, - ); - } else if (!std.mem.isAligned(handle, alignment)) { - logMessage( - "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment", - .{ data.kind.getName(), handle, data.type_descriptor.getName(), alignment }, - ); - } else { - logMessage( - "{s} address 0x{x} with insufficient space for an object of type {s}", - .{ data.kind.getName(), handle, data.type_descriptor.getName() }, - ); - } -} - -const UnreachableData = extern struct { - loc: SourceLocation, -}; - -fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn { - logMessage("execution reached an unreachable program point", .{}); -} - -fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn { - logMessage("execution reached the end of a value-returning function without returning a value", .{}); -} - -const NonNullReturnData = extern struct { - attribute_loc: SourceLocation, -}; - -fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn { - logMessage("null pointer returned from function declared to never return null", .{}); -} - -const NonNullArgData = extern struct { - loc: SourceLocation, - attribute_loc: SourceLocation, - arg_index: i32, -}; - -fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn { - logMessage( - "null pointer passed as argument {}, which is declared to never be null", - .{data.arg_index}, - ); -} - -const InvalidValueData = extern struct { - loc: SourceLocation, - type_descriptor: *const TypeDescriptor, -}; - -fn loadInvalidValue( - data: *const InvalidValueData, - value_handle: ValueHandle, -) callconv(.c) noreturn { - logMessage("load of value {}, which is not valid for type {s}", .{ - value_handle.getValue(data), data.type_descriptor.getName(), - }); -} - -fn SimpleHandler(comptime error_name: []const u8) type { - return struct { - fn handler() callconv(.c) noreturn { - logMessage("{s}", .{error_name}); - } - }; -} - -inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn { - std.debug.panicExtra(null, @returnAddress(), fmt, args); -} - -fn exportHandler( - handler: anytype, - comptime sym_name: []const u8, - comptime abort: bool, -) void { - const linkage = if (builtin.is_test) .internal else .weak; - { - const N = "__ubsan_handle_" ++ sym_name; - @export(handler, .{ .name = N, .linkage = linkage }); - } - if (abort) { - const N = "__ubsan_handle_" ++ sym_name ++ "_abort"; - @export(handler, .{ .name = N, .linkage = linkage }); - } -} - -fn exportMinimal( - err_name: anytype, - comptime sym_name: []const u8, - comptime abort: bool, -) void { - const handler = &SimpleHandler(err_name).handler; - const linkage = if (builtin.is_test) .internal else .weak; - { - const N = "__ubsan_handle_" ++ sym_name ++ "_minimal"; - @export(handler, .{ .name = N, .linkage = linkage }); - } - if (abort) { - const N = "__ubsan_handle_" ++ sym_name ++ "_minimal_abort"; - @export(handler, .{ .name = N, .linkage = linkage }); - } -} - -fn exportHelper( - comptime err_name: []const u8, - comptime sym_name: []const u8, - comptime abort: bool, -) void { - exportHandler(&SimpleHandler(err_name).handler, sym_name, abort); - exportMinimal(err_name, sym_name, abort); -} - -comptime { - overflowHandler("add_overflow", "+"); - overflowHandler("sub_overflow", "-"); - overflowHandler("mul_overflow", "*"); - exportHandler(&negationHandler, "negate_overflow", true); - exportHandler(&divRemHandler, "divrem_overflow", true); - exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true); - exportHandler(&shiftOob, "shift_out_of_bounds", true); - exportHandler(&outOfBounds, "out_of_bounds", true); - exportHandler(&pointerOverflow, "pointer_overflow", true); - exportHandler(&typeMismatch, "type_mismatch_v1", true); - exportHandler(&builtinUnreachable, "builtin_unreachable", false); - exportHandler(&missingReturn, "missing_return", false); - exportHandler(&nonNullReturn, "nonnull_return_v1", true); - exportHandler(&nonNullArg, "nonnull_arg", true); - exportHandler(&loadInvalidValue, "load_invalid_value", true); - - exportHelper("vla-bound-not-positive", "vla_bound_not_positive", true); - exportHelper("float-cast-overflow", "float_cast_overflow", true); - exportHelper("invalid-builtin", "invalid_builtin", true); - exportHelper("function-type-mismatch", "function_type_mismatch", true); - exportHelper("implicit-conversion", "implicit_conversion", true); - exportHelper("nullability-arg", "nullability_arg", true); - exportHelper("nullability-return", "nullability_return", true); - exportHelper("cfi-check-fail", "cfi_check_fail", true); - exportHelper("function-type-mismatch-v1", "function_type_mismatch_v1", true); - - exportMinimal("builtin-unreachable", "builtin_unreachable", false); - exportMinimal("add-overflow", "add_overflow", true); - exportMinimal("sub-overflow", "sub_overflow", true); - exportMinimal("mul-overflow", "mul_overflow", true); - exportMinimal("negation-handler", "negate_overflow", true); - exportMinimal("divrem-handler", "divrem_overflow", true); - exportMinimal("alignment-assumption-handler", "alignment_assumption", true); - exportMinimal("shift-oob", "shift_out_of_bounds", true); - exportMinimal("out-of-bounds", "out_of_bounds", true); - exportMinimal("pointer-overflow", "pointer_overflow", true); - exportMinimal("type-mismatch", "type_mismatch", true); - - // these checks are nearly impossible to duplicate in zig, as they rely on nuances - // in the Itanium C++ ABI. - // exportHelper("dynamic_type_cache_miss", "dynamic-type-cache-miss", true); - // exportHelper("vptr_type_cache", "vptr-type-cache", true); -} diff --git a/lib/ubsan.zig b/lib/ubsan.zig @@ -0,0 +1,509 @@ +//! Minimal UBSan Runtime + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; + +const SourceLocation = extern struct { + file_name: ?[*:0]const u8, + line: u32, + col: u32, +}; + +const TypeDescriptor = extern struct { + kind: Kind, + info: Info, + // name: [?:0]u8 + + const Kind = enum(u16) { + integer = 0x0000, + float = 0x0001, + unknown = 0xFFFF, + }; + + const Info = extern union { + integer: packed struct(u16) { + signed: bool, + bit_width: u15, + }, + }; + + fn getIntegerSize(desc: TypeDescriptor) u64 { + assert(desc.kind == .integer); + const bit_width = desc.info.integer.bit_width; + return @as(u64, 1) << @intCast(bit_width); + } + + fn isSigned(desc: TypeDescriptor) bool { + return desc.kind == .integer and desc.info.integer.signed; + } + + fn getName(desc: *const TypeDescriptor) [:0]const u8 { + return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor)); + } +}; + +const ValueHandle = *const opaque { + fn getValue(handle: ValueHandle, data: anytype) Value { + return .{ .handle = handle, .type_descriptor = data.type_descriptor }; + } +}; + +const Value = extern struct { + type_descriptor: *const TypeDescriptor, + handle: ValueHandle, + + fn getUnsignedInteger(value: Value) u128 { + assert(!value.type_descriptor.isSigned()); + const size = value.type_descriptor.getIntegerSize(); + const max_inline_size = @bitSizeOf(ValueHandle); + if (size <= max_inline_size) { + return @intFromPtr(value.handle); + } + + return switch (size) { + 64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*, + 128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*, + else => unreachable, + }; + } + + fn getSignedInteger(value: Value) i128 { + assert(value.type_descriptor.isSigned()); + const size = value.type_descriptor.getIntegerSize(); + const max_inline_size = @bitSizeOf(ValueHandle); + if (size <= max_inline_size) { + const extra_bits: std.math.Log2Int(usize) = @intCast(max_inline_size - size); + const handle: isize = @bitCast(@intFromPtr(value.handle)); + return (handle << extra_bits) >> extra_bits; + } + return switch (size) { + 64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*, + 128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*, + else => unreachable, + }; + } + + fn isMinusOne(value: Value) bool { + return value.type_descriptor.isSigned() and + value.getSignedInteger() == -1; + } + + fn isNegative(value: Value) bool { + return value.type_descriptor.isSigned() and + value.getSignedInteger() < 0; + } + + fn getPositiveInteger(value: Value) u128 { + if (value.type_descriptor.isSigned()) { + const signed = value.getSignedInteger(); + assert(signed >= 0); + return @intCast(signed); + } else { + return value.getUnsignedInteger(); + } + } + + pub fn format( + value: Value, + comptime fmt: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + comptime assert(fmt.len == 0); + + switch (value.type_descriptor.kind) { + .integer => { + if (value.type_descriptor.isSigned()) { + try writer.print("{}", .{value.getSignedInteger()}); + } else { + try writer.print("{}", .{value.getUnsignedInteger()}); + } + }, + .float => @panic("TODO: write float"), + .unknown => try writer.writeAll("(unknown)"), + } + } +}; + +const OverflowData = extern struct { + loc: SourceLocation, + type_descriptor: *const TypeDescriptor, +}; + +fn overflowHandler( + comptime sym_name: []const u8, + comptime operator: []const u8, +) void { + const S = struct { + fn handler( + data: *const OverflowData, + lhs_handle: ValueHandle, + rhs_handle: ValueHandle, + ) callconv(.c) noreturn { + const lhs = lhs_handle.getValue(data); + const rhs = rhs_handle.getValue(data); + + const is_signed = data.type_descriptor.isSigned(); + const fmt = "{s} integer overflow: " ++ "{} " ++ + operator ++ " {} cannot be represented in type {s}"; + + logMessage(fmt, .{ + if (is_signed) "signed" else "unsigned", + lhs, + rhs, + data.type_descriptor.getName(), + }); + } + }; + + exportHandler(&S.handler, sym_name, true); +} + +fn negationHandler( + data: *const OverflowData, + old_value_handle: ValueHandle, +) callconv(.c) noreturn { + const old_value = old_value_handle.getValue(data); + logMessage( + "negation of {} cannot be represented in type {s}", + .{ old_value, data.type_descriptor.getName() }, + ); +} + +fn divRemHandler( + data: *const OverflowData, + lhs_handle: ValueHandle, + rhs_handle: ValueHandle, +) callconv(.c) noreturn { + const is_signed = data.type_descriptor.isSigned(); + const lhs = lhs_handle.getValue(data); + const rhs = rhs_handle.getValue(data); + + if (is_signed and rhs.getSignedInteger() == -1) { + logMessage( + "division of {} by -1 cannot be represented in type {s}", + .{ lhs, data.type_descriptor.getName() }, + ); + } else logMessage("division by zero", .{}); +} + +const AlignmentAssumptionData = extern struct { + loc: SourceLocation, + assumption_loc: SourceLocation, + type_descriptor: *const TypeDescriptor, +}; + +fn alignmentAssumptionHandler( + data: *const AlignmentAssumptionData, + pointer: ValueHandle, + alignment: ValueHandle, + maybe_offset: ?ValueHandle, +) callconv(.c) noreturn { + _ = pointer; + // TODO: add the hint here? + // const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset); + // const lsb = @ctz(real_pointer); + // const actual_alignment = @as(u64, 1) << @intCast(lsb); + // const mask = @intFromPtr(alignment) - 1; + // const misalignment_offset = real_pointer & mask; + // _ = actual_alignment; + // _ = misalignment_offset; + + if (maybe_offset) |offset| { + logMessage( + "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed", + .{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() }, + ); + } else { + logMessage( + "assumption of {} byte alignment for pointer of type {s} failed", + .{ alignment.getValue(data), data.type_descriptor.getName() }, + ); + } +} + +const ShiftOobData = extern struct { + loc: SourceLocation, + lhs_type: *const TypeDescriptor, + rhs_type: *const TypeDescriptor, +}; + +fn shiftOob( + data: *const ShiftOobData, + lhs_handle: ValueHandle, + rhs_handle: ValueHandle, +) callconv(.c) noreturn { + const lhs: Value = .{ .handle = lhs_handle, .type_descriptor = data.lhs_type }; + const rhs: Value = .{ .handle = rhs_handle, .type_descriptor = data.rhs_type }; + + if (rhs.isNegative() or + rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize()) + { + if (rhs.isNegative()) { + logMessage("shift exponent {} is negative", .{rhs}); + } else { + logMessage( + "shift exponent {} is too large for {}-bit type {s}", + .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() }, + ); + } + } else { + if (lhs.isNegative()) { + logMessage("left shift of negative value {}", .{lhs}); + } else { + logMessage( + "left shift of {} by {} places cannot be represented in type {s}", + .{ lhs, rhs, data.lhs_type.getName() }, + ); + } + } +} + +const OutOfBoundsData = extern struct { + loc: SourceLocation, + array_type: *const TypeDescriptor, + index_type: *const TypeDescriptor, +}; + +fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.c) noreturn { + const index: Value = .{ .handle = index_handle, .type_descriptor = data.index_type }; + logMessage( + "index {} out of bounds for type {s}", + .{ index, data.array_type.getName() }, + ); +} + +const PointerOverflowData = extern struct { + loc: SourceLocation, +}; + +fn pointerOverflow( + _: *const PointerOverflowData, + base: usize, + result: usize, +) callconv(.c) noreturn { + if (base == 0) { + if (result == 0) { + logMessage("applying zero offset to null pointer", .{}); + } else { + logMessage("applying non-zero offset {} to null pointer", .{result}); + } + } else { + if (result == 0) { + logMessage( + "applying non-zero offset to non-null pointer 0x{x} produced null pointer", + .{base}, + ); + } else { + @panic("TODO"); + } + } +} + +const TypeMismatchData = extern struct { + loc: SourceLocation, + type_descriptor: *const TypeDescriptor, + log_alignment: u8, + kind: enum(u8) { + load, + store, + reference_binding, + member_access, + member_call, + constructor_call, + downcast_pointer, + downcast_reference, + upcast, + upcast_to_virtual_base, + nonnull_assign, + dynamic_operation, + + fn getName(kind: @This()) []const u8 { + return switch (kind) { + .load => "load of", + .store => "store of", + .reference_binding => "reference binding to", + .member_access => "member access within", + .member_call => "member call on", + .constructor_call => "constructor call on", + .downcast_pointer, .downcast_reference => "downcast of", + .upcast => "upcast of", + .upcast_to_virtual_base => "cast to virtual base of", + .nonnull_assign => "_Nonnull binding to", + .dynamic_operation => "dynamic operation on", + }; + } + }, +}; + +fn typeMismatch( + data: *const TypeMismatchData, + pointer: ?ValueHandle, +) callconv(.c) noreturn { + const alignment = @as(usize, 1) << @intCast(data.log_alignment); + const handle: usize = @intFromPtr(pointer); + + if (pointer == null) { + logMessage( + "{s} null pointer of type {s}", + .{ data.kind.getName(), data.type_descriptor.getName() }, + ); + } else if (!std.mem.isAligned(handle, alignment)) { + logMessage( + "{s} misaligned address 0x{x} for type {s}, which requires {} byte alignment", + .{ data.kind.getName(), handle, data.type_descriptor.getName(), alignment }, + ); + } else { + logMessage( + "{s} address 0x{x} with insufficient space for an object of type {s}", + .{ data.kind.getName(), handle, data.type_descriptor.getName() }, + ); + } +} + +const UnreachableData = extern struct { + loc: SourceLocation, +}; + +fn builtinUnreachable(_: *const UnreachableData) callconv(.c) noreturn { + logMessage("execution reached an unreachable program point", .{}); +} + +fn missingReturn(_: *const UnreachableData) callconv(.c) noreturn { + logMessage("execution reached the end of a value-returning function without returning a value", .{}); +} + +const NonNullReturnData = extern struct { + attribute_loc: SourceLocation, +}; + +fn nonNullReturn(_: *const NonNullReturnData) callconv(.c) noreturn { + logMessage("null pointer returned from function declared to never return null", .{}); +} + +const NonNullArgData = extern struct { + loc: SourceLocation, + attribute_loc: SourceLocation, + arg_index: i32, +}; + +fn nonNullArg(data: *const NonNullArgData) callconv(.c) noreturn { + logMessage( + "null pointer passed as argument {}, which is declared to never be null", + .{data.arg_index}, + ); +} + +const InvalidValueData = extern struct { + loc: SourceLocation, + type_descriptor: *const TypeDescriptor, +}; + +fn loadInvalidValue( + data: *const InvalidValueData, + value_handle: ValueHandle, +) callconv(.c) noreturn { + logMessage("load of value {}, which is not valid for type {s}", .{ + value_handle.getValue(data), data.type_descriptor.getName(), + }); +} + +fn SimpleHandler(comptime error_name: []const u8) type { + return struct { + fn handler() callconv(.c) noreturn { + logMessage("{s}", .{error_name}); + } + }; +} + +inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn { + std.debug.panicExtra(null, @returnAddress(), fmt, args); +} + +fn exportHandler( + handler: anytype, + comptime sym_name: []const u8, + comptime abort: bool, +) void { + const linkage = if (builtin.is_test) .internal else .weak; + { + const N = "__ubsan_handle_" ++ sym_name; + @export(handler, .{ .name = N, .linkage = linkage }); + } + if (abort) { + const N = "__ubsan_handle_" ++ sym_name ++ "_abort"; + @export(handler, .{ .name = N, .linkage = linkage }); + } +} + +fn exportMinimal( + err_name: anytype, + comptime sym_name: []const u8, + comptime abort: bool, +) void { + const handler = &SimpleHandler(err_name).handler; + const linkage = if (builtin.is_test) .internal else .weak; + { + const N = "__ubsan_handle_" ++ sym_name ++ "_minimal"; + @export(handler, .{ .name = N, .linkage = linkage }); + } + if (abort) { + const N = "__ubsan_handle_" ++ sym_name ++ "_minimal_abort"; + @export(handler, .{ .name = N, .linkage = linkage }); + } +} + +fn exportHelper( + comptime err_name: []const u8, + comptime sym_name: []const u8, + comptime abort: bool, +) void { + exportHandler(&SimpleHandler(err_name).handler, sym_name, abort); + exportMinimal(err_name, sym_name, abort); +} + +comptime { + overflowHandler("add_overflow", "+"); + overflowHandler("sub_overflow", "-"); + overflowHandler("mul_overflow", "*"); + exportHandler(&negationHandler, "negate_overflow", true); + exportHandler(&divRemHandler, "divrem_overflow", true); + exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true); + exportHandler(&shiftOob, "shift_out_of_bounds", true); + exportHandler(&outOfBounds, "out_of_bounds", true); + exportHandler(&pointerOverflow, "pointer_overflow", true); + exportHandler(&typeMismatch, "type_mismatch_v1", true); + exportHandler(&builtinUnreachable, "builtin_unreachable", false); + exportHandler(&missingReturn, "missing_return", false); + exportHandler(&nonNullReturn, "nonnull_return_v1", true); + exportHandler(&nonNullArg, "nonnull_arg", true); + exportHandler(&loadInvalidValue, "load_invalid_value", true); + + exportHelper("vla-bound-not-positive", "vla_bound_not_positive", true); + exportHelper("float-cast-overflow", "float_cast_overflow", true); + exportHelper("invalid-builtin", "invalid_builtin", true); + exportHelper("function-type-mismatch", "function_type_mismatch", true); + exportHelper("implicit-conversion", "implicit_conversion", true); + exportHelper("nullability-arg", "nullability_arg", true); + exportHelper("nullability-return", "nullability_return", true); + exportHelper("cfi-check-fail", "cfi_check_fail", true); + exportHelper("function-type-mismatch-v1", "function_type_mismatch_v1", true); + + exportMinimal("builtin-unreachable", "builtin_unreachable", false); + exportMinimal("add-overflow", "add_overflow", true); + exportMinimal("sub-overflow", "sub_overflow", true); + exportMinimal("mul-overflow", "mul_overflow", true); + exportMinimal("negation-handler", "negate_overflow", true); + exportMinimal("divrem-handler", "divrem_overflow", true); + exportMinimal("alignment-assumption-handler", "alignment_assumption", true); + exportMinimal("shift-oob", "shift_out_of_bounds", true); + exportMinimal("out-of-bounds", "out_of_bounds", true); + exportMinimal("pointer-overflow", "pointer_overflow", true); + exportMinimal("type-mismatch", "type_mismatch", true); + + // these checks are nearly impossible to duplicate in zig, as they rely on nuances + // in the Itanium C++ ABI. + // exportHelper("dynamic_type_cache_miss", "dynamic-type-cache-miss", true); + // exportHelper("vptr_type_cache", "vptr-type-cache", true); +} diff --git a/src/Compilation.zig b/src/Compilation.zig @@ -79,6 +79,7 @@ implib_emit: ?Path, docs_emit: ?Path, root_name: [:0]const u8, include_compiler_rt: bool, +include_ubsan_rt: bool, /// Resolved into known paths, any GNU ld scripts already resolved. link_inputs: []const link.Input, /// Needed only for passing -F args to clang. @@ -226,6 +227,12 @@ libunwind_static_lib: ?CrtFile = null, /// Populated when we build the TSAN library. A Job to build this is placed in the queue /// and resolved before calling linker.flush(). tsan_lib: ?CrtFile = null, +/// Populated when we build the UBSAN library. A Job to build this is placed in the queue +/// and resolved before calling linker.flush(). +ubsan_rt_lib: ?CrtFile = null, +/// Populated when we build the UBSAN object. A Job to build this is placed in the queue +/// and resolved before calling linker.flush(). +ubsan_rt_obj: ?CrtFile = null, /// Populated when we build the libc static library. A Job to build this is placed in the queue /// and resolved before calling linker.flush(). libc_static_lib: ?CrtFile = null, @@ -283,6 +290,8 @@ digest: ?[Cache.bin_digest_len]u8 = null, const QueuedJobs = struct { compiler_rt_lib: bool = false, compiler_rt_obj: bool = false, + ubsan_rt_lib: bool = false, + ubsan_rt_obj: bool = false, fuzzer_lib: bool = false, update_builtin_zig: bool, musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false), @@ -789,6 +798,7 @@ pub const MiscTask = enum { libcxx, libcxxabi, libtsan, + libubsan, libfuzzer, wasi_libc_crt_file, compiler_rt, @@ -1064,6 +1074,7 @@ pub const CreateOptions = struct { /// Position Independent Executable. If the output mode is not an /// executable this field is ignored. want_compiler_rt: ?bool = null, + want_ubsan_rt: ?bool = null, want_lto: ?bool = null, function_sections: bool = false, data_sections: bool = false, @@ -1297,6 +1308,9 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const include_compiler_rt = options.want_compiler_rt orelse (!options.skip_linker_dependencies and is_exe_or_dyn_lib); + const include_ubsan_rt = options.want_ubsan_rt orelse + (!options.skip_linker_dependencies and is_exe_or_dyn_lib); + if (include_compiler_rt and output_mode == .Obj) { // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` // injected into the object. @@ -1323,6 +1337,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod); } + if (include_ubsan_rt and output_mode == .Obj) { + const ubsan_rt_mod = try Package.Module.create(arena, .{ + .global_cache_directory = options.global_cache_directory, + .paths = .{ + .root = .{ + .root_dir = options.zig_lib_directory, + }, + .root_src_path = "ubsan.zig", + }, + .fully_qualified_name = "ubsan_rt", + .cc_argv = &.{}, + .inherited = .{}, + .global = options.config, + .parent = options.root_mod, + .builtin_mod = options.root_mod.getBuiltinDependency(), + .builtin_modules = null, // `builtin_mod` is set + }); + try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod); + } + if (options.verbose_llvm_cpu_features) { if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { const target = options.root_mod.resolved_target.result; @@ -1500,6 +1534,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .version = options.version, .libc_installation = libc_dirs.libc_installation, .include_compiler_rt = include_compiler_rt, + .include_ubsan_rt = include_ubsan_rt, .link_inputs = options.link_inputs, .framework_dirs = options.framework_dirs, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, @@ -1885,6 +1920,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } } + if (comp.include_ubsan_rt and capable_of_building_compiler_rt) { + if (is_exe_or_dyn_lib) { + log.debug("queuing a job to build ubsan_rt_lib", .{}); + comp.job_queued_ubsan_rt_lib = true; + } else if (output_mode != .Obj) { + log.debug("queuing a job to build ubsan_rt_obj", .{}); + comp.job_queued_ubsan_rt_obj = true; + } + } + if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) { log.debug("queuing a job to build libfuzzer", .{}); comp.queued_jobs.fuzzer_lib = true; @@ -1937,9 +1982,16 @@ pub fn destroy(comp: *Compilation) void { if (comp.compiler_rt_obj) |*crt_file| { crt_file.deinit(gpa); } + if (comp.ubsan_rt_lib) |*crt_file| { + crt_file.deinit(gpa); + } + if (comp.ubsan_rt_obj) |*crt_file| { + crt_file.deinit(gpa); + } if (comp.fuzzer_lib) |*crt_file| { crt_file.deinit(gpa); } + if (comp.libc_static_lib) |*crt_file| { crt_file.deinit(gpa); } @@ -2207,6 +2259,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { _ = try pt.importPkg(zcu.main_mod); } + if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { + _ = try pt.importPkg(ubsan_rt_mod); + } + if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { _ = try pt.importPkg(compiler_rt_mod); } @@ -2248,6 +2304,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod); } + + if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { + try comp.queueJob(.{ .analyze_mod = ubsan_rt_mod }); + zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod); + } } try comp.performAllTheWork(main_progress_node); @@ -2593,6 +2654,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.add(comp.link_eh_frame_hdr); man.hash.add(comp.skip_linker_dependencies); man.hash.add(comp.include_compiler_rt); + man.hash.add(comp.include_ubsan_rt); man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.addListOfBytes(comp.framework_dirs); @@ -3683,6 +3745,14 @@ fn performAllTheWorkInner( comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node }); } + if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) { + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan.zig", .libubsan, .Lib, &comp.ubsan_rt_lib, main_progress_node }); + } + + if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) { + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan.zig", .libubsan, .Obj, &comp.ubsan_rt_obj, main_progress_node }); + } + if (comp.queued_jobs.glibc_shared_objects) { comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node }); } @@ -5916,7 +5986,11 @@ pub fn addCCArgs( // These args have to be added after the `-fsanitize` arg or // they won't take effect. if (mod.sanitize_c) { + // This check requires implementing the Itanium C++ ABI. + // We would make it `-fsanitize-trap=vptr`, however this check requires + // a full runtime due to the type hashing involved. try argv.append("-fno-sanitize=vptr"); + // It is very common, and well-defined, for a pointer on one side of a C ABI // to have a different but compatible element type. Examples include: // `char*` vs `uint8_t*` on a system with 8-bit bytes @@ -5926,6 +6000,8 @@ pub fn addCCArgs( // function was called. try argv.append("-fno-sanitize=function"); + // It's recommended to use the minimal runtime in production environments + // due to the security implications of the full runtime. if (mod.optimize_mode == .ReleaseSafe) { try argv.append("-fsanitize-minimal-runtime"); } diff --git a/src/link.zig b/src/link.zig @@ -1107,6 +1107,11 @@ pub const File = struct { else null; + const ubsan_rt_path: ?Path = if (comp.include_ubsan_rt) + comp.ubsan_rt_obj.?.full_object_path + else + null; + // This function follows the same pattern as link.Elf.linkWithLLD so if you want some // insight as to what's going on here you can read that function body which is more // well-commented. @@ -1136,6 +1141,7 @@ pub const File = struct { } try man.addOptionalFile(zcu_obj_path); try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -1181,6 +1187,7 @@ pub const File = struct { } if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); + if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); if (comp.verbose_link) { std.debug.print("ar rcs {s}", .{full_out_path_z}); diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -2162,6 +2162,15 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); } + const ubsan_rt_path: ?Path = blk: { + if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; + if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + if (ubsan_rt_path) |path| { + try argv.append(try path.toString(arena)); + } + if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { if (!comp.config.link_libc) { if (comp.libc_static_lib) |lib| { diff --git a/src/link/Elf.zig b/src/link/Elf.zig @@ -1541,6 +1541,11 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; break :blk null; }; + const ubsan_rt_path: ?Path = blk: { + if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; + if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; // Here we want to determine whether we can save time by not invoking LLD when the // 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 } try man.addOptionalFile(module_obj_path); try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); @@ -1974,6 +1980,10 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s try argv.append(try lib.full_object_path.toString(arena)); } + if (ubsan_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + // libc if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies and diff --git a/src/link/MachO.zig b/src/link/MachO.zig @@ -344,11 +344,21 @@ pub fn deinit(self: *MachO) void { self.thunks.deinit(gpa); } -pub fn flush(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn flush( + self: *MachO, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.File.FlushError!void { try self.flushModule(arena, tid, prog_node); } -pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn flushModule( + self: *MachO, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -409,6 +419,16 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path)); } + if (comp.ubsan_rt_lib) |crt_file| { + const path = crt_file.full_object_path; + self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| + diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); + } else if (comp.ubsan_rt_obj) |crt_file| { + const path = crt_file.full_object_path; + self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| + diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); + } + for (positionals.items) |link_input| { self.classifyInputFile(link_input) catch |err| diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); @@ -813,6 +833,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); + if (comp.ubsan_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); + if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); } Compilation.dump_argv(argv.items); diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig @@ -97,6 +97,10 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path)); } + if (comp.include_ubsan_rt) { + try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path)); + } + for (positionals.items) |link_input| { macho_file.classifyInputFile(link_input) catch |err| diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig @@ -3879,6 +3879,11 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; break :blk null; }; + const ubsan_rt_path: ?Path = blk: { + if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; + if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; + break :blk null; + }; const id_symlink_basename = "lld.id"; @@ -3901,6 +3906,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: } try man.addOptionalFile(module_obj_path); try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); man.hash.add(wasm.base.stack_size); man.hash.add(wasm.base.build_id); @@ -4148,6 +4154,10 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: try argv.append(try p.toString(arena)); } + if (ubsan_rt_path) |p| { + try argv.append(try p.toStringZ(arena)); + } + if (comp.verbose_link) { // Skip over our own name so that the LLD linker name is the first argv item. Compilation.dump_argv(argv.items[1..]); diff --git a/src/main.zig b/src/main.zig @@ -849,6 +849,7 @@ fn buildOutputType( var emit_h: Emit = .no; var soname: SOName = undefined; var want_compiler_rt: ?bool = null; + var want_ubsan_rt: ?bool = null; var linker_script: ?[]const u8 = null; var version_script: ?[]const u8 = null; var linker_repro: ?bool = null; @@ -1376,6 +1377,10 @@ fn buildOutputType( want_compiler_rt = true; } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { want_compiler_rt = false; + } else if (mem.eql(u8, arg, "-fubsan-rt")) { + want_ubsan_rt = true; + } else if (mem.eql(u8, arg, "-fno-ubsan-rt")) { + want_ubsan_rt = false; } else if (mem.eql(u8, arg, "-feach-lib-rpath")) { create_module.each_lib_rpath = true; } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { @@ -3504,6 +3509,7 @@ fn buildOutputType( .windows_lib_names = create_module.windows_libs.keys(), .wasi_emulated_libs = create_module.wasi_emulated_libs.items, .want_compiler_rt = want_compiler_rt, + .want_ubsan_rt = want_ubsan_rt, .hash_style = hash_style, .linker_script = linker_script, .version_script = version_script,