DI: reject method probes on Kernel#lambda#5954
Conversation
The method-probe wrapper invokes the original method via super(&block).
On Ruby 3.3+ the original Kernel#lambda raises ArgumentError when reached
this way with a non-literal block, so a probe on Kernel#lambda would break
every lambda { ... } call site that passes a captured block. Probing lambda
creation has no legitimate customer use case.
Reject such probes at hook time with Error::ProbeTargetForbidden, the same
error 5907 uses for Datadog-namespace targets. The check is textual on the
probe's declared type name and method name and runs before class resolution,
tolerating the ::Kernel and Object::Kernel alias forms that const_get
resolves to the top-level Kernel module.
Companion to #5560.
Typing analysisNote: Ignored files are excluded from the next sections.
|
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: fae2565 | Docs | Datadog PR Page | Give us feedback! |
BenchmarksBenchmark execution time: 2026-06-25 19:10:12 Comparing candidate commit fae2565 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 48 metrics, 1 unstable metrics.
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0e9f24869
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR updates the Dynamic Instrumentation (DI) method-probe hook path to proactively reject probes targeting Kernel#lambda, since wrapping Kernel#lambda is not safe on Ruby 3.3+ (can raise ArgumentError when reached via super(&block) from a wrapper). This prevents silently breaking lambda { ... } call sites and surfaces an immediate ERROR status for the probe.
Changes:
- Add an early
Instrumenter#hook_methodguard that raisesError::ProbeTargetForbiddenfor probes targetingKernel#lambda(including::Kernel/Object::Kernelalias forms). - Introduce a
KERNEL_TYPE_NAMEmatcher +kernel_type_name?helper for textual detection of the top-levelKernelmodule name. - Add RSpec coverage for the forbidden target, including alias forms and non-matching cases.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
lib/datadog/di/instrumenter.rb |
Rejects Kernel#lambda method probes at hook time using a new kernel_type_name? helper. |
spec/datadog/di/instrumenter_spec.rb |
Adds specs ensuring Kernel#lambda probes (and alias forms) are rejected with ProbeTargetForbidden. |
sig/datadog/di/instrumenter.rbs |
Updates RBS to include KERNEL_TYPE_NAME and kernel_type_name?. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address review comment from Strech: the `shared_examples 'rejects the probe'` block wrapped a single `it` and was invoked five times, adding indirection without saving anything. Replace it with a plain `.each` loop over the alias forms, matching how other DI specs parameterize cases (serializer_spec, utils_spec, redactor_spec). No behavior change — the same five alias forms are still asserted to raise ProbeTargetForbidden. - spec/datadog/di/instrumenter_spec.rb: shared_examples -> .each loop
Address Codex review comment (endorsed by Strech): the previous guard
matched only textual "Kernel" aliases, but hook_method installs probes on
inherited instance methods too. Every class inherits Kernel#lambda, so a
probe with type_name "Object" (or "String", or any class that does not
override lambda) bypassed the textual check, prepended the wrapper ahead
of the inherited Kernel#lambda, and reached the same super(&block) path.
On Ruby 3.3+ that path raises ArgumentError for every lambda { ... } call
site passing a captured block.
Resolve the target method's owner instead of matching the type name:
reject when method_name == "lambda" and the owner is Kernel. This covers
inherited targets and leaves classes that define their own lambda alone.
Remove the now-dead KERNEL_TYPE_NAME constant and kernel_type_name?
method (and their RBS signatures).
Verified on Ruby 3.2.3:
- Object/String#lambda probes now raise ProbeTargetForbidden (fail-before:
nothing was raised; pass-after: rejected).
- A class overriding lambda is not rejected and the user method runs.
- Full instrumenter_spec.rb: 105 examples, 0 failures, 8 pending.
- standardrb and steep clean on the changed files.
The Ruby 3.3+ ArgumentError itself was not reproduced locally (Ruby
3.2.3); the rejection is version-independent.
- lib/datadog/di/instrumenter.rb: owner-based rejection; drop textual check
- sig/datadog/di/instrumenter.rbs: drop KERNEL_TYPE_NAME / kernel_type_name?
- spec/datadog/di/instrumenter_spec.rb: add Object/String + override cases
|
Indeed it was — |
|
Nice |
Conflict in lib/datadog/di/instrumenter.rb resolved by keeping the branch's refactored hook_method (RubyVersion split, extracted run_method_probe, re-entrancy guard, DI.invoke_proc) and folding in master's changes since the merge-base: - require_relative 'fatal_exceptions' and Datadog::DI.reraise_if_fatal in all catch-all rescues (run_method_probe and line_trace_point_callback). - The #5954 Kernel#lambda probe rejection, via target_method&.owner == ::Kernel. Kernel#lambda probing is prohibited, so the branch's stdlib integration test that installed a Kernel#lambda probe was removed; master's rejection tests in instrumenter_spec.rb cover the prohibition.

What does this PR do?
Rejects method probes that target
Kernel#lambda. A probe whose targettype resolves to the top-level
Kernelmodule and whose method islambdanow raises
Error::ProbeTargetForbiddenat hook time instead of beinginstalled.
Companion to #5560.
Motivation
The method-probe wrapper invokes the original method via
super(&block).On Ruby 3.3+ the original
Kernel#lambdaraisesArgumentErrorwhen it isreached this way with a non-literal block, so a probe on
Kernel#lambdawould break every
lambda { ... }call site that passes a captured block.This is the customer-facing limitation documented in #5560 ("On Ruby 3.3+,
method probes on
Kernel#lambdamay raiseArgumentErrorwhen the probedcall site passes a non-literal block").
There is no legitimate customer use case for probing lambda creation, and
the failure mode is silent and pervasive (every block-passing lambda call
in the process). Rejecting at hook time surfaces an immediate
ERRORstatus back to the backend so the user sees the problem in the Live
Debugger UI rather than wondering why their application started raising
ArgumentError.This mirrors #5907 (reject probes on the Datadog namespace): both refuse a
class of method probes that cannot work, using the same
Error::ProbeTargetForbidden. It is orthogonal to #5560 — that PR makesstdlib probes safe in general; this PR refuses the one stdlib target that
remains broken at a language layer below the tracer's control. Either can
land first.
Change log entry
Yes. Dynamic Instrumentation: reject method probes that target
Kernel#lambda, which cannot be instrumented safely.Implementation
Instrumenter#hook_methodresolves the target class, then rejects theprobe when
probe.method_name == "lambda"and the resolved method's owneris
Kernel. Because every class inheritsKernel#lambda, this covers theKernelmodule named directly (including the::Kernel,Object::Kernel,and chained
Object::Object::Kernelalias forms thatObject.const_getresolves to the same module) as well as inherited targets such as
Object#lambdaandString#lambda— a probe naming any type that doesnot override
lambdaresolves toKernel#lambdaand is rejected. A typethat defines its own
lambdahas a different method owner and fallsthrough to normal class resolution as an ordinary user method.
Owner resolution replaces an earlier textual
Kernelname match, whichmissed inherited targets: a probe on
Object#lambdanamed a type whosename was not
Kernel, so it bypassed the textual check and reached thesame broken
super(&block)path.The error reuses
Error::ProbeTargetForbidden(added in #5907) and flowsthrough
ProbeManager#add_probevia the existingrescue => excbranch —it is recorded as a failed probe and an
ERRORstatus is sent to thebackend without any further changes to the manager or notifier.
Line probes are unaffected: they install via TracePoint and do not carry a
type_name/method_name, so the rejection branch is unreachable for them.How to test the change?
105 examples, 0 failures, 8 pending (the same 8 Ruby-2-only pending tests as
on master).
The new specs live under
describe '.hook_method'→context 'when targeting Kernel#lambda'and cover:'Kernel','::Kernel','Object::Kernel','::Object::Kernel','Object::Object::Kernel'with methodlambda— the Kernel module nameddirectly and via aliases, all rejected with
ProbeTargetForbidden'Object'and'String'with methodlambda— inheritedKernel#lambdatargets, also rejected withProbeTargetForbidden'Kernel'with a non-lambdamethod — not rejected (prepend stubbed sothe example does not install onto the real
Kernel)'KernelLike'with methodlambda— not defined here; falls through toDITargetNotDefinedas expectedlambda— not rejected; the user-definedmethod is instrumented and still returns its value