-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Non-deterministic System.DateTime usage Roslyn Analyzer (#284)
- Loading branch information
1 parent
d9438aa
commit a275358
Showing
17 changed files
with
1,095 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
; Shipped analyzer releases | ||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
; Unshipped analyzer release | ||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md | ||
|
||
### New Rules | ||
|
||
Rule ID | Category | Severity | Notes | ||
--------|----------|----------|------- | ||
DURABLE0001 | Orchestration | Warning | DateTimeOrchestrationAnalyzer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
namespace Microsoft.DurableTask.Analyzers; | ||
|
||
/// <summary> | ||
/// Provides a set of well-known categories that are used by the analyzers diagnostics. | ||
/// </summary> | ||
static class AnalyzersCategories | ||
{ | ||
/// <summary> | ||
/// The category for the orchestration related analyzers. | ||
/// </summary> | ||
public const string Orchestration = "Orchestration"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Microsoft.DurableTask.Analyzers; | ||
|
||
/// <summary> | ||
/// Provides a set of well-known types that are used by the analyzers. | ||
/// Inspired by KnownTypeSymbols class in | ||
/// <see href="https://github.com/dotnet/runtime/blob/2a846acb1a92e811427babe3ff3f047f98c5df02/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs">System.Text.Json.SourceGeneration</see> source code. | ||
/// Lazy initialization is used to avoid the the initialization of all types during class construction, since not all symbols are used by all analyzers. | ||
/// </summary> | ||
sealed class KnownTypeSymbols(Compilation compilation) | ||
{ | ||
readonly Compilation compilation = compilation; | ||
|
||
INamedTypeSymbol? functionOrchestrationAttribute; | ||
INamedTypeSymbol? functionNameAttribute; | ||
INamedTypeSymbol? taskOrchestratorInterface; | ||
INamedTypeSymbol? taskOrchestratorBaseClass; | ||
INamedTypeSymbol? durableTaskRegistry; | ||
|
||
/// <summary> | ||
/// Gets an OrchestrationTriggerAttribute type symbol. | ||
/// </summary> | ||
public INamedTypeSymbol? FunctionOrchestrationAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.OrchestrationTriggerAttribute", ref this.functionOrchestrationAttribute); | ||
|
||
/// <summary> | ||
/// Gets a FunctionNameAttribute type symbol. | ||
/// </summary> | ||
public INamedTypeSymbol? FunctionNameAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.FunctionAttribute", ref this.functionNameAttribute); | ||
|
||
/// <summary> | ||
/// Gets an ITaskOrchestrator type symbol. | ||
/// </summary> | ||
public INamedTypeSymbol? TaskOrchestratorInterface => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.ITaskOrchestrator", ref this.taskOrchestratorInterface); | ||
|
||
/// <summary> | ||
/// Gets a TaskOrchestrator type symbol. | ||
/// </summary> | ||
public INamedTypeSymbol? TaskOrchestratorBaseClass => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.TaskOrchestrator`2", ref this.taskOrchestratorBaseClass); | ||
|
||
/// <summary> | ||
/// Gets a DurableTaskRegistry type symbol. | ||
/// </summary> | ||
public INamedTypeSymbol? DurableTaskRegistry => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.DurableTaskRegistry", ref this.durableTaskRegistry); | ||
|
||
INamedTypeSymbol? GetOrResolveFullyQualifiedType(string fullyQualifiedName, ref INamedTypeSymbol? field) | ||
{ | ||
if (field != null) | ||
{ | ||
return field; | ||
} | ||
|
||
return field = this.compilation.GetTypeByMetadataName(fullyQualifiedName); | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
src/Analyzers/Orchestration/DateTimeOrchestrationAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Concurrent; | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Microsoft.DurableTask.Analyzers.Orchestration; | ||
|
||
/// <summary> | ||
/// Analyzer that reports a warning when a non-deterministic DateTime property is used in an orchestration method. | ||
/// </summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class DateTimeOrchestrationAnalyzer : OrchestrationAnalyzer | ||
{ | ||
/// <summary> | ||
/// Diagnostic ID supported for the analyzer. | ||
/// </summary> | ||
public const string DiagnosticId = "DURABLE0001"; | ||
|
||
static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.DateTimeOrchestrationAnalyzerTitle), Resources.ResourceManager, typeof(Resources)); | ||
static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.DateTimeOrchestrationAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); | ||
|
||
static readonly DiagnosticDescriptor Rule = new( | ||
DiagnosticId, | ||
Title, | ||
MessageFormat, | ||
AnalyzersCategories.Orchestration, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true); | ||
|
||
/// <inheritdoc/> | ||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule]; | ||
|
||
/// <inheritdoc/> | ||
protected override void RegisterAdditionalCompilationStartAction(CompilationStartAnalysisContext context, OrchestrationAnalysisResult orchestrationAnalysisResult) | ||
{ | ||
INamedTypeSymbol systemDateTimeSymbol = context.Compilation.GetSpecialType(SpecialType.System_DateTime); | ||
|
||
// stores the symbols (such as methods) and the DateTime references used in them | ||
ConcurrentBag<(ISymbol Symbol, IPropertyReferenceOperation Operation)> dateTimeUsage = []; | ||
|
||
// search for usages of DateTime.Now, DateTime.UtcNow, DateTime.Today and store them | ||
context.RegisterOperationAction( | ||
ctx => | ||
{ | ||
ctx.CancellationToken.ThrowIfCancellationRequested(); | ||
|
||
var operation = (IPropertyReferenceOperation)ctx.Operation; | ||
IPropertySymbol property = operation.Property; | ||
|
||
if (!property.ContainingSymbol.Equals(systemDateTimeSymbol, SymbolEqualityComparer.Default)) | ||
{ | ||
return; | ||
} | ||
|
||
if (property.Name is nameof(DateTime.Now) or nameof(DateTime.UtcNow) or nameof(DateTime.Today)) | ||
{ | ||
ISymbol method = ctx.ContainingSymbol; | ||
dateTimeUsage.Add((method, operation)); | ||
} | ||
}, | ||
OperationKind.PropertyReference); | ||
|
||
// compare whether the found DateTime usages occur in methods invoked by orchestrations | ||
context.RegisterCompilationEndAction(ctx => | ||
{ | ||
foreach ((ISymbol symbol, IPropertyReferenceOperation operation) in dateTimeUsage) | ||
{ | ||
if (symbol is IMethodSymbol method) | ||
{ | ||
if (orchestrationAnalysisResult.OrchestrationsByMethod.TryGetValue(method, out ConcurrentBag<AnalyzedOrchestration> orchestrations)) | ||
{ | ||
string methodName = symbol.Name; | ||
string dateTimePropertyName = operation.Property.ToString(); | ||
string orchestrationNames = string.Join(", ", orchestrations.Select(o => o.Name).OrderBy(n => n)); | ||
|
||
// e.g.: "The method 'Method1' uses 'System.Date.Now' that may cause non-deterministic behavior when invoked from orchestration 'MyOrchestrator'" | ||
ctx.ReportDiagnostic(Rule, operation, methodName, dateTimePropertyName, orchestrationNames); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.