Skip to content

[#13216] fix(api): fix RemoveMetadataObject.equals ClassCastException and compare locations - #13219

Merged
jerryshao merged 2 commits into
apache:mainfrom
LuciferYang:fix/find-issues-01-metadata-object-remove-equals-cce
Sep 20, 2026
Merged

jerryshao merged 2 commits into
apache:mainfrom
LuciferYang:fix/find-issues-01-metadata-object-remove-equals-cce

Conversation

@LuciferYang

@LuciferYang LuciferYang commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

RemoveMetadataObject.equals now casts to RemoveMetadataObject and compares metadataObject and locations with Objects.equals, and hashCode includes locations. The constructor stores an ImmutableList copy of locations, so the value object is immutable and its hash key stays stable even if the caller later mutates the list it passed in.

Why are the changes needed?

After the getClass() guard the argument was cast to the sibling RenameMetadataObject, so comparing two RemoveMetadataObject instances (including HashSet/contains) always threw ClassCastException, and locations was ignored. Comparing with Objects.equals also avoids a NullPointerException when locations is null (constructible via remove(obj, null)), and the defensive copy keeps locations a stable hash key now that it participates in hashCode.

Fix: #13216

Does this PR introduce any user-facing change?

No API change. Comparing two RemoveMetadataObject instances no longer throws ClassCastException, equals/hashCode now take locations into account, and a built RemoveMetadataObject is immutable.

How was this patch tested?

Added TestMetadataObjectChange, which pins equals/hashCode for RemoveMetadataObject: comparing two instances and HashSet membership (fails on the pre-fix tree with ClassCastException), equality when locations is null (fails pre-fix with NullPointerException), and that mutating the caller's list after construction does not change the object or its HashSet membership.

…are locations

RemoveMetadataObject.equals cast its argument to the sibling class
RenameMetadataObject after the getClass() guard, so comparing two
RemoveMetadataObject instances (including HashSet/contains) always threw
ClassCastException. It also ignored locations, diverging from the
sibling RenameMetadataObject.equals.

Cast to RemoveMetadataObject, compare metadataObject and locations, and
include locations in hashCode to keep the equals/hashCode contract.

Repro TestMetadataObjectChange failed with ClassCastException on the
unfixed tree; passes after the fix. Found by find-issues sweep.
Copilot AI lite review requested due to automatic review settings September 16, 2026 11:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Handle null locations safely in equality and add regression coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Fixes RemoveMetadataObject.equals/hashCode and adds regression coverage.

Changes:

  • Corrects the equality cast and includes locations.
  • Adds equality and HashSet tests.
File summaries
File Summary
api/src/main/java/org/apache/gravitino/authorization/MetadataObjectChange.java Updates removal-change equality and hashing.
api/src/test/java/org/apache/gravitino/authorization/TestMetadataObjectChange.java Adds regression coverage.
Review details

Suppressed comments (1)

api/src/main/java/org/apache/gravitino/authorization/MetadataObjectChange.java:204

  • Now that locations participates in hashCode, retaining the caller's mutable list makes the HashSet behavior tested here unstable: mutating the list passed to remove (or exposed by getLocations()) after insertion changes the hash, so contains/remove can no longer find the entry. RenameMetadataObject defensively copies its locations; apply equivalent defensive/immutable handling to this value before using it as a hash key.
      return Objects.hash(metadataObject, locations);
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

RenameMetadataObject that = (RenameMetadataObject) o;
return metadataObject.equals(that.metadataObject);
RemoveMetadataObject that = (RemoveMetadataObject) o;
return metadataObject.equals(that.metadataObject) && locations.equals(that.locations);
Address review on the equals/hashCode fix: compare locations with
Objects.equals so a null locations (constructible via remove(obj, null))
does not NPE, and store an ImmutableList copy so the locations field —
now part of hashCode — is a stable hash key the caller cannot mutate
after construction.
@LuciferYang

Copy link
Copy Markdown
Contributor Author

Both points are valid and are addressed in 946bd50:

  • equals now compares metadataObject and locations with Objects.equals, so remove(obj, null) no longer throws a NullPointerException on comparison.
  • the constructor stores ImmutableList.copyOf(locations), so locations (which now participates in hashCode) is a stable, immutable hash key that the caller cannot mutate after construction.

Added regression tests for both: equality with null locations, and HashSet membership staying stable after the caller mutates the list it passed to remove(...).

@github-actions

Copy link
Copy Markdown

Code Coverage Report

Overall Project 70.08% -0.02% 🟢
Files changed 30.0% 🔴

Module Coverage
aliyun 19.74% 🔴
api 51.81% -0.17% 🟢
authorization-common 85.96% 🟢
authorization-ranger 4.38% 🔴
aws 53.54% 🟢
azure 32.1% 🔴
catalog-common 28.4% 🔴
catalog-fileset 82.2% 🟢
catalog-glue 71.52% 🟢
catalog-hive 83.07% 🟢
catalog-jdbc-common 45.72% 🟢
catalog-jdbc-doris 83.47% 🟢
catalog-jdbc-mysql 81.8% 🟢
catalog-jdbc-postgresql 83.89% 🟢
catalog-jdbc-starrocks 79.16% 🟢
catalog-kafka 75.21% 🟢
catalog-lakehouse-generic 61.16% 🟢
catalog-lakehouse-hudi 79.1% 🟢
catalog-lakehouse-iceberg 85.9% 🟢
catalog-lakehouse-paimon 84.29% 🟢
catalog-model 77.99% 🟢
cli 44.62% 🟢
client-java 77.96% 🟢
common 58.29% 🟢
core 84.65% 🟢
filesystem-hadoop3 76.48% 🟢
flink 0.0% 🔴
flink-common 53.63% 🟢
flink-runtime 0.0% 🔴
gcp 32.2% 🔴
hadoop-auth 68.0% 🟢
hadoop-common 17.84% 🔴
hive-metastore-common 54.41% 🟢
iceberg-aliyun-bundle 0.0% 🔴
iceberg-common 66.16% 🟢
iceberg-rest-server 77.03% 🟢
idp-basic 86.77% 🟢
integration-test-common 0.0% 🔴
jobs 62.92% 🟢
lance-common 34.02% 🔴
lance-rest-server 69.51% 🟢
lineage 59.39% 🟢
optimizer 83.17% 🟢
optimizer-api 21.95% 🔴
server 90.17% 🟢
server-common 82.0% 🟢
spark 56.42% 🟢
tencent 81.78% 🟢
trino-connector 59.91% 🟢
Files
Module File Coverage
api MetadataObjectChange.java 30.0% 🔴

@jerryshao
jerryshao merged commit 9f18a9e into apache:main Sep 20, 2026
38 checks passed
@LuciferYang

Copy link
Copy Markdown
Contributor Author

Thank you @jerryshao

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug report] RemoveMetadataObject.equals throws ClassCastException and ignores locations

3 participants