mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
509 lines
16 KiB
Zig
509 lines
16 KiB
Zig
//! 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);
|
|
}
|