using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; using JetBrains.Diagnostics; using JetBrains.Rd.Base; using JetBrains.Rd.Impl; using JetBrains.Util; using JetBrains.Util.Util; namespace JetBrains.Rd.Reflection { public class ScalarSerializer : IScalarSerializers { /// /// Types catalog required for providing information about statically discovered types during concrete serializer /// construction for sake of possibility for Rd serializers to lookup real type by representing RdId /// private readonly ITypesCatalog? myTypesCatalog; /// /// Black listed type. Any attempt to create serializer for these types should throw exception. /// Used to prevent attempts to pass an object which is well-known as non-serializable. /// For example, any component of tree-like structure or object graph should not be passed to /// serializer /// /// This predicate should return true only for blacklisted type /// private readonly Predicate myBlackListChecker; public ScalarSerializer(ITypesCatalog? typesCatalog, Predicate? blackListChecker = null) { myTypesCatalog = typesCatalog; myBlackListChecker = blackListChecker ?? (_ => false); } /// /// Creates static serializers for type /// /// /// /// public SerializerPair CreateSerializer(Type type, ISerializersSource serializers) { if (type == typeof(IntPtr)) { throw new ArgumentException($"Unable to serialize {type.ToString(true)}. Platform-specific types cannot be serialized."); } if (typeof(Delegate).IsAssignableFrom(type)) { throw new ArgumentException($"Unable to serialize {type.ToString(true)}. Delegates cannot be serialized."); } if (myBlackListChecker(type)) { Assertion.Fail($"Attempt to create serializer for black-listed type: {type.ToString(true)}"); } var typeInfo = type.GetTypeInfo(); var builtIn = BuiltInSerializers.TryGet(typeInfo, t1 => serializers.GetOrRegisterSerializerPair(t1, true)); if (builtIn != null) { myTypesCatalog?.AddType(type); return builtIn; } else if (type.IsEnum) { var serializer = ReflectionUtil.InvokeGenericThis(this, nameof(CreateEnumSerializer), type); return (SerializerPair) serializer!; } else if (ReflectionSerializerVerifier.IsValueTuple(typeInfo)) { return (SerializerPair) ReflectionUtil.InvokeGenericThis(this, nameof(CreateValueTupleSerializer), type, new object?[] { serializers })!; } else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericTypeArgument = typeInfo.GetGenericArguments()[0]; var nullableSerializer = (SerializerPair) ReflectionUtil.InvokeGenericThis(this, nameof(RegisterNullable), genericTypeArgument, new object?[] { serializers })!; return nullableSerializer; // return CreateGenericSerializer(member, typeInfo, implementingType, implementingTypeInfo); } else { myTypesCatalog?.AddType(type); var serializer = ReflectionUtil.InvokeGenericThis(this, nameof(CreateCustomScalar), type, new object[] { serializers }); return (SerializerPair) serializer!; } } private SerializerPair CreateCustomScalar(ISerializersSource serializers) { if (Mode.IsAssertion) ReflectionSerializerVerifier.AssertValidScalar(typeof(T).GetTypeInfo()); TypeInfo typeInfo = typeof(T).GetTypeInfo(); var allowNullable = ReflectionSerializerVerifier.CanBeNull(typeInfo); var memberInfos = SerializerReflectionUtil.GetSerializableFields(typeInfo); var memberSetters = memberInfos.Select(ReflectionUtil.GetSetter).ToArray(); var memberGetters = memberInfos.Select(ReflectionUtil.GetGetter).ToArray(); // todo: consider using IL emit CtxReadDelegate[]? memberDeserializers = null; CtxWriteDelegate[]? memberSerializers = null; CtxReadDelegate readerDelegate = (ctx, unsafeReader) => { if (memberDeserializers == null) using (new FirstChanceExceptionInterceptor.ThreadLocalDebugInfo(typeof(T))) InitMemberSerializers(); Assertion.AssertNotNull(memberDeserializers); if (allowNullable && !unsafeReader.ReadNullness()) return default; object instance = FormatterServices.GetUninitializedObject(typeof(T)); try { for (var index = 0; index < memberDeserializers.Length; index++) { var memberValue = memberDeserializers[index](ctx, unsafeReader); memberSetters[index](instance, memberValue); } } catch (ArgumentException e) { e.Data["Type:" + typeof(T).ToString(true)] = ""; throw; } return (T) instance; }; CtxWriteDelegate writerDelegate = (ctx, unsafeWriter, value) => { if (memberSerializers == null) using (new FirstChanceExceptionInterceptor.ThreadLocalDebugInfo(typeof(T))) InitMemberSerializers(); Assertion.AssertNotNull(memberSerializers); if (allowNullable) { unsafeWriter.WriteBoolean(value != null); if (value == null) return; } try { for (var i = 0; i < memberSerializers.Length; i++) { var memberValue = memberGetters[i](value!); memberSerializers[i](ctx, unsafeWriter, memberValue!); } } catch (ArgumentException e) { e.Data["Type:" + typeof(T).ToString(true)] = ""; throw; } }; return new SerializerPair(readerDelegate, writerDelegate); // Lazy resolve cyclic depencencies and give ability to serialize tree-like structures. void InitMemberSerializers() { var read = new CtxReadDelegate[memberInfos.Length]; var write = new CtxWriteDelegate[memberInfos.Length]; for (var index = 0; index < memberInfos.Length; index++) { var mi = memberInfos[index]; var returnType = ReflectionUtil.GetReturnType(mi); var serPair = serializers.GetOrRegisterSerializerPair(returnType, true); read[index] = SerializerReflectionUtil.ConvertReader(serPair.Reader); write[index] = SerializerReflectionUtil.ConvertWriter(serPair.Writer); } memberSerializers = write; memberDeserializers = read; } } private SerializerPair CreateEnumSerializer() { var readerParameter = Expression.Parameter(typeof(int), "reader"); var readerConvert = Expression.ConvertChecked(readerParameter, typeof(T)); var readerCaster = Expression.Lambda>(readerConvert, readerParameter).Compile(); var writerParameter = Expression.Parameter(typeof(T), "writer"); var writerConvert = Expression.ConvertChecked(writerParameter, typeof(int)); var writerCaster = Expression.Lambda>(writerConvert, writerParameter).Compile(); if (Mode.IsAssertion) Assertion.Require(typeof(T).IsSubclassOf(typeof(Enum)), "{0}", typeof(T)); var result = new SerializerPair( (CtxReadDelegate) ((_, reader) => readerCaster(reader.ReadInt())), (CtxWriteDelegate) ((_, writer, o) => writer.WriteInt32(writerCaster(o)))); return result; } private SerializerPair RegisterNullable(ISerializersSource serializers) where T : struct { // nullable can be only for value tuple, no need to aks for serializers serializer here var serPair = CreateSerializer(typeof(T), serializers); var ctxReadDelegate = serPair.GetReader(); var ctxWriteDelegate = serPair.GetWriter(); return new SerializerPair(ctxReadDelegate.NullableStruct(), ctxWriteDelegate.NullableStruct()); } /// /// Register serializer for ValueTuples /// /// private SerializerPair CreateValueTupleSerializer(ISerializersSource serializers) { TypeInfo typeInfo = typeof(T).GetTypeInfo(); ReflectionSerializerVerifier.AssertRoot(typeInfo); var argumentTypes = typeInfo.GetGenericArguments(); var memberGetters = typeInfo.GetFields().Select(ReflectionUtil.GetGetter).ToArray(); var memberDeserializers = new CtxReadDelegate[argumentTypes.Length]; var memberSerializers = new CtxWriteDelegate[argumentTypes.Length]; for (var index = 0; index < argumentTypes.Length; index++) { var argumentType = argumentTypes[index]; var serPair = serializers.GetOrRegisterSerializerPair(argumentType, true); memberDeserializers[index] = SerializerReflectionUtil.ConvertReader(serPair.Reader); memberSerializers[index] = SerializerReflectionUtil.ConvertWriter(serPair.Writer); } var type = typeInfo.AsType(); CtxReadDelegate readerDelegate = (ctx, unsafeReader) => { // todo: consider using IL emit var activatorArgs = new object[argumentTypes.Length]; for (var index = 0; index < argumentTypes.Length; index++) { var value = memberDeserializers[index](ctx, unsafeReader); activatorArgs[index] = value; } var instance = Activator.CreateInstance(type, activatorArgs); return (T) instance; }; CtxWriteDelegate writerDelegate = (ctx, unsafeWriter, value) => { // nrt suppression: value tuple cannot be null for (var i = 0; i < argumentTypes.Length; i++) { var memberValue = memberGetters[i](value!); memberSerializers[i](ctx, unsafeWriter, memberValue); } }; return new SerializerPair(readerDelegate, writerDelegate); } } }