mozilla/pdf.js · error · Error

getOperatorList - ignoring circular reference: ${objId}

Error message

getOperatorList - ignoring circular reference: ${objId}

What it means

Thrown at the very start of getOperatorList when the stream's objId already appears in prevRefs (a RefSet tracking ancestor streams). It is an anti-recursion guard: a Form XObject or content stream that (directly or transitively) references itself would otherwise loop forever. Unlike operator-dispatch errors, this is a bare throw at method entry and is NOT suppressed by ignoreErrors.

Source

Thrown at src/core/evaluator.js:1695

    resources,
    operatorList,
    initialState = null,
    fallbackFontDict = null,
    prevRefs = null,
  }) {
    if (stream.isAsync) {
      const bytes = await stream.asyncGetBytes();
      if (bytes) {
        stream = new Stream(bytes, 0, bytes.length, stream.dict);
      }
    }

    const objId = stream.dict?.objId;
    const seenRefs = new RefSet(prevRefs);

    if (objId) {
      if (prevRefs?.has(objId)) {
        throw new Error(
          `getOperatorList - ignoring circular reference: ${objId}`
        );
      }
      seenRefs.put(objId);
    }
    // Ensure that `resources`/`initialState` is correctly initialized,
    // even if the provided parameter is e.g. `null`.
    resources ||= Dict.empty;
    initialState ||= new EvalState();

    if (!operatorList) {
      throw new Error('getOperatorList: missing "operatorList" parameter');
    }

    const self = this;
    const xref = this.xref;
    const localImageCache = new LocalImageCache();
    const localColorSpaceCache = new LocalColorSpaceCache();

View on GitHub (pinned to 5903d58d58)

Solutions

  1. Wrap the page.render() / getOperatorList call in try/catch and present a graceful 'page could not be rendered' result, since ignoreErrors will not help here.
  2. Repair the PDF to break the cycle in the XObject reference graph.
  3. Upgrade PDF.js; cycle-detection has been hardened over releases.

Example fix

// before
const page = await pdf.getPage(n);
await page.render({ canvasContext, viewport }).promise; // rejects on cycle

// after
try {
  await page.render({ canvasContext, viewport }).promise;
} catch (e) {
  console.warn('Unrenderable page (possible circular XObject):', e.message);
}
Defensive patterns

Strategy: try-catch

Try / catch

// ignoreErrors does NOT suppress this; catch at the render boundary.
try {
  await page.render({ canvasContext, viewport }).promise;
} catch (e) {
  if (/circular reference/.test(e.message)) {
    showUnrenderablePagePlaceholder(page.pageNumber);
  } else throw e;
}

Prevention

When it happens

Trigger: buildFormXObject calls getOperatorList with prevRefs including the current objId, which happens when a Form XObject's stream chain re-enters a stream already on the ancestor path (cycle in the XObject graph).

Common situations: Malformed or maliciously crafted PDFs with cyclic Form XObject references; PDFs damaged in transit so that object references form a loop.

Related errors


AI-assisted analysis of mozilla/pdf.js@5903d58d58 (2026-08-13). Data as JSON: /api/errors/607f2cf6c3496283. Report an issue: GitHub.