microsoft / microsoft/msix-packaging

[BUG] AOSP Jni singleton caches JNIEnv* and can reuse it on the wrong thread

Open
#715 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Needs-Triage
Dominant language
C
Stars
1.1k
Forks
193
Avg merge
2h 16m
Merged PRs (30d)
2

Description

Project
MSIX SDK

Describe the bug
Jni is a process-wide Meyers singleton (Jni::Instance()). GetEnv() stores JNIEnv* in m_env and, after the first successful lookup, returns that pointer forever.

JNIEnv* is valid only for the thread that obtained it. A later call on another thread skips JavaVM::GetEnv / AttachCurrentThread and uses the first thread’s env. JNI calls and DeleteLocalRef (via JObjectDeleter) then run against the wrong environment.

The destructor calls DetachCurrentThread if m_isThreadAttached is set, but the static singleton is destroyed at process exit on whatever thread runs atexit, which may not be the thread that attached.

To Reproduce
From source inspection of current master; no runtime log attached.

  1. Build the AOSP (-DAOSP=on) configuration.
  2. First JNI use happens on thread A: Jni::Instance()->GetEnv() caches m_env.
  3. Thread B later calls Jni::Instance()->GetEnv() or destroys a unique_ptr that uses JObjectDeleter.
  4. Thread B receives thread A’s JNIEnv* without a GetEnv/Attach on B.

https://github.com/microsoft/msix-packaging/blob/master/src/msix/PAL/Interop/AOSP/JniHelper.hpp

static Jni* Instance()
{
    static Jni jni;
    return &jni;
}

JNIEnv* GetEnv()
{
    if (!m_env)
    {
        int result = g_JavaVM->GetEnv(reinterpret_cast<void**>(&m_env), JNI_VERSION_1_6);
        if (result < 0)
        {
            result = g_JavaVM->AttachCurrentThread(&m_env, nullptr);
            m_isThreadAttached = true;
        }
    }
    return m_env;
}

Expected behavior
Do not cache JNIEnv* on the singleton. Each GetEnv() should call JavaVM::GetEnv for the current thread, and AttachCurrentThread only when that thread is detached. DetachCurrentThread must run on the same thread that attached (for example thread_local with a destructor), not in the process-wide ~Jni().

Screenshots
N/A

Platform
Android / AOSP (src/msix/PAL/Interop/AOSP/JniHelper.hpp, current master)

Additional context
JObjectDeleter always does Jni::Instance()->GetEnv()->DeleteLocalRef(obj), so a local ref created on one thread can be deleted with another thread’s env if the unique_ptr is released there.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/msix/PAL/Interop/AOSP/JniHelper.hpp and inspect Jni::GetEnv(), the singleton lifetime, and JObjectDeleter. Build the AOSP configuration, then verify that each call resolves the current thread's JNIEnv* and that thread attachment is detached on the same thread. Done means no process-wide JNIEnv* cache or destructor-time detach remains.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, cpp
Domain
mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.