11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4- using System . Linq ;
4+ using System . IO ;
55using System . Reflection ;
6+ using System . Reflection . Emit ;
7+ using System . Reflection . Metadata ;
8+ using System . Reflection . Metadata . Ecma335 ;
9+ using System . Reflection . PortableExecutable ;
10+ using System . Runtime . Loader ;
611using Xunit ;
712
813namespace System . Runtime . InteropServices . Tests
@@ -29,31 +34,82 @@ public void Ctor_ShortUnmanagedType(short umanagedType)
2934 Assert . Equal ( ( UnmanagedType ) umanagedType , attribute . Value ) ;
3035 }
3136
32- [ Theory ]
33- [ InlineData ( nameof ( ISafeArrayMarshallingTest . Method1 ) ) ]
34- [ InlineData ( nameof ( ISafeArrayMarshallingTest . Method2 ) ) ]
35- public void SafeArrayParameter_NoUserDefinedSubType_CustomAttributesDoNotThrow ( string methodName )
37+ [ Fact ]
38+ public void SafeArrayParameter_ZeroLengthUserDefinedSubType_DoesNotThrow ( )
3639 {
37- MethodInfo method = typeof ( ISafeArrayMarshallingTest ) . GetMethod ( methodName ) ;
38- ParameterInfo parameter = method . GetParameters ( ) . Single ( ) ;
40+ // Build a PE with a FieldMarshal blob that encodes
41+ // NATIVE_TYPE_SAFEARRAY + VT_DISPATCH + compressed-string-length-0.
42+ // tlbimp produces this format when there is no user-defined sub type name,
43+ // and it previously caused TypeLoadException because the native code returned
44+ // a non-null pointer past the blob boundary for the zero-length string.
45+ byte [ ] peImage = CreatePEWithSafeArrayFieldMarshal ( ) ;
46+
47+ AssemblyLoadContext alc = new ( nameof ( SafeArrayParameter_ZeroLengthUserDefinedSubType_DoesNotThrow ) , isCollectible : true ) ;
48+ try
49+ {
50+ Assembly asm = alc . LoadFromStream ( new MemoryStream ( peImage ) ) ;
51+ Type iface = asm . GetType ( "TestInterface" ) ;
52+ MethodInfo method = iface . GetMethod ( "TestMethod" ) ;
53+ ParameterInfo param = method . GetParameters ( ) [ 0 ] ;
3954
40- // Accessing CustomAttributes should not throw TypeLoadException
41- // when SafeArrayUserDefinedSubType is not specified.
42- var attributes = parameter . CustomAttributes . ToList ( ) ;
55+ // Accessing custom attributes must not throw TypeLoadException.
56+ _ = param . GetCustomAttributes ( false ) ;
4357
44- MarshalAsAttribute marshalAs = ( MarshalAsAttribute ) Attribute . GetCustomAttribute ( parameter , typeof ( MarshalAsAttribute ) ) ;
45- Assert . NotNull ( marshalAs ) ;
46- Assert . Equal ( UnmanagedType . SafeArray , marshalAs . Value ) ;
47- Assert . Null ( marshalAs . SafeArrayUserDefinedSubType ) ;
58+ MarshalAsAttribute attr = ( MarshalAsAttribute ) Attribute . GetCustomAttribute ( param , typeof ( MarshalAsAttribute ) ) ;
59+ Assert . NotNull ( attr ) ;
60+ Assert . Equal ( UnmanagedType . SafeArray , attr . Value ) ;
61+ Assert . Null ( attr . SafeArrayUserDefinedSubType ) ;
62+ }
63+ finally
64+ {
65+ alc . Unload ( ) ;
66+ }
4867 }
4968
50- [ ComImport ]
51- [ Guid ( "1FC06EAF-2B18-4D54-B7D4-E654A8BEEF5B" ) ]
52- [ InterfaceType ( ComInterfaceType . InterfaceIsIDispatch ) ]
53- private interface ISafeArrayMarshallingTest
69+ /// <summary>
70+ /// Builds a minimal PE containing an interface with a method whose parameter
71+ /// has a FieldMarshal blob matching what tlbimp generates for SafeArray
72+ /// without a user-defined sub type:
73+ /// byte 0x1D (NATIVE_TYPE_SAFEARRAY), 0x09 (VT_DISPATCH), 0x00 (string len 0)
74+ /// The trailing zero-length string distinguishes this from blobs the C# compiler
75+ /// produces (which omit the length byte entirely).
76+ /// </summary>
77+ private static byte [ ] CreatePEWithSafeArrayFieldMarshal ( )
5478 {
55- void Method1 ( [ MarshalAs ( UnmanagedType . SafeArray , SafeArraySubType = VarEnum . VT_DISPATCH ) ] object [ ] args ) ;
56- void Method2 ( [ MarshalAs ( UnmanagedType . SafeArray , SafeArraySubType = VarEnum . VT_UNKNOWN ) ] object [ ] args ) ;
79+ PersistedAssemblyBuilder ab = new ( new AssemblyName ( "SafeArrayTestAsm" ) , typeof ( object ) . Assembly ) ;
80+ ModuleBuilder moduleDef = ab . DefineDynamicModule ( "SafeArrayTestAsm.dll" ) ;
81+ TypeBuilder typeDef = moduleDef . DefineType ( "TestInterface" ,
82+ TypeAttributes . Interface | TypeAttributes . Abstract | TypeAttributes . Public ) ;
83+
84+ MethodBuilder methodDef = typeDef . DefineMethod ( "TestMethod" ,
85+ MethodAttributes . Public | MethodAttributes . Abstract | MethodAttributes . Virtual |
86+ MethodAttributes . NewSlot | MethodAttributes . HideBySig ,
87+ typeof ( void ) , new [ ] { typeof ( object [ ] ) } ) ;
88+
89+ methodDef . DefineParameter ( 1 , ParameterAttributes . HasFieldMarshal , "args" ) ;
90+ typeDef . CreateType ( ) ;
91+
92+ MetadataBuilder mdBuilder = ab . GenerateMetadata ( out BlobBuilder ilStream , out _ ) ;
93+
94+ // Inject the problematic FieldMarshal descriptor directly:
95+ // 0x1D NATIVE_TYPE_SAFEARRAY
96+ // 0x09 VT_DISPATCH (compressed uint)
97+ // 0x00 compressed string length = 0 (empty user-defined type name)
98+ BlobBuilder marshalDescriptor = new ( ) ;
99+ marshalDescriptor . WriteBytes ( new byte [ ] { 0x1D , 0x09 , 0x00 } ) ;
100+ mdBuilder . AddMarshallingDescriptor (
101+ MetadataTokens . ParameterHandle ( 1 ) ,
102+ mdBuilder . GetOrAddBlob ( marshalDescriptor ) ) ;
103+
104+ ManagedPEBuilder peAssembler = new (
105+ PEHeaderBuilder . CreateLibraryHeader ( ) ,
106+ new MetadataRootBuilder ( mdBuilder ) ,
107+ ilStream ,
108+ flags : CorFlags . ILOnly ) ;
109+
110+ BlobBuilder peOutput = new ( ) ;
111+ peAssembler . Serialize ( peOutput ) ;
112+ return peOutput . ToArray ( ) ;
57113 }
58114 }
59115}
0 commit comments