| ... | @@ -227,7 +227,7 @@ fn partialInsertionSort(a: usize, b: usize, context: anytype) bool { | ... | @@ -227,7 +227,7 @@ fn partialInsertionSort(a: usize, b: usize, context: anytype) bool { |
| 227 | // shift the smaller element to the left. | 227 | // shift the smaller element to the left. |
| 228 | if (i - a >= 2) { | 228 | if (i - a >= 2) { |
| 229 | var j = i - 1; | 229 | var j = i - 1; |
| 230 | while (j >= 1) : (j -= 1) { | 230 | while (j > a) : (j -= 1) { |
| 231 | if (!context.lessThan(j, j - 1)) break; | 231 | if (!context.lessThan(j, j - 1)) break; |
| 232 | context.swap(j, j - 1); | 232 | context.swap(j, j - 1); |
| 233 | } | 233 | } |
| ... | @@ -328,3 +328,50 @@ fn reverseRange(a: usize, b: usize, context: anytype) void { | ... | @@ -328,3 +328,50 @@ fn reverseRange(a: usize, b: usize, context: anytype) void { |
| 328 | j -= 1; | 328 | j -= 1; |
| 329 | } | 329 | } |
| 330 | } | 330 | } |
| | 331 | |
| | 332 | test "pdqContext respects arbitrary range boundaries" { |
| | 333 | // Regression test for issue #25250 |
| | 334 | // pdqsort should never access indices outside the specified [a, b) range |
| | 335 | var data: [2000]i32 = @splat(0); |
| | 336 | |
| | 337 | // Fill with data that triggers the partialInsertionSort path |
| | 338 | for (0..data.len) |i| { |
| | 339 | data[i] = @intCast(@mod(@as(i32, @intCast(i)) * 7, 100)); |
| | 340 | } |
| | 341 | |
| | 342 | const TestContext = struct { |
| | 343 | items: []i32, |
| | 344 | range_start: usize, |
| | 345 | range_end: usize, |
| | 346 | |
| | 347 | pub fn lessThan(ctx: @This(), a: usize, b: usize) bool { |
| | 348 | // Assert indices are within the expected range |
| | 349 | testing.expect(a >= ctx.range_start and a < ctx.range_end) catch @panic("index a out of range"); |
| | 350 | testing.expect(b >= ctx.range_start and b < ctx.range_end) catch @panic("index b out of range"); |
| | 351 | return ctx.items[a] < ctx.items[b]; |
| | 352 | } |
| | 353 | |
| | 354 | pub fn swap(ctx: @This(), a: usize, b: usize) void { |
| | 355 | // Assert indices are within the expected range |
| | 356 | testing.expect(a >= ctx.range_start and a < ctx.range_end) catch @panic("index a out of range"); |
| | 357 | testing.expect(b >= ctx.range_start and b < ctx.range_end) catch @panic("index b out of range"); |
| | 358 | mem.swap(i32, &ctx.items[a], &ctx.items[b]); |
| | 359 | } |
| | 360 | }; |
| | 361 | |
| | 362 | // Test sorting a sub-range that doesn't start at 0 |
| | 363 | const start = 1118; |
| | 364 | const end = 1764; |
| | 365 | const ctx = TestContext{ |
| | 366 | .items = &data, |
| | 367 | .range_start = start, |
| | 368 | .range_end = end, |
| | 369 | }; |
| | 370 | |
| | 371 | pdqContext(start, end, ctx); |
| | 372 | |
| | 373 | // Verify the range is sorted |
| | 374 | for ((start + 1)..end) |i| { |
| | 375 | try testing.expect(data[i - 1] <= data[i]); |
| | 376 | } |
| | 377 | } |