Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using System.Collections.Immutable;

namespace Orleans.Analyzers
Expand All @@ -16,44 +15,52 @@ public class AbstractPropertiesCannotBeSerializedAnalyzer : DiagnosticAnalyzer

internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [Rule];

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterSyntaxNodeAction(CheckSyntaxNode, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration);
}

private void CheckSyntaxNode(SyntaxNodeAnalysisContext context)
{
if (context.Node is TypeDeclarationSyntax declaration && SerializationAttributesHelper.ShouldGenerateSerializer(declaration))
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(context =>
{
var analysis = SerializationAttributesHelper.AnalyzeTypeDeclaration(declaration);
foreach (var member in analysis.AnnotatedMembers)
var idAttribute = context.Compilation.GetTypeByMetadataName("Orleans.IdAttribute");
var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute");
if (idAttribute is null || generateSerializerAttributeSymbol is null)
{
string modifier = null;
if (member.IsAbstract())
{
modifier = "abstract";
}
else if (member.IsStatic())
return;
}

context.RegisterSymbolStartAction(context =>
{
if (SerializationAttributesHelper.ShouldGenerateSerializer((INamedTypeSymbol)context.Symbol, generateSerializerAttributeSymbol))
{
modifier = "static";
context.RegisterOperationAction(context => AnalyzeAttribute(context, idAttribute), OperationKind.Attribute);
}
}, SymbolKind.NamedType);
});
}

if (modifier is not null)
{
var location = member.GetLocation();
if (member.TryGetAttribute(Constants.IdAttributeName, out var attribute))
{
location = attribute.GetLocation();
}
private static void AnalyzeAttribute(OperationAnalysisContext context, INamedTypeSymbol idAttribute)
{
var attributeOperation = (IAttributeOperation)context.Operation;
string modifier;
if (context.ContainingSymbol.IsAbstract)
{
modifier = "abstract";
}
else if (context.ContainingSymbol.IsStatic)
{
modifier = "static";
}
else
{
return;
}

var name = member.GetMemberNameOrDefault();
context.ReportDiagnostic(Diagnostic.Create(Rule, location, name, modifier));
}
}
if (attributeOperation.Operation is IObjectCreationOperation objectCreationOperation &&
idAttribute.Equals(objectCreationOperation.Constructor.ContainingType, SymbolEqualityComparer.Default))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, attributeOperation.Syntax.GetLocation(), context.ContainingSymbol.Name, modifier));
}
}
}
Expand Down
82 changes: 37 additions & 45 deletions src/Orleans.Analyzers/AliasClashAttributeAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,29 @@ public class AliasClashAttributeAnalyzer : DiagnosticAnalyzer
helpLinkUri: null,
customTags: [WellKnownDiagnosticTags.CompilationEnd]);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [Rule];

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(
GeneratedCodeAnalysisFlags.Analyze |
GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(compilationContext =>
context.RegisterCompilationStartAction(context =>
{
var aliasMap = new ConcurrentDictionary<string, ConcurrentBag<TypeAliasInfo>>();

compilationContext.RegisterSyntaxNodeAction(
nodeContext => CollectTypeAliases(nodeContext, aliasMap),
SyntaxKind.EnumDeclaration,
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.RecordDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.RecordStructDeclaration);
var aliasAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.AliasAttribute");
context.RegisterSymbolAction(
context => CollectTypeAliases(context, aliasMap, aliasAttributeSymbol),
SymbolKind.NamedType);

// We can immediately check duplicate method‐aliases in grain interfaces.
compilationContext.RegisterSyntaxNodeAction(
nodeContext => CheckMethodAliases(nodeContext, aliasMap),
SyntaxKind.InterfaceDeclaration);
context.RegisterSymbolAction(
context => CheckMethodAliases(context, aliasMap, aliasAttributeSymbol),
SymbolKind.NamedType);

// Only at the very end, we do one single‐threaded scan for type‐alias clashes only.
compilationContext.RegisterCompilationEndAction(endContext =>
context.RegisterCompilationEndAction(context =>
{
foreach (var kvp in aliasMap)
{
Expand All @@ -77,40 +70,35 @@ public override void Initialize(AnalysisContext context)
var firstType = distinctTypes[0];
foreach (var info in infos.Where(i => i.TypeName != firstType))
{
endContext.ReportDiagnostic(Diagnostic.Create(Rule, info.Location, alias, firstType));
context.ReportDiagnostic(Diagnostic.Create(Rule, info.Location, alias, firstType));
}
}
});
});
}

private static void CollectTypeAliases(
SyntaxNodeAnalysisContext context,
ConcurrentDictionary<string, ConcurrentBag<TypeAliasInfo>> aliasMap)
SymbolAnalysisContext context,
ConcurrentDictionary<string, ConcurrentBag<TypeAliasInfo>> aliasMap,
INamedTypeSymbol aliasAttributeSymbol)
{
if (context.Node is not BaseTypeDeclarationSyntax decl)
return;

var semanticModel = context.SemanticModel;
var typeSymbol = semanticModel.GetDeclaredSymbol(decl);
if (typeSymbol == null)
{
return;
}

if (decl is InterfaceDeclarationSyntax iface && !iface.ExtendsGrainInterface(semanticModel))
var typeSymbol = (INamedTypeSymbol)context.Symbol;

if (typeSymbol.TypeKind == TypeKind.Interface && !typeSymbol.ExtendsGrainInterface())
{
return; // Skip interfaces that dont extend IAddressable
}

var attrs = decl.AttributeLists.GetAttributeSyntaxes(Constants.AliasAttributeName);
foreach (var attr in attrs)
foreach (var attr in typeSymbol.GetAttributes())
{
var alias = attr.GetArgumentValue(semanticModel);
if (!aliasAttributeSymbol.Equals(attr.AttributeClass, SymbolEqualityComparer.Default))
continue;

var alias = attr.ConstructorArguments.FirstOrDefault().Value as string;
if (string.IsNullOrEmpty(alias))
continue;

var info = new TypeAliasInfo(typeSymbol.ToDisplayString(), attr.GetLocation());
var info = new TypeAliasInfo(typeSymbol.ToDisplayString(), attr.ApplicationSyntaxReference.GetSyntax().GetLocation());

aliasMap.AddOrUpdate(
key: alias,
Expand All @@ -124,26 +112,30 @@ private static void CollectTypeAliases(
}

private static void CheckMethodAliases(
SyntaxNodeAnalysisContext context,
ConcurrentDictionary<string, ConcurrentBag<TypeAliasInfo>> aliasMap)
SymbolAnalysisContext context,
ConcurrentDictionary<string, ConcurrentBag<TypeAliasInfo>> aliasMap,
INamedTypeSymbol aliasAttributeSymbol)
{
if (context.Node is not InterfaceDeclarationSyntax interfaceDecl)
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Interface } interfaceSymbol)
{
return;
}

var semanticModel = context.SemanticModel;
var methodBags = new List<(string Alias, Location Location)>();

foreach (var method in interfaceDecl.Members.OfType<MethodDeclarationSyntax>())
foreach (var method in interfaceSymbol.GetMembers().OfType<IMethodSymbol>())
{
var methodAttrs = method.AttributeLists.GetAttributeSyntaxes(Constants.AliasAttributeName);
foreach (var attr in methodAttrs)
foreach (var attr in method.GetAttributes())
{
var alias = attr.GetArgumentValue(semanticModel);
if (!aliasAttributeSymbol.Equals(attr.AttributeClass, SymbolEqualityComparer.Default))
{
continue;
}

var alias = attr.ConstructorArguments.FirstOrDefault().Value as string;
if (!string.IsNullOrEmpty(alias))
{
methodBags.Add((alias, attr.GetLocation()));
methodBags.Add((alias, attr.ApplicationSyntaxReference.GetSyntax().GetLocation()));
}
}
}
Expand Down
22 changes: 12 additions & 10 deletions src/Orleans.Analyzers/AlwaysInterleaveDiagnosticAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,27 @@ public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(
AnalyzeSyntax,
SyntaxKind.MethodDeclaration);
context.RegisterCompilationStartAction(context =>
{
var alwaysInterleaveAttributeSymbol = context.Compilation.GetTypeByMetadataName(AlwaysInterleaveAttributeName);
if (alwaysInterleaveAttributeSymbol is not null)
{
context.RegisterSymbolAction(context => AnalyzeMethod(context, alwaysInterleaveAttributeSymbol), SymbolKind.Method);
}
});
}

private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
private static void AnalyzeMethod(SymbolAnalysisContext context, INamedTypeSymbol alwaysInterleaveAttribute)
{
var alwaysInterleaveAttribute = context.Compilation.GetTypeByMetadataName(AlwaysInterleaveAttributeName);

var syntax = (MethodDeclarationSyntax)context.Node;
var symbol = context.SemanticModel.GetDeclaredSymbol(syntax, context.CancellationToken);
var methodSymbol = (IMethodSymbol)context.Symbol;

if (symbol.ContainingType.TypeKind == TypeKind.Interface)
if (methodSymbol.ContainingType.TypeKind == TypeKind.Interface)
{
// TODO: Check that interface inherits from IGrain
return;
}

foreach (var attribute in symbol.GetAttributes())
foreach (var attribute in methodSymbol.GetAttributes())
{
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, alwaysInterleaveAttribute))
{
Expand Down
35 changes: 25 additions & 10 deletions src/Orleans.Analyzers/AtMostOneOrleansConstructorAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace Orleans.Analyzers
{
Expand All @@ -21,18 +19,35 @@ public class AtMostOneOrleansConstructorAnalyzer : DiagnosticAnalyzer
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterSyntaxNodeAction(CheckSyntaxNode, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration);
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(context =>
{
var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute");
if (generateSerializerAttributeSymbol is not null)
{
context.RegisterSymbolAction(context => AnalyzeNamedType(context, generateSerializerAttributeSymbol), SymbolKind.NamedType);
}
});
}

private void CheckSyntaxNode(SyntaxNodeAnalysisContext context)
private void AnalyzeNamedType(SymbolAnalysisContext context, INamedTypeSymbol generateSerializerAttributeSymbol)
{
if (context.Node is TypeDeclarationSyntax declaration && SerializationAttributesHelper.ShouldGenerateSerializer(declaration))
var symbol = (INamedTypeSymbol)context.Symbol;
if (SerializationAttributesHelper.ShouldGenerateSerializer(symbol, generateSerializerAttributeSymbol))
{
var analysis = SerializationAttributesHelper.AnalyzeTypeDeclaration(declaration);
if (analysis.AnnotatedConstructorCount > 1)
var foundAttribute = false;
foreach (var constructor in symbol.Constructors)
{
context.ReportDiagnostic(Diagnostic.Create(Rule, declaration.GetLocation()));
if (constructor.HasAttribute(generateSerializerAttributeSymbol))
{
if (foundAttribute)
{
context.ReportDiagnostic(Diagnostic.Create(Rule, symbol.Locations[0]));
return;
}

foundAttribute = true;
}
}
}
}
Expand Down
Loading
Loading