-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
integer overflow in std.sort.sort due to incorrect lessThan
implementation
#8289
Comments
The problem is your predicate, the algorithm seems to work only when the comparison is less-than (or greater-than). |
Would be nice to have some debug safety checks, perhaps calling lessThan(a, b) and lessThan(b, a) and asserting that the return values are not equal. |
lessThan
implementation
When trying to implement the suggested solution I face the problem of obtaining two unique values A and B of any type My current solution would be to handle each type separately which is very cumbersome and seems kind of fragile: /// Applies a safety check to the user-provided lessThan function to make sure that
/// the comparison is either less-than or greater-than and does not include an equality comparison of A and B as that doesn't work on the algorithm.
fn checkLessThan(
comptime T: type,
context: anytype,
comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) void {
// We need two values A and B distinct from each other of type T. If A and B are equal, it means the comparison function checks for equality.
const a = comptime getUnique(T, 0) orelse return; // TODO: do we @compileError if we can't obtain two unique values from this type?
const b = comptime getUnique(T, 1) orelse return;
assert(lessThan(context, a, b) != lessThan(context, b, a)); // Make sure lessThan only checks for less-than or greater-than and doesn't include an equality check!
}
fn getUnique(comptime T: type, comptime index: u1) ?T {
switch (@typeInfo(T)) {
.ComptimeInt, .Int, .ComptimeFloat, .Float => {
return @as(T, index);
},
.Enum, .EnumLiteral => {
return std.meta.intToEnum(T, index) orelse null;
},
.Void => {
return null;
},
.Bool => {
return index != 0;
},
.Optional, .Null => {
return switch (index) {
0 => @as(T, null),
1 => @as(T, getUnique(T, index)),
};
},
.Struct => |struct_info| {
if (@sizeOf(T) == 0) return null;
var value: T = undefined;
inline for (struct_info.fields) |field| {
if (!field.is_comptime) {
@field(value, field.name) = getUnique(field.type, index) orelse return null;
}
}
return value;
},
.Pointer => return @intToPtr(T, index + 1),
.Array => {
var arr: T = undefined;
arr[0] = getUnique(T, index);
return arr;
},
.Vector => {
// TODO: test this
var arr: T = undefined;
arr[0] = getUnique(T, index);
return arr;
},
.Union => {
return null; // TODO
},
.ErrorUnion => return @as(u1!error{}, index),
.ErrorSet => {
return switch (index) {
0 => error{a},
1 => error{b},
};
},
.Fn => {
const a = struct {
fn a() void {}
}.a;
const b = struct {
fn b() void {}
}.b;
return switch (index) {
0 => a,
1 => b,
};
},
.Type => {
return switch (index) {
0 => u0,
1 => u1,
};
},
.NoReturn => return null,
.Undefined => return null,
.Opaque => @compileError("TODO"),
.Frame => @compileError("TODO"),
.AnyFrame => @compileError("TODO"),
}
} Is this approach a way at all? Alternatively, should we only support this safety check for types that can be bit-casted, or just types where So, the complexity of this safety check depends on whether we consider it possible for the user to, for example, check two function prototypes for equality in As another alternative, maybe we can just copy-paste a note about how the |
The check could just be done at each call to lessThan (pseudo code): sort(lessThan):
actualSort(lamda a, b:
if debug: assert(lessThan(a, b) != lessThan(b, a)
return lessThan(a, b)
) The invariant doesn't need to be amortized to the start since it's only on debug and perf doesnt matter there. The invariant could also be broken on |
this doesn't work when array contains duplicate items const std = @import("std");
test {
var items: [4]u8 = .{ 1, 2, 2, 3 };
std.sort.block(u8, &items, {}, std.sort.asc(u8));
} |
It only needs to check that if |
I encountered an integer overflow in
std.sort.sort
on master (96ae451)Example code (
sort_test.zig
)zig test
results:Locals, from gdb:
I encountered this on a list with unique (I think) elements -- but this is more concise example.
The text was updated successfully, but these errors were encountered: