Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Expose nullability info from Reflection
  • Loading branch information
buyaa-n committed Jun 2, 2021
commit 2f7d8e6a86a19a0195ff02b3e4dc146e49811131
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Missing.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Module.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ModuleResolveEventHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Nullability\NullabilityInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Nullability\NullabilityInfoContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ObfuscateAssemblyAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ObfuscationAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ParameterAttributes.cs" />
Expand Down Expand Up @@ -2062,18 +2064,12 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetPid.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetPid.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Process.GetProcInfo.cs" Condition="'$(TargetsFreeBSD)' == 'true'"
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
<Compile Include="$(CommonPath)Interop\BSD\System.Native\Interop.Sysctl.cs" Condition="'$(TargetsFreeBSD)' == 'true'"
Link="Common\Interop\BSD\System.Native\Interop.Sysctl.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs" Condition="'$(TargetsLinux)' == 'true'"
Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)System\IO\StringParser.cs" Condition="'$(TargetsLinux)' == 'true'"
Link="Common\System\IO\StringParser.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libproc.GetProcessInfoById.cs" Condition="'$(IsOSXLike)' == 'true'"
Link="Common\Interop\OSX\Interop.libproc.GetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'"
Link="Common\Interop\SunOS\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" />
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Process.GetProcInfo.cs" Condition="'$(TargetsFreeBSD)' == 'true'" Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
<Compile Include="$(CommonPath)Interop\BSD\System.Native\Interop.Sysctl.cs" Condition="'$(TargetsFreeBSD)' == 'true'" Link="Common\Interop\BSD\System.Native\Interop.Sysctl.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs" Condition="'$(TargetsLinux)' == 'true'" Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)System\IO\StringParser.cs" Condition="'$(TargetsLinux)' == 'true'" Link="Common\System\IO\StringParser.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libproc.GetProcessInfoById.cs" Condition="'$(IsOSXLike)' == 'true'" Link="Common\Interop\OSX\Interop.libproc.GetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'" Link="Common\Interop\SunOS\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.FreeBSD.cs" Condition="'$(TargetsFreeBSD)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Linux.cs" Condition="'$(TargetsLinux)' == 'true'" />
Expand Down Expand Up @@ -2102,8 +2098,7 @@
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetsMacCatalyst)' == 'true'">
<Compile Include="$(CommonPath)Interop\OSX\System.Native\Interop.iOSSupportVersion.cs"
Link="Common\Interop\OSX\System.Native\Interop.iOSSupportVersion.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Native\Interop.iOSSupportVersion.cs" Link="Common\Interop\OSX\System.Native\Interop.iOSSupportVersion.cs" />
</ItemGroup>
<ItemGroup Condition="'$(SupportsX86Intrinsics)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Aes.cs" />
Expand Down Expand Up @@ -2184,8 +2179,7 @@
<ItemGroup Condition="'$(FeatureObjCMarshal)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ObjectiveC\ObjectiveCMarshal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ObjectiveC\ObjectiveCTrackedTypeAttribute.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Native\Interop.AutoreleasePool.cs"
Link="Common\Interop\OSX\System.Native\Interop.AutoreleasePool.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Native\Interop.AutoreleasePool.cs" Link="Common\Interop\OSX\System.Native\Interop.AutoreleasePool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\AutoreleasePool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolWorkQueue.AutoreleasePool.OSX.cs" />
</ItemGroup>
Expand All @@ -2212,4 +2206,4 @@
<Link>Interop\Windows\Kernel32\Interop.Threading.cs</Link>
</Compile>
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.ObjectModel;

namespace System.Reflection
{
public sealed class NullabilityInfo
{
/*internal NullabilityInfo(Type type, NullableState state)
{
Type = type;
ReadState = state;
WriteState = state;
}

internal NullabilityInfo(Type type, NullableState readState, NullableState writeState)
{
Type = type;
ReadState = readState;
WriteState = writeState;
}*/

internal NullabilityInfo(Type type, NullableState readState, NullableState writeState,
ReadOnlyCollection<NullableState>? arrayElements, ReadOnlyCollection<NullableState>? typeArguments)
{
Type = type;
ReadState = readState;
WriteState = writeState;
ArrayElements = arrayElements;
TypeArguments = typeArguments;
}

public Type Type { get; }
public NullableState ReadState { get; internal set; }
public NullableState WriteState { get; internal set; }
public ReadOnlyCollection<NullableState>? ArrayElements { get; }
public ReadOnlyCollection<NullableState>? TypeArguments { get; }
}

public enum NullableState
{
Unknown,
NonNullable,
Nullable,
NotNullableWhen, // Has NotNullWhenAttribute or NotNullIfNotNullAttribute, check CustomAttributes for the attribute and value
NullableWhen // Has MaybeNullWhenAttribute check CustomAttributes for the value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace System.Reflection
{
public sealed class NullabilityInfoContext
{
private static NullableState GetNullableContext(MemberInfo? memberInfo)
{
while (memberInfo != null)
{
var attributes = memberInfo.GetCustomAttributesData();
foreach (var attribute in attributes)
{
if (attribute.AttributeType.Name == "NullableContextAttribute" &&
attribute.AttributeType.Namespace == "System.Runtime.CompilerServices" &&
attribute.ConstructorArguments.Count == 1)
{
return TranslateByte(attribute.ConstructorArguments[0].Value);
}
}

memberInfo = memberInfo.DeclaringType;
}

return NullableState.Unknown;
}

public NullabilityInfo Create(ParameterInfo parameterInfo)
{
return GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parameterInfo.GetCustomAttributesData());
}

public NullabilityInfo Create(PropertyInfo propertyInfo)
{
var nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, propertyInfo.GetCustomAttributesData());
var getterAttributes = propertyInfo.GetGetMethod()?.ReturnParameter.GetCustomAttributesData();
var setterAttributes = propertyInfo.GetSetMethod()?.GetParameters()[0].GetCustomAttributesData();

if (getterAttributes != null)
{
foreach (var attribute in getterAttributes)
{
if (nullability.ReadState == NullableState.Nullable)
{
if (attribute.AttributeType.Name == "NotNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.ReadState = NullableState.NonNullable;
break;
}
}
else if (nullability.ReadState == NullableState.NonNullable)
{
if (attribute.AttributeType.Name == "MaybeNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.ReadState = NullableState.Nullable;
break;
}
}
}
}

if (setterAttributes != null)
{
foreach (var attribute in setterAttributes)
{
if (nullability.WriteState == NullableState.Nullable)
{
if (attribute.AttributeType.Name == "DisallowNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.WriteState = NullableState.NonNullable;
break;
}
}
else if (nullability.WriteState == NullableState.NonNullable)
{
if (attribute.AttributeType.Name == "AllowNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.WriteState = NullableState.Nullable;
break;
}
}
}
}

return nullability;
}

public NullabilityInfo Create(EventInfo eventInfo)
{
return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, eventInfo.GetCustomAttributesData());
}

public NullabilityInfo Create(FieldInfo fieldInfo)
{
var attributes = fieldInfo.GetCustomAttributesData();
var nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, attributes);

if (attributes != null)
{
foreach (var attribute in attributes)
{
if (nullability.ReadState == NullableState.Nullable)
{
if (attribute.AttributeType.Name == "NotNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.ReadState = NullableState.NonNullable;
break;
}
}
else if (nullability.ReadState == NullableState.NonNullable)
{
if (attribute.AttributeType.Name == "MaybeNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.ReadState = NullableState.Nullable;
break;
}
}

if (nullability.WriteState == NullableState.Nullable)
{
if (attribute.AttributeType.Name == "DisallowNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.WriteState = NullableState.NonNullable;
break;
}
}
else if (nullability.WriteState == NullableState.NonNullable)
{
if (attribute.AttributeType.Name == "AllowNullAttribute" &&
attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
{
nullability.WriteState = NullableState.Nullable;
break;
}
}
}
}

return nullability;
}

private static NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList<CustomAttributeData> customAttributes)
{
var offset = 0;
return GetNullabilityInfo(memberInfo, type, customAttributes, ref offset);
}

private static NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, IList<CustomAttributeData> customAttributes, ref int offset)
{
NullableState state = NullableState.Unknown;
bool found = false;
if (type.IsValueType)
{
var underlyingType = Nullable.GetUnderlyingType(type);
if ( underlyingType != null)
{
type = underlyingType;
state = NullableState.Nullable;
found = true;
}
}
else
{
foreach (var attribute in customAttributes)
{
if (attribute.AttributeType.Name == "NullableAttribute" &&
attribute.AttributeType.Namespace == "System.Runtime.CompilerServices" &&
attribute.ConstructorArguments.Count == 1)
{
var o = attribute.ConstructorArguments[0].Value;

if (o is byte b)
{
found = true;
state = TranslateByte(b);
}
else if (o is ReadOnlyCollection<CustomAttributeTypedArgument> args)
if (offset < args.Count &&
args[offset].Value is byte elementB)
state = TranslateByte(elementB);

break;
}
}

if (!found)
{
state = GetNullableContext(memberInfo);
}
}

// We consumed one element in the nullable array.
offset++;

ReadOnlyCollection<NullableState>? elementsState = null;
ReadOnlyCollection<NullableState>? genericArgumentsState = null;

if (type.IsArray)
{
var elements = new List<NullableState>();
var elementType = type.GetElementType()!;
int i = 0;
do
{
// add to elements
var n = GetNullabilityInfo(memberInfo, elementType, elementType.GetCustomAttributesData(), ref offset);
elementType = type.GetElementType()!;
elements[i++] = n.ReadState;
} while (type.IsArray);

elementsState = elements.AsReadOnly();
}
else if (type.IsGenericType)
{
var genericArguments = type.GetGenericArguments();
var argumentsState = new List<NullableState>(genericArguments.Length);

for (int i = 0; i < genericArguments.Length; i++)
{
var n = GetNullabilityInfo(memberInfo, genericArguments[i], genericArguments[i].GetCustomAttributesData(), ref offset);
argumentsState[i] = n.ReadState;
}

genericArgumentsState = argumentsState.AsReadOnly();
}

return new NullabilityInfo(type, state, state, elementsState, genericArgumentsState);
}

private static NullableState TranslateByte(object? singleValue)
{
return singleValue is byte b ? TranslateByte(b) : NullableState.Unknown;
}

private static NullableState TranslateByte(byte b) =>
b switch
{
1 => NullableState.NonNullable,
2 => NullableState.Nullable,
_ => NullableState.Unknown
};
}
}
24 changes: 24 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9073,6 +9073,30 @@ public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo
public virtual System.Type ResolveType(int metadataToken, System.Type[]? genericTypeArguments, System.Type[]? genericMethodArguments) { throw null; }
public override string ToString() { throw null; }
}
public sealed class NullabilityInfoContext
{
public System.Reflection.NullabilityInfo Create(System.Reflection.EventInfo eventInfo) { throw null; }
public System.Reflection.NullabilityInfo Create(System.Reflection.FieldInfo fieldInfo) { throw null; }
public System.Reflection.NullabilityInfo Create(System.Reflection.ParameterInfo parameterInfo) { throw null; }
public System.Reflection.NullabilityInfo Create(System.Reflection.PropertyInfo propertyInfo) { throw null; }
}
public sealed class NullabilityInfo
{
internal NullabilityInfo(System.Type type, System.Reflection.NullableState readState, System.Reflection.NullableState writeState, System.Collections.ObjectModel.ReadOnlyCollection<System.Reflection.NullableState>? arrayElements, System.Collections.ObjectModel.ReadOnlyCollection<System.Reflection.NullableState>? typeArguments) { }
public System.Type Type { get; }
public System.Reflection.NullableState ReadState { get; }
public System.Reflection.NullableState WriteState { get; }
public System.Collections.ObjectModel.ReadOnlyCollection<System.Reflection.NullableState>? ArrayElements { get; }
public System.Collections.ObjectModel.ReadOnlyCollection<System.Reflection.NullableState>? TypeArguments { get; }
}
public enum NullableState
{
Unknown,
NonNullable,
Nullable,
NotNullableWhen,
NullableWhen
}
public delegate System.Reflection.Module ModuleResolveEventHandler(object sender, System.ResolveEventArgs e);
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)]
public sealed partial class ObfuscateAssemblyAttribute : System.Attribute
Expand Down
Loading