Closed
Description
Describe the bug
java.lang.ArrayIndexOutOfBoundsException: 3
at com.esotericsoftware.kryo.kryo5.serializers.CompatibleFieldSerializer.readFields(CompatibleFieldSerializer.java:229)
at com.esotericsoftware.kryo.kryo5.serializers.CompatibleFieldSerializer.read(CompatibleFieldSerializer.java:123)
at com.esotericsoftware.kryo.kryo5.Kryo.readClassAndObject(Kryo.java:810)
com.esotericsoftware.kryo.kryo5.serializers.CompatibleFieldSerializer.readFields implement a binary search, but there is a bug of the implementation of binary search algorithm.
int low, mid, high, compare;
int lastFieldIndex = allFields.length;
outer:
for (int i = 0; i < length; i++) {
String schemaName = names[i];
low = 0;
high = lastFieldIndex; // should use lastFieldIndex - 1
while (low <= high) {
mid = (low + high) >>> 1;
compare = schemaName.compareTo(allFields[mid].name);
if (compare < 0)
high = mid - 1;
else if (compare > 0)
low = mid + 1;
else {
fields[i] = allFields[mid];
continue outer;
}
}
To Reproduce
The following code simulate the object layout is different with serialize side and deserialize side.
@Test
public void testKryoCompatibleReadWriteSideDiffOnlyDefaultType() throws ClassNotFoundException, IllegalAccessException, InstantiationException, IOException {
MemoryClassLoader writeClassLoader = new MemoryClassLoader(compile("MyObject.java", WRITE_MYOBJECT_CLASS), MemoryClassLoader.class.getClassLoader());
Class writeClass = writeClassLoader.findClass("MyObject");
Object writeObject = writeClass.newInstance();
Kryo writeKryo = new Kryo();
writeKryo.setReferences(true);
writeKryo.setRegistrationRequired(false);
writeKryo.setDefaultSerializer(new SerializerFactory.CompatibleFieldSerializerFactory());
setValueForDefaultType(writeObject);
Output output1 = new Output(4096);
writeKryo.writeClassAndObject(output1, writeObject);
output1.close();
MemoryClassLoader readClassLoader = new MemoryClassLoader(compile("MyObject.java", READ_MYOBJECT_CLASS), MemoryClassLoader.class.getClassLoader());
Class readClass = readClassLoader.findClass("MyObject");
assertTrue(readClass != writeClass);
Kryo readKryo = new Kryo();
readKryo.setRegistrationRequired(false);
readKryo.setReferences(true);
readKryo.setClassLoader(readClassLoader);
readKryo.setDefaultSerializer(new SerializerFactory.CompatibleFieldSerializerFactory());
Input input1 = new Input(output1.getBuffer(), 0, output1.position());
Object readObject1 = readKryo.readClassAndObject(input1);
input1.close();
}
private void setValueForDefaultType(Object o) throws IllegalAccessException, MalformedURLException {
writeField(o, "z1", true,true);//boolean
writeDeclaredField(o, "z2", Boolean.TRUE,true);//Boolean
writeDeclaredField(o, "b1", (byte) 245,true);//byte
writeDeclaredField(o, "b2", Byte.valueOf((byte) 120),true);//Byte
writeDeclaredField(o, "c1", 'x',true);//char
writeDeclaredField(o, "c2", Character.valueOf('~'),true);//Character
writeDeclaredField(o, "s1", (short)8812,true);//short
writeDeclaredField(o, "s2", Short.valueOf((short) 123),true);//Short
writeDeclaredField(o, "i1", 123,true);//int
writeDeclaredField(o, "i2", Integer.valueOf(12312),true);//Integer
writeDeclaredField(o, "l1", 13210313103311L,true);//long
writeDeclaredField(o, "l2", Long.valueOf(1231313131313808L),true);//Long
writeDeclaredField(o, "f1", 0.12345678f,true);//float
writeDeclaredField(o, "f2", Float.valueOf(1231.1313f),true);//Float
writeDeclaredField(o, "d1", 13131.99912,true);//double
writeDeclaredField(o, "d2", Double.valueOf(131313.8899),true);//Double
writeDeclaredField(o, "string1", "String1",true);//String
writeDeclaredField(o, "bigInteger1", new BigInteger("1321310381038103810"),true);//BigInteger
writeDeclaredField(o, "bigDecimal1", new BigDecimal("1310230424204"),true);//BigDecimal
writeDeclaredField(o, "date1", new Date(),true);//Date
writeDeclaredField(o, "timestamp1", new Timestamp(System.currentTimeMillis()),true);//Timestamp
writeDeclaredField(o, "javaSqlDate1", new java.sql.Date(System.currentTimeMillis()),true);//java.sql.Date
writeDeclaredField(o, "time1", new Time(System.currentTimeMillis()),true);//Time
writeDeclaredField(o, "currency1", Currency.getInstance(Locale.getDefault()),true);//Currency
writeDeclaredField(o, "stringBuffer1", new StringBuffer("stringBuffer1"),true);//StringBuffer
writeDeclaredField(o, "stringBuilder1", new StringBuilder("stringBuilder1"),true);//StringBuilder
writeDeclaredField(o, "timezone1", TimeZone.getDefault(),true);//TimeZone
writeDeclaredField(o, "calendar1", Calendar.getInstance(),true);//Calendar
writeDeclaredField(o, "locale1", Locale.getDefault(),true);//Locale
writeDeclaredField(o, "charset1", Charset.forName("UTF-8"),true);//Charset
writeDeclaredField(o, "url1", new URL("http://www.xyz.com"),true);//URL
writeDeclaredField(o, "common1",(byte) 0x99,true);//byte
writeDeclaredField(o, "common2", 1231381203812038L,true);//Long
writeDeclaredField(o, "common3", "common3stringcontent",true);//String
}
private Map<String, byte[]> compile(String fileName, String source) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
Boolean result = task.call();
if (result == null || !result.booleanValue()) {
throw new RuntimeException("Compilation failed.");
}
return manager.getClassBytes();
}
}
private static final String WRITE_MYOBJECT_CLASS =
"import java.util.*;"
+ "import java.math.BigDecimal;"
+ "import java.math.BigInteger;"
+ "import java.net.URL;"
+ "import java.nio.charset.Charset;"
+ "import java.sql.Time;"
+ "import java.sql.Timestamp;"
+ "public class MyObject{"
+ "boolean z1;"
+ "Boolean z2;"
+ "byte b1;"
+ "Byte b2;"
+ "char c1;"
+ "Character c2;"
+ "short s1;"
+ "Short s2;"
+ "int i1;"
+ "Integer i2;"
+ "long l1;"
+ "Long l2;"
+ "float f1;"
+ "Float f2;"
+ "double d1;"
+ "Double d2;"
+ "String string1;"
+ "BigInteger bigInteger1;"
+ "BigDecimal bigDecimal1;"
+ "Date date1;"
+ "Timestamp timestamp1;"
+ "java.sql.Date javaSqlDate1;"
+ "Time time1;"
+ "Currency currency1;"
+ "StringBuffer stringBuffer1;"
+ "StringBuilder stringBuilder1;"
+ "TimeZone timezone1;"
+ "Calendar calendar1;"
+ "Locale locale1;"
+ "Charset charset1;"
+ "URL url1;"
+ "byte common1;"
+ "Long common2;"
+ "String common3;"
+ "}";
private static final String READ_MYOBJECT_CLASS = "import java.util.*;"
+ "import java.math.BigDecimal;"
+ "import java.math.BigInteger;"
+ "import java.net.URL;"
+ "import java.nio.charset.Charset;"
+ "import java.sql.Time;"
+ "import java.sql.Timestamp;"
+ "public class MyObject{"
+ "byte common1;"
+ "Long common2;"
+ "String common3;"
+ "}";
public class MemoryClassLoader extends URLClassLoader {
Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
public MemoryClassLoader(Map<String, byte[]> classBytes,ClassLoader parent) {
super(new URL[0],parent);
this.classBytes = classBytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
if (buf == null) {
return super.findClass(name);
}
classBytes.remove(name);
return defineClass(name, buf, 0, buf.length);
}
}
Environment:
- OS: [e.g. Ubuntu]
- JDK Version: [e.g. 11]
- Kryo Version: [e.g. 5.1.0]
Additional context
Add any other context about the problem here.