Skip to content

Commit 7233209

Browse files
authored
Add better feedback when using abstract classes in serialization (#1179)
* Add better feedback when using abstract class in serialization * Add one more check for abstract class * Add check for primitive * Add check for array class * fix: Remove abstract class check from hot path, add a new test for abstract class serialization and add more informative error message in defaultInstantiatorStrategy * remove dev logs * Add more tests regarding abstract classes and add more feedback for InstantionError * Clean up the Tests and move creation of exceptions into its own method * remove unnecessary import * Apply formatter and add license header to DefaultInstantiatorStrategyTest
1 parent 22a6495 commit 7233209

File tree

2 files changed

+96
-8
lines changed

2 files changed

+96
-8
lines changed

src/com/esotericsoftware/kryo/util/DefaultInstantiatorStrategy.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/* Copyright (c) 2008-2023, Nathan Sweet
22
* All rights reserved.
3-
*
3+
*
44
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
55
* conditions are met:
6-
*
6+
*
77
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
88
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
99
* disclaimer in the documentation and/or other materials provided with the distribution.
1010
* - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
1111
* from this software without specific prior written permission.
12-
*
12+
*
1313
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
1414
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
1515
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
@@ -49,6 +49,7 @@ public InstantiatorStrategy getFallbackInstantiatorStrategy () {
4949
}
5050

5151
public ObjectInstantiator newInstantiatorOf (final Class type) {
52+
5253
if (!Util.isAndroid) {
5354
// Use ReflectASM if the class is not a non-static member class.
5455
Class enclosingType = type.getEnclosingClass();
@@ -61,8 +62,8 @@ public ObjectInstantiator newInstantiatorOf (final Class type) {
6162
public Object newInstance () {
6263
try {
6364
return access.newInstance();
64-
} catch (Exception ex) {
65-
throw new KryoException("Error constructing instance of class: " + className(type), ex);
65+
} catch (Exception | InstantiationError ex) {
66+
throw createInstantiationError(type, ex);
6667
}
6768
}
6869
};
@@ -86,17 +87,17 @@ public Object newInstance () {
8687
try {
8788
return constructor.newInstance();
8889
} catch (Exception ex) {
89-
throw new KryoException("Error constructing instance of class: " + className(type), ex);
90+
throw createInstantiationError(type, ex);
9091
}
9192
}
9293
};
9394
} catch (Exception ignored) {
9495
}
9596

9697
if (fallbackStrategy == null) {
97-
if (type.isMemberClass() && !Modifier.isStatic(type.getModifiers()))
98+
if (type.isMemberClass() && !Modifier.isStatic(type.getModifiers())) {
9899
throw new KryoException("Class cannot be created (non-static member class): " + className(type));
99-
else {
100+
} else {
100101
StringBuilder message = new StringBuilder("Class cannot be created (missing no-arg constructor): " + className(type));
101102
if (type.getSimpleName().equals("")) {
102103
message
@@ -108,10 +109,29 @@ public Object newInstance () {
108109
.append("2. Register a FieldSerializer for the containing class and call FieldSerializer\n")
109110
.append("setIgnoreSyntheticFields(false) on it. This is not safe but may be sufficient temporarily.");
110111
}
112+
113+
if (type.isInterface()) {
114+
message.append(
115+
"\nNote: The type you are trying to serialize into is abstract (interface). Kryo will not be able to create an instance of it. Possible solutions:\n")
116+
.append(
117+
"You can either use a class that implements the interface or use a custom ObjectInstantiator to create an instance.");
118+
}
119+
111120
throw new KryoException(message.toString());
112121
}
113122
}
114123
// InstantiatorStrategy.
115124
return fallbackStrategy.newInstantiatorOf(type);
116125
}
126+
127+
private KryoException createInstantiationError (Class type, Throwable throwable) {
128+
StringBuilder message = new StringBuilder("Error constructing instance of class: " + className(type));
129+
// Note: For Array and Primitive types the abstract bit is always set.
130+
if (!type.isArray() && !type.isPrimitive() && Modifier.isAbstract(type.getModifiers())) {
131+
message.append(
132+
"\nNote: The type you are trying to serialize into is abstract. Kryo will not be able to create an instance of it. Possible solutions:\n")
133+
.append("You can either use a concrete subclass or use a custom ObjectInstantiator to create an instance.");
134+
}
135+
return new KryoException(message.toString(), throwable);
136+
}
117137
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* Copyright (c) 2025, Daniel Pavlov
2+
* All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
5+
* conditions are met:
6+
*
7+
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8+
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
9+
* disclaimer in the documentation and/or other materials provided with the distribution.
10+
* - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
11+
* from this software without specific prior written permission.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
14+
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
15+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
17+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
18+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
19+
20+
package com.esotericsoftware.kryo;
21+
22+
import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
23+
import org.junit.jupiter.api.Test;
24+
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
28+
29+
public class DefaultInstantiatorStrategyTest {
30+
31+
DefaultInstantiatorStrategy instantiatorStrategy = new DefaultInstantiatorStrategy();
32+
33+
@Test
34+
public void testAbstractStaticMemberClassCannotBeInstantiated() {
35+
KryoException thrown = assertThrows(KryoException.class, () -> tryInstantiate(AbstractStaticMemberClass.class));
36+
assertTrue(thrown.getMessage().contains("The type you are trying to serialize into is abstract."));
37+
}
38+
39+
@Test
40+
public void testInterfaceMemberClassCannotBeInstantiated() {
41+
KryoException thrown = assertThrows(KryoException.class, () -> tryInstantiate(MemberInterface.class));
42+
assertTrue(thrown.getMessage().contains("The type you are trying to serialize into is abstract (interface)."));
43+
}
44+
45+
@Test
46+
public void testAbstractClassCannotBeInstantiated() {
47+
KryoException thrown = assertThrows(KryoException.class, () -> tryInstantiate(AbstracClass.class));
48+
assertTrue(thrown.getMessage().contains("The type you are trying to serialize into is abstract."));
49+
}
50+
51+
@Test
52+
public void testInterfaceClassCannotBeInstantiated() {
53+
KryoException thrown = assertThrows(KryoException.class, () -> tryInstantiate(InterfaceClass.class));
54+
assertTrue(thrown.getMessage().contains("The type you are trying to serialize into is abstract (interface)."));
55+
}
56+
57+
public void tryInstantiate(Class type) {
58+
instantiatorStrategy.newInstantiatorOf(type).newInstance();
59+
}
60+
61+
private static abstract class AbstractStaticMemberClass {}
62+
63+
private interface MemberInterface {}
64+
}
65+
66+
abstract class AbstracClass {}
67+
68+
interface InterfaceClass {}

0 commit comments

Comments
 (0)