Skip to content

Instantly share code, notes, and snippets.

@moosichu
Last active March 17, 2023 06:17
Show Gist options
  • Save moosichu/ee3dd8407653640a36fb9625ac586082 to your computer and use it in GitHub Desktop.
Save moosichu/ee3dd8407653640a36fb9625ac586082 to your computer and use it in GitHub Desktop.
Zig Ubsan
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
// Grabbed from this PR https://github.com/ziglang/zig/pull/5165
// For this issue https://github.com/ziglang/zig/issues/5163
// Useful references!
// https://compiler-rt.llvm.org/
// https://github.com/llvm-mirror/compiler-rt/blob/master/lib/ubsan
// https://github.com/llvm-mirror/compiler-rt/blob/master/lib/ubsan/ubsan_handlers.h
// https://github.com/llvm-mirror/compiler-rt/blob/master/lib/ubsan/ubsan_handlers.cpp
// https://github.com/llvm-mirror/compiler-rt/blob/master/lib/ubsan/ubsan_handlers_cxx.h
// https://github.com/llvm-mirror/compiler-rt/blob/master/lib/ubsan/ubsan_handlers_cxx.cpp"
// https://github.com/llvm-mirror/compiler-rt/tree/master/lib/ubsan_minimal
// Runtime support for Clang's Undefined Behavior sanitizer
const std = @import("std");
const ubsan_value = @import("ubsan_value.zig");
const builtin = std.builtin;
// Creates two handlers for a given error, both of them print the specified
// return message but the `abort_` version stops the execution of the program
// XXX: Don't depend on the stdlib
fn makeHandler(comptime error_msg: []const u8) type {
return struct {
pub fn recover_handler() callconv(.C) void {
std.debug.warn("ubsan: " ++ error_msg, .{});
}
pub fn abort_handler() callconv(.C) noreturn {
@panic("ubsan: " ++ error_msg);
}
};
}
const OverflowData = extern struct {
source_location: ubsan_value.SourceLocation,
type_descriptor: *const ubsan_value.TypeDescriptor, // this is a const ref in C++, hopefully this works?
};
const DynamicTypeCacheMissData = extern struct {
source_location: ubsan_value.SourceLocation,
type_descriptor: *const ubsan_value.TypeDescriptor,
type_info: ?*c_void,
type_check_kind: u8,
};
fn exportHandlers(comptime handlers: anytype, comptime export_name: []const u8) void {
const linkage: builtin.GlobalLinkage = if (std.builtin.is_test) .Internal else .Weak;
{
const handler_symbol = std.builtin.ExportOptions{ .name = "__ubsan_" ++ export_name, .linkage = linkage };
@export(handlers.recover_handler, handler_symbol);
}
{
const handler_symbol = std.builtin.ExportOptions{ .name = "__ubsan_" ++ export_name ++ "_abort", .linkage = linkage };
@export(handlers.abort_handler, handler_symbol);
}
}
const abort_log = std.debug.panic;
const warn_log = std.debug.warn;
fn handleOverflow(comptime report_log: anytype, comptime name: []const u8, comptime symbol: []const u8, overflow_data: *OverflowData, lhs: ubsan_value.ValueHandle, rhs: ubsan_value.ValueHandle) void {
const source_location = overflow_data.source_location.acquire();
const format_string = "ubsan: " ++ name ++ " Overflow: {} " ++ symbol ++ " {} in type {s}\n{s}:{}:{}\n";
const type_name = overflow_data.type_descriptor.getNameAsString();
if (overflow_data.type_descriptor.isSignedInteger()) {
const lhs_value = overflow_data.type_descriptor.getSignedIntValue(lhs);
const rhs_value = overflow_data.type_descriptor.getSignedIntValue(rhs);
report_log(format_string, .{ lhs_value, rhs_value, type_name, source_location.file_name, source_location.line, source_location.column });
} else {
const lhs_value = overflow_data.type_descriptor.getUnsignedIntValue(lhs);
const rhs_value = overflow_data.type_descriptor.getUnsignedIntValue(rhs);
report_log(format_string, .{ lhs_value, rhs_value, type_name, source_location.file_name, source_location.line, source_location.column });
}
}
fn makeOverflowHandler(comptime export_name: []const u8, comptime name: []const u8, comptime symbol: []const u8) void {
const handlers = struct {
pub fn recover_handler(overflow_data: *OverflowData, lhs: ubsan_value.ValueHandle, rhs: ubsan_value.ValueHandle) callconv(.C) void {
handleOverflow(warn_log, name, symbol, overflow_data, lhs, rhs);
}
pub fn abort_handler(overflow_data: *OverflowData, lhs: ubsan_value.ValueHandle, rhs: ubsan_value.ValueHandle) callconv(.C) void {
handleOverflow(abort_log, name, symbol, overflow_data, lhs, rhs);
}
};
exportHandlers(handlers, export_name);
}
comptime {
makeOverflowHandler("handle_add_overflow", "Addition", "+");
makeOverflowHandler("handle_sub_overflow", "Subtraction", "-");
makeOverflowHandler("handle_mul_overflow", "Multiplication", "*");
}
fn handleNegateOverflow(comptime report_log: anytype, overflow_data: *OverflowData, value: ubsan_value.ValueHandle) void {
const source_location = overflow_data.source_location.acquire();
const format_string = "ubsan: Negation of {} cannot be represented in type {s}\n{s}:{}:{}\n";
const type_name = overflow_data.type_descriptor.getNameAsString();
if (overflow_data.type_descriptor.isSignedInteger()) {
const int_value = overflow_data.type_descriptor.getSignedIntValue(value);
report_log(format_string, .{ int_value, type_name, source_location.file_name, source_location.line, source_location.column });
} else {
const int_value = overflow_data.type_descriptor.getUnsignedIntValue(value);
report_log(format_string, .{ int_value, type_name, source_location.file_name, source_location.line, source_location.column });
}
}
comptime {
const handlers = struct {
pub fn recover_handler(overflow_data: *OverflowData, value: ubsan_value.ValueHandle) callconv(.C) void {
handleNegateOverflow(warn_log, overflow_data, value);
}
pub fn abort_handler(overflow_data: *OverflowData, value: ubsan_value.ValueHandle) callconv(.C) void {
handleNegateOverflow(abort_log, overflow_data, value);
}
};
exportHandlers(handlers, "handle_negate_overflow");
}
fn handleDivremOverflow(comptime report_log: anytype, overflow_data: *OverflowData, lhs: ubsan_value.ValueHandle, rhs: ubsan_value.ValueHandle) void {
const source_location = overflow_data.source_location.acquire();
if (overflow_data.type_descriptor.isSignedInteger() and (overflow_data.type_descriptor.getSignedIntValue(rhs) == -1)) {
const format_string = "ubsan: Divsion of {} by -1 cannot be represented in type {s}\n{s}:{}:{}\n";
const type_name = overflow_data.type_descriptor.getNameAsString();
const int_value = overflow_data.type_descriptor.getSignedIntValue(lhs);
report_log(format_string, .{ int_value, type_name, source_location.file_name, source_location.line, source_location.column });
} else switch (overflow_data.type_descriptor.kind) {
.float, .integer => {
const format_string = "ubsan: Division by 0\n{s}:{}:{}\n";
report_log(format_string, .{ source_location.file_name, source_location.line, source_location.column });
},
.unknown => {
unreachable;
},
}
}
comptime {
const handlers = struct {
pub fn recover_handler(overflow_data: *OverflowData, lhs: ubsan_value.ValueHandle, rhs: ubsan_value.ValueHandle) callconv(.C) void {
handleDivremOverflow(warn_log, overflow_data, lhs, rhs);
}
pub fn abort_handler(overflow_data: *OverflowData, lhs: ubsan_value.ValueHandle, rhs: ubsan_value.ValueHandle) callconv(.C) void {
handleDivremOverflow(abort_log, overflow_data, lhs, rhs);
}
};
exportHandlers(handlers, "handle_divrem_overflow");
}
fn handleDynamicTypeCacheMiss(dynamic_type_cache_miss_data: *DynamicTypeCacheMissData, pointer: ubsan_value.ValueHandle, hash: ubsan_value.ValueHandle) bool {
_ = dynamic_type_cache_miss_data;
_ = pointer;
_ = hash;
// TODO(TRC):NowNow handle dynamic type cache miss properly
return false;
}
export fn __ubsan_handle_dynamic_type_cache_miss_abort(dynamic_type_cache_miss_data: *DynamicTypeCacheMissData, pointer: ubsan_value.ValueHandle, hash: ubsan_value.ValueHandle) callconv(.C) void {
if (handleDynamicTypeCacheMiss(dynamic_type_cache_miss_data, pointer, hash)) {
@panic("ubsan: " ++ "handle_dynamic_type_cache_miss");
}
}
export fn __ubsan_handle_dynamic_type_cache_miss(dynamic_type_cache_miss_data: *DynamicTypeCacheMissData, pointer: ubsan_value.ValueHandle, hash: ubsan_value.ValueHandle) callconv(.C) void {
if (handleDynamicTypeCacheMiss(dynamic_type_cache_miss_data, pointer, hash)) {
std.debug.warn("ubsan: " ++ "handle_dynamic_type_cache_miss", .{});
}
}
comptime {
const HANDLERS = .{
.{ "handle_type_mismatch", "type-mismatch", .Both, .Both },
.{ "handle_alignment_assumption", "alignment-assumption", .Both, .Both },
.{ "handle_add_overflow", "add-overflow", .Both, .Minimal },
.{ "handle_sub_overflow", "sub-overflow", .Both, .Minimal },
.{ "handle_mul_overflow", "mul-overflow", .Both, .Minimal },
.{ "handle_negate_overflow", "negate-overflow", .Both, .Minimal },
.{ "handle_divrem_overflow", "divrem-overflow", .Both, .Minimal },
.{ "handle_shift_out_of_bounds", "shift-out-of-bounds", .Both, .Both },
.{ "handle_out_of_bounds", "out-of-bounds", .Both, .Both },
.{ "handle_builtin_unreachable", "builtin-unreachable", .Recover, .Both },
.{ "handle_missing_return", "missing-return", .Recover, .Both },
.{ "handle_vla_bound_not_positive", "vla-bound-not-positive", .Both, .Both },
.{ "handle_float_cast_overflow", "float-cast-overflow", .Both, .Both },
.{ "handle_load_invalid_value", "load-invalid-value", .Both, .Both },
.{ "handle_invalid_builtin", "invalid-builtin", .Both, .Both },
.{ "handle_function_type_mismatch", "function-type-mismatch", .Both, .Both },
.{ "handle_implicit_conversion", "implicit-conversion", .Both, .Both },
.{ "handle_nonnull_arg", "nonnull-arg", .Both, .Both },
.{ "handle_nonnull_return", "nonnull-return", .Both, .Both },
.{ "handle_nullability_arg", "nullability-arg", .Both, .Both },
.{ "handle_nullability_return", "nullability-return", .Both, .Both },
.{ "handle_pointer_overflow", "pointer-overflow", .Both, .Both },
.{ "handle_cfi_check_fail", "cfi-check-fail", .Both, .Both },
.{ "handle_type_mismatch_v1", "type-mismatch-v1", .Both, .Full },
.{ "handle_function_type_mismatch_v1", "function-type-mismatch-v1", .Both, .Full },
// .{ "handle_dynamic_type_cache_miss", "dynamic-type-cache-miss", .Both, .Full },
.{ "vptr_type_cache", "vptr-type-cache", .Recover, .Full },
.{ "handle_sub_overflow_abort", "sub-overflow-abort", .Abort, .Full },
.{ "handle_shift_out_of_bounds_abort", "shift-out-of-bounds-abort", .Abort, .Full },
.{ "handle_pointer_overflow_abort", "pointer-overflow-abort", .Abort, .Full },
.{ "handle_out_of_bounds_abort", "out-of-bounds-abort", .Abort, .Full },
.{ "handle_nonnull_arg_abort", "nonnull-arg-abort", .Abort, .Full },
.{ "handle_negate_overflow_abort", "negate-overflow-abort", .Abort, .Full },
.{ "handle_mul_overflow_abort", "mul-overflow-abort", .Abort, .Full },
.{ "handle_load_invalid_value_abort", "load-invalid-value-abort", .Abort, .Full },
.{ "handle_float_cast_overflow_abort", "float-cast-overflow-abort", .Abort, .Full },
.{ "handle_divrem_overflow_abort", "divrem-overflow-abort", .Abort, .Full },
.{ "handle_add_overflow_abort", "add-overflow-abort", .Abort, .Full },
};
const linkage: builtin.GlobalLinkage = if (std.builtin.is_test) .Internal else .Weak;
inline for (HANDLERS) |entry| {
const handler = makeHandler(entry[1]);
if ((entry[2] == .Both or entry[2] == .Recover) and (entry[3] == .Both or entry[3] == .Full)) {
const handler_name = "__ubsan_" ++ entry[0];
const handler_symbol = std.builtin.ExportOptions{ .name = handler_name, .linkage = linkage };
@export(handler.recover_handler, handler_symbol);
}
if ((entry[2] == .Both or entry[2] == .Abort) and (entry[3] == .Both or entry[3] == .Full)) {
const handler_name = "__ubsan_" ++ entry[0] ++ "_abort";
const handler_symbol = std.builtin.ExportOptions{ .name = handler_name, .linkage = linkage };
@export(handler.abort_handler, handler_symbol);
}
// Minimal traps as well - these are meant to be simpler so should probably make them different implementations long-term
if ((entry[2] == .Both or entry[2] == .Recover) and (entry[3] == .Both or entry[3] == .Minimal)) {
const handler_name = "__ubsan_" ++ entry[0] ++ "_minimal";
const handler_symbol = std.builtin.ExportOptions{ .name = handler_name, .linkage = linkage };
@export(handler.recover_handler, handler_symbol);
}
if ((entry[2] == .Both or entry[2] == .Abort) and (entry[3] == .Both or entry[3] == .Minimal)) {
const handler_name = "__ubsan_" ++ entry[0] ++ "_minimal_abort";
const handler_symbol = std.builtin.ExportOptions{ .name = handler_name, .linkage = linkage };
@export(handler.abort_handler, handler_symbol);
}
}
}
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
pub const SourceLocation = extern struct {
file_name: ?[*:0]u8,
line: u32,
column: u32,
pub fn acquire(source_location: *SourceLocation) SourceLocation {
// TODO: Figure out why zig gives the error
// error: @atomicRmw atomic ordering must not be Unordered
// const returnColumn: u32 = @atomicRmw(u32, &source_location.column, .Xchg, ~@as(u32, 0), .Unordered);
const returnColumn: u32 = @atomicRmw(u32, &source_location.column, .Xchg, ~@as(u32, 0), .Monotonic);
return SourceLocation{
.file_name = source_location.file_name,
.line = source_location.line,
.column = returnColumn,
};
}
};
pub const TypeKind = enum(u16) {
// If you have an integer type:
// - Lowest bit on info = signed/unsigned integer
// - The remaining bits are log_2(bit width)
integer = 0x0000,
// If you have a floating point type:
// - Type info is the bit width.
float = 0x0001,
// Any other type. The value representation is unspecified.
unknown = 0xffff };
pub const TypeDescriptor = extern struct {
kind: TypeKind,
info: u16,
name: [1]u8,
pub fn getNameAsString(type_descriptor: *const TypeDescriptor) [*:0]const u8 {
return @ptrCast([*:0]const u8, &type_descriptor.name);
}
pub fn isSignedInteger(type_descriptor: *const TypeDescriptor) bool {
return type_descriptor.kind == .integer and (type_descriptor.info & 1) == 1;
}
pub fn getIntegerSize(type_descriptor: *const TypeDescriptor) u64 {
if (type_descriptor.kind != .integer) unreachable;
// Bit-shift the signed value away and then get 2^n out
return @as(u64, 1) << @intCast(u6, type_descriptor.info >> 1);
}
pub fn getFloatSize(type_descriptor: *const TypeDescriptor) u64 {
if (type_descriptor.kind != .float) unreachable;
return info;
}
fn getIntValueBits(type_descriptor: *const TypeDescriptor, value_handle: ValueHandle) u128 {
if (type_descriptor.kind != .integer) unreachable;
const size = type_descriptor.getIntegerSize();
const max_inline_size = @sizeOf(ValueHandle) * 8;
if (size <= max_inline_size) {
return @ptrToInt(value_handle);
} else {
if (size == 64) {
return @ptrCast(*u64, @alignCast(8, value_handle)).*;
} else if (size == 128) {
return @ptrCast(*u128, @alignCast(16, value_handle)).*;
} else {
unreachable;
}
}
}
pub fn getSignedIntValue(type_descriptor: *const TypeDescriptor, value_handle: ValueHandle) i128 {
if (!type_descriptor.isSignedInteger()) unreachable;
return @bitCast(i128, type_descriptor.getIntValueBits(value_handle));
}
pub fn getUnsignedIntValue(type_descriptor: *const TypeDescriptor, value_handle: ValueHandle) u128 {
if (type_descriptor.isSignedInteger()) unreachable;
return type_descriptor.getIntValueBits(value_handle);
}
};
// Represents either a float or an integer.
// If the type is small enough - its value is stored in
// in the ValueHandle pointer, otherwise it is what the
// the ValueHandle points at.
pub const ValueHandle = *c_void;
@franciscod
Copy link

patches for 0.10.1 here: ziglang/zig#5165

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment