zig

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

blob cbfb2bbc (12246B) - Raw


      1 //! Minimal UBSan Runtime
      2 
      3 const std = @import("std");
      4 const builtin = @import("builtin");
      5 const assert = std.debug.assert;
      6 
      7 const SourceLocation = extern struct {
      8     file_name: ?[*:0]const u8,
      9     line: u32,
     10     col: u32,
     11 };
     12 
     13 const TypeDescriptor = extern struct {
     14     kind: Kind,
     15     info: Info,
     16     // name: [?:0]u8
     17 
     18     const Kind = enum(u16) {
     19         integer = 0x0000,
     20         float = 0x0001,
     21         unknown = 0xFFFF,
     22     };
     23 
     24     const Info = extern union {
     25         integer: packed struct(u16) {
     26             signed: bool,
     27             bit_width: u15,
     28         },
     29     };
     30 
     31     fn getIntegerSize(desc: TypeDescriptor) u64 {
     32         assert(desc.kind == .integer);
     33         const bit_width = desc.info.integer.bit_width;
     34         return @as(u64, 1) << @intCast(bit_width);
     35     }
     36 
     37     fn isSigned(desc: TypeDescriptor) bool {
     38         return desc.kind == .integer and desc.info.integer.signed;
     39     }
     40 
     41     fn getName(desc: *const TypeDescriptor) [:0]const u8 {
     42         return std.mem.span(@as([*:0]const u8, @ptrCast(desc)) + @sizeOf(TypeDescriptor));
     43     }
     44 };
     45 
     46 const ValueHandle = *const opaque {
     47     fn getValue(handle: ValueHandle, data: anytype) Value {
     48         return .{ .handle = handle, .type_descriptor = data.type_descriptor };
     49     }
     50 };
     51 
     52 const Value = extern struct {
     53     type_descriptor: *const TypeDescriptor,
     54     handle: ValueHandle,
     55 
     56     fn getUnsignedInteger(value: Value) u128 {
     57         assert(!value.type_descriptor.isSigned());
     58         const size = value.type_descriptor.getIntegerSize();
     59         const max_inline_size = @bitSizeOf(ValueHandle);
     60         if (size <= max_inline_size) {
     61             return @intFromPtr(value.handle);
     62         }
     63 
     64         return switch (size) {
     65             64 => @as(*const u64, @alignCast(@ptrCast(value.handle))).*,
     66             128 => @as(*const u128, @alignCast(@ptrCast(value.handle))).*,
     67             else => unreachable,
     68         };
     69     }
     70 
     71     fn getSignedInteger(value: Value) i128 {
     72         assert(value.type_descriptor.isSigned());
     73         const size = value.type_descriptor.getIntegerSize();
     74         const max_inline_size = @bitSizeOf(ValueHandle);
     75         if (size <= max_inline_size) {
     76             const extra_bits: u6 = @intCast(max_inline_size - size);
     77             const handle: i64 = @bitCast(@intFromPtr(value.handle));
     78             return (handle << extra_bits) >> extra_bits;
     79         }
     80         return switch (size) {
     81             64 => @as(*const i64, @alignCast(@ptrCast(value.handle))).*,
     82             128 => @as(*const i128, @alignCast(@ptrCast(value.handle))).*,
     83             else => unreachable,
     84         };
     85     }
     86 
     87     fn isMinusOne(value: Value) bool {
     88         return value.type_descriptor.isSigned() and
     89             value.getSignedInteger() == -1;
     90     }
     91 
     92     fn isNegative(value: Value) bool {
     93         return value.type_descriptor.isSigned() and
     94             value.getSignedInteger() < 0;
     95     }
     96 
     97     fn getPositiveInteger(value: Value) u128 {
     98         if (value.type_descriptor.isSigned()) {
     99             const signed = value.getSignedInteger();
    100             assert(signed >= 0);
    101             return @intCast(signed);
    102         } else {
    103             return value.getUnsignedInteger();
    104         }
    105     }
    106 
    107     pub fn format(
    108         value: Value,
    109         comptime fmt: []const u8,
    110         _: std.fmt.FormatOptions,
    111         writer: anytype,
    112     ) !void {
    113         comptime assert(fmt.len == 0);
    114 
    115         switch (value.type_descriptor.kind) {
    116             .integer => {
    117                 if (value.type_descriptor.isSigned()) {
    118                     try writer.print("{}", .{value.getSignedInteger()});
    119                 } else {
    120                     try writer.print("{}", .{value.getUnsignedInteger()});
    121                 }
    122             },
    123             .float => @panic("TODO: write float"),
    124             .unknown => try writer.writeAll("(unknown)"),
    125         }
    126     }
    127 };
    128 
    129 const OverflowData = extern struct {
    130     loc: SourceLocation,
    131     type_descriptor: *const TypeDescriptor,
    132 };
    133 
    134 fn overflowHandler(
    135     comptime sym_name: []const u8,
    136     comptime operator: []const u8,
    137 ) void {
    138     const S = struct {
    139         fn handler(
    140             data: *OverflowData,
    141             lhs_handle: ValueHandle,
    142             rhs_handle: ValueHandle,
    143         ) callconv(.C) noreturn {
    144             const lhs = lhs_handle.getValue(data);
    145             const rhs = rhs_handle.getValue(data);
    146 
    147             const is_signed = data.type_descriptor.isSigned();
    148             const fmt = "{s} integer overflow: " ++ "{} " ++
    149                 operator ++ " {} cannot be represented in type {s}";
    150 
    151             logMessage(fmt, .{
    152                 if (is_signed) "signed" else "unsigned",
    153                 lhs,
    154                 rhs,
    155                 data.type_descriptor.getName(),
    156             });
    157         }
    158     };
    159 
    160     exportHandler(&S.handler, sym_name, true);
    161 }
    162 
    163 fn negationHandler(
    164     data: *const OverflowData,
    165     old_value_handle: ValueHandle,
    166 ) callconv(.C) noreturn {
    167     const old_value = old_value_handle.getValue(data);
    168     logMessage(
    169         "negation of {} cannot be represented in type {s}",
    170         .{ old_value, data.type_descriptor.getName() },
    171     );
    172 }
    173 
    174 fn divRemHandler(
    175     data: *const OverflowData,
    176     lhs_handle: ValueHandle,
    177     rhs_handle: ValueHandle,
    178 ) callconv(.C) noreturn {
    179     const is_signed = data.type_descriptor.isSigned();
    180     const lhs = lhs_handle.getValue(data);
    181     const rhs = rhs_handle.getValue(data);
    182 
    183     if (is_signed and rhs.getSignedInteger() == -1) {
    184         logMessage(
    185             "division of {} by -1 cannot be represented in type {s}",
    186             .{ lhs, data.type_descriptor.getName() },
    187         );
    188     } else logMessage("division by zero", .{});
    189 }
    190 
    191 const AlignmentAssumptionData = extern struct {
    192     loc: SourceLocation,
    193     assumption_loc: SourceLocation,
    194     type_descriptor: *const TypeDescriptor,
    195 };
    196 
    197 fn alignmentAssumptionHandler(
    198     data: *const AlignmentAssumptionData,
    199     pointer: ValueHandle,
    200     alignment: ValueHandle,
    201     maybe_offset: ?ValueHandle,
    202 ) callconv(.C) noreturn {
    203     _ = pointer;
    204     // TODO: add the hint here?
    205     // const real_pointer = @intFromPtr(pointer) - @intFromPtr(maybe_offset);
    206     // const lsb = @ctz(real_pointer);
    207     // const actual_alignment = @as(u64, 1) << @intCast(lsb);
    208     // const mask = @intFromPtr(alignment) - 1;
    209     // const misalignment_offset = real_pointer & mask;
    210     // _ = actual_alignment;
    211     // _ = misalignment_offset;
    212 
    213     if (maybe_offset) |offset| {
    214         logMessage(
    215             "assumption of {} byte alignment (with offset of {} byte) for pointer of type {s} failed",
    216             .{ alignment.getValue(data), @intFromPtr(offset), data.type_descriptor.getName() },
    217         );
    218     } else {
    219         logMessage(
    220             "assumption of {} byte alignment for pointer of type {s} failed",
    221             .{ alignment.getValue(data), data.type_descriptor.getName() },
    222         );
    223     }
    224 }
    225 
    226 const ShiftOobData = extern struct {
    227     loc: SourceLocation,
    228     lhs_type: *const TypeDescriptor,
    229     rhs_type: *const TypeDescriptor,
    230 };
    231 
    232 fn shiftOob(
    233     data: *const ShiftOobData,
    234     lhs_handle: ValueHandle,
    235     rhs_handle: ValueHandle,
    236 ) callconv(.C) noreturn {
    237     const lhs: Value = .{ .handle = lhs_handle, .type_descriptor = data.lhs_type };
    238     const rhs: Value = .{ .handle = rhs_handle, .type_descriptor = data.rhs_type };
    239 
    240     if (rhs.isNegative() or
    241         rhs.getPositiveInteger() >= data.lhs_type.getIntegerSize())
    242     {
    243         if (rhs.isNegative()) {
    244             logMessage("shift exponent {} is negative", .{rhs});
    245         } else {
    246             logMessage(
    247                 "shift exponent {} is too large for {}-bit type {s}",
    248                 .{ rhs, data.lhs_type.getIntegerSize(), data.lhs_type.getName() },
    249             );
    250         }
    251     } else {
    252         if (lhs.isNegative()) {
    253             logMessage("left shift of negative value {}", .{lhs});
    254         } else {
    255             logMessage(
    256                 "left shift of {} by {} places cannot be represented in type {s}",
    257                 .{ lhs, rhs, data.lhs_type.getName() },
    258             );
    259         }
    260     }
    261 }
    262 
    263 const OutOfBoundsData = extern struct {
    264     loc: SourceLocation,
    265     array_type: *const TypeDescriptor,
    266     index_type: *const TypeDescriptor,
    267 };
    268 
    269 fn outOfBounds(data: *const OutOfBoundsData, index_handle: ValueHandle) callconv(.C) noreturn {
    270     const index: Value = .{ .handle = index_handle, .type_descriptor = data.index_type };
    271     logMessage(
    272         "index {} out of bounds for type {s}",
    273         .{ index, data.array_type.getName() },
    274     );
    275 }
    276 
    277 const PointerOverflowData = extern struct {
    278     loc: SourceLocation,
    279 };
    280 
    281 fn pointerOverflow(
    282     _: *const PointerOverflowData,
    283     base: usize,
    284     result: usize,
    285 ) callconv(.C) noreturn {
    286     if (base == 0) {
    287         if (result == 0) {
    288             logMessage("applying zero offset to null pointer", .{});
    289         } else {
    290             logMessage("applying non-zero offset {} to null pointer", .{result});
    291         }
    292     } else {
    293         if (result == 0) {
    294             logMessage(
    295                 "applying non-zero offset to non-null pointer 0x{x} produced null pointer",
    296                 .{base},
    297             );
    298         } else {
    299             @panic("TODO");
    300         }
    301     }
    302 }
    303 
    304 const TypeMismatchData = extern struct {
    305     loc: SourceLocation,
    306     type_descriptor: *const TypeDescriptor,
    307     log_alignment: u8,
    308     kind: enum(u8) {
    309         load,
    310         store,
    311         reference_binding,
    312         member_access,
    313         member_call,
    314         constructor_call,
    315         downcast_pointer,
    316         downcast_reference,
    317         upcast,
    318         upcast_to_virtual_base,
    319         nonnull_assign,
    320         dynamic_operation,
    321     },
    322 };
    323 
    324 fn simpleHandler(
    325     comptime sym_name: []const u8,
    326     comptime error_name: []const u8,
    327     comptime abort: bool,
    328 ) void {
    329     const S = struct {
    330         fn handler() callconv(.C) noreturn {
    331             logMessage("{s}", .{error_name});
    332         }
    333     };
    334     exportHandler(&S.handler, sym_name, abort);
    335 }
    336 
    337 inline fn logMessage(comptime fmt: []const u8, args: anytype) noreturn {
    338     std.debug.panicExtra(null, @returnAddress(), fmt, args);
    339 }
    340 
    341 fn exportHandler(
    342     handler: anytype,
    343     comptime sym_name: []const u8,
    344     comptime abort: bool,
    345 ) void {
    346     const linkage = if (builtin.is_test) .internal else .weak;
    347     {
    348         const N = "__ubsan_handle_" ++ sym_name;
    349         @export(handler, .{ .name = N, .linkage = linkage });
    350     }
    351     if (abort) {
    352         const N = "__ubsan_handle_" ++ sym_name ++ "_abort";
    353         @export(handler, .{ .name = N, .linkage = linkage });
    354     }
    355 }
    356 
    357 comptime {
    358     overflowHandler("add_overflow", "+");
    359     overflowHandler("sub_overflow", "-");
    360     overflowHandler("mul_overflow", "*");
    361     exportHandler(&negationHandler, "negate_overflow", true);
    362     exportHandler(&divRemHandler, "divrem_overflow", true);
    363     exportHandler(&alignmentAssumptionHandler, "alignment_assumption", true);
    364     exportHandler(&shiftOob, "shift_out_of_bounds", true);
    365     exportHandler(&outOfBounds, "out_of_bounds", true);
    366     exportHandler(&pointerOverflow, "pointer_overflow", true);
    367 
    368     simpleHandler("type_mismatch_v1", "type-mismatch-v1", true);
    369     simpleHandler("builtin_unreachable", "builtin-unreachable", false);
    370     simpleHandler("missing_return", "missing-return", false);
    371     simpleHandler("vla_bound_not_positive", "vla-bound-not-positive", true);
    372     simpleHandler("float_cast_overflow", "float-cast-overflow", true);
    373     simpleHandler("load_invalid_value", "load-invalid-value", true);
    374     simpleHandler("invalid_builtin", "invalid-builtin", true);
    375     simpleHandler("function_type_mismatch", "function-type-mismatch", true);
    376     simpleHandler("implicit_conversion", "implicit-conversion", true);
    377     simpleHandler("nonnull_arg", "nonnull-arg", true);
    378     simpleHandler("nonnull_return", "nonnull-return", true);
    379     simpleHandler("nullability_arg", "nullability-arg", true);
    380     simpleHandler("nullability_return", "nullability-return", true);
    381     simpleHandler("cfi_check_fail", "cfi-check-fail", true);
    382     simpleHandler("function_type_mismatch_v1", "function-type-mismatch-v1", true);
    383 
    384     // these checks are nearly impossible to duplicate in zig, as they rely on nuances
    385     // in the Itanium C++ ABI.
    386     simpleHandler("dynamic_type_cache_miss", "dynamic-type-cache-miss", true);
    387     simpleHandler("vptr_type_cache", "vptr-type-cache", true);
    388 }