Open scheibo opened 1 month ago
Simpler reproduction:
const std = @import("std");
fn mul(a: *std.math.big.int.Managed, b: std.math.big.int.Managed, c: std.math.big.int.Managed) !void {
try a.mul(&b, &c);
}
test "bigint error" {
var a = try std.math.big.int.Managed.init(std.testing.allocator);
defer a.deinit();
var b = try std.math.big.int.Managed.init(std.testing.allocator);
defer b.deinit();
try a.set(536870912000000000000000000000000000000);
try b.set(10);
try mul(&a, a, b);
try b.set(5368709120000000000000000000000000000000);
try std.testing.expect(a.order(b) == .eq);
}
Succeeds on ReleaseFast/ReleaseSmall, fails on Debug/ReleaseSafe
Issues:
std.math.big.int.Managed.mul
assumes that if the limbs array of rma
aliases a
then rma
is the same pointer as a
, but that is never asserted
rma.ensureMulCapacity(a.toConst(), b.toConst())
reallocates rma.limbs
. This invalidates the pointer to a.limbs
if a
is a copy of rma
instead of the same pointer./// std.math.big.int.Managed.mul
pub fn mul(rma: *Managed, a: *const Managed, b: *const Managed) !void {
var alias_count: usize = 0;
if (rma.limbs.ptr == a.limbs.ptr) {
std.debug.assert(rma == a);
alias_count += 1;
}
if (rma.limbs.ptr == b.limbs.ptr) {
std.debug.assert(rma == b);
alias_count += 1;
}
std.math.big.Rational.mul
accepts a
and b
by value, rather than by reference. This means that a
becomes a copy of r
, and mutating r
does not mutate a
pub fn mul(r: *Rational, a: *const Rational, b: *const Rational) !void {
a
becomes 4184734490257787175890526282138444277401570296309356341930
, which in hex is 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
), but I would expect it to crash when trying to access memory that was freed
// in std.math.big.int.Managed.ensureCapacity
pub fn ensureCapacity(self: *Managed, capacity: usize) !void {
if (capacity <= self.limbs.len) {
return;
}
const prev_limbs = self.limbs;
std.log.err("{x}", .{prev_limbs[0]});
self.limbs = try self.allocator.realloc(self.limbs, capacity);
std.log.err("{x}", .{prev_limbs[0]});
// ->
[default] (err): 4800000000000000
[default] (err): aaaaaaaaaaaaaaaa
test "dereferencing dead pointer" {
const alloc = std.testing.allocator;
const ptr1 = try alloc.alloc(usize, 4);
ptr1[0] = 0x25;
std.log.err("ptr1: {x}", .{ptr1[0]});
const ptr2 = try alloc.realloc(ptr1, 5);
std.log.err("ptr1: {x}", .{ptr1[0]});
alloc.free(ptr2);
}
// ->
Segmentation fault at address 0x70a7fc89e000
a.zig:35:36: 0x103fe8a in test.dereferencing dead pointer (test)
std.log.err("ptr1: {x}", .{ptr1[0]});
^
Zig Version
0.14.0-dev.994+9f46abf59
Steps to Reproduce and Observed Behavior
Expected Behavior
ReleaseFast
the answer ismath.big.rational.Rational{ .p = 109418989131512359209, .q = 562949953421312000000000000000000000000000000000000000000000000000000000000 }
The results should be the same in both
Debug
/ReleaseSafe
andReleaseFast
/ReleaseSmall
- if there is undefined behavior involved in the answers being different thenReleaseSafe
should catch it.