[EL] DEBUG-4880 Align probe expression compilation with runtime types by dudikeleti · Pull Request #7992 · DataDog/dd-trace-dotnet
## Summary of changes - Reworked `ProbeExpressionEvaluator` to cache compiled expressions **per runtime type combination** (invocation target, return value, and scope members), instead of a single `Lazy<>` instance shared across all invocations. - Introduced `ExpressionCacheKey` to uniquely identify a compilation context using **runtime types** (`Value.GetType()` when available) and member runtime types to support polymorphic calls. - Replaced single cached fields with `ConcurrentDictionary<ExpressionCacheKey, …>` caches for: - Templates (`CompiledExpression<string>[]?`) - Condition (`CompiledExpression<bool>?`) - Metric (`CompiledExpression<double>?`) - Span decorations (compiled decorations array) - Preserved evaluation semantics and error aggregation, while ensuring compiled delegates match the actual runtime types. ## Reason for change The previous implementation compiled expressions once (on first invocation) and reused them for all subsequent calls. This fails in polymorphic scenarios where: - The declared type differs from the runtime type (e.g., generics/base class/interface method invoked on a derived instance). - Parameters/locals are declared as a base type (or `object`) but carry different concrete runtime types across invocations. In these cases, delegates compiled against the first observed type can fail later (typically invalid casts / expression binding mismatches), causing evaluation errors and incorrect probe results. ## Implementation details - Compute a **runtime-aware cache key** on each `Evaluate()` call: - `thisType`: `InvocationTarget.Value?.GetType()` (fallback to declared type, then `typeof(object)`) - `returnType`: `Return.Value?.GetType()` (fallback to declared type) - `members`: runtime types captured from `ScopeMember.Value?.GetType()` (fallback to declared `ScopeMember.Type`) - Store compiled artifacts in thread-safe caches: - `ConcurrentDictionary<ExpressionCacheKey, ...>` - On cache miss: compile, then `TryAdd` - Added `ExpressionCacheKey` struct: - Precomputes hash over `thisType`, `returnType`, and member runtime types. - Implements equality by comparing `ThisType`, `ReturnType`, and the captured member type sequence (hash used as a fast-path). - Added debug logging for cache misses (type + hash + cache size) to help diagnose compilation churn and verify caching behavior. ## Test coverage - Existing `DebuggerExpressionLanguageTests` remain supported via `CompiledTemplates` / `CompiledCondition` accessors (now returning the first cached entry). - Snapshot Exploration Test ## Dependencies - Depends on: #7991, #7992, #7993 - Merge order: #7991 → #7992 → #7993 → (this PR) ## Other details - Caches may grow with many unique type combinations; however this is required for correctness under polymorphic workloads. If needed later, we can add eviction/size caps. - This PR is part of an effort to make the Snapshot Exploration Test run successfully end-to-end.