Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b74e22cb authored by Shai Barack's avatar Shai Barack
Browse files

Properly manage JniStringCache lifecycle and clear on unload.

The `JniStringCache` singleton is now initialized using `std::call_once` and a global pointer. A `JNI_OnUnload` function is added to clear the cache and delete the singleton instance, ensuring that global JNI references are released when the library is unloaded.

With this change we successfully pass hwasan:
https://android-build.corp.google.com/builds/abtd/run/L01400030017696946

Bug: 443493398
Flag: EXEMPT BUGFIX
Change-Id: I5474a64ec7146b638094f792c8f2bb47254c0449
parent dc5fa92e
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@
#include <dlfcn.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/JniInvocation.h>
#include "com_android_internal_os_JniStringCache.h"
#include <server_configurable_flags/get_flags.h>
#include <signal.h>
#include <stdio.h>
@@ -109,6 +110,10 @@ extern int register_android_media_ToneGenerator(JNIEnv *env);
extern int register_android_media_audio_common_AidlConversion(JNIEnv* env);
extern int register_android_media_midi(JNIEnv *env);

extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
    JniStringCache::Unload(vm);
}

namespace android {

/*
+25 −4
Original line number Diff line number Diff line
@@ -20,10 +20,12 @@
#include <android_runtime/AndroidRuntime.h>

#include <atomic>
#include <mutex>

namespace android {

namespace {

template <typename TChar>
uint32_t computeHash(const TChar* s, size_t len) {
    uint32_t h = 0;
@@ -65,11 +67,14 @@ bool StringsAreEqual(JNIEnv* env, jstring jstr, const char* chars, size_t len) {
    return result;
}

JniStringCache* gInstance = nullptr;
std::once_flag gInstanceFlag;

} // namespace

JniStringCache& JniStringCache::getInstance() {
    static JniStringCache instance;
    return instance;
    std::call_once(gInstanceFlag, []() { gInstance = new JniStringCache(); });
    return *gInstance;
}

JniStringCache::JniStringCache() : mHits(0), mMisses(0), mEvictions(0), mSkips(0) {}
@@ -261,12 +266,14 @@ size_t JniStringCache::skips() const {
}

void JniStringCache::clear() {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    clear(AndroidRuntime::getJNIEnv());
}

void JniStringCache::clear(JNIEnv* env) {
    if (env == nullptr) {
        DCHECK(false) << "JNIEnv is null, can't clear cache";
        return;
    }

    auto clear_cache = [&](std::atomic<CacheEntry>* cache) {
        for (size_t i = 0; i < kCacheSize; ++i) {
            std::atomic<CacheEntry>& slot = cache[i];
@@ -306,6 +313,20 @@ void JniStringCache::clear() {
    clear_cache(mUtf8Cache);
}

void JniStringCache::Unload(JavaVM* vm) {
    if (gInstance == nullptr) {
        return;
    }

    JNIEnv* env = nullptr;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) == JNI_OK) {
        gInstance->clear(env);
    }

    delete gInstance;
    gInstance = nullptr;
}

static jlong com_android_internal_os_JniStringCache_nativeHits() {
    return static_cast<jlong>(JniStringCache::getInstance().hits());
}
+4 −0
Original line number Diff line number Diff line
@@ -41,6 +41,9 @@ public:
    // Global instance to use in order to maximize cache hits.
    static JniStringCache& getInstance();

    // Clears all global references held by the cache.
    static void Unload(JavaVM* vm);

    JniStringCache();
    ~JniStringCache();

@@ -67,6 +70,7 @@ public:
    // Under concurrent usage, some entries may not be cleared.
    // Use this for instance to trim memory usage if needed.
    void clear();
    void clear(JNIEnv* env);

private:
    struct CacheEntry {