From dbd194535469c442743fed74dc43896ebb151805 Mon Sep 17 00:00:00 2001 From: Colin Higgins Date: Thu, 8 Aug 2019 13:09:54 -0400 Subject: [PATCH] Convert AdoNet to use mdToken (#438) * Convert AdoNet to use mdToken * Use DbCommand as concrete type --- .../Emit/MethodBuilder.cs | 20 +-- .../Integrations/AdoNetIntegration.cs | 114 ++++++++++++------ 2 files changed, 90 insertions(+), 44 deletions(-) diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Emit/MethodBuilder.cs b/src/Datadog.Trace.ClrProfiler.Managed/Emit/MethodBuilder.cs index d3b466f6d5f8..82d84809020a 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Emit/MethodBuilder.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Emit/MethodBuilder.cs @@ -17,7 +17,7 @@ internal class MethodBuilder private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(new KeyComparer()); private static readonly ILog Log = LogProvider.GetLogger(typeof(MethodBuilder)); - private readonly Assembly _callingAssembly; + private readonly Assembly _resolutionAssembly; private readonly int _mdToken; private readonly int _originalOpCodeValue; private readonly OpCodeValue _opCode; @@ -36,9 +36,9 @@ internal class MethodBuilder private Type[] _methodGenerics; private bool _forceMethodDefResolve; - private MethodBuilder(Assembly callingAssembly, int mdToken, int opCode, string methodName) + private MethodBuilder(Assembly resolutionAssembly, int mdToken, int opCode, string methodName) { - _callingAssembly = callingAssembly; + _resolutionAssembly = resolutionAssembly; _mdToken = mdToken; _opCode = (OpCodeValue)opCode; _originalOpCodeValue = opCode; @@ -46,9 +46,9 @@ private MethodBuilder(Assembly callingAssembly, int mdToken, int opCode, string _forceMethodDefResolve = false; } - public static MethodBuilder Start(Assembly callingAssembly, int mdToken, int opCode, string methodName) + public static MethodBuilder Start(Assembly resolutionAssembly, int mdToken, int opCode, string methodName) { - return new MethodBuilder(callingAssembly, mdToken, opCode, methodName); + return new MethodBuilder(resolutionAssembly, mdToken, opCode, methodName); } public MethodBuilder WithConcreteType(Type type) @@ -60,7 +60,7 @@ public MethodBuilder WithConcreteType(Type type) public MethodBuilder WithConcreteTypeName(string typeName) { - var concreteType = _callingAssembly.GetType(typeName); + var concreteType = _resolutionAssembly.GetType(typeName); return this.WithConcreteType(concreteType); } @@ -108,7 +108,7 @@ public MethodBuilder WithReturnType(Type returnType) public TDelegate Build() { var cacheKey = new Key( - callingModule: _callingAssembly.ManifestModule, + callingModule: _resolutionAssembly.ManifestModule, mdToken: _mdToken, callOpCode: _opCode, concreteType: _concreteType, @@ -136,12 +136,12 @@ private TDelegate EmitDelegate() if (_forceMethodDefResolve || (_declaringTypeGenerics == null && _methodGenerics == null)) { _methodBase = - _callingAssembly.ManifestModule.ResolveMethod(metadataToken: _mdToken); + _resolutionAssembly.ManifestModule.ResolveMethod(metadataToken: _mdToken); } else { _methodBase = - _callingAssembly.ManifestModule.ResolveMethod( + _resolutionAssembly.ManifestModule.ResolveMethod( metadataToken: _mdToken, genericTypeArguments: _declaringTypeGenerics, genericMethodArguments: _methodGenerics); @@ -281,7 +281,7 @@ private MethodInfo MakeGenericMethod(MethodInfo methodInfo) private MethodInfo VerifyMethodFromToken(MethodInfo methodInfo) { // Verify baselines to ensure this isn't the wrong method somehow - var detailMessage = $"Unexpected method: {_concreteTypeName}.{_methodName} received for mdToken: {_mdToken} in assembly: {_callingAssembly.FullName}"; + var detailMessage = $"Unexpected method: {_concreteTypeName}.{_methodName} received for mdToken: {_mdToken} in assembly: {_resolutionAssembly.FullName}"; if (!string.Equals(_methodName, methodInfo.Name)) { diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs index 5b02948a9af7..b3833152ccd5 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs @@ -1,9 +1,11 @@ using System; using System.Data; using System.Data.Common; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Datadog.Trace.ClrProfiler.Emit; +using Datadog.Trace.ClrProfiler.Helpers; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Logging; @@ -16,6 +18,11 @@ public static class AdoNetIntegration { private const string IntegrationName = "AdoNet"; private const string Major4 = "4"; + private const string FrameworkAssembly = "System.Data"; + private const string CoreAssembly = "System.Data.Common"; + private const string DbCommand = "System.Data.Common.DbCommand"; + private const string DbDataReader = "System.Data.Common.DbDataReader"; + private const string CommandBehavior = "System.Data.CommandBehavior"; private static readonly ILog Log = LogProvider.GetLogger(typeof(AdoNetIntegration)); @@ -28,37 +35,49 @@ public static class AdoNetIntegration /// The mdToken of the original method call. /// The value returned by the instrumented method. [InterceptMethod( - TargetAssembly = "System.Data", // .NET Framework - TargetType = "System.Data.Common.DbCommand", - TargetSignatureTypes = new[] { "System.Data.Common.DbDataReader", "System.Data.CommandBehavior" }, + TargetAssembly = FrameworkAssembly, + TargetType = DbCommand, + TargetSignatureTypes = new[] { DbDataReader, CommandBehavior }, TargetMinimumVersion = Major4, TargetMaximumVersion = Major4)] [InterceptMethod( - TargetAssembly = "System.Data.Common", // .NET Core - TargetType = "System.Data.Common.DbCommand", - TargetSignatureTypes = new[] { "System.Data.Common.DbDataReader", "System.Data.CommandBehavior" }, + TargetAssembly = CoreAssembly, + TargetType = DbCommand, + TargetSignatureTypes = new[] { DbDataReader, CommandBehavior }, TargetMinimumVersion = Major4, TargetMaximumVersion = Major4)] public static object ExecuteDbDataReader(object @this, int behavior, int opCode, int mdToken) { var command = (DbCommand)@this; var commandBehavior = (CommandBehavior)behavior; + var instanceType = command.GetType(); - var executeReader = Emit.DynamicMethodBuilder> - .GetOrCreateMethodCallDelegate( - typeof(DbCommand), - "ExecuteDbDataReader", - (OpCodeValue)opCode); + Func instrumentedMethod = null; + + try + { + instrumentedMethod = + MethodBuilder> + .Start(instanceType.Assembly, mdToken, opCode, nameof(ExecuteDbDataReader)) + .WithConcreteType(typeof(DbCommand)) + .WithParameters(commandBehavior) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {DbCommand}.{nameof(ExecuteDbDataReader)}(...)", ex); + throw; + } using (var scope = CreateScope(command)) { try { - return executeReader(command, commandBehavior); + return instrumentedMethod(command, commandBehavior); } - catch (Exception ex) when (scope?.Span.SetExceptionForFilter(ex) ?? false) + catch (Exception ex) { - // unreachable code + scope?.Span.SetException(ex); throw; } } @@ -74,50 +93,77 @@ public static object ExecuteDbDataReader(object @this, int behavior, int opCode, /// The mdToken of the original method call. /// The value returned by the instrumented method. [InterceptMethod( - TargetAssembly = "System.Data", // .NET Framework - TargetType = "System.Data.Common.DbCommand", - TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", "System.Data.CommandBehavior", "System.Threading.CancellationToken" }, + TargetAssembly = FrameworkAssembly, + TargetType = DbCommand, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", CommandBehavior, ClrNames.CancellationToken }, TargetMinimumVersion = Major4, TargetMaximumVersion = Major4)] [InterceptMethod( - TargetAssembly = "System.Data.Common", // .NET Core - TargetType = "System.Data.Common.DbCommand", - TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", "System.Data.CommandBehavior", "System.Threading.CancellationToken" }, + TargetAssembly = CoreAssembly, + TargetType = DbCommand, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", CommandBehavior, ClrNames.CancellationToken }, TargetMinimumVersion = Major4, TargetMaximumVersion = Major4)] - public static object ExecuteDbDataReaderAsync(object @this, int behavior, object cancellationTokenSource, int opCode, int mdToken) + public static object ExecuteDbDataReaderAsync( + object @this, + int behavior, + object cancellationTokenSource, + int opCode, + int mdToken) { var tokenSource = cancellationTokenSource as CancellationTokenSource; var cancellationToken = tokenSource?.Token ?? CancellationToken.None; var command = (DbCommand)@this; + var instanceType = command.GetType(); var commandBehavior = (CommandBehavior)behavior; - var callOpCode = (OpCodeValue)opCode; + var callingAssembly = Assembly.GetCallingAssembly(); + var dataReaderType = callingAssembly.GetType(DbDataReader); + + Func instrumentedMethod = null; - return ExecuteDbDataReaderAsyncInternal(command, commandBehavior, cancellationToken, callOpCode); + try + { + instrumentedMethod = + MethodBuilder> + .Start(Assembly.GetCallingAssembly(), mdToken, opCode, nameof(ExecuteDbDataReaderAsync)) + .WithConcreteType(typeof(DbCommand)) + .WithParameters(commandBehavior, cancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {DbCommand}.{nameof(ExecuteDbDataReaderAsync)}(...)", ex); + throw; + } + + return AsyncHelper.InvokeGenericTaskDelegate( + instanceType, + dataReaderType, + nameof(ExecuteDbDataReaderAsyncInternal), + typeof(AdoNetIntegration), + command, + commandBehavior, + cancellationToken, + instrumentedMethod); } - private static async Task ExecuteDbDataReaderAsyncInternal( + private static async Task ExecuteDbDataReaderAsyncInternal( DbCommand command, CommandBehavior behavior, CancellationToken cancellationToken, - OpCodeValue callOpCode) + Func originalMethod) { - var executeReader = Emit.DynamicMethodBuilder>> - .GetOrCreateMethodCallDelegate( - typeof(DbCommand), - "ExecuteDbDataReaderAsync", - callOpCode); - using (var scope = CreateScope(command)) { try { - return await executeReader(command, behavior, cancellationToken).ConfigureAwait(false); + var awaitable = (Task)originalMethod(command, behavior, cancellationToken); + return await awaitable.ConfigureAwait(false); } - catch (Exception ex) when (scope?.Span.SetExceptionForFilter(ex) ?? false) + catch (Exception ex) { - // unreachable code + scope?.Span.SetException(ex); throw; } }