Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
lingjun-cg opened this issue Apr 21, 2021 · 2 comments · Fixed by #824
Labels

Comments

@lingjun-cg
Copy link
Contributor

lingjun-cg commented Apr 21, 2021

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.

@theigl
Copy link
Collaborator

theigl commented Apr 21, 2021

@lingjun-cg: Good catch and thanks for the test-case! This is indeed a bug.

I'm wondering why our existing tests didn't catch this. I'll push a fix for this shortly.

@theigl
Copy link
Collaborator

theigl commented Apr 21, 2021

I created a PR with a much simpler test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging a pull request may close this issue.

2 participants