google/ExoPlayer · error · IllegalStateException

Top bit not zero: {result}

Error message

Top bit not zero: {result}

What it means

ParsableByteArray.readUnsignedIntToInt() reads the next four bytes as a signed int and throws IllegalStateException if the result is negative, i.e. if the most significant bit (top bit) of the four bytes is set. The method's contract is to narrow a 32-bit unsigned value into a Java int; a set top bit means the byte stream's value actually needs 32 unsigned bits (>= 2^31), which does not fit, so the data is either malformed or being read at the wrong position with the wrong endianness.

Source

Thrown at library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java:403

   * @return The parsed value.
   */
  public int readSynchSafeInt() {
    int b1 = readUnsignedByte();
    int b2 = readUnsignedByte();
    int b3 = readUnsignedByte();
    int b4 = readUnsignedByte();
    return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4;
  }

  /**
   * Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero.
   *
   * @throws IllegalStateException Thrown if the top bit of the input data is set.
   */
  public int readUnsignedIntToInt() {
    int result = readInt();
    if (result < 0) {
      throw new IllegalStateException("Top bit not zero: " + result);
    }
    return result;
  }

  /**
   * Reads the next four bytes as a little endian unsigned integer into an integer, if the top bit
   * is a zero.
   *
   * @throws IllegalStateException Thrown if the top bit of the input data is set.
   */
  public int readLittleEndianUnsignedIntToInt() {
    int result = readLittleEndianInt();
    if (result < 0) {
      throw new IllegalStateException("Top bit not zero: " + result);
    }
    return result;
  }

View on GitHub (pinned to dd430f7053)

Solutions

  1. Validate position/limit alignment before reading the field — usually the real bug is an earlier misread offset
  2. If the field legitimately needs 32 bits, switch to readUnsignedInt() (returns long) and range-check it yourself
  3. Use the correct endianness variant: readLittleEndianUnsignedIntToInt for little-endian formats
  4. Wrap container parsing per-item so one malformed field skips the section (with a parser exception) instead of crashing playback

Example fix

// before
int size = parsableByteArray.readUnsignedIntToInt(); // top bit set on 4GB-ish payloads
// after
long sizeLong = parsableByteArray.readUnsignedInt();
checkState(sizeLong <= Integer.MAX_VALUE, "Size %s exceeds int range", sizeLong);
int size = (int) sizeLong;
Defensive patterns

Strategy: try-catch

Validate before calling

if (data.limit() - data.getPosition() < 4) { /* not enough bytes: fail before reading */ }

Try / catch

try { v = p.readUnsignedIntToInt(); } catch (IllegalStateException e) { throw new ParserException("Malformed 32-bit field", e); }

Prevention

When it happens

Trigger: Parsing a container/protocol field documented as '31-bit max' where the encoder wrote a full 32-bit value; reading at a wrong offset so a large size/timestamp field lands in the int slot; interpreting big-endian data as big-endian here is correct but data was written little-endian (use readLittleEndianUnsignedIntToInt instead); corrupted or truncated downloads feeding garbage into the parser.

Common situations: Custom or evolving MP4/EBML/section parsers (DVB, PES, HLS tags) where a length field occasionally exceeds Integer.MAX_VALUE on 4K/long recordings; partial file reads making subsequent fields misaligned; firmware/encoder writing nonstandard 32-bit values into fields spec'd at 31 bits.

Related errors


AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14). Data as JSON: /api/errors/bd8f085fc257e189. Report an issue: GitHub.