diff --git a/Datadog.Trace.Build.g.sln b/Datadog.Trace.Build.g.sln
index f705ab4f9040..438b3aaffbca 100644
--- a/Datadog.Trace.Build.g.sln
+++ b/Datadog.Trace.Build.g.sln
@@ -175,6 +175,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks.Trace", "tracer\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.DuckTyping.Tests", "tracer\test\Datadog.Trace.DuckTyping.Tests\Datadog.Trace.DuckTyping.Tests.csproj", "{91E50134-0E55-4D22-B180-6967174FCE0B}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.DuckTyping.Tests.Fixtures.Shared", "tracer\test\Datadog.Trace.DuckTyping.Tests.Fixtures\Shared\Datadog.Trace.DuckTyping.Tests.Fixtures.Shared.csproj", "{AF81B51B-C799-468D-9488-2D30C7A435B6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.DuckTyping.Tests.Fixtures.Target", "tracer\test\Datadog.Trace.DuckTyping.Tests.Fixtures\Target\Datadog.Trace.DuckTyping.Tests.Fixtures.Target.csproj", "{DE30EF39-4373-4485-8DC2-8CC08233CEA1}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CallTargetNativeTest", "tracer\test\test-applications\instrumentation\CallTargetNativeTest\CallTargetNativeTest.csproj", "{021EFBA6-C4BA-4DE5-BF3F-C263EE9E20DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AspNet472.LoaderOptimizationRegKey", "tracer\test\test-applications\aspnet\Samples.AspNet472.LoaderOptimizationRegKey\Samples.AspNet472.LoaderOptimizationRegKey.csproj", "{BBB60B0F-BF01-4499-936A-4A299A9ACFD4}"
@@ -369,6 +373,14 @@ Global
{91E50134-0E55-4D22-B180-6967174FCE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91E50134-0E55-4D22-B180-6967174FCE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91E50134-0E55-4D22-B180-6967174FCE0B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Release|Any CPU.Build.0 = Release|Any CPU
{021EFBA6-C4BA-4DE5-BF3F-C263EE9E20DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{021EFBA6-C4BA-4DE5-BF3F-C263EE9E20DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{021EFBA6-C4BA-4DE5-BF3F-C263EE9E20DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -602,6 +614,8 @@ Global
{C00D8070-A38A-4267-9730-E9985CAE77DF} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB}
{B8D132F6-43E3-44D9-902C-78051DBAD01C} = {E5439139-6F94-44FA-9590-C32FCC1C7A93}
{91E50134-0E55-4D22-B180-6967174FCE0B} = {8CEC2042-F11C-49F5-A674-2355793B600A}
+ {AF81B51B-C799-468D-9488-2D30C7A435B6} = {8CEC2042-F11C-49F5-A674-2355793B600A}
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1} = {8CEC2042-F11C-49F5-A674-2355793B600A}
{021EFBA6-C4BA-4DE5-BF3F-C263EE9E20DB} = {933F1D4B-1216-4BC1-956E-8C30818BAA0F}
{BBB60B0F-BF01-4499-936A-4A299A9ACFD4} = {AFA0AB23-64F0-4AC1-9050-6CE8FE06F580}
{78004AA7-26DD-44DB-A2C7-C287A5BBE5D6} = {A0C5FBBB-CFB2-4FB9-B8F0-55676E9DCF06}
diff --git a/Datadog.Trace.sln b/Datadog.Trace.sln
index 5582c2937e50..8dd1047bbd81 100644
--- a/Datadog.Trace.sln
+++ b/Datadog.Trace.sln
@@ -227,6 +227,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependency-libs", "dependen
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.DuckTyping.Tests", "tracer\test\Datadog.Trace.DuckTyping.Tests\Datadog.Trace.DuckTyping.Tests.csproj", "{91E50134-0E55-4D22-B180-6967174FCE0B}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.DuckTyping.Tests.Fixtures.Shared", "tracer\test\Datadog.Trace.DuckTyping.Tests.Fixtures\Shared\Datadog.Trace.DuckTyping.Tests.Fixtures.Shared.csproj", "{AF81B51B-C799-468D-9488-2D30C7A435B6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.DuckTyping.Tests.Fixtures.Target", "tracer\test\Datadog.Trace.DuckTyping.Tests.Fixtures\Target\Datadog.Trace.DuckTyping.Tests.Fixtures.Target.csproj", "{DE30EF39-4373-4485-8DC2-8CC08233CEA1}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{A0C5FBBB-CFB2-4FB9-B8F0-55676E9DCF06}"
ProjectSection(SolutionItems) = preProject
tracer\build\PackageVersionsGeneratorDefinitions.json = tracer\build\PackageVersionsGeneratorDefinitions.json
@@ -871,6 +875,14 @@ Global
{91E50134-0E55-4D22-B180-6967174FCE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91E50134-0E55-4D22-B180-6967174FCE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91E50134-0E55-4D22-B180-6967174FCE0B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AF81B51B-C799-468D-9488-2D30C7A435B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1}.Release|Any CPU.Build.0 = Release|Any CPU
{D79491F0-CA92-439B-98CE-7AF9F57EBEB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D79491F0-CA92-439B-98CE-7AF9F57EBEB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D79491F0-CA92-439B-98CE-7AF9F57EBEB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1661,6 +1673,8 @@ Global
{AFA0AB23-64F0-4AC1-9050-6CE8FE06F580} = {9518425A-36A5-4B8F-B0B8-6137DB88441D}
{8683D82A-2BBE-4199-9C36-C59F48804F90} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{91E50134-0E55-4D22-B180-6967174FCE0B} = {8CEC2042-F11C-49F5-A674-2355793B600A}
+ {AF81B51B-C799-468D-9488-2D30C7A435B6} = {8CEC2042-F11C-49F5-A674-2355793B600A}
+ {DE30EF39-4373-4485-8DC2-8CC08233CEA1} = {8CEC2042-F11C-49F5-A674-2355793B600A}
{CC53E5C5-9D3E-4AD9-A9CA-D2190463EB5B} = {A0C5FBBB-CFB2-4FB9-B8F0-55676E9DCF06}
{E5439139-6F94-44FA-9590-C32FCC1C7A93} = {8CEC2042-F11C-49F5-A674-2355793B600A}
{D79491F0-CA92-439B-98CE-7AF9F57EBEB0} = {498A300E-D036-49B7-A43D-821D1CAF11A5}
diff --git a/tracer/build/_build/Build.Steps.cs b/tracer/build/_build/Build.Steps.cs
index ce8ce52e31e2..9dd04a626a9c 100644
--- a/tracer/build/_build/Build.Steps.cs
+++ b/tracer/build/_build/Build.Steps.cs
@@ -1427,6 +1427,8 @@ void PrepareMonitoringHomeLinuxForPackaging(AbsolutePath assetsDirectory, string
.Executes(() =>
{
//we need to build in this exact order
+ DotnetBuild(TracerDirectory.GlobFiles("test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/*.csproj"));
+ DotnetBuild(TracerDirectory.GlobFiles("test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/*.csproj"));
DotnetBuild(TracerDirectory.GlobFiles("test/**/*TestHelpers.csproj"));
DotnetBuild(TracerDirectory.GlobFiles("test/**/*TestHelpers.AutoInstrumentation.csproj"));
});
diff --git a/tracer/src/Datadog.Trace.Trimming/build/Datadog.Trace.Trimming.xml b/tracer/src/Datadog.Trace.Trimming/build/Datadog.Trace.Trimming.xml
index f7ee6a2f3b32..8809ff7afd33 100644
--- a/tracer/src/Datadog.Trace.Trimming/build/Datadog.Trace.Trimming.xml
+++ b/tracer/src/Datadog.Trace.Trimming/build/Datadog.Trace.Trimming.xml
@@ -1107,6 +1107,7 @@
+
diff --git a/tracer/src/Datadog.Trace/DuckTyping/DuckType.Statics.cs b/tracer/src/Datadog.Trace/DuckTyping/DuckType.Statics.cs
index e88b2515e4f6..1b9335e9e261 100644
--- a/tracer/src/Datadog.Trace/DuckTyping/DuckType.Statics.cs
+++ b/tracer/src/Datadog.Trace/DuckTyping/DuckType.Statics.cs
@@ -12,6 +12,9 @@
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
+#if NETCOREAPP3_1 || NET6_0_OR_GREATER
+using System.Runtime.Loader;
+#endif
// ReSharper disable InconsistentNaming
namespace Datadog.Trace.DuckTyping
@@ -40,6 +43,18 @@ public static partial class DuckType
private static readonly MethodInfo? _methodBuilderGetToken;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static readonly ConstructorInfo? _ignoresAccessChecksToAttributeCtor;
+#if NETSTANDARD2_0 || NETCOREAPP3_1
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private static readonly MethodInfo? _defineDynamicAssemblyMethodInfo;
+#endif
+#if NETSTANDARD2_0
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private static readonly MethodInfo? _getAssemblyLoadContextMethodInfo;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private static readonly object? _duckTypeAssemblyLoadContext;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private static readonly Dictionary AssembliesInDuckTypeLoadContext;
+#endif
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static long _assemblyCount;
@@ -59,6 +74,15 @@ static DuckType()
_methodBuilderGetToken = typeof(MethodBuilder).GetMethod("GetToken", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?? typeof(MethodBuilder).GetProperty("MetadataToken", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)?.GetMethod;
_ignoresAccessChecksToAttributeCtor = typeof(IgnoresAccessChecksToAttribute).GetConstructor(new[] { typeof(string) });
+#if NETSTANDARD2_0 || NETCOREAPP3_1
+ _defineDynamicAssemblyMethodInfo = typeof(AssemblyBuilder).GetMethod(nameof(AssemblyBuilder.DefineDynamicAssembly), new[] { typeof(AssemblyName), typeof(AssemblyBuilderAccess) });
+#endif
+#if NETSTANDARD2_0
+ var assemblyLoadContextType = Type.GetType("System.Runtime.Loader.AssemblyLoadContext, System.Runtime.Loader", throwOnError: false);
+ _getAssemblyLoadContextMethodInfo = assemblyLoadContextType?.GetMethod("GetLoadContext", BindingFlags.Public | BindingFlags.Static, binder: null, [typeof(Assembly)], modifiers: null);
+ _duckTypeAssemblyLoadContext = _getAssemblyLoadContextMethodInfo?.Invoke(obj: null, [typeof(DuckType).Assembly]);
+ AssembliesInDuckTypeLoadContext = new();
+#endif
_assemblyCount = 0;
_typeCount = 0;
@@ -139,6 +163,21 @@ private static ConstructorInfo IgnoresAccessChecksToAttributeCtor
}
}
+#if NETSTANDARD2_0 || NETCOREAPP3_1
+ private static MethodInfo DefineDynamicAssemblyMethodInfo
+ {
+ get
+ {
+ if (_defineDynamicAssemblyMethodInfo is null)
+ {
+ DuckTypeException.Throw($"{nameof(AssemblyBuilder)}.{nameof(AssemblyBuilder.DefineDynamicAssembly)}() cannot be found.");
+ }
+
+ return _defineDynamicAssemblyMethodInfo;
+ }
+ }
+#endif
+
///
/// Gets the ModuleBuilder instance from a target type. (.NET Framework / Non AssemblyLoadContext version)
///
@@ -180,9 +219,81 @@ static ModuleBuilder CreateModuleBuilder(string name, Assembly targetAssembly)
{
var assemblyName = new AssemblyName(name + $"_{++_assemblyCount}");
assemblyName.Version = targetAssembly.GetName().Version;
+
+#if NET6_0_OR_GREATER
+ AssemblyBuilder assemblyBuilder;
+ if (IsAssemblyInCurrentLoadContext(targetAssembly))
+ {
+ assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
+ }
+ else
+ {
+ using var contextualReflectionScope = AssemblyLoadContext.EnterContextualReflection(targetAssembly);
+ assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
+ }
+#elif NETSTANDARD2_0 || NETCOREAPP3_1
+ var assemblyBuilder = IsAssemblyInCurrentLoadContext(targetAssembly)
+ ? AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run)
+ : DefineDynamicAssemblyInTargetLoadContext(assemblyName, targetAssembly.ManifestModule);
+#else
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
+#endif
return assemblyBuilder.DefineDynamicModule("MainModule");
}
+
+#if NETCOREAPP3_1 || NET6_0_OR_GREATER
+ static bool IsAssemblyInCurrentLoadContext(Assembly targetAssembly)
+ {
+#if NET6_0_OR_GREATER
+ var currentLoadContext = AssemblyLoadContext.CurrentContextualReflectionContext
+ ?? AssemblyLoadContext.GetLoadContext(typeof(DuckType).Assembly);
+#else
+ var currentLoadContext = AssemblyLoadContext.GetLoadContext(typeof(DuckType).Assembly);
+#endif
+ return ReferenceEquals(AssemblyLoadContext.GetLoadContext(targetAssembly), currentLoadContext);
+ }
+#elif NETSTANDARD2_0
+ static bool IsAssemblyInCurrentLoadContext(Assembly targetAssembly)
+ {
+ // AssemblyLoadContext is not part of the .NET Standard 2.0 contract. Resolve it at runtime
+ // so the fast path does not add a System.Runtime.Loader dependency to Datadog.Trace.
+ if (_getAssemblyLoadContextMethodInfo is null)
+ {
+ return true;
+ }
+
+ if (!AssembliesInDuckTypeLoadContext.TryGetValue(targetAssembly, out var isInDuckTypeLoadContext))
+ {
+ var targetAssemblyLoadContext = _getAssemblyLoadContextMethodInfo.Invoke(obj: null, [targetAssembly]);
+ isInDuckTypeLoadContext = ReferenceEquals(targetAssemblyLoadContext, _duckTypeAssemblyLoadContext);
+ AssembliesInDuckTypeLoadContext.Add(targetAssembly, isInDuckTypeLoadContext);
+ }
+
+ return isInDuckTypeLoadContext;
+ }
+#endif
+
+#if NETSTANDARD2_0 || NETCOREAPP3_1
+ static AssemblyBuilder DefineDynamicAssemblyInTargetLoadContext(AssemblyName assemblyName, Module targetModule)
+ {
+ // Before .NET 6, DefineDynamicAssembly does not honor contextual reflection. Associating the
+ // call site with the target module makes the runtime create the proxy in the target load context.
+ var createAssemblyMethod = new DynamicMethod(
+ "CreateDuckTypeAssembly",
+ typeof(AssemblyBuilder),
+ [typeof(AssemblyName)],
+ targetModule,
+ skipVisibility: false);
+ var il = createAssemblyMethod.GetILGenerator();
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldc_I4, (int)AssemblyBuilderAccess.Run);
+ il.Emit(OpCodes.Call, DefineDynamicAssemblyMethodInfo);
+ il.Emit(OpCodes.Ret);
+
+ var createAssembly = (Func)createAssemblyMethod.CreateDelegate(typeof(Func));
+ return createAssembly(assemblyName);
+ }
+#endif
}
///
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Directory.Build.props b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Directory.Build.props
new file mode 100644
index 000000000000..c13ea1ed0a66
--- /dev/null
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/Datadog.Trace.DuckTyping.Tests.Fixtures.Shared.csproj b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/Datadog.Trace.DuckTyping.Tests.Fixtures.Shared.csproj
new file mode 100644
index 000000000000..e62ba593a204
--- /dev/null
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/Datadog.Trace.DuckTyping.Tests.Fixtures.Shared.csproj
@@ -0,0 +1,9 @@
+
+
+
+ netstandard2.0
+ false
+ enable
+
+
+
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/FieldValue.cs b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/FieldValue.cs
new file mode 100644
index 000000000000..f1d217a70315
--- /dev/null
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Shared/FieldValue.cs
@@ -0,0 +1,13 @@
+//
+// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
+// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
+//
+
+namespace Datadog.Trace.DuckTyping.Tests.Fixtures.Shared;
+
+public sealed class FieldValue
+{
+ public const int ExpectedValue = 42;
+
+ public int Value => ExpectedValue;
+}
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/Datadog.Trace.DuckTyping.Tests.Fixtures.Target.csproj b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/Datadog.Trace.DuckTyping.Tests.Fixtures.Target.csproj
new file mode 100644
index 000000000000..65a5d262232f
--- /dev/null
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/Datadog.Trace.DuckTyping.Tests.Fixtures.Target.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.0
+ false
+ enable
+
+
+
+
+
+
+
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/DuckTypingTarget.cs b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/DuckTypingTarget.cs
new file mode 100644
index 000000000000..f194821034e4
--- /dev/null
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests.Fixtures/Target/DuckTypingTarget.cs
@@ -0,0 +1,15 @@
+//
+// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
+// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
+//
+
+using Datadog.Trace.DuckTyping.Tests.Fixtures.Shared;
+
+namespace Datadog.Trace.DuckTyping.Tests.Fixtures.Target;
+
+public sealed class DuckTypingTarget
+{
+ private readonly FieldValue _field = new();
+
+ public object FieldValue => _field;
+}
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests/AssemblyLoadContextTests.cs b/tracer/test/Datadog.Trace.DuckTyping.Tests/AssemblyLoadContextTests.cs
new file mode 100644
index 000000000000..57db118d5acc
--- /dev/null
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests/AssemblyLoadContextTests.cs
@@ -0,0 +1,132 @@
+//
+// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
+// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
+//
+
+#if NETCOREAPP2_1_OR_GREATER
+
+#nullable enable
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+using Datadog.Trace.DuckTyping.Tests.Fixtures.Shared;
+using FluentAssertions;
+using Xunit;
+
+namespace Datadog.Trace.DuckTyping.Tests;
+
+public class AssemblyLoadContextTests
+{
+ private const string SharedAssemblyName = "Datadog.Trace.DuckTyping.Tests.Fixtures.Shared";
+ private const string TargetAssemblyName = "Datadog.Trace.DuckTyping.Tests.Fixtures.Target";
+ private const string TargetTypeName = "Datadog.Trace.DuckTyping.Tests.Fixtures.Target.DuckTypingTarget";
+
+ internal interface ITargetProxy
+ {
+ [DuckField(Name = "_field")]
+ IFieldValue? Field { get; }
+ }
+
+ internal interface IFieldValue
+ {
+ int Value { get; }
+ }
+
+ [Fact]
+ public void DuckProxyInDefaultAssemblyLoadContext()
+ {
+ var proxy = new FieldValue().DuckCast();
+
+ proxy.Value.Should().Be(FieldValue.ExpectedValue);
+ AssemblyLoadContext.GetLoadContext(proxy.GetType().Assembly).Should().BeSameAs(AssemblyLoadContext.Default);
+ }
+
+ [Fact]
+ public void DuckFieldAcrossAssemblyLoadContexts()
+ {
+ var defaultContext = AssemblyLoadContext.Default;
+ var targetContext = new TestAssemblyLoadContext();
+
+ var defaultSharedAssembly = typeof(FieldValue).Assembly;
+ AssemblyLoadContext.GetLoadContext(typeof(DuckType).Assembly).Should().BeSameAs(defaultContext);
+ AssemblyLoadContext.GetLoadContext(typeof(ITargetProxy).Assembly).Should().BeSameAs(defaultContext);
+ AssemblyLoadContext.GetLoadContext(defaultSharedAssembly).Should().BeSameAs(defaultContext);
+ AppDomain.CurrentDomain.GetAssemblies().Should().NotContain(
+ assembly => AssemblyLoadContext.GetLoadContext(assembly) == defaultContext && assembly.GetName().Name == TargetAssemblyName);
+
+ // The target field and the generated proxy resolve the same dependency identity in different contexts,
+ // matching Azure Functions where Event Grid has a separate Azure.Core copy in its load context.
+ var targetType = LoadTargetType(targetContext, out var targetSharedAssembly);
+ targetSharedAssembly.FullName.Should().Be(defaultSharedAssembly.FullName);
+ targetSharedAssembly.Should().NotBeSameAs(defaultSharedAssembly);
+ AssemblyLoadContext.GetLoadContext(targetSharedAssembly).Should().BeSameAs(targetContext);
+ var targetField = targetType.GetField("_field", BindingFlags.Instance | BindingFlags.NonPublic)!;
+ targetField.FieldType.Assembly.Should().BeSameAs(targetSharedAssembly);
+ var target = Activator.CreateInstance(targetType)!;
+
+ var proxy = target.DuckCast();
+ var field = proxy.Field;
+ field.Should().NotBeNull();
+ field!.Value.Should().Be(FieldValue.ExpectedValue);
+
+ var proxyType = proxy.GetType();
+ proxyType.Assembly.IsDynamic.Should().BeTrue();
+ AssemblyLoadContext.GetLoadContext(proxyType.Assembly).Should().BeSameAs(targetContext);
+ }
+
+ [Fact]
+ public void CachesProxyPerTargetAssemblyLoadContext()
+ {
+ var firstContext = new TestAssemblyLoadContext();
+ var secondContext = new TestAssemblyLoadContext();
+ var firstTargetType = LoadTargetType(firstContext, out _);
+ var secondTargetType = LoadTargetType(secondContext, out _);
+
+ firstTargetType.FullName.Should().Be(secondTargetType.FullName);
+ firstTargetType.Assembly.FullName.Should().Be(secondTargetType.Assembly.FullName);
+ firstTargetType.Should().NotBeSameAs(secondTargetType);
+
+ var firstTarget = Activator.CreateInstance(firstTargetType)!;
+ var secondTarget = Activator.CreateInstance(secondTargetType)!;
+ var firstProxy = firstTarget.DuckCast();
+ var cachedFirstProxy = firstTarget.DuckCast();
+ var secondProxy = secondTarget.DuckCast();
+
+ var firstField = firstProxy.Field;
+ var secondField = secondProxy.Field;
+ firstField.Should().NotBeNull();
+ firstField!.Value.Should().Be(FieldValue.ExpectedValue);
+ secondField.Should().NotBeNull();
+ secondField!.Value.Should().Be(FieldValue.ExpectedValue);
+
+ var firstProxyType = firstProxy.GetType();
+ var cachedFirstProxyType = cachedFirstProxy.GetType();
+ var secondProxyType = secondProxy.GetType();
+ cachedFirstProxyType.Should().BeSameAs(firstProxyType);
+ secondProxyType.Should().NotBeSameAs(firstProxyType);
+ AssemblyLoadContext.GetLoadContext(firstProxyType.Assembly).Should().BeSameAs(firstContext);
+ AssemblyLoadContext.GetLoadContext(secondProxyType.Assembly).Should().BeSameAs(secondContext);
+ }
+
+ private static Type LoadTargetType(AssemblyLoadContext targetContext, out Assembly targetSharedAssembly)
+ {
+ var assemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
+ var fixtureDirectory = Path.Combine(assemblyDirectory, "AssemblyLoadContextFixtures");
+ var sharedAssemblyPath = Path.Combine(fixtureDirectory, SharedAssemblyName + ".dll");
+ var targetAssemblyPath = Path.Combine(fixtureDirectory, TargetAssemblyName + ".dll");
+
+ targetSharedAssembly = targetContext.LoadFromAssemblyPath(sharedAssemblyPath);
+ var targetAssembly = targetContext.LoadFromAssemblyPath(targetAssemblyPath);
+ AssemblyLoadContext.GetLoadContext(targetAssembly).Should().BeSameAs(targetContext);
+ return targetAssembly.GetType(TargetTypeName, throwOnError: true)!;
+ }
+
+ private sealed class TestAssemblyLoadContext : AssemblyLoadContext
+ {
+ protected override Assembly? Load(AssemblyName assemblyName) => null;
+ }
+}
+
+#endif
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests/Datadog.Trace.DuckTyping.Tests.csproj b/tracer/test/Datadog.Trace.DuckTyping.Tests/Datadog.Trace.DuckTyping.Tests.csproj
index 4d0b91086b2b..f2426473d096 100644
--- a/tracer/test/Datadog.Trace.DuckTyping.Tests/Datadog.Trace.DuckTyping.Tests.csproj
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests/Datadog.Trace.DuckTyping.Tests.csproj
@@ -9,6 +9,15 @@
+
+
$(DefineConstants);INTERFACE_DEFAULTS
diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests/GetAssemblyTests.cs b/tracer/test/Datadog.Trace.DuckTyping.Tests/GetAssemblyTests.cs
index 7e5111d01f92..619465008427 100644
--- a/tracer/test/Datadog.Trace.DuckTyping.Tests/GetAssemblyTests.cs
+++ b/tracer/test/Datadog.Trace.DuckTyping.Tests/GetAssemblyTests.cs
@@ -54,9 +54,9 @@ public void GetAssemblyTest()
#if NETFRAMEWORK
asmDuckTypes.Should().Be(1510);
#elif NETCOREAPP2_1
- asmDuckTypes.Should().Be(1513);
+ asmDuckTypes.Should().Be(1520);
#else
- asmDuckTypes.Should().Be(1514);
+ asmDuckTypes.Should().Be(1521);
#endif
}
else
@@ -65,9 +65,9 @@ public void GetAssemblyTest()
#if NETFRAMEWORK
asmDuckTypes.Should().BeGreaterThan(1510);
#elif NETCOREAPP2_1
- asmDuckTypes.Should().BeGreaterThan(1513);
+ asmDuckTypes.Should().BeGreaterThan(1520);
#else
- asmDuckTypes.Should().BeGreaterThan(1514);
+ asmDuckTypes.Should().BeGreaterThan(1521);
#endif
}
}