/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Utils.h" #include "Types.h" #include #include #include "mozilla/Assertions.h" #include "mozilla/java/GeckoAppShellWrappers.h" #include "mozilla/java/GeckoThreadWrappers.h" #include "AndroidBuild.h" #include "nsAppShell.h" #include "nsExceptionHandler.h" namespace mozilla { namespace jni { namespace detail { #define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName) \ \ constexpr JNIType (JNIEnv::*TypeAdapter::Call)( \ jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \ constexpr JNIType (JNIEnv::*TypeAdapter::StaticCall)( \ jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \ constexpr JNIType (JNIEnv::*TypeAdapter::Get)(jobject, jfieldID) \ ABIName; \ constexpr JNIType (JNIEnv::*TypeAdapter::StaticGet)( \ jclass, jfieldID) ABIName; \ constexpr void (JNIEnv::*TypeAdapter::Set)(jobject, jfieldID, \ JNIType) ABIName; \ constexpr void (JNIEnv::*TypeAdapter::StaticSet)( \ jclass, jfieldID, JNIType) ABIName; \ constexpr void (JNIEnv::*TypeAdapter::GetArray)( \ JNIType##Array, jsize, jsize, JNIType*) DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean, /*nothing*/); DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte, /*nothing*/); DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char, /*nothing*/); DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short, /*nothing*/); DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int, /*nothing*/); DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long, /*nothing*/); DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float, MOZ_JNICALL_ABI); DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double, MOZ_JNICALL_ABI); #undef DEFINE_PRIMITIVE_TYPE_ADAPTER } // namespace detail template <> const char ObjectBase::name[] = "java/lang/Object"; template <> const char ObjectBase, jstring>::name[] = "java/lang/String"; template <> const char ObjectBase, jclass>::name[] = "java/lang/Class"; template <> const char ObjectBase, jthrowable>::name[] = "java/lang/Throwable"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Boolean"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Byte"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Character"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Short"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Integer"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Long"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Float"; template <> const char ObjectBase, jobject>::name[] = "java/lang/Double"; template <> const char ObjectBase, jbooleanArray>::name[] = "[Z"; template <> const char ObjectBase, jbyteArray>::name[] = "[B"; template <> const char ObjectBase, jcharArray>::name[] = "[C"; template <> const char ObjectBase, jshortArray>::name[] = "[S"; template <> const char ObjectBase, jintArray>::name[] = "[I"; template <> const char ObjectBase, jlongArray>::name[] = "[J"; template <> const char ObjectBase, jfloatArray>::name[] = "[F"; template <> const char ObjectBase, jdoubleArray>::name[] = "[D"; template <> const char ObjectBase, jobjectArray>::name[] = "[Ljava/lang/Object;"; template <> const char ObjectBase::name[] = "java/nio/ByteBuffer"; JavaVM* sJavaVM; JNIEnv* sGeckoThreadEnv; namespace { pthread_key_t sThreadEnvKey; jclass sOOMErrorClass; jobject sClassLoader; jmethodID sClassLoaderLoadClass; void UnregisterThreadEnv(void* env) { if (!env) { // We were never attached. return; } // The thread may have already been detached. In that case, it's still // okay to call DetachCurrentThread(); it'll simply return an error. // However, we must not access | env | because it may be invalid. MOZ_ASSERT(sJavaVM); sJavaVM->DetachCurrentThread(); } } // namespace void SetGeckoThreadEnv(JNIEnv* aEnv) { MOZ_ASSERT(aEnv); MOZ_ASSERT(!sGeckoThreadEnv || sGeckoThreadEnv == aEnv); if (!sGeckoThreadEnv && pthread_key_create(&sThreadEnvKey, UnregisterThreadEnv)) { MOZ_CRASH("Failed to initialize required TLS"); } sGeckoThreadEnv = aEnv; MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, aEnv)); MOZ_ALWAYS_TRUE(!aEnv->GetJavaVM(&sJavaVM)); MOZ_ASSERT(sJavaVM); sOOMErrorClass = Class::GlobalRef( Class::LocalRef::Adopt(aEnv->FindClass("java/lang/OutOfMemoryError"))) .Forget(); aEnv->ExceptionClear(); sClassLoader = Object::GlobalRef(java::GeckoThread::ClsLoader()).Forget(); sClassLoaderLoadClass = aEnv->GetMethodID( Class::LocalRef::Adopt(aEnv->GetObjectClass(sClassLoader)).Get(), "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); MOZ_ASSERT(sClassLoader && sClassLoaderLoadClass); } JNIEnv* GetEnvForThread() { MOZ_ASSERT(sGeckoThreadEnv); JNIEnv* env = static_cast(pthread_getspecific(sThreadEnvKey)); if (env) { return env; } // We don't have a saved JNIEnv, so try to get one. // AttachCurrentThread() does the same thing as GetEnv() when a thread is // already attached, so we don't have to call GetEnv() at all. if (!sJavaVM->AttachCurrentThread(&env, nullptr)) { MOZ_ASSERT(env); MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, env)); return env; } MOZ_CRASH("Failed to get JNIEnv for thread"); return nullptr; // unreachable } bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage) { MOZ_ASSERT(aEnv, "Invalid thread JNI env"); Class::LocalRef cls = Class::LocalRef::Adopt(aEnv->FindClass(aClass)); MOZ_ASSERT(cls, "Cannot find exception class"); return !aEnv->ThrowNew(cls.Get(), aMessage); } bool HandleUncaughtException(JNIEnv* aEnv) { MOZ_ASSERT(aEnv, "Invalid thread JNI env"); if (!aEnv->ExceptionCheck()) { return false; } #ifdef MOZ_CHECK_JNI aEnv->ExceptionDescribe(); #endif Throwable::LocalRef e = Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred()); MOZ_ASSERT(e); aEnv->ExceptionClear(); String::LocalRef stack = java::GeckoAppShell::GetExceptionStackTrace(e); if (stack && ReportException(aEnv, e.Get(), stack.Get())) { return true; } aEnv->ExceptionClear(); java::GeckoAppShell::HandleUncaughtException(e); if (NS_WARN_IF(aEnv->ExceptionCheck())) { aEnv->ExceptionDescribe(); aEnv->ExceptionClear(); } return true; } bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack) { bool result = true; result &= NS_SUCCEEDED(CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::JavaStackTrace, String::Ref::From(aStack)->ToCString())); auto appNotes = java::GeckoAppShell::GetAppNotes(); if (NS_WARN_IF(aEnv->ExceptionCheck())) { aEnv->ExceptionDescribe(); aEnv->ExceptionClear(); } else if (appNotes) { CrashReporter::AppendAppNotesToCrashReport("\n"_ns + appNotes->ToCString()); } if (sOOMErrorClass && aEnv->IsInstanceOf(aExc, sOOMErrorClass)) { NS_ABORT_OOM(0); // Unknown OOM size } return result; } namespace { jclass sJNIObjectClass; jfieldID sJNIObjectHandleField; bool EnsureJNIObject(JNIEnv* env, jobject instance) { if (!sJNIObjectClass) { sJNIObjectClass = Class::GlobalRef(Class::LocalRef::Adopt(GetClassRef( env, "org/mozilla/gecko/mozglue/JNIObject"))) .Forget(); sJNIObjectHandleField = env->GetFieldID(sJNIObjectClass, "mHandle", "J"); } MOZ_ASSERT(env->IsInstanceOf(instance, sJNIObjectClass), "Java class is not derived from JNIObject"); return true; } } // namespace uintptr_t GetNativeHandle(JNIEnv* env, jobject instance) { if (!EnsureJNIObject(env, instance)) { return 0; } return static_cast( env->GetLongField(instance, sJNIObjectHandleField)); } void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle) { if (!EnsureJNIObject(env, instance)) { return; } env->SetLongField(instance, sJNIObjectHandleField, static_cast(handle)); } jclass GetClassRef(JNIEnv* aEnv, const char* aClassName) { // First try the default class loader. auto classRef = Class::LocalRef::Adopt(aEnv, aEnv->FindClass(aClassName)); if ((!classRef || aEnv->ExceptionCheck()) && sClassLoader) { // If the default class loader failed but we have an app class loader, try // that. Clear the pending exception from failed FindClass call above. aEnv->ExceptionClear(); classRef = Class::LocalRef::Adopt( aEnv, jclass(aEnv->CallObjectMethod(sClassLoader, sClassLoaderLoadClass, StringParam(aClassName, aEnv).Get()))); } if (classRef && !aEnv->ExceptionCheck()) { return classRef.Forget(); } __android_log_print( ANDROID_LOG_ERROR, "Gecko", ">>> FATAL JNI ERROR! FindClass(\"%s\") failed. " "Does the class require a newer API version? " "Or did ProGuard optimize away something it shouldn't have?", aClassName); aEnv->ExceptionDescribe(); MOZ_CRASH("Cannot find JNI class"); return nullptr; } void DispatchToGeckoPriorityQueue(already_AddRefed aCall) { class RunnableEvent : public nsAppShell::Event { nsCOMPtr mCall; public: explicit RunnableEvent(already_AddRefed aCall) : mCall(aCall) {} void Run() override { NS_ENSURE_SUCCESS_VOID(mCall->Run()); } }; nsAppShell::PostEvent(MakeUnique(std::move(aCall))); } int GetAPIVersion() { static int32_t apiVersion = 0; if (!apiVersion && IsAvailable()) { apiVersion = java::sdk::Build::VERSION::SDK_INT(); } return apiVersion; } pid_t GetUIThreadId() { static pid_t uiThreadId; if (!uiThreadId) { uiThreadId = pid_t(java::GeckoThread::UiThreadId()); } return uiThreadId; } bool IsOOMException(JNIEnv* aEnv) { MOZ_ASSERT(aEnv->ExceptionCheck()); Throwable::LocalRef e = Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred()); return sOOMErrorClass && aEnv->IsInstanceOf(e.Get(), sOOMErrorClass); } } // namespace jni } // namespace mozilla