Skip to content

Commit

Permalink
Emit and roundtrip underlying instance field
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed May 9, 2024
1 parent 8d2e7af commit b78e1ea
Show file tree
Hide file tree
Showing 5 changed files with 892 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1871,7 +1871,7 @@ public override TypeKind TypeKind
}

// PROTOTYPE consider caching/optimizing this computation
if (!TryGetExtensionMarkerMethod().IsNil)
if (!TryGetExtensionInfo().MarkerMethod.IsNil)
{
// Extension
result = TypeKind.Extension;
Expand All @@ -1895,10 +1895,12 @@ public override TypeKind TypeKind

/// <summary>
/// Superficially checks whether this is a valid extension type
/// and returns the extension marker method (to be validated later)
/// and returns the extension marker method and underlying instance field if applicable
/// if it is.
/// Both will be validated later.
/// </summary>
private MethodDefinitionHandle TryGetExtensionMarkerMethod()
private (MethodDefinitionHandle MarkerMethod, FieldDefinitionHandle UnderlyingInstanceField)
TryGetExtensionInfo()
{
var moduleSymbol = this.ContainingPEModule;
var module = moduleSymbol.Module;
Expand All @@ -1913,15 +1915,39 @@ private MethodDefinitionHandle TryGetExtensionMarkerMethod()

try
{
// They must not contain any instance state
// The only expected instance state is the underlying instance field
FieldDefinitionHandle foundUnderlyingInstanceField = default;
foreach (var field in module.GetFieldsOfTypeOrThrow(_handle))
{
if ((module.GetFieldDefFlagsOrThrow(field) & FieldAttributes.Static) == 0)
if (module.GetFieldDefNameOrThrow(field) == WellKnownMemberNames.ExtensionFieldName)
{
if ((module.GetFieldDefFlagsOrThrow(field) & FieldAttributes.Static) != 0)
{
// It must be an instance field
return default;
}

if (this.IsStatic)
{
// It's only allowed in non-static extension types
return default;
}

Debug.Assert(foundUnderlyingInstanceField.IsNil);
foundUnderlyingInstanceField = field;
}
else if ((module.GetFieldDefFlagsOrThrow(field) & FieldAttributes.Static) == 0)
{
return default;
}
}

if (!this.IsStatic && foundUnderlyingInstanceField.IsNil)
{
// Non-static extensions must have an underlying instance field (to be validated later)
return default;
}

// They must have a single marker method (to be validated later)
MethodDefinitionHandle foundMarkerMethod = default;
foreach (var methodHandle in module.GetMethodsOfTypeOrThrow(_handle))
Expand All @@ -1940,7 +1966,7 @@ private MethodDefinitionHandle TryGetExtensionMarkerMethod()
}
}

return foundMarkerMethod;
return (foundMarkerMethod, foundUnderlyingInstanceField);
}
catch (BadImageFormatException)
{
Expand Down Expand Up @@ -1968,10 +1994,11 @@ private void DecodeExtensionType(out bool isExplicit, out TypeSymbol underlyingT

bool tryDecodeExtensionType(out bool isExplicit, [NotNullWhen(true)] out TypeSymbol? underlyingType)
{
var markerMethod = TryGetExtensionMarkerMethod();
var (markerMethod, underlyingInstanceField) = TryGetExtensionInfo();
Debug.Assert(!markerMethod.IsNil);
var moduleSymbol = this.ContainingPEModule;

// Decode and validate marker method
isExplicit = false;
underlyingType = null;

Expand All @@ -1987,7 +2014,7 @@ bool tryDecodeExtensionType(out bool isExplicit, [NotNullWhen(true)] out TypeSym
}

// PROTOTYPE do we want to tighten the flags check further? (require that type be sealed?)
if ((localFlags & MethodAttributes.Private) == 0 ||
if ((localFlags & MethodAttributes.MemberAccessMask) != MethodAttributes.Private ||
(localFlags & MethodAttributes.Static) == 0)
{
return false;
Expand All @@ -2010,7 +2037,9 @@ bool tryDecodeExtensionType(out bool isExplicit, [NotNullWhen(true)] out TypeSym

// PROTOTYPE need to decode extension type references (may be some cycle issues)
type = ApplyTransforms(type, paramInfo.Handle, moduleSymbol);
ImmutableArray<CustomModifier> customModifiers = CSharpCustomModifier.Convert(paramInfo.CustomModifiers);

// PROTOTYPE consider checking top-level nullability annotation
if (paramInfo.IsByRef || !paramInfo.CustomModifiers.IsDefault)
{
var info = new CSDiagnosticInfo(ErrorCode.ERR_MalformedExtensionInMetadata, this); // PROTOTYPE need to report use-site diagnostic
Expand All @@ -2031,10 +2060,22 @@ bool tryDecodeExtensionType(out bool isExplicit, [NotNullWhen(true)] out TypeSym
{
var info = new CSDiagnosticInfo(ErrorCode.ERR_MalformedExtensionInMetadata, this); // PROTOTYPE need to report use-site diagnostic
underlyingType = new ExtendedErrorTypeSymbol(type, LookupResultKind.NotReferencable, info, unreported: true);
return false;
}
else
{
underlyingType = type;
// Validate instance field
if (!underlyingInstanceField.IsNil
&& !validateUnderlyingInstanceField(underlyingInstanceField, moduleSymbol, type))
{
var info = new CSDiagnosticInfo(ErrorCode.ERR_MalformedExtensionInMetadata, this); // PROTOTYPE need to report use-site diagnostic
underlyingType = new ExtendedErrorTypeSymbol(type, LookupResultKind.NotReferencable, info, unreported: true);
return false;
}
else
{
underlyingType = type;
}
}
}
else
Expand All @@ -2046,6 +2087,27 @@ bool tryDecodeExtensionType(out bool isExplicit, [NotNullWhen(true)] out TypeSym
Debug.Assert(underlyingType is not null);
return true;
}

bool validateUnderlyingInstanceField(FieldDefinitionHandle underlyingInstanceFieldHandle, PEModuleSymbol moduleSymbol, TypeSymbol underlyingType)
{
var fieldSymbol = new PEFieldSymbol(moduleSymbol, this, underlyingInstanceFieldHandle);

if (fieldSymbol.DeclaredAccessibility != Accessibility.Private
|| fieldSymbol.IsStatic
|| fieldSymbol.RefKind != RefKind.None
|| fieldSymbol.IsReadOnly)
{
return false;
}

if (!fieldSymbol.TypeWithAnnotations.Equals(TypeWithAnnotations.Create(underlyingType), TypeCompareKind.CLRSignatureCompareOptions))
{
return false;
}

// PROTOTYPE do we want to tighten the checks further? (required)
return true;
}
}
#nullable disable

Expand Down Expand Up @@ -2186,12 +2248,18 @@ private IEnumerable<PENamedTypeSymbol> CreateNestedTypes()
}
}

var underlyingInstanceField = TryGetExtensionInfo().UnderlyingInstanceField;
try
{
foreach (var fieldRid in module.GetFieldsOfTypeOrThrow(_handle))
{
try
{
if (!underlyingInstanceField.IsNil && fieldRid == underlyingInstanceField)
{
continue;
}

if (!(isOrdinaryEmbeddableStruct ||
(isOrdinaryStruct && (module.GetFieldDefFlagsOrThrow(fieldRid) & FieldAttributes.Static) == 0) ||
module.ShouldImportField(fieldRid, moduleSymbol.ImportOptions)))
Expand Down Expand Up @@ -2231,7 +2299,7 @@ private IEnumerable<PENamedTypeSymbol> CreateNestedTypes()
// PROTOTYPE are extensions embeddable?
// for ordinary embeddable struct types we import private members so that we can report appropriate errors if the structure is used
var isOrdinaryEmbeddableStruct = (this.TypeKind == TypeKind.Struct) && (this.SpecialType == Microsoft.CodeAnalysis.SpecialType.None) && this.ContainingAssembly.IsLinked;
var extensionMarkerMethod = TryGetExtensionMarkerMethod();
var extensionMarkerMethod = TryGetExtensionInfo().MarkerMethod;

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -18,6 +19,8 @@ internal sealed class SourceExtensionTypeSymbol : SourceNamedTypeSymbol
private ExtensionInfo _lazyDeclaredExtensionInfo = ExtensionInfo.Sentinel;
// PROTOTYPE consider renaming ExtensionUnderlyingType->ExtendedType (here and elsewhere)
private TypeSymbol? _lazyExtensionUnderlyingType = ErrorTypeSymbol.UnknownResultType;
// For non-static extensions, we emit a field of the underlying type
private FieldSymbol? _lazyUnderlyingInstanceField = null;

internal SourceExtensionTypeSymbol(NamespaceOrTypeSymbol containingSymbol, MergedTypeDeclaration declaration, BindingDiagnosticBag diagnostics)
: base(containingSymbol, declaration, diagnostics)
Expand Down Expand Up @@ -395,5 +398,37 @@ internal static bool IsRestrictedExtensionUnderlyingType(TypeSymbol type)

return false;
}

private FieldSymbol? UnderlyingInstanceField
{
get
{
if (IsStatic)
{
throw ExceptionUtilities.Unreachable();
}

var extendedType = GetExtendedTypeNoUseSiteDiagnostics(null);
if (extendedType is null)
{
return null;
}

if (_lazyUnderlyingInstanceField is null)
{
var field = new SynthesizedFieldSymbol(this, extendedType, WellKnownMemberNames.ExtensionFieldName);
Interlocked.CompareExchange(ref _lazyUnderlyingInstanceField, field, comparand: null);
}

return _lazyUnderlyingInstanceField;
}
}

internal override IEnumerable<FieldSymbol> GetFieldsToEmit()
{
return !IsStatic && UnderlyingInstanceField is { } underlyingField
? [underlyingField, .. base.GetFieldsToEmit()]
: base.GetFieldsToEmit();
}
}
}

0 comments on commit b78e1ea

Please sign in to comment.