mirror of
https://github.com/ziglang/zig.git
synced 2026-02-17 06:49:23 +00:00
ubsan: add a basic runtime
This commit is contained in:
parent
e902c231c8
commit
5e0073c898
@ -44,6 +44,7 @@ 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");
|
||||
|
||||
390
lib/std/ubsan.zig
Normal file
390
lib/std/ubsan.zig
Normal file
@ -0,0 +1,390 @@
|
||||
//! 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}\n";
|
||||
|
||||
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}\n",
|
||||
.{ 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}\n",
|
||||
.{ lhs, data.type_descriptor.getName() },
|
||||
);
|
||||
} else logMessage("division by zero\n", .{});
|
||||
}
|
||||
|
||||
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\n",
|
||||
.{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() },
|
||||
);
|
||||
} else {
|
||||
logMessage(
|
||||
"assumption of {} byte alignment for pointer of type {s} failed\n",
|
||||
.{ 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\n", .{rhs});
|
||||
} else {
|
||||
logMessage(
|
||||
"shift exponent {} is too large for {}-bit type {s}\n",
|
||||
.{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (lhs.isNegative()) {
|
||||
logMessage("left shift of negative value {}\n", .{lhs});
|
||||
} else {
|
||||
logMessage(
|
||||
"left shift of {} by {} places cannot be represented in type {s}\n",
|
||||
.{ 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}\n",
|
||||
.{ 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\n", .{});
|
||||
} else {
|
||||
logMessage("applying non-zero offset {} to null pointer\n", .{result});
|
||||
}
|
||||
} else {
|
||||
if (result == 0) {
|
||||
logMessage(
|
||||
"applying non-zero offset to non-null pointer 0x{x} produced null pointer\n",
|
||||
.{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 simpleHandler(
|
||||
comptime sym_name: []const u8,
|
||||
comptime error_name: []const u8,
|
||||
comptime abort: bool,
|
||||
) void {
|
||||
const S = struct {
|
||||
fn handler() callconv(.C) noreturn {
|
||||
logMessage("{s}", .{error_name});
|
||||
}
|
||||
};
|
||||
exportHandler(&S.handler, sym_name, abort);
|
||||
}
|
||||
|
||||
inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
|
||||
std.debug.print(fmt, args);
|
||||
std.debug.dumpCurrentStackTrace(@returnAddress());
|
||||
std.posix.abort();
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
simpleHandler("type_mismatch_v1", "type-mismatch-v1", true);
|
||||
simpleHandler("builtin_unreachable", "builtin-unreachable", false);
|
||||
simpleHandler("missing_return", "missing-return", false);
|
||||
simpleHandler("vla_bound_not_positive", "vla-bound-not-positive", true);
|
||||
simpleHandler("float_cast_overflow", "float-cast-overflow", true);
|
||||
simpleHandler("load_invalid_value", "load-invalid-value", true);
|
||||
simpleHandler("invalid_builtin", "invalid-builtin", true);
|
||||
simpleHandler("function_type_mismatch", "function-type-mismatch", true);
|
||||
simpleHandler("implicit_conversion", "implicit-conversion", true);
|
||||
simpleHandler("nonnull_arg", "nonnull-arg", true);
|
||||
simpleHandler("nonnull_return", "nonnull-return", true);
|
||||
simpleHandler("nullability_arg", "nullability-arg", true);
|
||||
simpleHandler("nullability_return", "nullability-return", true);
|
||||
simpleHandler("cfi_check_fail", "cfi-check-fail", true);
|
||||
simpleHandler("function_type_mismatch_v1", "function-type-mismatch-v1", true);
|
||||
|
||||
// these checks are nearly impossible to duplicate in zig, as they rely on nuances
|
||||
// in the Itanium C++ ABI.
|
||||
simpleHandler("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
|
||||
simpleHandler("vptr_type_cache", "vptr-type-cache", true);
|
||||
}
|
||||
@ -5916,7 +5916,7 @@ pub fn addCCArgs(
|
||||
// These args have to be added after the `-fsanitize` arg or
|
||||
// they won't take effect.
|
||||
if (mod.sanitize_c) {
|
||||
try argv.append("-fsanitize-trap=undefined");
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user