From 017f865fa388245bafc694d535df43a5b106b319 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 11 Dec 2017 18:32:12 +0000 Subject: [PATCH 1/3] CORDA-852 - Fix AMQP serialisation of nested generic --- .../corda/nodeapi/internal/serialization/amqp/GenericsTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index 9882c79b41..e9ab19db4f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -111,4 +111,4 @@ class GenericsTests { } } } -} \ No newline at end of file +} From 41220de816ddd11832af36a9d5851447a00ad605 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 11 Dec 2017 20:01:46 +0000 Subject: [PATCH 2/3] CORDA-855 - Fix for fingerprinting generics in AMQP * Undo refactor --- .../amqp/EnumEvolutionSerializer.kt | 4 +- .../serialization/amqp/EvolutionSerializer.kt | 49 +++++++++++++ .../internal/serialization/amqp/Schema.kt | 67 +++++++++++------- .../serialization/amqp/SerializerFactory.kt | 69 ++++++++++--------- .../amqp/JavaSerialiseEnumTests.java | 4 +- .../amqp/JavaSerializationOutputTests.java | 7 +- .../amqp/ListsSerializationJavaTest.java | 4 +- .../serialization/amqp/AMQPTestUtils.kt | 2 + .../amqp/DeserializeAndReturnEnvelopeTests.kt | 4 +- .../serialization/amqp/DeserializeMapTests.kt | 2 +- .../DeserializeNeedingCarpentryOfEnumsTest.kt | 4 +- ...erializeNeedingCarpentrySimpleTypesTest.kt | 4 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 2 +- .../amqp/DeserializeSimpleTypesTests.kt | 4 +- .../internal/serialization/amqp/EnumTests.kt | 2 +- .../serialization/amqp/GenericsTests.kt | 69 +++++++++++++++++-- .../amqp/SerializationOutputTests.kt | 6 +- .../amqp/SerializeAndReturnSchemaTest.kt | 2 +- ...ticInitialisationOfSerializedObjectTest.kt | 4 +- 19 files changed, 223 insertions(+), 86 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt index 88639d466b..6e96d4bad8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt @@ -106,7 +106,7 @@ class EnumEvolutionSerializer( // to the name as it exists. We want to test any new constants have been added to the end // of the enum class val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices - .associateBy ({ it.value.toInt() }, { conversions[it.name] })) + .associateBy({ it.value.toInt() }, { conversions[it.name] })) if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) { throw NotSerializableException("Constants have been reordered, additions must be appended to the end") @@ -133,4 +133,4 @@ class EnumEvolutionSerializer( override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } -} \ No newline at end of file +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index 9293729306..e2d9ce671a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -130,3 +130,52 @@ class EvolutionSerializer( } } +/** + * Instances of this type are injected into a [SerializerFactory] at creation time to dictate the + * behaviour of evolution within that factory. Under normal circumstances this will simply + * be an object that returns an [EvolutionSerializer]. Of course, any implementation that + * extends this class can be written to invoke whatever behaviour is desired. + */ +abstract class EvolutionSerializerGetterBase { + abstract fun getEvolutionSerializer( + factory: SerializerFactory, + typeNotation: TypeNotation, + newSerializer: AMQPSerializer, + schemas: SerializationSchemas): AMQPSerializer +} + +/** + * The normal use case for generating an [EvolutionSerializer]'s based on the differences + * between the received schema and the class as it exists now on the class path, + */ +class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { + override fun getEvolutionSerializer(factory: SerializerFactory, + typeNotation: TypeNotation, + newSerializer: AMQPSerializer, + schemas: SerializationSchemas): AMQPSerializer = + factory.serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { + when (typeNotation) { + is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory) + is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas) + } + } +} + +/** + * An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a + * [SerializerFactory]. This is most useful in testing where it is known that evolution should not be + * occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This + * prevents that by simply throwing an exception whenever such a serializer is requested. + */ +class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() { + override fun getEvolutionSerializer(factory: SerializerFactory, + typeNotation: TypeNotation, + newSerializer: AMQPSerializer, + schemas: SerializationSchemas): AMQPSerializer { + throw NotSerializableException("No evolution should be occurring\n" + + " ${typeNotation.name}\n" + + " ${typeNotation.descriptor.name}\n" + + " ${newSerializer.type.typeName}\n" + + " ${newSerializer.typeDescriptor}\n\n${schemas.schema}") + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 79b2a57b93..dcfdfe9b7a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -348,17 +348,50 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact // This method concatentates various elements of the types recursively as unencoded strings into the hasher, effectively // creating a unique string for a type which we then hash in the calling function above. -private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory): Hasher { - return if (type in alreadySeen) { +private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet, + hasher: Hasher, factory: SerializerFactory, offset: String = ""): Hasher { + // We don't include Example and Example where type is ? or T in this otherwise we + // generate different fingerprints for class Outer(val a: Inner) when serialising + // and deserializing (assuming deserialization is occurring in a factory that didn't + // serialise the object in the first place (and thus the cache lookup fails). This is also + // true of Any, where we need Example and Example to have the same fingerprint + return if (type in alreadySeen && (type !is SerializerFactory.AnyType) && (type !is TypeVariable<*>)) { hasher.putUnencodedChars(ALREADY_SEEN_HASH) } else { alreadySeen += type try { when (type) { - is SerializerFactory.AnyType -> hasher.putUnencodedChars(ANY_TYPE_HASH) + is ParameterizedType -> { + // Hash the rawType + params + val clazz = type.rawType as Class<*> + + val startingHash = if (isCollectionOrMap(clazz)) { + hasher.putUnencodedChars(clazz.name) + } else { + hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) { + fingerprintForObject(type, type, alreadySeen, hasher, factory, "$offset ") + } + } + + // ... and concatentate the type data for each parameter type. + type.actualTypeArguments.fold(startingHash) { orig, paramType -> + fingerprintForType(paramType, type, alreadySeen, orig, factory, "$offset ") + } + } + // Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when + // looking at A and B from Example (remember we call this function recursively). When + // serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but + // when deserializing we only have the wilcard placeholder ?, or AnyType + // + // Note, TypeVariable<*> used to be encided as TYPE_VARIABLE_HASH but that again produces a + // differing fingerprint on serialisation and deserialization + is SerializerFactory.AnyType, + is TypeVariable<*> -> { + hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH) + } is Class<*> -> { if (type.isArray) { - fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory).putUnencodedChars(ARRAY_HASH) + fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, "$offset ").putUnencodedChars(ARRAY_HASH) } else if (SerializerFactory.isPrimitive(type)) { hasher.putUnencodedChars(type.name) } else if (isCollectionOrMap(type)) { @@ -377,31 +410,15 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta // to the CorDapp but maybe reference to the JAR in the short term. hasher.putUnencodedChars(type.name) } else { - fingerprintForObject(type, type, alreadySeen, hasher, factory) + fingerprintForObject(type, type, alreadySeen, hasher, factory, "$offset ") } } } } - is ParameterizedType -> { - // Hash the rawType + params - val clazz = type.rawType as Class<*> - val startingHash = if (isCollectionOrMap(clazz)) { - hasher.putUnencodedChars(clazz.name) - } else { - hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) { - fingerprintForObject(type, type, alreadySeen, hasher, factory) - } - } - // ... and concatentate the type data for each parameter type. - type.actualTypeArguments.fold(startingHash) { orig, paramType -> - fingerprintForType(paramType, type, alreadySeen, orig, factory) - } - } // Hash the element type + some array hash is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen, - hasher, factory).putUnencodedChars(ARRAY_HASH) + hasher, factory, "$offset ").putUnencodedChars(ARRAY_HASH) // TODO: include bounds - is TypeVariable<*> -> hasher.putUnencodedChars(type.name).putUnencodedChars(TYPE_VARIABLE_HASH) is WildcardType -> hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH) else -> throw NotSerializableException("Don't know how to hash") } @@ -416,15 +433,15 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) && !EnumSet::class.java.isAssignableFrom(type) -private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory): Hasher { +private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory, offset: String = ""): Hasher { // Hash the class + properties + interfaces val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type") propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters .fold(hasher.putUnencodedChars(name)) { orig, prop -> - fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory) + fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, "$offset ") .putUnencodedChars(prop.name) .putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH) } - interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) } + interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, "$offset ") } return hasher } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index a6c587bc6e..2a7c6557bb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -20,6 +20,10 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ /** * Factory of serializers designed to be shared across threads and invocations. + * + * @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal + * use case is an [EvolutionSerializer] type is returned. However, in some scenarios, primarily testing, this + * can be altered to fit the requirements of the test. */ // TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency // TODO: maybe support for caching of serialized form of some core types for performance @@ -33,9 +37,12 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ // TODO: need to rethink matching of constructor to properties in relation to implementing interfaces and needing those properties etc. // TODO: need to support super classes as well as interfaces with our current code base... what's involved? If we continue to ban, what is the impact? @ThreadSafe -open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { +open class SerializerFactory( + val whitelist: ClassWhitelist, + cl: ClassLoader, + private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter()) { private val serializersByType = ConcurrentHashMap>() - private val serializersByDescriptor = ConcurrentHashMap>() + val serializersByDescriptor = ConcurrentHashMap>() private val customSerializers = CopyOnWriteArrayList() val transformsCache = ConcurrentHashMap>>() @@ -44,17 +51,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { val classloader: ClassLoader get() = classCarpenter.classloader - private fun getEvolutionSerializer( - typeNotation: TypeNotation, - newSerializer: AMQPSerializer, - schemas: SerializationSchemas): AMQPSerializer { - return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { - when (typeNotation) { - is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this) - is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, this, schemas) - } - } - } + private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer, + schemas: SerializationSchemas) + = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) /** * Look up, and manufacture if necessary, a serializer for the given type. @@ -93,7 +92,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { whitelist.requireWhitelisted(actualType) EnumSerializer(actualType, actualClass ?: declaredClass, this) } - else -> makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType) + else -> { + makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType) + } } serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer) @@ -102,23 +103,23 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { } /** - * Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared - * type. + * Try and infer concrete types for any generics type variables for the actual class encountered, + * based on the declared type. */ // TODO: test GenericArrayType - private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, declaredType: Type): Type? = - when (declaredType) { - is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType) - // Nothing to infer, otherwise we'd have ParameterizedType - is Class<*> -> actualClass - is GenericArrayType -> { - val declaredComponent = declaredType.genericComponentType - inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray() - } - is TypeVariable<*> -> actualClass - is WildcardType -> actualClass - else -> null - } + private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, + declaredType: Type) : Type? = when (declaredType) { + is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType) + // Nothing to infer, otherwise we'd have ParameterizedType + is Class<*> -> actualClass + is GenericArrayType -> { + val declaredComponent = declaredType.genericComponentType + inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray() + } + is TypeVariable<*> -> actualClass + is WildcardType -> actualClass + else -> null + } /** * Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared @@ -214,9 +215,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { try { val serialiser = processSchemaEntry(typeNotation) - // if we just successfully built a serialiser for the type but the type fingerprint + // if we just successfully built a serializer for the type but the type fingerprint // doesn't match that of the serialised object then we are dealing with different - // instance of the class, as such we need to build an EvolutionSerialiser + // instance of the class, as such we need to build an EvolutionSerializer if (serialiser.typeDescriptor != typeNotation.descriptor.name) { getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas) } @@ -331,13 +332,15 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { private val namesOfPrimitiveTypes: Map> = primitiveTypeNames.map { it.value to it.key }.toMap() - fun nameForType(type: Type): String = when (type) { + fun nameForType(type: Type): String = when (type) { is Class<*> -> { primitiveTypeName(type) ?: if (type.isArray) { "${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}" } else type.name } - is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>" + is ParameterizedType -> { + "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>" + } is GenericArrayType -> "${nameForType(type.genericComponentType)}[]" is WildcardType -> "?" is TypeVariable<*> -> "?" diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java index e0b65ad27c..a64f9c3d9e 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerialiseEnumTests.java @@ -29,7 +29,9 @@ public class JavaSerialiseEnumTests { public void testJavaConstructorAnnotations() throws NotSerializableException { Bra bra = new Bra(Bras.UNDERWIRE); - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); SerializationOutput ser = new SerializationOutput(factory1); SerializedBytes bytes = ser.serialize(bra); } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java index 1743c645b4..4379718003 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java @@ -172,8 +172,11 @@ public class JavaSerializationOutputTests { } private Object serdes(Object obj) throws NotSerializableException { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); - SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); + SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); SerializationOutput ser = new SerializationOutput(factory1); SerializedBytes bytes = ser.serialize(obj); diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java index 6085546c37..c77f452b33 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ListsSerializationJavaTest.java @@ -125,7 +125,9 @@ public class ListsSerializationJavaTest { // Have to have own version as Kotlin inline functions cannot be easily called from Java private static void assertEqualAfterRoundTripSerialization(T container, Class clazz) throws Exception { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); SerializationOutput ser = new SerializationOutput(factory1); SerializedBytes bytes = ser.serialize(container); DeserializationInput des = new DeserializationInput(factory1); diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt index 8bbe597b26..d7701aed52 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt @@ -5,6 +5,8 @@ import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) +fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(), + EvolutionSerializerGetterTesting()) fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()) class TestSerializationOutput( diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt index 7b9c7a4e39..d793c9f0d2 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt @@ -11,13 +11,14 @@ class DeserializeAndReturnEnvelopeTests { @Suppress("NOTHING_TO_INLINE") inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" + val factory = testDefaultFactoryNoEvolution() + @Test fun oneType() { data class A(val a: Int, val b: String) val a = A(10, "20") - val factory = testDefaultFactory() fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) @@ -33,7 +34,6 @@ class DeserializeAndReturnEnvelopeTests { val b = B(A(10, "20"), 30.0F) - val factory = testDefaultFactory() fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt index 68e106fd89..2e910bf19c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeMapTests.kt @@ -12,7 +12,7 @@ class DeserializeMapTests { private const val VERBOSE = false } - private val sf = testDefaultFactory() + private val sf = testDefaultFactoryNoEvolution() @Test fun mapTest() { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt index de33b7cf18..caa3e06c4f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt @@ -18,7 +18,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) { // // Setup the test // - val setupFactory = testDefaultFactory() + val setupFactory = testDefaultFactoryNoEvolution() val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() }) @@ -57,7 +57,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) { // // Setup the test // - val setupFactory = testDefaultFactory() + val setupFactory = testDefaultFactoryNoEvolution() val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() }) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt index 2ed861152d..002adc5e24 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt @@ -17,8 +17,8 @@ class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase(AllWhitelis private const val VERBOSE = false } - private val sf = testDefaultFactory() - private val sf2 = testDefaultFactory() + private val sf = testDefaultFactoryNoEvolution() + private val sf2 = testDefaultFactoryNoEvolution() @Test fun singleInt() { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index fdd981cd08..f54112b2dc 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -27,7 +27,7 @@ class DeserializeNeedingCarpentryTests : AmqpCarpenterBase(AllWhitelist) { private const val VERBOSE = false } - private val sf1 = testDefaultFactory() + private val sf1 = testDefaultFactoryNoEvolution() // Deserialize with whitelisting on to check that `CordaSerializable` annotation present. private val sf2 = testDefaultFactoryWithWhitelist() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt index 4c5c9311ec..6f2dcd3bc2 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt @@ -16,8 +16,8 @@ class DeserializeSimpleTypesTests { private const val VERBOSE = false } - val sf1 = testDefaultFactory() - val sf2 = testDefaultFactory() + val sf1 = testDefaultFactoryNoEvolution() + val sf2 = testDefaultFactoryNoEvolution() @Test fun testChar() { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt index 60805c994f..cc46273f08 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumTests.kt @@ -65,7 +65,7 @@ class EnumTests { @Suppress("NOTHING_TO_INLINE") inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" - private val sf1 = testDefaultFactory() + private val sf1 = testDefaultFactoryNoEvolution() @Test fun serialiseSimpleTest() { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index e9ab19db4f..001ea67929 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -3,25 +3,82 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.SerializedBytes import net.corda.nodeapi.internal.serialization.AllWhitelist import org.junit.Test +import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals class GenericsTests { + companion object { + val VERBOSE = false + } + + private fun printSeparator() = if(VERBOSE) println ("\n\n-------------------------------------------\n\n") else Unit + + private fun BytesAndSchemas.printSchema() = if (VERBOSE) println ("${this.schema}\n") else Unit + + private fun ConcurrentHashMap>.printKeyToType() { + if (!VERBOSE) return + + forEach { + println ("Key = ${it.key} - ${it.value.type.typeName}") + } + println() + } + + @Test + fun twoDifferntTypesSameParameterizedOuter() { + data class G(val a: A) + + val factory = testDefaultFactoryNoEvolution() + + val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() } + + factory.serializersByDescriptor.printKeyToType() + + val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() } + + factory.serializersByDescriptor.printKeyToType() + + listOf (factory, testDefaultFactory()).forEach { f -> + DeserializationInput(f).deserialize(bytes1.obj).apply { assertEquals("hi", this.a) } + DeserializationInput(f).deserialize(bytes2.obj).apply { assertEquals(121, this.a) } + } + } + + @Test + fun doWeIgnoreMultipleParams() { + data class G1(val a: T) + data class G2(val a: T) + data class Wrapper(val a: Int, val b: G1, val c: G2) + + val factory = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactoryNoEvolution() + + val bytes = SerializationOutput(factory).serializeAndReturnSchema(Wrapper(1, G1("hi"), G2("poop"))).apply { printSchema() } + printSeparator() + DeserializationInput(factory2).deserialize(bytes.obj) + } @Test fun nestedSerializationOfGenerics() { - data class G(val a: T) - data class Wrapper(val a: Int, val b: G) + data class G(val a: T) + data class Wrapper(val a: Int, val b: G) - val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - val altContextFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + val factory = testDefaultFactoryNoEvolution() + val altContextFactory = testDefaultFactoryNoEvolution() val ser = SerializationOutput(factory) - val bytes = ser.serializeAndReturnSchema(G("hi")) + val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() } + + factory.serializersByDescriptor.printKeyToType() assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a) assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a) - val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))) + val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))).apply { printSchema() } + + factory.serializersByDescriptor.printKeyToType() + + printSeparator() DeserializationInput(factory).deserialize(bytes2.obj).apply { assertEquals(1, a) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 9871bff1c6..c6b1eeff4d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -169,9 +169,11 @@ class SerializationOutputTests { private inline fun serdes(obj: T, factory: SerializerFactory = SerializerFactory( - AllWhitelist, ClassLoader.getSystemClassLoader()), + AllWhitelist, ClassLoader.getSystemClassLoader(), + EvolutionSerializerGetterTesting()), freshDeserializationFactory: SerializerFactory = SerializerFactory( - AllWhitelist, ClassLoader.getSystemClassLoader()), + AllWhitelist, ClassLoader.getSystemClassLoader(), + EvolutionSerializerGetterTesting()), expectedEqual: Boolean = true, expectDeserializedEqual: Boolean = true): T { val ser = SerializationOutput(factory) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt index acd8c5b8fe..51248a4d4e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializeAndReturnSchemaTest.kt @@ -10,7 +10,7 @@ class SerializeAndReturnSchemaTest { @Suppress("NOTHING_TO_INLINE") inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" - val factory = testDefaultFactory() + val factory = testDefaultFactoryNoEvolution() // just a simple test to verify the internal test extension for serialize does // indeed give us the correct schema back. This is more useful in support of other diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt index 2533bdf182..1a85fc784b 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/StaticInitialisationOfSerializedObjectTest.kt @@ -45,7 +45,7 @@ class StaticInitialisationOfSerializedObjectTest { } @Test - fun KotlinObjectWithCompanionObject() { + fun kotlinObjectWithCompanionObject() { data class D(val c: C) val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -104,7 +104,7 @@ class StaticInitialisationOfSerializedObjectTest { override val classCarpenter = ClassCarpenter(ClassLoader.getSystemClassLoader(), wl2) } - // This time have the serilization factory and the carpenter use different whitelists + // This time have the serialization factory and the carpenter use different whitelists @Test fun deserializeTest2() { data class D(val c: C2) From 97793447d50680230313bb27932be6b2432977c8 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Tue, 2 Jan 2018 18:19:20 +0000 Subject: [PATCH 3/3] CORDA-855 - Adding tests for wild card generics Can't actually get something to go through the serializer with a wild card in place as it seems that's an impossible situation * Review Changes * CORDA-855 - Review Comments * Review Comments * Review comments --- .gitignore | 1 + .../amqp/DeserializationInput.kt | 1 - .../serialization/amqp/EvolutionSerializer.kt | 20 +-- .../internal/serialization/amqp/Schema.kt | 38 +++-- .../serialization/amqp/SerializerFactory.kt | 10 +- .../serialization/amqp/TransformsSchema.kt | 2 +- .../serialization/amqp/ErrorMessageTests.java | 7 +- .../serialization/amqp/JavaGenericsTest.java | 93 ++++++++++++ .../amqp/JavaPrivatePropertyTests.java | 8 +- .../amqp/SetterConstructorTests.java | 31 +++- .../amqp/EnumEvolvabilityTests.kt | 22 +-- .../amqp/EvolutionSerializerGetterTesting.kt | 22 +++ .../serialization/amqp/GenericsTests.kt | 133 ++++++++++++++++-- .../amqp/GenericsTests.loadGenericFromFile | Bin 0 -> 265 bytes 14 files changed, 322 insertions(+), 66 deletions(-) create mode 100644 node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializerGetterTesting.kt create mode 100644 node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.loadGenericFromFile diff --git a/.gitignore b/.gitignore index 2bc98f9dfd..05182bed10 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ crashlytics-build.properties # docs related docs/virtualenv/ +virtualenv/ # bft-smart **/config/currentView diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index e260f5a891..d34ee9c12e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -54,7 +54,6 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) { inline fun deserialize(bytes: SerializedBytes): T = deserialize(bytes, T::class.java) - @Throws(NotSerializableException::class) inline internal fun deserializeAndReturnEnvelope(bytes: SerializedBytes): ObjectAndEnvelope = deserializeAndReturnEnvelope(bytes, T::class.java) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index e2d9ce671a..7335db0fa3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -153,7 +153,7 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { typeNotation: TypeNotation, newSerializer: AMQPSerializer, schemas: SerializationSchemas): AMQPSerializer = - factory.serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { + factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) { when (typeNotation) { is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory) is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas) @@ -161,21 +161,3 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { } } -/** - * An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a - * [SerializerFactory]. This is most useful in testing where it is known that evolution should not be - * occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This - * prevents that by simply throwing an exception whenever such a serializer is requested. - */ -class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() { - override fun getEvolutionSerializer(factory: SerializerFactory, - typeNotation: TypeNotation, - newSerializer: AMQPSerializer, - schemas: SerializationSchemas): AMQPSerializer { - throw NotSerializableException("No evolution should be occurring\n" + - " ${typeNotation.name}\n" + - " ${typeNotation.descriptor.name}\n" + - " ${newSerializer.type.typeName}\n" + - " ${newSerializer.typeDescriptor}\n\n${schemas.schema}") - } -} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index dcfdfe9b7a..e79336a731 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -346,10 +346,11 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact } } -// This method concatentates various elements of the types recursively as unencoded strings into the hasher, effectively +// This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively // creating a unique string for a type which we then hash in the calling function above. private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet, - hasher: Hasher, factory: SerializerFactory, offset: String = ""): Hasher { + hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher { + // We don't include Example and Example where type is ? or T in this otherwise we // generate different fingerprints for class Outer(val a: Inner) when serialising // and deserializing (assuming deserialization is occurring in a factory that didn't @@ -369,21 +370,21 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta hasher.putUnencodedChars(clazz.name) } else { hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) { - fingerprintForObject(type, type, alreadySeen, hasher, factory, "$offset ") + fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4) } } - // ... and concatentate the type data for each parameter type. + // ... and concatenate the type data for each parameter type. type.actualTypeArguments.fold(startingHash) { orig, paramType -> - fingerprintForType(paramType, type, alreadySeen, orig, factory, "$offset ") + fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4) } } // Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when // looking at A and B from Example (remember we call this function recursively). When // serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but - // when deserializing we only have the wilcard placeholder ?, or AnyType + // when deserializing we only have the wildcard placeholder ?, or AnyType // - // Note, TypeVariable<*> used to be encided as TYPE_VARIABLE_HASH but that again produces a + // Note, TypeVariable<*> used to be encoded as TYPE_VARIABLE_HASH but that again produces a // differing fingerprint on serialisation and deserialization is SerializerFactory.AnyType, is TypeVariable<*> -> { @@ -391,7 +392,8 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta } is Class<*> -> { if (type.isArray) { - fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, "$offset ").putUnencodedChars(ARRAY_HASH) + fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4) + .putUnencodedChars(ARRAY_HASH) } else if (SerializerFactory.isPrimitive(type)) { hasher.putUnencodedChars(type.name) } else if (isCollectionOrMap(type)) { @@ -410,16 +412,18 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta // to the CorDapp but maybe reference to the JAR in the short term. hasher.putUnencodedChars(type.name) } else { - fingerprintForObject(type, type, alreadySeen, hasher, factory, "$offset ") + fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4) } } } } // Hash the element type + some array hash is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen, - hasher, factory, "$offset ").putUnencodedChars(ARRAY_HASH) + hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH) // TODO: include bounds - is WildcardType -> hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH) + is WildcardType -> { + hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH) + } else -> throw NotSerializableException("Don't know how to hash") } } catch (e: NotSerializableException) { @@ -433,15 +437,21 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) && !EnumSet::class.java.isAssignableFrom(type) -private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory, offset: String = ""): Hasher { +private fun fingerprintForObject( + type: Type, + contextType: Type?, + alreadySeen: MutableSet, + hasher: Hasher, + factory: SerializerFactory, + offset: Int = 0): Hasher { // Hash the class + properties + interfaces val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type") propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters .fold(hasher.putUnencodedChars(name)) { orig, prop -> - fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, "$offset ") + fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, offset+4) .putUnencodedChars(prop.name) .putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH) } - interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, "$offset ") } + interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) } return hasher } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 2a7c6557bb..75f9bf0ded 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -42,9 +42,9 @@ open class SerializerFactory( cl: ClassLoader, private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter()) { private val serializersByType = ConcurrentHashMap>() - val serializersByDescriptor = ConcurrentHashMap>() + private val serializersByDescriptor = ConcurrentHashMap>() private val customSerializers = CopyOnWriteArrayList() - val transformsCache = ConcurrentHashMap>>() + private val transformsCache = ConcurrentHashMap>>() open val classCarpenter = ClassCarpenter(cl, whitelist) @@ -55,6 +55,10 @@ open class SerializerFactory( schemas: SerializationSchemas) = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) + fun getSerializersByDescriptor() = serializersByDescriptor + + fun getTransformsCache() = transformsCache + /** * Look up, and manufacture if necessary, a serializer for the given type. * @@ -332,7 +336,7 @@ open class SerializerFactory( private val namesOfPrimitiveTypes: Map> = primitiveTypeNames.map { it.value to it.key }.toMap() - fun nameForType(type: Type): String = when (type) { + fun nameForType(type: Type): String = when (type) { is Class<*> -> { primitiveTypeName(type) ?: if (type.isArray) { "${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt index 1d01f91f4f..50c38be9f5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt @@ -200,7 +200,7 @@ data class TransformsSchema(val types: Map>(TransformTypes::class.java) try { val clazz = sf.classloader.loadClass(name) diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java index 258d7ed3b4..7cd21c4f21 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java @@ -31,7 +31,12 @@ public class ErrorMessageTests { @Test public void testJavaConstructorAnnotations() { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); + SerializationOutput ser = new SerializationOutput(factory1); Assertions.assertThatThrownBy(() -> ser.serialize(new C(1))) diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java new file mode 100644 index 0000000000..feb416e991 --- /dev/null +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaGenericsTest.java @@ -0,0 +1,93 @@ +package net.corda.nodeapi.internal.serialization.amqp; + +import net.corda.core.serialization.SerializedBytes; +import net.corda.nodeapi.internal.serialization.AllWhitelist; +import org.junit.Test; +import java.io.NotSerializableException; + +import static org.jgroups.util.Util.assertEquals; + +public class JavaGenericsTest { + private static class Inner { + private final Integer v; + + private Inner(Integer v) { this.v = v; } + public Integer getV() { return v; } + } + + private static class A { + private final T t; + + private A(T t) { this.t = t; } + public T getT() { return t; } + } + + @Test + public void basicGeneric() throws NotSerializableException { + A a1 = new A(1); + + SerializerFactory factory = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + new EvolutionSerializerGetter()); + + SerializationOutput ser = new SerializationOutput(factory); + SerializedBytes bytes = ser.serialize(a1); + + DeserializationInput des = new DeserializationInput(factory); + A a2 = des.deserialize(bytes, A.class); + + assertEquals(1, a2.getT()); + } + + private SerializedBytes forceWildcardSerialize(A a) throws NotSerializableException { + SerializerFactory factory = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + new EvolutionSerializerGetter()); + + return (new SerializationOutput(factory)).serialize(a); + } + + private SerializedBytes forceWildcardSerializeFactory( + A a, + SerializerFactory factory) throws NotSerializableException { + return (new SerializationOutput(factory)).serialize(a); + } + + private A forceWildcardDeserialize(SerializedBytes bytes) throws NotSerializableException { + SerializerFactory factory = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + new EvolutionSerializerGetter()); + + DeserializationInput des = new DeserializationInput(factory); + return des.deserialize(bytes, A.class); + } + + private A forceWildcardDeserializeFactory( + SerializedBytes bytes, + SerializerFactory factory) throws NotSerializableException { + return (new DeserializationInput(factory)).deserialize(bytes, A.class); + } + + @Test + public void forceWildcard() throws NotSerializableException { + SerializedBytes bytes = forceWildcardSerialize(new A(new Inner(29))); + Inner i = (Inner)forceWildcardDeserialize(bytes).getT(); + assertEquals(29, i.getV()); + } + + @Test + public void forceWildcardSharedFactory() throws NotSerializableException { + SerializerFactory factory = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + new EvolutionSerializerGetter()); + + SerializedBytes bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory); + Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT(); + + assertEquals(29, i.getV()); + } +} diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java index 8c495766d0..29f1b949d5 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java @@ -25,7 +25,9 @@ public class JavaPrivatePropertyTests { @Test public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { - SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerializerGetter); SerializationOutput ser = new SerializationOutput(factory); DeserializationInput des = new DeserializationInput(factory); @@ -53,7 +55,9 @@ public class JavaPrivatePropertyTests { @Test public void singlePrivateWithConstructorAndGetter() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { - SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), evolutionSerializerGetter); SerializationOutput ser = new SerializationOutput(factory); DeserializationInput des = new DeserializationInput(factory); diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java index eefdd0957b..bb710a841e 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java @@ -109,7 +109,12 @@ public class SetterConstructorTests { // despite having no constructor we should still be able to serialise an instance of C @Test public void serialiseC() throws NotSerializableException { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); + SerializationOutput ser = new SerializationOutput(factory1); C c1 = new C(); @@ -178,7 +183,11 @@ public class SetterConstructorTests { @Test public void deserialiseC() throws NotSerializableException { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); C cPre1 = new C(); @@ -241,7 +250,11 @@ public class SetterConstructorTests { @Test public void serialiseOuterAndInner() throws NotSerializableException { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); Inner1 i1 = new Inner1("Hello"); Inner2 i2 = new Inner2(); @@ -263,7 +276,11 @@ public class SetterConstructorTests { @Test public void typeMistmatch() throws NotSerializableException { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); TypeMismatch tm = new TypeMismatch(); tm.setA(10); @@ -279,7 +296,11 @@ public class SetterConstructorTests { @Test public void typeMistmatch2() throws NotSerializableException { - SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter(); + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + evolutionSerialiserGetter); TypeMismatch2 tm = new TypeMismatch2(); tm.setA("10"); diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt index d18b970cfa..2769c31798 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt @@ -6,6 +6,8 @@ import org.assertj.core.api.Assertions import org.junit.Test import java.io.File import java.io.NotSerializableException +import java.util.* +import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -387,21 +389,25 @@ class EnumEvolvabilityTests { data class C1(val annotatedEnum: AnnotatedEnumOnce) val sf = testDefaultFactory() + val f = sf.javaClass.getDeclaredField("transformsCache") + f.isAccessible = true - assertEquals(0, sf.transformsCache.size) + val transformsCache = f.get(sf) as ConcurrentHashMap>> + + assertEquals(0, transformsCache.size) val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D)) - assertEquals(2, sf.transformsCache.size) - assertTrue(sf.transformsCache.containsKey(C1::class.java.name)) - assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name)) + assertEquals(2, transformsCache.size) + assertTrue(transformsCache.containsKey(C1::class.java.name)) + assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name)) val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D)) - assertEquals(3, sf.transformsCache.size) - assertTrue(sf.transformsCache.containsKey(C1::class.java.name)) - assertTrue(sf.transformsCache.containsKey(C2::class.java.name)) - assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name)) + assertEquals(3, transformsCache.size) + assertTrue(transformsCache.containsKey(C1::class.java.name)) + assertTrue(transformsCache.containsKey(C2::class.java.name)) + assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name)) assertEquals(sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name], sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name]) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializerGetterTesting.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializerGetterTesting.kt new file mode 100644 index 0000000000..fc0d636c21 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializerGetterTesting.kt @@ -0,0 +1,22 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import java.io.NotSerializableException + +/** + * An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a + * [SerializerFactory]. This is most useful in testing where it is known that evolution should not be + * occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This + * prevents that by simply throwing an exception whenever such a serializer is requested. + */ +class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() { + override fun getEvolutionSerializer(factory: SerializerFactory, + typeNotation: TypeNotation, + newSerializer: AMQPSerializer, + schemas: SerializationSchemas): AMQPSerializer { + throw NotSerializableException("No evolution should be occurring\n" + + " ${typeNotation.name}\n" + + " ${typeNotation.descriptor.name}\n" + + " ${newSerializer.type.typeName}\n" + + " ${newSerializer.typeDescriptor}\n\n${schemas.schema}") + } +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index 001ea67929..bd858bb103 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -2,43 +2,50 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.SerializedBytes import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.testing.common.internal.ProjectStructure.projectRootDir import org.junit.Test +import java.io.File +import java.net.URI import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals class GenericsTests { companion object { val VERBOSE = false + + @Suppress("UNUSED") + var localPath = projectRootDir.toUri().resolve( + "node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp") } - private fun printSeparator() = if(VERBOSE) println ("\n\n-------------------------------------------\n\n") else Unit + private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit - private fun BytesAndSchemas.printSchema() = if (VERBOSE) println ("${this.schema}\n") else Unit + private fun BytesAndSchemas.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit private fun ConcurrentHashMap>.printKeyToType() { if (!VERBOSE) return forEach { - println ("Key = ${it.key} - ${it.value.type.typeName}") + println("Key = ${it.key} - ${it.value.type.typeName}") } println() } @Test - fun twoDifferntTypesSameParameterizedOuter() { + fun twoDifferentTypesSameParameterizedOuter() { data class G(val a: A) val factory = testDefaultFactoryNoEvolution() val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() } - factory.serializersByDescriptor.printKeyToType() + factory.getSerializersByDescriptor().printKeyToType() val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() } - factory.serializersByDescriptor.printKeyToType() + factory.getSerializersByDescriptor().printKeyToType() - listOf (factory, testDefaultFactory()).forEach { f -> + listOf(factory, testDefaultFactory()).forEach { f -> DeserializationInput(f).deserialize(bytes1.obj).apply { assertEquals("hi", this.a) } DeserializationInput(f).deserialize(bytes2.obj).apply { assertEquals(121, this.a) } } @@ -69,14 +76,14 @@ class GenericsTests { val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() } - factory.serializersByDescriptor.printKeyToType() + factory.getSerializersByDescriptor().printKeyToType() assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a) assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a) val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))).apply { printSchema() } - factory.serializersByDescriptor.printKeyToType() + factory.getSerializersByDescriptor().printKeyToType() printSeparator() @@ -93,7 +100,7 @@ class GenericsTests { @Test fun nestedGenericsReferencesByteArrayViaSerializedBytes() { - data class G(val a : Int) + data class G(val a: Int) data class Wrapper(val a: Int, val b: SerializedBytes) val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -128,27 +135,30 @@ class GenericsTests { ser.serialize(Wrapper(Container(InnerA(1)))).apply { factories.forEach { DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) } + it.getSerializersByDescriptor().printKeyToType(); printSeparator() } } ser.serialize(Wrapper(Container(InnerB(1)))).apply { factories.forEach { DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) } + it.getSerializersByDescriptor().printKeyToType(); printSeparator() } } ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply { factories.forEach { DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) } + it.getSerializersByDescriptor().printKeyToType(); printSeparator() } } } @Test fun nestedSerializationWhereGenericDoesntImpactFingerprint() { - data class Inner(val a : Int) + data class Inner(val a: Int) data class Container(val b: Inner) - data class Wrapper(val c: Container) + data class Wrapper(val c: Container) val factorys = listOf( SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()), @@ -168,4 +178,103 @@ class GenericsTests { } } } + + data class ForceWildcard(val t: T) + + private fun forceWildcardSerialize( + a: ForceWildcard<*>, + factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())): SerializedBytes<*> { + val bytes = SerializationOutput(factory).serializeAndReturnSchema(a) + factory.getSerializersByDescriptor().printKeyToType() + bytes.printSchema() + return bytes.obj + } + + @Suppress("UNCHECKED_CAST") + private fun forceWildcardDeserializeString( + bytes: SerializedBytes<*>, + factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) { + DeserializationInput(factory).deserialize(bytes as SerializedBytes>) + } + + @Suppress("UNCHECKED_CAST") + private fun forceWildcardDeserializeDouble( + bytes: SerializedBytes<*>, + factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) { + DeserializationInput(factory).deserialize(bytes as SerializedBytes>) + } + + @Suppress("UNCHECKED_CAST") + private fun forceWildcardDeserialize( + bytes: SerializedBytes<*>, + factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) { + DeserializationInput(factory).deserialize(bytes as SerializedBytes>) + } + + @Test + fun forceWildcard() { + forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"))) + forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0))) + } + + @Test + fun forceWildcardSharedFactory() { + val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"), f), f) + forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0), f), f) + } + + @Test + fun forceWildcardDeserialize() { + forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"))) + forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10))) + forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0))) + } + + @Test + fun forceWildcardDeserializeSharedFactory() { + val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"), f), f) + forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10), f), f) + forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0), f), f) + } + + @Test + fun loadGenericFromFile() { + val resource = "${javaClass.simpleName}.${testName()}" + val sf = testDefaultFactory() + + // Uncomment to re-generate test files, needs to be done in three stages + // File(URI("$localPath/$resource")).writeBytes(forceWildcardSerialize(ForceWildcard("wibble")).bytes) + + assertEquals("wibble", + DeserializationInput(sf).deserialize(SerializedBytes>( + File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t) + } + + interface DifferentBounds { + fun go() + } + + @Test + fun differentBounds() { + data class A (val a: Int): DifferentBounds { + override fun go() { + println(a) + } + } + + data class G(val b: T) + + val factorys = listOf( + SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()), + SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) + + val ser = SerializationOutput(factorys[0]) + + ser.serialize(G(A(10))).apply { + factorys.forEach { + } + } + } } diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.loadGenericFromFile b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.loadGenericFromFile new file mode 100644 index 0000000000000000000000000000000000000000..5c467153f860402ddf0ce2b596ee7869a97d8a51 GIT binary patch literal 265 zcmYe!FG@*dWME)uIGO|`85kH3d}U@>tdy5pqL&PkvvM&>ba4vJb;-#KOxG?c%5?Ou zaMupBwLQSexR9+pGbt%26|R}-z-q<=D;VK?)&sLy7ka|Y(#y+FNlh%s)XU5(NiE7t z%+V`OEy_&H$*f8&$;{8wOUx}S&~s1C0}3V=holyl6sx%97bT~LXXd0NCl;kRxWY|l lKA^@#j4K@uNFtoWa)5_@A!ErxMlDwd2S+EkVVnmU82~ZBQoH~F literal 0 HcmV?d00001