Skip to content

Commit

Permalink
Rule S4823: check also top level statements
Browse files Browse the repository at this point in the history
  • Loading branch information
costin-zaharia-sonarsource committed Dec 1, 2021
1 parent 914651a commit 1dfbfab
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ namespace StyleCop.Analyzers.Lightup
{
public static class INamedTypeSymbolExtensions
{
public const string MainMethodImplicitName = "<Main>$";

private static readonly HashSet<string> ProgramClassImplicitName = new HashSet<string> { "Program", "<Program>$" };
private static readonly Func<INamedTypeSymbol, INamedTypeSymbol> TupleUnderlyingTypeAccessor;
private static readonly Func<INamedTypeSymbol, ImmutableArray<IFieldSymbol>> TupleElementsAccessor;
private static readonly Func<INamedTypeSymbol, bool> IsSerializableAccessor;
Expand All @@ -31,10 +28,5 @@ static INamedTypeSymbolExtensions()
public static ImmutableArray<IFieldSymbol> TupleElements(this INamedTypeSymbol symbol) => TupleElementsAccessor(symbol);

public static bool IsSerializable(this INamedTypeSymbol symbol) => IsSerializableAccessor(symbol);

public static bool IsTopLevelProgram(this INamedTypeSymbol symbol) =>
ProgramClassImplicitName.Contains(symbol.Name)
&& symbol.ContainingNamespace.IsGlobalNamespace
&& symbol.GetMembers(MainMethodImplicitName).Any();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ public static ProgramState RemoveConstraint(this ISymbol symbol, SymbolicValueCo
public static IEnumerable<SyntaxNode> GetLocationNodes(this ISymbol symbol, SyntaxNode node) =>
symbol.Locations.SelectMany(location => GetDescendantNodes(location, node));

public static bool IsTopLevelMain(this ISymbol symbol) =>
symbol is IMethodSymbol { Name: INamedTypeSymbolExtensions.MainMethodImplicitName };

private static IEnumerable<SyntaxNode> GetDescendantNodes(Location location, SyntaxNode invocation)
{
var locationRootNode = location.SourceTree?.GetRoot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using SonarAnalyzer.Common;
using SonarAnalyzer.Extensions;
using SonarAnalyzer.Helpers;
using StyleCop.Analyzers.Lightup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SonarAnalyzer.Extensions;
using StyleCop.Analyzers.Lightup;

namespace SonarAnalyzer.Helpers.Trackers
Expand All @@ -39,29 +42,49 @@ public override Condition ParameterAtIndexIsUsed(int index) =>
return false;
}

var methodDeclaration = context.MethodSymbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<BaseMethodDeclarationSyntax>()
.FirstOrDefault(declaration => declaration.HasBodyOrExpressionBody());

if (methodDeclaration == null)
var methodInfo = GetMethodInfo(context);
if (methodInfo?.DescendantNodes == null)
{
return false;
}

var semanticModel = context.GetSemanticModel(methodDeclaration);

var descendantNodes = methodDeclaration?.Body?.DescendantNodes()
?? methodDeclaration?.ExpressionBody()?.DescendantNodes()
?? Enumerable.Empty<SyntaxNode>();

return descendantNodes.Any(
return methodInfo.DescendantNodes.Any(
node =>
node.IsKind(SyntaxKind.IdentifierName)
&& ((IdentifierNameSyntax)node).Identifier.ValueText == parameterSymbol.Name
&& parameterSymbol.Equals(semanticModel.GetSymbolInfo(node).Symbol));
&& parameterSymbol.Equals(methodInfo.SemanticModel.GetSymbolInfo(node).Symbol));
};

private static MethodInfo GetMethodInfo(MethodDeclarationContext context)
{
if (context.MethodSymbol.IsTopLevelMain())
{
var declaration = context.MethodSymbol
.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<CompilationUnitSyntax>()
.First();

return new MethodInfo(context.GetSemanticModel(declaration), declaration.GetTopLevelMainBody().SelectMany(x => x.DescendantNodes()));
}
else
{
var declaration = context.MethodSymbol
.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<BaseMethodDeclarationSyntax>()
.FirstOrDefault(declaration => declaration.HasBodyOrExpressionBody());
if (declaration == null)
{
return null;
}

return new MethodInfo(
context.GetSemanticModel(declaration),
declaration.Body?.DescendantNodes() ?? declaration.ExpressionBody()?.DescendantNodes() ?? Enumerable.Empty<SyntaxNode>());
}
}

protected override SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration) =>
methodDeclaration switch
{
Expand All @@ -77,5 +100,18 @@ public override Condition ParameterAtIndexIsUsed(int index) =>
_ => null
}
};

private class MethodInfo
{
public SemanticModel SemanticModel { get; }

public IEnumerable<SyntaxNode> DescendantNodes { get; }

public MethodInfo(SemanticModel model, IEnumerable<SyntaxNode> descendantNodes)
{
SemanticModel = model;
DescendantNodes = descendantNodes;
}
}
}
}
31 changes: 31 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Common/TopLevelStatements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2021 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Collections.Generic;

namespace SonarAnalyzer.Common
{
public static class TopLevelStatements
{
public const string MainMethodImplicitName = "<Main>$";

public static readonly HashSet<string> ProgramClassImplicitName = new HashSet<string> { "Program", "<Program>$" };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2021 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Linq;
using Microsoft.CodeAnalysis;
using SonarAnalyzer.Common;

namespace SonarAnalyzer.Extensions
{
public static class INamedTypeSymbolExtensions
{
public static bool IsTopLevelProgram(this INamedTypeSymbol symbol) =>
TopLevelStatements.ProgramClassImplicitName.Contains(symbol.Name)
&& symbol.ContainingNamespace.IsGlobalNamespace
&& symbol.GetMembers(TopLevelStatements.MainMethodImplicitName).Any();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

using System.Linq;
using Microsoft.CodeAnalysis;
using SonarAnalyzer.Common;

namespace SonarAnalyzer.Helpers
{
Expand All @@ -33,5 +34,8 @@ public static SyntaxNode GetFirstSyntaxRef(this ISymbol symbol) =>

public static bool IsAutoProperty(this ISymbol symbol) =>
symbol.Kind == SymbolKind.Property && symbol.ContainingType.GetMembers().OfType<IFieldSymbol>().Any(x => symbol.Equals(x.AssociatedSymbol));

public static bool IsTopLevelMain(this ISymbol symbol) =>
symbol is IMethodSymbol { Name: TopLevelStatements.MainMethodImplicitName };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,17 @@ void TrackMethodDeclaration(SymbolAnalysisContext c)
{
foreach (var declaration in c.Symbol.DeclaringSyntaxReferences)
{
var methodIdentifier = GetMethodIdentifier(declaration.GetSyntax());
if (methodIdentifier.HasValue)
if (c.Symbol.IsTopLevelMain())
{
c.ReportIssue(Diagnostic.Create(input.Rule, methodIdentifier.Value.GetLocation()));
c.ReportIssue(Diagnostic.Create(input.Rule, null));
}
else
{
var methodIdentifier = GetMethodIdentifier(declaration.GetSyntax());
if (methodIdentifier.HasValue)
{
c.ReportIssue(Diagnostic.Create(input.Rule, methodIdentifier.Value.GetLocation()));
}
}
}
}
Expand All @@ -70,7 +77,8 @@ public Condition IsOrdinaryMethod() =>
context => context.MethodSymbol.MethodKind == MethodKind.Ordinary;

public Condition IsMainMethod() =>
context => context.MethodSymbol.IsMainMethod();
context => context.MethodSymbol.IsMainMethod()
|| context.MethodSymbol.IsTopLevelMain();

internal Condition AnyParameterIsOfType(params KnownType[] types)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
// Noncompliant {{Make sure that command line arguments are used safely here.}}
using System;

Console.WriteLine(args[0]); // FN
Console.WriteLine(args[0]);

static void Main(string arg) // Compliant, this is a local method named `Main`
{
Expand Down

0 comments on commit 1dfbfab

Please sign in to comment.