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

TypeMap API (Dynamic implementation) #113954

Merged
merged 25 commits into from
Apr 2, 2025
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bd76011
TypeMap API (Dynamic implementation)
AaronRobinsonMSFT Mar 26, 2025
a2bfc27
Add basic tests
AaronRobinsonMSFT Mar 26, 2025
7ed138b
Feedback and fixes from testing.
AaronRobinsonMSFT Mar 27, 2025
2205ac8
Testing for all attributes
AaronRobinsonMSFT Mar 27, 2025
91862d1
Convert Tuple<,> to private type.
AaronRobinsonMSFT Mar 28, 2025
cf32158
Feedback pass 1
AaronRobinsonMSFT Mar 29, 2025
60e18c9
Always fallback and validate TypeSpec.
AaronRobinsonMSFT Mar 29, 2025
fb1e1a6
Handle exceptions from managed callbacks.
AaronRobinsonMSFT Mar 29, 2025
04d9b10
Convert assert to explicit check for TypeSpec.
AaronRobinsonMSFT Mar 29, 2025
49c5ad6
Delay parsing contents until needed.
AaronRobinsonMSFT Mar 30, 2025
479c15b
Fix build
AaronRobinsonMSFT Mar 31, 2025
e7f9e6a
Call Return on ArrayPool array.
AaronRobinsonMSFT Mar 31, 2025
eb9e9e2
Remove unnecessary lock.
AaronRobinsonMSFT Mar 31, 2025
380b809
Stop processing on error.
AaronRobinsonMSFT Mar 31, 2025
e94234f
Use GetMaxCharCount to estimate needed buffer length.
AaronRobinsonMSFT Mar 31, 2025
d2123d0
Perform type loading via the TypeNameResolver.GetTypeHelper API.
AaronRobinsonMSFT Mar 31, 2025
7e99942
Move managed VersionResilientHashCode logic
AaronRobinsonMSFT Mar 31, 2025
593f09e
Use newly shared VersionResilientHashCode logic.
AaronRobinsonMSFT Mar 31, 2025
9ac6068
Remove TypeMapLazyDictionary.cs from mono build.
AaronRobinsonMSFT Mar 31, 2025
51f8342
Remove unused message.
AaronRobinsonMSFT Mar 31, 2025
697d7e1
Throw NotSupportedException for TypeMap APIs.
AaronRobinsonMSFT Mar 31, 2025
edf0a13
Feedback
AaronRobinsonMSFT Apr 1, 2025
30148a3
Remove assert
AaronRobinsonMSFT Apr 1, 2025
6c6e35c
Add message to SPCL.
AaronRobinsonMSFT Apr 1, 2025
2713896
Remove unnecessary changes.
AaronRobinsonMSFT Apr 1, 2025
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
Prev Previous commit
Next Next commit
Handle exceptions from managed callbacks.
Add tests.
AaronRobinsonMSFT committed Mar 29, 2025
commit fb1e1a6b3eb70c34f9b288ced3f733ee2a5c2d43
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;

namespace System.Runtime.InteropServices
@@ -40,6 +41,8 @@ public LazyProxyTypeDictionary ProxyTypeMap
return _proxyTypeMap;
}
}

public ExceptionDispatchInfo? CreationException { get; set; }
}

// See assemblynative.hpp for native version.
@@ -77,18 +80,25 @@ private static unsafe void NewExternalTypeEntry(CallbackContext* context, Proces
Debug.Assert(arg->Utf8String1 != null);
Debug.Assert(arg->Utf8String2 != null);

string externalTypeName = new((sbyte*)arg->Utf8String1, 0, arg->StringLen1, Encoding.UTF8);
string targetType = new((sbyte*)arg->Utf8String2, 0, arg->StringLen2, Encoding.UTF8);
try
{
string externalTypeName = new((sbyte*)arg->Utf8String1, 0, arg->StringLen1, Encoding.UTF8);
string targetType = new((sbyte*)arg->Utf8String2, 0, arg->StringLen2, Encoding.UTF8);

TypeName parsedTarget = TypeNameParser.Parse(targetType, throwOnError: true)!;
if (parsedTarget.AssemblyName is null)
TypeName parsedTarget = TypeNameParser.Parse(targetType, throwOnError: true)!;
if (parsedTarget.AssemblyName is null)
{
// The assembly name is not included in the type name, so use the fallback assembly name.
Debug.Assert(arg->Utf8String3 != null);
string fallbackAssemblyName = new((sbyte*)arg->Utf8String3, 0, arg->StringLen3, Encoding.UTF8);
parsedTarget = CreateNewTypeName(targetType, fallbackAssemblyName);
}
context->ExternalTypeMap.Add(externalTypeName, parsedTarget);
}
catch (Exception ex)
{
// The assembly name is not included in the type name, so use the fallback assembly name.
Debug.Assert(arg->Utf8String3 != null);
string fallbackAssemblyName = new((sbyte*)arg->Utf8String3, 0, arg->StringLen3, Encoding.UTF8);
parsedTarget = CreateNewTypeName(targetType, fallbackAssemblyName);
context->CreationException = ExceptionDispatchInfo.Capture(ex);
}
context->ExternalTypeMap.Add(externalTypeName, parsedTarget);
}

[UnmanagedCallersOnly]
@@ -99,26 +109,33 @@ private static unsafe void NewProxyTypeEntry(CallbackContext* context, ProcessAt
Debug.Assert(arg->Utf8String1 != null);
Debug.Assert(arg->Utf8String2 != null);

string sourceType = new((sbyte*)arg->Utf8String1, 0, arg->StringLen1, Encoding.UTF8);
string proxyType = new((sbyte*)arg->Utf8String2, 0, arg->StringLen2, Encoding.UTF8);

TypeName parsedSource = TypeNameParser.Parse(sourceType, throwOnError: true)!;
TypeName parsedProxy = TypeNameParser.Parse(proxyType, throwOnError: true)!;
if (parsedSource.AssemblyName is null || parsedProxy.AssemblyName is null)
try
{
// The assembly name is not included in the type name, so use the fallback assembly name.
Debug.Assert(arg->Utf8String3 != null);
string fallbackAssemblyName = new((sbyte*)arg->Utf8String3, 0, arg->StringLen3, Encoding.UTF8);
if (parsedSource.AssemblyName is null)
{
parsedSource = CreateNewTypeName(sourceType, fallbackAssemblyName);
}
if (parsedProxy.AssemblyName is null)
string sourceType = new((sbyte*)arg->Utf8String1, 0, arg->StringLen1, Encoding.UTF8);
string proxyType = new((sbyte*)arg->Utf8String2, 0, arg->StringLen2, Encoding.UTF8);

TypeName parsedSource = TypeNameParser.Parse(sourceType, throwOnError: true)!;
TypeName parsedProxy = TypeNameParser.Parse(proxyType, throwOnError: true)!;
if (parsedSource.AssemblyName is null || parsedProxy.AssemblyName is null)
{
parsedProxy = CreateNewTypeName(proxyType, fallbackAssemblyName);
// The assembly name is not included in the type name, so use the fallback assembly name.
Debug.Assert(arg->Utf8String3 != null);
string fallbackAssemblyName = new((sbyte*)arg->Utf8String3, 0, arg->StringLen3, Encoding.UTF8);
if (parsedSource.AssemblyName is null)
{
parsedSource = CreateNewTypeName(sourceType, fallbackAssemblyName);
}
if (parsedProxy.AssemblyName is null)
{
parsedProxy = CreateNewTypeName(proxyType, fallbackAssemblyName);
}
}
context->ProxyTypeMap.Add(parsedSource, parsedProxy);
}
catch (Exception ex)
{
context->CreationException = ExceptionDispatchInfo.Capture(ex);
}
context->ProxyTypeMap.Add(parsedSource, parsedProxy);
}

private static unsafe CallbackContext CreateMaps(
@@ -139,6 +156,9 @@ private static unsafe CallbackContext CreateMaps(
newExternalTypeEntry,
newProxyTypeEntry,
&context);

context.CreationException?.Throw();

return context;
}

6 changes: 2 additions & 4 deletions src/tests/Interop/TypeMap/GroupTypes.cs
Original file line number Diff line number Diff line change
@@ -19,10 +19,8 @@ public class TypicalUseCase { }

public class DuplicateTypeNameKey { }

public class InvalidTypeNameKey { }

public class MultipleTypeMapAssemblies { }

public class UnknownAssemblyReference { }

public class InvalidExternalTypeName { } // TypeMapAttribute

public class InvalidSourceTypeName { } // TypeMapAssociationAttribute
19 changes: 15 additions & 4 deletions src/tests/Interop/TypeMap/TypeMapApp.cs
Original file line number Diff line number Diff line change
@@ -50,8 +50,11 @@
[assembly: TypeMapAssociation<C2<string>.I1>(typeof(object), typeof(string))]
[assembly: TypeMapAssociation<C2<string>.I2<string>>(typeof(object), typeof(string))]

[assembly: TypeMap<InvalidExternalTypeName>(null!, typeof(object))]
[assembly: TypeMapAssociation<InvalidSourceTypeName>(null!, typeof(object))]
[assembly: TypeMap<InvalidTypeNameKey>(null!, typeof(object))]
[assembly: TypeMapAssociation<InvalidTypeNameKey>(null!, typeof(object))]

[assembly: TypeMap<DuplicateTypeNameKey>("1", typeof(object))]
[assembly: TypeMap<DuplicateTypeNameKey>("1", typeof(object))]

[assembly: TypeMapAssociation<DuplicateTypeNameKey>(typeof(DupType_MapObject), typeof(object))]
[assembly: TypeMapAssociation<DuplicateTypeNameKey>(typeof(DupType_MapString), typeof(string))]
@@ -148,6 +151,14 @@ public static void Validate_ProxyTypeMapping()
Assert.False(map.TryGetValue(typeof(object), out Type? _));
}

[Fact]
public static void Validate_ExternalTypeMapping_DuplicateTypeKey()
{
Console.WriteLine(nameof(Validate_ExternalTypeMapping_DuplicateTypeKey));

Assert.Throws<ArgumentException>(() => TypeMapping.GetOrCreateExternalTypeMapping<DuplicateTypeNameKey>());
}

[Fact]
public static void Validate_ProxyTypeMapping_DuplicateTypeKey()
{
@@ -225,7 +236,7 @@ public static void Validate_EmptyOrInvalidMappings()
{
Console.WriteLine(nameof(Validate_EmptyOrInvalidMappings));

Assert.Throws<COMException>(() => TypeMapping.GetOrCreateExternalTypeMapping<InvalidExternalTypeName>());
Assert.Throws<COMException>(() => TypeMapping.GetOrCreateProxyTypeMapping<InvalidSourceTypeName>());
Assert.Throws<COMException>(() => TypeMapping.GetOrCreateExternalTypeMapping<InvalidTypeNameKey>());
Assert.Throws<COMException>(() => TypeMapping.GetOrCreateProxyTypeMapping<InvalidTypeNameKey>());
}
}

Unchanged files with check annotations Beta

<Message Importance="High" Text="$(MsgPrefix)Building managed test group $(__TestGroupToBuild): $(GroupBuildCmd)" />
<Exec Command="$(GroupBuildCmd)" />

Check failure on line 497 in src/tests/build.proj

Azure Pipelines / runtime (Build browser-wasm linux Release AllSubsets_Mono_RuntimeTests monointerpreter)

src/tests/build.proj#L497

src/tests/build.proj(497,5): error MSB3073: The command ""/__w/1/s/dotnet.sh" msbuild /__w/1/s/src/tests/build.proj /t:Build "/p:TargetArchitecture=wasm" "/p:Configuration=Release" "/p:LibrariesConfiguration=Release" "/p:TasksConfiguration=Release" "/p:TargetOS=browser" "/p:ToolsOS=" "/p:PackageOS=" "/p:RuntimeFlavor=mono" "/p:RuntimeVariant=monointerpreter" "/p:CLRTestBuildAllTargets=" "/p:UseCodeFlowEnforcement=" "/p:__TestGroupToBuild=3" "/p:__SkipRestorePackages=1" /nodeReuse:false /m:1 /bl:/__w/1/s/artifacts//log/Release/InnerManagedTestBuild.3.binlog /p:ContinuousIntegrationBuild=true "/p:DevTeamProvisioning=-"" exited with code 137.

Check failure on line 497 in src/tests/build.proj

Azure Pipelines / runtime

src/tests/build.proj#L497

src/tests/build.proj(497,5): error MSB3073: The command ""/__w/1/s/dotnet.sh" msbuild /__w/1/s/src/tests/build.proj /t:Build "/p:TargetArchitecture=wasm" "/p:Configuration=Release" "/p:LibrariesConfiguration=Release" "/p:TasksConfiguration=Release" "/p:TargetOS=browser" "/p:ToolsOS=" "/p:PackageOS=" "/p:RuntimeFlavor=mono" "/p:RuntimeVariant=monointerpreter" "/p:CLRTestBuildAllTargets=" "/p:UseCodeFlowEnforcement=" "/p:__TestGroupToBuild=3" "/p:__SkipRestorePackages=1" /nodeReuse:false /m:1 /bl:/__w/1/s/artifacts//log/Release/InnerManagedTestBuild.3.binlog /p:ContinuousIntegrationBuild=true "/p:DevTeamProvisioning=-"" exited with code 137.
</Target>
<Target Name="CheckTestBuildStep"