Skip to content

Commit

Permalink
Rule S2077: add support for Entity Framework Core new overloads (#5296)
Browse files Browse the repository at this point in the history
  • Loading branch information
costin-zaharia-sonarsource authored Jan 25, 2022
1 parent c9f45b0 commit c8662a7
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ protected override Location SecondaryLocationForExpression(ExpressionSyntax node
return Location.None;
}

if (node.Parent is EqualsValueClauseSyntax equalsValueClause
&& equalsValueClause.Parent is VariableDeclaratorSyntax declarationSyntax)
if (node.Parent is EqualsValueClauseSyntax {Parent: VariableDeclaratorSyntax declarationSyntax})
{
return declarationSyntax.Identifier.GetLocation();
}
Expand All @@ -95,7 +94,7 @@ private static bool AllConstants(IEnumerable<ArgumentSyntax> arguments, Semantic
private static bool IsConcatenationOfConstants(BinaryExpressionSyntax binaryExpression, SemanticModel semanticModel)
{
System.Diagnostics.Debug.Assert(binaryExpression.IsKind(SyntaxKind.AddExpression), "Binary expression should be of syntax kind add expression.");
if ((semanticModel.GetTypeInfo(binaryExpression).Type is ITypeSymbol) && binaryExpression.Right.HasConstantValue(semanticModel))
if ((semanticModel.GetTypeInfo(binaryExpression).Type != null) && binaryExpression.Right.HasConstantValue(semanticModel))
{
var nestedLeft = binaryExpression.Left;
var nestedBinary = nestedLeft as BinaryExpressionSyntax;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public abstract class ExecutingSqlQueriesBase<TSyntaxKind, TExpressionSyntax, TI
where TExpressionSyntax : SyntaxNode
where TIdentifierNameSyntax : SyntaxNode
{
protected const string DiagnosticId = "S2077";
private const string DiagnosticId = "S2077";
private const string AssignmentWithFormattingMessage = "SQL Query is dynamically formatted and assigned to {0}.";
private const string AssignmentMessage = "SQL query is assigned to {0}.";
private const string MessageFormat = "Make sure using a dynamically formatted SQL query is safe here.";
Expand All @@ -38,11 +38,12 @@ public abstract class ExecutingSqlQueriesBase<TSyntaxKind, TExpressionSyntax, TI

private readonly KnownType[] constructorsForFirstArgument =
{
KnownType.Microsoft_EntityFrameworkCore_RawSqlString,
KnownType.System_Data_SqlClient_SqlCommand,
KnownType.System_Data_SqlClient_SqlDataAdapter,
KnownType.System_Data_Odbc_OdbcCommand,
KnownType.System_Data_Odbc_OdbcDataAdapter,
KnownType.System_Data_SqlClient_SqlCommand,
KnownType.System_Data_SqlClient_SqlDataAdapter,
KnownType.System_Data_Sqlite_SqliteCommand,
KnownType.System_Data_Sqlite_SQLiteDataAdapter,
KnownType.System_Data_SqlServerCe_SqlCeCommand,
KnownType.System_Data_SqlServerCe_SqlCeDataAdapter,
KnownType.System_Data_OracleClient_OracleCommand,
Expand All @@ -51,52 +52,58 @@ public abstract class ExecutingSqlQueriesBase<TSyntaxKind, TExpressionSyntax, TI
KnownType.MySql_Data_MySqlClient_MySqlDataAdapter,
KnownType.MySql_Data_MySqlClient_MySqlScript,
KnownType.Microsoft_Data_Sqlite_SqliteCommand,
KnownType.System_Data_Sqlite_SqliteCommand,
KnownType.System_Data_Sqlite_SQLiteDataAdapter,
KnownType.Microsoft_EntityFrameworkCore_RawSqlString
};

private readonly KnownType[] constructorsForSecondArgument =
{
KnownType.MySql_Data_MySqlClient_MySqlScript,
KnownType.MySql_Data_MySqlClient_MySqlScript
};

private readonly MemberDescriptor[] invocationsForFirstTwoArguments =
{
new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlCommandAsync"),
new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlCommand"),
new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions, "FromSql"),
new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlCommandAsync"),
new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlCommand"),
new(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions, "FromSql")
};

private readonly MemberDescriptor[] invocationsForFirstTwoArgumentsAfterV2 =
{
new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlRaw"),
new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlRawAsync"),
new(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions, "FromSqlRaw")
};

private readonly MemberDescriptor[] invocationsForFirstArgument =
{
new MemberDescriptor(KnownType.System_Data_Sqlite_SqliteCommand, "Execute"),
new(KnownType.System_Data_Sqlite_SqliteCommand, "Execute")
};

private readonly MemberDescriptor[] invocationsForSecondArgument =
{
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataRow"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataRowAsync"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataset"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDatasetAsync"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteNonQuery"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteNonQueryAsync"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteReader"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteReaderAsync"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteScalar"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteScalarAsync"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "UpdateDataSet"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlHelper, "UpdateDataSetAsync"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataRow"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataRowAsync"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataset"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDatasetAsync"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteNonQuery"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteNonQueryAsync"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteReader"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteReaderAsync"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteScalar"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteScalarAsync"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "UpdateDataSet"),
new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "UpdateDataSetAsync")
};

private readonly MemberDescriptor[] properties =
{
new MemberDescriptor(KnownType.System_Data_Odbc_OdbcCommand, "CommandText"),
new MemberDescriptor(KnownType.System_Data_OracleClient_OracleCommand, "CommandText"),
new MemberDescriptor(KnownType.System_Data_SqlClient_SqlCommand, "CommandText"),
new MemberDescriptor(KnownType.System_Data_SqlServerCe_SqlCeCommand, "CommandText"),
new MemberDescriptor(KnownType.MySql_Data_MySqlClient_MySqlCommand, "CommandText"),
new MemberDescriptor(KnownType.Microsoft_Data_Sqlite_SqliteCommand, "CommandText"),
new MemberDescriptor(KnownType.System_Data_Sqlite_SqliteCommand, "CommandText"),
new(KnownType.System_Data_Odbc_OdbcCommand, "CommandText"),
new(KnownType.System_Data_OracleClient_OracleCommand, "CommandText"),
new(KnownType.System_Data_SqlClient_SqlCommand, "CommandText"),
new(KnownType.System_Data_Sqlite_SqliteCommand, "CommandText"),
new(KnownType.System_Data_SqlServerCe_SqlCeCommand, "CommandText"),
new(KnownType.Microsoft_Data_Sqlite_SqliteCommand, "CommandText"),
new(KnownType.MySql_Data_MySqlClient_MySqlCommand, "CommandText")
};

protected abstract TExpressionSyntax GetArgumentAtIndex(InvocationContext context, int index);
Expand All @@ -113,10 +120,12 @@ protected override void Initialize(TrackerInput input)
var inv = Language.Tracker.Invocation;
inv.Track(input,
inv.MatchMethod(invocationsForFirstTwoArguments),
inv.And(
MethodHasRawSqlQueryParameter(),
inv.Or(ArgumentAtIndexIsTracked(0), ArgumentAtIndexIsTracked(1))
),
inv.And(MethodHasRawSqlQueryParameter(), inv.Or(ArgumentAtIndexIsTracked(0), ArgumentAtIndexIsTracked(1))),
inv.ExceptWhen(inv.ArgumentAtIndexIsConstant(0)));

inv.Track(input,
inv.MatchMethod(invocationsForFirstTwoArgumentsAfterV2),
inv.Or(ArgumentAtIndexIsTracked(0), ArgumentAtIndexIsTracked(1)),
inv.ExceptWhen(inv.ArgumentAtIndexIsConstant(0)));

TrackInvocations(input, invocationsForFirstArgument, FirstArgumentIndex);
Expand Down Expand Up @@ -161,7 +170,7 @@ private void TrackObjectCreation(TrackerInput input, KnownType[] objectCreationT
t.Track(input,
t.MatchConstructor(objectCreationTypes),
t.ArgumentAtIndexIs(argumentIndex, KnownType.System_String),
c => IsTracked(GetArgumentAtIndex(c, argumentIndex), c),
c => IsTracked(GetArgumentAtIndex(c, argumentIndex), c),
t.ExceptWhen(t.ArgumentAtIndexIsConst(argumentIndex)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace SonarAnalyzer.Helpers.Trackers
public class VisualBasicInvocationTracker : InvocationTracker<SyntaxKind>
{
protected override ILanguageFacade<SyntaxKind> Language => VisualBasicFacade.Instance;
protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.InvocationExpression };
protected override SyntaxKind[] TrackedSyntaxKinds { get; } = { SyntaxKind.InvocationExpression };

public override Condition ArgumentAtIndexIsConstant(int index) =>
context => ((InvocationExpressionSyntax)context.Node).ArgumentList is { } argumentList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private static string GetTestCaseFileName(string analyzerName) =>
"UsingCookies" => "UsingCookies_Net46",
"LooseFilePermissions" => "LooseFilePermissions.Windows",
#else
"ExecutingSqlQueries" => "ExecutingSqlQueries_NetCore",
"ExecutingSqlQueries" => "ExecutingSqlQueries_EntityFrameworkCoreLatest",
"UsingCookies" => "UsingCookies_NetCore",
"LooseFilePermissions" => "LooseFilePermissions.Unix",
"PermissiveCors" => "PermissiveCors.Net",
Expand Down Expand Up @@ -120,7 +120,7 @@ private static IEnumerable<MetadataReference> GetAdditionalReferences(string ana
nameof(UsingRegularExpressions) => MetadataReferenceFacade.RegularExpressions,
#if NET
nameof(DisablingCsrfProtection) => DisablingCsrfProtectionTest.AdditionalReferences(),
nameof(ExecutingSqlQueries) => ExecutingSqlQueriesTest.GetReferencesNetCore(Constants.DotNetCore220Version),
nameof(ExecutingSqlQueries) => ExecutingSqlQueriesTest.GetReferencesEntityFrameworkNetCore(Constants.NuGetLatestVersion),
nameof(LooseFilePermissions) => NuGetMetadataReference.MonoPosixNetStandard(),
nameof(PermissiveCors) => PermissiveCorsTest.AdditionalReferences,
nameof(UsingCookies) => UsingCookies.GetAdditionalReferencesForNetCore(Constants.DotNetCore220Version),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,25 @@ namespace SonarAnalyzer.UnitTest.Rules
[TestClass]
public class ExecutingSqlQueriesTest
{
private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled)).WithBasePath("Hotspots");
private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled)).WithBasePath("Hotspots");

#if NETFRAMEWORK // System.Data.OracleClient.dll is not available on .Net Core

[TestMethod]
public void ExecutingSqlQueries_CS_Net46() =>
OldVerifier.VerifyAnalyzer(
@"TestCases\Hotspots\ExecutingSqlQueries_Net46.cs",
new CS.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled),
GetReferencesNet46(Constants.NuGetLatestVersion));
builderCS
.AddPaths(@"ExecutingSqlQueries_Net46.cs")
.AddReferences(GetReferencesNet46(Constants.NuGetLatestVersion))
.Verify();

[TestMethod]
public void ExecutingSqlQueries_VB_Net46() =>
OldVerifier.VerifyAnalyzer(
@"TestCases\Hotspots\ExecutingSqlQueries_Net46.vb",
new VB.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled),
ParseOptionsHelper.FromVisualBasic15,
GetReferencesNet46(Constants.NuGetLatestVersion));
builderVB
.AddPaths(@"ExecutingSqlQueries_Net46.vb")
.WithOptions(ParseOptionsHelper.FromVisualBasic15)
.AddReferences(GetReferencesNet46(Constants.NuGetLatestVersion))
.Verify();

internal static IEnumerable<MetadataReference> GetReferencesNet46(string sqlServerCeVersion) =>
NetStandardMetadataReference.Netstandard
Expand All @@ -62,36 +65,55 @@ internal static IEnumerable<MetadataReference> GetReferencesNet46(string sqlServ
#else

[TestMethod]
public void ExecutingSqlQueries_CS_NetCore() =>
OldVerifier.VerifyAnalyzer(
@"TestCases\Hotspots\ExecutingSqlQueries_NetCore.cs",
new CS.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled),
ParseOptionsHelper.FromCSharp8,
GetReferencesNetCore(Constants.DotNetCore220Version));
public void ExecutingSqlQueries_CS_EntityFrameworkCore2() =>
builderCS
.AddPaths(@"ExecutingSqlQueries_EntityFrameworkCore2.cs")
.WithOptions(ParseOptionsHelper.FromCSharp8)
.AddReferences(GetReferencesEntityFrameworkNetCore("2.2.6"))
.Verify();

[TestMethod]
public void ExecutingSqlQueries_CS_EntityFrameworkCoreLatest() =>
builderCS
.AddPaths(@"ExecutingSqlQueries_EntityFrameworkCoreLatest.cs")
.WithOptions(ParseOptionsHelper.FromCSharp8)
.AddReferences(GetReferencesEntityFrameworkNetCore(Constants.NuGetLatestVersion))
.Verify();

[TestMethod]
public void ExecutingSqlQueries_CSharp9() =>
OldVerifier.VerifyAnalyzerFromCSharp9Console(
@"TestCases\Hotspots\ExecutingSqlQueries.CSharp9.cs",
new CS.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled),
GetReferencesNetCore(Constants.DotNetCore220Version).Concat(NuGetMetadataReference.MicrosoftDataSqliteCore()));
builderCS
.AddPaths(@"ExecutingSqlQueries.CSharp9.cs")
.WithTopLevelStatements()
.AddReferences(GetReferencesEntityFrameworkNetCore(Constants.DotNetCore220Version).Concat(NuGetMetadataReference.MicrosoftDataSqliteCore()))
.Verify();

[TestMethod]
public void ExecutingSqlQueries_CSharp10() =>
OldVerifier.VerifyAnalyzerFromCSharp10Console(
@"TestCases\Hotspots\ExecutingSqlQueries.CSharp10.cs",
new CS.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled),
GetReferencesNetCore(Constants.DotNetCore220Version).Concat(NuGetMetadataReference.MicrosoftDataSqliteCore()));
builderCS
.AddPaths(@"ExecutingSqlQueries.CSharp10.cs")
.WithOptions(ParseOptionsHelper.FromCSharp10)
.WithTopLevelStatements()
.AddReferences(GetReferencesEntityFrameworkNetCore(Constants.DotNetCore220Version).Concat(NuGetMetadataReference.MicrosoftDataSqliteCore()))
.Verify();

[TestMethod]
public void ExecutingSqlQueries_VB_EntityFrameworkCore2() =>
builderVB
.AddPaths(@"ExecutingSqlQueries_EntityFrameworkCore2.vb")
.WithOptions(ParseOptionsHelper.FromVisualBasic15)
.AddReferences(GetReferencesEntityFrameworkNetCore(Constants.DotNetCore220Version))
.Verify();

[TestMethod]
public void ExecutingSqlQueries_VB_NetCore() =>
OldVerifier.VerifyAnalyzer(
@"TestCases\Hotspots\ExecutingSqlQueries_NetCore.vb",
new VB.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled),
ParseOptionsHelper.FromVisualBasic15,
GetReferencesNetCore(Constants.DotNetCore220Version));
public void ExecutingSqlQueries_VB_EntityFrameworkCoreLatest() =>
builderVB
.AddPaths(@"ExecutingSqlQueries_EntityFrameworkCoreLatest.vb")
.WithOptions(ParseOptionsHelper.FromVisualBasic15)
.AddReferences(GetReferencesEntityFrameworkNetCore(Constants.NuGetLatestVersion))
.Verify();

internal static IEnumerable<MetadataReference> GetReferencesNetCore(string entityFrameworkVersion) =>
internal static IEnumerable<MetadataReference> GetReferencesEntityFrameworkNetCore(string entityFrameworkVersion) =>
Enumerable.Empty<MetadataReference>()
.Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCore(entityFrameworkVersion))
.Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational(entityFrameworkVersion));
Expand Down
Loading

0 comments on commit c8662a7

Please sign in to comment.