zig

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

commit 91eab35e08f72220fd2461bdf6da2f74aaff5c64 (tree)
parent ac562577381f6e94196337e0ccd8895bd1bcddb2
Author: Frank Denis <124872+jedisct1@users.noreply.github.com>
Date:   Thu, 18 Sep 2025 04:54:15 +0200

std.sort.pdq: fix out-of-bounds access in partialInsertionSort (#25253)

* std.sort.pdq: fix out-of-bounds access in partialInsertionSort

When sorting a sub-range that doesn't start at index 0, the
partialInsertionSort function could access indices below the range
start. The loop condition `while (j >= 1)` didn't respect the
arbitrary range boundaries [a, b).

This changes the condition to `while (j > a)` to ensure indices
never go below the range start, fixing the issue where pdqContext
would access out-of-bounds indices.

Fixes #25250

Diffstat:
Mlib/std/sort/pdq.zig | 49++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)

diff --git a/lib/std/sort/pdq.zig b/lib/std/sort/pdq.zig @@ -227,7 +227,7 @@ fn partialInsertionSort(a: usize, b: usize, context: anytype) bool { // shift the smaller element to the left. if (i - a >= 2) { var j = i - 1; - while (j >= 1) : (j -= 1) { + while (j > a) : (j -= 1) { if (!context.lessThan(j, j - 1)) break; context.swap(j, j - 1); } @@ -328,3 +328,50 @@ fn reverseRange(a: usize, b: usize, context: anytype) void { j -= 1; } } + +test "pdqContext respects arbitrary range boundaries" { + // Regression test for issue #25250 + // pdqsort should never access indices outside the specified [a, b) range + var data: [2000]i32 = @splat(0); + + // Fill with data that triggers the partialInsertionSort path + for (0..data.len) |i| { + data[i] = @intCast(@mod(@as(i32, @intCast(i)) * 7, 100)); + } + + const TestContext = struct { + items: []i32, + range_start: usize, + range_end: usize, + + pub fn lessThan(ctx: @This(), a: usize, b: usize) bool { + // Assert indices are within the expected range + testing.expect(a >= ctx.range_start and a < ctx.range_end) catch @panic("index a out of range"); + testing.expect(b >= ctx.range_start and b < ctx.range_end) catch @panic("index b out of range"); + return ctx.items[a] < ctx.items[b]; + } + + pub fn swap(ctx: @This(), a: usize, b: usize) void { + // Assert indices are within the expected range + testing.expect(a >= ctx.range_start and a < ctx.range_end) catch @panic("index a out of range"); + testing.expect(b >= ctx.range_start and b < ctx.range_end) catch @panic("index b out of range"); + mem.swap(i32, &ctx.items[a], &ctx.items[b]); + } + }; + + // Test sorting a sub-range that doesn't start at 0 + const start = 1118; + const end = 1764; + const ctx = TestContext{ + .items = &data, + .range_start = start, + .range_end = end, + }; + + pdqContext(start, end, ctx); + + // Verify the range is sorted + for ((start + 1)..end) |i| { + try testing.expect(data[i - 1] <= data[i]); + } +}