From f6fd6f80e835196a5ea60c5c06c4f79bc6557651 Mon Sep 17 00:00:00 2001 From: Dudi Keleti Date: Wed, 8 Feb 2023 03:02:08 +0200 Subject: [PATCH] [Dynamic Instrumentation] Display interface properties in snapshot (#3761) * Display interface properties in snapshot * Fix PR's comments * Add AsyncInterfaceProperties to exclude from unoptimize test --- .../Debugger/Expressions/ProbeProcessor.cs | 6 +- .../AsyncLineDebuggerInvoker.cs | 2 +- .../AsyncMethodDebuggerInvoker.cs | 8 +- .../Instrumentation/LineDebuggerInvoker.cs | 4 +- .../Instrumentation/MethodDebuggerInvoker.cs | 10 +- .../Snapshots/DebuggerSnapshotCreator.cs | 6 +- ...ests.AsyncInterfaceProperties.verified.txt | 180 ++++++++++++ ...robeTests.InterfaceProperties.verified.txt | 264 ++++++++++++++++++ ...ests.AsyncInterfaceProperties.verified.txt | 26 ++ ...robeTests.InterfaceProperties.verified.txt | 26 ++ .../ProbesTests.cs | 9 +- .../Shared/InterfaceTesting.cs | 58 ++++ .../SmokeTests/AsyncInterfaceProperties.cs | 42 +++ .../SmokeTests/InterfaceProperties.cs | 49 ++++ 14 files changed, 669 insertions(+), 21 deletions(-) create mode 100644 tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/snapshots/ProbeTests.AsyncInterfaceProperties.verified.txt create mode 100644 tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/snapshots/ProbeTests.InterfaceProperties.verified.txt create mode 100644 tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/statuses/ProbeTests.AsyncInterfaceProperties.verified.txt create mode 100644 tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/statuses/ProbeTests.InterfaceProperties.verified.txt create mode 100644 tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/Shared/InterfaceTesting.cs create mode 100644 tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/AsyncInterfaceProperties.cs create mode 100644 tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/InterfaceProperties.cs diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs index 00092c456c3d..ae527ce9ff4b 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs @@ -271,7 +271,8 @@ private void AddAsyncMethodArguments(DebuggerSnapshotCreator snapshotCreator, continue; } - snapshotCreator.AddScopeMember(arg.Name, arg.FieldType, arg.GetValue(asyncCaptureInfo.MoveNextInvocationTarget), ScopeMemberKind.Argument); + var argValue = arg.GetValue(asyncCaptureInfo.MoveNextInvocationTarget); + snapshotCreator.AddScopeMember(arg.Name, argValue?.GetType() ?? arg.FieldType, argValue, ScopeMemberKind.Argument); } } @@ -286,7 +287,8 @@ private void AddAsyncMethodLocals(DebuggerSnapshotCreator snapshotCreator, re continue; } - snapshotCreator.AddScopeMember(local.SanitizedName, local.Field.FieldType, local.Field.GetValue(asyncCaptureInfo.MoveNextInvocationTarget), ScopeMemberKind.Local); + var localValue = local.Field.GetValue(asyncCaptureInfo.MoveNextInvocationTarget); + snapshotCreator.AddScopeMember(local.SanitizedName, localValue?.GetType() ?? local.Field.FieldType, localValue, ScopeMemberKind.Local); } } diff --git a/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncLineDebuggerInvoker.cs b/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncLineDebuggerInvoker.cs index fa88b02bd719..23badaa4684f 100644 --- a/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncLineDebuggerInvoker.cs +++ b/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncLineDebuggerInvoker.cs @@ -53,7 +53,7 @@ public static void LogLocal(ref TLocal local, int index, ref AsyncLineDe return; } - var captureInfo = new CaptureInfo(value: local, type: typeof(TLocal), methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); + var captureInfo = new CaptureInfo(value: local, methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { diff --git a/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs b/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs index a70dbefda2fd..cf1dfe2e80ec 100644 --- a/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs +++ b/tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs @@ -163,7 +163,7 @@ public static void LogLocal(ref TLocal local, int index, ref AsyncMethod return; } - var captureInfo = new CaptureInfo(value: local, type: typeof(TLocal), methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); + var captureInfo = new CaptureInfo(value: local, methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); if (!asyncState.ProbeData.Processor.Process(ref captureInfo, asyncState.SnapshotCreator)) { asyncState.IsActive = false; @@ -192,7 +192,7 @@ public static DebuggerReturn EndMethod_StartMarker(TTarget instance, Ex } var asyncCaptureInfo = new AsyncCaptureInfo(asyncState.MoveNextInvocationTarget, asyncState.KickoffInvocationTarget, asyncState.MethodMetadataInfo.KickoffInvocationTargetType, hoistedLocals: asyncState.MethodMetadataInfo.AsyncMethodHoistedLocals, hoistedArgs: asyncState.MethodMetadataInfo.AsyncMethodHoistedArguments); - var capture = new CaptureInfo(value: exception, methodState: MethodState.ExitStartAsync, type: exception?.GetType(), asyncCaptureInfo: asyncCaptureInfo, memberKind: ScopeMemberKind.Exception); + var capture = new CaptureInfo(value: exception, methodState: MethodState.ExitStartAsync, asyncCaptureInfo: asyncCaptureInfo, memberKind: ScopeMemberKind.Exception); if (!asyncState.ProbeData.Processor.Process(ref capture, asyncState.SnapshotCreator)) { @@ -226,7 +226,7 @@ public static DebuggerReturn EndMethod_StartMarker(TT var asyncCaptureInfo = new AsyncCaptureInfo(asyncState.MoveNextInvocationTarget, asyncState.KickoffInvocationTarget, asyncState.MethodMetadataInfo.KickoffInvocationTargetType, hoistedLocals: asyncState.MethodMetadataInfo.AsyncMethodHoistedLocals, hoistedArgs: asyncState.MethodMetadataInfo.AsyncMethodHoistedArguments); if (exception != null) { - var captureInfo = new CaptureInfo(value: exception, type: exception.GetType(), methodState: MethodState.ExitStartAsync, memberKind: ScopeMemberKind.Exception, asyncCaptureInfo: asyncCaptureInfo); + var captureInfo = new CaptureInfo(value: exception, methodState: MethodState.ExitStartAsync, memberKind: ScopeMemberKind.Exception, asyncCaptureInfo: asyncCaptureInfo); if (!asyncState.ProbeData.Processor.Process(ref captureInfo, asyncState.SnapshotCreator)) { asyncState.IsActive = false; @@ -234,7 +234,7 @@ public static DebuggerReturn EndMethod_StartMarker(TT } else if (returnValue != null) { - var captureInfo = new CaptureInfo(value: returnValue, name: "@return", type: typeof(TReturn), methodState: MethodState.ExitStartAsync, memberKind: ScopeMemberKind.Return, asyncCaptureInfo: asyncCaptureInfo); + var captureInfo = new CaptureInfo(value: returnValue, name: "@return", methodState: MethodState.ExitStartAsync, memberKind: ScopeMemberKind.Return, asyncCaptureInfo: asyncCaptureInfo); if (!asyncState.ProbeData.Processor.Process(ref captureInfo, asyncState.SnapshotCreator)) { asyncState.IsActive = false; diff --git a/tracer/src/Datadog.Trace/Debugger/Instrumentation/LineDebuggerInvoker.cs b/tracer/src/Datadog.Trace/Debugger/Instrumentation/LineDebuggerInvoker.cs index 5b827050ee9e..f35c28f7e224 100644 --- a/tracer/src/Datadog.Trace/Debugger/Instrumentation/LineDebuggerInvoker.cs +++ b/tracer/src/Datadog.Trace/Debugger/Instrumentation/LineDebuggerInvoker.cs @@ -47,7 +47,7 @@ public static void LogArg(ref TArg arg, int index, ref LineDebuggerState s } var paramName = state.MethodMetadataInfo.ParameterNames[index]; - var captureInfo = new CaptureInfo(value: arg, type: typeof(TArg), methodState: MethodState.LogArg, name: paramName, memberKind: ScopeMemberKind.Argument); + var captureInfo = new CaptureInfo(value: arg, methodState: MethodState.LogArg, name: paramName, memberKind: ScopeMemberKind.Argument); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { @@ -85,7 +85,7 @@ public static void LogLocal(ref TLocal local, int index, ref LineDebugge return; } - var captureInfo = new CaptureInfo(value: local, type: typeof(TLocal), methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); + var captureInfo = new CaptureInfo(value: local, methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { diff --git a/tracer/src/Datadog.Trace/Debugger/Instrumentation/MethodDebuggerInvoker.cs b/tracer/src/Datadog.Trace/Debugger/Instrumentation/MethodDebuggerInvoker.cs index 9bc380d3a0d0..267cae860ed9 100644 --- a/tracer/src/Datadog.Trace/Debugger/Instrumentation/MethodDebuggerInvoker.cs +++ b/tracer/src/Datadog.Trace/Debugger/Instrumentation/MethodDebuggerInvoker.cs @@ -109,7 +109,7 @@ public static void LogArg(ref TArg arg, int index, ref MethodDebuggerState } var paramName = state.MethodMetadataInfo.ParameterNames[index]; - var captureInfo = new CaptureInfo(value: arg, type: typeof(TArg), methodState: MethodState.LogArg, name: paramName, memberKind: ScopeMemberKind.Argument); + var captureInfo = new CaptureInfo(value: arg, methodState: MethodState.LogArg, name: paramName, memberKind: ScopeMemberKind.Argument); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { @@ -140,7 +140,7 @@ public static void LogLocal(ref TLocal local, int index, ref MethodDebug return; } - var captureInfo = new CaptureInfo(value: local, type: typeof(TLocal), methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); + var captureInfo = new CaptureInfo(value: local, methodState: MethodState.LogLocal, name: localName, memberKind: ScopeMemberKind.Local); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { @@ -168,7 +168,7 @@ public static DebuggerReturn EndMethod_StartMarker(TTarget instance, Ex state.MethodPhase = EvaluateAt.Exit; - var captureInfo = new CaptureInfo(value: exception, type: exception?.GetType(), invocationTargetType: state.MethodMetadataInfo.DeclaringType, methodState: MethodState.ExitStart, memberKind: ScopeMemberKind.Exception, localsCount: state.MethodMetadataInfo.LocalVariableNames.Length, argumentsCount: state.MethodMetadataInfo.ParameterNames.Length); + var captureInfo = new CaptureInfo(value: exception, invocationTargetType: state.MethodMetadataInfo.DeclaringType, methodState: MethodState.ExitStart, memberKind: ScopeMemberKind.Exception, localsCount: state.MethodMetadataInfo.LocalVariableNames.Length, argumentsCount: state.MethodMetadataInfo.ParameterNames.Length); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { @@ -200,7 +200,7 @@ public static DebuggerReturn EndMethod_StartMarker(TT if (exception != null) { - var captureInfo = new CaptureInfo(value: exception, type: exception.GetType(), invocationTargetType: state.MethodMetadataInfo.DeclaringType, methodState: MethodState.ExitStart, memberKind: ScopeMemberKind.Exception, localsCount: state.MethodMetadataInfo.LocalVariableNames.Length, argumentsCount: state.MethodMetadataInfo.ParameterNames.Length); + var captureInfo = new CaptureInfo(value: exception, invocationTargetType: state.MethodMetadataInfo.DeclaringType, methodState: MethodState.ExitStart, memberKind: ScopeMemberKind.Exception, localsCount: state.MethodMetadataInfo.LocalVariableNames.Length, argumentsCount: state.MethodMetadataInfo.ParameterNames.Length); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { state.IsActive = false; @@ -208,7 +208,7 @@ public static DebuggerReturn EndMethod_StartMarker(TT } else { - var captureInfo = new CaptureInfo(value: returnValue, name: "@return", type: typeof(TReturn), invocationTargetType: state.MethodMetadataInfo.DeclaringType, methodState: MethodState.ExitStart, memberKind: ScopeMemberKind.Return, localsCount: state.MethodMetadataInfo.LocalVariableNames.Length, argumentsCount: state.MethodMetadataInfo.ParameterNames.Length); + var captureInfo = new CaptureInfo(value: returnValue, name: "@return", invocationTargetType: state.MethodMetadataInfo.DeclaringType, methodState: MethodState.ExitStart, memberKind: ScopeMemberKind.Return, localsCount: state.MethodMetadataInfo.LocalVariableNames.Length, argumentsCount: state.MethodMetadataInfo.ParameterNames.Length); if (!state.ProbeData.Processor.Process(ref captureInfo, state.SnapshotCreator)) { state.IsActive = false; diff --git a/tracer/src/Datadog.Trace/Debugger/Snapshots/DebuggerSnapshotCreator.cs b/tracer/src/Datadog.Trace/Debugger/Snapshots/DebuggerSnapshotCreator.cs index 643adbf76475..1a3bbe2bee6e 100644 --- a/tracer/src/Datadog.Trace/Debugger/Snapshots/DebuggerSnapshotCreator.cs +++ b/tracer/src/Datadog.Trace/Debugger/Snapshots/DebuggerSnapshotCreator.cs @@ -402,7 +402,7 @@ private void ExitMethodStart(ref CaptureInfo + { + public T Value { get; set; } + } + + public interface IInterface + { +#if NETCOREAPP3_0_OR_GREATER + // we should not see this property in the snapshot + public string CallToString => DateTime.Now.ToString(CultureInfo.CurrentCulture); +#endif + public string DoNotShowMe { get; set; } + public string ShowMe { get; set; } + } + + internal class Class : IInterface + { + internal string Field; + + public Class() + { + Field = "I'm a class field"; + } + + private string _privateField; + public string DoNotShowMe + { + get + { + _privateField = "This string should never be visible"; + return $"Do not show me!: {_privateField}"; + } + set + { + _privateField = value; + } + } + + public string ShowMe { get; set; } + } + + internal class Class : IGenericInterface where T : class + { + internal T GenericValue; + + public Class() + { + GenericValue = "I'm a generic field" as T; + } + + public T Value { get; set; } + } +} diff --git a/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/AsyncInterfaceProperties.cs b/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/AsyncInterfaceProperties.cs new file mode 100644 index 000000000000..b655e4122452 --- /dev/null +++ b/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/AsyncInterfaceProperties.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Samples.Probes.TestRuns.Shared; + +namespace Samples.Probes.TestRuns.SmokeTests +{ + [LineProbeTestData(lineNumber: 34)] + public class AsyncInterfaceProperties : IAsyncRun + { + [MethodImpl(MethodImplOptions.NoInlining)] + public async Task RunAsync() + { + var implementInterface = new Class { DoNotShowMe = "bla bla", ShowMe = "Show Me!" }; + await Method(implementInterface); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [MethodProbeTestData] + public async Task Method(IInterface parameter) + { + Console.WriteLine($"{parameter.ShowMe}, {parameter.DoNotShowMe}"); + + await Task.Delay(20); + IInterface iInterface = new Class { ShowMe = string.Empty }; + Console.WriteLine(iInterface.ShowMe); + await Task.Yield(); + + if (Check(iInterface)) + { + return iInterface.DoNotShowMe; + } + + return parameter.ShowMe; + } + + private bool Check(IInterface iInterface) + { + return iInterface.ShowMe.Length == ToString()!.Length; + } + } +} diff --git a/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/InterfaceProperties.cs b/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/InterfaceProperties.cs new file mode 100644 index 000000000000..b5ae9ba98edf --- /dev/null +++ b/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/InterfaceProperties.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.CompilerServices; +using Samples.Probes.TestRuns.Shared; + +namespace Samples.Probes.TestRuns.SmokeTests +{ + [LineProbeTestData(lineNumber: 36)] + internal class InterfaceProperties : IRun + { + [MethodImpl(MethodImplOptions.NoInlining)] + public void Run() + { + var implementGenericInterface = new Class { Value = "T Value" }; + var implementInterface = new Class { DoNotShowMe = "bla bla", ShowMe = "Show Me!" }; + Method(implementGenericInterface, implementInterface); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [MethodProbeTestData] + public T Method(IGenericInterface parameter1, IInterface parameter2) where T : class + { + Console.WriteLine($"{parameter1.Value}"); + Console.WriteLine($"{parameter2.ShowMe}, {parameter2.DoNotShowMe}"); + + IInterface iInterface = new Class { ShowMe = string.Empty}; + IGenericInterface iGenericInterface = (IGenericInterface)new Class { GenericValue = "", Value = "Value"}; + + Console.WriteLine($"{iInterface.ShowMe}"); + Console.WriteLine($"{iGenericInterface.Value}"); + + if (Check(iInterface) && Check(iGenericInterface)) + { + return parameter1.Value; + } + + return ((Class)parameter1).GenericValue; + } + + private bool Check(IInterface iInterface) + { + return iInterface.ShowMe.Length == ToString()!.Length; + } + + private bool Check(IGenericInterface iInterface) + { + return iInterface.Value.ToString()?.Length == ToString()!.Length; + } + } +}