Skip to content

Commit 380e2c4

Browse files
authored
Merge pull request #1025 from EdwardCooke/ec-1018-nullablefixes
Fix nullable field types due to compiler optimizations +semver:fix
2 parents d6a3c1f + 60536ac commit 380e2c4

File tree

4 files changed

+82
-29
lines changed

4 files changed

+82
-29
lines changed

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,3 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
140140

141141
Please see the Releases at https://github.com/aaubry/YamlDotNet/releases
142142

143-
## Sponsorships
144-
A big shout out to all of our sponsors
145-
146-
[![AWS](Sponsors/aws-logo-small.png)](https://www.github.com/aws)

Sponsors/aws-logo-small.png

-8.1 KB
Binary file not shown.

YamlDotNet.Test/Serialization/DeserializerTest.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,55 @@ public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNot
395395
}
396396

397397
[Fact]
398-
public void EnforceNulalbleTypesWhenNullThrowsException()
398+
public void EnforceNullableWhenClassIsDefaultNullableThrows()
399+
{
400+
var deserializer = new DeserializerBuilder().WithEnforceNullability().Build();
401+
var yaml = @"
402+
TestString: null
403+
TestBool: null
404+
TestBool1: null
405+
";
406+
try
407+
{
408+
var test = deserializer.Deserialize<NullableDefaultClass>(yaml);
409+
}
410+
catch (YamlException e)
411+
{
412+
if (e.InnerException is NullReferenceException)
413+
{
414+
return;
415+
}
416+
}
417+
418+
throw new Exception("Non nullable property was set to null.");
419+
}
420+
421+
[Fact]
422+
public void EnforceNullableWhenClassIsNotDefaultNullableThrows()
423+
{
424+
var deserializer = new DeserializerBuilder().WithEnforceNullability().Build();
425+
var yaml = @"
426+
TestString: null
427+
TestBool: null
428+
TestBool1: null
429+
";
430+
try
431+
{
432+
var test = deserializer.Deserialize<NullableNotDefaultClass>(yaml);
433+
}
434+
catch (YamlException e)
435+
{
436+
if (e.InnerException is NullReferenceException)
437+
{
438+
return;
439+
}
440+
}
441+
442+
throw new Exception("Non nullable property was set to null.");
443+
}
444+
445+
[Fact]
446+
public void EnforceNullableTypesWhenNullThrowsException()
399447
{
400448
var deserializer = new DeserializerBuilder().WithEnforceNullability().Build();
401449
var yaml = @"
@@ -610,6 +658,20 @@ public enum EnumMemberedEnum
610658
#endif
611659

612660
#nullable enable
661+
public class NullableDefaultClass
662+
{
663+
public string? TestString { get; set; }
664+
public string? TestBool { get; set; }
665+
public string TestBool1 { get; set; } = "";
666+
}
667+
668+
public class NullableNotDefaultClass
669+
{
670+
public string? TestString { get; set; }
671+
public string TestBool { get; set; } = "";
672+
public string TestBool1 { get; set; } = "";
673+
}
674+
613675
public class NonNullableClass
614676
{
615677
public string Test { get; set; } = "Some default value";

YamlDotNet/ReflectionExtensions.cs

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -305,39 +305,34 @@ public static Attribute[] GetAllCustomAttributes<TAttribute>(this PropertyInfo m
305305
return Attribute.GetCustomAttributes(member, typeof(TAttribute), inherit: true);
306306
}
307307

308-
private static readonly ConcurrentDictionary<Type, bool> TypesHaveNullContext = new();
309-
310308
public static bool AcceptsNull(this MemberInfo member)
311309
{
312-
var result = true; //default to allowing nulls, this will be set to false if there is a null context on the type
313310
#if NET8_0_OR_GREATER
314-
var typeHasNullContext = TypesHaveNullContext.GetOrAdd(member.DeclaringType, (Type t) =>
315-
{
316-
var attributes = t.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableContextAttribute), true);
317-
return (attributes?.Length ?? 0) > 0;
318-
});
311+
var classAttributes = member.DeclaringType.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableContextAttribute), true);
312+
var defaultFlag = classAttributes.OfType<System.Runtime.CompilerServices.NullableContextAttribute>().FirstOrDefault()?.Flag ?? 0;
319313

320-
if (typeHasNullContext)
321-
{
322-
// we have a nullable context on that type, only allow null if the NullableAttribute is on the member.
323-
var memberAttributes = member.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableAttribute), true);
324-
result = (memberAttributes?.Length ?? 0) > 0;
325-
}
314+
// we have a nullable context on that type, only allow null if the NullableAttribute is on the member.
315+
var memberAttributes = member.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableAttribute), true);
316+
var nullableFlag = memberAttributes.OfType<System.Runtime.CompilerServices.NullableAttribute>().FirstOrDefault()?.NullableFlags.Any(flag => flag == 2);
317+
var result = nullableFlag ?? defaultFlag == 2;
326318

327319
return result;
328320
#else
329-
var typeHasNullContext = TypesHaveNullContext.GetOrAdd(member.DeclaringType, (Type t) =>
321+
var classAttributes = member.DeclaringType.GetCustomAttributes(true);
322+
var classAttribute = classAttributes.FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
323+
var defaultFlag = 0;
324+
if (classAttribute != null)
330325
{
331-
var attributes = t.GetCustomAttributes(true);
332-
return attributes.Any(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
333-
});
334-
335-
if (typeHasNullContext)
336-
{
337-
var memberAttributes = member.GetCustomAttributes(true);
338-
result = memberAttributes.Any(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
326+
var classAttributeType = classAttribute.GetType();
327+
var classProperty = classAttributeType.GetProperty("Flag")!;
328+
defaultFlag = (byte)classProperty.GetValue(classAttribute)!;
339329
}
340-
330+
var memberAttributes = member.GetCustomAttributes(true);
331+
var memberAttribute = memberAttributes.FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
332+
var memberAttributeType = memberAttribute?.GetType();
333+
var memberProperty = memberAttributeType?.GetProperty("NullableFlags")!;
334+
var flags = (byte[])memberProperty.GetValue(memberAttribute)!;
335+
var result = flags.Any(x => x == 2) || defaultFlag == 2;
341336
return result;
342337
#endif
343338
}

0 commit comments

Comments
 (0)