can1357/oh-my-pi · error · ArchiveError

Invalid ${label} compressed data: match exceeds declared siz

Error message

Invalid ${label} compressed data: match exceeds declared size

What it means

A decoded match (back-reference) would write past outSize, the declared uncompressed size. The library refuses to overflow the caller-provided output buffer, which indicates corrupt commands or a wrong outSize.

Source

Thrown at packages/utils/src/ar/lzh.ts:226

	let commands: CanonicalHuffman | undefined;
	let positions: CanonicalHuffman | undefined;
	while (outputPosition < outSize) {
		if (blockRemaining === 0) {
			blockRemaining = reader.read(16);
			if (blockRemaining === 0) throw new ArchiveError(`Invalid ${label} compressed data: empty block`);
			const temporary = readTemporaryTree(reader, label);
			commands = readCommandTree(reader, temporary, label);
			positions = readPositionTree(reader, positionBits, positionSymbols, label);
		}
		blockRemaining--;
		const symbol = commands!.decode(reader);
		if (symbol < 256) {
			output[outputPosition++] = symbol;
			continue;
		}
		const length = symbol - 256 + 3;
		if (length > outSize - outputPosition) {
			throw new ArchiveError(`Invalid ${label} compressed data: match exceeds declared size`);
		}
		const positionCode = positions!.decode(reader);
		let distance = positionCode;
		if (positionCode > 1) {
			const lowBitCount = positionCode - 1;
			distance = 2 ** lowBitCount + reader.read(lowBitCount);
		}
		if (distance >= dictionarySize || distance >= outputPosition) {
			throw new ArchiveError(`Invalid ${label} compressed data: history distance is out of range`);
		}
		let sourcePosition = outputPosition - distance - 1;
		for (let index = 0; index < length; index++) output[outputPosition++] = output[sourcePosition++]!;
	}
	if (blockRemaining !== 0) throw new ArchiveError(`Invalid ${label} compressed data: block exceeds declared size`);
	reader.assertZeroPadding();
	return output;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-read the original-size field from the correct header location (including extended headers)
  2. Validate the archive source / re-download
  3. Catch ArchiveError and report the entry as corrupt instead of writing partial output

Example fix

// before
const outSize = u64(header, 7); // wrong offset, too small
// after
const outSize = sizeFromHeader(header); // use parsed real size field
Defensive patterns

Strategy: try-catch

Validate before calling

if (outSize <= 0 || !Number.isSafeInteger(outSize)) throw new Error("invalid declared output size");

Try / catch

try {
  const out = decompressLhStatic(packed, outSize, positionBits, positionSymbols, label);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("match exceeds")) return null;
  throw err;
}

Prevention

When it happens

Trigger: decompressLhStatic decodes a command symbol whose implied length (symbol-256+3) exceeds remaining output space — corrupt stream, or outSize computed smaller than the real uncompressed size (e.g. wrong u64 header parse or wrong header field).

Common situations: Header vs actual-data mismatch after editing an archive; wrong original-size field from a damaged extended header; fuzzed inputs.

Related errors


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