Skip to content

Commit 0e3bbac

Browse files
committed
Fix nullable field types due to compiler optimizations
1 parent 7bed5cf commit 0e3bbac

File tree

2 files changed

+82
-23
lines changed

2 files changed

+82
-23
lines changed

YamlDotNet.Test/Serialization/DeserializerTest.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,55 @@ public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNot
374374
}
375375

376376
[Fact]
377-
public void EnforceNulalbleTypesWhenNullThrowsException()
377+
public void EnforceNullableWhenClassIsDefaultNullableThrows()
378+
{
379+
var deserializer = new DeserializerBuilder().WithEnforceNullability().Build();
380+
var yaml = @"
381+
TestString: null
382+
TestBool: null
383+
TestBool1: null
384+
";
385+
try
386+
{
387+
var test = deserializer.Deserialize<NullableDefaultClass>(yaml);
388+
}
389+
catch (YamlException e)
390+
{
391+
if (e.InnerException is NullReferenceException)
392+
{
393+
return;
394+
}
395+
}
396+
397+
throw new Exception("Non nullable property was set to null.");
398+
}
399+
400+
[Fact]
401+
public void EnforceNullableWhenClassIsNotDefaultNullableThrows()
402+
{
403+
var deserializer = new DeserializerBuilder().WithEnforceNullability().Build();
404+
var yaml = @"
405+
TestString: null
406+
TestBool: null
407+
TestBool1: null
408+
";
409+
try
410+
{
411+
var test = deserializer.Deserialize<NullableNotDefaultClass>(yaml);
412+
}
413+
catch (YamlException e)
414+
{
415+
if (e.InnerException is NullReferenceException)
416+
{
417+
return;
418+
}
419+
}
420+
421+
throw new Exception("Non nullable property was set to null.");
422+
}
423+
424+
[Fact]
425+
public void EnforceNullableTypesWhenNullThrowsException()
378426
{
379427
var deserializer = new DeserializerBuilder().WithEnforceNullability().Build();
380428
var yaml = @"
@@ -589,6 +637,20 @@ public enum EnumMemberedEnum
589637
#endif
590638

591639
#nullable enable
640+
public class NullableDefaultClass
641+
{
642+
public string? TestString { get; set; }
643+
public string? TestBool { get; set; }
644+
public string TestBool1 { get; set; } = "";
645+
}
646+
647+
public class NullableNotDefaultClass
648+
{
649+
public string? TestString { get; set; }
650+
public string TestBool { get; set; } = "";
651+
public string TestBool1 { get; set; } = "";
652+
}
653+
592654
public class NonNullableClass
593655
{
594656
public string Test { get; set; } = "Some default value";

YamlDotNet/ReflectionExtensions.cs

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -309,35 +309,32 @@ public static Attribute[] GetAllCustomAttributes<TAttribute>(this PropertyInfo m
309309

310310
public static bool AcceptsNull(this MemberInfo member)
311311
{
312-
var result = true; //default to allowing nulls, this will be set to false if there is a null context on the type
313312
#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-
});
313+
var classAttributes = member.DeclaringType.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableContextAttribute), true);
314+
var defaultFlag = classAttributes.OfType<System.Runtime.CompilerServices.NullableContextAttribute>().FirstOrDefault()?.Flag ?? 0;
319315

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-
}
316+
// we have a nullable context on that type, only allow null if the NullableAttribute is on the member.
317+
var memberAttributes = member.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableAttribute), true);
318+
var nullableFlag = memberAttributes.OfType<System.Runtime.CompilerServices.NullableAttribute>().FirstOrDefault()?.NullableFlags.Any(flag => flag == 2);
319+
var result = nullableFlag ?? defaultFlag == 2;
326320

327321
return result;
328322
#else
329-
var typeHasNullContext = TypesHaveNullContext.GetOrAdd(member.DeclaringType, (Type t) =>
323+
var classAttributes = member.DeclaringType.GetCustomAttributes(true);
324+
var classAttribute = classAttributes.FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
325+
var defaultFlag = 0;
326+
if (classAttribute != null)
330327
{
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");
328+
var classAttributeType = classAttribute.GetType();
329+
var classProperty = classAttributeType.GetProperty("Flag")!;
330+
defaultFlag = (byte)classProperty.GetValue(classAttribute)!;
339331
}
340-
332+
var memberAttributes = member.GetCustomAttributes(true);
333+
var memberAttribute = memberAttributes.FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
334+
var memberAttributeType = memberAttribute?.GetType();
335+
var memberProperty = memberAttributeType?.GetProperty("NullableFlags")!;
336+
var flags = (byte[])memberProperty.GetValue(memberAttribute)!;
337+
var result = flags.Any(x => x == 2) || defaultFlag == 2;
341338
return result;
342339
#endif
343340
}

0 commit comments

Comments
 (0)