forked from mirrors/gecko-dev
This introduces the machinery needed to generate crash annotations from a YAML file. The relevant C++ functions are updated to take a typed enum. JavaScript calls are unaffected but they will throw if the string argument does not correspond to one of the known entries in the C++ enum. The existing whitelists and blacklists of annotations are also generated from the YAML file and all duplicate code related to them has been consolidated. Once written out to the .extra file the annotations are converted in string form and are no different than the existing ones. All existing annotations have been included in the list (and some obsolete ones have been removed) and all call sites have been updated including tests where appropriate. --HG-- extra : source : 4f6c43f2830701ec5552e08e3f1b06fe6d045860
331 lines
11 KiB
C++
331 lines
11 KiB
C++
#include "Utils.h"
|
|
#include "Types.h"
|
|
|
|
#include <android/log.h>
|
|
#include <pthread.h>
|
|
|
|
#include "mozilla/Assertions.h"
|
|
|
|
#include "GeneratedJNIWrappers.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<NativeType>::Call) \
|
|
(jobject, jmethodID, jvalue*) MOZ_JNICALL_ABI; \
|
|
constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall) \
|
|
(jclass, jmethodID, jvalue*) MOZ_JNICALL_ABI; \
|
|
constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Get) \
|
|
(jobject, jfieldID) ABIName; \
|
|
constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticGet) \
|
|
(jclass, jfieldID) ABIName; \
|
|
constexpr void (JNIEnv::*TypeAdapter<NativeType>::Set) \
|
|
(jobject, jfieldID, JNIType) ABIName; \
|
|
constexpr void (JNIEnv::*TypeAdapter<NativeType>::StaticSet) \
|
|
(jclass, jfieldID, JNIType) ABIName; \
|
|
constexpr void (JNIEnv::*TypeAdapter<NativeType>::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<Object, jobject>::name[] = "java/lang/Object";
|
|
template<> const char ObjectBase<TypedObject<jstring>, jstring>::name[] = "java/lang/String";
|
|
template<> const char ObjectBase<TypedObject<jclass>, jclass>::name[] = "java/lang/Class";
|
|
template<> const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[] = "java/lang/Throwable";
|
|
template<> const char ObjectBase<BoxedObject<jboolean>, jobject>::name[] = "java/lang/Boolean";
|
|
template<> const char ObjectBase<BoxedObject<jbyte>, jobject>::name[] = "java/lang/Byte";
|
|
template<> const char ObjectBase<BoxedObject<jchar>, jobject>::name[] = "java/lang/Character";
|
|
template<> const char ObjectBase<BoxedObject<jshort>, jobject>::name[] = "java/lang/Short";
|
|
template<> const char ObjectBase<BoxedObject<jint>, jobject>::name[] = "java/lang/Integer";
|
|
template<> const char ObjectBase<BoxedObject<jlong>, jobject>::name[] = "java/lang/Long";
|
|
template<> const char ObjectBase<BoxedObject<jfloat>, jobject>::name[] = "java/lang/Float";
|
|
template<> const char ObjectBase<BoxedObject<jdouble>, jobject>::name[] = "java/lang/Double";
|
|
template<> const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[] = "[Z";
|
|
template<> const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[] = "[B";
|
|
template<> const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[] = "[C";
|
|
template<> const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[] = "[S";
|
|
template<> const char ObjectBase<TypedObject<jintArray>, jintArray>::name[] = "[I";
|
|
template<> const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[] = "[J";
|
|
template<> const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[] = "[F";
|
|
template<> const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[] = "[D";
|
|
template<> const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[] = "[Ljava/lang/Object;";
|
|
template<> const char ObjectBase<ByteBuffer, jobject>::name[] = "java/nio/ByteBuffer";
|
|
|
|
JavaVM* sJavaVM;
|
|
JNIEnv* sGeckoThreadEnv;
|
|
|
|
namespace {
|
|
|
|
pthread_key_t sThreadEnvKey;
|
|
jclass sOOMErrorClass;
|
|
jobject sClassLoader;
|
|
jmethodID sClassLoaderLoadClass;
|
|
bool sIsFennec;
|
|
|
|
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);
|
|
|
|
if (java::GeckoThread::IsChildProcess()) {
|
|
// Disallow Fennec-only classes from being used in child processes.
|
|
sIsFennec = false;
|
|
return;
|
|
}
|
|
|
|
auto geckoAppClass = Class::LocalRef::Adopt(
|
|
aEnv->FindClass("org/mozilla/gecko/GeckoApp"));
|
|
aEnv->ExceptionClear();
|
|
sIsFennec = !!geckoAppClass;
|
|
}
|
|
|
|
JNIEnv* GetEnvForThread()
|
|
{
|
|
MOZ_ASSERT(sGeckoThreadEnv);
|
|
|
|
JNIEnv* env = static_cast<JNIEnv*>(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(NS_LITERAL_CSTRING("\n") +
|
|
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));
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
uintptr_t GetNativeHandle(JNIEnv* env, jobject instance)
|
|
{
|
|
if (!EnsureJNIObject(env, instance)) {
|
|
return 0;
|
|
}
|
|
|
|
return static_cast<uintptr_t>(
|
|
env->GetLongField(instance, sJNIObjectHandleField));
|
|
}
|
|
|
|
void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle)
|
|
{
|
|
if (!EnsureJNIObject(env, instance)) {
|
|
return;
|
|
}
|
|
|
|
env->SetLongField(instance, sJNIObjectHandleField,
|
|
static_cast<jlong>(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<nsIRunnable> aCall)
|
|
{
|
|
class RunnableEvent : public nsAppShell::Event
|
|
{
|
|
nsCOMPtr<nsIRunnable> mCall;
|
|
public:
|
|
explicit RunnableEvent(already_AddRefed<nsIRunnable> aCall) : mCall(aCall) {}
|
|
void Run() override { NS_ENSURE_SUCCESS_VOID(mCall->Run()); }
|
|
};
|
|
|
|
nsAppShell::PostEvent(MakeUnique<RunnableEvent>(std::move(aCall)));
|
|
}
|
|
|
|
bool IsFennec()
|
|
{
|
|
return sIsFennec;
|
|
}
|
|
|
|
int GetAPIVersion()
|
|
{
|
|
static int32_t apiVersion = 0;
|
|
if (!apiVersion && IsAvailable()) {
|
|
apiVersion = java::sdk::VERSION::SDK_INT();
|
|
}
|
|
return apiVersion;
|
|
}
|
|
|
|
pid_t GetUIThreadId()
|
|
{
|
|
static pid_t uiThreadId;
|
|
if (!uiThreadId) {
|
|
uiThreadId = pid_t(java::GeckoThread::UiThreadId());
|
|
}
|
|
return uiThreadId;
|
|
}
|
|
|
|
} // jni
|
|
} // mozilla
|