Skip to content

Commit 19e7040

Browse files
committed
Analyzer for string concatenation in raw SQL methods.
1 parent 63dba7a commit 19e7040

9 files changed

+1032
-48
lines changed

src/EFCore.Analyzers/AnalyzerReleases.Shipped.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,11 @@ EF1000 | Security | Disabled | RawSqlStringInjectionDiagnosticAnalyzer, [Docume
2222
### New Rules
2323
Rule ID | Category | Severity | Notes
2424
--------|----------|----------|-------
25-
EF1002 | Security | Warning | InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer, [Documentation](https://learn.microsoft.com/ef/core/querying/sql-queries#passing-parameters)
25+
EF1002 | Security | Warning | InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer, [Documentation](https://learn.microsoft.com/ef/core/querying/sql-queries#passing-parameters)
26+
27+
## Release 10.0.0
28+
29+
### New Rules
30+
Rule ID | Category | Severity | Notes
31+
--------|----------|----------|-------
32+
EF1003 | Security | Warning | StringConcatenationUsageInRawQueriesDiagnosticAnalyzer, [Documentation](https://learn.microsoft.com/ef/core/querying/sql-queries#passing-parameters)

src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer.cs

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private static bool AnalyzeFromSqlRawInvocation(IInvocationOperation invocation)
9999
Debug.Assert(targetMethod.Name == "FromSqlRaw");
100100

101101
var compilation = invocation.SemanticModel!.Compilation;
102-
var correctFromSqlRaw = FromSqlRawMethod(compilation);
102+
var correctFromSqlRaw = SqlRawMethodsCompilationHelper.FromSqlRawMethod(compilation);
103103

104104
Debug.Assert(correctFromSqlRaw is not null, "Unable to find original `FromSqlRaw` method");
105105

@@ -124,7 +124,7 @@ private static bool AnalyzeExecuteSqlRawInvocation(IInvocationOperation invocati
124124

125125
if (targetMethod.Name == "ExecuteSqlRaw")
126126
{
127-
var correctMethods = ExecuteSqlRawMethods(compilation);
127+
var correctMethods = SqlRawMethodsCompilationHelper.ExecuteSqlRawMethods(compilation);
128128

129129
Debug.Assert(correctMethods.Any(), "Unable to find any `ExecuteSqlRaw` methods");
130130

@@ -135,7 +135,7 @@ private static bool AnalyzeExecuteSqlRawInvocation(IInvocationOperation invocati
135135
}
136136
else
137137
{
138-
var correctMethods = ExecuteSqlRawAsyncMethods(compilation);
138+
var correctMethods = SqlRawMethodsCompilationHelper.ExecuteSqlRawAsyncMethods(compilation);
139139

140140
Debug.Assert(correctMethods.Any(), "Unable to find any `ExecuteSqlRawAsync` methods");
141141

@@ -164,7 +164,7 @@ private static bool AnalyzeSqlQueryRawInvocation(IInvocationOperation invocation
164164

165165
var compilation = invocation.SemanticModel!.Compilation;
166166

167-
var correctSqlQueryRaw = SqlQueryRawMethod(compilation);
167+
var correctSqlQueryRaw = SqlRawMethodsCompilationHelper.SqlQueryRawMethod(compilation);
168168

169169
Debug.Assert(correctSqlQueryRaw is not null, "Unable to find original `SqlQueryRaw` method");
170170

@@ -203,28 +203,4 @@ private static bool AnalyzeInterpolatedString(IInterpolatedStringOperation inter
203203

204204
return false;
205205
}
206-
207-
private static IMethodSymbol? FromSqlRawMethod(Compilation compilation)
208-
{
209-
var type = compilation.RelationalQueryableExtensionsType();
210-
return (IMethodSymbol?)type?.GetMembers("FromSqlRaw").FirstOrDefault(s => s is IMethodSymbol);
211-
}
212-
213-
private static IEnumerable<IMethodSymbol> ExecuteSqlRawMethods(Compilation compilation)
214-
{
215-
var type = compilation.RelationalDatabaseFacadeExtensionsType();
216-
return type?.GetMembers("ExecuteSqlRaw").Where(s => s is IMethodSymbol).Cast<IMethodSymbol>() ?? [];
217-
}
218-
219-
private static IEnumerable<IMethodSymbol> ExecuteSqlRawAsyncMethods(Compilation compilation)
220-
{
221-
var type = compilation.RelationalDatabaseFacadeExtensionsType();
222-
return type?.GetMembers("ExecuteSqlRawAsync").Where(s => s is IMethodSymbol).Cast<IMethodSymbol>() ?? [];
223-
}
224-
225-
private static IMethodSymbol? SqlQueryRawMethod(Compilation compilation)
226-
{
227-
var type = compilation.RelationalDatabaseFacadeExtensionsType();
228-
return (IMethodSymbol?)type?.GetMembers("SqlQueryRaw").FirstOrDefault(s => s is IMethodSymbol);
229-
}
230206
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Diagnostics.CodeAnalysis
5+
{
6+
#if !NETSTANDARD2_1
7+
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
8+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
9+
#if SYSTEM_PRIVATE_CORELIB
10+
public
11+
#else
12+
internal
13+
#endif
14+
sealed class AllowNullAttribute : Attribute
15+
{ }
16+
17+
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
18+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
19+
#if SYSTEM_PRIVATE_CORELIB
20+
public
21+
#else
22+
internal
23+
#endif
24+
sealed class DisallowNullAttribute : Attribute
25+
{ }
26+
27+
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
28+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
29+
#if SYSTEM_PRIVATE_CORELIB
30+
public
31+
#else
32+
internal
33+
#endif
34+
sealed class MaybeNullAttribute : Attribute
35+
{ }
36+
37+
/// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary>
38+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
39+
#if SYSTEM_PRIVATE_CORELIB
40+
public
41+
#else
42+
internal
43+
#endif
44+
sealed class NotNullAttribute : Attribute
45+
{ }
46+
47+
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
48+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
49+
#if SYSTEM_PRIVATE_CORELIB
50+
public
51+
#else
52+
internal
53+
#endif
54+
sealed class MaybeNullWhenAttribute : Attribute
55+
{
56+
/// <summary>Initializes the attribute with the specified return value condition.</summary>
57+
/// <param name="returnValue">
58+
/// The return value condition. If the method returns this value, the associated parameter may be null.
59+
/// </param>
60+
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
61+
62+
/// <summary>Gets the return value condition.</summary>
63+
public bool ReturnValue { get; }
64+
}
65+
66+
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
67+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
68+
#if SYSTEM_PRIVATE_CORELIB
69+
public
70+
#else
71+
internal
72+
#endif
73+
sealed class NotNullWhenAttribute : Attribute
74+
{
75+
/// <summary>Initializes the attribute with the specified return value condition.</summary>
76+
/// <param name="returnValue">
77+
/// The return value condition. If the method returns this value, the associated parameter will not be null.
78+
/// </param>
79+
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
80+
81+
/// <summary>Gets the return value condition.</summary>
82+
public bool ReturnValue { get; }
83+
}
84+
85+
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
86+
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
87+
#if SYSTEM_PRIVATE_CORELIB
88+
public
89+
#else
90+
internal
91+
#endif
92+
sealed class NotNullIfNotNullAttribute : Attribute
93+
{
94+
/// <summary>Initializes the attribute with the associated parameter name.</summary>
95+
/// <param name="parameterName">
96+
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
97+
/// </param>
98+
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
99+
100+
/// <summary>Gets the associated parameter name.</summary>
101+
public string ParameterName { get; }
102+
}
103+
104+
/// <summary>Applied to a method that will never return under any circumstance.</summary>
105+
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
106+
#if SYSTEM_PRIVATE_CORELIB
107+
public
108+
#else
109+
internal
110+
#endif
111+
sealed class DoesNotReturnAttribute : Attribute
112+
{ }
113+
114+
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
115+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
116+
#if SYSTEM_PRIVATE_CORELIB
117+
public
118+
#else
119+
internal
120+
#endif
121+
sealed class DoesNotReturnIfAttribute : Attribute
122+
{
123+
/// <summary>Initializes the attribute with the specified parameter value.</summary>
124+
/// <param name="parameterValue">
125+
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
126+
/// the associated parameter matches this value.
127+
/// </param>
128+
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
129+
130+
/// <summary>Gets the condition parameter value.</summary>
131+
public bool ParameterValue { get; }
132+
}
133+
#endif
134+
135+
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
136+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
137+
#if SYSTEM_PRIVATE_CORELIB
138+
public
139+
#else
140+
internal
141+
#endif
142+
sealed class MemberNotNullAttribute : Attribute
143+
{
144+
/// <summary>Initializes the attribute with a field or property member.</summary>
145+
/// <param name="member">
146+
/// The field or property member that is promised to be not-null.
147+
/// </param>
148+
public MemberNotNullAttribute(string member) => Members = [member];
149+
150+
/// <summary>Initializes the attribute with the list of field and property members.</summary>
151+
/// <param name="members">
152+
/// The list of field and property members that are promised to be not-null.
153+
/// </param>
154+
public MemberNotNullAttribute(params string[] members) => Members = members;
155+
156+
/// <summary>Gets field or property member names.</summary>
157+
public string[] Members { get; }
158+
}
159+
160+
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
161+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
162+
#if SYSTEM_PRIVATE_CORELIB
163+
public
164+
#else
165+
internal
166+
#endif
167+
sealed class MemberNotNullWhenAttribute : Attribute
168+
{
169+
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
170+
/// <param name="returnValue">
171+
/// The return value condition. If the method returns this value, the associated field or property member will not be null.
172+
/// </param>
173+
/// <param name="member">
174+
/// The field or property member that is promised to be not-null.
175+
/// </param>
176+
public MemberNotNullWhenAttribute(bool returnValue, string member)
177+
{
178+
ReturnValue = returnValue;
179+
Members = [member];
180+
}
181+
182+
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
183+
/// <param name="returnValue">
184+
/// The return value condition. If the method returns this value, the associated field and property members will not be null.
185+
/// </param>
186+
/// <param name="members">
187+
/// The list of field and property members that are promised to be not-null.
188+
/// </param>
189+
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
190+
{
191+
ReturnValue = returnValue;
192+
Members = members;
193+
}
194+
195+
/// <summary>Gets the return value condition.</summary>
196+
public bool ReturnValue { get; }
197+
198+
/// <summary>Gets field or property member names.</summary>
199+
public string[] Members { get; }
200+
}
201+
}

src/EFCore.Analyzers/Properties/AnalyzerStrings.Designer.cs

Lines changed: 32 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)