Skip to content

Commit d760312

Browse files
committed
Add diagnostics for unintendedly passing a single null value to the [Arguments] or [Params] attribute
1 parent a688920 commit d760312

File tree

9 files changed

+589
-92
lines changed

9 files changed

+589
-92
lines changed

src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ BDN1205 | Usage | Error | BDN1205_Attributes_GeneralParameterAttributes_N
2828
BDN1206 | Usage | Error | BDN1206_Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly
2929
BDN1207 | Usage | Error | BDN1207_Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter
3030
BDN1300 | Usage | Error | BDN1300_Attributes_ParamsAttribute_MustHaveValues
31-
BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_MustHaveMatchingValueType
32-
BDN1302 | Usage | Info | BDN1302_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute
33-
BDN1303 | Usage | Error | BDN1303_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType
34-
BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool
31+
BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_SingleNullArgumentNotAllowed
32+
BDN1302 | Usage | Error | BDN1302_Attributes_ParamsAttribute_MustHaveMatchingValueType
33+
BDN1303 | Usage | Info | BDN1303_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute
34+
BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType
35+
BDN1305 | Usage | Error | BDN1305_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool
3536
BDN1400 | Usage | Error | BDN1400_Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters
3637
BDN1500 | Usage | Error | BDN1500_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute
37-
BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount
38-
BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueType
38+
BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed
39+
BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount
40+
BDN1503 | Usage | Error | BDN1503_Attributes_ArgumentsAttribute_MustHaveMatchingValueType

src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer
1919
DiagnosticSeverity.Error,
2020
isEnabledByDefault: true);
2121

22+
internal static readonly DiagnosticDescriptor SingleNullArgumentNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed,
23+
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title)),
24+
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat)),
25+
"Usage",
26+
DiagnosticSeverity.Error,
27+
isEnabledByDefault: true);
28+
2229
internal static readonly DiagnosticDescriptor MustHaveMatchingValueCountRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount,
2330
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Title)),
2431
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_MessageFormat)),
@@ -38,6 +45,7 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer
3845
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
3946
[
4047
RequiresBenchmarkAttributeRule,
48+
SingleNullArgumentNotAllowedRule,
4149
MustHaveMatchingValueCountRule,
4250
MustHaveMatchingValueTypeRule
4351
];
@@ -57,6 +65,7 @@ public override void Initialize(AnalysisContext analysisContext)
5765
}
5866

5967
ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
68+
ctx.RegisterSyntaxNodeAction(AnalyzeAttributeSyntax, SyntaxKind.Attribute);
6069
});
6170
}
6271

@@ -67,7 +76,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
6776
return;
6877
}
6978

70-
var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute");
79+
var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation);
7180
var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute");
7281

7382
if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null)
@@ -313,6 +322,31 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc
313322
}
314323
}
315324

325+
private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context)
326+
{
327+
if (context.Node is not AttributeSyntax attributeSyntax)
328+
{
329+
return;
330+
}
331+
332+
var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation);
333+
334+
var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type;
335+
if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(argumentsAttributeTypeSymbol, SymbolEqualityComparer.Default))
336+
{
337+
if (attributeSyntax.ArgumentList is { Arguments.Count: 1 })
338+
{
339+
var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0];
340+
341+
var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression);
342+
if (constantValue is { HasValue: true, Value: null })
343+
{
344+
context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation()));
345+
}
346+
}
347+
}
348+
}
349+
316350
private static int? IndexOfNamedArgument(SeparatedSyntaxList<AttributeArgumentSyntax> attributeArguments)
317351
{
318352
var i = 0;
@@ -329,5 +363,7 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc
329363

330364
return null;
331365
}
366+
367+
private static INamedTypeSymbol? GetArgumentsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute");
332368
}
333369
}

src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer
1818
DiagnosticSeverity.Error,
1919
isEnabledByDefault: true);
2020

21+
internal static readonly DiagnosticDescriptor SingleNullArgumentNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed,
22+
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_Title)),
23+
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat)),
24+
"Usage",
25+
DiagnosticSeverity.Error,
26+
isEnabledByDefault: true);
27+
2128
internal static readonly DiagnosticDescriptor MustHaveMatchingValueTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_MustHaveMatchingValueType,
2229
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveMatchingValueType_Title)),
2330
AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveMatchingValueType_MessageFormat)),
@@ -36,6 +43,7 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer
3643
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
3744
[
3845
MustHaveValuesRule,
46+
SingleNullArgumentNotAllowedRule,
3947
MustHaveMatchingValueTypeRule,
4048
UnnecessarySingleValuePassedToAttributeRule
4149
];
@@ -97,12 +105,14 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
97105

98106
AnalyzeFieldOrPropertyTypeSyntax(context,
99107
fieldOrPropertyTypeSyntax,
100-
attributeSyntax);
108+
attributeSyntax,
109+
paramsAttributeTypeSymbol);
101110
}
102111

103112
private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context,
104113
TypeSyntax fieldOrPropertyTypeSyntax,
105-
AttributeSyntax attributeSyntax)
114+
AttributeSyntax attributeSyntax,
115+
INamedTypeSymbol paramsAttributeTypeSymbol)
106116
{
107117
if (attributeSyntax.ArgumentList == null)
108118
{
@@ -128,6 +138,23 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c
128138
return;
129139
}
130140

141+
var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type;
142+
if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default))
143+
{
144+
if (attributeSyntax.ArgumentList is { Arguments.Count: 1 })
145+
{
146+
var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0];
147+
148+
var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression);
149+
if (constantValue is { HasValue: true, Value: null })
150+
{
151+
context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation()));
152+
153+
return;
154+
}
155+
}
156+
}
157+
131158
var expectedValueTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type;
132159
if (expectedValueTypeSymbol == null || expectedValueTypeSymbol.TypeKind == TypeKind.Error)
133160
{
@@ -287,6 +314,35 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc
287314
}
288315
}
289316

317+
private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context)
318+
{
319+
if (context.Node is not AttributeSyntax attributeSyntax)
320+
{
321+
return;
322+
}
323+
324+
var paramsAttributeTypeSymbol = GetParamsAttributeTypeSymbol(context.Compilation);
325+
if (paramsAttributeTypeSymbol == null)
326+
{
327+
return;
328+
}
329+
330+
var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type;
331+
if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default))
332+
{
333+
if (attributeSyntax.ArgumentList is { Arguments.Count: 1 })
334+
{
335+
var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0];
336+
337+
var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression);
338+
if (constantValue is { HasValue: true, Value: null })
339+
{
340+
context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation()));
341+
}
342+
}
343+
}
344+
}
345+
290346
private static INamedTypeSymbol? GetParamsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute");
291347
}
292348
}

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs

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

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@
201201
<data name="General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_MessageFormat" xml:space="preserve">
202202
<value>Only one benchmark method can be marked as baseline per class and category</value>
203203
</data>
204-
<data name="General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_MessageFormat" xml:space="preserve">
205-
<value>Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "SomeCategory", ...) or a non-null value instead.</value>
204+
<data name="Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat" xml:space="preserve">
205+
<value>Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead.</value>
206206
</data>
207207
<data name="General_BenchmarkClass_MethodMustBePublic_Title" xml:space="preserve">
208208
<value>Benchmark methods must be public</value>
@@ -225,6 +225,9 @@
225225
<data name="General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_Title" xml:space="preserve">
226226
<value>Single null argument to the [BenchmarkCategory] attribute results in unintended null array</value>
227227
</data>
228+
<data name="Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title" xml:space="preserve">
229+
<value>Single null argument to the [Arguments] attribute results in unintended null array</value>
230+
</data>
228231
<data name="Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Description" xml:space="preserve">
229232
<value>Parameter attributes are mutually exclusive; only one of the attributes [Params], [ParamsSource] or [ParamsAllValues] can be applied to a field at any one time</value>
230233
</data>
@@ -370,4 +373,13 @@ Either add the [ArgumentsSource] or [Arguments] attribute(s) or remove the param
370373
<data name="Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Title" xml:space="preserve">
371374
<value>Benchmark methods without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters</value>
372375
</data>
376+
<data name="General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_MessageFormat" xml:space="preserve">
377+
<value>Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "SomeCategory", ...) or a non-null value instead.</value>
378+
</data>
379+
<data name="Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat" xml:space="preserve">
380+
<value>Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead.</value>
381+
</data>
382+
<data name="Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_Title" xml:space="preserve">
383+
<value>Single null argument to the [Params] attribute results in unintended null array</value>
384+
</data>
373385
</root>

0 commit comments

Comments
 (0)