Skip to content

Commit

Permalink
Fixes BigDecimal deserialization (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
dormidon committed Jan 12, 2023
1 parent 6d68aab commit 264664e
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 33 deletions.
5 changes: 2 additions & 3 deletions src/one/nio/serial/GeneratedSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package one.nio.serial;

import one.nio.gen.BytecodeGenerator;
import one.nio.serial.gen.Delegate;
import one.nio.serial.gen.DelegateGenerator;
import one.nio.serial.gen.StubGenerator;
Expand Down Expand Up @@ -55,7 +54,7 @@ public class GeneratedSerializer extends Serializer {
this.defaultFields = new FieldDescriptor[0];

checkFieldTypes();
this.delegate = BytecodeGenerator.INSTANCE.instantiate(code(), Delegate.class);
this.delegate = DelegateGenerator.instantiate(cls, fds, code());
}

@Override
Expand Down Expand Up @@ -93,7 +92,7 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept
this.defaultFields = assignDefaultFields(ownFields);

checkFieldTypes();
this.delegate = BytecodeGenerator.INSTANCE.instantiate(code(), Delegate.class);
this.delegate = DelegateGenerator.instantiate(cls, fds, code());
}

@Override
Expand Down
3 changes: 3 additions & 0 deletions src/one/nio/serial/Repository.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.*;
import java.nio.file.Files;
Expand Down Expand Up @@ -62,6 +63,7 @@ public class Repository {
public static final int INLINE = 4;
public static final int FIELD_SERIALIZATION = 8;
public static final int SYNTHETIC_FIELDS = 16;
public static final int PROVIDE_GET_FIELD = 32;

public static final int ARRAY_STUBS = 1;
public static final int COLLECTION_STUBS = 2;
Expand Down Expand Up @@ -150,6 +152,7 @@ public class Repository {
setOptions(StringBuilder.class, SKIP_CUSTOM_SERIALIZATION);
setOptions(StringBuffer.class, SKIP_CUSTOM_SERIALIZATION);
setOptions(BigInteger.class, SKIP_CUSTOM_SERIALIZATION);
setOptions(BigDecimal.class, PROVIDE_GET_FIELD);

// At some moment InetAddress fields were moved to an auxilary holder class.
// This resolves backward compatibility problem by inlining holder fields during serialization.
Expand Down
66 changes: 54 additions & 12 deletions src/one/nio/serial/gen/DelegateGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -90,32 +91,63 @@ private static void defineBootstrapClass(Method m, String classData) throws Refl
m.invoke(null, null, null, code, 0, code.length, null, null);
}

public static Delegate instantiate(Class cls, FieldDescriptor[] fds, byte[] code) {
Map<String, Field> fieldsMap = null;
if (Repository.hasOptions(cls, Repository.PROVIDE_GET_FIELD)) {
fieldsMap = new HashMap<>(fds.length, 1);
for (FieldDescriptor fd : fds) {
Field field = fd.ownField();
if (field != null) {
fieldsMap.put(field.getName(), field);
JavaInternals.setAccessible(field);
}
}
}
try {
return (Delegate) BytecodeGenerator.INSTANCE.defineClass(code)
.getDeclaredConstructor(Map.class)
.newInstance(fieldsMap);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot instantiate class", e);
}
}

public static Delegate instantiate(Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) {
return instantiate(cls, fds, generate(cls, fds, defaultFields));
}

public static byte[] generate(Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) {
String className = "sun/reflect/Delegate" + index.getAndIncrement() + '_' + cls.getSimpleName();

ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cv.visit(V1_6, ACC_PUBLIC | ACC_FINAL, className, null, MAGIC_CLASS,
new String[]{"one/nio/serial/gen/Delegate"});

generateConstructor(cv);
generateConstructor(cv, className);
generateCalcSize(cv, cls, fds);
generateWrite(cv, cls, fds);
generateRead(cv, cls, fds, defaultFields);
generateRead(cv, cls, fds, defaultFields, className);
generateSkip(cv, fds);
generateToJson(cv, cls, fds);
generateFromJson(cv, cls, fds, defaultFields);
generateFromJson(cv, cls, fds, defaultFields, className);

cv.visitEnd();
return cv.toByteArray();
}

private static void generateConstructor(ClassVisitor cv) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
private static void generateConstructor(ClassVisitor cv, String className) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/util/Map;)V", null, null);
cv.visitField(ACC_PRIVATE | ACC_FINAL, "fields", "Ljava/util/Map;", null, null).visitEnd();

mv.visitCode();

mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, MAGIC_CLASS, "<init>", "()V", false);

mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, className, "fields", "Ljava/util/Map;");

mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
Expand Down Expand Up @@ -212,7 +244,7 @@ private static void emitWriteObject(Class cls, MethodVisitor mv) {
}
}

private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) {
private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields, String className) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "read", "(Lone/nio/serial/DataStream;)Ljava/lang/Object;",
null, new String[]{"java/io/IOException", "java/lang/ClassNotFoundException"});
mv.visitCode();
Expand Down Expand Up @@ -261,20 +293,30 @@ private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] f
if (isRecord) {
generateCreateRecord(mv, cls, fds, defaultFields);
}

emitReadObject(cls, mv);
emitReadObject(cls, mv, className);

mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}

private static void emitReadObject(Class cls, MethodVisitor mv) {
private static void emitReadObject(Class cls, MethodVisitor mv, String className) {
MethodType methodType = MethodType.methodType(void.class, ObjectInputStream.class);
MethodHandleInfo m = MethodHandlesReflection.findInstanceMethod(cls, "readObject", methodType);
if (m != null && !Repository.hasOptions(m.getDeclaringClass(), Repository.SKIP_READ_OBJECT)) {
mv.visitInsn(DUP);
mv.visitFieldInsn(GETSTATIC, "one/nio/serial/gen/NullObjectInputStream", "INSTANCE", "Lone/nio/serial/gen/NullObjectInputStream;");
if (!Repository.hasOptions(m.getDeclaringClass(), Repository.PROVIDE_GET_FIELD)) {
mv.visitFieldInsn(GETSTATIC, "one/nio/serial/gen/NullObjectInputStream", "INSTANCE", "Lone/nio/serial/gen/NullObjectInputStream;");
} else {
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "one/nio/serial/gen/GetFieldInputStream");
mv.visitInsn(DUP_X1);
mv.visitInsn(SWAP);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, "fields", "Ljava/util/Map;");
mv.visitMethodInsn(INVOKESPECIAL, "one/nio/serial/gen/GetFieldInputStream", "<init>", "(Ljava/lang/Object;Ljava/util/Map;)V", false);
}
emitInvoke(mv, m);
}
}
Expand Down Expand Up @@ -377,7 +419,7 @@ private static void generateToJson(ClassVisitor cv, Class cls, FieldDescriptor[]
mv.visitEnd();
}

private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) {
private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields, String className) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "fromJson", "(Lone/nio/serial/JsonReader;)Ljava/lang/Object;",
null, new String[]{"java/io/IOException", "java/lang/ClassNotFoundException"});
mv.visitCode();
Expand Down Expand Up @@ -507,7 +549,7 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor
generateCreateRecord(mv, cls, fds, defaultFields);
}

emitReadObject(cls, mv);
emitReadObject(cls, mv, className);

mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
Expand Down
149 changes: 149 additions & 0 deletions src/one/nio/serial/gen/GetFieldInputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2022 Odnoklassniki Ltd, Mail.Ru Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package one.nio.serial.gen;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;
import java.util.Map;

class GetFieldInputStream extends NullObjectInputStream {

private final Map<String, Field> fields;
private final Object source;

GetFieldInputStream(Object source, Map<String, Field> fields) throws IOException, SecurityException {
this.fields = fields;
this.source = source;
}

@Override
public GetField readFields() {
return new ObjectGetField(fields, source);
}

private static class ObjectGetField extends ObjectInputStream.GetField {
private final Object object;
private final Map<String, Field> fields;

private ObjectGetField(Map<String, Field> fields, Object object) {
this.object = object;
this.fields = fields;
}

@Override
public ObjectStreamClass getObjectStreamClass() {
throw new UnsupportedOperationException();
}

@Override
public boolean defaulted(String name) {
return !fields.containsKey(name);
}

@Override
public boolean get(String name, boolean val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getBoolean(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public byte get(String name, byte val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getByte(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public char get(String name, char val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getChar(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public short get(String name, short val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getShort(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public int get(String name, int val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getInt(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public long get(String name, long val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getLong(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public float get(String name, float val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getFloat(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public double get(String name, double val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.getDouble(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}

@Override
public Object get(String name, Object val) throws IOException {
try {
Field field = fields.get(name);
return field != null ? field.get(object) : val;
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}
}
}
3 changes: 1 addition & 2 deletions src/one/nio/serial/gen/NullObjectInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ public class NullObjectInputStream extends ObjectInputStream {
}
}

private NullObjectInputStream() throws IOException {
// Singleton
protected NullObjectInputStream() throws IOException {
}

@Override
Expand Down
5 changes: 1 addition & 4 deletions test/one/nio/serial/ConversionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package one.nio.serial;

import one.nio.gen.BytecodeGenerator;
import one.nio.serial.gen.Delegate;
import one.nio.serial.gen.DelegateGenerator;
import org.junit.Test;
Expand All @@ -32,13 +31,11 @@ public class ConversionTest implements Serializable {

@Test
public void testFieldConversion() throws Exception {
byte[] code = DelegateGenerator.generate(ConversionTest.class, new FieldDescriptor[]{
Delegate delegate = DelegateGenerator.instantiate(ConversionTest.class, new FieldDescriptor[]{
fd("intField", BigInteger.class),
fd("longField", BigInteger.class)
}, new FieldDescriptor[0]);

Delegate delegate = BytecodeGenerator.INSTANCE.instantiate(code, Delegate.class);

byte[] data = new byte[100];
delegate.write(new ConversionTest(), new DataStream(data));
ConversionTest clone = (ConversionTest) delegate.read(new DataStream(data));
Expand Down
5 changes: 1 addition & 4 deletions test/one/nio/serial/DefaultFieldsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@

package one.nio.serial;

import one.nio.gen.BytecodeGenerator;
import one.nio.serial.gen.Delegate;
import one.nio.serial.gen.DelegateGenerator;
import org.junit.Test;

import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -63,9 +61,8 @@ public void testDefaultFields() throws Exception {
for (int i = 0; i < ownFields.length; i++) {
defaultFields[i] = new FieldDescriptor(ownFields[i], null, i);
}
byte[] code = DelegateGenerator.generate(DefaultFieldsTest.class, new FieldDescriptor[0], defaultFields);

Delegate delegate = BytecodeGenerator.INSTANCE.instantiate(code, Delegate.class);
Delegate delegate = DelegateGenerator.instantiate(DefaultFieldsTest.class, new FieldDescriptor[0], defaultFields);
DefaultFieldsTest obj = (DefaultFieldsTest) delegate.read(new DataStream(0));

assertEquals("abc", obj.s);
Expand Down
Loading

0 comments on commit 264664e

Please sign in to comment.