NullReferenceException in compareDocument on real-world .docx pairs (26.7, self-hosted)

Hello. The following output is admittedly a result with Claude Code but I did try to whittle out as much of the usual AI fluff to hopefully make this actually usable. I did, however, go through quite a bit of testing with it locally, reproducing the error with a local instance of Aspose.Words. Unfortunately, I can’t share any files (reasons stated below in the AI output) but I will follow up shortly and attach some relevant logs in a separate file.

Thanks in advance.


Product: Aspose.Words Cloud (self-hosted Docker deployment, docker.io/aspose/words-cloud:26.7)
Version: 26.7, the current release. Reproduced locally and running in our production deployment.
Client SDK: aspose_words_cloud Ruby gem 25.4
Endpoint: PUT /v4.0/words/online/put/compareDocument (compare_document_online)
Attachment: aspose-compare-nre-logs.txt


Summary

compareDocument returns HTTP 500 with a bare .NET null-reference error for certain pairs of real-world documents:

{"Error":{"Message":"Object reference not set to an instance of an object."},"RequestId":"..."}

Container logs show the failure originates from Aspose.Words.Cloud.WebApp.Filters.CoreResultExceptionFilter and resolves to two distinct null-deref sites inside the comparison engine.

Variant A, reached through table comparison:

System.NullReferenceException: Object reference not set to an instance of an object.
   at X9.b(Node d)
   at h9.p9iFQ(Char d)
   at p9.p(Int32 d)
   at p9.K(r9 d)
   at p9.X()
   at h9.O()
   at R9.t(Row d, Row v, T9 c)
   at W9.c(Table d, Table v, T9 c)
   at m9.n()
   at h9.O()
   at i9.t()
   at i9.c(Document d, Document v, CompareOptions c)
   at Aspose.Words.Cloud.DocumentActions.Compare.Model.CompareService..MoveNext()

Variant B, reached directly from document comparison:

System.NullReferenceException: Object reference not set to an instance of an object.
   at h9.p9iFQ(Char d)
   at p9.p(Int32 d)
   at p9.K(r9 d)
   at p9.X()
   at h9.O()
   at i9.t()
   at i9.c(Document d, Document v, CompareOptions c)
   at Aspose.Words.Cloud.DocumentActions.Compare.Model.CompareService..MoveNext()

Both variants share the inner call chain h9.O()p9.X()p9.K(r9)p9.p(Int32)h9.p9iFQ(Char). Variant A is reached via W9.c(Table, Table, T9)R9.t(Row, Row, T9) and throws in X9.b(Node). Variant B is reached directly from i9.c(Document, Document, CompareOptions) and throws in h9.p9iFQ(Char).

The stack traces are not present in the API response, only in the container logs, which is where the detail below comes from.


Observed frequency

Over the two-week window we retain traces for, this error occurred on 5 separate days, with a peak of approximately 40 failed requests in a single day, spread across 6 different end users. Document comparison is a core workflow in our product, so the failures are user-visible.


Request parameters

Reproduces with the following CompareOptions, which are our production settings:

{
  "Author": "<user name>",
  "CompareOptions": {
    "AcceptAllRevisionsBeforeComparison": true,
    "Granularity": "WordLevel",
    "IgnoreFormatting": true,
    "IgnoreHeadersAndFooters": true
  }
}

Both the document part and the FileReference part are .docx. The failure reproduces on the raw uploaded files and also after our normal preprocessing (deleteMacros then saveAs docx), so no preprocessing is required to trigger it.


What we determined

We have 6 real document pairs taken from production failures. 5 of them reproduce 100% deterministically on a local 26.7 instance. Findings from bisecting them:

1. Two distinct null-deref sites in the comparison engine

Our 5 reproducing pairs split 4/1 across the two stack traces above, and the split correlates exactly with the two marker types in item 2. The 4 comment-range cases all produce Variant A, the bookmark case produces Variant B.

One observation we cannot account for: Variant B contains no table frames, yet IgnoreTables: true still prevents it.

2. The trigger is a range marker overlapping table content

Programmatically stripping all w:bookmarkStart / w:bookmarkEnd and w:commentRangeStart / w:commentRangeEnd / w:commentReference elements from both documents clears all 5 pairs. Three shapes observed:

Pairs Marker Position Trace
3 w:commentRangeStart/End pair wholly inside a table cell, in the FileReference document A
1 w:commentRangeStart inside a table, with its matching w:commentRangeEnd as a direct child of w:body after the table, so the range straddles the table boundary FileReference document A
1 w:bookmarkStart/End pair spanning body[0..83] of 85 body children, crossing two tables document part B

Stripping only comment markers clears 4 of 5. Stripping only bookmarks clears the remaining 1.

3. It is asymmetric

Swapping which file is the document part and which is the FileReference changes the outcome. One direction throws, the other returns a valid result.

4. Merged or ragged cells are not required

The simplest failing pair is a single 1-row, 3-cell table on each side, with no revisions, no merged cells, and no bookmarks. Only the straddling comment range described above.

5. Minimal reduction

Automated delta-debugging of that pair reduced it to:

  • document part: one table, 1 row, 3 cells
  • FileReference part: the same table, followed by a body-level w:commentRangeEnd, followed by an empty w:p

6. A synthetic reconstruction did not reproduce

We built a document pair from scratch matching the structure in item 5, and it did not reproduce. Something about the surrounding content alignment appears to matter, so the structural description above may not be sufficient on its own to trigger the fault.


Workaround matrix (5 failing pairs)

Option Pairs cleared
IgnoreTables: true 5 / 5
IgnoreComments: true 4 / 5
Granularity: "CharLevel" 2 / 5
Granularity: "CharLevel" plus IgnoreComments: true 4 / 5

Toggling AcceptAllRevisionsBeforeComparison, IgnoreFormatting, IgnoreHeadersAndFooters, IgnoreFields, IgnoreFootnotes, IgnoreTextboxes, and IgnoreCaseChanges has no effect on any pair.

Only IgnoreTables clears all cases. We have not adopted it, since suppressing table comparison would mean edits inside tables stop appearing as tracked changes with no indication to the user.


Environment note

Our local reproduction runs the linux/amd64 image under emulation on Apple silicon. This is not an emulation artifact. The identical error occurs in our production Kubernetes cluster on native linux/amd64, on the same 26.7 build.


On sample documents

We cannot share the affected documents. They are confidential client legal agreements and we are not able to transmit them, in whole, in part, or in derived or redacted form, to any third party.

We can help in these ways instead:

  • Run any instrumented or debug build you provide against the documents in our own environment and return stack traces, logs, or diagnostic output.
  • Run additional experiments you specify, such as option combinations, structural mutations, or other endpoints, and report results.
  • Answer detailed structural questions about the documents’ OOXML.

We can also attach complete container logs for every failing request. We have verified these contain no document content, file names, or client-identifying strings, only ASP.NET framework diagnostics and the stack traces above, so they are shareable without restriction. See aspose-compare-nre-logs.txt.

The file with some relevant logs as mentioned in the original post.

aspose-compare-nre-logs.zip (2.2 KB)

Thanks for sharing logs and analysis, but as you can see the entire stacktrace is obfuscated, what can be the easiest way to help you is you create a sample document with the minimal structure that reproduce that issue, I mean we don’t need any actual data from the document, so you can put any text, but if you create the same structure with the same tables styles and etc. it will allow us to help you faster.