-
Notifications
You must be signed in to change notification settings - Fork 227
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
Create rule S6932: Use model binding instead of reading raw request data #8930
Changes from all commits
79b5811
d2c4e6c
f09a581
15659e6
3a72741
f56b0d2
203a1d0
aa567ef
02d7aa7
7deaaec
383b87f
2a2ffaa
2c60f8c
927639a
d31f523
43ec236
7e8d31d
8142b5c
99a523b
2743a4e
8e64f8e
6cd98dc
f5922df
f83cb2c
dd1a605
9c9f0c5
0c1ff8a
f259d3e
a07b70b
d5f16fb
93c9cb5
19d418f
6204f53
b067076
e3de32e
af82bbc
942252f
f35eb46
c1726a3
a084719
1dfab22
af98074
3ca6b46
892d211
460d2ca
1b61e74
259c7cf
6131b53
ae75770
173fb53
7efce24
c1d3ad0
383e5a5
016edfa
bbe60e8
cd149e1
e8ec464
a157346
053e04f
26b0d76
d1b9fea
1d95916
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"title": "Use model binding instead of reading raw request data", | ||
"type": "CODE_SMELL", | ||
"status": "ready", | ||
"remediation": { | ||
"func": "Constant\/Issue", | ||
"constantCost": "5min" | ||
}, | ||
"tags": [ | ||
"asp.net" | ||
], | ||
"defaultSeverity": "Major", | ||
"ruleSpecification": "RSPEC-6932", | ||
"sqKey": "S6932", | ||
"scope": "Main", | ||
"quickfix": "infeasible", | ||
"code": { | ||
"impacts": { | ||
"MAINTAINABILITY": "HIGH", | ||
"RELIABILITY": "MEDIUM", | ||
"SECURITY": "MEDIUM" | ||
}, | ||
"attribute": "FOCUSED" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
/* | ||
* SonarAnalyzer for .NET | ||
* Copyright (C) 2015-2023 SonarSource SA | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure about Sonar copyright claims in a file that is completely copied from Roslyn. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will be discussed in https://trello.com/c/nw3yLmEY |
||
* 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.CodeDom.Compiler; | ||
|
||
namespace SonarAnalyzer.Extensions; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To have a separation between our extensions and Roslyn extensions, and avoid potential clash with new methods, when we will need to update this code, I would keep the original namespace from Roslyn, and have two separate extension classes, instead of partial classes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be discussed in https://trello.com/c/nw3yLmEY |
||
|
||
[GeneratedCode("Copied From Roslyn", "575bc42589145ba18b4f1cc2267d02695f861d8f")] | ||
public partial class ExpressionSyntaxExtensions | ||
{ | ||
// Copied from | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method copied are out-of-order w.r.t. the original file in Roslyn. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is discussed in https://trello.com/c/nw3yLmEY |
||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L319 | ||
public static bool IsWrittenTo( | ||
this ExpressionSyntax expression, | ||
SemanticModel semanticModel, | ||
CancellationToken cancellationToken) | ||
{ | ||
if (expression == null) | ||
return false; | ||
|
||
expression = GetExpressionToAnalyzeForWrites(expression); | ||
|
||
if (expression.IsOnlyWrittenTo()) | ||
return true; | ||
|
||
if (expression.IsInRefContext(out var refParent)) | ||
{ | ||
// most cases of `ref x` will count as a potential write of `x`. An important exception is: | ||
// `ref readonly y = ref x`. In that case, because 'y' can't be written to, this would not | ||
// be a write of 'x'. | ||
if (refParent.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: { } variableDeclarationType } } }) | ||
{ | ||
if (ScopedTypeSyntaxWrapper.IsInstance(variableDeclarationType) && (ScopedTypeSyntaxWrapper)variableDeclarationType is { } scopedType) | ||
{ | ||
variableDeclarationType = scopedType.Type; | ||
} | ||
|
||
if (RefTypeSyntaxWrapper.IsInstance(variableDeclarationType) && ((RefTypeSyntaxWrapper)variableDeclarationType).ReadOnlyKeyword != default) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
// Similar to `ref x`, `&x` allows reads and write of the value, meaning `x` may be (but is not definitely) | ||
// written to. | ||
if (expression.Parent.IsKind(SyntaxKind.AddressOfExpression)) | ||
return true; | ||
|
||
// We're written if we're used in a ++, or -- expression. | ||
if (expression.IsOperandOfIncrementOrDecrementExpression()) | ||
return true; | ||
|
||
if (expression.IsLeftSideOfAnyAssignExpression()) | ||
return true; | ||
|
||
// An extension method invocation with a ref-this parameter can write to an expression. | ||
if (expression.Parent is MemberAccessExpressionSyntax memberAccess && | ||
expression == memberAccess.Expression) | ||
{ | ||
var symbol = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol; | ||
if (symbol is IMethodSymbol | ||
{ | ||
MethodKind: MethodKind.ReducedExtension, | ||
ReducedFrom.Parameters: { Length: > 0 } parameters, | ||
} && parameters[0].RefKind == RefKind.Ref) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L221 | ||
private static ExpressionSyntax GetExpressionToAnalyzeForWrites(ExpressionSyntax? expression) | ||
{ | ||
if (expression.IsRightSideOfDotOrArrow()) | ||
{ | ||
expression = (ExpressionSyntax)expression.Parent; | ||
} | ||
|
||
expression = (ExpressionSyntax)expression.WalkUpParentheses(); | ||
|
||
return expression; | ||
} | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L63 | ||
public static bool IsRightSideOfDotOrArrow(this ExpressionSyntax name) | ||
=> IsAnyMemberAccessExpressionName(name) || IsRightSideOfQualifiedName(name); | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L41 | ||
public static bool IsAnyMemberAccessExpressionName(this ExpressionSyntax expression) | ||
{ | ||
if (expression == null) | ||
return false; | ||
|
||
return expression == (expression.Parent as MemberAccessExpressionSyntax)?.Name || | ||
expression.IsMemberBindingExpressionName(); | ||
} | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L50 | ||
public static bool IsMemberBindingExpressionName(this ExpressionSyntax expression) | ||
=> expression?.Parent is MemberBindingExpressionSyntax memberBinding && | ||
memberBinding.Name == expression; | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L54 | ||
public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression) | ||
=> expression?.Parent is QualifiedNameSyntax qualifiedName && qualifiedName.Right == expression; | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L233 | ||
public static bool IsOnlyWrittenTo(this ExpressionSyntax expression) | ||
{ | ||
expression = GetExpressionToAnalyzeForWrites(expression); | ||
|
||
if (expression != null) | ||
{ | ||
if (expression.IsInOutContext()) | ||
{ | ||
return true; | ||
} | ||
|
||
if (expression.Parent != null) | ||
{ | ||
if (expression.IsLeftSideOfAssignExpression()) | ||
{ | ||
return true; | ||
} | ||
|
||
if (expression.IsAttributeNamedArgumentIdentifier()) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
if (IsExpressionOfArgumentInDeconstruction(expression)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// If this declaration or identifier is part of a deconstruction, find the deconstruction. | ||
/// If found, returns either an assignment expression or a foreach variable statement. | ||
/// Returns null otherwise. | ||
/// | ||
/// copied from SyntaxExtensions.GetContainingDeconstruction. | ||
/// </summary> | ||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L273 | ||
private static bool IsExpressionOfArgumentInDeconstruction(ExpressionSyntax expr) | ||
{ | ||
if (!expr.IsParentKind(SyntaxKind.Argument)) | ||
{ | ||
return false; | ||
} | ||
|
||
while (true) | ||
{ | ||
var parent = expr.Parent; | ||
if (parent == null) | ||
{ | ||
return false; | ||
} | ||
|
||
switch (parent.Kind()) | ||
{ | ||
case SyntaxKind.Argument: | ||
if (parent.Parent?.Kind() == SyntaxKindEx.TupleExpression) | ||
{ | ||
expr = (ExpressionSyntax)parent.Parent; | ||
continue; | ||
} | ||
|
||
return false; | ||
case SyntaxKind.SimpleAssignmentExpression: | ||
if (((AssignmentExpressionSyntax)parent).Left == expr) | ||
{ | ||
return true; | ||
} | ||
|
||
return false; | ||
case SyntaxKindEx.ForEachVariableStatement: | ||
if (((ForEachVariableStatementSyntaxWrapper)parent).Variable == expr) | ||
{ | ||
return true; | ||
} | ||
|
||
return false; | ||
|
||
default: | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L190 | ||
public static bool IsInOutContext(this ExpressionSyntax expression) | ||
=> expression?.Parent is ArgumentSyntax { RefOrOutKeyword: SyntaxToken { RawKind: (int)SyntaxKind.OutKeyword } } argument && | ||
argument.Expression == expression; | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L383 | ||
public static bool IsAttributeNamedArgumentIdentifier(this ExpressionSyntax expression) | ||
{ | ||
var nameEquals = expression?.Parent as NameEqualsSyntax; | ||
return nameEquals.IsParentKind(SyntaxKind.AttributeArgument); | ||
} | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L194 | ||
public static bool IsInRefContext(this ExpressionSyntax expression) | ||
=> IsInRefContext(expression, out _); | ||
|
||
/// <summary> | ||
/// Returns true if this expression is in some <c>ref</c> keyword context. If <see langword="true"/> then | ||
/// <paramref name="refParent"/> will be the node containing the <see langword="ref"/> keyword. | ||
/// </summary> | ||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L201 | ||
public static bool IsInRefContext(this ExpressionSyntax expression, out SyntaxNode refParent) | ||
{ | ||
while (expression?.Parent is ParenthesizedExpressionSyntax or PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression }) | ||
expression = (ExpressionSyntax)expression.Parent; | ||
|
||
if (expression?.Parent switch | ||
{ | ||
ArgumentSyntax { RefOrOutKeyword.RawKind: (int)SyntaxKind.RefKeyword } => true, | ||
var x when RefExpressionSyntaxWrapper.IsInstance(x) => true, | ||
_ => false, | ||
}) | ||
{ | ||
refParent = expression.Parent; | ||
return true; | ||
} | ||
|
||
refParent = null; | ||
return false; | ||
} | ||
|
||
// Copy of | ||
// https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L389 | ||
public static bool IsOperandOfIncrementOrDecrementExpression(this ExpressionSyntax expression) | ||
{ | ||
if (expression?.Parent is SyntaxNode parent) | ||
{ | ||
switch (parent.Kind()) | ||
{ | ||
case SyntaxKind.PostIncrementExpression: | ||
case SyntaxKind.PreIncrementExpression: | ||
case SyntaxKind.PostDecrementExpression: | ||
case SyntaxKind.PreDecrementExpression: | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file seems to follow Roslyn code conventions, which is where the file is taken from, as opposed to our own.
Given that we don't have an outcome yet, for this Trello card, I will not review this file from a stylistic point of view in this PR, but only functionally.
However, I would ask you to mention this file in the "How we work" ticket, as a possible action to to review/refactor this file, once a decision is taken on how to deal with imported code from Roslyn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not review. We will not make any edits to the file; the code is already being tested. We want to treat it as if it was a third party dll.