NullPointerException in DeepEquals.getContainingDescription when object's custom equals() is inconsistent with field-level equality
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 442
- Forks
- 115
- PR merge metrics
- No merged PRs in 30d
Description
Affected versions: confirmed on 3.3.0, still present in 4.107.0 (method is byte-for-byte identical).
Body:
DeepEquals.deepEquals(a, b) throws an NPE while generating the breadcrumb diff (only on the not-equal path) when the object graph contains a type whose custom equals() returns false for two instances that are nevertheless deeply field-equal.
Root cause is in getContainingDescription:
private static String getContainingDescription(List<ItemsToCompare> path) {
ListIterator<ItemsToCompare> it = path.listIterator(path.size());
String a = it.previous().difference.getDescription(); // <-- last node's `difference` not null-checked
if (it.hasPrevious()) {
Difference diff = it.previous().difference;
if (diff != null) { // parent node IS null-checked
String b = diff.getDescription();
if (b != null) {
return b;
}
}
}
return a;
}
The last node's difference is dereferenced without a null-check, unlike the parent node just below it. It becomes null via the custom-equals branch in the recursive comparison: when key1.equals(key2) is false but the recursive field-by-field deep comparison finds them equal (diff_item == null), nothing is pushed onto the stack, so the root node (new ItemsToCompare(a, b), difference == null) stays on top → NPE.
Minimal reproduction:
class IdentityEquals {
final String value = "x";
@Override public boolean equals(Object o) { return this == o; }
@Override public int hashCode() { return System.identityHashCode(this); }
}
DeepEquals.deepEquals(new IdentityEquals(), new IdentityEquals()); // throws NPE
Suggested fix: null-guard the last node the same way the parent node is guarded, e.g. String a = last.difference != null ? last.difference.getDescription() : null; (and fall back gracefully when both are null).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating DeepEquals.getContainingDescription and reproduce the issue with the provided IdentityEquals example through DeepEquals.deepEquals(a, b). Verify the breadcrumb-diff path when the custom equals check disagrees with field-level equality, then confirm the completed change no longer throws an NPE and falls back gracefully when both descriptions are absent.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100