Skip to content
Draft
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
36 changes: 31 additions & 5 deletions src/ZeroSerializerGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ public sealed class ZeroSerializerGenerator : ISourceGenerator
DiagnosticSeverity.Error,
isEnabledByDefault: true);

private static readonly DiagnosticDescriptor InvalidClassStructLayoutAttribute = new(
"ZEROS009",
"StructLayout attribute on class",
"StructLayout attribute on class '{0}' has no effect",
SerializerName,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public void Initialize(GeneratorInitializationContext initializationContext)
{
// Unity is pinned to Roslyn 3.8, so discovery must stay on the classic syntax-receiver API.
Expand Down Expand Up @@ -316,6 +324,20 @@ private static TypeGenerationModel CreateGenerationModel(
return generationModel;
}

if (serializableType.TypeKind != TypeKind.Struct)
{
var structLayoutAttr = GetStructLayoutAttribute(serializableType);
if (structLayoutAttr is not null)
{
Location? location = structLayoutAttr.ApplicationSyntaxReference?.GetSyntax().GetLocation()
?? GetTypeIdentifierLocation(serializableType);
executionContext.ReportDiagnostic(Diagnostic.Create(
InvalidClassStructLayoutAttribute,
location,
serializableType.Name));
}
}

if (serializableType.TypeKind == TypeKind.Struct && !isBlittableStruct)
{
var packOneAttr = GetSequentialPackOneAttribute(serializableType);
Expand Down Expand Up @@ -631,10 +653,9 @@ private static bool TryGetBlittableStructByteCount(INamedTypeSymbol candidateTyp
out byteCount);
}

private static AttributeData? GetSequentialPackOneAttribute(INamedTypeSymbol structType)
private static AttributeData? GetStructLayoutAttribute(INamedTypeSymbol typeSymbol)
{
AttributeData? structLayoutAttribute = null;
foreach (AttributeData candidateAttribute in structType.GetAttributes())
foreach (AttributeData candidateAttribute in typeSymbol.GetAttributes())
{
if (candidateAttribute.AttributeClass is INamedTypeSymbol
{
Expand All @@ -653,10 +674,15 @@ private static bool TryGetBlittableStructByteCount(INamedTypeSymbol candidateTyp
}
})
{
structLayoutAttribute = candidateAttribute;
break;
return candidateAttribute;
}
}
return null;
}

private static AttributeData? GetSequentialPackOneAttribute(INamedTypeSymbol structType)
{
AttributeData? structLayoutAttribute = GetStructLayoutAttribute(structType);
if (structLayoutAttribute is null
|| structLayoutAttribute.ConstructorArguments.Length != 1
|| structLayoutAttribute.ConstructorArguments[0].Value is not int layoutKind
Expand Down
61 changes: 61 additions & 0 deletions tests/Diagnostics/DiagnosticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,67 @@ await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGenerat
);
}

[Fact]
public async Task ZEROS009_Violation_ClassWithStructLayout()
{
string source = @"
using System.Runtime.InteropServices;
using ZeroSerializer;

[{|#0:StructLayout(LayoutKind.Sequential, Pack = 1)|}]
[ZeroSerializer]
public class MyClassWithStructLayout
{
public int Value { get; set; }
}
";

await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGeneratorAsync(
source,
new DiagnosticResult("ZEROS009", DiagnosticSeverity.Warning)
.WithLocation(0)
.WithMessage("StructLayout attribute on class 'MyClassWithStructLayout' has no effect")
);
}

[Fact]
public async Task ZEROS009_Compliant_StructWithAttributes()
{
string source = @"
using System.Runtime.InteropServices;
using ZeroSerializer;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
[ZeroSerializer]
public struct MyStructWithAttributes
{
public int Value { get; set; }
}
";

await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGeneratorAsync(
source
);
}

[Fact]
public async Task ZEROS009_Compliant_ClassWithoutZeroSerializer()
{
string source = @"
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class MyClassWithoutZeroSerializer
{
public int Value { get; set; }
}
";

await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGeneratorAsync(
source
);
}

[Fact]
public async Task ZEROS002_Violation_NonBlittableStructWithLayout()
{
Expand Down
Loading