-
-
Save moosichu/ee3dd8407653640a36fb9625ac586082 to your computer and use it in GitHub Desktop.
Zig Ubsan
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
patches for 0.10.1 here: ziglang/zig#5165