Skip to content

Commit 7f7d41d

Browse files
committed
fix graalvm for abstrct array/enum
1 parent 40646a4 commit 7f7d41d

6 files changed

Lines changed: 381 additions & 4 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.fory.graalvm;
21+
22+
import java.util.Arrays;
23+
import java.util.Objects;
24+
import org.apache.fory.Fory;
25+
import org.apache.fory.util.Preconditions;
26+
27+
/**
28+
* Test for abstract class corner cases in GraalVM native image. This tests the fix for issue
29+
* #2695: Abstract enums (enums with abstract methods) and arrays of abstract types.
30+
*/
31+
public class AbstractClassExample {
32+
33+
// Abstract enum with abstract methods - each enum value is an anonymous inner class
34+
public enum AbstractEnum {
35+
VALUE1 {
36+
@Override
37+
public int getValue() {
38+
return 1;
39+
}
40+
},
41+
VALUE2 {
42+
@Override
43+
public int getValue() {
44+
return 2;
45+
}
46+
};
47+
48+
public abstract int getValue();
49+
}
50+
51+
// Abstract base class for testing abstract object arrays
52+
public abstract static class AbstractBase {
53+
public int id;
54+
55+
public abstract String getType();
56+
57+
@Override
58+
public boolean equals(Object o) {
59+
if (this == o) return true;
60+
if (o == null || getClass() != o.getClass()) return false;
61+
AbstractBase that = (AbstractBase) o;
62+
return id == that.id;
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
return Objects.hash(id);
68+
}
69+
}
70+
71+
// Concrete implementation 1
72+
public static class ConcreteA extends AbstractBase {
73+
public String name;
74+
75+
@Override
76+
public String getType() {
77+
return "A";
78+
}
79+
80+
@Override
81+
public boolean equals(Object o) {
82+
if (this == o) return true;
83+
if (o == null || getClass() != o.getClass()) return false;
84+
if (!super.equals(o)) return false;
85+
ConcreteA concreteA = (ConcreteA) o;
86+
return Objects.equals(name, concreteA.name);
87+
}
88+
89+
@Override
90+
public int hashCode() {
91+
return Objects.hash(super.hashCode(), name);
92+
}
93+
}
94+
95+
// Concrete implementation 2
96+
public static class ConcreteB extends AbstractBase {
97+
public long value;
98+
99+
@Override
100+
public String getType() {
101+
return "B";
102+
}
103+
104+
@Override
105+
public boolean equals(Object o) {
106+
if (this == o) return true;
107+
if (o == null || getClass() != o.getClass()) return false;
108+
if (!super.equals(o)) return false;
109+
ConcreteB concreteB = (ConcreteB) o;
110+
return value == concreteB.value;
111+
}
112+
113+
@Override
114+
public int hashCode() {
115+
return Objects.hash(super.hashCode(), value);
116+
}
117+
}
118+
119+
// Container class that holds abstract enum and abstract array
120+
public static class Container {
121+
public AbstractEnum enumValue;
122+
public AbstractEnum[] enumArray;
123+
public AbstractBase[] baseArray;
124+
125+
@Override
126+
public boolean equals(Object o) {
127+
if (this == o) return true;
128+
if (o == null || getClass() != o.getClass()) return false;
129+
Container container = (Container) o;
130+
return enumValue == container.enumValue
131+
&& Arrays.equals(enumArray, container.enumArray)
132+
&& Arrays.equals(baseArray, container.baseArray);
133+
}
134+
135+
@Override
136+
public int hashCode() {
137+
int result = Objects.hash(enumValue);
138+
result = 31 * result + Arrays.hashCode(enumArray);
139+
result = 31 * result + Arrays.hashCode(baseArray);
140+
return result;
141+
}
142+
}
143+
144+
private static final Fory FORY;
145+
146+
static {
147+
FORY =
148+
Fory.builder()
149+
.withName(AbstractClassExample.class.getName())
150+
.registerGuavaTypes(false)
151+
.build();
152+
// Register enum type - abstract enums need to be registered
153+
// The fix for issue #2695 ensures that registering an abstract enum
154+
// also registers its inner classes (the anonymous enum value classes)
155+
FORY.register(AbstractEnum.class);
156+
// Register concrete types
157+
FORY.register(ConcreteA.class);
158+
FORY.register(ConcreteB.class);
159+
FORY.register(Container.class);
160+
// Register array types - the abstract component type should be handled correctly
161+
FORY.register(AbstractBase[].class);
162+
FORY.register(AbstractEnum[].class);
163+
// Ensure serializers are compiled - this is where the fix for issue #2695 matters
164+
FORY.ensureSerializersCompiled();
165+
}
166+
167+
public static void main(String[] args) {
168+
FORY.reset();
169+
170+
// Test abstract enum serialization
171+
testAbstractEnum();
172+
173+
// Test abstract enum array serialization
174+
testAbstractEnumArray();
175+
176+
// Test abstract object array serialization
177+
testAbstractObjectArray();
178+
179+
// Test container with abstract types
180+
testContainer();
181+
182+
System.out.println("AbstractClassExample succeed");
183+
}
184+
185+
private static void testAbstractEnum() {
186+
byte[] bytes1 = FORY.serialize(AbstractEnum.VALUE1);
187+
AbstractEnum result1 = FORY.deserialize(bytes1, AbstractEnum.class);
188+
Preconditions.checkArgument(result1 == AbstractEnum.VALUE1, "VALUE1 should match");
189+
Preconditions.checkArgument(result1.getValue() == 1, "VALUE1.getValue() should be 1");
190+
191+
byte[] bytes2 = FORY.serialize(AbstractEnum.VALUE2);
192+
AbstractEnum result2 = FORY.deserialize(bytes2, AbstractEnum.class);
193+
Preconditions.checkArgument(result2 == AbstractEnum.VALUE2, "VALUE2 should match");
194+
Preconditions.checkArgument(result2.getValue() == 2, "VALUE2.getValue() should be 2");
195+
}
196+
197+
private static void testAbstractEnumArray() {
198+
AbstractEnum[] array = new AbstractEnum[] {AbstractEnum.VALUE1, AbstractEnum.VALUE2};
199+
byte[] bytes = FORY.serialize(array);
200+
AbstractEnum[] result = FORY.deserialize(bytes, AbstractEnum[].class);
201+
Preconditions.checkArgument(Arrays.equals(array, result), "Enum arrays should match");
202+
Preconditions.checkArgument(result[0].getValue() == 1, "result[0].getValue() should be 1");
203+
Preconditions.checkArgument(result[1].getValue() == 2, "result[1].getValue() should be 2");
204+
}
205+
206+
private static void testAbstractObjectArray() {
207+
ConcreteA a = new ConcreteA();
208+
a.id = 1;
209+
a.name = "test";
210+
211+
ConcreteB b = new ConcreteB();
212+
b.id = 2;
213+
b.value = 100L;
214+
215+
AbstractBase[] array = new AbstractBase[] {a, b};
216+
byte[] bytes = FORY.serialize(array);
217+
AbstractBase[] result = FORY.deserialize(bytes, AbstractBase[].class);
218+
219+
Preconditions.checkArgument(result.length == 2, "Array length should be 2");
220+
Preconditions.checkArgument(result[0] instanceof ConcreteA, "result[0] should be ConcreteA");
221+
Preconditions.checkArgument(result[1] instanceof ConcreteB, "result[1] should be ConcreteB");
222+
Preconditions.checkArgument(result[0].equals(a), "result[0] should equal a");
223+
Preconditions.checkArgument(result[1].equals(b), "result[1] should equal b");
224+
Preconditions.checkArgument(
225+
"A".equals(result[0].getType()), "result[0].getType() should be 'A'");
226+
Preconditions.checkArgument(
227+
"B".equals(result[1].getType()), "result[1].getType() should be 'B'");
228+
}
229+
230+
private static void testContainer() {
231+
ConcreteA a = new ConcreteA();
232+
a.id = 10;
233+
a.name = "containerTest";
234+
235+
ConcreteB b = new ConcreteB();
236+
b.id = 20;
237+
b.value = 200L;
238+
239+
Container container = new Container();
240+
container.enumValue = AbstractEnum.VALUE1;
241+
container.enumArray = new AbstractEnum[] {AbstractEnum.VALUE2, AbstractEnum.VALUE1};
242+
container.baseArray = new AbstractBase[] {a, b};
243+
244+
byte[] bytes = FORY.serialize(container);
245+
Container result = FORY.deserialize(bytes, Container.class);
246+
247+
Preconditions.checkArgument(container.equals(result), "Container should match");
248+
Preconditions.checkArgument(
249+
result.enumValue.getValue() == 1, "container.enumValue.getValue() should be 1");
250+
}
251+
}

integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static void main(String[] args) throws Throwable {
4040
Benchmark.main(args);
4141
CollectionExample.main(args);
4242
ArrayExample.main(args);
43+
AbstractClassExample.main(args);
4344
FeatureTestExample.main(args);
4445
}
4546
}

integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Args=-H:+ReportExceptionStackTraces \
3232
org.apache.fory.graalvm.EnsureSerializerExample$CustomSerializer,\
3333
org.apache.fory.graalvm.CollectionExample,\
3434
org.apache.fory.graalvm.ArrayExample,\
35+
org.apache.fory.graalvm.AbstractClassExample,\
3536
org.apache.fory.graalvm.Benchmark,\
3637
org.apache.fory.graalvm.FeatureTestExample,\
3738
org.apache.fory.graalvm.FeatureTestExample$PrivateConstructorClass,\

java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,19 @@ public void register(Class<?> cls) {
433433
}
434434
register(cls, extRegistry.userIdGenerator);
435435
}
436+
// For abstract enums (enums with abstract methods), also register the inner classes
437+
// that implement the enum constants
438+
if (cls.isEnum()) {
439+
for (Object enumConstant : cls.getEnumConstants()) {
440+
Class<?> enumValueClass = enumConstant.getClass();
441+
// If the enum constant's class is different from the enum class itself,
442+
// it's an anonymous inner class that needs to be registered
443+
if (enumValueClass != cls
444+
&& !extRegistry.registeredClassIdMap.containsKey(enumValueClass)) {
445+
register(enumValueClass);
446+
}
447+
}
448+
}
436449
}
437450

438451
/**
@@ -1958,17 +1971,22 @@ public void ensureSerializersCompiled() {
19581971
createSerializer0(cls);
19591972
}
19601973
if (cls.isArray()) {
1961-
// Also create serializer for the component type
1962-
createSerializer0(TypeUtils.getArrayComponent(cls));
1974+
// Also create serializer for the component type if it's serializable
1975+
Class<?> componentType = TypeUtils.getArrayComponent(cls);
1976+
if (isSerializable(componentType)) {
1977+
createSerializer0(componentType);
1978+
}
19631979
}
19641980
}
19651981
// Always ensure array class serializers and their component type serializers
19661982
// are registered in GraalVM registry, since ObjectArraySerializer needs
19671983
// the component type serializer at construction time
19681984
if (cls.isArray() && GraalvmSupport.isGraalBuildtime()) {
1969-
// First ensure component type serializer is registered
1985+
// First ensure component type serializer is registered if it's serializable
19701986
Class<?> componentType = TypeUtils.getArrayComponent(cls);
1971-
createSerializer0(componentType);
1987+
if (isSerializable(componentType)) {
1988+
createSerializer0(componentType);
1989+
}
19721990
// Then register the array serializer
19731991
createSerializer0(cls);
19741992
}

java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,10 @@ final Class<?> loadClass(
481481
public abstract ClassDef getTypeDef(Class<?> cls, boolean resolveParent);
482482

483483
public final boolean isSerializable(Class<?> cls) {
484+
// Enums are always serializable, even if abstract (enums with abstract methods)
485+
if (cls.isEnum()) {
486+
return true;
487+
}
484488
if (ReflectionUtils.isAbstract(cls) || cls.isInterface()) {
485489
return false;
486490
}

0 commit comments

Comments
 (0)