diff --git a/src/core/annotation.js b/src/core/annotation.js index d420c2f66..2db3b26ef 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -39,7 +39,15 @@ import { createDefaultAppearance, parseDefaultAppearance, } from "./default_appearance.js"; -import { Dict, isDict, isName, isRef, isStream, Name } from "./primitives.js"; +import { + Dict, + isDict, + isName, + isRef, + isStream, + Name, + RefSet, +} from "./primitives.js"; import { ColorSpace } from "./colorspace.js"; import { OperatorList } from "./operator_list.js"; import { StringStream } from "./stream.js"; @@ -1066,14 +1074,27 @@ class WidgetAnnotation extends Annotation { } let loopDict = dict; + const visited = new RefSet(); + if (dict.objId) { + visited.put(dict.objId); + } while (loopDict.has("Parent")) { loopDict = loopDict.get("Parent"); - if (!isDict(loopDict)) { + if ( + !(loopDict instanceof Dict) || + (loopDict.objId && visited.has(loopDict.objId)) + ) { // Even though it is not allowed according to the PDF specification, // bad PDF generators may provide a `Parent` entry that is not a // dictionary, but `null` for example (issue 8143). + // + // If parent has been already visited, it means that we're + // in an infinite loop. break; } + if (loopDict.objId) { + visited.put(loopDict.objId); + } if (loopDict.has("T")) { fieldName.unshift(stringToPDFString(loopDict.get("T"))); diff --git a/src/core/core_utils.js b/src/core/core_utils.js index 2fb2d792e..cb205f141 100644 --- a/src/core/core_utils.js +++ b/src/core/core_utils.js @@ -19,7 +19,6 @@ import { bytesToString, objectSize, stringToPDFString, - warn, } from "../shared/util.js"; import { Dict, isName, isRef, isStream, RefSet } from "./primitives.js"; @@ -72,8 +71,7 @@ class XRefParseException extends BaseException {} * * If the key is not found in the tree, `undefined` is returned. Otherwise, * the value for the key is returned or, if `stopWhenFound` is `false`, a list - * of values is returned. To avoid infinite loops, the traversal is stopped when - * the loop limit is reached. + * of values is returned. * * @param {Dict} dict - Dictionary from where to start the traversal. * @param {string} key - The key of the property to find the value for. @@ -90,11 +88,13 @@ function getInheritableProperty({ getArray = false, stopWhenFound = true, }) { - const LOOP_LIMIT = 100; - let loopCount = 0; let values; + const visited = new RefSet(); - while (dict) { + while (dict instanceof Dict && !(dict.objId && visited.has(dict.objId))) { + if (dict.objId) { + visited.put(dict.objId); + } const value = getArray ? dict.getArray(key) : dict.get(key); if (value !== undefined) { if (stopWhenFound) { @@ -105,10 +105,6 @@ function getInheritableProperty({ } values.push(value); } - if (++loopCount > LOOP_LIMIT) { - warn(`getInheritableProperty: maximum loop count exceeded for "${key}"`); - break; - } dict = dict.get("Parent"); } return values; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 24e86226d..ddb5fcee3 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -76,6 +76,7 @@ !issue8798r.pdf !issue8823.pdf !issue9084.pdf +!issue12963.pdf !issue9105_reduced.pdf !issue9252.pdf !issue9262_reduced.pdf diff --git a/test/pdfs/issue12963.pdf b/test/pdfs/issue12963.pdf new file mode 100644 index 000000000..3c4640ef6 Binary files /dev/null and b/test/pdfs/issue12963.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 5a893ad2e..1658a0d8a 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -2445,6 +2445,15 @@ "lastPage": 1, "type": "eq" }, + { "id": "issue12963", + "file": "pdfs/issue12963.pdf", + "md5": "2590047a2ef0113e698d888c29918008", + "rounds": 1, + "firstPage": 1, + "lastPage": 1, + "type": "eq", + "annotations": true + }, { "id": "multiple-filters-length-zero", "file": "pdfs/multiple-filters-length-zero.pdf", "md5": "c273c3a6fb79cbf3034fe1b62b204728", diff --git a/test/unit/core_utils_spec.js b/test/unit/core_utils_spec.js index a9d813cb3..033df42df 100644 --- a/test/unit/core_utils_spec.js +++ b/test/unit/core_utils_spec.js @@ -123,30 +123,6 @@ describe("core_utils", function () { ["qux2", "quux"], ]); }); - - it("stops searching when the loop limit is reached", function () { - const dict = new Dict(); - let currentDict = dict; - let parentDict = null; - // Exceed the loop limit of 100. - for (let i = 0; i < 150; i++) { - parentDict = new Dict(); - currentDict.set("Parent", parentDict); - currentDict = parentDict; - } - parentDict.set("foo", "bar"); // Never found because of loop limit. - expect(getInheritableProperty({ dict, key: "foo" })).toEqual(undefined); - - dict.set("foo", "baz"); - expect( - getInheritableProperty({ - dict, - key: "foo", - getArray: false, - stopWhenFound: false, - }) - ).toEqual(["baz"]); - }); }); describe("toRomanNumerals", function () {