can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: empty LZX Huffman tree

Error message

Invalid CAB archive: empty LZX Huffman tree

What it means

This error is thrown by the LzxHuffmanTable constructor when every code length in the tree description is zero, i.e. no symbols are assigned any Huffman code. The LZX format requires a usable tree to decode the bitstream, so a tree with zero symbols is rejected unless the caller explicitly passes allowEmpty (used only for the length tree, which may legitimately be empty). It guards against corrupted or malformed CAB LZX block headers.

Source

Thrown at packages/utils/src/ar/codecs/lzx.ts:80

class LzxHuffmanTable {
	readonly #counts = new Uint32Array(17);
	readonly #firstCodes = new Uint32Array(17);
	readonly #firstSymbols = new Uint32Array(17);
	readonly #symbols: Uint16Array;
	readonly empty: boolean;

	constructor(lengths: Uint8Array, allowEmpty = false) {
		let symbolCount = 0;
		for (const length of lengths) {
			if (length > 16) throw new ArchiveError("Invalid CAB archive: invalid LZX Huffman code length");
			if (length !== 0) {
				this.#counts[length]++;
				symbolCount++;
			}
		}
		this.empty = symbolCount === 0;
		if (this.empty && !allowEmpty) {
			throw new ArchiveError("Invalid CAB archive: empty LZX Huffman tree");
		}

		let code = 0;
		let symbolOffset = 0;
		for (let length = 1; length <= 16; length++) {
			code = (code + this.#counts[length - 1]!) * 2;
			if (code + this.#counts[length]! > 2 ** length) {
				throw new ArchiveError("Invalid CAB archive: oversubscribed LZX Huffman tree");
			}
			this.#firstCodes[length] = code;
			this.#firstSymbols[length] = symbolOffset;
			symbolOffset += this.#counts[length]!;
		}

		this.#symbols = new Uint16Array(symbolCount);
		const next = this.#firstSymbols.slice();
		for (let symbol = 0; symbol < lengths.byteLength; symbol++) {
			const length = lengths[symbol]!;

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the CAB file integrity (checksums, re-download or re-extract the archive).
  2. Re-create the CAB with a conformant tool (e.g. makecab/cabarc) so each Huffman tree defines at least one symbol.
  3. If you are constructing trees manually for testing, pass allowEmpty=true or give at least one symbol a non-zero code length.
  4. Check that the bit reader is synchronized — an earlier parsing mistake can shift bits so zero lengths are read.

Example fix

// before
new LzxHuffmanTable(lengths) // throws when all lengths are 0
// after
new LzxHuffmanTable(lengths, lengths === this.#lengthLengths) // allow empty only for the secondary length tree
Defensive patterns

Strategy: try-catch

Validate before calling

// If you control the lengths, pre-check before constructing:
const hasSymbol = lengths.some(l => l !== 0)
if (!hasSymbol) throw new Error('Refusing to build an empty Huffman tree')

Type guard

function isNonEmptyTree(lengths: Uint8Array): boolean {
  return lengths.some(l => l !== 0)
}

Try / catch

try {
  const table = new LzxHuffmanTable(lengths)
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('empty LZX Huffman tree')) {
    // treat archive as corrupt: surface a user-friendly 'archive is damaged' error
  }
  throw err
}

Prevention

When it happens

Trigger: Decompressing a CAB folder whose LZX block header defines a main, length, aligned-offset, or pretree where all code lengths read from the bitstream are 0 (e.g. new LzxHuffmanTable(new Uint8Array(256)) without allowEmpty).

Common situations: Corrupted or truncated CAB downloads, archives produced by buggy or non-conforming LZX compressors, bit-level parsing desync from an earlier bad read leaving garbage where tree lengths should be.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/49847a5e534c3029. Report an issue: GitHub.