Fix recursive initialization on app image.
Assume class B extends class A and class A's clinit tries to instantiate
class B as a field. In this case, class A is under initializing but
ClassLinker allows initialize B as class A is initialized. If class B is
initialized successfully and transaction is commited then class A abort
the transaction, status of class B will not be reverted.
Fixed by a simple approach, when compiling images, AotClassLinker does
not allow initialize a subclass when it's superclass is not fully
initialized.
A testcase produce this error is added in /c/433381
Test: make test-art-host -j64
Change-Id: Ic6bcbf1a5162d0e6ec26979b336c0f644a1c39bc
diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc
index 871604b..b1bc3f8 100644
--- a/runtime/aot_class_linker.cc
+++ b/runtime/aot_class_linker.cc
@@ -31,18 +31,29 @@
bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
bool can_init_statics, bool can_init_parents) {
Runtime* const runtime = Runtime::Current();
+ bool strict_mode_ = runtime->IsActiveStrictTransactionMode();
DCHECK(klass != nullptr);
if (klass->IsInitialized() || klass->IsInitializing()) {
return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
}
- if (runtime->IsActiveStrictTransactionMode()) {
+ // Don't initialize klass if it's superclass is not initialized, because superclass might abort
+ // the transaction and rolled back after klass's change is commited.
+ if (strict_mode_ && !klass->IsInterface() && klass->HasSuperClass()) {
+ if (klass->GetSuperClass()->GetStatus() == mirror::Class::kStatusInitializing) {
+ runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve "
+ + klass->PrettyTypeOf() + " because it's superclass is not initialized.");
+ return false;
+ }
+ }
+
+ if (strict_mode_) {
runtime->EnterTransactionMode(true, klass.Get()->AsClass());
}
bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
- if (runtime->IsActiveStrictTransactionMode()) {
+ if (strict_mode_) {
if (success) {
// Exit Transaction if success.
runtime->ExitTransactionMode();
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 1219f6f..fae18ac 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -4895,7 +4895,9 @@
if (!super_initialized) {
// The super class was verified ahead of entering initializing, we should only be here if
// the super class became erroneous due to initialization.
- CHECK(handle_scope_super->IsErroneous() && self->IsExceptionPending())
+ // For the case of aot compiler, the super class might also be initializing but we don't
+ // want to process circular dependencies in pre-compile.
+ CHECK(self->IsExceptionPending())
<< "Super class initialization failed for "
<< handle_scope_super->PrettyDescriptor()
<< " that has unexpected status " << handle_scope_super->GetStatus()
diff --git a/test/660-clinit/src/Main.java b/test/660-clinit/src/Main.java
index cf2ffe7..96ba627 100644
--- a/test/660-clinit/src/Main.java
+++ b/test/660-clinit/src/Main.java
@@ -30,6 +30,9 @@
expectNotPreInit(A.class); // should pass
expectNotPreInit(B.class); // should fail
expectNotPreInit(C.class); // should fail
+ expectNotPreInit(G.class); // should fail
+ expectNotPreInit(Gs.class); // should fail
+ expectNotPreInit(Gss.class); // should fail
A x = new A();
System.out.println("A.a: " + A.a);
@@ -146,3 +149,19 @@
c = A.a; // read other's static field, fail
}
}
+
+class G {
+ static G g;
+ static int i;
+ static {
+ g = new Gss(); // fail because recursive dependency
+ i = A.a; // read other's static field, fail
+ }
+}
+
+// Gs will be successfully initialized as G's status is initializing at that point, which will
+// later aborted but Gs' transaction is already committed.
+// Instantiation of Gs will fail because we try to invoke G's <init>
+// but G's status will be StatusVerified. INVOKE_DIRECT will not initialize class.
+class Gs extends G {} // fail because super class can't be initialized
+class Gss extends Gs {}