Skip to content

Commit 0396afe

Browse files
authored
Allow users to define EmbeddedAttribute (#76523)
* Work on embedded attribute recognition in source * Add tests * Additional testing * Refactoring for simplicity * Always validate in source, and document breaking change * Typo * Update test * Synthesize an application of EmbeddedAttribute if EmbeddedAttribute itself didn't have one applied to it. * Add VB validation implementation. * More PR feedback. * PR feedback * Minor typo and feedback
1 parent 692a6cc commit 0396afe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1159
-104
lines changed

docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,25 @@ struct S
156156
public static void M([UnscopedRef] ref int x) { }
157157
}
158158
```
159+
160+
## `Microsoft.CodeAnalysis.EmbeddedAttribute` is validated on declaration
161+
162+
***Introduced in Visual Studio 2022 version 17.13***
163+
164+
The compiler now validates the shape of `Microsoft.CodeAnalysis.EmbeddedAttribute` when declared in source. Previously, the compiler
165+
would allow user-defined declarations of this attribute, but only when it didn't need to generate one itself. We now validate that:
166+
167+
1. It must be internal
168+
2. It must be a class
169+
3. It must be sealed
170+
4. It must be non-static
171+
5. It must have an internal or public parameterless constructor
172+
6. It must inherit from System.Attribute.
173+
7. It must be allowed on any type declaration (class, struct, interface, enum, or delegate)
174+
175+
```cs
176+
namespace Microsoft.CodeAnalysis;
177+
178+
// Previously, sometimes allowed. Now, CS9271
179+
public class EmbeddedAttribute : Attribute {}
180+
```

docs/compilers/Visual Basic/Compiler Breaking Changes - DotNet 10.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,27 @@ Class C
3535
End Class
3636
```
3737

38+
## `Microsoft.CodeAnalysis.EmbeddedAttribute` is validated on declaration
39+
40+
***Introduced in Visual Studio 2022 version 17.13***
41+
42+
The compiler now validates the shape of `Microsoft.CodeAnalysis.EmbeddedAttribute` when declared in source. Previously, the compiler
43+
would allow user-defined declarations of this attribute with any shape. We now validate that:
44+
45+
1. It must be Friend
46+
2. It must be a Class
47+
3. It must be NotInheritable
48+
4. It must not be a Module
49+
5. It must have a Public parameterless constructor
50+
6. It must inherit from System.Attribute.
51+
7. It must be allowed on any type declaration (Class, Structure, Interface, Enum, or Delegate)
52+
53+
```vb
54+
Namespace Microsoft.CodeAnalysis
55+
56+
' Previously allowed. Now, BC37335
57+
Public Class EmbeddedAttribute
58+
Inherits Attribute
59+
End Class
60+
End Namespace
61+
```

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5908,6 +5908,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
59085908
<data name="ERR_TypeReserved" xml:space="preserve">
59095909
<value>The type name '{0}' is reserved to be used by the compiler.</value>
59105910
</data>
5911+
<data name="ERR_EmbeddedAttributeMustFollowPattern" xml:space="preserve">
5912+
<value>The type 'Microsoft.CodeAnalysis.EmbeddedAttribute' must be non-generic, internal, sealed, non-static, have a parameterless constructor, inherit from System.Attribute, and be able to be applied to any type.</value>
5913+
</data>
59115914
<data name="ERR_InExtensionMustBeValueType" xml:space="preserve">
59125915
<value>The first 'in' or 'ref readonly' parameter of the extension method '{0}' must be a concrete (non-generic) value type.</value>
59135916
</data>

src/Compilers/CSharp/Portable/Emitter/Model/PEAssemblyBuilder.cs

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Diagnostics;
1111
using System.Linq;
1212
using System.Reflection;
13+
using System.Threading;
1314
using Microsoft.CodeAnalysis.CSharp.Symbols;
1415
using Microsoft.CodeAnalysis.Emit;
1516
using Microsoft.CodeAnalysis.PooledObjects;
@@ -32,7 +33,7 @@ internal abstract class PEAssemblyBuilderBase : PEModuleBuilder, Cci.IAssemblyRe
3233
/// <summary>This is a cache of a subset of <seealso cref="_lazyFiles"/>. We don't include manifest resources in ref assemblies</summary>
3334
private ImmutableArray<Cci.IFileReference> _lazyFilesWithoutManifestResources;
3435

35-
private SynthesizedEmbeddedAttributeSymbol _lazyEmbeddedAttribute;
36+
private NamedTypeSymbol _lazyEmbeddedAttribute;
3637
private SynthesizedEmbeddedAttributeSymbol _lazyIsReadOnlyAttribute;
3738
private SynthesizedEmbeddedAttributeSymbol _lazyRequiresLocationAttribute;
3839
private SynthesizedEmbeddedAttributeSymbol _lazyParamCollectionAttribute;
@@ -94,7 +95,11 @@ internal sealed override ImmutableArray<NamedTypeSymbol> GetEmbeddedTypes(Bindin
9495

9596
CreateEmbeddedAttributesIfNeeded(diagnostics);
9697

97-
builder.AddIfNotNull(_lazyEmbeddedAttribute);
98+
if (_lazyEmbeddedAttribute is SynthesizedEmbeddedAttributeSymbol)
99+
{
100+
builder.Add(_lazyEmbeddedAttribute);
101+
}
102+
98103
builder.AddIfNotNull(_lazyIsReadOnlyAttribute);
99104
builder.AddIfNotNull(_lazyRequiresLocationAttribute);
100105
builder.AddIfNotNull(_lazyParamCollectionAttribute);
@@ -387,7 +392,8 @@ private void CreateEmbeddedAttributesIfNeeded(BindingDiagnosticBag diagnostics)
387392
ref _lazyEmbeddedAttribute,
388393
diagnostics,
389394
AttributeDescription.CodeAnalysisEmbeddedAttribute,
390-
createParameterlessEmbeddedAttributeSymbol);
395+
createParameterlessEmbeddedAttributeSymbol,
396+
allowUserDefinedAttribute: true);
391397

392398
if ((needsAttributes & EmbeddableAttributes.IsReadOnlyAttribute) != 0)
393399
{
@@ -544,16 +550,32 @@ private SynthesizedEmbeddedRefSafetyRulesAttributeSymbol CreateRefSafetyRulesAtt
544550
GetWellKnownType(WellKnownType.System_Attribute, diagnostics),
545551
GetSpecialType(SpecialType.System_Int32, diagnostics));
546552

553+
#nullable enable
547554
private void CreateAttributeIfNeeded<T>(
548555
ref T symbol,
549556
BindingDiagnosticBag diagnostics,
550557
AttributeDescription description,
551-
Func<string, NamespaceSymbol, BindingDiagnosticBag, T> factory)
552-
where T : SynthesizedEmbeddedAttributeSymbolBase
558+
Func<string, NamespaceSymbol, BindingDiagnosticBag, T> factory,
559+
bool allowUserDefinedAttribute = false)
560+
where T : NamedTypeSymbol
553561
{
562+
Debug.Assert(!allowUserDefinedAttribute || typeof(T) == typeof(NamedTypeSymbol));
554563
if (symbol is null)
555564
{
556-
AddDiagnosticsForExistingAttribute(description, diagnostics);
565+
var userDefinedAttribute = getExistingType(description);
566+
567+
if (userDefinedAttribute is not null)
568+
{
569+
if (allowUserDefinedAttribute)
570+
{
571+
symbol = (T)userDefinedAttribute;
572+
return;
573+
}
574+
else
575+
{
576+
diagnostics.Add(ErrorCode.ERR_TypeReserved, userDefinedAttribute.GetFirstLocation(), description.FullName);
577+
}
578+
}
557579

558580
var containingNamespace = GetOrSynthesizeNamespace(description.Namespace);
559581

@@ -567,23 +589,17 @@ private void CreateAttributeIfNeeded<T>(
567589

568590
AddSynthesizedDefinition(containingNamespace, symbol);
569591
}
570-
}
571-
572-
#nullable enable
573592

574-
private void AddDiagnosticsForExistingAttribute(AttributeDescription description, BindingDiagnosticBag diagnostics)
575-
{
576-
var attributeMetadataName = MetadataTypeName.FromFullName(description.FullName);
577-
var userDefinedAttribute = _sourceAssembly.SourceModule.LookupTopLevelMetadataType(ref attributeMetadataName);
578-
Debug.Assert(userDefinedAttribute is null || (object)userDefinedAttribute.ContainingModule == _sourceAssembly.SourceModule);
579-
Debug.Assert(userDefinedAttribute?.IsErrorType() != true);
580-
581-
if (userDefinedAttribute is not null)
593+
NamedTypeSymbol? getExistingType(AttributeDescription description)
582594
{
583-
diagnostics.Add(ErrorCode.ERR_TypeReserved, userDefinedAttribute.GetFirstLocation(), description.FullName);
595+
var attributeMetadataName = MetadataTypeName.FromFullName(description.FullName);
596+
var userDefinedAttribute = _sourceAssembly.SourceModule.LookupTopLevelMetadataType(ref attributeMetadataName);
597+
Debug.Assert(userDefinedAttribute is null || (object)userDefinedAttribute.ContainingModule == _sourceAssembly.SourceModule);
598+
Debug.Assert(userDefinedAttribute?.IsErrorType() != true);
599+
600+
return userDefinedAttribute;
584601
}
585602
}
586-
587603
#nullable disable
588604

589605
protected NamespaceSymbol GetOrSynthesizeNamespace(string namespaceFullName)

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,8 @@ internal enum ErrorCode
23562356
WRN_UnscopedRefAttributeOldRules = 9269,
23572357
WRN_InterceptsLocationAttributeUnsupportedSignature = 9270,
23582358

2359+
ERR_EmbeddedAttributeMustFollowPattern = 9271,
2360+
23592361
// Note: you will need to do the following after adding errors:
23602362
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)
23612363

src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,6 +1844,7 @@ or ErrorCode.ERR_RefReturnReadonlyNotField
18441844
or ErrorCode.ERR_RefReturnReadonlyNotField2
18451845
or ErrorCode.ERR_ExplicitReservedAttr
18461846
or ErrorCode.ERR_TypeReserved
1847+
or ErrorCode.ERR_EmbeddedAttributeMustFollowPattern
18471848
or ErrorCode.ERR_RefExtensionMustBeValueTypeOrConstrainedToOne
18481849
or ErrorCode.ERR_InExtensionMustBeValueType
18491850
or ErrorCode.ERR_FieldsInRoStruct

src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#nullable disable
66

7+
using System;
78
using System.Collections.Generic;
89
using System.Collections.Immutable;
910
using System.Diagnostics;
@@ -1418,7 +1419,9 @@ internal override bool HasCodeAnalysisEmbeddedAttribute
14181419
get
14191420
{
14201421
var data = GetEarlyDecodedWellKnownAttributeData();
1421-
return data != null && data.HasCodeAnalysisEmbeddedAttribute;
1422+
return (data != null && data.HasCodeAnalysisEmbeddedAttribute)
1423+
// If this is Microsoft.CodeAnalysis.EmbeddedAttribute, we'll synthesize EmbeddedAttribute even if it's not applied.
1424+
|| this.IsMicrosoftCodeAnalysisEmbeddedAttribute();
14221425
}
14231426
}
14241427

@@ -1761,6 +1764,21 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r
17611764
ImmutableArray.Create(new TypedConstant(compilation.GetWellKnownType(WellKnownType.System_Type), TypedConstantKind.Type, originalType)),
17621765
isOptionalUse: true));
17631766
}
1767+
1768+
if (this.IsMicrosoftCodeAnalysisEmbeddedAttribute() && GetEarlyDecodedWellKnownAttributeData() is null or { HasCodeAnalysisEmbeddedAttribute: false })
1769+
{
1770+
// This is Microsoft.CodeAnalysis.EmbeddedAttribute, and the user didn't manually apply this attribute to itself. Grab the parameterless constructor
1771+
// and apply it; if there isn't a parameterless constructor, there would have been a declaration diagnostic
1772+
1773+
var parameterlessConstructor = InstanceConstructors.FirstOrDefault(c => c.ParameterCount == 0);
1774+
1775+
if (parameterlessConstructor is not null)
1776+
{
1777+
AddSynthesizedAttribute(
1778+
ref attributes,
1779+
SynthesizedAttributeData.Create(DeclaringCompilation, parameterlessConstructor, arguments: [], namedArguments: []));
1780+
}
1781+
}
17641782
}
17651783

17661784
#endregion
@@ -1918,6 +1936,34 @@ protected override void AfterMembersCompletedChecks(BindingDiagnosticBag diagnos
19181936
diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportInlineArrayTypes, GetFirstLocation());
19191937
}
19201938
}
1939+
1940+
if (this.IsMicrosoftCodeAnalysisEmbeddedAttribute())
1941+
{
1942+
// This is a user-defined implementation of the special attribute Microsoft.CodeAnalysis.EmbeddedAttribute. It needs to follow specific rules:
1943+
// 1. It must be internal
1944+
// 2. It must be a class
1945+
// 3. It must be sealed
1946+
// 4. It must be non-static
1947+
// 5. It must have an internal or public parameterless constructor
1948+
// 6. It must inherit from System.Attribute
1949+
// 7. It must be allowed on any type declaration (class, struct, interface, enum, or delegate)
1950+
// 8. It must be non-generic (checked as part of IsMicrosoftCodeAnalysisEmbeddedAttribute, we don't error on this because both types can exist)
1951+
1952+
const AttributeTargets expectedTargets = AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate;
1953+
1954+
if (DeclaredAccessibility != Accessibility.Internal
1955+
|| TypeKind != TypeKind.Class
1956+
|| !IsSealed
1957+
|| IsStatic
1958+
|| !InstanceConstructors.Any(c => c is { ParameterCount: 0, DeclaredAccessibility: Accessibility.Internal or Accessibility.Public })
1959+
|| !this.DeclaringCompilation.IsAttributeType(this)
1960+
|| (GetAttributeUsageInfo().ValidTargets & expectedTargets) != expectedTargets)
1961+
{
1962+
// The type 'Microsoft.CodeAnalysis.EmbeddedAttribute' must be non-generic, internal, sealed, non-static, have a parameterless constructor, inherit from System.Attribute, and be able to be applied to any type.
1963+
diagnostics.Add(ErrorCode.ERR_EmbeddedAttributeMustFollowPattern, GetFirstLocation());
1964+
}
1965+
1966+
}
19211967
}
19221968
}
19231969
}

src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2137,6 +2137,17 @@ internal static bool IsWellKnownTypeLock(this TypeSymbol typeSymbol)
21372137
typeSymbol.IsContainedInNamespace(nameof(System), nameof(System.Threading));
21382138
}
21392139

2140+
internal static bool IsMicrosoftCodeAnalysisEmbeddedAttribute(this TypeSymbol typeSymbol)
2141+
{
2142+
return typeSymbol is NamedTypeSymbol
2143+
{
2144+
Name: "EmbeddedAttribute",
2145+
Arity: 0,
2146+
ContainingType: null,
2147+
}
2148+
&& typeSymbol.IsContainedInNamespace("Microsoft", "CodeAnalysis");
2149+
}
2150+
21402151
private static bool IsWellKnownInteropServicesTopLevelType(this TypeSymbol typeSymbol, string name)
21412152
{
21422153
if (typeSymbol.Name != name || typeSymbol.ContainingType is object)

src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)