Skip to content

com.esotericsoftware.kryo.kryo5.serializers.CompatibleFieldSerializer.readFields thrown java.lang.ArrayIndexOutOfBoundsException #823

@lingjun-cg

Description

@lingjun-cg

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions