|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;
using System.Text.RegularExpressions;
namespace System.Xml.Serialization
{
[RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
[RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
internal class XmlSerializationILGen
{
private int _nextMethodNumber;
private readonly Dictionary<TypeMapping, string> _methodNames = new Dictionary<TypeMapping, string>();
// Lookup name->created Method
private readonly Dictionary<string, MethodBuilderInfo> _methodBuilders = new Dictionary<string, MethodBuilderInfo>();
// Lookup name->created Type
internal Dictionary<string, Type> CreatedTypes = new Dictionary<string, Type>();
// Lookup name->class Member
internal Dictionary<string, MemberInfo> memberInfos = new Dictionary<string, MemberInfo>();
private readonly ReflectionAwareILGen _raCodeGen;
private readonly TypeScope[] _scopes;
private readonly TypeDesc? _stringTypeDesc;
private readonly TypeDesc? _qnameTypeDesc;
private readonly string _className;
private TypeMapping[]? _referencedMethods;
private int _references;
private readonly HashSet<TypeMapping> _generatedMethods = new HashSet<TypeMapping>();
private ModuleBuilder? _moduleBuilder;
private readonly TypeAttributes _typeAttributes;
protected TypeBuilder typeBuilder = null!;
protected CodeGenerator ilg = null!;
internal XmlSerializationILGen(TypeScope[] scopes, string access, string className)
{
_scopes = scopes;
if (scopes.Length > 0)
{
_stringTypeDesc = scopes[0].GetTypeDesc(typeof(string));
_qnameTypeDesc = scopes[0].GetTypeDesc(typeof(XmlQualifiedName));
}
_raCodeGen = new ReflectionAwareILGen();
_className = className;
System.Diagnostics.Debug.Assert(access == "public");
_typeAttributes = TypeAttributes.Public;
}
internal int NextMethodNumber { get { return _nextMethodNumber; } set { _nextMethodNumber = value; } }
internal ReflectionAwareILGen RaCodeGen { get { return _raCodeGen; } }
internal TypeDesc? StringTypeDesc { get { return _stringTypeDesc; } }
internal TypeDesc? QnameTypeDesc { get { return _qnameTypeDesc; } }
internal string ClassName { get { return _className; } }
internal TypeScope[] Scopes { get { return _scopes; } }
internal Dictionary<TypeMapping, string> MethodNames { get { return _methodNames; } }
internal HashSet<TypeMapping> GeneratedMethods { get { return _generatedMethods; } }
internal ModuleBuilder ModuleBuilder
{
get { System.Diagnostics.Debug.Assert(_moduleBuilder != null); return _moduleBuilder; }
set { System.Diagnostics.Debug.Assert(_moduleBuilder == null && value != null); _moduleBuilder = value; }
}
internal TypeAttributes TypeAttributes { get { return _typeAttributes; } }
private static readonly string[] s_typeString = new string[] { "type" };
private static readonly string[] s_xmlReaderString = new string[] { "xmlReader" };
private static readonly string[] s_objectToSerializeWriterString = new string[] { "objectToSerialize", "writer" };
private static readonly string[] s_readerString = new string[] { "reader" };
private static readonly Type[] s_typeType = new Type[] { typeof(Type) };
private static readonly Type[] s_xmlReaderType = new Type[] { typeof(XmlReader) };
private static readonly Type[] s_objectXmlSerializationWriterType = new Type[] { typeof(object), typeof(XmlSerializationWriter) };
private static readonly Type[] s_xmlSerializationReaderType = new Type[] { typeof(XmlSerializationReader) };
internal MethodBuilder EnsureMethodBuilder(TypeBuilder typeBuilder, string methodName,
MethodAttributes attributes, Type? returnType, Type[] parameterTypes)
{
MethodBuilderInfo? methodBuilderInfo;
if (!_methodBuilders.TryGetValue(methodName, out methodBuilderInfo))
{
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodName,
attributes,
returnType,
parameterTypes);
methodBuilderInfo = new MethodBuilderInfo(methodBuilder, parameterTypes);
_methodBuilders.Add(methodName, methodBuilderInfo);
}
#if DEBUG
else
{
methodBuilderInfo.Validate(returnType, parameterTypes, attributes);
}
#endif
return methodBuilderInfo.MethodBuilder;
}
internal MethodBuilderInfo GetMethodBuilder(string methodName)
{
System.Diagnostics.Debug.Assert(_methodBuilders.ContainsKey(methodName));
return _methodBuilders[methodName];
}
internal virtual void GenerateMethod(TypeMapping mapping) { }
internal void GenerateReferencedMethods()
{
while (_references > 0)
{
TypeMapping mapping = _referencedMethods![--_references];
GenerateMethod(mapping);
}
}
internal string? ReferenceMapping(TypeMapping mapping)
{
if (!_generatedMethods.Contains(mapping))
{
_referencedMethods = EnsureArrayIndex(_referencedMethods, _references);
_referencedMethods[_references++] = mapping;
}
string? methodName;
_methodNames.TryGetValue(mapping, out methodName);
return methodName;
}
private static TypeMapping[] EnsureArrayIndex(TypeMapping[]? a, int index)
{
if (a == null) return new TypeMapping[32];
if (index < a.Length) return a;
TypeMapping[] b = new TypeMapping[a.Length + 32];
Array.Copy(a, b, index);
return b;
}
[return: NotNullIfNotNull(nameof(value))]
internal static string? GetCSharpString(string? value)
{
return ReflectionAwareILGen.GetCSharpString(value);
}
internal FieldBuilder GenerateHashtableGetBegin(string privateName, string publicName, TypeBuilder serializerContractTypeBuilder)
{
FieldBuilder fieldBuilder = serializerContractTypeBuilder.DefineField(
privateName,
typeof(Hashtable),
FieldAttributes.Private
);
ilg = new CodeGenerator(serializerContractTypeBuilder);
PropertyBuilder propertyBuilder = serializerContractTypeBuilder.DefineProperty(
publicName,
PropertyAttributes.None,
CallingConventions.HasThis,
typeof(Hashtable),
null, null, null, null, null);
ilg.BeginMethod(
typeof(Hashtable),
$"get_{publicName}",
Type.EmptyTypes,
Array.Empty<string>(),
CodeGenerator.PublicOverrideMethodAttributes | MethodAttributes.SpecialName);
propertyBuilder.SetGetMethod(ilg.MethodBuilder!);
ilg.Ldarg(0);
ilg.LoadMember(fieldBuilder);
ilg.Load(null);
// this 'if' ends in GenerateHashtableGetEnd
ilg.If(Cmp.EqualTo);
ConstructorInfo Hashtable_ctor = typeof(Hashtable).GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
LocalBuilder _tmpLoc = ilg.DeclareLocal(typeof(Hashtable), "_tmp");
ilg.New(Hashtable_ctor);
ilg.Stloc(_tmpLoc);
return fieldBuilder;
}
internal void GenerateHashtableGetEnd(FieldBuilder fieldBuilder)
{
ilg!.Ldarg(0);
ilg.LoadMember(fieldBuilder);
ilg.Load(null);
ilg.If(Cmp.EqualTo);
{
ilg.Ldarg(0);
ilg.Ldloc(typeof(Hashtable), "_tmp");
ilg.StoreMember(fieldBuilder);
}
ilg.EndIf();
// 'endif' from GenerateHashtableGetBegin
ilg.EndIf();
ilg.Ldarg(0);
ilg.LoadMember(fieldBuilder);
ilg.GotoMethodEnd();
ilg.EndMethod();
}
internal FieldBuilder GeneratePublicMethods(string privateName, string publicName, string[] methods, XmlMapping[] xmlMappings, TypeBuilder serializerContractTypeBuilder)
{
FieldBuilder fieldBuilder = GenerateHashtableGetBegin(privateName, publicName, serializerContractTypeBuilder);
if (methods != null && methods.Length != 0 && xmlMappings != null && xmlMappings.Length == methods.Length)
{
MethodInfo Hashtable_set_Item = typeof(Hashtable).GetMethod(
"set_Item",
new Type[] { typeof(object), typeof(object) }
)!;
for (int i = 0; i < methods.Length; i++)
{
if (methods[i] == null)
continue;
ilg!.Ldloc(typeof(Hashtable), "_tmp");
ilg.Ldstr(GetCSharpString(xmlMappings[i].Key));
ilg.Ldstr(GetCSharpString(methods[i]));
ilg.Call(Hashtable_set_Item);
}
}
GenerateHashtableGetEnd(fieldBuilder);
return fieldBuilder;
}
internal void GenerateSupportedTypes(Type[] types, TypeBuilder serializerContractTypeBuilder)
{
ilg = new CodeGenerator(serializerContractTypeBuilder);
ilg.BeginMethod(
typeof(bool),
"CanSerialize",
s_typeType,
s_typeString,
CodeGenerator.PublicOverrideMethodAttributes);
var uniqueTypes = new HashSet<Type>();
for (int i = 0; i < types.Length; i++)
{
Type type = types[i];
if (type == null)
continue;
if (!type.IsPublic && !type.IsNestedPublic)
continue;
if (!uniqueTypes.Add(type))
continue;
// DDB172141: Wrong generated CS for serializer of List<string> type
if (type.IsGenericType || type.ContainsGenericParameters)
continue;
ilg.Ldarg("type");
ilg.Ldc(type);
ilg.If(Cmp.EqualTo);
{
ilg.Ldc(true);
ilg.GotoMethodEnd();
}
ilg.EndIf();
}
ilg.Ldc(false);
ilg.GotoMethodEnd();
ilg.EndMethod();
}
internal string GenerateBaseSerializer(string baseSerializer, string readerClass, string writerClass, CodeIdentifiers classes)
{
baseSerializer = CodeIdentifier.MakeValid(baseSerializer);
baseSerializer = classes.AddUnique(baseSerializer, baseSerializer);
TypeBuilder baseSerializerTypeBuilder = CodeGenerator.CreateTypeBuilder(
_moduleBuilder!,
CodeIdentifier.GetCSharpName(baseSerializer),
TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.BeforeFieldInit,
typeof(XmlSerializer),
Type.EmptyTypes);
ConstructorInfo readerCtor = CreatedTypes[readerClass].GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg = new CodeGenerator(baseSerializerTypeBuilder);
ilg.BeginMethod(typeof(XmlSerializationReader),
"CreateReader",
Type.EmptyTypes,
Array.Empty<string>(),
CodeGenerator.ProtectedOverrideMethodAttributes);
ilg.New(readerCtor);
ilg.EndMethod();
ConstructorInfo writerCtor = CreatedTypes[writerClass].GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg.BeginMethod(typeof(XmlSerializationWriter),
"CreateWriter",
Type.EmptyTypes,
Array.Empty<string>(),
CodeGenerator.ProtectedOverrideMethodAttributes);
ilg.New(writerCtor);
ilg.EndMethod();
baseSerializerTypeBuilder.DefineDefaultConstructor(
MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
Type baseSerializerType = baseSerializerTypeBuilder.CreateType();
CreatedTypes.Add(baseSerializerType.Name, baseSerializerType);
return baseSerializer;
}
internal string GenerateTypedSerializer(string readMethod, string writeMethod, XmlMapping mapping, CodeIdentifiers classes, string baseSerializer, string readerClass, string writerClass)
{
string serializerName = CodeIdentifier.MakeValid(Accessor.UnescapeName(mapping.Accessor.Mapping!.TypeDesc!.Name));
serializerName = classes.AddUnique($"{serializerName}Serializer", mapping);
TypeBuilder typedSerializerTypeBuilder = CodeGenerator.CreateTypeBuilder(
_moduleBuilder!,
CodeIdentifier.GetCSharpName(serializerName),
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit,
CreatedTypes[baseSerializer],
Type.EmptyTypes
);
ilg = new CodeGenerator(typedSerializerTypeBuilder);
ilg.BeginMethod(
typeof(bool),
"CanDeserialize",
s_xmlReaderType,
s_xmlReaderString,
CodeGenerator.PublicOverrideMethodAttributes
);
if (mapping.Accessor.Any)
{
ilg.Ldc(true);
ilg.Stloc(ilg.ReturnLocal);
ilg.Br(ilg.ReturnLabel);
}
else
{
MethodInfo XmlReader_IsStartElement = typeof(XmlReader).GetMethod(
"IsStartElement",
CodeGenerator.InstanceBindingFlags,
new Type[] { typeof(string), typeof(string) }
)!;
ilg.Ldarg(ilg.GetArg("xmlReader"));
ilg.Ldstr(GetCSharpString(mapping.Accessor.Name));
ilg.Ldstr(GetCSharpString(mapping.Accessor.Namespace));
ilg.Call(XmlReader_IsStartElement);
ilg.Stloc(ilg.ReturnLocal);
ilg.Br(ilg.ReturnLabel);
}
ilg.MarkLabel(ilg.ReturnLabel);
ilg.Ldloc(ilg.ReturnLocal);
ilg.EndMethod();
if (writeMethod != null)
{
ilg = new CodeGenerator(typedSerializerTypeBuilder);
ilg.BeginMethod(
typeof(void),
"Serialize",
s_objectXmlSerializationWriterType,
s_objectToSerializeWriterString,
CodeGenerator.ProtectedOverrideMethodAttributes);
MethodInfo writerType_writeMethod = CreatedTypes[writerClass].GetMethod(
writeMethod,
CodeGenerator.InstanceBindingFlags,
new Type[] { (mapping is XmlMembersMapping) ? typeof(object[]) : typeof(object) }
)!;
ilg.Ldarg("writer");
ilg.Castclass(CreatedTypes[writerClass]);
ilg.Ldarg("objectToSerialize");
if (mapping is XmlMembersMapping)
{
ilg.ConvertValue(typeof(object), typeof(object[]));
}
ilg.Call(writerType_writeMethod);
ilg.EndMethod();
}
if (readMethod != null)
{
ilg = new CodeGenerator(typedSerializerTypeBuilder);
ilg.BeginMethod(
typeof(object),
"Deserialize",
s_xmlSerializationReaderType,
s_readerString,
CodeGenerator.ProtectedOverrideMethodAttributes);
MethodInfo readerType_readMethod = CreatedTypes[readerClass].GetMethod(
readMethod,
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg.Ldarg("reader");
ilg.Castclass(CreatedTypes[readerClass]);
ilg.Call(readerType_readMethod);
ilg.EndMethod();
}
typedSerializerTypeBuilder.DefineDefaultConstructor(CodeGenerator.PublicMethodAttributes);
Type typedSerializerType = typedSerializerTypeBuilder.CreateType();
CreatedTypes.Add(typedSerializerType.Name, typedSerializerType);
return typedSerializerType.Name;
}
private FieldBuilder GenerateTypedSerializers(Dictionary<string, string> serializers, TypeBuilder serializerContractTypeBuilder)
{
string privateName = "typedSerializers";
FieldBuilder fieldBuilder = GenerateHashtableGetBegin(privateName, "TypedSerializers", serializerContractTypeBuilder);
MethodInfo Hashtable_Add = typeof(Hashtable).GetMethod(
"Add",
CodeGenerator.InstanceBindingFlags,
new Type[] { typeof(object), typeof(object) }
)!;
foreach (string key in serializers.Keys)
{
ConstructorInfo ctor = CreatedTypes[(string)serializers[key]].GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg!.Ldloc(typeof(Hashtable), "_tmp");
ilg.Ldstr(GetCSharpString(key));
ilg.New(ctor);
ilg.Call(Hashtable_Add);
}
GenerateHashtableGetEnd(fieldBuilder);
return fieldBuilder;
}
//GenerateGetSerializer(serializers, xmlMappings);
private void GenerateGetSerializer(Dictionary<string, string> serializers, XmlMapping[] xmlMappings, TypeBuilder serializerContractTypeBuilder)
{
ilg = new CodeGenerator(serializerContractTypeBuilder);
ilg.BeginMethod(
typeof(XmlSerializer),
"GetSerializer",
s_typeType,
s_typeString,
CodeGenerator.PublicOverrideMethodAttributes);
for (int i = 0; i < xmlMappings.Length; i++)
{
if (xmlMappings[i] is XmlTypeMapping)
{
Type? type = xmlMappings[i].Accessor.Mapping!.TypeDesc!.Type;
if (type == null)
continue;
if (!type.IsPublic && !type.IsNestedPublic)
continue;
// DDB172141: Wrong generated CS for serializer of List<string> type
if (type.IsGenericType || type.ContainsGenericParameters)
continue;
ilg.Ldarg("type");
ilg.Ldc(type);
ilg.If(Cmp.EqualTo);
{
ConstructorInfo ctor = CreatedTypes[(string)serializers[xmlMappings[i].Key!]].GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg.New(ctor);
ilg.Stloc(ilg.ReturnLocal);
ilg.Br(ilg.ReturnLabel);
}
ilg.EndIf();
}
}
ilg.Load(null);
ilg.Stloc(ilg.ReturnLocal);
ilg.Br(ilg.ReturnLabel);
ilg.MarkLabel(ilg.ReturnLabel);
ilg.Ldloc(ilg.ReturnLocal);
ilg.EndMethod();
}
internal void GenerateSerializerContract(XmlMapping[] xmlMappings, Type[] types, string readerType, string[] readMethods, string writerType, string[] writerMethods, Dictionary<string, string> serializers)
{
TypeBuilder serializerContractTypeBuilder = CodeGenerator.CreateTypeBuilder(
_moduleBuilder!,
"XmlSerializerContract",
TypeAttributes.Public | TypeAttributes.BeforeFieldInit,
typeof(XmlSerializerImplementation),
Type.EmptyTypes
);
ilg = new CodeGenerator(serializerContractTypeBuilder);
PropertyBuilder propertyBuilder = serializerContractTypeBuilder.DefineProperty(
"Reader",
PropertyAttributes.None,
typeof(XmlSerializationReader),
null, null, null, null, null);
ilg.BeginMethod(
typeof(XmlSerializationReader),
"get_Reader",
Type.EmptyTypes,
Array.Empty<string>(),
CodeGenerator.PublicOverrideMethodAttributes | MethodAttributes.SpecialName);
propertyBuilder.SetGetMethod(ilg.MethodBuilder!);
ConstructorInfo ctor = CreatedTypes[readerType].GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg.New(ctor);
ilg.EndMethod();
ilg = new CodeGenerator(serializerContractTypeBuilder);
propertyBuilder = serializerContractTypeBuilder.DefineProperty(
"Writer",
PropertyAttributes.None,
typeof(XmlSerializationWriter),
null, null, null, null, null);
ilg.BeginMethod(
typeof(XmlSerializationWriter),
"get_Writer",
Type.EmptyTypes,
Array.Empty<string>(),
CodeGenerator.PublicOverrideMethodAttributes | MethodAttributes.SpecialName);
propertyBuilder.SetGetMethod(ilg.MethodBuilder!);
ctor = CreatedTypes[writerType].GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg.New(ctor);
ilg.EndMethod();
FieldBuilder readMethodsField = GeneratePublicMethods(nameof(readMethods), "ReadMethods", readMethods, xmlMappings, serializerContractTypeBuilder);
FieldBuilder writeMethodsField = GeneratePublicMethods("writeMethods", "WriteMethods", writerMethods, xmlMappings, serializerContractTypeBuilder);
FieldBuilder typedSerializersField = GenerateTypedSerializers(serializers, serializerContractTypeBuilder);
GenerateSupportedTypes(types, serializerContractTypeBuilder);
GenerateGetSerializer(serializers, xmlMappings, serializerContractTypeBuilder);
// Default ctor
ConstructorInfo baseCtor = typeof(XmlSerializerImplementation).GetConstructor(
CodeGenerator.InstanceBindingFlags,
Type.EmptyTypes
)!;
ilg = new CodeGenerator(serializerContractTypeBuilder);
ilg.BeginMethod(
typeof(void),
".ctor",
Type.EmptyTypes,
Array.Empty<string>(),
CodeGenerator.PublicMethodAttributes | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName
);
ilg.Ldarg(0);
ilg.Load(null);
ilg.StoreMember(readMethodsField);
ilg.Ldarg(0);
ilg.Load(null);
ilg.StoreMember(writeMethodsField);
ilg.Ldarg(0);
ilg.Load(null);
ilg.StoreMember(typedSerializersField);
ilg.Ldarg(0);
ilg.Call(baseCtor);
ilg.EndMethod();
// Instantiate type
Type serializerContractType = serializerContractTypeBuilder.CreateType();
CreatedTypes.Add(serializerContractType.Name, serializerContractType);
}
internal static bool IsWildcard(SpecialMapping mapping)
{
if (mapping is SerializableMapping)
return ((SerializableMapping)mapping).IsAny;
return mapping.TypeDesc!.CanBeElementValue;
}
internal void ILGenLoad(string source)
{
ILGenLoad(source, null);
}
internal void ILGenLoad(string source, Type? type)
{
if (source.StartsWith("o.@", StringComparison.Ordinal))
{
System.Diagnostics.Debug.Assert(memberInfos.ContainsKey(source.Substring(3)));
MemberInfo memInfo = memberInfos[source.Substring(3)];
ilg!.LoadMember(ilg.GetVariable("o"), memInfo);
if (type != null)
{
Type memType = (memInfo is FieldInfo) ? ((FieldInfo)memInfo).FieldType : ((PropertyInfo)memInfo).PropertyType;
ilg.ConvertValue(memType, type);
}
}
else
{
SourceInfo info = new SourceInfo(source, null, null, null, ilg!);
info.Load(type);
}
}
}
}
|