Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule S4823: check also top level statements #5129

Merged
merged 1 commit into from
Dec 2, 2021
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 @@ -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 @@ -24,7 +24,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SonarAnalyzer.SymbolicExecution.Sonar;
using SonarAnalyzer.SymbolicExecution.Sonar.Constraints;
using StyleCop.Analyzers.Lightup;

namespace SonarAnalyzer.Extensions
{
Expand Down Expand Up @@ -69,9 +68,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()));
costin-zaharia-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}
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 sealed 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.Immutable;

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

public static readonly ImmutableHashSet<string> ProgramClassImplicitName = ImmutableHashSet.Create("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 @@ -43,9 +43,18 @@ public void Track(TrackerInput input, params Condition[] conditions)

void TrackMethodDeclaration(SymbolAnalysisContext c)
{
if (IsTrackedMethod((IMethodSymbol)c.Symbol, c.Compilation))
if (!IsTrackedMethod((IMethodSymbol)c.Symbol, c.Compilation))
{
foreach (var declaration in c.Symbol.DeclaringSyntaxReferences)
return;
}

foreach (var declaration in c.Symbol.DeclaringSyntaxReferences)
{
if (c.Symbol.IsTopLevelMain())
{
c.ReportIssue(Diagnostic.Create(input.Rule, null));
}
else
{
var methodIdentifier = GetMethodIdentifier(declaration.GetSyntax());
if (methodIdentifier.HasValue)
Expand All @@ -70,7 +79,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