Skip to content

Commit 346bbab

Browse files
Fixed: Add validation warning for sealed classes containing benchmarks (dotnet#2660)
* Add validation for sealed classes containing benchmarks * Moved public class validation to CompilationValidator Co-authored-by: Tim Cassell <[email protected]>
1 parent af8bde4 commit 346bbab

File tree

4 files changed

+94
-16
lines changed

4 files changed

+94
-16
lines changed

src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,7 @@ internal static bool ContainsRunnableBenchmarks(this Type type)
155155
{
156156
var typeInfo = type.GetTypeInfo();
157157

158-
if (typeInfo.IsAbstract
159-
|| typeInfo.IsSealed
160-
|| typeInfo.IsNotPublic
161-
|| typeInfo.IsGenericType && !IsRunnableGenericType(typeInfo))
158+
if (typeInfo.IsAbstract || typeInfo.IsGenericType && !IsRunnableGenericType(typeInfo))
162159
return false;
163160

164161
return typeInfo.GetBenchmarks().Any();

src/BenchmarkDotNet/Running/BenchmarkConverter.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,7 @@ private static void AssertMethodIsAccessible(string methodType, MethodInfo metho
263263
{
264264
if (!methodInfo.IsPublic)
265265
throw new InvalidBenchmarkDeclarationException($"{methodType} method {methodInfo.Name} has incorrect access modifiers.\nMethod must be public.");
266-
267-
var declaringType = methodInfo.DeclaringType;
268-
269-
while (declaringType != null)
270-
{
271-
if (!declaringType.GetTypeInfo().IsPublic && !declaringType.GetTypeInfo().IsNestedPublic)
272-
throw new InvalidBenchmarkDeclarationException($"{declaringType.FullName} containing {methodType} method {methodInfo.Name} has incorrect access modifiers.\nDeclaring type must be public.");
273-
274-
declaringType = declaringType.DeclaringType;
275-
}
266+
/* Moved the code that verifies if DeclaringType of a given MethodInfo (a method) is publicly accessible to CompilationValidator */
276267
}
277268

278269
private static void AssertMethodIsNotGeneric(string methodType, MethodInfo methodInfo)

src/BenchmarkDotNet/Validators/CompilationValidator.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using BenchmarkDotNet.Running;
88
using BenchmarkDotNet.Toolchains;
99
using Microsoft.CodeAnalysis.CSharp;
10+
using BenchmarkDotNet.Attributes;
1011

1112
namespace BenchmarkDotNet.Validators
1213
{
@@ -25,8 +26,38 @@ private CompilationValidator() { }
2526
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
2627
=> ValidateCSharpNaming(validationParameters.Benchmarks)
2728
.Union(ValidateNamingConflicts(validationParameters.Benchmarks))
29+
.Union(ValidateClassModifiers((validationParameters.Benchmarks))
2830
.Union(ValidateAccessModifiers(validationParameters.Benchmarks))
29-
.Union(ValidateBindingModifiers(validationParameters.Benchmarks));
31+
.Union(ValidateBindingModifiers(validationParameters.Benchmarks))
32+
);
33+
34+
private static IEnumerable<ValidationError> ValidateClassModifiers(IEnumerable<BenchmarkCase> benchmarks)
35+
{
36+
return benchmarks
37+
.Distinct(BenchmarkMethodEqualityComparer.Instance)
38+
.SelectMany(benchmark =>
39+
{
40+
var type = benchmark.Descriptor.Type;
41+
var errors = new List<ValidationError>();
42+
43+
if (type.IsSealed)
44+
{
45+
errors.Add(new ValidationError(
46+
true,
47+
$"Benchmarked method `{benchmark.Descriptor.WorkloadMethod.Name}` is within a sealed class, Declaring type must be unsealed.",
48+
benchmark));
49+
}
50+
if (!type.IsVisible)
51+
{
52+
errors.Add(new ValidationError(
53+
true,
54+
$"Benchmarked method `{benchmark.Descriptor.WorkloadMethod.Name}` is within a non-visible class, all declaring types must be public.",
55+
benchmark));
56+
}
57+
// TODO: Generics validation
58+
return errors;
59+
});
60+
}
3061

3162
private static IEnumerable<ValidationError> ValidateCSharpNaming(IEnumerable<BenchmarkCase> benchmarks)
3263
=> benchmarks

tests/BenchmarkDotNet.Tests/Validators/CompilationValidatorTests.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using BenchmarkDotNet.Attributes;
88
using BenchmarkDotNet.Validators;
99
using Xunit;
10+
using System.Reflection;
1011

1112
namespace BenchmarkDotNet.Tests.Validators
1213
{
@@ -59,6 +60,29 @@ public void BenchmarkedMethodNameMustNotUseCsharpKeywords()
5960
"Benchmarked method `typeof` contains illegal character(s) or uses C# keyword. Please use `[<Benchmark(Description = \"Custom name\")>]` to set custom display name."));
6061
}
6162

63+
[Theory]
64+
/* BenchmarkDotNet can only benchmark public unsealed classes*/
65+
[InlineData(typeof(BenchMarkPublicClass), false)]
66+
[InlineData(typeof(BenchMarkPublicClass.PublicNestedClass), false)]
67+
[InlineData(typeof(SealedClass.PublicNestedClass), false)]
68+
[InlineData(typeof(OuterClass.PublicNestedClass), true)]
69+
[InlineData(typeof(SealedClass), true)]
70+
[InlineData(typeof(MyPrivateClass), true)]
71+
[InlineData(typeof(MyPublicProtectedClass), true)]
72+
[InlineData(typeof(MyPrivateProtectedClass), true)]
73+
[InlineData(typeof(MyProtectedInternalClass), true)]
74+
[InlineData(typeof(MyInternalClass), true)]
75+
[InlineData(typeof(OuterClass), true)]
76+
[InlineData(typeof(OuterClass.InternalNestedClass), true)]
77+
[InlineData(typeof(BenchMarkPublicClass.InternalNestedClass), true)]
78+
/* Generics Remaining */
79+
public void Benchmark_Class_Modifers_Must_Be_Public(Type type, bool hasErrors)
80+
{
81+
var validationErrors = CompilationValidator.FailOnError.Validate(BenchmarkConverter.TypeToBenchmarks(type));
82+
83+
Assert.Equal(hasErrors, validationErrors.Any());
84+
}
85+
6286
[Theory]
6387
[InlineData(typeof(BenchmarkClassWithStaticMethod), true)]
6488
[InlineData(typeof(BenchmarkClass<PublicClass>), false)]
@@ -115,7 +139,17 @@ protected internal class ProtectedInternalClass
115139
{
116140
protected internal class ProtectedInternalNestedClass { }
117141
}
118-
}
142+
143+
private class MyPrivateClass{ [Benchmark] public void PublicMethod(){} }
144+
145+
protected class MyPublicProtectedClass{ [Benchmark] public void PublicMethod(){} }
146+
147+
private protected class MyPrivateProtectedClass{ [Benchmark] public void PublicMethod(){} }
148+
149+
internal class MyInternalClass{ [Benchmark] public void PublicMethod(){} }
150+
151+
protected internal class MyProtectedInternalClass{ [Benchmark] public void PublicMethod() { } }
152+
}
119153

120154
public class BenchmarkClassWithStaticMethod
121155
{
@@ -138,4 +172,29 @@ internal class InternalClass
138172
{
139173
internal class InternalNestedClass { }
140174
}
175+
176+
public sealed class SealedClass
177+
{
178+
[Benchmark] public void PublicMethod() { }
179+
180+
public class PublicNestedClass { [Benchmark] public void PublicMethod() { } }
181+
}
182+
183+
internal class OuterClass
184+
{
185+
[Benchmark] public void PublicMethod(){}
186+
187+
internal class InternalNestedClass { [Benchmark] public void PublicMethod() { } }
188+
189+
public class PublicNestedClass { [Benchmark] public void PublicMethod() { } }
190+
}
191+
192+
public class BenchMarkPublicClass
193+
{
194+
[Benchmark] public void PublicMethod(){}
195+
196+
public class PublicNestedClass { [Benchmark] public void PublicMethod() { } }
197+
198+
internal class InternalNestedClass { [Benchmark] public void PublicMethod() { } }
199+
}
141200
}

0 commit comments

Comments
 (0)