Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 46 additions & 23 deletions pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateState.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/
package org.apache.pdfbox.cos;

import java.util.ArrayDeque;
import java.util.Deque;

/**
* A {@link COSUpdateState} instance manages update states for a {@link COSUpdateInfo}. Such states are used to create
* a {@link COSIncrement} for the incremental saving of a {@link COSDocument}.
Expand Down Expand Up @@ -117,51 +120,71 @@ public void setOriginDocumentState(COSDocumentState originDocumentState)
*/
private void setOriginDocumentState(COSDocumentState originDocumentState, boolean dereferencing)
{
if(this.originDocumentState != null || originDocumentState == null)
if (originDocumentState == null)
{
return;
}
this.originDocumentState = originDocumentState;
if(!dereferencing)
// The structure is traversed with an explicit stack instead of recursion, as it may be
// nested deeper than the call stack allows (PDFBOX-6200).
Deque<COSUpdateState> pending = new ArrayDeque<>();
pending.push(this);
while (!pending.isEmpty())
{
update();
COSUpdateState state = pending.pop();
if (state.originDocumentState != null)
{
continue;
}
state.originDocumentState = originDocumentState;
if (!dereferencing)
{
state.update();
}
state.pushSubstructures(pending);
}

if(updateInfo instanceof COSDictionary)
}

/**
* Pushes the {@link COSUpdateState}s of the substructures directly contained by the managed
* {@link #updateInfo} onto the given stack.
*
* @param pending The stack of {@link COSUpdateState}s that still have to be processed.
*/
private void pushSubstructures(Deque<COSUpdateState> pending)
{
if (updateInfo instanceof COSDictionary)
{
COSDictionary dictionary = (COSDictionary) updateInfo;
for(COSBase entry : dictionary.getValues())
for (COSBase entry : dictionary.getValues())
{
if (entry instanceof COSUpdateInfo)
{
((COSUpdateInfo) entry).getUpdateState().setOriginDocumentState(originDocumentState, dereferencing);
}
pushUpdateState(pending, entry);
}
}
else if(updateInfo instanceof COSArray)
else if (updateInfo instanceof COSArray)
{
COSArray array = (COSArray) updateInfo;
for(COSBase entry : array)
for (COSBase entry : array)
{
if (entry instanceof COSUpdateInfo)
{
((COSUpdateInfo) entry).getUpdateState().setOriginDocumentState(originDocumentState, dereferencing);
}
pushUpdateState(pending, entry);
}
}
else if(updateInfo instanceof COSObject)
else if (updateInfo instanceof COSObject)
{
COSObject object = (COSObject) updateInfo;
if (object.isDereferenced())
{
COSBase reference = object.getObject();
if (reference instanceof COSUpdateInfo)
{
((COSUpdateInfo) reference).getUpdateState().setOriginDocumentState(originDocumentState, dereferencing);
}
pushUpdateState(pending, object.getObject());
}
}
}

private static void pushUpdateState(Deque<COSUpdateState> pending, COSBase base)
{
if (base instanceof COSUpdateInfo)
{
pending.push(((COSUpdateInfo) base).getUpdateState());
}
}

/**
* <p>
Expand Down
43 changes: 43 additions & 0 deletions pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.pdfbox.cos;

import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;

Expand Down Expand Up @@ -58,4 +59,46 @@ void testIsSetNeedToBeUpdate()
assertFalse(testCOSObject.isNeedToBeUpdated());
}


/**
* PDFBOX-6200: linking a document state to a deeply nested structure must not overflow the
* stack.
*/
@Test
void testSetOriginDocumentStateOfDeeplyNestedStructure()
{
COSDocumentState origin = new COSDocumentState();
origin.setParsing(false);

// a chain of dictionaries, arrays and dereferenced indirect objects, built from the end
COSDictionary deepest = new COSDictionary();
COSBase current = deepest;
for (int i = 0; i < 50_000; i++)
{
switch (i % 3)
{
case 0:
COSDictionary dictionary = new COSDictionary();
dictionary.setItem(COSName.getPDFName("Next"), current);
current = dictionary;
break;
case 1:
COSArray array = new COSArray();
array.add(current);
current = array;
break;
default:
current = new COSObject(current);
break;
}
}
COSUpdateInfo root = (COSUpdateInfo) current;

root.getUpdateState().setOriginDocumentState(origin);

assertSame(origin, deepest.getUpdateState().getOriginDocumentState());
assertTrue(root.isNeedToBeUpdated());
assertTrue(deepest.isNeedToBeUpdated());
}

}