-
-
Notifications
You must be signed in to change notification settings - Fork 910
/
ComplexSerializerRegistry.cs
472 lines (387 loc) · 20.5 KB
/
ComplexSerializerRegistry.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System.IO;
using System.Runtime.Versioning;
using Stride.Core.AssemblyProcessor.Serializers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor
{
internal class ComplexSerializerRegistry
{
private static HashSet<string> forbiddenKeywords;
private static HashSet<IMemberDefinition> ignoredMembers;
static ComplexSerializerRegistry()
{
ignoredMembers = new HashSet<IMemberDefinition>();
forbiddenKeywords = new HashSet<string>(new[]
{ "obj", "stream", "mode",
"abstract", "event", "new", "struct",
"as", "explicit", "null", "switch",
"base", "extern", "object", "this",
"bool", "false", "operator", "throw",
"break", "finally", "out", "true",
"byte", "fixed", "override", "try",
"case", "float", "params", "typeof",
"catch", "for", "private", "uint",
"char", "foreach", "protected", "ulong",
"checked", "goto", "public", "unchecked",
"class", "if", "readonly", "unsafe",
"const", "implicit", "ref", "ushort",
"continue", "in", "return", "using",
"decimal", "int", "sbyte", "virtual",
"default", "interface", "sealed", "volatile",
"delegate", "internal", "short", "void",
"do", "is", "sizeof", "while",
"double", "lock", "stackalloc",
"else", "long", "static",
"enum", "namespace", "string" });
}
public List<TypeReference> ReferencedAssemblySerializerFactoryTypes { get; } = new List<TypeReference>();
public CecilSerializerContext Context { get; }
//private List<IDataSerializerFactory> serializerFactories = new List<IDataSerializerFactory>();
public string TargetFramework { get; }
public string ClassName { get; }
public AssemblyDefinition Assembly { get; }
public List<ICecilSerializerDependency> SerializerDependencies { get; } = new List<ICecilSerializerDependency>();
public List<ICecilSerializerFactory> SerializerFactories { get; } = new List<ICecilSerializerFactory>();
public ComplexSerializerRegistry(PlatformType platform, AssemblyDefinition assembly, TextWriter log)
{
Assembly = assembly;
ClassName = Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory";
// Register referenced assemblies serializer factory, so that we can call them recursively
foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences)
{
try
{
var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName);
var assemblySerializerFactoryType = GetSerializerFactoryType(referencedAssembly);
if (assemblySerializerFactoryType != null)
ReferencedAssemblySerializerFactoryTypes.Add(assemblySerializerFactoryType);
}
catch (AssemblyResolutionException)
{
continue;
}
}
// Find target framework and replicate it for serializer assembly.
var targetFrameworkAttribute = assembly.CustomAttributes
.FirstOrDefault(x => x.AttributeType.FullName == typeof(TargetFrameworkAttribute).FullName);
if (targetFrameworkAttribute != null)
{
TargetFramework = "\"" + (string)targetFrameworkAttribute.ConstructorArguments[0].Value + "\"";
var frameworkDisplayNameField = targetFrameworkAttribute.Properties.FirstOrDefault(x => x.Name == "FrameworkDisplayName");
if (frameworkDisplayNameField.Name != null)
{
TargetFramework += ", FrameworkDisplayName=\"" + (string)frameworkDisplayNameField.Argument.Value + "\"";
}
}
// Prepare serializer processors
Context = new CecilSerializerContext(platform, assembly, log);
var processors = new List<ICecilSerializerProcessor>();
// Import list of serializer registered by referenced assemblies
processors.Add(new ReferencedAssemblySerializerProcessor());
// Generate serializers for types tagged as serializable
processors.Add(new CecilComplexClassSerializerProcessor());
// Generate serializers for PropertyKey and ParameterKey
processors.Add(new PropertyKeySerializerProcessor());
// Update Engine (with AnimationData<T>)
processors.Add(new UpdateEngineProcessor());
// Profile serializers
processors.Add(new ProfileSerializerProcessor());
// Data contract aliases
processors.Add(new DataContractAliasProcessor());
// Apply each processor
foreach (var processor in processors)
processor.ProcessSerializers(Context);
}
private static TypeDefinition GetSerializerFactoryType(AssemblyDefinition referencedAssembly)
{
var assemblySerializerFactoryAttribute =
referencedAssembly.CustomAttributes.FirstOrDefault(
x =>
x.AttributeType.FullName ==
"Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
// No serializer factory?
if (assemblySerializerFactoryAttribute == null)
return null;
var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value;
if (typeReference == null)
return null;
return typeReference.Resolve();
}
private static string TypeNameWithoutGenericEnding(TypeReference type)
{
var typeName = type.Name;
// Remove generics ending (i.e. `1)
var genericCharIndex = typeName.LastIndexOf('`');
if (genericCharIndex != -1)
typeName = typeName.Substring(0, genericCharIndex);
return typeName;
}
public static string SerializerTypeName(TypeReference type, bool appendGenerics, bool appendSerializer)
{
var typeName = TypeNameWithoutGenericEnding(type);
// Prepend nested class
if (type.IsNested)
typeName = TypeNameWithoutGenericEnding(type.DeclaringType) + "_" + typeName;
// Prepend namespace
if (!String.IsNullOrEmpty(type.Namespace))
typeName = type.Namespace.Replace(".", String.Empty) + "_" + typeName;
// Append Serializer
if (appendSerializer)
typeName += "Serializer";
// Append Generics
if (appendGenerics)
typeName += type.GenerateGenerics();
return typeName;
}
public static string GetSerializerInstantiateMethodName(TypeReference serializerType, bool appendGenerics)
{
return "Instantiate_" + SerializerTypeName(serializerType, appendGenerics, false);
}
/// <summary>
/// Generates the generic constraints in a code form.
/// </summary>
/// <param name="type">The type.</param>
/// <returns></returns>
public static string GenerateGenericConstraints(TypeReference type)
{
if (!type.HasGenericParameters)
return String.Empty;
var result = new StringBuilder();
foreach (var genericParameter in type.GenericParameters)
{
// If no constraints, skip it
var hasContraints = genericParameter.HasReferenceTypeConstraint || genericParameter.HasNotNullableValueTypeConstraint || genericParameter.Constraints.Count > 0 || genericParameter.HasDefaultConstructorConstraint;
if (!hasContraints)
{
continue;
}
bool hasFirstContraint = false;
result.AppendFormat(" where {0}: ", genericParameter.Name);
// Where class/struct constraint must be before any other constraint
if (genericParameter.HasReferenceTypeConstraint)
{
result.AppendFormat("class");
hasFirstContraint = true;
}
else if (genericParameter.HasNotNullableValueTypeConstraint)
{
result.AppendFormat("struct");
hasFirstContraint = true;
}
foreach (var genericParameterConstraint in genericParameter.Constraints)
{
// Skip value type constraint
if (genericParameterConstraint.ConstraintType.FullName != typeof(ValueType).FullName)
{
if (hasFirstContraint)
{
result.Append(", ");
}
result.AppendFormat("{0}", genericParameterConstraint.ConstraintType.ConvertCSharp());
result.AppendLine();
hasFirstContraint = true;
}
}
// New constraint must be last
if (!genericParameter.HasNotNullableValueTypeConstraint && genericParameter.HasDefaultConstructorConstraint)
{
if (hasFirstContraint)
{
result.Append(", ");
}
result.AppendFormat("new()");
result.AppendLine();
}
}
return result.ToString();
}
public static void IgnoreMember(IMemberDefinition memberInfo)
{
ignoredMembers.Add(memberInfo);
}
public static IEnumerable<SerializableItem> GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null)
{
foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride))
{
var serializableItem = serializableItemOriginal;
// Try to resolve open generic types with context to have closed types.
if (serializableItem.Type.ContainsGenericParameter())
{
serializableItem.Type = ResolveGenericsVisitor.Process(type, serializableItem.Type);
}
yield return serializableItem;
}
}
public static IEnumerable<SerializableItem> GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null)
{
ComplexTypeSerializerFlags flags;
var fields = new List<FieldDefinition>();
var properties = new List<PropertyDefinition>();
foreach (var field in type.Fields)
{
if (field.IsStatic)
continue;
if (ignoredMembers.Contains(field))
continue;
if (field.IsPublic || (field.IsAssembly && field.CustomAttributes.Any(a => a.AttributeType.FullName == "Stride.Core.DataMemberAttribute")))
fields.Add(field);
}
// If there is a explicit or sequential layout, sort by offset
if (type.IsSequentialLayout || type.IsExplicitLayout)
fields.Sort((x,y) => x.Offset.CompareTo(y.Offset));
foreach (var property in type.Properties)
{
if (IsAccessibleThroughAccessModifiers(property) == false)
continue;
// Need a non-static public get method
if (property.GetMethod.IsStatic)
continue;
// If it's a struct (!IsValueType), we need a set method as well
if (property.PropertyType.IsValueType && property.SetMethod == null)
continue;
// Only take virtual properties (override ones will be handled by parent serializers)
if (property.GetMethod.IsVirtual && !property.GetMethod.IsNewSlot)
{
// Exception: if this one has a DataMember, let's assume parent one was Ignore and we explicitly want to serialize this one
if (!property.CustomAttributes.Any(x => x.AttributeType.FullName == "Stride.Core.DataMemberAttribute"))
continue;
}
// Ignore blacklisted properties
if (ignoredMembers.Contains(property))
continue;
properties.Add(property);
}
if (flagsOverride.HasValue)
flags = flagsOverride.Value;
else if (type.IsClass && !type.IsValueType)
flags = ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties;
else if (type.Fields.Any(x => x.IsPublic && !x.IsStatic))
flags = ComplexTypeSerializerFlags.SerializePublicFields;
else
flags = ComplexTypeSerializerFlags.SerializePublicProperties;
// Find default member mode (find DataContractAttribute in the hierarchy)
var defaultMemberMode = DataMemberMode.Default;
var currentType = type;
while (currentType != null)
{
var dataContractAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute");
if (dataContractAttribute != null)
{
var dataMemberModeArg = dataContractAttribute.Properties.FirstOrDefault(x => x.Name == "DefaultMemberMode").Argument;
if (dataMemberModeArg.Value != null)
{
defaultMemberMode = (DataMemberMode)(int)dataMemberModeArg.Value;
break;
}
}
currentType = currentType.BaseType?.Resolve();
}
if ((flags & ComplexTypeSerializerFlags.SerializePublicFields) != 0)
{
foreach (var field in fields)
{
if (IsMemberIgnored(field.CustomAttributes, flags, defaultMemberMode)) continue;
var attributes = field.CustomAttributes;
var fixedAttribute = field.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == typeof(FixedBufferAttribute).FullName);
var assignBack = !field.IsInitOnly;
// If not assigned back, check that type is serializable in place
if (!assignBack && !IsReadOnlyTypeSerializable(field.FieldType))
continue;
yield return new SerializableItem { MemberInfo = field, Type = field.FieldType, Name = field.Name, Attributes = attributes, AssignBack = assignBack, NeedReference = false, HasFixedAttribute = fixedAttribute != null };
}
}
if ((flags & ComplexTypeSerializerFlags.SerializePublicProperties) != 0)
{
// Only process properties with public get and set methods
foreach (var property in properties)
{
// Ignore properties with indexer
if (property.GetMethod.Parameters.Count > 0)
continue;
if (IsMemberIgnored(property.CustomAttributes, flags, defaultMemberMode)) continue;
var attributes = property.CustomAttributes;
var assignBack = property.SetMethod != null && (property.SetMethod.IsPublic || property.SetMethod.IsAssembly);
// If not assigned back, check that type is serializable in place
if (!assignBack && !IsReadOnlyTypeSerializable(property.PropertyType))
continue;
yield return new SerializableItem { MemberInfo = property, Type = property.PropertyType, Name = property.Name, Attributes = attributes, AssignBack = assignBack, NeedReference = !type.IsClass || type.IsValueType };
}
}
}
static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property)
{
var get = property.GetMethod;
var set = property.SetMethod;
if (get == null)
return false;
bool forced = property.CustomAttributes.Any(a => a.AttributeType.FullName == "Stride.Core.DataMemberAttribute");
if (forced && (get.IsPublic || get.IsAssembly))
return true;
// By default, allow access for get-only auto-property, i.e.: { get; } but not { get => }
// as the later may create side effects, and without a setter, we can't 'set' as a fallback for those exceptional cases.
if (get.IsPublic)
return set?.IsPublic == true || set == null && property.DeclaringType.Fields.Any(x => x.Name == $"<{property.Name}>k__BackingField");
return false;
}
internal static bool IsMemberIgnored(ICollection<CustomAttribute> customAttributes, ComplexTypeSerializerFlags flags, DataMemberMode dataMemberMode)
{
// Check for DataMemberIgnore
if (customAttributes.Any(x => x.AttributeType.FullName == "Stride.Core.DataMemberIgnoreAttribute"))
{
// Still allow members with DataMemberUpdatable if we are running UpdateEngineProcessor
if (!((flags & ComplexTypeSerializerFlags.Updatable) != 0
&& customAttributes.Any(x => x.AttributeType.FullName == "Stride.Updater.DataMemberUpdatableAttribute")))
return true;
}
var dataMemberAttribute = customAttributes.FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.DataMemberAttribute");
if (dataMemberAttribute != null)
{
var dataMemberModeArg = dataMemberAttribute.ConstructorArguments.FirstOrDefault(x => x.Type.Name == nameof(DataMemberMode));
if (dataMemberModeArg.Value != null)
{
dataMemberMode = (DataMemberMode)(int)dataMemberModeArg.Value;
}
else
{
// Default value if not specified in .ctor
dataMemberMode = DataMemberMode.Default;
}
}
// Ignored?
if (dataMemberMode == DataMemberMode.Never)
return true;
return false;
}
private static bool IsReadOnlyTypeSerializable(TypeReference type)
{
// For now, we allow any non-valuetype (class & interface) which is not a string (since they are immutable)
return type.MetadataType != MetadataType.String
// sometimes class/valuetype is not properly set in some reference types (not sure if it was the exact same case)
&& !((type.MetadataType == MetadataType.ValueType || type.MetadataType == MetadataType.Class) && type.Resolve().IsValueType);
}
public static string CreateMemberVariableName(IMemberDefinition memberInfo)
{
var memberVariableName = Char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name.Substring(1);
if (forbiddenKeywords.Contains(memberVariableName))
memberVariableName += "_";
return memberVariableName;
}
public struct SerializableItem
{
public bool HasFixedAttribute;
public string Name;
public IMemberDefinition MemberInfo;
public TypeReference Type { get; set; }
public bool NeedReference;
public bool AssignBack;
public IList<CustomAttribute> Attributes;
}
}
}