[Debugger] Avoid instantiating state machine attributes#8816
Conversation
Use CustomAttributeData for debugger state-machine resolution so method matching does not execute user attribute constructors while preserving async and iterator behavior. Co-authored-by: Cursor <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR hardens the Debugger’s async/iterator state-machine resolution paths by reading compiler state-machine attributes via CustomAttributeData (metadata-only) instead of instantiating attribute objects, avoiding potential user code execution from attribute constructors during exception replay/method resolution.
Changes:
- Introduces
StateMachineAttributeHelperto detect/read state-machine attributes from metadata without constructing attribute instances. - Updates async kickoff resolution, fully-qualified name resolution, method matching, and IL analysis to use the new helper.
- Adds a regression test ensuring overloaded async method matching does not instantiate method attributes (i.e., does not run attribute constructors).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tracer/test/Datadog.Trace.Tests/Debugger/MethodMatcherTests.cs | Adds regression coverage to ensure method attribute constructors are not executed during state-machine/method matching. |
| tracer/src/Datadog.Trace/Debugger/Helpers/StateMachineAttributeHelper.cs | New helper that extracts state-machine type info from CustomAttributeData (metadata-only). |
| tracer/src/Datadog.Trace/Debugger/Helpers/MethodExtensions.cs | Switches state-machine method resolution to use metadata-only attribute inspection. |
| tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs | Uses metadata-only async state-machine attribute reading to find kickoff methods. |
| tracer/src/Datadog.Trace/Debugger/ExceptionAutoInstrumentation/MethodMatcher.cs | Avoids instantiating attributes when disambiguating overloaded async methods. |
| tracer/src/Datadog.Trace/Debugger/ExceptionAutoInstrumentation/ILAnalyzer.cs | Uses metadata-only async state-machine type lookup when selecting MoveNext for analysis. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8816) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (73ms) : 69, 77
master - mean (73ms) : 69, 76
section Bailout
This PR (8816) - mean (76ms) : 72, 79
master - mean (77ms) : 74, 79
section CallTarget+Inlining+NGEN
This PR (8816) - mean (1,083ms) : 1044, 1122
master - mean (1,079ms) : 1025, 1133
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (114ms) : 107, 120
master - mean (113ms) : 108, 119
section Bailout
This PR (8816) - mean (111ms) : 109, 113
master - mean (112ms) : 108, 116
section CallTarget+Inlining+NGEN
This PR (8816) - mean (776ms) : 752, 800
master - mean (773ms) : 755, 791
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (100ms) : 95, 105
master - mean (100ms) : 95, 105
section Bailout
This PR (8816) - mean (98ms) : 96, 100
master - mean (99ms) : 96, 102
section CallTarget+Inlining+NGEN
This PR (8816) - mean (934ms) : 888, 979
master - mean (936ms) : 897, 975
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (95ms) : 93, 97
master - mean (95ms) : 92, 97
section Bailout
This PR (8816) - mean (96ms) : 94, 98
master - mean (98ms) : 94, 103
section CallTarget+Inlining+NGEN
This PR (8816) - mean (813ms) : 779, 847
master - mean (815ms) : 776, 855
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (204ms) : 198, 210
master - mean (203ms) : 198, 208
section Bailout
This PR (8816) - mean (206ms) : 201, 212
master - mean (207ms) : 201, 213
section CallTarget+Inlining+NGEN
This PR (8816) - mean (1,201ms) : 1153, 1249
master - mean (1,212ms) : 1166, 1257
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (288ms) : 279, 298
master - mean (291ms) : 282, 300
section Bailout
This PR (8816) - mean (289ms) : 283, 295
master - mean (292ms) : 283, 301
section CallTarget+Inlining+NGEN
This PR (8816) - mean (966ms) : 947, 984
master - mean (973ms) : 950, 995
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (282ms) : 274, 289
master - mean (286ms) : 276, 295
section Bailout
This PR (8816) - mean (284ms) : 278, 290
master - mean (288ms) : 283, 292
section CallTarget+Inlining+NGEN
This PR (8816) - mean (1,166ms) : 1129, 1203
master - mean (1,172ms) : 1129, 1214
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8816) - mean (281ms) : 276, 286
master - mean (283ms) : 274, 292
section Bailout
This PR (8816) - mean (284ms) : 277, 291
master - mean (284ms) : 279, 288
section CallTarget+Inlining+NGEN
This PR (8816) - mean (1,047ms) : 1000, 1094
master - mean (1,045ms) : 998, 1092
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-06-22 18:47:13 Comparing candidate commit 2143e74 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 72 metrics, 0 unstable metrics, 59 known flaky benchmarks, 67 flaky benchmarks without significant changes.
|
Co-authored-by: Cursor <[email protected]>
This comment has been minimized.
This comment has been minimized.
## Summary of changes - Add a single debugger safety boundaries guide covering reflection/type-loading risks and customer-code execution policy. - Make the safety guide visible from `AGENTS.md` so future agents and reviewers check it before changing debugger safety-sensitive paths. - Add focused snapshot tests for the current policy around default object getters, selected exception properties, and supported collection enumeration. ## Reason for change Define safety boundaries for debugger code running inside customer processes. Keeping the reflection audit, customer-code execution policy, and agent-facing guidance in one document gives reviewers one place to evaluate the current contract and the representative tests that lock it in. ## Implementation details This branch intentionally does not change capture behavior. The tests document that default object capture reads backing fields without invoking arbitrary getters, exception capture reads selected `System.Exception` properties, and supported collection serialization reads `Count` and enumerates under existing limits. The merged safety guide classifies related debugger reflection paths and defines when code may invoke getters, enumerators, exception overrides, attributes, or `ToString()`. ## Test coverage - `DebuggerSnapshotCreatorTests.Policy_` - `DebuggerSnapshotCreatorTests` Related follow-up PRs: - #8814: static member capture guard - #8815: metadata-only Exception Replay IL call scanning - #8816: metadata-only state-machine attribute checks --------- Co-authored-by: Cursor <[email protected]>
Summary of changes
CustomAttributeDatainstead of constructing attribute instances.MoveNextselection.Reason for change
Debugger state-machine resolution only needs compiler metadata such as
AsyncStateMachineAttribute,IteratorStateMachineAttribute, andAsyncIteratorStateMachineAttribute. Instantiating attributes can execute user attribute constructors, so these resolution paths should stay metadata-only.Implementation details
The helper compares attribute type full names and reads the state-machine type from
CustomAttributeData.ConstructorArguments. It preserves the previous async/iterator behavior, including marking iterator and async-iterator state machines soGetFullyQualifiedName()still appends the+MoveNext()annotation where expected.Test coverage
MethodMatcherTests|ILAnalyzerTests