Skip to content

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

Closed
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions