Fix DURABLE0010 false positives when replay-safe logger is passed to helpers (#717)#718
Fix DURABLE0010 false positives when replay-safe logger is passed to helpers (#717)#718Meir017 wants to merge 2 commits intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes DURABLE0010 false positives by replacing the logger analyzer’s simple reachable-method scan with a per-orchestration flow analysis that tries to distinguish replay-safe loggers from unsafe ILogger usages.
Changes:
- Reworked
LoggerOrchestrationAnalyzerto traceILoggervalues across locals, helper parameters, conditionals, and coalescing expressions. - Added analyzer tests covering helper methods, local functions, recursion, mixed safe/unsafe call sites, and field reads.
- Documented the analyzer fix in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Analyzers/Orchestration/LoggerOrchestrationAnalyzer.cs |
Replaces the previous method-level scan with logger-origin flow analysis and updated reporting logic. |
test/Analyzers.Tests/Orchestration/LoggerOrchestrationAnalyzerTests.cs |
Adds regression tests for replay-safe logger propagation through helpers and related cases. |
CHANGELOG.md |
Notes the DURABLE0010 false-positive fix in the unreleased changelog. |
| // Resolve the local's origin from its declarator's initializer. This is flow-insensitive: | ||
| // re-assignments after the initial declaration are not tracked. The common pattern | ||
| // `var logger = context.CreateReplaySafeLogger(...);` is fully supported. | ||
| if (local.DeclaringSyntaxReferences.Length == 0) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| SyntaxNode declSyntax = local.DeclaringSyntaxReferences[0].GetSyntax(); | ||
| if (declSyntax is not VariableDeclaratorSyntax declarator || declarator.Initializer is null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // First check for exact match with ILogger | ||
| SemanticModel semanticModel = this.compilation.GetSemanticModel(declarator.SyntaxTree); | ||
| IOperation? initOperation = semanticModel.GetOperation(declarator.Initializer.Value); | ||
| return initOperation != null && this.IsExpressionSafe(initOperation, visiting); |
There was a problem hiding this comment.
Fixed in d47e764. The analyzer now records every assignment to ILogger locals (initializer + reassignments) in a unified map; IsLocalSafe requires every recorded value to be safe. Added regression test TaskOrchestratorLocalReassignedToUnsafeValueHasDiag.
| // For helper parameters we require every observed call site to pass a safe value. | ||
| // No observed call sites means the helper isn't called from this orchestration's | ||
| // reachable graph — be conservative and treat as unsafe. | ||
| if (!this.callSites.TryGetValue(parameter, out List<IOperation>? args) || args.Count == 0) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| foreach (IOperation arg in args) | ||
| { | ||
| if (!this.IsExpressionSafe(arg, visiting)) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; |
There was a problem hiding this comment.
Fixed in d47e764. Reassignments to ILogger parameters inside helpers are now recorded alongside call-site args; IsParameterSafe requires both to be safe. Added regression test TaskOrchestratorHelperParameterReassignedToUnsafeValueHasDiag.
|
Thanks for the review! Both findings were valid false-negatives — addressed in d47e764:
All 26 tests pass. |
|
@halspang / @YunchuWang please review |
…helpers The LoggerOrchestrationAnalyzer previously flagged ILogger references inside helper methods called from an orchestrator even when the logger originated from context.CreateReplaySafeLogger(...). This is a common pattern that should not produce a warning. The analyzer now performs a per-orchestration data-flow analysis: - Walks all reachable methods from the orchestration root, recording every ILogger reference and every call-site argument of ILogger type. - Resolves each ILogger reference's safety on demand: locals are resolved through their initializers; helper-method parameters are resolved by requiring every observed call site to pass a safe value. - Recognizes safe sources: TaskOrchestrationContext.CreateReplaySafeLogger invocations (walking OverriddenMethod chains), conditional and coalesce expressions where all operands are safe, and unwraps conversions and parenthesized expressions. - Local function helpers are included in the reachable set. - Write-only references (LHS of simple assignments) are excluded from the candidate list. Fixes microsoft#717 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address PR review feedback: a local initialized from CreateReplaySafeLogger that is later reassigned to a non-replay-safe value, or a helper parameter reassigned inside the helper, must flip the symbol to unsafe. Records all assignments (initializers + reassignments) and requires every assigned value to be safe (flow-insensitive AND semantics). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
d47e764 to
5682402
Compare
Summary
Fixes #717. The
LoggerOrchestrationAnalyzer(DURABLE0010) previously produced false positives when an orchestrator passed a replay-safe logger (created viacontext.CreateReplaySafeLogger(...)) to a helper method. This made the rule unusable for the common pattern of factoring logging into private helpers.Repro (no longer flagged)
Approach
Replaced the previous per-method visit with a per-orchestration data-flow analysis (
ReplaySafeLoggerFlowAnalysis):IInvocationOperationtargets, recording every ILogger reference and every call-site argument of ILogger type. Local function helpers are included.visitingset:ILocalSymbol.DeclaringSyntaxReferences).TaskOrchestrationContext.CreateReplaySafeLoggerinvocations, walking theOverriddenMethodchain to support subclass overrides.? :) and coalesce (??) expressions where all operands are safe.IConversionOperationandIParenthesizedOperationare unwrapped.this.field = ...no longer flags the LHS — only subsequent reads.Why a hand-rolled flow analysis
The polished
Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.*framework (PointsTo / TaintedData) used by the CA security rules would have collapsed this to ~30 lines, but it is not a publicly stable NuGet — it is source-vendored fromdotnet/roslyn-analyzers, which is not appropriate for a Microsoft-shipped analyzer assembly.SemanticModel.AnalyzeDataFlowonly tracks symbol-level liveness, not value origin, so it is not applicable here.Tests
24/24 logger analyzer tests pass (14 pre-existing + 10 new):
ILoggeris passed to a helper method called from an orchestrator #717 (helper receiving safe logger → no diag)var b = a;whereacame fromCreateReplaySafeLogger)Run → Outer → Inner)CreateReplaySafeLoggeris still flagged on read (documents the intentional field-is-unsafe limitation)Documented limitations (accepted for v1)
ref/outparameter aliasing is not tracked.CreateReplaySafeLogger(a future enhancement could relax this for fields assigned exactly once in an orchestrator-local context).