Skip to content

Commit

Permalink
Merge pull request #341 from PHOENIXCONTACT/fix/entrySerializeOnClass…
Browse files Browse the repository at this point in the history
…AndProperty

If you put a EntrySerialize over a Resource itself and also use it in one of the properties, there will be a StackOverflow Exception
  • Loading branch information
Toxantron authored Oct 26, 2023
2 parents 8d0028b + 1aa6a40 commit d356d90
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 8 deletions.
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

0 comments on commit d356d90

Please sign in to comment.