Reland "Initialize classes in runtime app images."
This reverts commit 16ec60dbdc41155613bee7462430366856ef52c3.
Bug: 260557058
Reason for revert:
- Reload the copy pointer after we push new data in the temporary buffer.
- Use the class contents to see if superclass is initialized. The old
code was just using some random address.
Test: MTS with the debug apex.
Change-Id: Ie839744e247c79bb9f4f47b6278947f9b77e11d5
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 7848a80..bd7ae4c 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -382,6 +382,12 @@
ArtField* LookupResolvedField(uint32_t field_idx, ArtMethod* referrer, bool is_static)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Find a field by its field index.
+ ArtField* LookupResolvedField(uint32_t field_idx,
+ ObjPtr<mirror::DexCache> dex_cache,
+ ObjPtr<mirror::ClassLoader> class_loader,
+ bool is_static)
+ REQUIRES_SHARED(Locks::mutator_lock_);
ArtField* ResolveField(uint32_t field_idx, ArtMethod* referrer, bool is_static)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
@@ -1113,13 +1119,6 @@
REQUIRES(!Locks::classlinker_classes_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Find a field by its field index.
- ArtField* LookupResolvedField(uint32_t field_idx,
- ObjPtr<mirror::DexCache> dex_cache,
- ObjPtr<mirror::ClassLoader> class_loader,
- bool is_static)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
void RegisterDexFileLocked(const DexFile& dex_file,
ObjPtr<mirror::DexCache> dex_cache,
ObjPtr<mirror::ClassLoader> class_loader)
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
index b8a9c5a..f41d4c9 100644
--- a/runtime/runtime_image.cc
+++ b/runtime/runtime_image.cc
@@ -37,6 +37,7 @@
#include "class_loader_context.h"
#include "class_loader_utils.h"
#include "class_root-inl.h"
+#include "dex/class_accessor-inl.h"
#include "gc/space/image_space.h"
#include "image.h"
#include "mirror/object-inl.h"
@@ -228,10 +229,25 @@
return reinterpret_cast<uintptr_t>(obj) - boot_image_begin_ < boot_image_size_;
}
+ // Returns the image contents for `cls`. If `cls` is in the boot image, the
+ // method just returns it.
+ mirror::Class* GetClassContent(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (cls == nullptr || IsInBootImage(cls.Ptr())) {
+ return cls.Ptr();
+ }
+ const dex::ClassDef* class_def = cls->GetClassDef();
+ DCHECK(class_def != nullptr) << cls->PrettyClass();
+ auto it = classes_.find(class_def);
+ DCHECK(it != classes_.end()) << cls->PrettyClass();
+ mirror::Class* result = reinterpret_cast<mirror::Class*>(objects_.data() + it->second);
+ DCHECK(result->GetClass()->IsClass());
+ return result;
+ }
+
// Returns a pointer that can be stored in `objects_`:
// - The pointer itself for boot image objects,
// - The offset in the image for all other objects.
- mirror::Object* GetOrComputeImageAddress(ObjPtr<mirror::Object> object)
+ template <typename T> T* GetOrComputeImageAddress(ObjPtr<T> object)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (object == nullptr || IsInBootImage(object.Ptr())) {
DCHECK(object == nullptr || Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(object));
@@ -259,7 +275,7 @@
} else {
offset = CopyObject(object);
}
- return reinterpret_cast<mirror::Object*>(image_begin_ + sizeof(ImageHeader) + offset);
+ return reinterpret_cast<T*>(image_begin_ + sizeof(ImageHeader) + offset);
}
void CreateImageSections() {
@@ -860,7 +876,9 @@
}
}
- void CopyMethodArrays(ObjPtr<mirror::Class> cls, uint32_t class_image_address)
+ void CopyMethodArrays(ObjPtr<mirror::Class> cls,
+ uint32_t class_image_address,
+ bool is_class_initialized)
REQUIRES_SHARED(Locks::mutator_lock_) {
size_t number_of_methods = cls->NumMethods();
if (number_of_methods == 0) {
@@ -915,7 +933,7 @@
stub = StubType::kQuickGenericJNITrampoline;
} else if (!cls->IsVerified()) {
stub = StubType::kQuickToInterpreterBridge;
- } else if (method->NeedsClinitCheckBeforeCall()) {
+ } else if (!is_class_initialized && method->NeedsClinitCheckBeforeCall()) {
stub = StubType::kQuickResolutionTrampoline;
} else if (interpreter::IsNterpSupported() && CanMethodUseNterp(method)) {
stub = StubType::kNterpTrampoline;
@@ -1180,11 +1198,11 @@
MemberOffset offset,
bool is_static) const
REQUIRES_SHARED(Locks::mutator_lock_) {
- // We don't copy static fields, instead classes will be marked as resolved
- // and initialized at runtime.
+ // We don't copy static fields, they are being handled when we try to
+ // initialize the class.
ObjPtr<mirror::Object> ref =
is_static ? nullptr : obj->GetFieldObject<mirror::Object>(offset);
- mirror::Object* address = image_->GetOrComputeImageAddress(ref.Ptr());
+ mirror::Object* address = image_->GetOrComputeImageAddress(ref);
mirror::Object* copy =
reinterpret_cast<mirror::Object*>(image_->objects_.data() + copy_offset_);
copy->GetFieldObjectReferenceAddr<kVerifyNone>(offset)->Assign(address);
@@ -1345,6 +1363,192 @@
return offset;
}
+ bool IsInitialized(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInBootImage(cls)) {
+ const OatDexFile* oat_dex_file = cls->GetDexFile().GetOatDexFile();
+ DCHECK(oat_dex_file != nullptr) << "We should always have an .oat file for a boot image";
+ uint16_t class_def_index = cls->GetDexClassDefIndex();
+ ClassStatus oat_file_class_status = oat_dex_file->GetOatClass(class_def_index).GetStatus();
+ return oat_file_class_status == ClassStatus::kVisiblyInitialized;
+ } else {
+ return cls->IsVisiblyInitialized<kVerifyNone>();
+ }
+ }
+ // Try to initialize `copy`. Note that `cls` may not be initialized.
+ // This is called after the image generation logic has visited super classes
+ // and super interfaces, so we can just check those directly.
+ bool TryInitializeClass(mirror::Class* copy, ObjPtr<mirror::Class> cls, uint32_t class_offset)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!cls->IsVerified()) {
+ return false;
+ }
+ if (cls->IsArrayClass()) {
+ return true;
+ }
+
+ // Check if we have been able to initialize the super class.
+ mirror::Class* super = GetClassContent(cls->GetSuperClass());
+ DCHECK(super != nullptr)
+ << "App image classes should always have a super class: " << cls->PrettyClass();
+ if (!IsInitialized(super)) {
+ return false;
+ }
+
+ // We won't initialize class with class initializers.
+ if (cls->FindClassInitializer(kRuntimePointerSize) != nullptr) {
+ return false;
+ }
+
+ // For non-interface classes, we require all implemented interfaces to be
+ // initialized.
+ if (!cls->IsInterface()) {
+ for (size_t i = 0; i < cls->NumDirectInterfaces(); i++) {
+ mirror::Class* itf = GetClassContent(cls->GetDirectInterface(i));
+ if (!IsInitialized(itf)) {
+ return false;
+ }
+ }
+ }
+
+ // Trivial case: no static fields.
+ if (cls->NumStaticFields() == 0u) {
+ return true;
+ }
+
+ // Go over all static fields and try to initialize them.
+ EncodedStaticFieldValueIterator it(cls->GetDexFile(), *cls->GetClassDef());
+ if (!it.HasNext()) {
+ return true;
+ }
+
+ // Temporary string offsets in case we failed to initialize the class. We
+ // will add the offsets at the end of this method if we are successful.
+ ArenaVector<AppImageReferenceOffsetInfo> string_offsets(allocator_.Adapter());
+ ClassLinker* linker = Runtime::Current()->GetClassLinker();
+ ClassAccessor accessor(cls->GetDexFile(), *cls->GetClassDef());
+ for (const ClassAccessor::Field& field : accessor.GetStaticFields()) {
+ if (!it.HasNext()) {
+ break;
+ }
+ ArtField* art_field = linker->LookupResolvedField(field.GetIndex(),
+ cls->GetDexCache(),
+ cls->GetClassLoader(),
+ /* is_static= */ true);
+ DCHECK_NE(art_field, nullptr);
+ MemberOffset offset(art_field->GetOffset());
+ switch (it.GetValueType()) {
+ case EncodedArrayValueIterator::ValueType::kBoolean:
+ copy->SetFieldBoolean<false>(offset, it.GetJavaValue().z);
+ break;
+ case EncodedArrayValueIterator::ValueType::kByte:
+ copy->SetFieldByte<false>(offset, it.GetJavaValue().b);
+ break;
+ case EncodedArrayValueIterator::ValueType::kShort:
+ copy->SetFieldShort<false>(offset, it.GetJavaValue().s);
+ break;
+ case EncodedArrayValueIterator::ValueType::kChar:
+ copy->SetFieldChar<false>(offset, it.GetJavaValue().c);
+ break;
+ case EncodedArrayValueIterator::ValueType::kInt:
+ copy->SetField32<false>(offset, it.GetJavaValue().i);
+ break;
+ case EncodedArrayValueIterator::ValueType::kLong:
+ copy->SetField64<false>(offset, it.GetJavaValue().j);
+ break;
+ case EncodedArrayValueIterator::ValueType::kFloat:
+ copy->SetField32<false>(offset, it.GetJavaValue().i);
+ break;
+ case EncodedArrayValueIterator::ValueType::kDouble:
+ copy->SetField64<false>(offset, it.GetJavaValue().j);
+ break;
+ case EncodedArrayValueIterator::ValueType::kNull:
+ copy->SetFieldObject<false>(offset, nullptr);
+ break;
+ case EncodedArrayValueIterator::ValueType::kString: {
+ ObjPtr<mirror::String> str =
+ linker->LookupString(dex::StringIndex(it.GetJavaValue().i), cls->GetDexCache());
+ mirror::String* str_copy = nullptr;
+ if (str == nullptr) {
+ // String wasn't created yet.
+ return false;
+ } else if (IsInBootImage(str.Ptr())) {
+ str_copy = str.Ptr();
+ } else {
+ uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode());
+ DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode()))
+ << "Dex cache strings should be interned";
+ auto string_it = intern_table_.FindWithHash(str.Ptr(), hash);
+ if (string_it == intern_table_.end()) {
+ // The string must be interned.
+ uint32_t string_offset = CopyObject(str);
+ // Reload the class copy after having copied the string.
+ copy = reinterpret_cast<mirror::Class*>(objects_.data() + class_offset);
+ uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader);
+ intern_table_.InsertWithHash(address, hash);
+ str_copy = reinterpret_cast<mirror::String*>(address);
+ } else {
+ str_copy = reinterpret_cast<mirror::String*>(*string_it);
+ }
+ string_offsets.emplace_back(sizeof(ImageHeader) + class_offset, offset.Int32Value());
+ }
+ uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value();
+ mirror::HeapReference<mirror::Object>* objref_addr =
+ reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
+ objref_addr->Assign</* kIsVolatile= */ false>(str_copy);
+ break;
+ }
+ case EncodedArrayValueIterator::ValueType::kType: {
+ // Note that it may be that the referenced type hasn't been processed
+ // yet by the image generation logic. In this case we bail out for
+ // simplicity.
+ ObjPtr<mirror::Class> type =
+ linker->LookupResolvedType(dex::TypeIndex(it.GetJavaValue().i), cls);
+ mirror::Class* type_copy = nullptr;
+ if (type == nullptr) {
+ // Class wasn't resolved yet.
+ return false;
+ } else if (IsInBootImage(type.Ptr())) {
+ // Make sure the type is in our class table.
+ uint32_t hash = type->DescriptorHash();
+ class_table_.InsertWithHash(ClassTable::TableSlot(type.Ptr(), hash), hash);
+ type_copy = type.Ptr();
+ } else if (type->IsArrayClass()) {
+ std::string class_name;
+ type->GetDescriptor(&class_name);
+ auto class_it = array_classes_.find(class_name);
+ if (class_it == array_classes_.end()) {
+ return false;
+ }
+ type_copy = reinterpret_cast<mirror::Class*>(
+ image_begin_ + sizeof(ImageHeader) + class_it->second);
+ } else {
+ const dex::ClassDef* class_def = type->GetClassDef();
+ DCHECK_NE(class_def, nullptr);
+ auto class_it = classes_.find(class_def);
+ if (class_it == classes_.end()) {
+ return false;
+ }
+ type_copy = reinterpret_cast<mirror::Class*>(
+ image_begin_ + sizeof(ImageHeader) + class_it->second);
+ }
+ uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value();
+ mirror::HeapReference<mirror::Object>* objref_addr =
+ reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
+ objref_addr->Assign</* kIsVolatile= */ false>(type_copy);
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unreachable";
+ }
+ it.Next();
+ }
+ // We have successfully initialized the class, we can now record the string
+ // offsets.
+ string_reference_offsets_.insert(
+ string_reference_offsets_.end(), string_offsets.begin(), string_offsets.end());
+ return true;
+ }
+
uint32_t CopyClass(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(!cls->IsBootStrapClassLoaded());
uint32_t offset = 0u;
@@ -1384,17 +1588,37 @@
} else {
copy->SetStatusInternal(cls->IsVerified() ? ClassStatus::kVerified : ClassStatus::kResolved);
}
- copy->SetObjectSizeAllocFastPath(std::numeric_limits<uint32_t>::max());
- copy->SetAccessFlags(copy->GetAccessFlags() & ~kAccRecursivelyInitialized);
// Clear static field values.
- MemberOffset static_offset = cls->GetFirstReferenceStaticFieldOffset(kRuntimePointerSize);
- memset(objects_.data() + offset + static_offset.Uint32Value(),
- 0,
- cls->GetClassSize() - static_offset.Uint32Value());
+ auto clear_class = [&] () REQUIRES_SHARED(Locks::mutator_lock_) {
+ MemberOffset static_offset = cls->GetFirstReferenceStaticFieldOffset(kRuntimePointerSize);
+ memset(objects_.data() + offset + static_offset.Uint32Value(),
+ 0,
+ cls->GetClassSize() - static_offset.Uint32Value());
+ };
+ clear_class();
+
+ bool is_class_initialized = TryInitializeClass(copy, cls, offset);
+ // Reload the copy, it may have moved after `TryInitializeClass`.
+ copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset);
+ if (is_class_initialized) {
+ copy->SetStatusInternal(ClassStatus::kVisiblyInitialized);
+ if (!cls->IsArrayClass() && !cls->IsFinalizable()) {
+ copy->SetObjectSizeAllocFastPath(RoundUp(cls->GetObjectSize(), kObjectAlignment));
+ }
+ if (cls->IsInterface()) {
+ copy->SetAccessFlags(copy->GetAccessFlags() | kAccRecursivelyInitialized);
+ }
+ } else {
+ // If we fail to initialize, remove initialization related flags and
+ // clear again.
+ copy->SetObjectSizeAllocFastPath(std::numeric_limits<uint32_t>::max());
+ copy->SetAccessFlags(copy->GetAccessFlags() & ~kAccRecursivelyInitialized);
+ clear_class();
+ }
CopyFieldArrays(cls, class_image_address);
- CopyMethodArrays(cls, class_image_address);
+ CopyMethodArrays(cls, class_image_address, is_class_initialized);
if (cls->ShouldHaveImt()) {
CopyImTable(cls);
}
diff --git a/test/845-data-image/src-art/Main.java b/test/845-data-image/src-art/Main.java
index 3c76e88..e74a6d6 100644
--- a/test/845-data-image/src-art/Main.java
+++ b/test/845-data-image/src-art/Main.java
@@ -21,8 +21,28 @@
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
+import java.math.BigInteger;
import java.util.concurrent.CyclicBarrier;
+// This class helps testing that we don't mark `InheritsBigInteger` as initialized,
+// given we do not expect `BigInteger` to be initialized in the boot image.
+class InheritsBigInteger extends BigInteger {
+ InheritsBigInteger(String value) {
+ super(value);
+ }
+}
+
+class SuperClass {}
+
+class ClassWithStatics extends SuperClass {
+ public static final String STATIC_STRING = "foo";
+ public static final int STATIC_INT = 42;
+}
+
+class ClassWithStaticType {
+ public static final Class<?> STATIC_TYPE = Object.class;
+}
+
// Add an interface for testing generating classes and interfaces.
interface Itf {
public int someMethod();
@@ -198,6 +218,8 @@
public static Itf itf = new Main();
public static Itf2 itf2 = new Itf2Impl();
+ public static ClassWithStatics statics = new ClassWithStatics();
+ public static ClassWithStaticType staticType = new ClassWithStaticType();
public static void runClassTests() {
// Test Class.getName, app images expect all strings to have hash codes.
@@ -229,6 +251,11 @@
assertEquals("int", int.class.getName());
assertEquals("[I", int[].class.getName());
+ assertEquals("foo", statics.STATIC_STRING);
+ assertEquals(42, statics.STATIC_INT);
+
+ assertEquals(Object.class, staticType.STATIC_TYPE);
+
// Call all interface methods to trigger the creation of a imt conflict method.
itf2.defaultMethod1();
itf2.defaultMethod2();
@@ -281,6 +308,9 @@
itf2.defaultMethod49();
itf2.defaultMethod50();
itf2.defaultMethod51();
+
+ InheritsBigInteger bigInteger = new InheritsBigInteger("42");
+ assertEquals("42", bigInteger.toString());
}
private static void assertEquals(int expected, int actual) {