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 }