diff options
| author | 2015-04-22 18:10:36 -0700 | |
|---|---|---|
| committer | 2015-04-22 18:10:36 -0700 | |
| commit | bfdcdc1e2c0af34aeaf7b5b4d499975e0c3157be (patch) | |
| tree | 17143f52b438284477fa6e3ac8fe74b0d82a5744 | |
| parent | 8e58d76eb30a50e38c46bd6277186116937ba396 (diff) | |
ART: Fix re-throwing failures of non-convention errors
While it is convention that Throwable subclasses should have a
constructor with a String argument, that is not rigorously enforced.
So if a static initializer throws an error that omits that
constructor, we must not provide a message when trying to throw
again.
Bug: 20495321
Bug: 20497840
Change-Id: Ia4334fa24223750f90a8f2732f1eb1e738575e8d
| -rw-r--r-- | runtime/class_linker.cc | 32 | ||||
| -rw-r--r-- | runtime/class_linker.h | 6 | ||||
| -rw-r--r-- | test/008-exceptions/expected.txt | 13 | ||||
| -rw-r--r-- | test/008-exceptions/src/Main.java | 41 |
4 files changed, 84 insertions, 8 deletions
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index c344eb4b41..dc8bf2ac5e 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -92,8 +92,28 @@ static void ThrowNoClassDefFoundError(const char* fmt, ...) { va_end(args); } -static void ThrowEarlierClassFailure(mirror::Class* c) +static bool HasInitWithString(Thread* self, ClassLinker* class_linker, const char* descriptor) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::ArtMethod* method = self->GetCurrentMethod(nullptr); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> class_loader(hs.NewHandle(method != nullptr ? + method->GetDeclaringClass()->GetClassLoader() + : nullptr)); + mirror::Class* exception_class = class_linker->FindClass(self, descriptor, class_loader); + + if (exception_class == nullptr) { + // No exc class ~ no <init>-with-string. + CHECK(self->IsExceptionPending()); + self->ClearException(); + return false; + } + + mirror::ArtMethod* exception_init_method = + exception_class->FindDeclaredDirectMethod("<init>", "(Ljava/lang/String;)V"); + return exception_init_method != nullptr; +} + +void ClassLinker::ThrowEarlierClassFailure(mirror::Class* c) { // The class failed to initialize on a previous attempt, so we want to throw // a NoClassDefFoundError (v2 2.17.5). The exception to this rule is if we // failed in verification, in which case v2 5.4.1 says we need to re-throw @@ -112,9 +132,15 @@ static void ThrowEarlierClassFailure(mirror::Class* c) } else { if (c->GetVerifyErrorClass() != nullptr) { // TODO: change the verifier to store an _instance_, with a useful detail message? + // It's possible the exception doesn't have a <init>(String). std::string temp; - self->ThrowNewException(c->GetVerifyErrorClass()->GetDescriptor(&temp), - PrettyDescriptor(c).c_str()); + const char* descriptor = c->GetVerifyErrorClass()->GetDescriptor(&temp); + + if (HasInitWithString(self, this, descriptor)) { + self->ThrowNewException(descriptor, PrettyDescriptor(c).c_str()); + } else { + self->ThrowNewException(descriptor, nullptr); + } } else { self->ThrowNewException("Ljava/lang/NoClassDefFoundError;", PrettyDescriptor(c).c_str()); diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 8e274130f0..1bd9f0a7e9 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -657,6 +657,12 @@ class ClassLinker { // Return the quick generic JNI stub for testing. const void* GetRuntimeQuickGenericJniStub() const; + // Throw the class initialization failure recorded when first trying to initialize the given + // class. + // Note: Currently we only store the descriptor, so we cannot throw the exact throwable, only + // a recreation with a custom string. + void ThrowEarlierClassFailure(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + std::vector<const DexFile*> boot_class_path_; std::vector<std::unique_ptr<const DexFile>> opened_dex_files_; diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt index ef6eaff59a..92c79dc2a0 100644 --- a/test/008-exceptions/expected.txt +++ b/test/008-exceptions/expected.txt @@ -1,9 +1,12 @@ Got an NPE: second throw java.lang.NullPointerException: second throw - at Main.catchAndRethrow(Main.java:39) - at Main.exceptions_007(Main.java:23) - at Main.main(Main.java:31) + at Main.catchAndRethrow(Main.java:58) + at Main.exceptions_007(Main.java:41) + at Main.main(Main.java:49) Caused by: java.lang.NullPointerException: first throw - at Main.throwNullPointerException(Main.java:46) - at Main.catchAndRethrow(Main.java:36) + at Main.throwNullPointerException(Main.java:65) + at Main.catchAndRethrow(Main.java:55) ... 2 more +Static Init +BadError: This is bad by convention +BadError: This is bad by convention diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java index 1f76f12460..7f6d0c5956 100644 --- a/test/008-exceptions/src/Main.java +++ b/test/008-exceptions/src/Main.java @@ -14,6 +14,24 @@ * limitations under the License. */ +// An exception that doesn't have a <init>(String) method. +class BadError extends Error { + public BadError() { + super("This is bad by convention"); + } +} + +// A class that throws BadException during static initialization. +class BadInit { + static int dummy; + static { + System.out.println("Static Init"); + if (true) { + throw new BadError(); + } + } +} + /** * Exceptions across method calls */ @@ -29,6 +47,7 @@ public class Main { } public static void main (String args[]) { exceptions_007(); + exceptionsRethrowClassInitFailure(); } private static void catchAndRethrow() { @@ -45,4 +64,26 @@ public class Main { private static void throwNullPointerException() { throw new NullPointerException("first throw"); } + + private static void exceptionsRethrowClassInitFailure() { + try { + try { + BadInit.dummy = 1; + throw new IllegalStateException("Should not reach here."); + } catch (BadError e) { + System.out.println(e); + } + + // Check if it works a second time. + + try { + BadInit.dummy = 1; + throw new IllegalStateException("Should not reach here."); + } catch (BadError e) { + System.out.println(e); + } + } catch (Exception error) { + error.printStackTrace(); + } + } } |