Skip to content

feat(agent): implement @Return and @Duration in ClassFileApiBackend#843

Open
jbachorik wants to merge 1 commit into
developfrom
muse/classfileapi-return-duration
Open

feat(agent): implement @Return and @Duration in ClassFileApiBackend#843
jbachorik wants to merge 1 commit into
developfrom
muse/classfileapi-return-duration

Conversation

@jbachorik
Copy link
Copy Markdown
Collaborator

@jbachorik jbachorik commented May 18, 2026

Summary

Fixes #837ClassFileApiBackend (used for Java 26+ class files) previously skipped all handlers containing @Return or @Duration parameters. This PR adds full support for both, matching the behavior of the existing ASM-based backend.

  • @Return: Captures the return value via dup/dup2 (based on TypeKind.slotSize()), stores it to an allocated local slot, and loads it before the INVOKEDYNAMIC probe call. Void methods are silently skipped.
  • @Duration: Captures System.nanoTime() at method entry, computes the delta at each normal return point, and also covers exceptional exits via a finally-block exception handler (matching the existing ASM instrumentation behavior).

Key correctness constraints respected:

  • INVOKEDYNAMIC descriptor used verbatim from om.getTargetDescriptor() — not rebuilt.
  • Local slots allocated via CodeBuilder.allocateLocal() to avoid slot collisions.
  • Pre-validation of all handler arguments before any stack pushes to prevent partial-stack corruption (VerifyError).
  • Store TypeKind passed through to the load site so ISTORE+ALOAD mismatches are impossible even for @AnyType-typed handler parameters.

Note: @TargetInstance and Kind.CALL remain unsupported and are skipped with a DEBUG-level log. Tracking issue: #844 — this must be resolved before this backend is considered fully feature-equivalent to the ASM backend, but is not a merge blocker for this PR.

Test plan

  • supportsVersionAbove69 — ClassFileApiBackend selected for version 70+
  • returnsNullWhenNoProbesMatch — no probes → null
  • entryProbeInjectedIntoMatchingMethod — INVOKEDYNAMIC injected at method entry (JDK 26+)
  • entryProbeNotInjectedWhenMethodNameMismatches — null for non-matching method
  • returnProbeInjectedBeforeReturn — INVOKEDYNAMIC injected before RETURN (JDK 26+)
  • noInjectionForUnsupportedKind — Kind.CALL skipped
  • returnProbeInjectedWithReturnValueInt — int return value captured in descriptor (JDK 26+)
  • returnProbeInjectedWithReturnValueLong — long (2-slot) return value captured (JDK 26+)
  • returnProbeSkippedForVoidMethod — no INVOKEDYNAMIC + ClassLoader load asserts no VerifyError (JDK 26+)
  • durationProbeInjectedOnNormalReturn — long duration parameter in descriptor (JDK 26+)
  • durationProbeInjectedOnExceptionExit — 2 INVOKEDYNAMIC calls (normal + exception handler) (JDK 26+)
  • returnAndDurationSlotsDoNotCollide — at least 2 distinct store slots (JDK 26+)

Tests requiring class file version 70 (Java 26) skip automatically on JDK < 26 via Assumptions.assumeTrue.

🤖 Generated with Claude Code


This change is Reviewable

…ackend

ClassFileApiBackend (used for Java 26+ class files) previously skipped
handlers with @return or @duration parameters. This change adds full support:

- @return: dup/dup2 based on TypeKind.slotSize(), store to an allocated
  local slot, load before INVOKEDYNAMIC. Void methods are silently skipped.
- @duration: System.nanoTime() captured at method entry; delta computed at
  each RETURN point and in a finally-block exception handler (matching the
  existing ASM instrumentation behavior).

Bug fixes applied after chorus review:
- emitProbeCall: pre-validate all handler arguments before pushing any onto
  the stack; an early return mid-loop would leave orphaned stack values and
  cause a VerifyError on class loading.
- emitProbeCall: load the return-value slot using the actual store TypeKind
  from the ReturnInstruction, not TypeKind.fromDescriptor on the handler
  descriptor (which would yield REFERENCE for AnyType→Object handlers,
  mismatching the ISTORE/LSTORE used during capture).
- atStart/accept: bind startLabel in accept() alongside entryTsSlot
  allocation so they are always in sync; an unpairable label in atEnd
  could leave an unclosed try region.
- atEnd: register exceptionCatchAll after binding all three labels; replace
  stream pipeline with a plain for-loop.

Tests cover @return (int, long, void-skip), @duration (normal exit, exception
exit), and combined @return+@duration slot-collision check. Tests that require
class file version 70 parsing (Java 26+) skip automatically on JDK < 26
via Assumptions.assumeTrue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClassFileApiBackend (Java 26+): @Return, @TargetInstance, @Duration handlers not implemented

1 participant