0

Revert "Implement equals/hashCode methods for generated Java Mojo structs/unions"

This reverts commit 57ceaaf284.

Reason for revert: binary size increase is too large

Original change's description:
> Implement equals/hashCode methods for generated Java Mojo structs/unions
>
> This will allow clients to compare Mojo structs or unions of the same
> type for equality. Java Mojo structs are equal when all of their public
> fields are deeply equal.
>
> Android binary size increase explanations
> - Added symbols named "ForTest"
> This is an existing Mojo Java class. It is deeply coupled with Mojo's
> interface code and we're just adding two new methods to that class (not
> introducing the ForTest violation).
> - Dex Methods Count/Size increase
> Generating 2 new methods for all Java Mojo structs/unions. This should
> be a temporary increase until we find a long term solution tracked here:
> https://crbug.com/399297934
>
> Binary-Size: See above
> Change-Id: I848e80bcab0053a49370ac98064683a3d622e6f2
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6275565
> Reviewed-by: Ashley Newson <ashleynewson@chromium.org>
> Commit-Queue: Alex Mitra <alexmitra@chromium.org>
> Reviewed-by: Oksana Zhuravlova <oksamyt@chromium.org>
> Reviewed-by: Fred Shih <ffred@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1426225}

(cherry picked from commit 940317b8c3)

Bug: 399297934
Fixed: 401200774
Change-Id: I4b54f5c03043095e6a2ce17059b83a0c74b38176
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6322989
Reviewed-by: Fred Shih <ffred@chromium.org>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Alex Mitra <alexmitra@chromium.org>
Cr-Original-Commit-Position: refs/heads/main@{#1428823}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6329530
Cr-Commit-Position: refs/branch-heads/7049@{#390}
Cr-Branched-From: 2dab7846d0951a552bdc4f350dad497f986e6fed-refs/heads/main@{#1427262}
This commit is contained in:
Alex Mitra
2025-03-10 07:43:34 -07:00
committed by Chromium LUCI CQ
parent 7544240aeb
commit ff48a42974
8 changed files with 0 additions and 606 deletions
mojo/public
interfaces
java
bindings
system
BUILD.gn
javatests
src
org
chromium
mojo
tools

@ -64,7 +64,6 @@ mojom("test_interfaces") {
generate_legacy_js_bindings = true
sources = [
"equals_test_structs.test-mojom",
"math_calculator.test-mojom",
"no_module.test-mojom",
"nullable_value_types.test-mojom",

@ -1,89 +0,0 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"]
module mojo.test;
struct StructWithPrimitives {
uint8 a;
bool b;
};
struct StructWithPrimitiveArrays {
array<int32> a;
array<float> b;
};
struct StructOfObjects {
StructWithPrimitives a;
};
struct StructWithObjectArrays {
array<StructWithPrimitives> a;
array<float?> b;
};
struct StructWithHandle {
handle a;
};
struct StructWithNullables {
StructWithPrimitives? a;
string? b;
bool? c;
};
struct MixedStruct {
bool a;
string? b;
StructOfObjects c;
array<StructWithObjectArrays> d;
};
struct StructWithFloat {
float a;
};
struct StructWithFloat2 {
float a;
};
struct StructWithNestedArray {
array<array<int32>> a;
};
union UnionWithPrimitives {
uint8 a;
bool b;
};
union UnionWithPrimitiveArrays {
array<int32> a;
array<float> b;
};
union UnionOfObjects {
StructWithPrimitives a;
UnionWithPrimitives b;
};
union UnionWithObjectArrays {
array<UnionWithPrimitives> a;
array<float?> b;
};
union UnionWithHandle {
handle a;
bool b;
};
union UnionWithFloat {
float a;
bool b;
};
union UnionWithFloat2 {
float a;
bool b;
};

@ -249,24 +249,6 @@ IPC. Serialization and deserialization when being sent or received across a Mojo
channel will both perform nullness checks. If you receive a struct over IPC, it
is guaranteed to comply with the nullability specified in the mojom file.
### Struct equality
Structs autogenerate `equals` and `hashCode` methods. However, you should take
caution if you plan to use a generated Java struct in a collection that relies
on the `hashCode` as the struct's Java object itself is mutable.
Additionally, structs with `float` fields set to NaN will report those fields as
equal despite `Float.NaN != Float.NaN` being `true` in Java. This behavior was
chosen to satisfy the reflexivity requirement for Java objects (for any non-null
object x, `x.equals(x)` should be `true`). This decision has the side effect of
meaning that float fields set to `-0.0f` and `0.0f` respectively will report as
not equal despite `-0.0f == 0.0f` being `true`.
Structs with fields like handles, remotes and receivers will compare these with
reference equality checks. This means that it will be very rare for structs
containing these field types to be equal unless they contain the same instance
in those fields.
## Unions
```
@ -317,14 +299,6 @@ The constants for the different tags/variants of a union are available under a
static Tag class under the union's generated Java class and use
PascalCase. (Note that this is unlike the variants for enums.)
### Union equality
Unions also generate `equals` and `hashCode` methods with the same rules and
caveats as structs (see [Struct equality](#struct-equality)). One important note
for unions is that only the tag and the field it represents is compared. Two
unions may be equal despite some of their fields holding different data
(provided the tag for those fields is not set).
# Interfaces
Consider the following interface used as an example in the following sections:

@ -107,7 +107,6 @@ android_library("mojo_javatests") {
"javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java",
"javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java",
"javatests/src/org/chromium/mojo/bindings/ConnectorTest.java",
"javatests/src/org/chromium/mojo/bindings/EqualsTest.java",
"javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java",
"javatests/src/org/chromium/mojo/bindings/InterfacesTest.java",
"javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java",

@ -1,390 +0,0 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.mojo.bindings;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.build.annotations.NullMarked;
import org.chromium.build.annotations.Nullable;
import org.chromium.mojo.HandleMock;
import org.chromium.mojo.bindings.test.mojom.mojo.StructOfObjects;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithFloat;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithFloat2;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithHandle;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithNestedArray;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithNullables;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithObjectArrays;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithPrimitiveArrays;
import org.chromium.mojo.bindings.test.mojom.mojo.StructWithPrimitives;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionOfObjects;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionWithFloat;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionWithFloat2;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionWithHandle;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionWithObjectArrays;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionWithPrimitiveArrays;
import org.chromium.mojo.bindings.test.mojom.mojo.UnionWithPrimitives;
/**
* Tests for the equals logic of the generated structs/unions, using structs/unions defined in
* mojo/public/interfaces/bindings/tests/equals_test_structs.test-mojom .
*/
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
@NullMarked
@SuppressWarnings("ArgumentSelectionDefectChecker")
public class EqualsTest {
@Test
@SmallTest
public void testStructWithPrimitives() {
StructWithPrimitives first = new StructWithPrimitives();
first.a = 5;
first.b = true;
StructWithPrimitives second = new StructWithPrimitives();
second.a = 5;
second.b = true;
StructWithPrimitives third = new StructWithPrimitives();
third.a = 7;
third.b = false;
StructWithPrimitives fourth = new StructWithPrimitives();
fourth.a = 5;
fourth.b = false;
checkEqualWithHashCode(first, second);
checkEqualWithHashCode(second, first);
checkNotEqual(first, third);
checkNotEqual(second, fourth);
checkNotEqual(first, null);
checkNotEqual(first, new Object());
}
@Test
@SmallTest
public void testStructWithPrimitiveArrays() {
StructWithPrimitiveArrays first = new StructWithPrimitiveArrays();
first.a = new int[] {1, 2, 3, 4};
first.b = new float[] {4.0f, 3.0f, 2.0f, 1.0f};
StructWithPrimitiveArrays second = new StructWithPrimitiveArrays();
second.a = first.a;
second.b = first.b;
StructWithPrimitiveArrays third = new StructWithPrimitiveArrays();
third.a = new int[] {1, 2, 3, 4};
third.b = new float[] {4.0f, 3.0f, 2.0f, 1.0f};
StructWithPrimitiveArrays fourth = new StructWithPrimitiveArrays();
fourth.a = new int[] {1, 4, 3, 2};
fourth.b = new float[] {4.0f, 2.0f, 2.0f, 1.0f};
checkEqualWithHashCode(first, second);
checkEqualWithHashCode(second, first);
checkEqualWithHashCode(first, third);
checkNotEqual(second, fourth);
}
@Test
@SmallTest
public void testStructOfObjects() {
StructWithPrimitives sub = new StructWithPrimitives();
sub.a = 5;
sub.b = true;
StructWithPrimitives sub2 = new StructWithPrimitives();
sub2.a = 5;
sub2.b = true;
StructWithPrimitives sub3 = new StructWithPrimitives();
sub3.a = 7;
sub3.b = false;
StructOfObjects first = new StructOfObjects();
first.a = sub;
StructOfObjects second = new StructOfObjects();
second.a = sub2;
StructOfObjects third = new StructOfObjects();
third.a = sub3;
checkEqualWithHashCode(first, second);
checkEqualWithHashCode(second, first);
checkNotEqual(first, third);
}
@Test
@SmallTest
public void testStructWithObjectArrays() {
StructWithPrimitives[] arr = new StructWithPrimitives[] {new StructWithPrimitives()};
arr[0].a = 7;
StructWithObjectArrays first = new StructWithObjectArrays();
first.a = arr;
StructWithPrimitives[] arr2 = new StructWithPrimitives[] {new StructWithPrimitives()};
arr2[0].a = 5;
StructWithObjectArrays second = new StructWithObjectArrays();
second.a = arr2;
StructWithObjectArrays third = new StructWithObjectArrays();
third.a = arr;
third.b = new Float[] {5.0f};
StructWithObjectArrays fourth = new StructWithObjectArrays();
fourth.a = arr;
fourth.b = new Float[] {null};
checkNotEqual(first, second);
checkNotEqual(third, fourth);
}
@Test
@SmallTest
public void testStructWithHandle() {
StructWithHandle first = new StructWithHandle();
first.a = new HandleMock();
StructWithHandle second = new StructWithHandle();
second.a = first.a;
StructWithHandle third = new StructWithHandle();
third.a = new HandleMock();
checkEqualWithHashCode(first, second);
checkNotEqual(first, third);
}
@Test
@SmallTest
public void testStructWithNullables() {
StructWithNullables first = new StructWithNullables();
first.a = new StructWithPrimitives();
first.a.a = 5;
first.a.b = true;
first.b = "hello world";
first.c = false;
StructWithNullables second = new StructWithNullables();
second.a = new StructWithPrimitives();
second.a.a = 4;
second.a.b = true;
second.b = new String(first.b);
second.c = first.c;
StructWithNullables third = new StructWithNullables();
third.a = first.a;
third.b = null;
third.c = first.c;
StructWithNullables fourth = new StructWithNullables();
fourth.a = first.a;
fourth.b = first.b;
fourth.c = null;
checkNotEqual(first, second);
checkNotEqual(first, third);
checkNotEqual(first, fourth);
}
@Test
@SmallTest
public void testNaNBehavior() {
StructWithFloat first = new StructWithFloat();
first.a = Float.NaN;
StructWithFloat second = new StructWithFloat();
second.a = Float.NaN;
// Check reflexivity
checkEqualWithHashCode(first, first);
checkEqualWithHashCode(first, second);
}
@Test
@SmallTest
public void testFloatZeroBehavior() {
StructWithFloat first = new StructWithFloat();
first.a = -0.0f;
StructWithFloat second = new StructWithFloat();
second.a = 0.0f;
// Check reflexivity
checkEqualWithHashCode(first, first);
checkNotEqual(first, second);
}
@Test
@SmallTest
public void testDifferentStructTypesAreNotEqual() {
StructWithFloat first = new StructWithFloat();
first.a = 5.0f;
StructWithFloat2 second = new StructWithFloat2();
second.a = 5.0f;
checkNotEqual(first, second);
}
@Test
@SmallTest
public void testNestedArrays() {
StructWithNestedArray first = new StructWithNestedArray();
first.a = new int[][] {{0, 1}, {2, 3}};
StructWithNestedArray second = new StructWithNestedArray();
second.a = new int[][] {{0, 1}, {2, 3}};
StructWithNestedArray third = new StructWithNestedArray();
third.a = new int[][] {{0, 1}, {2, 4}};
checkEqualWithHashCode(first, second);
checkNotEqual(first, third);
}
@Test
@SmallTest
public void testUnionWithPrimitives() {
UnionWithPrimitives first = new UnionWithPrimitives();
first.setA((byte) 5);
first.setB(true);
UnionWithPrimitives second = new UnionWithPrimitives();
second.setA((byte) 5);
second.setB(true);
UnionWithPrimitives third = new UnionWithPrimitives();
third.setA((byte) 7);
third.setB(false);
// Check that unions with different tags aren't equal.
UnionWithPrimitives fourth = new UnionWithPrimitives();
fourth.setB(true);
fourth.setA((byte) 5);
checkEqualWithHashCode(first, second);
checkEqualWithHashCode(second, first);
checkNotEqual(first, third);
checkNotEqual(first, fourth);
checkNotEqual(first, null);
checkNotEqual(first, new Object());
}
@Test
@SmallTest
public void testUnionWithPrimitiveArrays() {
UnionWithPrimitiveArrays first = new UnionWithPrimitiveArrays();
first.setA(new int[] {1, 2, 3, 4});
UnionWithPrimitiveArrays second = new UnionWithPrimitiveArrays();
second.setA(first.getA());
UnionWithPrimitiveArrays third = new UnionWithPrimitiveArrays();
third.setA(new int[] {1, 2, 3, 4});
UnionWithPrimitiveArrays fourth = new UnionWithPrimitiveArrays();
fourth.setA(new int[] {1, 2, 4, 5});
checkEqualWithHashCode(first, second);
checkEqualWithHashCode(third, first);
checkNotEqual(first, fourth);
}
@Test
@SmallTest
public void testUnionOfObjects() {
StructWithPrimitives sub = new StructWithPrimitives();
sub.a = 5;
sub.b = true;
StructWithPrimitives sub2 = new StructWithPrimitives();
sub2.a = 5;
sub2.b = true;
StructWithPrimitives sub3 = new StructWithPrimitives();
sub3.a = 7;
sub3.b = false;
UnionOfObjects first = new UnionOfObjects();
first.setA(sub);
UnionOfObjects second = new UnionOfObjects();
second.setA(sub2);
UnionOfObjects third = new UnionOfObjects();
third.setA(sub3);
checkEqualWithHashCode(first, second);
checkEqualWithHashCode(second, first);
checkNotEqual(first, third);
}
@Test
@SmallTest
public void testUnionWithObjectArrays() {
UnionWithPrimitives[] arr = new UnionWithPrimitives[] {new UnionWithPrimitives()};
arr[0].setA((byte) 7);
UnionWithObjectArrays first = new UnionWithObjectArrays();
first.setA(arr);
UnionWithPrimitives[] arr2 = new UnionWithPrimitives[] {new UnionWithPrimitives()};
arr2[0].setA((byte) 7);
UnionWithObjectArrays second = new UnionWithObjectArrays();
second.setA(arr2);
UnionWithPrimitives[] arr3 = new UnionWithPrimitives[] {new UnionWithPrimitives()};
arr3[0].setA((byte) 5);
UnionWithObjectArrays third = new UnionWithObjectArrays();
third.setA(arr3);
checkEqualWithHashCode(first, second);
checkNotEqual(first, third);
}
@Test
@SmallTest
public void testUnionWithHandle() {
UnionWithHandle first = new UnionWithHandle();
first.setA(new HandleMock());
UnionWithHandle second = new UnionWithHandle();
second.setA(first.getA());
UnionWithHandle third = new UnionWithHandle();
third.setA(new HandleMock());
checkEqualWithHashCode(first, second);
checkNotEqual(first, third);
}
@Test
@SmallTest
public void testNaNBehaviorForUnions() {
UnionWithFloat first = new UnionWithFloat();
first.setA(Float.NaN);
UnionWithFloat second = new UnionWithFloat();
second.setA(Float.NaN);
// Check reflexivity
checkEqualWithHashCode(first, first);
checkEqualWithHashCode(first, second);
}
@Test
@SmallTest
public void testFloatZeroBehaviorForUnions() {
UnionWithFloat first = new UnionWithFloat();
first.setA(-0.0f);
UnionWithFloat second = new UnionWithFloat();
second.setA(0.0f);
// Check reflexivity
checkEqualWithHashCode(first, first);
checkNotEqual(first, second);
}
@Test
@SmallTest
public void testDifferentUnionTypesAreNotEqual() {
UnionWithFloat first = new UnionWithFloat();
first.setA(5.0f);
UnionWithFloat2 second = new UnionWithFloat2();
second.setA(5.0f);
checkNotEqual(first, second);
}
@Test
@SmallTest
public void testActiveVariantIsCompared() {
UnionWithPrimitives first = new UnionWithPrimitives();
first.setA((byte) 5);
first.setB(true);
UnionWithPrimitives second = new UnionWithPrimitives();
second.setA((byte) 999999);
second.setB(true);
checkEqualWithHashCode(second, first);
}
private void checkEqualWithHashCode(Object first, @Nullable Object second) {
Assert.assertTrue(first.equals(second));
Assert.assertEquals(first.hashCode(), second.hashCode());
}
private void checkNotEqual(Object first, @Nullable Object second) {
Assert.assertFalse(first.equals(second));
}
}

@ -270,53 +270,6 @@ if ({{variable}} != null) {
{%- endfor %}
{%- endfor %}
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!(o instanceof {{struct|name}})) {
return false;
}
{%- if struct.fields %}
{{struct|name}} other = ({{struct|name}}) o;
{%- endif %}
{%- for field in struct.fields %}
{#- Covers the case for Object[] (for which we want to check deepEquals). #}
{#- We also want to box and use deepEquals for float arrays due to NaN logic. #}
{%- if field.kind|is_pointer_array_kind or field.kind|is_union_array_kind or (field.kind|is_array_kind and field.kind.kind|is_nullable_kind) %}
if (!Arrays.deepEquals(this.{{field|name}}, other.{{field|name}})) {
return false;
}
{#- Covers the case for primitive arrays (equals suffices). #}
{%- elif field.kind|is_array_kind %}
if (!Arrays.equals(this.{{field|name}}, other.{{field|name}})) {
return false;
}
{#- Covers the case for object fields and floats (which we box for NaN logic). #}
{%- elif field.kind|is_object_kind or field.kind|is_float_kind %}
if (!Objects.equals(this.{{field|name}}, other.{{field|name}})) {
return false;
}
{#- Covers the case for primitive fields. #}
{%- else %}
if (this.{{field|name}} != other.{{field|name}}) {
return false;
}
{%- endif %}
{% endfor %}
return true;
}
@Override
public int hashCode() {
return Arrays.deepHashCode(new Object[] {
{%- for field in struct.fields %}
this.{{field|name}},
{%- endfor %}
});
}
}
{%- endmacro %}
@ -407,51 +360,5 @@ public final class {{union|name}} extends org.chromium.mojo.bindings.Union {
}
return result;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!(o instanceof {{union|name}})) {
return false;
}
{{union|name}} other = ({{union|name}}) o;
if (mTag != other.mTag) {
return false;
}
switch (mTag) {
{%- for field in union.fields %}
case Tag.{{field|ucc}}: {
{#- Covers the case for Object[] (for which we want to check deepEquals). #}
{%- if field.kind|is_pointer_array_kind or field.kind|is_union_array_kind or (field.kind|is_array_kind and field.kind.kind|is_nullable_kind) %}
return Arrays.deepEquals(this.m{{field|ucc}}, other.m{{field|ucc}});
{#- Covers the case for primitive arrays (equals suffices). #}
{%- elif field.kind|is_array_kind %}
return Arrays.equals(this.m{{field|ucc}}, other.m{{field|ucc}});
{#- Covers the case for object fields and floats (which we box for NaN logic). #}
{%- elif field.kind|is_object_kind or field.kind|is_float_kind %}
return Objects.equals(this.m{{field|ucc}}, other.m{{field|ucc}});
{#- Covers the case for primitive fields. #}
{%- else %}
return this.m{{field|ucc}} == other.m{{field|ucc}};
{%- endif %}
}
{%- endfor %}
}
throw new RuntimeException("Could not find tagged field on union.");
}
@Override
public int hashCode() {
switch (mTag) {
{%- for field in union.fields %}
case Tag.{{field|ucc}}: {
return Arrays.deepHashCode(new Object[] {this.mTag, this.m{{field|ucc}}});
}
{%- endfor %}
}
throw new RuntimeException("Could not find tagged field on union.");
}
}
{%- endmacro %}

@ -11,9 +11,5 @@
package {{package}};
import androidx.annotation.IntDef;
import org.chromium.build.annotations.NullMarked;
import org.chromium.build.annotations.Nullable;
import java.util.Arrays;
import java.util.Objects;

@ -538,12 +538,10 @@ class Generator(generator.Generator):
'is_bool_kind': mojom.IsBoolKind,
'is_any_handle_kind': mojom.IsAnyHandleKind,
"is_enum_kind": mojom.IsEnumKind,
"is_float_kind": mojom.IsFloatKind,
'is_map_kind': mojom.IsMapKind,
'is_nullable_kind': mojom.IsNullableKind,
"is_nullable_value_kind_packed_field":
pack.IsNullableValueKindPackedField,
"is_object_kind": mojom.IsObjectKind,
"is_primary_nullable_value_kind_packed_field":
pack.IsPrimaryNullableValueKindPackedField,
'is_pointer_array_kind': IsPointerArrayKind,