Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If you put a EntrySerialize over a Resource itself and also use it in one of the properties, there will be a StackOverflow Exception #341

Merged
merged 3 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Moryx/Serialization/DefaultSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public virtual EntryPrototype[] Prototypes(Type memberType, ICustomAttributeProv
}
else
{
// check if this member is abstract
if (memberType.IsAbstract) return prototypes.ToArray();

var prototype = Activator.CreateInstance(memberType);
if (memberType.IsClass)
ValueProviderExecutor.Execute(prototype, new ValueProviderExecutorSettings().AddDefaultValueProvider());
Expand Down
5 changes: 5 additions & 0 deletions src/Moryx/Serialization/EntryConvert/EntryConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@ public static Entry EncodeObject(object instance, ICustomSerialization customSer
var filtered = customSerialization.GetProperties(instance.GetType());
foreach (var property in filtered)
{
//exclude property that can lead to self reference
var propertyType = property.PropertyType;
if (propertyType == instanceType)
continue;

var convertedProperty = EncodeProperty(property, customSerialization);

object value;
Expand Down
32 changes: 24 additions & 8 deletions src/Moryx/Serialization/EntrySerializeSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ public EntrySerializeSerialization(Type filterBaseType)
public override IEnumerable<ConstructorInfo> GetConstructors(Type sourceType)
{
var constructors = from ctor in base.GetConstructors(sourceType)
let mode = EvaluateSerializeMode(ctor)
where mode.HasValue && mode.Value == EntrySerializeMode.Always
select ctor;
let mode = EvaluateSerializeMode(ctor)
where mode.HasValue && mode.Value == EntrySerializeMode.Always
select ctor;

return constructors;
}
Expand All @@ -54,9 +54,9 @@ public override IEnumerable<MethodInfo> GetMethods(Type sourceType)
{
var methods = sourceType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(m => !m.IsSpecialName);
methods = from method in methods
let mode = EvaluateSerializeMode(method)
where mode.HasValue && mode.Value == EntrySerializeMode.Always
select method;
let mode = EvaluateSerializeMode(method)
where mode.HasValue && mode.Value == EntrySerializeMode.Always
select method;

return methods;
}
Expand All @@ -66,7 +66,6 @@ public override IEnumerable<PropertyInfo> GetProperties(Type sourceType)
{
var properties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(BasePropertyFilter).Where(ExplicitPropertyFilter).ToArray();

return EntrySerializeAttributeFilter(sourceType, properties);
}

Expand Down Expand Up @@ -147,6 +146,23 @@ private static IEnumerable<PropertyInfo> EntrySerializeAttributeFilter(Type sour
return properties;
}

/// <summary>
/// Iterate the inheritance tree and find lowest declaration of the attribute
/// </summary>
private static EntrySerializeMode? EvaluateSerializeMode(Type attributeProvider)
{
// If more than 1 is declared, determine the lowest definition as it takes precedence
// For each declaration check assignability to determine lower type
var currentType = attributeProvider;
EntrySerializeAttribute lowestDeclaration = null;
while (currentType != typeof(object))
{
lowestDeclaration = currentType.GetCustomAttribute<EntrySerializeAttribute>(false) ?? lowestDeclaration;
currentType = currentType.BaseType;
}
return lowestDeclaration?.Mode;
}

/// <summary>
/// Checks if the <see cref="EntrySerializeAttribute"/> is existent and activated
/// </summary>
Expand All @@ -161,7 +177,7 @@ private static PropertyMode EvaluateSerializeMode(PropertyInfo property)
return new PropertyMode
{
Property = property,
Mode = EvaluateSerializeMode((ICustomAttributeProvider)property)
Mode = property.GetCustomAttribute<EntrySerializeAttribute>(true)?.Mode
};
}

Expand Down
23 changes: 23 additions & 0 deletions src/Tests/Moryx.Tests/Serialization/EntrySerializeDummies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ public class EntrySerialize_NeverClassNoMember
public string NullMethod2() => "1234";
}

[EntrySerialize]
public class EntrySerialize_AlwaysClassAlwaysMember
{
[EntrySerialize]
public string AlwaysProperty { get; set; } = "123456";
[EntrySerialize]
public int Property1 { get; set; }

[EntrySerialize]
public EntrySerialize_AlwaysClassAlwaysMember AnotherProperty { get; set; }

internal IExplicitInterface ExplicitInterface { get; }
}

// ReSharper disable once InconsistentNaming
[EntrySerialize(EntrySerializeMode.Never)]
public class EntrySerialize_NeverClassAlwaysMember
Expand Down Expand Up @@ -103,6 +117,15 @@ public class EntrySerialize_Inherited : EntrySerialize_InheritedBase
public bool NullProperty3 { get; set; } = false;
}

[EntrySerialize]
public class AlwaysClass_Inherited : EntrySerialize_InheritedBase
{
[EntrySerialize]
public string NullProperty2 { get; set; } = "789456";

public bool NullProperty3 { get; set; } = false;
}

public class EntrySerialize_Methods : EntrySerialize_InheritedBase
{
[EntrySerialize]
Expand Down
34 changes: 34 additions & 0 deletions src/Tests/Moryx.Tests/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Moryx.Configuration;
using Moryx.Serialization;
Expand Down Expand Up @@ -394,6 +395,39 @@ public void SurviveGetterException()
Assert.NotNull(encoded.SubEntries[0].Value.Current);
}


[Test(Description = "Class with EntrySerializationAttribute should not Override the attribute on the Base Class Resource|PublicResource")]
public void ClassWithSerializationAttribute_AndBaseClass()
{
// Arrange
var serialization = new EntrySerializeSerialization { FormatProvider = new CultureInfo("en-US") };
var dummy = new AlwaysClass_Inherited();

// Act
var encoded = EntryConvert.EncodeObject(dummy, serialization);

// Assert
var alwaysProperties = 1;
Assert.That(encoded.SubEntries.Count, Is.EqualTo(alwaysProperties));
}

[Test(Description = "Class with EntrySerializationAttribute should not Override the attribute on the Base Class Resource|PublicResource")]
public void ClassWithSerializationAttribute()
{
// Arrange
var serialization = new EntrySerializeSerialization { FormatProvider = new CultureInfo("en-US") };
var dummy = new EntrySerialize_AlwaysClassAlwaysMember();

// Act
var encoded = EntryConvert.EncodeObject(dummy, serialization);

// Assert
var alwaysProperties = 3;
Assert.That(encoded.SubEntries.Count, Is.EqualTo(alwaysProperties));
}



[TestCase(CollectionType.Array, 3, 2)]
[TestCase(CollectionType.Array, 0, 4)]
[TestCase(CollectionType.List, 5, 3)]
Expand Down
Loading