Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invokeSuspend edge case fix #2228

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

invokeSuspend edge case fix #2228

wants to merge 16 commits into from

Conversation

kanderson250
Copy link
Contributor

@kanderson250 kanderson250 commented Jan 30, 2025

Resolves #2201

Description

This PR addresses a bug we found when attempting to weave invokeSuspend in Kotlin Coroutines.

The weaver replaces RETURN instructions with GOTO instructions. This replacement can be unsafe if the operand stack prior to the RETURN instruction is not tidied up (ie, has more operands than needed to perform the return). This issue was previously unknown to us, because most compilers already clear their return stacks. However, the Kotlin compiler intentionally adds extra operands to Coroutines methods (for debugging purposes) and doesn't clean them up.

The purpose of this PR is to check the offending method (invokeSuspend) and tidy up its return stacks before the return instructions are replaced with gotos.

Structure

Here is how a method will be processed with this fix:

  1. The method is intercepted in getInliner of MethodCallInlinerAdapter. At this point, the method's bytecode has not been modified.
  2. If the method’s name is invokeSuspend, it is visited with a ClearReturnAdapter.
  3. The adapter calls ReturnInsnProcessor.clearReturnStacks(), which :
    • Computes the operand stacks for the entire method, locates the return instructions, and stores their stack sizes
    • Adds a STORE - n-many POPs - LOAD sequence to the instructions list, where n is the number of pops needed to tidy the stack. To perform the STORE and LOAD, it creates a new local variable slot.
  4. Returns the method to the normal weaver execution flow.

Issues

One issue that I couldn't figure out how to address is how to handle failures of the frame analyzer (will leave a comment on the code). Unfortunately, the weaver can't log anything, and propagating an error upwards is also probably not feasible. That means it is currently a silent failure.

Another issue we should take seriously whenever bytecode is being modified is thread safety. I have left several comments in the code about this.

Testing

New tests can be found in ReturnInsnProcessorTest and WeaveCoroutineTest. A test dependency on coroutines has been added to the weaver, because coroutines are the only known way to get the compiler to make the weird bytecode.

Other

Some utility methods are added to WeaveUtils, which I developed during the debugging of this fix and think would be helpful to keep for the future.

Feature Flag

I added a non-public system property to disable this feature:

-Dnewrelic.config.class_transformer.clear_return_stacks=false

Setting this property to false will allow the AIOOB error through as before.

try {
stacks = getReturnStacks(owner, mn);
} catch (AnalyzerException e) {
//Something went wrong calculating return stacks, leave the method unmodified.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a silent failure

@codecov-commenter
Copy link

codecov-commenter commented Jan 31, 2025

Codecov Report

Attention: Patch coverage is 82.27848% with 14 lines in your changes missing coverage. Please review.

Project coverage is 70.60%. Comparing base (4431da3) to head (b39c17d).
Report is 145 commits behind head on main.

Files with missing lines Patch % Lines
...main/java/com/newrelic/weave/utils/WeaveUtils.java 55.55% 8 Missing ⚠️
.../com/newrelic/weave/utils/ReturnInsnProcessor.java 87.80% 2 Missing and 3 partials ⚠️
...a/com/newrelic/weave/MethodCallInlinerAdapter.java 95.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2228      +/-   ##
============================================
- Coverage     70.74%   70.60%   -0.14%     
+ Complexity    10006     9997       -9     
============================================
  Files           842      843       +1     
  Lines         40346    40452     +106     
  Branches       6115     6130      +15     
============================================
+ Hits          28542    28563      +21     
- Misses         9054     9111      +57     
- Partials       2750     2778      +28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

Handle invokeSuspend edge case in the Weaver
3 participants