From 68d9fdb781fae55023ae0a8eb71a60f36b23ead9 Mon Sep 17 00:00:00 2001 From: Tom White Date: Wed, 9 Jan 2019 16:17:33 +0000 Subject: [PATCH] Fix to ensure that SeekableStream#available() never returns a negative value. (#1255) * For files that are larger than 2GB (e.g. an uncompressed FASTA) it was previously possible that `length - position` overflows an int, resulting in a negative value being returned. --- .../seekablestream/SeekableStream.java | 9 ++- .../seekablestream/SeekableStreamTest.java | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java b/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java index 9d2a82093c..3ec3f81331 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java @@ -107,8 +107,13 @@ public int available() throws IOException { return 0; } final long remaining = length() - position(); - // the remaining might be negative if the length is not available (0) - return (remaining < 0) ? 0 : (int) remaining; + if (remaining < 0) { // remaining might be negative if the length is not available (0) + return 0; + } else if (remaining > Integer.MAX_VALUE) { // remaining might be bigger than Integer.MAX_VALUE for very large files + return Integer.MAX_VALUE; + } else { + return (int) remaining; + } } /** diff --git a/src/test/java/htsjdk/samtools/seekablestream/SeekableStreamTest.java b/src/test/java/htsjdk/samtools/seekablestream/SeekableStreamTest.java index d19f6b074f..07d189c62e 100644 --- a/src/test/java/htsjdk/samtools/seekablestream/SeekableStreamTest.java +++ b/src/test/java/htsjdk/samtools/seekablestream/SeekableStreamTest.java @@ -28,6 +28,7 @@ import org.testng.Assert; import org.testng.annotations.Test; +import java.io.IOException; import java.util.Random; /** @@ -81,6 +82,15 @@ public void testAvailable() throws Exception { Assert.assertEquals(stream.available(), 0); } + @Test + public void testAvailableDoesNotOverflowInteger() throws Exception { + // initiate large stream (longer than Integer.MAX_VALUE) + final long length = Integer.MAX_VALUE * 2L; + final SeekableStream stream = getLargeSeekableStream(length); + // check that available returns Integer.MAX_VALUE (and not a negative number due to overflow) + Assert.assertEquals(stream.available(), Integer.MAX_VALUE); + } + private static SeekableStream getRandomSeekableStream(final int size) { // generate random array final byte[] array = new byte[size]; @@ -89,4 +99,50 @@ private static SeekableStream getRandomSeekableStream(final int size) { return new SeekableMemoryStream(array, "test"); } + private static SeekableStream getLargeSeekableStream(final long size) { + // a long file of zeros + return new SeekableStream() { + long pos = 0; + @Override + public long length() { + return size; + } + + @Override + public long position() throws IOException { + return pos; + } + + @Override + public void seek(long position) throws IOException { + pos = position; + } + + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + return length; + } + + @Override + public void close() throws IOException { + + } + + @Override + public boolean eof() throws IOException { + return pos == length(); + } + + @Override + public String getSource() { + return null; + } + + @Override + public int read() throws IOException { + return 0; + } + }; + } + } \ No newline at end of file