-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #227 from SteveDunn/prohibit-reflection
Prohibit reflection with Activator.CreateInstance
- Loading branch information
Showing
13 changed files
with
205 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Analyzer.Utilities.Extensions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Vogen.Diagnostics; | ||
|
||
namespace Vogen.Analyzers; | ||
|
||
/// <summary> | ||
/// An analyzer that stops `CustomerId = default;`. | ||
/// </summary> | ||
[Generator] | ||
public class CreationUsingReflectionAnalyzer : IIncrementalGenerator | ||
{ | ||
public record struct FoundItem(Location Location, INamedTypeSymbol VoClass); | ||
|
||
public void Initialize(IncrementalGeneratorInitializationContext context) | ||
{ | ||
IncrementalValuesProvider<FoundItem?> targets = GetTargets(context); | ||
|
||
IncrementalValueProvider<(Compilation, ImmutableArray<FoundItem?>)> compilationAndTypes | ||
= context.CompilationProvider.Combine(targets.Collect()); | ||
|
||
context.RegisterSourceOutput(compilationAndTypes, | ||
static (spc, source) => Execute(source.Item2, spc)); | ||
} | ||
|
||
private static IncrementalValuesProvider<FoundItem?> GetTargets(IncrementalGeneratorInitializationContext context) | ||
{ | ||
return context.SyntaxProvider.CreateSyntaxProvider( | ||
predicate: static (s, _) => s is InvocationExpressionSyntax, | ||
transform: static (ctx, _) => TryGetTarget(ctx)) | ||
.Where(static m => m is not null); | ||
} | ||
|
||
private static FoundItem? TryGetTarget(GeneratorSyntaxContext ctx) | ||
{ | ||
var syntax = (InvocationExpressionSyntax) ctx.Node; | ||
var methodSymbol = (ctx.SemanticModel.GetSymbolInfo(syntax).Symbol as IMethodSymbol); | ||
if (methodSymbol == null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (methodSymbol.ReceiverType?.FullNamespace() != "System") return null; | ||
if (methodSymbol.ReceiverType.Name != "Activator") return null; | ||
|
||
if (methodSymbol.Name != "CreateInstance") | ||
{ | ||
return null; | ||
} | ||
|
||
if (methodSymbol.Parameters.Length == 0) | ||
{ | ||
if (!methodSymbol.IsGenericMethod) return null; | ||
|
||
var returnType = methodSymbol.ReturnType as INamedTypeSymbol; | ||
|
||
if (returnType == null) return null; | ||
|
||
if (!VoFilter.IsTarget(returnType)) return null; | ||
|
||
return new FoundItem(syntax.GetLocation(), returnType); | ||
} | ||
|
||
if (methodSymbol.Parameters.Length == 1) | ||
{ | ||
var childNodes = syntax.DescendantNodes().OfType<TypeOfExpressionSyntax>().ToList(); | ||
|
||
if (childNodes.Count != 1) return null; | ||
|
||
TypeInfo xxy = ctx.SemanticModel.GetTypeInfo(childNodes[0].Type); | ||
|
||
var syntaxNode = xxy.Type as INamedTypeSymbol; | ||
|
||
if (!VoFilter.IsTarget(syntaxNode)) return null; | ||
|
||
return new FoundItem(syntax.GetLocation(), syntaxNode!); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
static void Execute(ImmutableArray<FoundItem?> typeDeclarations, SourceProductionContext context) | ||
{ | ||
foreach (FoundItem? eachFoundItem in typeDeclarations) | ||
{ | ||
if (eachFoundItem is not null) | ||
{ | ||
context.ReportDiagnostic(DiagnosticItems.UsingActivatorProhibited(eachFoundItem.Value.Location, eachFoundItem.Value.VoClass.Name)); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
tests/SmallTests/DiagnosticsTests/DisallowCreationWithReflectionTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System.Linq; | ||
using FluentAssertions; | ||
using FluentAssertions.Execution; | ||
using Microsoft.CodeAnalysis; | ||
using Vogen.Analyzers; | ||
using Xunit; | ||
|
||
namespace NotSystem | ||
{ | ||
public static class Activator | ||
{ | ||
public static T? CreateInstance<T>() => default(T); | ||
} | ||
} | ||
|
||
namespace SmallTests.DiagnosticsTests | ||
{ | ||
public class DisallowCreationWithReflectionTests | ||
{ | ||
[Fact] | ||
public void Allows_using_Activate_CreateInstance_from_another_namespace() | ||
{ | ||
var x = NotSystem.Activator.CreateInstance<string>(); | ||
} | ||
|
||
|
||
[Theory] | ||
[InlineData("partial class")] | ||
[InlineData("partial struct")] | ||
[InlineData("readonly partial struct")] | ||
[InlineData("partial record class")] | ||
[InlineData("partial record struct")] | ||
[InlineData("readonly partial record struct")] | ||
public void Disallows_generic_method(string type) | ||
{ | ||
var source = $@"using Vogen; | ||
using System; | ||
namespace Whatever; | ||
[ValueObject(typeof(int))] | ||
public {type} CustomerId {{ }} | ||
var c = Activator.CreateInstance<CustomerId>(); | ||
"; | ||
|
||
var (diagnostics, _) = TestHelper.GetGeneratedOutput<CreationUsingReflectionAnalyzer>(source); | ||
|
||
using var _ = new AssertionScope(); | ||
|
||
diagnostics.Should().HaveCount(1); | ||
Diagnostic diagnostic = diagnostics.Single(); | ||
|
||
diagnostic.Id.Should().Be("VOG025"); | ||
diagnostic.ToString().Should().Match("*error VOG025: Type 'CustomerId' cannot be constructed via Reflection as it is prohibited."); | ||
} | ||
|
||
[Theory] | ||
[InlineData("partial class")] | ||
[InlineData("partial struct")] | ||
[InlineData("readonly partial struct")] | ||
[InlineData("partial record class")] | ||
[InlineData("partial record struct")] | ||
[InlineData("readonly partial record struct")] | ||
public void Disallows_non_generic_method(string type) | ||
{ | ||
var source = $@"using Vogen; | ||
using System; | ||
namespace Whatever; | ||
[ValueObject(typeof(int))] | ||
public {type} CustomerId {{ }} | ||
var c = Activator.CreateInstance(typeof(CustomerId)); | ||
"; | ||
|
||
var (diagnostics, _) = TestHelper.GetGeneratedOutput<CreationUsingReflectionAnalyzer>(source); | ||
|
||
using var _ = new AssertionScope(); | ||
|
||
diagnostics.Should().HaveCount(1); | ||
Diagnostic diagnostic = diagnostics.Single(); | ||
|
||
diagnostic.Id.Should().Be("VOG025"); | ||
diagnostic.ToString().Should().Match("*error VOG025: Type 'CustomerId' cannot be constructed via Reflection as it is prohibited."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters