modify Java decoder in a way it could be transpiled to exception unfriendly languages PiperOrigin-RevId: 769488037
diff --git a/java/org/brotli/dec/BitReader.java b/java/org/brotli/dec/BitReader.java index f12099d..ec17f0c 100644 --- a/java/org/brotli/dec/BitReader.java +++ b/java/org/brotli/dec/BitReader.java
@@ -6,6 +6,14 @@ package org.brotli.dec; +import static org.brotli.dec.BrotliError.BROTLI_ERROR; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_PADDING_BITS; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_READ_AFTER_END; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_TRUNCATED_INPUT; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_UNUSED_BYTES_AFTER_END; +import static org.brotli.dec.BrotliError.BROTLI_OK; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_UNALIGNED_COPY_BYTES; + /** * Bit reading helpers. */ @@ -16,8 +24,8 @@ private static final int LOG_BITNESS = Utils.getLogBintness(); // Not only Java compiler prunes "if (const false)" code, but JVM as well. - // Code under "if (DEBUG != 0)" have zero performance impact (outside unit tests). - private static final int DEBUG = Utils.isDebugMode(); + // Code under "if (BIT_READER_DEBUG != 0)" have zero performance impact (outside unit tests). + private static final int BIT_READER_DEBUG = Utils.isDebugMode(); static final int BITNESS = 1 << LOG_BITNESS; @@ -36,30 +44,25 @@ private static final int HALF_SIZE = BYTENESS / 2; private static final int HALVES_CAPACITY = CAPACITY / HALF_SIZE; private static final int HALF_BUFFER_SIZE = BUFFER_SIZE / HALF_SIZE; - private static final int HALF_WATERLINE = WATERLINE / HALF_SIZE; private static final int LOG_HALF_SIZE = LOG_BITNESS - 4; + static final int HALF_WATERLINE = WATERLINE / HALF_SIZE; + /** * Fills up the input buffer. * - * <p> No-op if there are at least 36 bytes present after current position. + * <p> Should not be called if there are at least 36 bytes present after current position. * * <p> After encountering the end of the input stream, 64 additional zero bytes are copied to the * buffer. */ - static void readMoreInput(State s) { - if (s.halfOffset > HALF_WATERLINE) { - doReadMoreInput(s); - } - } - - static void doReadMoreInput(State s) { + static int readMoreInput(State s) { if (s.endOfStreamReached != 0) { if (halfAvailable(s) >= -2) { - return; + return BROTLI_OK; } - throw new BrotliRuntimeException("No more input"); + return Utils.makeError(s, BROTLI_ERROR_TRUNCATED_INPUT); } final int readOffset = s.halfOffset << LOG_HALF_SIZE; int bytesInBuffer = CAPACITY - readOffset; @@ -70,6 +73,9 @@ final int spaceLeft = CAPACITY - bytesInBuffer; final int len = Utils.readInput(s, s.byteBuffer, bytesInBuffer, spaceLeft); // EOF is -1 in Java, but 0 in C#. + if (len < BROTLI_ERROR) { + return len; + } if (len <= 0) { s.endOfStreamReached = 1; s.tailBytes = bytesInBuffer; @@ -79,19 +85,21 @@ bytesInBuffer += len; } bytesToNibbles(s, bytesInBuffer); + return BROTLI_OK; } - static void checkHealth(State s, int endOfStream) { + static int checkHealth(State s, int endOfStream) { if (s.endOfStreamReached == 0) { - return; + return BROTLI_OK; } final int byteOffset = (s.halfOffset << LOG_HALF_SIZE) + ((s.bitOffset + 7) >> 3) - BYTENESS; if (byteOffset > s.tailBytes) { - throw new BrotliRuntimeException("Read after end"); + return Utils.makeError(s, BROTLI_ERROR_READ_AFTER_END); } if ((endOfStream != 0) && (byteOffset != s.tailBytes)) { - throw new BrotliRuntimeException("Unused bytes after end"); + return Utils.makeError(s, BROTLI_ERROR_UNUSED_BYTES_AFTER_END); } + return BROTLI_OK; } static void assertAccumulatorHealthy(State s) { @@ -101,7 +109,7 @@ } static void fillBitWindow(State s) { - if (DEBUG != 0) { + if (BIT_READER_DEBUG != 0) { assertAccumulatorHealthy(s); } if (s.bitOffset >= HALF_BITNESS) { @@ -118,7 +126,7 @@ } static void doFillBitWindow(State s) { - if (DEBUG != 0) { + if (BIT_READER_DEBUG != 0) { assertAccumulatorHealthy(s); } if (BITNESS == 64) { @@ -165,7 +173,7 @@ return low | (readFewBits(s, n - 16) << 16); } - static void initBitReader(State s) { + static int initBitReader(State s) { s.byteBuffer = new byte[BUFFER_SIZE]; if (BITNESS == 64) { s.accumulator64 = 0; @@ -177,30 +185,41 @@ s.bitOffset = BITNESS; s.halfOffset = HALVES_CAPACITY; s.endOfStreamReached = 0; - prepare(s); + return prepare(s); } - private static void prepare(State s) { - readMoreInput(s); - checkHealth(s, 0); - doFillBitWindow(s); - doFillBitWindow(s); - } - - static void reload(State s) { - if (s.bitOffset == BITNESS) { - prepare(s); + private static int prepare(State s) { + if (s.halfOffset > BitReader.HALF_WATERLINE) { + int result = readMoreInput(s); + if (result != BROTLI_OK) { + return result; + } } + int health = checkHealth(s, 0); + if (health != BROTLI_OK) { + return health; + } + doFillBitWindow(s); + doFillBitWindow(s); + return BROTLI_OK; } - static void jumpToByteBoundary(State s) { + static int reload(State s) { + if (s.bitOffset == BITNESS) { + return prepare(s); + } + return BROTLI_OK; + } + + static int jumpToByteBoundary(State s) { final int padding = (BITNESS - s.bitOffset) & 7; if (padding != 0) { final int paddingBits = readFewBits(s, padding); if (paddingBits != 0) { - throw new BrotliRuntimeException("Corrupted padding bits"); + return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_PADDING_BITS); } } + return BROTLI_OK; } static int halfAvailable(State s) { @@ -211,11 +230,11 @@ return limit - s.halfOffset; } - static void copyRawBytes(State s, byte[] data, int offset, int length) { + static int copyRawBytes(State s, byte[] data, int offset, int length) { int pos = offset; int len = length; if ((s.bitOffset & 7) != 0) { - throw new BrotliRuntimeException("Unaligned copyBytes"); + return Utils.makeError(s, BROTLI_PANIC_UNALIGNED_COPY_BYTES); } // Drain accumulator. @@ -225,7 +244,7 @@ len--; } if (len == 0) { - return; + return BROTLI_OK; } // Get data from shadow buffer with "sizeof(int)" granularity. @@ -239,7 +258,7 @@ s.halfOffset += copyNibbles; } if (len == 0) { - return; + return BROTLI_OK; } // Read tail bytes. @@ -251,19 +270,23 @@ s.bitOffset += 8; len--; } - checkHealth(s, 0); - return; + return checkHealth(s, 0); } // Now it is possible to copy bytes directly. while (len > 0) { final int chunkLen = Utils.readInput(s, data, pos, len); - if (chunkLen == -1) { - throw new BrotliRuntimeException("Unexpected end of input"); + // EOF is -1 in Java, but 0 in C#. + if (len < BROTLI_ERROR) { + return len; + } + if (chunkLen <= 0) { + return Utils.makeError(s, BROTLI_ERROR_TRUNCATED_INPUT); } pos += chunkLen; len -= chunkLen; } + return BROTLI_OK; } /**
diff --git a/java/org/brotli/dec/BrotliError.java b/java/org/brotli/dec/BrotliError.java new file mode 100644 index 0000000..223bbac --- /dev/null +++ b/java/org/brotli/dec/BrotliError.java
@@ -0,0 +1,47 @@ +/* Copyright 2025 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +package org.brotli.dec; + +/** + * Possible errors from decoder. + */ +public class BrotliError { + public static final int BROTLI_OK = 0; + public static final int BROTLI_OK_DONE = BROTLI_OK + 1; + public static final int BROTLI_OK_NEED_MORE_OUTPUT = BROTLI_OK + 2; + + // It is important that actual error codes are LESS than -1! + public static final int BROTLI_ERROR = -1; + public static final int BROTLI_ERROR_CORRUPTED_CODE_LENGTH_TABLE = BROTLI_ERROR - 1; + public static final int BROTLI_ERROR_CORRUPTED_CONTEXT_MAP = BROTLI_ERROR - 2; + public static final int BROTLI_ERROR_CORRUPTED_HUFFMAN_CODE_HISTOGRAM = BROTLI_ERROR - 3; + public static final int BROTLI_ERROR_CORRUPTED_PADDING_BITS = BROTLI_ERROR - 4; + public static final int BROTLI_ERROR_CORRUPTED_RESERVED_BIT = BROTLI_ERROR - 5; + public static final int BROTLI_ERROR_DUPLICATE_SIMPLE_HUFFMAN_SYMBOL = BROTLI_ERROR - 6; + public static final int BROTLI_ERROR_EXUBERANT_NIBBLE = BROTLI_ERROR - 7; + public static final int BROTLI_ERROR_INVALID_BACKWARD_REFERENCE = BROTLI_ERROR - 8; + public static final int BROTLI_ERROR_INVALID_METABLOCK_LENGTH = BROTLI_ERROR - 9; + public static final int BROTLI_ERROR_INVALID_WINDOW_BITS = BROTLI_ERROR - 10; + public static final int BROTLI_ERROR_NEGATIVE_DISTANCE = BROTLI_ERROR - 11; + public static final int BROTLI_ERROR_READ_AFTER_END = BROTLI_ERROR - 12; + public static final int BROTLI_ERROR_READ_FAILED = BROTLI_ERROR - 13; + public static final int BROTLI_ERROR_SYMBOL_OUT_OF_RANGE = BROTLI_ERROR - 14; + public static final int BROTLI_ERROR_TRUNCATED_INPUT = BROTLI_ERROR - 15; + public static final int BROTLI_ERROR_UNUSED_BYTES_AFTER_END = BROTLI_ERROR - 16; + public static final int BROTLI_ERROR_UNUSED_HUFFMAN_SPACE = BROTLI_ERROR - 17; + + public static final int BROTLI_PANIC = -21; + public static final int BROTLI_PANIC_ALREADY_CLOSED = BROTLI_PANIC - 1; + public static final int BROTLI_PANIC_MAX_DISTANCE_TOO_SMALL = BROTLI_PANIC - 2; + public static final int BROTLI_PANIC_STATE_NOT_FRESH = BROTLI_PANIC - 3; + public static final int BROTLI_PANIC_STATE_NOT_INITIALIZED = BROTLI_PANIC - 4; + public static final int BROTLI_PANIC_STATE_NOT_UNINITIALIZED = BROTLI_PANIC - 5; + public static final int BROTLI_PANIC_TOO_MANY_DICTIONARY_CHUNKS = BROTLI_PANIC - 6; + public static final int BROTLI_PANIC_UNEXPECTED_STATE = BROTLI_PANIC - 7; + public static final int BROTLI_PANIC_UNREACHABLE = BROTLI_PANIC - 8; + public static final int BROTLI_PANIC_UNALIGNED_COPY_BYTES = BROTLI_PANIC - 9; +}
diff --git a/java/org/brotli/dec/BrotliInputStream.java b/java/org/brotli/dec/BrotliInputStream.java index 46df98f..24935db 100644 --- a/java/org/brotli/dec/BrotliInputStream.java +++ b/java/org/brotli/dec/BrotliInputStream.java
@@ -111,6 +111,7 @@ @Override public void close() throws IOException { Decode.close(state); + Utils.closeInput(state); } /**
diff --git a/java/org/brotli/dec/Decode.java b/java/org/brotli/dec/Decode.java index f5df240..345e9cd 100644 --- a/java/org/brotli/dec/Decode.java +++ b/java/org/brotli/dec/Decode.java
@@ -6,7 +6,30 @@ package org.brotli.dec; -import java.io.IOException; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_CODE_LENGTH_TABLE; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_CONTEXT_MAP; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_HUFFMAN_CODE_HISTOGRAM; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_RESERVED_BIT; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_DUPLICATE_SIMPLE_HUFFMAN_SYMBOL; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_EXUBERANT_NIBBLE; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_INVALID_BACKWARD_REFERENCE; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_INVALID_METABLOCK_LENGTH; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_INVALID_WINDOW_BITS; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_NEGATIVE_DISTANCE; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_SYMBOL_OUT_OF_RANGE; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_UNUSED_HUFFMAN_SPACE; +import static org.brotli.dec.BrotliError.BROTLI_OK; +import static org.brotli.dec.BrotliError.BROTLI_OK_DONE; +import static org.brotli.dec.BrotliError.BROTLI_OK_NEED_MORE_OUTPUT; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_ALREADY_CLOSED; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_MAX_DISTANCE_TOO_SMALL; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_STATE_NOT_FRESH; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_STATE_NOT_INITIALIZED; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_STATE_NOT_UNINITIALIZED; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_TOO_MANY_DICTIONARY_CHUNKS; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_UNEXPECTED_STATE; +import static org.brotli.dec.BrotliError.BROTLI_PANIC_UNREACHABLE; + import java.nio.ByteBuffer; /** @@ -22,6 +45,7 @@ //---------------------------------------------------------------------------- // RunningState //---------------------------------------------------------------------------- + // NB: negative values are used for errors. private static final int UNINITIALIZED = 0; private static final int INITIALIZED = 1; private static final int BLOCK_START = 2; @@ -156,9 +180,9 @@ // TODO(eustas): add a correctness test for this function when // large-window and dictionary are implemented. - private static int calculateDistanceAlphabetLimit(int maxDistance, int npostfix, int ndirect) { + private static int calculateDistanceAlphabetLimit(State s, int maxDistance, int npostfix, int ndirect) { if (maxDistance < ndirect + (2 << npostfix)) { - throw new IllegalArgumentException("maxDistance is too small"); + return Utils.makeError(s, BROTLI_PANIC_MAX_DISTANCE_TOO_SMALL); } final int offset = ((maxDistance - ndirect) >> npostfix) + 4; final int ndistbits = log2floor(offset) - 1; @@ -237,9 +261,8 @@ return -1; } return n; - } else { - return 8 + n; } + return 8 + n; } return 17; } @@ -251,24 +274,26 @@ * * @param s initialized state, before any read is performed. */ - static void enableEagerOutput(State s) { + static int enableEagerOutput(State s) { if (s.runningState != INITIALIZED) { - throw new IllegalStateException("State MUST be freshly initialized"); + return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_FRESH); } s.isEager = 1; + return BROTLI_OK; } - static void enableLargeWindow(State s) { + static int enableLargeWindow(State s) { if (s.runningState != INITIALIZED) { - throw new IllegalStateException("State MUST be freshly initialized"); + return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_FRESH); } s.isLargeWindow = 1; + return BROTLI_OK; } // TODO(eustas): do we need byte views? - static void attachDictionaryChunk(State s, byte[] data) { + static int attachDictionaryChunk(State s, byte[] data) { if (s.runningState != INITIALIZED) { - throw new IllegalStateException("State MUST be freshly initialized"); + return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_FRESH); } if (s.cdNumChunks == 0) { s.cdChunks = new byte[16][]; @@ -276,45 +301,52 @@ s.cdBlockBits = -1; } if (s.cdNumChunks == 15) { - throw new IllegalStateException("Too many dictionary chunks"); + return Utils.makeError(s, BROTLI_PANIC_TOO_MANY_DICTIONARY_CHUNKS); } s.cdChunks[s.cdNumChunks] = data; s.cdNumChunks++; s.cdTotalSize += data.length; s.cdChunkOffsets[s.cdNumChunks] = s.cdTotalSize; + return BROTLI_OK; } /** * Associate input with decoder state. * * @param s uninitialized state without associated input - * @param input compressed data source */ - static void initState(State s) { + static int initState(State s) { if (s.runningState != UNINITIALIZED) { - throw new IllegalStateException("State MUST be uninitialized"); + return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_UNINITIALIZED); } /* 6 trees + 1 extra "offset" slot to simplify table decoding logic. */ s.blockTrees = new int[7 + 3 * (HUFFMAN_TABLE_SIZE_258 + HUFFMAN_TABLE_SIZE_26)]; s.blockTrees[0] = 7; s.distRbIdx = 3; - final int maxDistanceAlphabetLimit = - calculateDistanceAlphabetLimit(MAX_ALLOWED_DISTANCE, 3, 15 << 3); + int result = calculateDistanceAlphabetLimit(s, MAX_ALLOWED_DISTANCE, 3, 15 << 3); + if (result < BROTLI_OK) { + return result; + } + final int maxDistanceAlphabetLimit = result; s.distExtraBits = new byte[maxDistanceAlphabetLimit]; s.distOffset = new int[maxDistanceAlphabetLimit]; - BitReader.initBitReader(s); + result = BitReader.initBitReader(s); + if (result < BROTLI_OK) { + return result; + } s.runningState = INITIALIZED; + return BROTLI_OK; } - static void close(State s) throws IOException { + static int close(State s) { if (s.runningState == UNINITIALIZED) { - throw new IllegalStateException("State MUST be initialized"); + return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_INITIALIZED); } if (s.runningState == CLOSED) { - return; + return Utils.makeError(s, BROTLI_PANIC_ALREADY_CLOSED); } s.runningState = CLOSED; - Utils.closeInput(s); + return BROTLI_OK; } /** @@ -326,37 +358,36 @@ final int n = BitReader.readFewBits(s, 3); if (n == 0) { return 1; - } else { - return BitReader.readFewBits(s, n) + (1 << n); } + return BitReader.readFewBits(s, n) + (1 << n); } return 0; } - private static void decodeMetaBlockLength(State s) { + private static int decodeMetaBlockLength(State s) { BitReader.fillBitWindow(s); s.inputEnd = BitReader.readFewBits(s, 1); s.metaBlockLength = 0; s.isUncompressed = 0; s.isMetadata = 0; if ((s.inputEnd != 0) && BitReader.readFewBits(s, 1) != 0) { - return; + return BROTLI_OK; } final int sizeNibbles = BitReader.readFewBits(s, 2) + 4; if (sizeNibbles == 7) { s.isMetadata = 1; if (BitReader.readFewBits(s, 1) != 0) { - throw new BrotliRuntimeException("Corrupted reserved bit"); + return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_RESERVED_BIT); } final int sizeBytes = BitReader.readFewBits(s, 2); if (sizeBytes == 0) { - return; + return BROTLI_OK; } for (int i = 0; i < sizeBytes; ++i) { BitReader.fillBitWindow(s); final int bits = BitReader.readFewBits(s, 8); if (bits == 0 && i + 1 == sizeBytes && sizeBytes > 1) { - throw new BrotliRuntimeException("Exuberant nibble"); + return Utils.makeError(s, BROTLI_ERROR_EXUBERANT_NIBBLE); } s.metaBlockLength += bits << (i * 8); } @@ -365,7 +396,7 @@ BitReader.fillBitWindow(s); final int bits = BitReader.readFewBits(s, 4); if (bits == 0 && i + 1 == sizeNibbles && sizeNibbles > 4) { - throw new BrotliRuntimeException("Exuberant nibble"); + return Utils.makeError(s, BROTLI_ERROR_EXUBERANT_NIBBLE); } s.metaBlockLength += bits << (i * 4); } @@ -374,6 +405,7 @@ if (s.inputEnd == 0) { s.isUncompressed = BitReader.readFewBits(s, 1); } + return BROTLI_OK; } /** @@ -428,7 +460,7 @@ } } - private static void readHuffmanCodeLengths( + private static int readHuffmanCodeLengths( int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths, State s) { int symbol = 0; int prevCodeLen = DEFAULT_CODE_LENGTH; @@ -440,7 +472,12 @@ Huffman.buildHuffmanTable(table, tableIdx, 5, codeLengthCodeLengths, CODE_LENGTH_CODES); while (symbol < numSymbols && space > 0) { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + int result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } BitReader.fillBitWindow(s); final int p = BitReader.peekBits(s) & 31; s.bitOffset += table[p] >> 16; @@ -471,7 +508,7 @@ repeat += BitReader.readFewBits(s, extraBits) + 3; final int repeatDelta = repeat - oldRepeat; if (symbol + repeatDelta > numSymbols) { - throw new BrotliRuntimeException("symbol + repeatDelta > numSymbols"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_CODE_LENGTH_TABLE); } for (int i = 0; i < repeatDelta; ++i) { codeLengths[symbol++] = repeatCodeLen; @@ -482,20 +519,22 @@ } } if (space != 0) { - throw new BrotliRuntimeException("Unused space"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_UNUSED_HUFFMAN_SPACE); } // TODO(eustas): Pass max_symbol to Huffman table builder instead? Utils.fillIntsWithZeroes(codeLengths, symbol, numSymbols); + return BROTLI_OK; } - private static void checkDupes(int[] symbols, int length) { + private static int checkDupes(State s, int[] symbols, int length) { for (int i = 0; i < length - 1; ++i) { for (int j = i + 1; j < length; ++j) { if (symbols[i] == symbols[j]) { - throw new BrotliRuntimeException("Duplicate simple Huffman code symbol"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_DUPLICATE_SIMPLE_HUFFMAN_SYMBOL); } } } + return BROTLI_OK; } /** @@ -514,11 +553,14 @@ BitReader.fillBitWindow(s); final int symbol = BitReader.readFewBits(s, maxBits); if (symbol >= alphabetSizeLimit) { - throw new BrotliRuntimeException("Can't readHuffmanCode"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_SYMBOL_OUT_OF_RANGE); } symbols[i] = symbol; } - checkDupes(symbols, numSymbols); + int result = checkDupes(s, symbols, numSymbols); + if (result < BROTLI_OK) { + return result; + } int histogramId = numSymbols; if (numSymbols == 4) { @@ -583,14 +625,19 @@ if (v != 0) { space -= (32 >> v); numCodes++; - if (space <= 0) break; + if (space <= 0) { + break; + } } } if (space != 0 && numCodes != 1) { - throw new BrotliRuntimeException("Corrupted Huffman code histogram"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_HUFFMAN_CODE_HISTOGRAM); } - readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSizeLimit, codeLengths, s); + int result = readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSizeLimit, codeLengths, s); + if (result < BROTLI_OK) { + return result; + } return Huffman.buildHuffmanTable( tableGroup, tableIdx, HUFFMAN_TABLE_BITS, codeLengths, alphabetSizeLimit); @@ -603,18 +650,28 @@ */ private static int readHuffmanCode(int alphabetSizeMax, int alphabetSizeLimit, int[] tableGroup, int tableIdx, State s) { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + int result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } BitReader.fillBitWindow(s); final int simpleCodeOrSkip = BitReader.readFewBits(s, 2); if (simpleCodeOrSkip == 1) { return readSimpleHuffmanCode(alphabetSizeMax, alphabetSizeLimit, tableGroup, tableIdx, s); - } else { - return readComplexHuffmanCode(alphabetSizeLimit, simpleCodeOrSkip, tableGroup, tableIdx, s); } + return readComplexHuffmanCode(alphabetSizeLimit, simpleCodeOrSkip, tableGroup, tableIdx, s); } private static int decodeContextMap(int contextMapSize, byte[] contextMap, State s) { - BitReader.readMoreInput(s); + int result; + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } final int numTrees = decodeVarLenUnsignedByte(s) + 1; if (numTrees == 1) { @@ -633,10 +690,18 @@ /* Speculative single entry table group. */ final int[] table = new int[tableSize + 1]; final int tableIdx = table.length - 1; - readHuffmanCode(alphabetSize, alphabetSize, table, tableIdx, s); + result = readHuffmanCode(alphabetSize, alphabetSize, table, tableIdx, s); + if (result < BROTLI_OK) { + return result; + } int i = 0; while (i < contextMapSize) { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } BitReader.fillBitWindow(s); final int code = readSymbol(table, tableIdx, s); if (code == 0) { @@ -647,7 +712,7 @@ int reps = (1 << code) + BitReader.readFewBits(s, code); while (reps != 0) { if (i >= contextMapSize) { - throw new BrotliRuntimeException("Corrupted context map"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_CONTEXT_MAP); } contextMap[i] = 0; i++; @@ -732,24 +797,36 @@ s.ringBufferSize = newSize; } - private static void readNextMetablockHeader(State s) { + private static int readNextMetablockHeader(State s) { if (s.inputEnd != 0) { s.nextRunningState = FINISHED; s.runningState = INIT_WRITE; - return; + return BROTLI_OK; } // TODO(eustas): Reset? Do we need this? s.literalTreeGroup = new int[0]; s.commandTreeGroup = new int[0]; s.distanceTreeGroup = new int[0]; - BitReader.readMoreInput(s); - decodeMetaBlockLength(s); + int result; + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } + result = decodeMetaBlockLength(s); + if (result < BROTLI_OK) { + return result; + } if ((s.metaBlockLength == 0) && (s.isMetadata == 0)) { - return; + return BROTLI_OK; } if ((s.isUncompressed != 0) || (s.isMetadata != 0)) { - BitReader.jumpToByteBoundary(s); + result = BitReader.jumpToByteBoundary(s); + if (result < BROTLI_OK) { + return result; + } if (s.isMetadata == 0) { s.runningState = COPY_UNCOMPRESSED; } else { @@ -760,7 +837,7 @@ } if (s.isMetadata != 0) { - return; + return BROTLI_OK; } s.expectedTotalSize += s.metaBlockLength; if (s.expectedTotalSize > 1 << 30) { @@ -769,6 +846,7 @@ if (s.ringBufferSize < s.maxRingBufferSize) { maybeReallocateRingBuffer(s); } + return BROTLI_OK; } private static int readMetablockPartition(State s, int treeType, int numBlockTypes) { @@ -780,13 +858,21 @@ } final int blockTypeAlphabetSize = numBlockTypes + 2; - offset += readHuffmanCode( + int result = readHuffmanCode( blockTypeAlphabetSize, blockTypeAlphabetSize, s.blockTrees, 2 * treeType, s); + if (result < BROTLI_OK) { + return result; + } + offset += result; s.blockTrees[2 * treeType + 1] = offset; final int blockLengthAlphabetSize = NUM_BLOCK_LENGTH_CODES; - offset += readHuffmanCode( + result = readHuffmanCode( blockLengthAlphabetSize, blockLengthAlphabetSize, s.blockTrees, 2 * treeType + 1, s); + if (result < BROTLI_OK) { + return result; + } + offset += result; s.blockTrees[2 * treeType + 2] = offset; return readBlockLength(s.blockTrees, 2 * treeType + 1, s); @@ -825,15 +911,32 @@ } } - private static void readMetablockHuffmanCodesAndContextMaps(State s) { + private static int readMetablockHuffmanCodesAndContextMaps(State s) { s.numLiteralBlockTypes = decodeVarLenUnsignedByte(s) + 1; - s.literalBlockLength = readMetablockPartition(s, 0, s.numLiteralBlockTypes); + int result = readMetablockPartition(s, 0, s.numLiteralBlockTypes); + if (result < BROTLI_OK) { + return result; + } + s.literalBlockLength = result; s.numCommandBlockTypes = decodeVarLenUnsignedByte(s) + 1; - s.commandBlockLength = readMetablockPartition(s, 1, s.numCommandBlockTypes); + result = readMetablockPartition(s, 1, s.numCommandBlockTypes); + if (result < BROTLI_OK) { + return result; + } + s.commandBlockLength = result; s.numDistanceBlockTypes = decodeVarLenUnsignedByte(s) + 1; - s.distanceBlockLength = readMetablockPartition(s, 2, s.numDistanceBlockTypes); + result = readMetablockPartition(s, 2, s.numDistanceBlockTypes); + if (result < BROTLI_OK) { + return result; + } + s.distanceBlockLength = result; - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } BitReader.fillBitWindow(s); s.distancePostfixBits = BitReader.readFewBits(s, 2); s.numDirectDistanceCodes = BitReader.readFewBits(s, 4) << s.distancePostfixBits; @@ -848,13 +951,22 @@ s.contextModes[i] = (byte) BitReader.readFewBits(s, 2); i++; } - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } } // TODO(eustas): Reuse? final int contextMapLength = s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS; s.contextMap = new byte[contextMapLength]; - final int numLiteralTrees = decodeContextMap(contextMapLength, s.contextMap, s); + result = decodeContextMap(contextMapLength, s.contextMap, s); + if (result < BROTLI_OK) { + return result; + } + final int numLiteralTrees = result; s.trivialLiteralContext = 1; for (int j = 0; j < contextMapLength; ++j) { if ((int) s.contextMap[j] != j >> LITERAL_CONTEXT_BITS) { @@ -865,24 +977,46 @@ // TODO(eustas): Reuse? s.distContextMap = new byte[s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS]; - final int numDistTrees = decodeContextMap(s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS, + result = decodeContextMap(s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS, s.distContextMap, s); + if (result < BROTLI_OK) { + return result; + } + final int numDistTrees = result; - s.literalTreeGroup = decodeHuffmanTreeGroup(NUM_LITERAL_CODES, NUM_LITERAL_CODES, - numLiteralTrees, s); - s.commandTreeGroup = decodeHuffmanTreeGroup(NUM_COMMAND_CODES, NUM_COMMAND_CODES, - s.numCommandBlockTypes, s); + s.literalTreeGroup = new int[huffmanTreeGroupAllocSize(NUM_LITERAL_CODES, numLiteralTrees)]; + result = decodeHuffmanTreeGroup( + NUM_LITERAL_CODES, NUM_LITERAL_CODES, numLiteralTrees, s, s.literalTreeGroup); + if (result < BROTLI_OK) { + return result; + } + s.commandTreeGroup = + new int[huffmanTreeGroupAllocSize(NUM_COMMAND_CODES, s.numCommandBlockTypes)]; + result = decodeHuffmanTreeGroup( + NUM_COMMAND_CODES, NUM_COMMAND_CODES, s.numCommandBlockTypes, s, s.commandTreeGroup); + if (result < BROTLI_OK) { + return result; + } int distanceAlphabetSizeMax = calculateDistanceAlphabetSize( s.distancePostfixBits, s.numDirectDistanceCodes, MAX_DISTANCE_BITS); int distanceAlphabetSizeLimit = distanceAlphabetSizeMax; if (s.isLargeWindow == 1) { distanceAlphabetSizeMax = calculateDistanceAlphabetSize( s.distancePostfixBits, s.numDirectDistanceCodes, MAX_LARGE_WINDOW_DISTANCE_BITS); - distanceAlphabetSizeLimit = calculateDistanceAlphabetLimit( - MAX_ALLOWED_DISTANCE, s.distancePostfixBits, s.numDirectDistanceCodes); + result = calculateDistanceAlphabetLimit( + s, MAX_ALLOWED_DISTANCE, s.distancePostfixBits, s.numDirectDistanceCodes); + if (result < BROTLI_OK) { + return result; + } + distanceAlphabetSizeLimit = result; } - s.distanceTreeGroup = decodeHuffmanTreeGroup(distanceAlphabetSizeMax, distanceAlphabetSizeLimit, - numDistTrees, s); + s.distanceTreeGroup = + new int[huffmanTreeGroupAllocSize(distanceAlphabetSizeLimit, numDistTrees)]; + result = decodeHuffmanTreeGroup( + distanceAlphabetSizeMax, distanceAlphabetSizeLimit, numDistTrees, s, s.distanceTreeGroup); + if (result < BROTLI_OK) { + return result; + } calculateDistanceLut(s, distanceAlphabetSizeLimit); s.contextMapSlice = 0; @@ -898,30 +1032,42 @@ s.rings[7] = 0; s.rings[8] = 1; s.rings[9] = 0; + return BROTLI_OK; } - private static void copyUncompressedData(State s) { + private static int copyUncompressedData(State s) { final byte[] ringBuffer = s.ringBuffer; + int result; // Could happen if block ends at ring buffer end. if (s.metaBlockLength <= 0) { - BitReader.reload(s); + result = BitReader.reload(s); + if (result < BROTLI_OK) { + return result; + } s.runningState = BLOCK_START; - return; + return BROTLI_OK; } final int chunkLength = Utils.min(s.ringBufferSize - s.pos, s.metaBlockLength); - BitReader.copyRawBytes(s, ringBuffer, s.pos, chunkLength); + result = BitReader.copyRawBytes(s, ringBuffer, s.pos, chunkLength); + if (result < BROTLI_OK) { + return result; + } s.metaBlockLength -= chunkLength; s.pos += chunkLength; if (s.pos == s.ringBufferSize) { s.nextRunningState = COPY_UNCOMPRESSED; s.runningState = INIT_WRITE; - return; + return BROTLI_OK; } - BitReader.reload(s); + result = BitReader.reload(s); + if (result < BROTLI_OK) { + return result; + } s.runningState = BLOCK_START; + return BROTLI_OK; } private static int writeRingBuffer(State s) { @@ -936,22 +1082,28 @@ } if (s.outputUsed < s.outputLength) { - return 1; - } else { - return 0; + return BROTLI_OK; } + return BROTLI_OK_NEED_MORE_OUTPUT; } - private static int[] decodeHuffmanTreeGroup(int alphabetSizeMax, int alphabetSizeLimit, - int n, State s) { + private static int huffmanTreeGroupAllocSize(int alphabetSizeLimit, int n) { final int maxTableSize = MAX_HUFFMAN_TABLE_SIZE[(alphabetSizeLimit + 31) >> 5]; - final int[] group = new int[n + n * maxTableSize]; + return n + n * maxTableSize; + } + + private static int decodeHuffmanTreeGroup(int alphabetSizeMax, int alphabetSizeLimit, + int n, State s, int[] group) { int next = n; for (int i = 0; i < n; ++i) { group[i] = next; - next += readHuffmanCode(alphabetSizeMax, alphabetSizeLimit, group, i, s); + int result = readHuffmanCode(alphabetSizeMax, alphabetSizeLimit, group, i, s); + if (result < BROTLI_OK) { + return result; + } + next += result; } - return group; + return BROTLI_OK; } // Returns offset in ringBuffer that should trigger WRITE when filled. @@ -963,24 +1115,27 @@ return result; } - private static void doUseDictionary(State s, int fence) { + private static int doUseDictionary(State s, int fence) { if (s.distance > MAX_ALLOWED_DISTANCE) { - throw new BrotliRuntimeException("Invalid backward reference"); + return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE); } final int address = s.distance - s.maxDistance - 1 - s.cdTotalSize; if (address < 0) { - initializeCompoundDictionaryCopy(s, -address - 1, s.copyLength); + int result = initializeCompoundDictionaryCopy(s, -address - 1, s.copyLength); + if (result < BROTLI_OK) { + return result; + } s.runningState = COPY_FROM_COMPOUND_DICTIONARY; } else { // Force lazy dictionary initialization. final ByteBuffer dictionaryData = Dictionary.getData(); final int wordLength = s.copyLength; if (wordLength > Dictionary.MAX_DICTIONARY_WORD_LENGTH) { - throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE); } final int shift = Dictionary.sizeBits[wordLength]; if (shift == 0) { - throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE); } int offset = Dictionary.offsets[wordLength]; final int mask = (1 << shift) - 1; @@ -989,7 +1144,7 @@ offset += wordIdx * wordLength; final Transform.Transforms transforms = Transform.RFC_TRANSFORMS; if (transformIdx >= transforms.numTransforms) { - throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE); } final int len = Transform.transformDictionaryWord(s.ringBuffer, s.pos, dictionaryData, offset, wordLength, transforms, transformIdx); @@ -998,10 +1153,11 @@ if (s.pos >= fence) { s.nextRunningState = MAIN_LOOP; s.runningState = INIT_WRITE; - return; + return BROTLI_OK; } s.runningState = MAIN_LOOP; } + return BROTLI_OK; } private static void initializeCompoundDictionary(State s) { @@ -1024,7 +1180,7 @@ } } - private static void initializeCompoundDictionaryCopy(State s, int address, int length) { + private static int initializeCompoundDictionaryCopy(State s, int address, int length) { if (s.cdBlockBits == -1) { initializeCompoundDictionary(s); } @@ -1033,7 +1189,7 @@ index++; } if (s.cdTotalSize > address + length) { - throw new BrotliRuntimeException("Invalid backward reference"); + return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE); } /* Update the recent distances cache */ s.distRbIdx = (s.distRbIdx + 1) & 0x3; @@ -1043,6 +1199,7 @@ s.cdBrOffset = address - s.cdChunkOffsets[index]; s.cdBrLength = length; s.cdBrCopied = 0; + return BROTLI_OK; } private static int copyFromCompoundDictionary(State s, int fence) { @@ -1078,17 +1235,21 @@ /** * Actual decompress implementation. */ - static void decompress(State s) { + static int decompress(State s) { + int result; if (s.runningState == UNINITIALIZED) { - throw new IllegalStateException("Can't decompress until initialized"); + return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_INITIALIZED); + } + if (s.runningState < 0) { + return Utils.makeError(s, BROTLI_PANIC_UNEXPECTED_STATE); } if (s.runningState == CLOSED) { - throw new IllegalStateException("Can't decompress after close"); + return Utils.makeError(s, BROTLI_PANIC_ALREADY_CLOSED); } if (s.runningState == INITIALIZED) { final int windowBits = decodeWindowBits(s); if (windowBits == -1) { /* Reserved case for future expansion. */ - throw new BrotliRuntimeException("Invalid 'windowBits' code"); + return Utils.makeError(s, BROTLI_ERROR_INVALID_WINDOW_BITS); } s.maxRingBufferSize = 1 << windowBits; s.maxBackwardDistance = s.maxRingBufferSize - 16; @@ -1104,26 +1265,38 @@ switch (s.runningState) { case BLOCK_START: if (s.metaBlockLength < 0) { - throw new BrotliRuntimeException("Invalid metablock length"); + return Utils.makeError(s, BROTLI_ERROR_INVALID_METABLOCK_LENGTH); } - readNextMetablockHeader(s); + result = readNextMetablockHeader(s); + if (result < BROTLI_OK) { + return result; + } /* Ring-buffer would be reallocated here. */ fence = calculateFence(s); ringBufferMask = s.ringBufferSize - 1; ringBuffer = s.ringBuffer; continue; - case COMPRESSED_BLOCK_START: - readMetablockHuffmanCodesAndContextMaps(s); + case COMPRESSED_BLOCK_START: { + result = readMetablockHuffmanCodesAndContextMaps(s); + if (result < BROTLI_OK) { + return result; + } s.runningState = MAIN_LOOP; continue; + } case MAIN_LOOP: if (s.metaBlockLength <= 0) { s.runningState = BLOCK_START; continue; } - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } if (s.commandBlockLength == 0) { decodeCommandBlockSwitch(s); } @@ -1152,7 +1325,12 @@ case INSERT_LOOP: if (s.trivialLiteralContext != 0) { while (s.j < s.insertLength) { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } if (s.literalBlockLength == 0) { decodeLiteralBlockSwitch(s); } @@ -1171,7 +1349,12 @@ int prevByte1 = (int) ringBuffer[(s.pos - 1) & ringBufferMask] & 0xFF; int prevByte2 = (int) ringBuffer[(s.pos - 2) & ringBufferMask] & 0xFF; while (s.j < s.insertLength) { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } if (s.literalBlockLength == 0) { decodeLiteralBlockSwitch(s); } @@ -1206,7 +1389,12 @@ // distanceCode in untouched; assigning it 0 won't affect distance ring buffer rolling. s.distance = s.rings[s.distRbIdx]; } else { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } if (s.distanceBlockLength == 0) { decodeDistanceBlockSwitch(s); } @@ -1220,7 +1408,7 @@ (s.distRbIdx + DISTANCE_SHORT_CODE_INDEX_OFFSET[distanceCode]) & 0x3; s.distance = s.rings[index] + DISTANCE_SHORT_CODE_VALUE_OFFSET[distanceCode]; if (s.distance < 0) { - throw new BrotliRuntimeException("Negative distance"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_NEGATIVE_DISTANCE); } } else { final int extraBits = (int) s.distExtraBits[distanceCode]; @@ -1253,7 +1441,7 @@ } if (s.copyLength > s.metaBlockLength) { - throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE + return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE); } s.j = 0; s.runningState = COPY_LOOP; @@ -1300,7 +1488,10 @@ continue; case USE_DICTIONARY: - doUseDictionary(s, fence); + result = doUseDictionary(s, fence); + if (result < BROTLI_OK) { + return result; + } continue; case COPY_FROM_COMPOUND_DICTIONARY: @@ -1308,14 +1499,19 @@ if (s.pos >= fence) { s.nextRunningState = COPY_FROM_COMPOUND_DICTIONARY; s.runningState = INIT_WRITE; - return; + return BROTLI_OK_NEED_MORE_OUTPUT; } s.runningState = MAIN_LOOP; continue; case READ_METADATA: while (s.metaBlockLength > 0) { - BitReader.readMoreInput(s); + if (s.halfOffset > BitReader.HALF_WATERLINE) { + result = BitReader.readMoreInput(s); + if (result < BROTLI_OK) { + return result; + } + } // Optimize BitReader.fillBitWindow(s); BitReader.readFewBits(s, 8); @@ -1325,7 +1521,10 @@ continue; case COPY_UNCOMPRESSED: - copyUncompressedData(s); + result = copyUncompressedData(s); + if (result < BROTLI_OK) { + return result; + } continue; case INIT_WRITE: @@ -1334,9 +1533,10 @@ continue; case WRITE: - if (writeRingBuffer(s) == 0) { + result = writeRingBuffer(s); + if (result != BROTLI_OK) { // Output buffer is full. - return; + return result; } if (s.pos >= s.maxBackwardDistance) { s.maxDistance = s.maxBackwardDistance; @@ -1353,15 +1553,23 @@ continue; default: - throw new BrotliRuntimeException("Unexpected state " + String.valueOf(s.runningState)); + return Utils.makeError(s, BROTLI_PANIC_UNEXPECTED_STATE); } } - if (s.runningState == FINISHED) { - if (s.metaBlockLength < 0) { - throw new BrotliRuntimeException("Invalid metablock length"); - } - BitReader.jumpToByteBoundary(s); - BitReader.checkHealth(s, 1); + if (s.runningState != FINISHED) { + return Utils.makeError(s, BROTLI_PANIC_UNREACHABLE); } + if (s.metaBlockLength < 0) { + return Utils.makeError(s, BROTLI_ERROR_INVALID_METABLOCK_LENGTH); + } + result = BitReader.jumpToByteBoundary(s); + if (result != BROTLI_OK) { + return result; + } + result = BitReader.checkHealth(s, 1); + if (result != BROTLI_OK) { + return result; + } + return BROTLI_OK_DONE; } }
diff --git a/java/org/brotli/dec/Dictionary.java b/java/org/brotli/dec/Dictionary.java index e4378e5..9760445 100644 --- a/java/org/brotli/dec/Dictionary.java +++ b/java/org/brotli/dec/Dictionary.java
@@ -39,19 +39,23 @@ } } + private static final int DICTIONARY_DEBUG = Utils.isDebugMode(); + public static void setData(ByteBuffer newData, int[] newSizeBits) { - if ((Utils.isDirect(newData) == 0) || (Utils.isReadOnly(newData) == 0)) { - throw new BrotliRuntimeException("newData must be a direct read-only byte buffer"); - } - // TODO: is that so? - if (newSizeBits.length > MAX_DICTIONARY_WORD_LENGTH) { - throw new BrotliRuntimeException( - "sizeBits length must be at most " + String.valueOf(MAX_DICTIONARY_WORD_LENGTH)); - } - for (int i = 0; i < MIN_DICTIONARY_WORD_LENGTH; ++i) { - if (newSizeBits[i] != 0) { + if (DICTIONARY_DEBUG != 0) { + if ((Utils.isDirect(newData) == 0) || (Utils.isReadOnly(newData) == 0)) { + throw new BrotliRuntimeException("newData must be a direct read-only byte buffer"); + } + // TODO: is that so? + if (newSizeBits.length > MAX_DICTIONARY_WORD_LENGTH) { throw new BrotliRuntimeException( - "first " + String.valueOf(MIN_DICTIONARY_WORD_LENGTH) + " must be 0"); + "sizeBits length must be at most " + String.valueOf(MAX_DICTIONARY_WORD_LENGTH)); + } + for (int i = 0; i < MIN_DICTIONARY_WORD_LENGTH; ++i) { + if (newSizeBits[i] != 0) { + throw new BrotliRuntimeException( + "first " + String.valueOf(MIN_DICTIONARY_WORD_LENGTH) + " must be 0"); + } } } final int[] dictionaryOffsets = Dictionary.offsets; @@ -65,20 +69,24 @@ dictionaryOffsets[i] = pos; final int bits = dictionarySizeBits[i]; if (bits != 0) { - if (bits >= 31) { - throw new BrotliRuntimeException("newSizeBits values must be less than 31"); - } - pos += i << bits; - if (pos <= 0 || pos > limit) { - throw new BrotliRuntimeException("newSizeBits is inconsistent: overflow"); + pos += i << (bits & 31); + if (DICTIONARY_DEBUG != 0) { + if (bits >= 31) { + throw new BrotliRuntimeException("newSizeBits values must be less than 31"); + } + if (pos <= 0 || pos > limit) { + throw new BrotliRuntimeException("newSizeBits is inconsistent: overflow"); + } } } } for (int i = newSizeBits.length; i < 32; ++i) { dictionaryOffsets[i] = pos; } - if (pos != limit) { - throw new BrotliRuntimeException("newSizeBits is inconsistent: underflow"); + if (DICTIONARY_DEBUG != 0) { + if (pos != limit) { + throw new BrotliRuntimeException("newSizeBits is inconsistent: underflow"); + } } Dictionary.data = newData; }
diff --git a/java/org/brotli/dec/DictionaryData.java b/java/org/brotli/dec/DictionaryData.java index dc3657c..de368a3 100644 --- a/java/org/brotli/dec/DictionaryData.java +++ b/java/org/brotli/dec/DictionaryData.java
@@ -33,12 +33,16 @@ */ private static final String SIZE_BITS_DATA = "AAAAKKLLKKKKKJJIHHIHHGGFF"; + private static final int DICTIONARY_DATA_DEBUG = Utils.isDebugMode(); + private static void unpackDictionaryData(ByteBuffer dictionary, String data0, String data1, String skipFlip, int[] sizeBits, String sizeBitsData) { // Initialize lower 7 bits of every byte in the dictionary. final byte[] dict = Utils.toUsAsciiBytes(data0 + data1); - if (dict.length != dictionary.capacity()) { - throw new RuntimeException("Corrupted brotli dictionary"); + if (DICTIONARY_DATA_DEBUG != 0) { + if (dict.length != dictionary.capacity()) { + throw new RuntimeException("Corrupted brotli dictionary"); + } } // Toggle high bit using run-length delta encoded "skipFlip".
diff --git a/java/org/brotli/dec/SynthTest.java b/java/org/brotli/dec/SynthTest.java index 9aca0c4..fcfbb9a 100644 --- a/java/org/brotli/dec/SynthTest.java +++ b/java/org/brotli/dec/SynthTest.java
@@ -50,7 +50,7 @@ assertArrayEquals(expected, actual); } catch (IOException ex) { if (expectSuccess) { - fail("expected to succeed decoding, but failed"); + throw new AssertionError("expected to succeed decoding, but failed", ex); } } }
diff --git a/java/org/brotli/dec/Utils.java b/java/org/brotli/dec/Utils.java index 304cc1b..97fba3d 100644 --- a/java/org/brotli/dec/Utils.java +++ b/java/org/brotli/dec/Utils.java
@@ -6,6 +6,10 @@ package org.brotli.dec; +import static org.brotli.dec.BrotliError.BROTLI_ERROR_READ_FAILED; +import static org.brotli.dec.BrotliError.BROTLI_OK; +import static org.brotli.dec.BrotliError.BROTLI_PANIC; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -29,8 +33,8 @@ * less than 16. * * @param dest array to fill with zeroes - * @param offset the first byte to fill - * @param length number of bytes to change + * @param start the first item to fill + * @param end the last item to fill (exclusive) */ static void fillBytesWithZeroes(byte[] dest, int start, int end) { int cursor = start; @@ -48,8 +52,8 @@ * less than 16. * * @param dest array to fill with zeroes - * @param offset the first item to fill - * @param length number of item to change + * @param start the first item to fill + * @param end the last item to fill (exclusive) */ static void fillIntsWithZeroes(int[] dest, int start, int end) { int cursor = start; @@ -72,7 +76,7 @@ try { return s.input.read(dst, offset, length); } catch (IOException e) { - throw new BrotliRuntimeException("Failed to read input", e); + return makeError(s, BROTLI_ERROR_READ_FAILED); } } @@ -134,4 +138,18 @@ static int min(int a, int b) { return Math.min(a, b); } + + static int makeError(State s, int code) { + if (code >= BROTLI_OK) { + return code; + } + if (s.runningState >= 0) { + s.runningState = code; // Only the first error is remembered. + } + // TODO(eustas): expand codes to messages, if ever necessary. + if (code <= BROTLI_PANIC) { + throw new IllegalStateException("Brotli error code: " + code); + } + throw new BrotliRuntimeException("Error code: " + code); + } }