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
Expand Up @@ -127,6 +127,52 @@ void Foo(bool b) {
if (b) Foo(false);
}
}
";
await Verify.VerifyAsync(test);
}

[Test]
public async Task NoWarn_When_Same_Method_Is_Called_From_Lambda()
{
// Issue #318: a call to the same method from within a lambda is not
// an unconditional recursive call -- the lambda body is deferred.
var test = @"
using System;
class C {
void Foo() {
Action a = () => Foo();
a();
}
}
";
await Verify.VerifyAsync(test);
}

[Test]
public async Task NoWarn_When_Same_Method_Is_Called_From_AnonymousMethod()
{
var test = @"
using System;
class C {
void Foo() {
Action a = delegate { Foo(); };
a();
}
}
";
await Verify.VerifyAsync(test);
}

[Test]
public async Task NoWarn_When_Same_Method_Is_Called_From_LocalFunction()
{
var test = @"
class C {
void Foo() {
void Local() { Foo(); }
Local();
}
}
";
await Verify.VerifyAsync(test);
}
Expand Down
20 changes: 20 additions & 0 deletions src/ErrorProne.NET.CoreAnalyzers/RecursiveCallAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ private static void AnalyzeMethodBody(OperationAnalysisContext context)

foreach (var invocation in methodBody.Descendants().OfType<IInvocationOperation>())
{
// Calls inside a lambda or local function don't execute as part of this method's
// immediate control flow, so they aren't unconditional recursion. See issue #318.
if (IsInsideNestedFunction(invocation))
{
continue;
}

// Check if all parameters are passed as-is
// So Factorial(n - 1) should be totally fine!
if (invocation.Arguments.Length == method.Parameters.Length &&
Expand Down Expand Up @@ -62,6 +69,19 @@ arg.Value is IParameterReferenceOperation paramRef &&
}
}

private static bool IsInsideNestedFunction(IOperation operation)
{
for (var parent = operation.Parent; parent != null; parent = parent.Parent)
{
if (parent is IAnonymousFunctionOperation or ILocalFunctionOperation)
{
return true;
}
}

return false;
}

private static bool HasTouchedRefParameterBeforeCall(IInvocationOperation recursiveCall, IMethodSymbol method, HashSet<IParameterSymbol> touchedRefParameters)
{
// Check if any ref parameter in the recursive call was touched
Expand Down
Loading