Skip to content

Commit e8f757a

Browse files
Jim8yajara87shargon
authored
Analyze unsupported ref element arguments (#1914)
Co-authored-by: Alvaro <amjarag@gmail.com> Co-authored-by: Shargon <shargon@gmail.com>
1 parent 9a83718 commit e8f757a

3 files changed

Lines changed: 122 additions & 3 deletions

File tree

src/Neo.SmartContract.Analyzer/AnalyzerReleases.Unshipped.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ NC4060 | Method | Error | BitOperationsUsageAnalyzer
99
Rule ID | Category | Severity | Notes
1010
--------|----------|----------|------------------------------------------------
1111
NC4032 | Usage | Error | Superseded by NC4036 for unsupported query expressions
12+
13+
### Changed Rules
14+
15+
Rule ID | New Category | New Severity | Old Category | Old Severity | Notes
16+
--------|--------------|--------------|--------------|--------------|------------------------------------------------
17+
NC4010 | Usage | Error | Usage | Warning | All reported by-reference forms are unsupported by the compiler

src/Neo.SmartContract.Analyzer/RefKeywordUsageAnalyzer.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ public class RefKeywordUsageAnalyzer : DiagnosticAnalyzer
2222
{
2323
public const string DiagnosticId = "NC4010";
2424
private static readonly string Title = "Ref keyword usage";
25-
private static readonly string MessageFormat = "'ref' keyword used in {0}";
26-
private static readonly string Description = "Checks for the usage of 'ref' keywords.";
25+
private static readonly string MessageFormat = "Unsupported by-reference usage in {0}";
26+
private static readonly string Description = "Reports by-reference invocation forms that the Neo compiler cannot lower.";
2727
private const string Category = "Usage";
2828

2929
private static readonly DiagnosticDescriptor Rule = new(
3030
DiagnosticId,
3131
Title,
3232
MessageFormat,
3333
Category,
34-
DiagnosticSeverity.Warning,
34+
DiagnosticSeverity.Error,
3535
isEnabledByDefault: true,
3636
description: Description);
3737

@@ -49,6 +49,18 @@ private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
4949
var invocationExpression = (InvocationExpressionSyntax)context.Node;
5050
foreach (var argument in invocationExpression.ArgumentList.Arguments)
5151
{
52+
if ((argument.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword) ||
53+
argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword)) &&
54+
argument.Expression is ElementAccessExpressionSyntax elementAccess &&
55+
GetElementContainerKind(context, elementAccess.Expression) is { } containerKind)
56+
{
57+
context.ReportDiagnostic(Diagnostic.Create(
58+
Rule,
59+
argument.GetLocation(),
60+
$"{containerKind} element argument; bind it to a ref local before forwarding"));
61+
continue;
62+
}
63+
5264
// Report only on unsupported 'in' arguments (regular ref/out are supported)
5365
if (argument.RefOrOutKeyword.IsKind(SyntaxKind.InKeyword) &&
5466
!IsDelegateInvocation(context, invocationExpression))
@@ -59,6 +71,26 @@ private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
5971
}
6072
}
6173

74+
private static string? GetElementContainerKind(
75+
SyntaxNodeAnalysisContext context,
76+
ExpressionSyntax expression)
77+
{
78+
var type = context.SemanticModel.GetTypeInfo(expression, context.CancellationToken).Type;
79+
if (type is IArrayTypeSymbol)
80+
{
81+
return "array";
82+
}
83+
84+
if (type is INamedTypeSymbol namedType &&
85+
namedType.ContainingNamespace.ToDisplayString() == "System" &&
86+
namedType.Name == "Span")
87+
{
88+
return "Span";
89+
}
90+
91+
return null;
92+
}
93+
6294
private static bool IsDelegateInvocation(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpression)
6395
{
6496
return context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken).Symbol is IMethodSymbol

tests/Neo.SmartContract.Analyzer.UnitTests/RefKeywordUsageAnalyzerUnitTest.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,87 @@ public void TestMethod(ref int value)
103103
await VerifyCS.VerifyAnalyzerAsync(test);
104104
}
105105

106+
[TestMethod]
107+
public async Task ArrayElementRefArgument_ShouldReportDiagnostic()
108+
{
109+
var test = @"
110+
class TestClass
111+
{
112+
private static void Increment(ref int value) => value++;
113+
114+
public static void Caller(int[] values)
115+
{
116+
Increment({|#0:ref values[0]|});
117+
}
118+
}";
119+
120+
var expected = VerifyCS.Diagnostic(RefKeywordUsageAnalyzer.DiagnosticId)
121+
.WithLocation(0)
122+
.WithArguments("array element argument; bind it to a ref local before forwarding");
123+
await VerifyCS.VerifyAnalyzerAsync(test, expected);
124+
}
125+
126+
[TestMethod]
127+
public async Task SpanElementRefArgument_ShouldReportDiagnostic()
128+
{
129+
var test = @"
130+
using System;
131+
132+
class TestClass
133+
{
134+
private static void Increment(ref int value) => value++;
135+
136+
public static void Caller()
137+
{
138+
Span<int> values = new int[] { 1, 2, 3 };
139+
Increment({|#0:ref values[1]|});
140+
}
141+
}";
142+
143+
var expected = VerifyCS.Diagnostic(RefKeywordUsageAnalyzer.DiagnosticId)
144+
.WithLocation(0)
145+
.WithArguments("Span element argument; bind it to a ref local before forwarding");
146+
await VerifyCS.VerifyAnalyzerAsync(test, expected);
147+
}
148+
149+
[TestMethod]
150+
public async Task ArrayElementOutArgument_ShouldReportDiagnostic()
151+
{
152+
var test = @"
153+
class TestClass
154+
{
155+
private static void Reset(out int value) => value = 0;
156+
157+
public static void Caller(int[] values)
158+
{
159+
Reset({|#0:out values[0]|});
160+
}
161+
}";
162+
163+
var expected = VerifyCS.Diagnostic(RefKeywordUsageAnalyzer.DiagnosticId)
164+
.WithLocation(0)
165+
.WithArguments("array element argument; bind it to a ref local before forwarding");
166+
await VerifyCS.VerifyAnalyzerAsync(test, expected);
167+
}
168+
169+
[TestMethod]
170+
public async Task RefLocalArgument_ShouldNotReportDiagnostic()
171+
{
172+
var test = @"
173+
class TestClass
174+
{
175+
private static void Increment(ref int value) => value++;
176+
177+
public static void Caller(int[] values)
178+
{
179+
ref int value = ref values[0];
180+
Increment(ref value);
181+
}
182+
}";
183+
184+
await VerifyPreviewAnalyzerAsync(test);
185+
}
186+
106187
[TestMethod]
107188
public async Task OutParameter_ShouldNotReportDiagnostic()
108189
{

0 commit comments

Comments
 (0)