Skip to content
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
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

View workflow job for this annotation

GitHub Actions / all

Use 'SymbolEqualityComparer' when comparing symbols

Check warning on line 123 in src/D2L.CodeStyle.Analyzers/ApiUsage/Serialization/SerializationFrameworkAnalyzer.cs

View workflow job for this annotation

GitHub Actions / all

Use 'SymbolEqualityComparer' when comparing symbols
.Select( compilation.GetTypeByMetadataName )
.Where( t => !t.IsNullOrErrorType() )
.OfType<ITypeSymbol>()
.ToImmutableHashSet();

return bannedTypes;
}

}
}
1 change: 1 addition & 0 deletions src/D2L.CodeStyle.Analyzers/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal static class Attributes {
internal static readonly RoslynAttribute Singleton = new RoslynAttribute( "D2L.LP.Extensibility.Activation.Domain.SingletonAttribute" );
internal static readonly RoslynAttribute DIFramework = new RoslynAttribute( "D2L.LP.Extensibility.Activation.Domain.DIFrameworkAttribute" );
internal static readonly RoslynAttribute Dependency = new RoslynAttribute( "D2L.LP.Extensibility.Activation.Domain.DependencyAttribute" );
internal static readonly RoslynAttribute SerializationFramework = new RoslynAttribute( "D2L.LP.Serialization.SerializationFrameworkAttribute" );

internal sealed class RoslynAttribute {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Title>D2L.CodeStyle.Analyzers</Title>
<Product>D2L.CodeStyle</Product>
<Description>D2L.CodeStyle analyzers</Description>
<Version>0.203.0</Version>
<Version>0.204.0</Version>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/Brightspace/D2L.CodeStyle</PackageProjectUrl>
<Authors>D2L</Authors>
Expand Down
9 changes: 9 additions & 0 deletions src/D2L.CodeStyle.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,5 +757,14 @@ public static class Diagnostics {
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true
);

public static readonly DiagnosticDescriptor DangerousSerializationTypeReference = new DiagnosticDescriptor(
id: "D2L0102",
title: "Internal serialization type references are restricted to serialization framework internals",
messageFormat: "Internal serialization types shouldn't be used outside of the serialization framework. Use 'ISerialization' or 'IDeserialization' implementations instead.",
category: "Correctness",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true
);
}
}
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 {
Copy link
Contributor

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.

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();
}
}
}


}
Loading