diff --git a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/INamedTypeSymbolExtensions.cs b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/INamedTypeSymbolExtensions.cs index 0c99b09358c..9f25bc54e3f 100644 --- a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/INamedTypeSymbolExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/INamedTypeSymbolExtensions.cs @@ -12,9 +12,6 @@ namespace StyleCop.Analyzers.Lightup { public static class INamedTypeSymbolExtensions { - public const string MainMethodImplicitName = "
$"; - - private static readonly HashSet ProgramClassImplicitName = new HashSet { "Program", "$" }; private static readonly Func TupleUnderlyingTypeAccessor; private static readonly Func> TupleElementsAccessor; private static readonly Func IsSerializableAccessor; @@ -31,10 +28,5 @@ static INamedTypeSymbolExtensions() public static ImmutableArray 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(); } } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ISymbolExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ISymbolExtensions.cs index 4016d3ee063..28263e275ac 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ISymbolExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ISymbolExtensions.cs @@ -69,9 +69,6 @@ public static ProgramState RemoveConstraint(this ISymbol symbol, SymbolicValueCo public static IEnumerable 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 GetDescendantNodes(Location location, SyntaxNode invocation) { var locationRootNode = location.SourceTree?.GetRoot(); diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs index fa6ce930387..92029618736 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs @@ -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; diff --git a/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpMethodDeclarationTracker.cs b/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpMethodDeclarationTracker.cs index 7a65feb967a..8eb428cc0fe 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpMethodDeclarationTracker.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpMethodDeclarationTracker.cs @@ -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 @@ -39,29 +42,49 @@ public override Condition ParameterAtIndexIsUsed(int index) => return false; } - var methodDeclaration = context.MethodSymbol.DeclaringSyntaxReferences - .Select(r => r.GetSyntax()) - .OfType() - .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(); - - 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() + .First(); + + return new MethodInfo(context.GetSemanticModel(declaration), declaration.GetTopLevelMainBody().SelectMany(x => x.DescendantNodes())); + } + else + { + var declaration = context.MethodSymbol + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType() + .FirstOrDefault(declaration => declaration.HasBodyOrExpressionBody()); + if (declaration == null) + { + return null; + } + + return new MethodInfo( + context.GetSemanticModel(declaration), + declaration.Body?.DescendantNodes() ?? declaration.ExpressionBody()?.DescendantNodes() ?? Enumerable.Empty()); + } + } + protected override SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration) => methodDeclaration switch { @@ -77,5 +100,18 @@ public override Condition ParameterAtIndexIsUsed(int index) => _ => null } }; + + private class MethodInfo + { + public SemanticModel SemanticModel { get; } + + public IEnumerable DescendantNodes { get; } + + public MethodInfo(SemanticModel model, IEnumerable descendantNodes) + { + SemanticModel = model; + DescendantNodes = descendantNodes; + } + } } } diff --git a/analyzers/src/SonarAnalyzer.Common/Common/TopLevelStatements.cs b/analyzers/src/SonarAnalyzer.Common/Common/TopLevelStatements.cs new file mode 100644 index 00000000000..f56af3a037d --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Common/TopLevelStatements.cs @@ -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 = "
$"; + + public static readonly HashSet ProgramClassImplicitName = new HashSet { "Program", "$" }; + } +} diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/INamedTypeSymbolExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/INamedTypeSymbolExtensions.cs new file mode 100644 index 00000000000..ff2256b6e11 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/INamedTypeSymbolExtensions.cs @@ -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(); + } +} diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.cs index eca8c50a31a..83fee792a58 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.cs @@ -20,6 +20,7 @@ using System.Linq; using Microsoft.CodeAnalysis; +using SonarAnalyzer.Common; namespace SonarAnalyzer.Helpers { @@ -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().Any(x => symbol.Equals(x.AssociatedSymbol)); + + public static bool IsTopLevelMain(this ISymbol symbol) => + symbol is IMethodSymbol { Name: TopLevelStatements.MainMethodImplicitName }; } } diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/MethodDeclarationTracker.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/MethodDeclarationTracker.cs index 919fc6b40cf..0cdfa9736de 100644 --- a/analyzers/src/SonarAnalyzer.Common/Trackers/MethodDeclarationTracker.cs +++ b/analyzers/src/SonarAnalyzer.Common/Trackers/MethodDeclarationTracker.cs @@ -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())); + } } } } @@ -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) { diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UsingCommandLineArguments.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UsingCommandLineArguments.CSharp9.cs index 202380d89e3..21c351a9261 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UsingCommandLineArguments.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/Hotspots/UsingCommandLineArguments.CSharp9.cs @@ -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` {