Skip to content
Permalink
Browse files
IGNITE-16298 Object identity distinction must be preserved after mars…
…halling+unmarshalling
  • Loading branch information
rpuch authored and SammyVimes committed Jan 17, 2022
1 parent 96e4e7f commit c507456fcf6bf83ac600f148fde8dc26b5bf57a1
Showing 3 changed files with 90 additions and 54 deletions.
@@ -113,28 +113,18 @@ private void marshalToOutput(@Nullable Object object, Class<?> declaredClass, Da

DescribedObject afterReplacement = applyWriteReplaceIfNeeded(object, originalDescriptor);

if (canParticipateInCycles(afterReplacement.descriptor)) {
if (hasObjectIdentity(afterReplacement.object, afterReplacement.descriptor)) {
Integer alreadySeenObjectId = context.rememberAsSeen(afterReplacement.object);
if (alreadySeenObjectId != null) {
writeReference(alreadySeenObjectId, output);
} else {
marshalCycleable(afterReplacement, output, context);
marshalIdentifiable(afterReplacement, output, context);
}
} else {
marshalNonCycleable(afterReplacement, output, context);
marshalValue(afterReplacement, output, context);
}
}

/**
* Returns {@code true} if an instance of the type represented by the descriptor may participate in a cycle.
*
* @param descriptor descriptor to check
* @return {@code true} if an instance of the type represented by the descriptor may actively form a cycle
*/
boolean canParticipateInCycles(ClassDescriptor descriptor) {
return !builtInNonContainerMarshallers.supports(descriptor.clazz());
}

private boolean objectIsMemberOfEnumWithAnonymousClassesForMembers(Object object, Class<?> declaredClass) {
return declaredClass.isEnum() && object.getClass().getSuperclass() == declaredClass;
}
@@ -171,32 +161,6 @@ private boolean isCapturingClosure(Class<?> objectClass) {
return false;
}

private DescribedObject applyWriteReplaceIfNeeded(@Nullable Object objectBefore, ClassDescriptor descriptorBefore)
throws MarshalException {
if (!descriptorBefore.supportsWriteReplace()) {
return new DescribedObject(objectBefore, descriptorBefore);
}

Object replacedObject = applyWriteReplace(objectBefore, descriptorBefore);
ClassDescriptor replacementDescriptor = getOrCreateDescriptor(replacedObject, objectClass(replacedObject));

if (descriptorBefore.describesSameClass(replacementDescriptor)) {
return new DescribedObject(replacedObject, replacementDescriptor);
} else {
// Let's do it again!
return applyWriteReplaceIfNeeded(replacedObject, replacementDescriptor);
}
}

@Nullable
private Object applyWriteReplace(Object originalObject, ClassDescriptor originalDescriptor) throws MarshalException {
try {
return originalDescriptor.serializationMethods().writeReplace(originalObject);
} catch (SpecialMethodInvocationException e) {
throw new MarshalException("Cannot apply writeReplace()", e);
}
}

private ClassDescriptor getOrCreateDescriptor(@Nullable Object object, Class<?> declaredClass) {
if (object == null) {
return localDescriptors.getNullDescriptor();
@@ -237,20 +201,54 @@ private boolean isEnumArray(Class<?> objectClass) {
return objectClass.isArray() && objectClass.getComponentType().isEnum();
}

private DescribedObject applyWriteReplaceIfNeeded(@Nullable Object objectBefore, ClassDescriptor descriptorBefore)
throws MarshalException {
if (!descriptorBefore.supportsWriteReplace()) {
return new DescribedObject(objectBefore, descriptorBefore);
}

Object replacedObject = applyWriteReplace(objectBefore, descriptorBefore);
ClassDescriptor replacementDescriptor = getOrCreateDescriptor(replacedObject, objectClass(replacedObject));

if (descriptorBefore.describesSameClass(replacementDescriptor)) {
return new DescribedObject(replacedObject, replacementDescriptor);
} else {
// Let's do it again!
return applyWriteReplaceIfNeeded(replacedObject, replacementDescriptor);
}
}

@Nullable
private Object applyWriteReplace(Object originalObject, ClassDescriptor originalDescriptor) throws MarshalException {
try {
return originalDescriptor.serializationMethods().writeReplace(originalObject);
} catch (SpecialMethodInvocationException e) {
throw new MarshalException("Cannot apply writeReplace()", e);
}
}

private boolean hasObjectIdentity(@Nullable Object object, ClassDescriptor descriptor) {
return object != null && mayHaveObjectIdentity(descriptor);
}

private boolean mayHaveObjectIdentity(ClassDescriptor descriptor) {
return !descriptor.clazz().isPrimitive() && !descriptor.isNull();
}

private void writeReference(int objectId, DataOutput output) throws IOException {
ProtocolMarshalling.writeDescriptorOrCommandId(BuiltInType.REFERENCE.descriptorId(), output);
ProtocolMarshalling.writeObjectId(objectId, output);
}

private void marshalCycleable(DescribedObject describedObject, DataOutputStream output, MarshallingContext context)
private void marshalIdentifiable(DescribedObject describedObject, DataOutputStream output, MarshallingContext context)
throws IOException, MarshalException {
writeDescriptorId(describedObject.descriptor, output);
ProtocolMarshalling.writeObjectId(context.objectId(describedObject.object), output);

writeObject(describedObject.object, describedObject.descriptor, output, context);
}

private void marshalNonCycleable(DescribedObject describedObject, DataOutputStream output, MarshallingContext context)
private void marshalValue(DescribedObject describedObject, DataOutputStream output, MarshallingContext context)
throws IOException, MarshalException {
writeDescriptorId(describedObject.descriptor, output);

@@ -318,12 +316,7 @@ private <T> T unmarshalFromInput(DataInputStream input, UnmarshallingContext con
}

ClassDescriptor descriptor = context.getRequiredDescriptor(commandOrDescriptorId);
Object readObject;
if (canParticipateInCycles(descriptor)) {
readObject = readCycleable(input, context, descriptor);
} else {
readObject = readNonCycleable(input, descriptor, context);
}
Object readObject = readObject(input, context, descriptor);

@SuppressWarnings("unchecked") T resolvedObject = (T) applyReadResolveIfNeeded(descriptor, readObject);
return resolvedObject;
@@ -334,9 +327,40 @@ private <T> T unmarshalReference(DataInput input, UnmarshallingContext context)
return context.dereference(objectId);
}

private Object readCycleable(DataInputStream input, UnmarshallingContext context, ClassDescriptor descriptor)
@Nullable
private Object readObject(DataInputStream input, UnmarshallingContext context, ClassDescriptor descriptor)
throws IOException, UnmarshalException {
int objectId = ProtocolMarshalling.readObjectId(input);
if (!mayHaveObjectIdentity(descriptor)) {
return readValue(input, descriptor, context);
} else if (mustBeReadInOneStage(descriptor)) {
return readIdentifiableInOneStage(input, context, descriptor);
} else {
return readIdentifiableInTwoStages(input, context, descriptor);
}
}

private boolean mustBeReadInOneStage(ClassDescriptor descriptor) {
return builtInNonContainerMarshallers.supports(descriptor.clazz());
}

@Nullable
private Object readIdentifiableInOneStage(DataInputStream input, UnmarshallingContext context, ClassDescriptor descriptor)
throws IOException, UnmarshalException {
int objectId = readObjectId(input);

Object object = readValue(input, descriptor, context);
context.registerReference(objectId, object);

return object;
}

private int readObjectId(DataInputStream input) throws IOException {
return ProtocolMarshalling.readObjectId(input);
}

private Object readIdentifiableInTwoStages(DataInputStream input, UnmarshallingContext context, ClassDescriptor descriptor)
throws IOException, UnmarshalException {
int objectId = readObjectId(input);

Object preInstantiatedObject = preInstantiate(descriptor, input, context);
context.registerReference(objectId, preInstantiatedObject);
@@ -406,7 +430,7 @@ private void fillGenericRefArrayFrom(DataInputStream input, Object[] array, Unma
}

@Nullable
private Object readNonCycleable(DataInputStream input, ClassDescriptor descriptor, UnmarshallingContext context)
private Object readValue(DataInputStream input, ClassDescriptor descriptor, UnmarshallingContext context)
throws IOException, UnmarshalException {
if (isBuiltInNonContainer(descriptor)) {
return builtInNonContainerMarshallers.readBuiltIn(descriptor, input, context);
@@ -38,7 +38,7 @@ class DefaultUserObjectMarshallerCommonTest {
private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);

@Test
void marshalsAndUnmarshalsBareObject() throws Exception {
void throwsOnExcessiveInputWhenUnmarshalling() throws Exception {
MarshalledObject marshalled = marshaller.marshal(null);

byte[] tooManyBytes = Arrays.copyOf(marshalled.bytes(), marshalled.bytes().length + 1);
@@ -65,6 +65,8 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {

private static boolean constructorCalled;

private static final int INT_OUT_OF_INT_CACHE_RANGE = 1_000_000;

@Test
void marshalsAndUnmarshalsSimpleClassInstances() throws Exception {
Simple unmarshalled = marshalAndUnmarshalNonNull(new Simple(42));
@@ -450,11 +452,21 @@ void unmarshalsReferencesToSameObjectOfNonBuiltInTypeToSameObject() throws Excep
assertThat(unmarshalled.get(0), sameInstance(unmarshalled.get(1)));
}

@Test
void unmarshalsSamePrimitiveWrapperReferencesToSameInstances() throws Exception {
Integer obj = INT_OUT_OF_INT_CACHE_RANGE;
List<?> list = new ArrayList<>(Arrays.asList(obj, obj));

List<?> unmarshalled = marshalAndUnmarshalNonNull(list);

assertThat(unmarshalled.get(0), sameInstance(unmarshalled.get(1)));
}

@Test
void unmarshalsDifferentButEqualObjectsToDifferentObjects() throws Exception {
long longValue = 1_000_000;
String obj1 = String.valueOf(longValue);
String obj2 = String.valueOf(longValue);
int intValue = INT_OUT_OF_INT_CACHE_RANGE;
String obj1 = String.valueOf(intValue);
String obj2 = String.valueOf(intValue);
List<?> list = new ArrayList<>(Arrays.asList(obj1, obj2));

List<?> unmarshalled = marshalAndUnmarshalNonNull(list);

0 comments on commit c507456

Please sign in to comment.