Skip to content

Commit a3cef68

Browse files
committed
Add conservative reset of analysis data in presence of escaped lambda and local function delegates
Fixes #5684 We were already performing escape analysis since #4113. However, we were not propertly resetting all the analysis data on encountering such escapes. In future, we can consider making the analysis more aggressive for such escapes by not resetting the entire analysis data on escapes, but instead just resetting the analysis data pertaining to capture variables and any points to locations reachable from these variables.
1 parent 52a0323 commit a3cef68

File tree

2 files changed

+150
-15
lines changed

2 files changed

+150
-15
lines changed

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/DataFlowOperationVisitor.cs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2278,7 +2278,7 @@ private TAbstractAnalysisValue PerformInterproceduralAnalysis(
22782278
// Implement Non-context sensitive interprocedural analysis to
22792279
// merge the relevant data from invoked method's analysis result into CurrentAnalysisData.
22802280
// For now, retain the original logic of resetting the analysis data.
2281-
ResetAnalysisData();
2281+
ResetAnalysisData(hasEscapedLambdaOrLocalFunctions: false);
22822282
}
22832283
}
22842284
finally
@@ -2308,12 +2308,12 @@ private TAbstractAnalysisValue PerformInterproceduralAnalysis(
23082308
// Local functions
23092309
TAbstractAnalysisValue ResetAnalysisDataAndReturnDefaultValue()
23102310
{
2311-
ResetAnalysisData();
2312-
MarkEscapedLambdasAndLocalFunctionsFromArguments();
2311+
var hasEscapes = MarkEscapedLambdasAndLocalFunctionsFromArguments();
2312+
ResetAnalysisData(hasEscapes);
23132313
return defaultValue;
23142314
}
23152315

2316-
void ResetAnalysisData()
2316+
void ResetAnalysisData(bool hasEscapedLambdaOrLocalFunctions)
23172317
{
23182318
// Interprocedural analysis did not succeed, so we need to conservatively reset relevant analysis data.
23192319
if (!PessimisticAnalysis)
@@ -2322,7 +2322,7 @@ void ResetAnalysisData()
23222322
return;
23232323
}
23242324

2325-
if (isLambdaOrLocalFunction)
2325+
if (isLambdaOrLocalFunction || hasEscapedLambdaOrLocalFunctions)
23262326
{
23272327
// For local/lambda cases, we reset all analysis data.
23282328
ResetCurrentAnalysisData();
@@ -2336,23 +2336,36 @@ void ResetAnalysisData()
23362336
}
23372337
}
23382338

2339-
void MarkEscapedLambdasAndLocalFunctionsFromArguments()
2339+
bool MarkEscapedLambdasAndLocalFunctionsFromArguments()
23402340
{
2341-
if (!IsPointsToAnalysis)
2342-
{
2343-
// Only mark escaped lambdas and local functions for points to analysis.
2344-
return;
2345-
}
2346-
2341+
var hasEscapes = false;
23472342
foreach (var argument in arguments)
23482343
{
23492344
if (argument.Parameter?.Type.TypeKind == TypeKind.Delegate ||
23502345
argument.Parameter?.Type.SpecialType == SpecialType.System_Object)
23512346
{
2352-
var pointsToValue = GetPointsToAbstractValue(argument);
2353-
MarkEscapedLambdasAndLocalFunctions(pointsToValue);
2347+
if (!IsPointsToAnalysis)
2348+
{
2349+
// For non-points to analysis, pessimistically assume delegate arguments
2350+
// lead to escaped lambda or local function target which may get invoked.
2351+
if (argument.Parameter.Type.TypeKind == TypeKind.Delegate)
2352+
{
2353+
hasEscapes = true;
2354+
break;
2355+
}
2356+
}
2357+
else
2358+
{
2359+
// For points to analysis, we try to compute the target lambda or local function
2360+
// to determine if we have an escape.
2361+
var pointsToValue = GetPointsToAbstractValue(argument);
2362+
if (MarkEscapedLambdasAndLocalFunctions(pointsToValue))
2363+
hasEscapes = true;
2364+
}
23542365
}
23552366
}
2367+
2368+
return hasEscapes;
23562369
}
23572370

23582371
InterproceduralAnalysisData<TAnalysisData, TAnalysisContext, TAbstractAnalysisValue> ComputeInterproceduralAnalysisData()
@@ -3206,10 +3219,11 @@ private TAbstractAnalysisValue VisitInvocation_NonLambdaOrDelegateOrLocalFunctio
32063219
invokedAsDelegate: false, originalOperation: operation, defaultValue: value);
32073220
}
32083221

3209-
private protected void MarkEscapedLambdasAndLocalFunctions(PointsToAbstractValue pointsToAbstractValue)
3222+
private protected bool MarkEscapedLambdasAndLocalFunctions(PointsToAbstractValue pointsToAbstractValue)
32103223
{
32113224
Debug.Assert(IsPointsToAnalysis);
32123225

3226+
var hasEscapes = false;
32133227
using var methodTargetsOptBuilder = PooledHashSet<(IMethodSymbol method, IOperation? instance)>.GetInstance();
32143228
using var lambdaTargets = PooledHashSet<IFlowAnonymousFunctionOperation>.GetInstance();
32153229
if (ResolveLambdaOrDelegateOrLocalFunctionTargets(pointsToAbstractValue, methodTargetsOptBuilder, lambdaTargets))
@@ -3219,14 +3233,18 @@ private protected void MarkEscapedLambdasAndLocalFunctions(PointsToAbstractValue
32193233
if (targetMethod.MethodKind == MethodKind.LocalFunction)
32203234
{
32213235
_escapedLocalFunctions.Add(targetMethod);
3236+
hasEscapes = true;
32223237
}
32233238
}
32243239

32253240
foreach (var flowAnonymousFunctionOperation in lambdaTargets)
32263241
{
32273242
_escapedLambdas.Add(flowAnonymousFunctionOperation);
3243+
hasEscapes = true;
32283244
}
32293245
}
3246+
3247+
return hasEscapes;
32303248
}
32313249

32323250
private bool ResolveLambdaOrDelegateOrLocalFunctionTargets(

src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidDeadConditionalCode_NullAnalysis.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7579,5 +7579,122 @@ void M(A a)
75797579
LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8,
75807580
}.RunAsync();
75817581
}
7582+
7583+
[Trait(Traits.DataflowAnalysis, Traits.Dataflow.PointsToAnalysis)]
7584+
[Trait(Traits.DataflowAnalysis, Traits.Dataflow.NullAnalysis)]
7585+
[Theory, CombinatorialData, WorkItem(5684, "https://github.com/dotnet/roslyn-analyzers/issues/5684")]
7586+
public async Task EscapedLambdaWithMethodCall_AssignsCapturedVariable_NoDiagnosticsAsync(bool enableInterproceduralAnalysis)
7587+
{
7588+
var interproceduralValue = enableInterproceduralAnalysis ? "ContextSensitive" : "None";
7589+
var editorConfigText = $"dotnet_code_quality.interprocedural_analysis_kind = {interproceduralValue}";
7590+
7591+
var test = new VerifyCS.Test
7592+
{
7593+
TestState =
7594+
{
7595+
Sources =
7596+
{
7597+
@"
7598+
#nullable enable
7599+
7600+
using System;
7601+
7602+
internal class C
7603+
{
7604+
static void M1(Action a)
7605+
{
7606+
a();
7607+
}
7608+
7609+
static void M2()
7610+
{
7611+
Exception? exception = null;
7612+
7613+
M1(() =>
7614+
{
7615+
exception = new Exception();
7616+
});
7617+
7618+
if (exception != null)
7619+
{
7620+
}
7621+
}
7622+
}"
7623+
},
7624+
AnalyzerConfigFiles = { ("/.editorconfig", $@"root = true
7625+
7626+
[*]
7627+
{editorConfigText}
7628+
"),
7629+
7630+
},
7631+
},
7632+
LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8
7633+
};
7634+
7635+
if (enableInterproceduralAnalysis)
7636+
{
7637+
test.ExpectedDiagnostics.Add(
7638+
// /0/Test0.cs(22,13): warning CA1508: 'exception != null' is always 'true'. Remove or refactor the condition(s) to avoid dead code.
7639+
GetCSharpResultAt(22, 13, "exception != null", "true"));
7640+
}
7641+
7642+
await test.RunAsync();
7643+
}
7644+
7645+
[Trait(Traits.DataflowAnalysis, Traits.Dataflow.PointsToAnalysis)]
7646+
[Trait(Traits.DataflowAnalysis, Traits.Dataflow.NullAnalysis)]
7647+
[Theory, CombinatorialData, WorkItem(5684, "https://github.com/dotnet/roslyn-analyzers/issues/5684")]
7648+
public async Task EscapedLambdaInTaskRun_AssignsCapturedVariable_NoDiagnosticsAsync(bool enableInterproceduralAnalysis)
7649+
{
7650+
var interproceduralValue = enableInterproceduralAnalysis ? "ContextSensitive" : "None";
7651+
var editorConfigText = $"dotnet_code_quality.interprocedural_analysis_kind = {interproceduralValue}";
7652+
7653+
var test = new VerifyCS.Test
7654+
{
7655+
TestState =
7656+
{
7657+
Sources =
7658+
{
7659+
@"
7660+
#nullable enable
7661+
7662+
using System;
7663+
using System.Threading.Tasks;
7664+
7665+
internal class C
7666+
{
7667+
static async Task M()
7668+
{
7669+
Exception? exception = null;
7670+
7671+
await Task.Run(() =>
7672+
{
7673+
exception = new Exception();
7674+
}).ConfigureAwait(false);
7675+
7676+
if (exception != null)
7677+
{
7678+
}
7679+
}
7680+
}"
7681+
},
7682+
AnalyzerConfigFiles = { ("/.editorconfig", $@"root = true
7683+
7684+
[*]
7685+
{editorConfigText}
7686+
"),
7687+
7688+
},
7689+
},
7690+
LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8
7691+
};
7692+
7693+
// NOTE: Even with interprocedural analysis enabled, we do not have flow analysis logic
7694+
// to perform interprocedural analysis for a delegate passed to Task.Run.
7695+
// Hence the results are identical with interprocedural analysis enabled and disabled.
7696+
7697+
await test.RunAsync();
7698+
}
75827699
}
75837700
}

0 commit comments

Comments
 (0)