Skip to content
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
Expand Up @@ -269,8 +269,20 @@ internal static unsafe MarshalAsAttribute GetMarshalAs(ConstArray nativeType, Ru
? null
: Text.Encoding.UTF8.GetString(MemoryMarshal.CreateReadOnlySpanFromNullTerminated(marshalCookieRaw));

RuntimeType? safeArrayUserDefinedType = string.IsNullOrEmpty(safeArrayUserDefinedTypeName) ? null :
TypeNameResolver.GetTypeReferencedByCustomAttribute(safeArrayUserDefinedTypeName, scope);
RuntimeType? safeArrayUserDefinedType = null;

try
{
safeArrayUserDefinedType = string.IsNullOrEmpty(safeArrayUserDefinedTypeName) ? null :
TypeNameResolver.GetTypeReferencedByCustomAttribute(safeArrayUserDefinedTypeName, scope);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update marshalTypeName resolution below to use IsNullOrEmpty for consistency?

}
catch (TypeLoadException)
{
// The user may have supplied a bad type name string causing this TypeLoadException
// Regardless, we return the bad type name
Debug.Assert(safeArrayUserDefinedTypeName is not null);
}

RuntimeType? marshalTypeRef = null;

try
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/vm/managedmdimport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ FCIMPL11(FC_BOOL_RET, MetaDataImport::GetMarshalAs,

*safeArraySubType = info.m_SafeArrayElementVT;

*safeArrayUserDefinedSubType = info.m_strSafeArrayUserDefTypeName;
*safeArrayUserDefinedSubType = info.m_cSafeArrayUserDefTypeNameBytes > 0 ? info.m_strSafeArrayUserDefTypeName : NULL;
#else
*iidParamIndex = 0;

Expand All @@ -59,9 +59,9 @@ FCIMPL11(FC_BOOL_RET, MetaDataImport::GetMarshalAs,
*safeArrayUserDefinedSubType = NULL;
#endif

*marshalType = info.m_strCMMarshalerTypeName;
*marshalType = info.m_cCMMarshalerTypeNameBytes > 0 ? info.m_strCMMarshalerTypeName : NULL;

*marshalCookie = info.m_strCMCookie;
*marshalCookie = info.m_cCMCookieStrBytes > 0 ? info.m_strCMCookie : NULL;

FC_RETURN_BOOL(TRUE);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Runtime.Loader;
using Xunit;

namespace System.Runtime.InteropServices.Tests
Expand All @@ -11,7 +18,7 @@ public class MarshalAsAttributeTests
[InlineData((UnmanagedType)(-1))]
[InlineData(UnmanagedType.HString)]
[InlineData((UnmanagedType)int.MaxValue)]
public void Ctor_UmanagedTye(UnmanagedType unmanagedType)
public void Ctor_UnmanagedType(UnmanagedType unmanagedType)
{
var attribute = new MarshalAsAttribute(unmanagedType);
Assert.Equal(unmanagedType, attribute.Value);
Expand All @@ -26,5 +33,83 @@ public void Ctor_ShortUnmanagedType(short umanagedType)
var attribute = new MarshalAsAttribute(umanagedType);
Assert.Equal((UnmanagedType)umanagedType, attribute.Value);
}

[Fact]
public void SafeArrayParameter_ZeroLengthUserDefinedSubType_DoesNotThrow()
{
// Build a PE with a FieldMarshal blob that encodes
// NATIVE_TYPE_SAFEARRAY + VT_DISPATCH + compressed-string-length-0.
// tlbimp produces this format when there is no user-defined sub type name,
// and it previously caused TypeLoadException because the native code returned
// a non-null pointer past the blob boundary for the zero-length string.
byte[] peImage = CreatePEWithSafeArrayFieldMarshal();

AssemblyLoadContext alc = new(nameof(SafeArrayParameter_ZeroLengthUserDefinedSubType_DoesNotThrow), isCollectible: true);
try
{
Assembly asm = alc.LoadFromStream(new MemoryStream(peImage));
Type iface = asm.GetType("TestInterface");
MethodInfo method = iface.GetMethod("TestMethod");
ParameterInfo param = method.GetParameters()[0];

// Accessing custom attributes must not throw TypeLoadException.
_ = param.GetCustomAttributes(false);

MarshalAsAttribute attr = (MarshalAsAttribute)Attribute.GetCustomAttribute(param, typeof(MarshalAsAttribute));
Assert.NotNull(attr);
Assert.Equal(UnmanagedType.SafeArray, attr.Value);
Assert.Null(attr.SafeArrayUserDefinedSubType);
}
finally
{
alc.Unload();
}
}

/// <summary>
/// Builds a minimal PE containing an interface with a method whose parameter
/// has a FieldMarshal blob matching what tlbimp generates for SafeArray
/// without a user-defined sub type:
/// byte 0x1D (NATIVE_TYPE_SAFEARRAY), 0x09 (VT_DISPATCH), 0x00 (string len 0)
/// The trailing zero-length string distinguishes this from blobs the C# compiler
/// produces (which omit the length byte entirely).
/// </summary>
private static byte[] CreatePEWithSafeArrayFieldMarshal()
{
PersistedAssemblyBuilder ab = new(new AssemblyName("SafeArrayTestAsm"), typeof(object).Assembly);
ModuleBuilder moduleDef = ab.DefineDynamicModule("SafeArrayTestAsm.dll");
TypeBuilder typeDef = moduleDef.DefineType("TestInterface",
TypeAttributes.Interface | TypeAttributes.Abstract | TypeAttributes.Public);

MethodBuilder methodDef = typeDef.DefineMethod("TestMethod",
MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual |
MethodAttributes.NewSlot | MethodAttributes.HideBySig,
typeof(void), new[] { typeof(object[]) });

methodDef.DefineParameter(1, ParameterAttributes.HasFieldMarshal, "args");
typeDef.CreateType();

MetadataBuilder mdBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out _);

// Inject the problematic FieldMarshal descriptor directly:
// 0x1D NATIVE_TYPE_SAFEARRAY
// 0x09 VT_DISPATCH (compressed uint)
// 0x00 compressed string length = 0 (empty user-defined type name)
BlobBuilder marshalDescriptor = new();
marshalDescriptor.WriteBytes(new byte[] { 0x1D, 0x09, 0x00 });
mdBuilder.AddMarshallingDescriptor(
MetadataTokens.ParameterHandle(1),
mdBuilder.GetOrAddBlob(marshalDescriptor));

ManagedPEBuilder peAssembler = new(
PEHeaderBuilder.CreateLibraryHeader(),
new MetadataRootBuilder(mdBuilder),
ilStream,
flags: CorFlags.ILOnly);

BlobBuilder peOutput = new();
peAssembler.Serialize(peOutput);
return peOutput.ToArray();
}
}
}
Loading