{"record":{"id":"2e708fcb447fccb1","repo":"mozilla/pdf.js","slug":"invalid-root-reference","errorCode":null,"errorMessage":"Invalid Root reference.","messagePattern":"Invalid Root reference\\.","errorType":"exception","errorClass":"InvalidPDFException","httpStatus":null,"severity":"critical","filePath":"src/core/xref.js","lineNumber":184,"sourceCode":"      try {\n        const pages = root.get(\"Pages\");\n        if (pages instanceof Dict) {\n          this.root = root;\n          return;\n        }\n      } catch (ex) {\n        if (ex instanceof MissingDataException) {\n          throw ex;\n        }\n        warn(`XRef.parse - Invalid \"Pages\" reference: \"${ex}\".`);\n      }\n    }\n\n    if (!recoveryMode) {\n      throw new XRefParseException();\n    }\n    // Even recovery failed, there's nothing more we can do here.\n    throw new InvalidPDFException(\"Invalid Root reference.\");\n  }\n\n  processXRefTable(parser) {\n    // Stores state of the table as we process it so we can resume\n    // from middle of table in case of missing data error\n    this._tableState ??= {\n      entryNum: 0,\n      streamPos: parser.lexer.stream.pos,\n      parserBuf1: parser.buf1,\n      parserBuf2: parser.buf2,\n    };\n\n    const obj = this.readXRefTable(parser);\n\n    // Sanity check\n    if (!isCmd(obj, \"trailer\")) {\n      throw new FormatError(\n        \"Invalid XRef table: could not find trailer dictionary\"","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/mozilla/pdf.js/blob/5903d58d58e4dd9ce6ffa3834aea8480f06b4ada/src/core/xref.js#L166-L202","documentation":"Thrown by XRef.parse() (xref.js:184) as an InvalidPDFException when the PDF's cross-reference structure is irrecoverably broken: the trailer's /Root is not a Dict, or its /Pages entry is missing/not a Dict, AND a full-stream recovery pass (recoveryMode) has already been attempted and also failed. This is the terminal failure after worker.js retries parse with recoveryMode=true. The message 'Invalid Root reference.' is the inline literal.","triggerScenarios":"PDF.js loads a file, fails to find a valid /Root->/Pages catalog (XRefParseException), then worker.js requests the full loaded stream and retries XRef.parse with recoveryMode=true; recovery still cannot locate a usable catalog, so this exception is thrown. Reached via getDocument()/PDFWorker when opening the file.","commonSituations":"A truncated or partially downloaded PDF missing the trailer/xref/catalog; a file that is not actually a PDF (wrong magic, HTML/JSON saved as .pdf); a PDF whose /Root or /Pages indirect object is zeroed/corrupted; incremental-update or linearization corruption; a PDF produced by a buggy writer with a broken xref table.","solutions":["Confirm the file is a real PDF: check the '%PDF-' header and a '%%EOF' marker, and verify the file size matches the source (re-download if truncated).","Open the file in another viewer (Acrobat/mutool) to confirm it is genuinely corrupt; if it opens there, capture the xref/trailer bytes and file a PDF.js issue with the sample.","Repair the PDF with a tool like qpdf --check / mutool clean / ghostscript to rebuild the xref and catalog, then load the repaired copy.","If you control generation, fix the writer to emit a valid trailer with /Root pointing at a catalog Dict that has a valid /Pages tree.","In your loading code, handle InvalidPDFException and present a clear 'corrupt or unsupported file' message rather than crashing."],"exampleFix":"// before\nconst doc = await getDocument(data).promise;\n// after\nimport { InvalidPDFException, MissingPDFException } from \"pdfjs-dist\";\ntry {\n  const doc = await getDocument(data).promise;\n} catch (e) {\n  if (e instanceof InvalidPDFException) {\n    // file is corrupt or not a valid PDF (e.g. broken /Root)\n    showError(\"This file is not a valid PDF and cannot be opened.\");\n  } else if (e instanceof MissingPDFException) {\n    showError(\"File not found.\");\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Before handing the file to getDocument, sanity-check it is a real,\n// non-truncated PDF with an xref/trailer.\nasync function looksLikeValidPdf(data) {\n  const head = new TextDecoder().decode(data.slice(0, 5));\n  if (head !== \"%PDF-\") return false;\n  // must end with %%EOF (allow trailing whitespace)\n  const tail = new TextDecoder().decode(data.slice(-1024));\n  if (!/%%EOF(\\s)*$/.test(tail)) return false;\n  // size sanity: reject obviously truncated uploads (< a few hundred bytes)\n  if (data.byteLength < 200) return false;\n  return true;\n}","typeGuard":"// pdfjs-dist exposes exception classes; use them to narrow the catch.\nimport { InvalidPDFException, MissingPDFException, PasswordException } from \"pdfjs-dist\";\nfunction isInvalidPdf(e) {\n  return e instanceof InvalidPDFException;\n}","tryCatchPattern":"import { getDocument } from \"pdfjs-dist\";\nimport { InvalidPDFException, MissingPDFException } from \"pdfjs-dist\";\ntry {\n  const loadingTask = getDocument({ data });\n  const pdf = await loadingTask.promise;\n  // ... use pdf\n} catch (e) {\n  if (e instanceof InvalidPDFException) {\n    // /Root or xref irrecoverably broken (e.g. 'Invalid Root reference.')\n    showUserError(\"This file is corrupt or is not a valid PDF.\");\n  } else if (e instanceof MissingPDFException) {\n    showUserError(\"File not found.\");\n  } else if (e instanceof PasswordException) {\n    showUserError(\"A password is required.\");\n  } else {\n    throw e;\n  }\n}","preventionTips":["Validate the '%PDF-' header and a trailing '%%EOF' before loading.","Verify the uploaded file's byte length matches the source to catch truncation.","Run 'qpdf --check' or 'mutool clean' on suspect files to rebuild the xref/catalog before loading.","Always handle InvalidPDFException separately from network/password errors so users get an accurate message.","If you generate PDFs, ensure the writer emits a valid trailer with /Root pointing to a catalog Dict that has a valid /Pages tree."],"tags":["pdf","xref","catalog","corruption","invalid-pdf"],"backgroundTag":null,"analyzedSha":"5903d58d58e4dd9ce6ffa3834aea8480f06b4ada","analyzedAt":"2026-08-13T02:28:27.364Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}