Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions Datadog.Trace.sln
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datadog.FeatureFlags.OpenFe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.LifetimeManager.TerminationSignals", "tracer\test\test-applications\integrations\Samples.LifetimeManager.TerminationSignals\Samples.LifetimeManager.TerminationSignals.csproj", "{0E3E1069-80FF-99C9-D29F-936D96D5F516}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datadog.AutoInstrumentation.Generator.Core", "tracer\src\Datadog.AutoInstrumentation.Generator.Core\Datadog.AutoInstrumentation.Generator.Core.csproj", "{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1549,6 +1551,10 @@ Global
{0E3E1069-80FF-99C9-D29F-936D96D5F516}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E3E1069-80FF-99C9-D29F-936D96D5F516}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E3E1069-80FF-99C9-D29F-936D96D5F516}.Release|Any CPU.Build.0 = Release|Any CPU
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1801,6 +1807,7 @@ Global
{64258238-DE63-4A0F-A618-DF51735BA22A} = {8CEC2042-F11C-49F5-A674-2355793B600A}
{CFECF8D4-3A46-35A8-7CB1-BA359974A1A9} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB}
{0E3E1069-80FF-99C9-D29F-936D96D5F516} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// <copyright file="AssemblyBrowser.cs" company="Datadog">
// 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.
// </copyright>

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using dnlib.DotNet;

namespace Datadog.AutoInstrumentation.Generator.Core;

/// <summary>
/// Thin wrapper around dnlib for resolving methods from assemblies.
/// Assembly browsing (type listing, etc.) is handled by dotnet-inspect.
/// </summary>
public class AssemblyBrowser : IDisposable
{
private readonly AssemblyDef _assemblyDef;

public AssemblyBrowser(string path)
{
_assemblyDef = AssemblyDef.Load(path);
}

public AssemblyBrowser(Stream stream)
{
_assemblyDef = AssemblyDef.Load(stream);
}

/// <summary>
/// Resolves a method by type full name and method name.
/// Supports overload disambiguation via parameter types or overload index.
/// </summary>
/// <param name="typeFullName">Fully qualified type name (e.g., "MyLib.MyClass")</param>
/// <param name="methodName">Method name</param>
/// <param name="parameterTypes">Optional parameter type full names for overload disambiguation</param>
/// <param name="overloadIndex">Optional 0-based index among matching overloads</param>
/// <returns>The resolved MethodDef, or null if not found</returns>
public MethodDef? ResolveMethod(string typeFullName, string methodName, string[]? parameterTypes = null, int? overloadIndex = null)
{
TypeDef? typeDef = null;

foreach (var module in _assemblyDef.Modules)
{
typeDef = FindType(module, typeFullName);
if (typeDef is not null)
{
break;
}
}

if (typeDef is null)
{
return null;
}

var methods = typeDef.Methods.Where(m => m.Name.String == methodName).ToList();

if (methods.Count == 0)
{
return null;
}

// Apply disambiguation
if (parameterTypes is { Length: > 0 })
{
return methods.FirstOrDefault(m => MatchesParameterTypes(m, parameterTypes));
}

if (overloadIndex.HasValue)
{
if (overloadIndex.Value >= 0 && overloadIndex.Value < methods.Count)
{
return methods[overloadIndex.Value];
}

return null;
}

if (methods.Count == 1)
{
return methods[0];
}

// Multiple overloads with no disambiguation — return null to trigger helpful error
return null;
}

/// <summary>
/// Lists all available overloads for a method, useful for disambiguation.
/// </summary>
public IReadOnlyList<MethodDef> ListOverloads(string typeFullName, string methodName)
{
TypeDef? typeDef = null;

foreach (var module in _assemblyDef.Modules)
{
typeDef = FindType(module, typeFullName);
if (typeDef is not null)
{
break;
}
}

if (typeDef is null)
{
return Array.Empty<MethodDef>();
}

return typeDef.Methods.Where(m => m.Name == methodName).ToList();
}

public void Dispose()
{
// AssemblyDef doesn't implement IDisposable, but we free module contexts
foreach (var module in _assemblyDef.Modules)
{
module?.Dispose();
}
}

private static TypeDef? FindType(ModuleDef module, string typeFullName)
{
// Handle nested types: "Outer+Inner" or "Outer/Inner"
var parts = typeFullName.Split('+', '/');

TypeDef? current = null;
foreach (var part in parts)
{
if (current is null)
{
current = module.Find(part, isReflectionName: false)
?? module.Types.FirstOrDefault(t => t.FullName == part);
}
else
{
current = current.NestedTypes.FirstOrDefault(t => t.Name == part);
}

if (current is null)
{
return null;
}
}

return current;
}

private static bool MatchesParameterTypes(MethodDef method, string[] parameterTypes)
{
var parameters = method.Parameters.Where(p => !p.IsHiddenThisParameter).ToArray();
if (parameters.Length != parameterTypes.Length)
{
return false;
}

for (var i = 0; i < parameters.Length; i++)
{
var paramFullName = parameters[i].Type.FullName;
if (!string.Equals(paramFullName, parameterTypes[i], StringComparison.Ordinal))
{
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net10.0</TargetFrameworks>
<SignAssembly>false</SignAssembly>
<Nullable>enable</Nullable>
<!-- Suppress documentation warnings for internal tooling -->
<NoWarn>$(NoWarn);CS1591;SA1600;SA1601;SA1602;SA1611;SA1615;SA1618</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="dnlib" Version="3.6.0" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Resources\Data\**\*" />
</ItemGroup>

<ItemGroup>
<Compile Remove="Resources\Data\**\*.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="EditorHelper.cs" company="Datadog">
// <copyright file="EditorHelper.cs" company="Datadog">
// 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.
// </copyright>
Expand All @@ -12,7 +12,7 @@
using dnlib.DotNet;
// ReSharper disable InconsistentNaming

namespace Datadog.AutoInstrumentation.Generator;
namespace Datadog.AutoInstrumentation.Generator.Core;

internal static class EditorHelper
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// <copyright file="GenerationConfiguration.cs" company="Datadog">
// 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.
// </copyright>

using System;
using System.Linq;
using System.Threading.Tasks;
using dnlib.DotNet;

namespace Datadog.AutoInstrumentation.Generator.Core;

/// <summary>
/// Configuration for code generation. Plain POCO with all boolean flags
/// that control what gets generated.
/// </summary>
public class GenerationConfiguration
{
// OnMethod handlers
public bool CreateOnMethodBegin { get; set; } = true;

public bool CreateOnMethodEnd { get; set; } = true;

public bool CreateOnAsyncMethodEnd { get; set; }

// Duck typing general
public bool UseDuckCopyStruct { get; set; }

// Duck type: Instance
public bool CreateDucktypeInstance { get; set; }

public bool DucktypeInstanceFields { get; set; }

public bool DucktypeInstanceProperties { get; set; } = true;

public bool DucktypeInstanceMethods { get; set; }

public bool DucktypeInstanceDuckChaining { get; set; }

// Duck type: Arguments
public bool CreateDucktypeArguments { get; set; }

public bool DucktypeArgumentsFields { get; set; }

public bool DucktypeArgumentsProperties { get; set; } = true;

public bool DucktypeArgumentsMethods { get; set; }

public bool DucktypeArgumentsDuckChaining { get; set; }

// Duck type: Return Value
public bool CreateDucktypeReturnValue { get; set; }

public bool DucktypeReturnValueFields { get; set; }

public bool DucktypeReturnValueProperties { get; set; } = true;

public bool DucktypeReturnValueMethods { get; set; }

public bool DucktypeReturnValueDuckChaining { get; set; }

// Duck type: Async Return Value
public bool CreateDucktypeAsyncReturnValue { get; set; }

public bool DucktypeAsyncReturnValueFields { get; set; }

public bool DucktypeAsyncReturnValueProperties { get; set; } = true;

public bool DucktypeAsyncReturnValueMethods { get; set; }

public bool DucktypeAsyncReturnValueDuckChaining { get; set; }

/// <summary>
/// Creates a configuration with smart defaults based on the method being instrumented.
/// Mirrors the GUI auto-detection logic from MainViewModel.Configuration.cs.
/// </summary>
public static GenerationConfiguration CreateForMethod(MethodDef methodDef)
{
var config = new GenerationConfiguration();

var isAsync = methodDef.ReturnType.FullName.StartsWith(typeof(Task).FullName!, StringComparison.Ordinal) ||
methodDef.ReturnType.FullName.StartsWith(typeof(ValueTask).FullName!, StringComparison.Ordinal);

if (isAsync)
{
config.CreateOnMethodEnd = false;
config.CreateDucktypeReturnValue = false;
config.CreateOnAsyncMethodEnd = true;
}
else
{
config.CreateOnMethodEnd = true;
config.CreateOnAsyncMethodEnd = false;
config.CreateDucktypeAsyncReturnValue = false;
}

if (methodDef.IsStatic || (!config.CreateOnMethodBegin && !config.CreateOnMethodEnd && !config.CreateOnAsyncMethodEnd))
{
config.CreateDucktypeInstance = false;
}

return config;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// <copyright file="GenerationResult.cs" company="Datadog">
// 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.
// </copyright>

namespace Datadog.AutoInstrumentation.Generator.Core;

/// <summary>
/// Result of code generation.
/// </summary>
public class GenerationResult
{
public bool Success { get; set; }

public string? ErrorMessage { get; set; }

public string? SourceCode { get; set; }

public string? FileName { get; set; }

public string? Namespace { get; set; }

public InstrumentMethodMetadata? Metadata { get; set; }

public static GenerationResult Error(string message) => new() { Success = false, ErrorMessage = message };
}
Loading
Loading