Skip to content

Commit d437607

Browse files
committed
guard against empty maps
- guard against accessing last element of emty vector - use `checkbounds(A..)` to create proper error - add tests: - check for BoundsError thrown - zero mask (full collapse of dimension) - matrices of size (0,n) and (m,0)
1 parent f4fecba commit d437607

2 files changed

Lines changed: 98 additions & 5 deletions

File tree

lib/cusparse/src/array.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -524,14 +524,13 @@ end
524524
# slice matrix by masking rows and columns
525525

526526
function Base.getindex(A::CuSparseMatrixCSR{Tv, Ti}, Imask::CuVector{Bool}, Jmask::CuVector{Bool}) where {Tv, Ti}
527-
m, n = size(A)
528-
length(Imask) == m || throw(DimensionMismatch("boolean row mask length $(length(Imask)) must match row count $m"))
529-
length(Jmask) == n || throw(DimensionMismatch("boolean column mask length $(length(Jmask)) must match column count $n"))
527+
@boundscheck checkbounds(A, Imask, Jmask)
530528

529+
m, n = size(A)
531530
rowmap = cumsum(Ti.(Imask))
532531
colmap = cumsum(Ti.(Jmask))
533-
new_m = Int(CUDACore.@allowscalar rowmap[end])
534-
new_n = Int(CUDACore.@allowscalar colmap[end])
532+
new_m = m > 0 ? Int(CUDACore.@allowscalar rowmap[end]) : 0
533+
new_n = n > 0 ? Int(CUDACore.@allowscalar colmap[end]) : 0
535534

536535
# pass 1: count kept entries per new row
537536
counts = CUDACore.zeros(Ti, new_m)
@@ -603,6 +602,7 @@ end
603602
# CSC: reinterpret as transposed CSR, index with swapped masks, reinterpret back.
604603
# A CSC (colPtr, rowVal, nzVal, (m,n)) is the same layout as CSR (rowPtr, colVal, nzVal, (n,m)).
605604
function Base.getindex(A::CuSparseMatrixCSC{Tv, Ti}, Imask::CuVector{Bool}, Jmask::CuVector{Bool}) where {Tv, Ti}
605+
@boundscheck checkbounds(A, Imask, Jmask)
606606
A_as_csr = CuSparseMatrixCSR{Tv, Ti}(A.colPtr, A.rowVal, A.nzVal, reverse(size(A)))
607607
result_csr = A_as_csr[Jmask, Imask]
608608
return CuSparseMatrixCSC{Tv, Ti}(result_csr.rowPtr, result_csr.colVal, result_csr.nzVal, reverse(size(result_csr)))

lib/cusparse/test/interfaces.jl

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,5 +364,98 @@ nB = 2
364364
CUDA.@allowscalar for i in eachindex(S_cpu, S_csr)
365365
@test S_cpu[i] S_csr[i]
366366
end
367+
368+
# wrong mask size: throws BoundsError for both too-long and too-short, matching the behaviour of dense Array.
369+
@test_throws BoundsError A_csc[CuVector(trues(m + 1)), colmask_d]
370+
@test_throws BoundsError A_csc[rowmask_d, CuVector(trues(n + 1))]
371+
@test_throws BoundsError A_csc[CuVector(trues(m - 1)), colmask_d]
372+
@test_throws BoundsError A_csc[rowmask_d, CuVector(trues(n - 1))]
373+
@test_throws BoundsError A_csr[CuVector(trues(m + 1)), colmask_d]
374+
@test_throws BoundsError A_csr[rowmask_d, CuVector(trues(n + 1))]
375+
@test_throws BoundsError A_csr[CuVector(trues(m - 1)), colmask_d]
376+
@test_throws BoundsError A_csr[rowmask_d, CuVector(trues(n - 1))]
377+
378+
# empty mask (all zeros): cumsum gives all-zero rowmap/colmap, new_m or new_n = 0,
379+
# both kernels are guarded by `new_m > 0 && new_n > 0`, so nothing executes.
380+
# new_rowPtr collapses to [1] (or all-ones), nnz = 0. Same as CPU SparseArrays.
381+
S_empty_rows_csc = A_csc[CuVector(falses(m)), CuVector(trues(n))]
382+
@test S_empty_rows_csc isa CuSparseMatrixCSC
383+
@test size(S_empty_rows_csc) == (0, n)
384+
@test nnz(S_empty_rows_csc) == 0
385+
386+
S_empty_cols_csc = A_csc[CuVector(trues(m)), CuVector(falses(n))]
387+
@test S_empty_cols_csc isa CuSparseMatrixCSC
388+
@test size(S_empty_cols_csc) == (m, 0)
389+
@test nnz(S_empty_cols_csc) == 0
390+
391+
S_empty_rows_csr = A_csr[CuVector(falses(m)), CuVector(trues(n))]
392+
@test S_empty_rows_csr isa CuSparseMatrixCSR
393+
@test size(S_empty_rows_csr) == (0, n)
394+
@test nnz(S_empty_rows_csr) == 0
395+
396+
S_empty_cols_csr = A_csr[CuVector(trues(m)), CuVector(falses(n))]
397+
@test S_empty_cols_csr isa CuSparseMatrixCSR
398+
@test size(S_empty_cols_csr) == (m, 0)
399+
@test nnz(S_empty_cols_csr) == 0
400+
401+
S_empty_both_csc = A_csc[CuVector(falses(m)), CuVector(falses(n))]
402+
@test S_empty_both_csc isa CuSparseMatrixCSC
403+
@test size(S_empty_both_csc) == (0, 0)
404+
@test nnz(S_empty_both_csc) == 0
405+
406+
S_empty_both_csr = A_csr[CuVector(falses(m)), CuVector(falses(n))]
407+
@test S_empty_both_csr isa CuSparseMatrixCSR
408+
@test size(S_empty_both_csr) == (0, 0)
409+
@test nnz(S_empty_both_csr) == 0
410+
411+
# all-ones mask: rowmap = 1:m, colmap = 1:n, both kernels run unfiltered.
412+
# Result should equal the full matrix. Same as CPU SparseArrays.
413+
S_all_csc = A_csc[CuVector(trues(m)), CuVector(trues(n))]
414+
@test S_all_csc isa CuSparseMatrixCSC
415+
@test collect(S_all_csc) Matrix(A)
416+
417+
S_all_csr = A_csr[CuVector(trues(m)), CuVector(trues(n))]
418+
@test S_all_csr isa CuSparseMatrixCSR
419+
@allowscalar for i in eachindex(A, S_all_csr)
420+
@test A[i] S_all_csr[i]
421+
end
422+
423+
# zero-dimension matrix: accessing rowmap[end] / colmap[end] on an empty CuVector
424+
# would crash without the `m > 0` / `n > 0` guard in the implementation.
425+
A_zero_rows_csr = CuSparseMatrixCSR(spzeros(elty, 0, n))
426+
S_zr = A_zero_rows_csr[CuVector{Bool}([]), CuVector(trues(n))]
427+
@test S_zr isa CuSparseMatrixCSR
428+
@test size(S_zr) == (0, n)
429+
@test nnz(S_zr) == 0
430+
431+
A_zero_cols_csr = CuSparseMatrixCSR(spzeros(elty, m, 0))
432+
S_zc = A_zero_cols_csr[CuVector(trues(m)), CuVector{Bool}([])]
433+
@test S_zc isa CuSparseMatrixCSR
434+
@test size(S_zc) == (m, 0)
435+
@test nnz(S_zc) == 0
436+
437+
A_zero_both_csr = CuSparseMatrixCSR(spzeros(elty, 0, 0))
438+
S_zb = A_zero_both_csr[CuVector{Bool}([]), CuVector{Bool}([])]
439+
@test S_zb isa CuSparseMatrixCSR
440+
@test size(S_zb) == (0, 0)
441+
@test nnz(S_zb) == 0
442+
443+
A_zero_rows_csc = CuSparseMatrixCSC(spzeros(elty, 0, n))
444+
S_zr_csc = A_zero_rows_csc[CuVector{Bool}([]), CuVector(trues(n))]
445+
@test S_zr_csc isa CuSparseMatrixCSC
446+
@test size(S_zr_csc) == (0, n)
447+
@test nnz(S_zr_csc) == 0
448+
449+
A_zero_cols_csc = CuSparseMatrixCSC(spzeros(elty, m, 0))
450+
S_zc_csc = A_zero_cols_csc[CuVector(trues(m)), CuVector{Bool}([])]
451+
@test S_zc_csc isa CuSparseMatrixCSC
452+
@test size(S_zc_csc) == (m, 0)
453+
@test nnz(S_zc_csc) == 0
454+
455+
A_zero_both_csc = CuSparseMatrixCSC(spzeros(elty, 0, 0))
456+
S_zb_csc = A_zero_both_csc[CuVector{Bool}([]), CuVector{Bool}([])]
457+
@test S_zb_csc isa CuSparseMatrixCSC
458+
@test size(S_zb_csc) == (0, 0)
459+
@test nnz(S_zb_csc) == 0
367460
end
368461
end

0 commit comments

Comments
 (0)