-
Notifications
You must be signed in to change notification settings - Fork 22
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
Add SerializationFrameworkAnalyzer #922
Closed
Closed
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
src/D2L.CodeStyle.Analyzers/ApiUsage/Serialization/SerializationFrameworkAnalyzer.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,133 @@ | ||
using System.Collections.Immutable; | ||
using D2L.CodeStyle.Analyzers.Extensions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace D2L.CodeStyle.Analyzers.ApiUsage.Serialization { | ||
|
||
[DiagnosticAnalyzer( LanguageNames.CSharp )] | ||
public sealed class SerializationFrameworkAnalyzer : DiagnosticAnalyzer { | ||
|
||
private static readonly ImmutableArray<string> DisallowedTypeMetadataNames = ImmutableArray.Create( | ||
"D2L.LP.Serialization.ISerializationWriter", | ||
"D2L.LP.Serialization.ISerializer", | ||
"D2L.LP.Serialization.ISerializationWriterExtensions", | ||
"D2L.LP.Serialization.ITrySerializer" | ||
); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create( | ||
Diagnostics.DangerousSerializationTypeReference | ||
); | ||
|
||
public override void Initialize( AnalysisContext context ) { | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis( GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics ); | ||
context.RegisterCompilationStartAction( RegisterAnalyzer ); | ||
} | ||
|
||
public static void RegisterAnalyzer( CompilationStartAnalysisContext context ) { | ||
|
||
ImmutableHashSet<ITypeSymbol> dangerousInterfaces = GetDangerousInterfaces( context.Compilation ); | ||
|
||
if( dangerousInterfaces.IsEmpty ) { | ||
return; | ||
} | ||
|
||
context.RegisterOperationAction( | ||
context => { | ||
IInvocationOperation invocation = (IInvocationOperation)context.Operation; | ||
AnalyzeMemberUsage( context, invocation.TargetMethod, dangerousInterfaces ); | ||
}, | ||
OperationKind.Invocation | ||
); | ||
|
||
context.RegisterOperationAction( | ||
context => { | ||
IMethodReferenceOperation reference = (IMethodReferenceOperation)context.Operation; | ||
AnalyzeMemberUsage( context, reference.Method, dangerousInterfaces ); | ||
}, | ||
OperationKind.MethodReference | ||
); | ||
|
||
context.RegisterOperationAction( | ||
context => { | ||
IPropertyReferenceOperation reference = (IPropertyReferenceOperation)context.Operation; | ||
AnalyzeMemberUsage( context, reference.Property, dangerousInterfaces ); | ||
}, | ||
OperationKind.PropertyReference | ||
); | ||
|
||
context.RegisterSymbolAction( | ||
context => { | ||
IFieldSymbol field = (IFieldSymbol)context.Symbol; | ||
AnalyzeTypeUsage( context, field.Type, dangerousInterfaces ); | ||
}, | ||
SymbolKind.Field | ||
); | ||
|
||
context.RegisterSymbolAction( | ||
context => { | ||
IPropertySymbol property = (IPropertySymbol)context.Symbol; | ||
AnalyzeTypeUsage( context, property.Type, dangerousInterfaces ); | ||
}, | ||
SymbolKind.Property | ||
); | ||
} | ||
|
||
private static void AnalyzeMemberUsage( | ||
OperationAnalysisContext context, | ||
ISymbol member, | ||
ImmutableHashSet<ITypeSymbol> bannedTypes | ||
) { | ||
if( !ImplementsDangerousInterface( bannedTypes, member.ContainingType ) ) { | ||
return; | ||
} | ||
|
||
if( IsSerializationFrameworkInternal( context.ContainingSymbol ) ) { | ||
return; | ||
} | ||
|
||
context.ReportDiagnostic( | ||
Diagnostics.DangerousSerializationTypeReference, | ||
context.Operation.Syntax.GetLocation() | ||
); | ||
} | ||
|
||
private static void AnalyzeTypeUsage( | ||
SymbolAnalysisContext context, | ||
ITypeSymbol type, | ||
ImmutableHashSet<ITypeSymbol> bannedTypes | ||
) { | ||
if( !ImplementsDangerousInterface( bannedTypes, type ) ) { | ||
return; | ||
} | ||
|
||
if( IsSerializationFrameworkInternal( context.Symbol ) ) { | ||
return; | ||
} | ||
|
||
context.ReportDiagnostic( | ||
Diagnostics.DangerousSerializationTypeReference, | ||
context.Symbol.Locations[0] | ||
); | ||
} | ||
|
||
private static bool ImplementsDangerousInterface( ImmutableHashSet<ITypeSymbol> dangerousInterface, ITypeSymbol type ) => | ||
dangerousInterface.Contains( type ) || type.AllInterfaces.Any( dangerousInterface.Contains ); | ||
|
||
private static bool IsSerializationFrameworkInternal( ISymbol symbol ) => | ||
symbol.GetAllContainingTypes().Any( Attributes.SerializationFramework.IsDefined ); | ||
|
||
private static ImmutableHashSet<ITypeSymbol> GetDangerousInterfaces( Compilation compilation ) { | ||
ImmutableHashSet<ITypeSymbol> bannedTypes = DisallowedTypeMetadataNames | ||
Check warning on line 123 in src/D2L.CodeStyle.Analyzers/ApiUsage/Serialization/SerializationFrameworkAnalyzer.cs GitHub Actions / all
|
||
.Select( compilation.GetTypeByMetadataName ) | ||
.Where( t => !t.IsNullOrErrorType() ) | ||
.OfType<ITypeSymbol>() | ||
.ToImmutableHashSet(); | ||
|
||
return bannedTypes; | ||
} | ||
|
||
} | ||
} |
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
157 changes: 157 additions & 0 deletions
157
tests/D2L.CodeStyle.Analyzers.Test/Specs/SerializationFrameworkAnalyzer.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,157 @@ | ||
// analyzer: D2L.CodeStyle.Analyzers.ApiUsage.Serialization.SerializationFrameworkAnalyzer | ||
|
||
using System; | ||
using D2L.LP.Serialization | ||
|
||
namespace D2L.LP.Serialization { | ||
|
||
public sealed class SerializationFrameworkAttribute : Attribute { } | ||
|
||
[SerializationFramework] | ||
public class DangerousExplicitClass : ISerializer, ITrySerializer, ISerializationWriter { | ||
public void Foo() { } | ||
void ISerializer.Serialize() { } | ||
bool ITrySerializer.TrySerialize() { return true; } | ||
void ISerializationWriter.Write() { } | ||
} | ||
|
||
[SerializationFramework] | ||
public class DangerousImplicitClass : ISerializer, ITrySerializer, ISerializationWriter { | ||
public void Serialize() { } | ||
public bool TrySerialize() { return true; } | ||
public void Write() { } | ||
} | ||
|
||
public interface ISerializer { | ||
public void Serialize(); | ||
} | ||
|
||
public interface ITrySerializer { | ||
public bool TrySerialize(); | ||
} | ||
|
||
public interface ISerializationWriter { | ||
public void Write(); | ||
} | ||
|
||
[SerializationFramework] | ||
public static class ISerializationWriterExtensions { | ||
public static void Write( this ISerializationWriter writer, string _ ) { | ||
writer.Write(); | ||
} | ||
} | ||
|
||
[SerializationFramework] | ||
public static class SerializationFactory { | ||
public static ISerializer Serializer { | ||
get { return new DangerousExplicitClass(); } | ||
} | ||
public static ITrySerializer TrySerializer { | ||
get { return new DangerousExplicitClass(); } | ||
} | ||
public static ISerializer SerializationWriter { | ||
get { return new DangerousExplicitClass(); } | ||
} | ||
} | ||
|
||
} | ||
|
||
namespace D2L.CodeStyle.Analyzers.SerializationFrameworkAnalyzer.Examples { | ||
using D2L.LP.Serialization; | ||
|
||
public sealed class BadClass { | ||
|
||
private DangerousExplicitClass /* DangerousSerializationTypeReference */ m_dangerousExplicitClass /**/; | ||
private DangerousImplicitClass /* DangerousSerializationTypeReference */ m_dangerousImplicitClass /**/; | ||
|
||
public void Uses_ISerializer() { | ||
ISerializer explicitSerializer = SerializationFactory.Serializer; | ||
/* DangerousSerializationTypeReference */ | ||
explicitSerializer.Serialize() /**/ ; | ||
} | ||
|
||
public void Uses_ITrySerializer() { | ||
ITrySerializer explicitTrySerializer = SerializationFactory.TrySerializer; | ||
/* DangerousSerializationTypeReference */ | ||
explicitTrySerializer.TrySerialize() /**/ ; | ||
} | ||
|
||
public void Uses_ISerializationWriter() { | ||
ISerializationWriter explicitWriter = SerializationFactory.SerializationWriter; | ||
/* DangerousSerializationTypeReference */ | ||
explicitWriter.Write() /**/ ; | ||
} | ||
|
||
public void Uses_ISerializationWriterExtensions() { | ||
ISerializationWriter explicitWriter = SerializationFactory.SerializationWriter; | ||
/* DangerousSerializationTypeReference */ | ||
explicitWriter.Write( "foo" ) /**/ ; | ||
} | ||
|
||
public void Uses_DangerousImplicitReferences() { | ||
DangerousImplicitClass dangerousClass = new DangerousImplicitClass(); | ||
/* DangerousSerializationTypeReference */ | ||
dangerousClass.Serialize() /**/ ; | ||
/* DangerousSerializationTypeReference */ | ||
dangerousClass.TrySerialize() /**/ ; | ||
} | ||
|
||
public void Uses_DangerousImplementingType() { | ||
DangerousExplicitClass dangerousClass = new DangerousExplicitClass(); | ||
/* DangerousSerializationTypeReference */ | ||
dangerousClass.Foo() /**/ ; | ||
} | ||
|
||
} | ||
|
||
[SerializationFramework] | ||
public sealed class SerializationFrameworkClass { | ||
|
||
private readonly ISerializer m_serializer; | ||
private readonly ITrySerializer m_trySerializer; | ||
private readonly ISerializationWriter m_serializationWriter; | ||
|
||
public SerializationFrameworkClass( | ||
ISerializer serializer, | ||
ITrySerializer trySerializer, | ||
ISerializationWriter serializationWriter | ||
) { | ||
m_serializer = serializer; | ||
m_trySerializer = trySerializer; | ||
m_serializationWriter = serializationWriter; | ||
} | ||
|
||
public void InSerializationClass() { | ||
ISerializer ok = SerializationFactory.Serializer; | ||
ok.Serialize(); | ||
|
||
ITrySerializer alsoOk = SerializationFactory.TrySerializer; | ||
alsoOk.TrySerialize(); | ||
|
||
ISerializationWriter okayToo = SerializationFactory.SerializationWriter; | ||
okayToo.Write(); | ||
} | ||
|
||
public ISerializer Serializer { | ||
get { return m_serializer; } | ||
} | ||
} | ||
|
||
[SerializationFramework] | ||
public sealed class SerializationFrameworkUsageInNestedClass { | ||
private static class Nested { | ||
public static void Usage() { | ||
ISerializer ok = SerializationFactory.Serializer; | ||
ok.Serialize(); | ||
|
||
ITrySerializer alsoOk = SerializationFactory.TrySerializer; | ||
alsoOk.TrySerialize(); | ||
|
||
ISerializationWriter okayToo = SerializationFactory.SerializationWriter; | ||
okayToo.Write(); | ||
} | ||
} | ||
} | ||
|
||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I expect the analyzer to catch this, but you may want a class that inherits
DangerousImplicitClass
and checking that the analyzer catches that.