From 796705928240d0def07900b8b3d6235ea852e6df Mon Sep 17 00:00:00 2001 From: Nils Homer Date: Sun, 29 Mar 2026 22:56:16 -0700 Subject: [PATCH] fix: map unmapped reads (tid=-1) to correct index in slow_idxstats When iterating CRAM records, unmapped reads have tid == -1. Casting this to usize wraps to usize::MAX, causing an index-out-of-bounds panic. Map tid == -1 to the designated unmapped slot at index nref. --- src/bam/mod.rs | 25 +++++++++++++++++++++---- src/tbx/mod.rs | 4 ++-- test/test_cram_unmapped.cram | Bin 0 -> 1695 bytes test/test_cram_unmapped.cram.crai | Bin 0 -> 75 bytes 4 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 test/test_cram_unmapped.cram create mode 100644 test/test_cram_unmapped.cram.crai diff --git a/src/bam/mod.rs b/src/bam/mod.rs index 41fe6f134..d13852f90 100644 --- a/src/bam/mod.rs +++ b/src/bam/mod.rs @@ -846,8 +846,11 @@ impl IndexedReader { return Err(Error::InvalidTid { tid }); } + // Map unmapped reads (tid == -1) to the last slot (nref) to avoid usize wrapping. + let count_idx = if tid == -1 { nref } else { tid as usize }; + if tid != last_tid { - if (last_tid >= -1) && (counts[tid as usize][0] + counts[tid as usize][1]) > 0 { + if (last_tid >= -1) && (counts[count_idx][0] + counts[count_idx][1]) > 0 { return Err(Error::BamUnsorted); } last_tid = tid; @@ -858,7 +861,7 @@ impl IndexedReader { } else { 0 }; - counts[(*b).core.tid as usize][idx] += 1; + counts[count_idx][idx] += 1; } if ret == -1 { @@ -1029,8 +1032,8 @@ impl Read for IndexedReader { impl Drop for IndexedReader { fn drop(&mut self) { unsafe { - if self.itr.is_some() { - htslib::hts_itr_destroy(self.itr.unwrap()); + if let Some(itr) = self.itr { + htslib::hts_itr_destroy(itr); } htslib::hts_close(self.htsfile); } @@ -3166,6 +3169,20 @@ CCCCCCCCCCCCCCCCCCC"[..], assert_eq!(expected, actual); } + #[test] + fn test_slow_idxstats_cram_unmapped() { + let mut reader = IndexedReader::from_path("test/test_cram_unmapped.cram").unwrap(); + reader.set_reference("test/test_cram.fa").unwrap(); + let expected = vec![ + (0, 120, 2, 0), + (1, 120, 2, 0), + (2, 120, 2, 0), + (-1, 0, 0, 2), + ]; + let actual = reader.index_stats().unwrap(); + assert_eq!(expected, actual); + } + // #[test] // fn test_number_mapped_and_unmapped_cram() { // let mut reader = IndexedReader::from_path("test/test_cram.cram").unwrap(); diff --git a/src/tbx/mod.rs b/src/tbx/mod.rs index 5994c49e3..18ea00bba 100644 --- a/src/tbx/mod.rs +++ b/src/tbx/mod.rs @@ -316,8 +316,8 @@ impl Read for Reader { impl Drop for Reader { fn drop(&mut self) { unsafe { - if self.itr.is_some() { - htslib::hts_itr_destroy(self.itr.unwrap()); + if let Some(itr) = self.itr { + htslib::hts_itr_destroy(itr); } htslib::tbx_destroy(self.tbx); htslib::hts_close(self.hts_file); diff --git a/test/test_cram_unmapped.cram b/test/test_cram_unmapped.cram new file mode 100644 index 0000000000000000000000000000000000000000..acf26eaf4d3b3378bc6bd5409a7287cf07ae9e1d GIT binary patch literal 1695 zcmd6ne@s(X6vxkf@4iwf^Z}M1g`|MMEWhP32T{haYWdkx+RklJhENDU6vh;`Ny7GE zG7S!0tjt8>)>v^20&3V6Hs-`^V@`vxIABDah=GLYSQLk0Ve4*NFvMZp{@eZIotJaZ zz2~0K`QEG0CaY*vRkWun%KFJO?9MOGtF9=|ulyFl(x51)dq&>N7ZOjMZ+({EL7{!d z(m8Za{mpt;!9?RgZR1x-?$=+t`?Icf>&diWobhtl_g}n!;B?onV_zqnq>uLT_{y1U ziQ^ylP7N1w+aiZ_pQIfz|9*lS`SSG5J8*664_Rks(Z8bSMP-1t`H}{l#2! zOT+yd;mB-!`{luf#oCND_0t`PpR4)xmgKu!Nuu)RNK?y0?=_AK_4?u8`W6Ov>`I-Q z@9}$Z3kIZzCue-evhEgVQ2`SYt<-OgY=sb=v2P@GmXA)yQe4-ViignBN~A=i&zN)5`^5esk9JT$Y2quNR)?K zM`p=hYBKAk*CFJox5g8XQf;^CYK8)rloTvLC={V!RgT@SwfaRlWQnsj-R{yUh)ayX zvHRU5pdc{Nc4VQ0$eDHX@bt3f)s7z8N-xZH_4Z2PO6v2k9;{#Y0x+byD2K&+fJI`l zp=5V-WHi_e2Or#~#SC^9$xU)f8q8QM4=stEZ#EhLvoy_{b=4(0GhjEOcG);(5d!w0 zC0_WCDUX?E}(tI*_+ zdi+bgiUr26XG;oTnXm7Hd44~ z?OGz~7(4VB0^RmrR~8jUKlVQT`$8Nw}v_XxBf$VsS+txd9l*N&$kg|Imu| zFj&ThOoZTMMXFxpKp~@}kskTsMiGMpRwg*}(i?Lusnb^P#FlKOwPpm&`LS$b1On@$ Kfne5vGXDlnH?f=m literal 0 HcmV?d00001 diff --git a/test/test_cram_unmapped.cram.crai b/test/test_cram_unmapped.cram.crai new file mode 100644 index 0000000000000000000000000000000000000000..f0147797d0dfbc24ad46f358a91b69af91757620 GIT binary patch literal 75 zcmb2|=3oE=;gIf)yax<;4yXN^&NNZl`rQBi&}?xhg%z%W-L?F$T30!%sXqHUYs-9( dO)`qHk8?H|%RM}Pzm|!IVaeeI3j%;<000219ZCQI literal 0 HcmV?d00001