Skip to content

[native] Fix a JNI reference leak and drop new[] from jstring_array_wrapper - #12568

Open
simonrozsival wants to merge 1 commit into
dev/simonrozsival/clr-drop-std-semaphorefrom
dev/simonrozsival/fix-jstring-array-wrapper
Open

[native] Fix a JNI reference leak and drop new[] from jstring_array_wrapper#12568
simonrozsival wants to merge 1 commit into
dev/simonrozsival/clr-drop-std-semaphorefrom
dev/simonrozsival/fix-jstring-array-wrapper

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Part of the drop-libc++ effort. Stacked on #12560.

Split out of #12560, where it had been sitting alongside the DSO loader changes despite being unrelated to them.

jstring_array_wrapper had two problems.

A JNI local reference leak

jstring_wrapper::release () bailed out early when it had no UTF chars to release:

if (cstr == nullptr) {
    return;
}

But jstring_array_wrapper::operator[] fetches the array element's reference eagerly, while cstr is only populated on the first get_cstr () call. So any element that was indexed but never read kept its local reference until the frame was popped. The reference and the UTF chars have independent lifetimes, so they are now released independently.

new[] / delete[]

The wrapper allocated its elements with new jstring_wrapper[len], which is where _Znam and _ZdaPv in host.cc.o came from. It now uses malloc() with explicit placement construction and destruction.

jstring_wrapper is not an implicit-lifetime type — it has a user-provided destructor — so malloc() alone cannot begin its lifetime; placement new is required. Its default constructor is private, with jstring_array_wrapper as a friend, which is what makes that legal here.

Results

before after
host.cc.o refs 11 9
CoreCLR total refs 23 21

All three runtime lanes build clean.

…rapper

`jstring_array_wrapper::operator[]` fetches the element's JNI reference on
first access, but the UTF characters behind it are only fetched later, when
something calls `get_cstr ()`. `jstring_wrapper::release ()` bailed out early
whenever `cstr` was null, so an element that was indexed but never read kept
its local reference until control returned to Java. Release the characters and
the reference independently instead.

The overflow storage used `new jstring_wrapper[]`/`delete[]`, which is where
`operator new[]` and `operator delete[]` entered `host.cc`. Allocate the array
with `malloc ()` and run the constructors and destructors explicitly. Placement
new is a compile-time construct, so it does not pull anything in from libc++.

This removes `_Znam` and `_ZdaPv` from `host.cc`, taking the CoreCLR libc++
reference count from 23 down to 21.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Copilot AI lite review requested due to automatic review settings August 28, 2026 12:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

Review tier: Lite
Findings: 1 High severity

New issues introduced by this change (1)
Severity Finding
High severity src/​native/​common/​include/​runtime-base/​jni-wrappers.hh — 🤖 ❌ error Native memorylen * sizeof (jstring_wrapper) can overflow on 32-bit ABIs (since…
What changed in this PR

This PR updates the native JNI wrapper utilities to (1) correctly release JNI local references even when UTF chars were never requested, and (2) remove new[]/delete[] usage from jstring_array_wrapper to help reduce C++ runtime symbol dependencies as part of the “drop-libc++” effort.

Changes:

  • Fix jstring_wrapper::release() so it always releases the JNI reference, independently of whether UTF chars were fetched.
  • Replace new[]/delete[] allocation in jstring_array_wrapper with malloc() + placement-new construction + explicit destruction.
  • Add <new> include to support placement new.
File Description
src/​native/​common/​include/​runtime-base/​jni-wrappers.hh Fixes JNI local ref lifetime handling and replaces new[] with malloc() + placement new to reduce C++ runtime dependencies.

Comment on lines +170 to 174
wrappers = static_cast<jstring_wrapper*> (std::malloc (len * sizeof (jstring_wrapper)));
abort_unless (wrappers != nullptr, "Failed to allocate the JNI string array wrapper");
for (size_t i = 0; i < len; i++) {
new (&wrappers [i]) jstring_wrapper ();
}
@simonrozsival simonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drop-libcpp Work to remove the libc++ dependency from Android NativeAOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants