ART: Throw StackOverflowError in native code
Initialize stack-overflow errors in native code to be able to reduce
the preserved area size of the stack.
Includes a refactoring away from constexpr in instruction_set.h to allow
for easy changing of the values.
Change-Id: I117cc8485f43da5f0a470f0f5e5b3dc3b5a06246
diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc
index 0fa0e41..c1c7631 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -110,8 +110,8 @@
void ThrowStackOverflowError(Thread* self) {
if (self->IsHandlingStackOverflow()) {
- LOG(ERROR) << "Recursive stack overflow.";
- // We don't fail here because SetStackEndForStackOverflow will print better diagnostics.
+ LOG(ERROR) << "Recursive stack overflow.";
+ // We don't fail here because SetStackEndForStackOverflow will print better diagnostics.
}
if (Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) {
@@ -123,15 +123,90 @@
JNIEnvExt* env = self->GetJniEnv();
std::string msg("stack size ");
msg += PrettySize(self->GetStackSize());
- // Use low-level JNI routine and pre-baked error class to avoid class linking operations that
- // would consume more stack.
- int rc = ::art::ThrowNewException(env, WellKnownClasses::java_lang_StackOverflowError,
- msg.c_str(), NULL);
- if (rc != JNI_OK) {
- // TODO: ThrowNewException failed presumably because of an OOME, we continue to throw the OOME
- // or die in the CHECK below. We may want to throw a pre-baked StackOverflowError
- // instead.
- LOG(ERROR) << "Couldn't throw new StackOverflowError because JNI ThrowNew failed.";
+
+ // Avoid running Java code for exception initialization.
+ // TODO: Checks to make this a bit less brittle.
+
+ std::string error_msg;
+
+ // Allocate an uninitialized object.
+ ScopedLocalRef<jobject> exc(env,
+ env->AllocObject(WellKnownClasses::java_lang_StackOverflowError));
+ if (exc.get() != nullptr) {
+ // "Initialize".
+ // StackOverflowError -> VirtualMachineError -> Error -> Throwable -> Object.
+ // Only Throwable has "custom" fields:
+ // String detailMessage.
+ // Throwable cause (= this).
+ // List<Throwable> suppressedExceptions (= Collections.emptyList()).
+ // Object stackState;
+ // StackTraceElement[] stackTrace;
+ // Only Throwable has a non-empty constructor:
+ // this.stackTrace = EmptyArray.STACK_TRACE_ELEMENT;
+ // fillInStackTrace();
+
+ // detailMessage.
+ // TODO: Use String::FromModifiedUTF...?
+ ScopedLocalRef<jstring> s(env, env->NewStringUTF(msg.c_str()));
+ if (s.get() != nullptr) {
+ jfieldID detail_message_id = env->GetFieldID(WellKnownClasses::java_lang_Throwable,
+ "detailMessage", "Ljava/lang/String;");
+ env->SetObjectField(exc.get(), detail_message_id, s.get());
+
+ // cause.
+ jfieldID cause_id = env->GetFieldID(WellKnownClasses::java_lang_Throwable,
+ "cause", "Ljava/lang/Throwable;");
+ env->SetObjectField(exc.get(), cause_id, exc.get());
+
+ // suppressedExceptions.
+ jfieldID emptylist_id = env->GetStaticFieldID(WellKnownClasses::java_util_Collections,
+ "EMPTY_LIST", "Ljava/util/List;");
+ ScopedLocalRef<jobject> emptylist(env, env->GetStaticObjectField(
+ WellKnownClasses::java_util_Collections, emptylist_id));
+ CHECK(emptylist.get() != nullptr);
+ jfieldID suppressed_id = env->GetFieldID(WellKnownClasses::java_lang_Throwable,
+ "suppressedExceptions", "Ljava/util/List;");
+ env->SetObjectField(exc.get(), suppressed_id, emptylist.get());
+
+ // stackState is set as result of fillInStackTrace. fillInStackTrace calls
+ // nativeFillInStackTrace.
+ ScopedLocalRef<jobject> stack_state_val(env, nullptr);
+ {
+ ScopedObjectAccessUnchecked soa(env);
+ stack_state_val.reset(soa.Self()->CreateInternalStackTrace<false>(soa));
+ }
+ if (stack_state_val.get() != nullptr) {
+ jfieldID stackstateID = env->GetFieldID(WellKnownClasses::java_lang_Throwable,
+ "stackState", "Ljava/lang/Object;");
+ env->SetObjectField(exc.get(), stackstateID, stack_state_val.get());
+
+ // stackTrace.
+ jfieldID stack_trace_elem_id = env->GetStaticFieldID(
+ WellKnownClasses::libcore_util_EmptyArray, "STACK_TRACE_ELEMENT",
+ "[Ljava/lang/StackTraceElement;");
+ ScopedLocalRef<jobject> stack_trace_elem(env, env->GetStaticObjectField(
+ WellKnownClasses::libcore_util_EmptyArray, stack_trace_elem_id));
+ jfieldID stacktrace_id = env->GetFieldID(
+ WellKnownClasses::java_lang_Throwable, "stackTrace", "[Ljava/lang/StackTraceElement;");
+ env->SetObjectField(exc.get(), stacktrace_id, stack_trace_elem.get());
+
+ // Throw the exception.
+ ThrowLocation throw_location = self->GetCurrentLocationForThrow();
+ self->SetException(throw_location,
+ reinterpret_cast<mirror::Throwable*>(self->DecodeJObject(exc.get())));
+ } else {
+ error_msg = "Could not create stack trace.";
+ }
+ } else {
+ // Could not allocate a string object.
+ error_msg = "Couldn't throw new StackOverflowError because JNI NewStringUTF failed.";
+ }
+ } else {
+ error_msg = "Could not allocate StackOverflowError object.";
+ }
+
+ if (!error_msg.empty()) {
+ LOG(ERROR) << error_msg;
CHECK(self->IsExceptionPending());
}