Add class status for resolved erroneous classes.
Split the old ambiguous status mirror::Class::kStatusError
into kStatusErrorUnresolved and kStatusErrorResolved. Once
a class has been resolved, IsResolved() shall return true
even if the class later becomes erroneous. Allow returning
erroneous class from ClassLinker::EnsureResolved() if it has
been previously resolved. This allows consistent behavior
for retrieving classes, immune to multi-threaded races and
multi-dex weirdness. It also allows JVMTI to properly report
"prepared" (i.e. resolved) classes that are also erroneous.
The new behavior is consistent with the RI.
Add regression tests to 008-exceptions for inconsistent
behavior for multi-dex retrieval of erroneous resolved class
(wrapping or not wrapping the old exception based on which
dex file is used for lookup) and for a CHECK(IsResolved())
crash in ClassLinker::LoadSuperAndInterfaces() (without any
tests for similar checks that could have previously failed
only due to extremely unlikely race conditions; these should
now also be fixed).
Inconsistency still remains for class verification as shown
by the new exceptionsForSuperClassInitFailure() test in
008-exceptions, where interpreter and Optimizing still
cause different exceptions to be thrown.
Note: This is partially changing behavior implemented for
bug 28787733. Since we allow the class loader to retrieve an
erroneous resolved class, the ExceptionInInitializerError is
not thrown at all from VMClassLoader_findLoadedClass(), so
there is nothing to wrap in ClassNotFoundException.
Test: m test-art-host
Bug: 30627598
Bug: 28787733
Change-Id: I86cdca00f35a0d6221d2559e3026ac0428a3613c
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 9964b73..f08d4da 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -115,7 +115,9 @@
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
bool class_linker_initialized = class_linker != nullptr && class_linker->IsInitialized();
if (LIKELY(class_linker_initialized)) {
- if (UNLIKELY(new_status <= old_status && new_status != kStatusError &&
+ if (UNLIKELY(new_status <= old_status &&
+ new_status != kStatusErrorUnresolved &&
+ new_status != kStatusErrorResolved &&
new_status != kStatusRetired)) {
LOG(FATAL) << "Unexpected change back of class status for " << h_this->PrettyClass()
<< " " << old_status << " -> " << new_status;
@@ -127,10 +129,12 @@
<< h_this->PrettyClass() << " " << old_status << " -> " << new_status;
}
}
- if (UNLIKELY(new_status == kStatusError)) {
- CHECK_NE(h_this->GetStatus(), kStatusError)
+ if (UNLIKELY(IsErroneous(new_status))) {
+ CHECK(!h_this->IsErroneous())
<< "Attempt to set as erroneous an already erroneous class "
- << h_this->PrettyClass();
+ << h_this->PrettyClass()
+ << " old_status: " << old_status << " new_status: " << new_status;
+ CHECK_EQ(new_status == kStatusErrorResolved, old_status >= kStatusResolved);
if (VLOG_IS_ON(class_linker)) {
LOG(ERROR) << "Setting " << h_this->PrettyDescriptor() << " to erroneous.";
if (self->IsExceptionPending()) {
@@ -177,7 +181,7 @@
// Class is a temporary one, ensure that waiters for resolution get notified of retirement
// so that they can grab the new version of the class from the class linker's table.
CHECK_LT(new_status, kStatusResolved) << h_this->PrettyDescriptor();
- if (new_status == kStatusRetired || new_status == kStatusError) {
+ if (new_status == kStatusRetired || new_status == kStatusErrorUnresolved) {
h_this->NotifyAll(self);
}
} else {
@@ -305,7 +309,7 @@
}
if (h_this->NumStaticFields() > 0) {
os << " static fields (" << h_this->NumStaticFields() << " entries):\n";
- if (h_this->IsResolved() || h_this->IsErroneous()) {
+ if (h_this->IsResolved()) {
for (size_t i = 0; i < h_this->NumStaticFields(); ++i) {
os << StringPrintf(" %2zd: %s\n", i,
ArtField::PrettyField(h_this->GetStaticField(i)).c_str());
@@ -316,7 +320,7 @@
}
if (h_this->NumInstanceFields() > 0) {
os << " instance fields (" << h_this->NumInstanceFields() << " entries):\n";
- if (h_this->IsResolved() || h_this->IsErroneous()) {
+ if (h_this->IsResolved()) {
for (size_t i = 0; i < h_this->NumInstanceFields(); ++i) {
os << StringPrintf(" %2zd: %s\n", i,
ArtField::PrettyField(h_this->GetInstanceField(i)).c_str());
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index fb2792a..7f6aa12 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -84,6 +84,13 @@
// will be gc'ed once all refs to the class point to the newly
// cloned version.
//
+ // kStatusErrorUnresolved, kStatusErrorResolved: Class is erroneous. We need
+ // to distinguish between classes that have been resolved and classes that
+ // have not. This is important because the const-class instruction needs to
+ // return a previously resolved class even if its subsequent initialization
+ // failed. We also need this to decide whether to wrap a previous
+ // initialization failure in ClassDefNotFound error or not.
+ //
// kStatusNotReady: If a Class cannot be found in the class table by
// FindClass, it allocates an new one with AllocClass in the
// kStatusNotReady and calls LoadClass. Note if it does find a
@@ -119,8 +126,9 @@
//
// TODO: Explain the other states
enum Status {
- kStatusRetired = -2, // Retired, should not be used. Use the newly cloned one instead.
- kStatusError = -1,
+ kStatusRetired = -3, // Retired, should not be used. Use the newly cloned one instead.
+ kStatusErrorResolved = -2,
+ kStatusErrorUnresolved = -1,
kStatusNotReady = 0,
kStatusIdx = 1, // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_.
kStatusLoaded = 2, // DEX idx values resolved.
@@ -158,8 +166,25 @@
// Returns true if the class has failed to link.
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+ bool IsErroneousUnresolved() REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetStatus<kVerifyFlags>() == kStatusErrorUnresolved;
+ }
+
+ // Returns true if the class has failed to initialize.
+ template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+ bool IsErroneousResolved() REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetStatus<kVerifyFlags>() == kStatusErrorResolved;
+ }
+
+ // Returns true if the class status indicets that the class has failed to link or initialize.
+ static bool IsErroneous(Status status) {
+ return status == kStatusErrorUnresolved || status == kStatusErrorResolved;
+ }
+
+ // Returns true if the class has failed to link or initialize.
+ template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
bool IsErroneous() REQUIRES_SHARED(Locks::mutator_lock_) {
- return GetStatus<kVerifyFlags>() == kStatusError;
+ return IsErroneous(GetStatus<kVerifyFlags>());
}
// Returns true if the class has been loaded.
@@ -177,7 +202,8 @@
// Returns true if the class has been linked.
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
bool IsResolved() REQUIRES_SHARED(Locks::mutator_lock_) {
- return GetStatus<kVerifyFlags>() >= kStatusResolved;
+ Status status = GetStatus<kVerifyFlags>();
+ return status >= kStatusResolved || status == kStatusErrorResolved;
}
// Returns true if the class was compile-time verified.
@@ -345,7 +371,7 @@
// be replaced with a class with the right size for embedded imt/vtable.
bool IsTemp() REQUIRES_SHARED(Locks::mutator_lock_) {
Status s = GetStatus();
- return s < Status::kStatusResolving && ShouldHaveEmbeddedVTable();
+ return s < Status::kStatusResolving && s != kStatusErrorResolved && ShouldHaveEmbeddedVTable();
}
String* GetName() REQUIRES_SHARED(Locks::mutator_lock_); // Returns the cached name.
@@ -1017,7 +1043,7 @@
// Returns the number of instance fields containing reference types. Does not count fields in any
// super classes.
uint32_t NumReferenceInstanceFields() REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(IsResolved() || IsErroneous());
+ DCHECK(IsResolved());
return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_instance_fields_));
}
@@ -1045,7 +1071,7 @@
// Returns the number of static fields containing reference types.
uint32_t NumReferenceStaticFields() REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(IsResolved() || IsErroneous());
+ DCHECK(IsResolved());
return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_static_fields_));
}