Skip to content
Merged
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
Expand Up @@ -882,7 +882,7 @@ void ReportDiagnosticsForMismatchedMemberSignatures(DiagnosticReporter diagnosti
// First verify all usages in the managed->unmanaged shape.
IMethodSymbol toUnmanagedMethod = methods.ToUnmanaged ?? methods.ToUnmanagedWithBuffer;
unmanagedType = toUnmanagedMethod.ReturnType;
if (!unmanagedType.IsUnmanagedType && !unmanagedType.IsStrictlyBlittable())
if (!unmanagedType.IsUnmanagedType && !unmanagedType.IsStrictlyBlittableInContext(_compilation))
{
diagnosticReporter.CreateAndReportDiagnostic(UnmanagedTypeMustBeUnmanagedRule, toUnmanagedMethod.ToDisplayString());
}
Expand Down Expand Up @@ -1198,7 +1198,7 @@ void ReportDiagnosticsForMismatchedMemberSignatures(DiagnosticReporter diagnosti
{
// First verify all usages in the managed->unmanaged shape.
unmanagedType = methods.ToUnmanaged.ReturnType;
if (!unmanagedType.IsUnmanagedType && !unmanagedType.IsStrictlyBlittable())
if (!unmanagedType.IsUnmanagedType && !unmanagedType.IsStrictlyBlittableInContext(_compilation))
{
diagnosticReporter.CreateAndReportDiagnostic(UnmanagedTypeMustBeUnmanagedRule, methods.ToUnmanaged.ToDisplayString());
}
Expand All @@ -1217,7 +1217,7 @@ void ReportDiagnosticsForMismatchedMemberSignatures(DiagnosticReporter diagnosti
{
unmanagedType = fromUnmanagedMethod.Parameters[0].Type;

if (!unmanagedType.IsUnmanagedType && !unmanagedType.IsStrictlyBlittable())
if (!unmanagedType.IsUnmanagedType && !unmanagedType.IsStrictlyBlittableInContext(_compilation))
{
diagnosticReporter.CreateAndReportDiagnostic(UnmanagedTypeMustBeUnmanagedRule, fromUnmanagedMethod.ToDisplayString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public MarshallingInfo GetMarshallingInfo(ITypeSymbol type, int indirectionDepth
}
else
{
return new UnmanagedBlittableMarshallingInfo(type.IsStrictlyBlittable());
return new UnmanagedBlittableMarshallingInfo(type.IsStrictlyBlittableInContext(_compilation));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ or MarshalMode.UnmanagedToManagedRef
ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType),
HasState: false,
shape,
nativeType.IsStrictlyBlittable(),
nativeType.IsStrictlyBlittableInContext(compilation),
bufferElementType,
collectionElementTypeInfo,
collectionElementMarshallingInfo);
Expand Down Expand Up @@ -606,7 +606,7 @@ or MarshalMode.UnmanagedToManagedRef
ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType),
HasState: true,
shape,
nativeType.IsStrictlyBlittable(),
nativeType.IsStrictlyBlittableInContext(compilation),
bufferElementType,
CollectionElementType: collectionElementTypeInfo,
CollectionElementMarshallingInfo: collectionElementMarshallingInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ public static bool IsConsideredBlittable(this ITypeSymbol type)
{
unsafe
{
return IsBlittableWorker(type, ImmutableHashSet.Create<ITypeSymbol>(SymbolEqualityComparer.Default), &IsConsideredBlittableWorker);
// We can pass a null Compilation here since our blittability check does not depend on the compilation.
return IsBlittableWorker(type, ImmutableHashSet.Create<ITypeSymbol>(SymbolEqualityComparer.Default), compilation: null!, &IsConsideredBlittableWorker);
}

static bool IsConsideredBlittableWorker(ITypeSymbol t, ImmutableHashSet<ITypeSymbol> seenTypes)
static bool IsConsideredBlittableWorker(ITypeSymbol t, ImmutableHashSet<ITypeSymbol> seenTypes, Compilation compilation)
{
return t.IsUnmanagedType;
}
Expand All @@ -50,46 +51,53 @@ static bool IsConsideredBlittableWorker(ITypeSymbol t, ImmutableHashSet<ITypeSym
/// </remarks>
/// <param name="type">The type to check.</param>
/// <returns>Returns true if strictly blittable, otherwise false.</returns>
public static bool IsStrictlyBlittable(this ITypeSymbol type)
/// <param name="compilation">The compilation context of the source being compiled.</param>
public static bool IsStrictlyBlittableInContext(this ITypeSymbol type, Compilation compilation)
{
unsafe
{
return IsBlittableWorker(type, ImmutableHashSet.Create<ITypeSymbol>(SymbolEqualityComparer.Default), &IsStrictlyBlittableWorker);
return IsBlittableWorker(type, ImmutableHashSet.Create<ITypeSymbol>(SymbolEqualityComparer.Default), compilation, &IsStrictlyBlittableWorker);
}

static unsafe bool IsStrictlyBlittableWorker(ITypeSymbol t, ImmutableHashSet<ITypeSymbol> seenTypes)
static unsafe bool IsStrictlyBlittableWorker(ITypeSymbol t, ImmutableHashSet<ITypeSymbol> seenTypes, Compilation compilation)
{
if (t.SpecialType is not SpecialType.None)
{
return t.SpecialType.IsAlwaysBlittable();
}
else if (t.IsValueType)
{
// If the containing assembly for the type is backed by metadata (non-null),
// then the type is not internal and therefore coming from a reference assembly
// that we can not confirm is strictly blittable.
if (t.ContainingAssembly is not null
&& t.ContainingAssembly.GetMetadata() is not null)
// If the containing assembly for the type is not the same assembly as the assembly defining the interop stub,
// then we can't trust the type definition as it may differ at runtime from the compile-time definition.
if (t.ContainingAssembly is not ISourceAssemblySymbol sourceAssembly
|| sourceAssembly.Compilation != compilation)
{
return false;
}

return t.HasOnlyBlittableFields(seenTypes, &IsStrictlyBlittableWorker);
return t.HasOnlyBlittableFields(seenTypes, compilation, &IsStrictlyBlittableWorker);
}

return false;
}
}

private static unsafe bool IsBlittableWorker(this ITypeSymbol type, ImmutableHashSet<ITypeSymbol> seenTypes, delegate*<ITypeSymbol, ImmutableHashSet<ITypeSymbol>, bool> isBlittable)
private static unsafe bool IsBlittableWorker(this ITypeSymbol type, ImmutableHashSet<ITypeSymbol> seenTypes, Compilation compilation, delegate*<ITypeSymbol, ImmutableHashSet<ITypeSymbol>, Compilation, bool> isBlittable)
{
// Assume that type parameters that can be blittable are blittable.
// We'll re-evaluate blittability for generic fields of generic types at instantiation time.
if (type.TypeKind == TypeKind.TypeParameter && !type.IsReferenceType)
{
return true;
}
if (type.IsAutoLayout() || !isBlittable(type, seenTypes))

// Treat pointers as always blittable.
if (type.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer)
{
return true;
}

if (type.IsAutoLayout() || !isBlittable(type, seenTypes, compilation))
{
return false;
}
Expand Down Expand Up @@ -121,7 +129,7 @@ private static bool IsAutoLayout(this ITypeSymbol type)
return type.IsReferenceType;
}

private static unsafe bool HasOnlyBlittableFields(this ITypeSymbol type, ImmutableHashSet<ITypeSymbol> seenTypes, delegate*<ITypeSymbol, ImmutableHashSet<ITypeSymbol>, bool> isBlittable)
private static unsafe bool HasOnlyBlittableFields(this ITypeSymbol type, ImmutableHashSet<ITypeSymbol> seenTypes, Compilation compilation, delegate*<ITypeSymbol, ImmutableHashSet<ITypeSymbol>, Compilation, bool> isBlittable)
{
if (seenTypes.Contains(type))
{
Expand All @@ -134,7 +142,7 @@ private static unsafe bool HasOnlyBlittableFields(this ITypeSymbol type, Immutab
{
if (!field.IsStatic)
{
if (!IsBlittableWorker(field.Type, seenTypes.Add(type), isBlittable))
if (!IsBlittableWorker(field.Type, seenTypes.Add(type), compilation, isBlittable))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,35 +882,43 @@ public async Task ValidateSnippets_InvalidCodeGracefulFailure(string id, string
[Fact]
public async Task ValidateDisableRuntimeMarshallingForBlittabilityCheckFromAssemblyReference()
{
// Emit the referenced assembly to a stream so we reference it through a metadata reference.
// Our check for strict blittability doesn't work correctly when using source compilation references.
// (There are sometimes false-positives.)
// This causes any diagnostics that depend on strict blittability being correctly calculated to
// not show up in the IDE experience. However, since they correctly show up when doing builds,
// either by running the Build command in the IDE or a command line build, we aren't allowing invalid code.
// This test validates the Build-like experience. In the future, we should update this test to validate the
// IDE-like experience once we fix that case
// (If the IDE experience works, then the command-line experience will also work.)
// This bug is tracked in https://github.com/dotnet/runtime/issues/84739.
string assemblySource = $$"""
using System.Runtime.InteropServices.Marshalling;
{{CodeSnippets.ValidateDisableRuntimeMarshalling.NonBlittableUserDefinedTypeWithNativeType}}
""";
Compilation assemblyComp = await TestUtils.CreateCompilation(assemblySource);
Assert.Empty(assemblyComp.GetDiagnostics());

var ms = new MemoryStream();
Assert.True(assemblyComp.Emit(ms).Success);

string testSource = CodeSnippets.ValidateDisableRuntimeMarshalling.TypeUsage(string.Empty);

const string AdditionalProjectName = "AdditionalProject";

VerifyCS.Test test = new(referenceAncillaryInterop: false)
{
TestCode = testSource,
TestState =
{
Sources =
{
testSource
},
AdditionalProjectReferences =
{
AdditionalProjectName
},
AdditionalProjects =
{
[AdditionalProjectName] =
{
Sources =
{
assemblySource

}
}
}
},
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
};

test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromImage(ms.ToArray()));
test.TestState.AdditionalProjects[AdditionalProjectName].AdditionalReferences.AddRange(test.TestState.AdditionalReferences);

// The errors should indicate the DisableRuntimeMarshalling is required.
test.ExpectedDiagnostics.Add(
Expand Down