Initialize classes in runtime app images.
Test: 845-data-image
Bug: 260557058
Change-Id: I78832389ae716c95cae78bae82ca481ab3959070
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..1eabe82 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"
@@ -231,7 +232,7 @@
// 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 +260,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 +861,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 +918,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;
@@ -1184,7 +1187,7 @@
// and initialized at runtime.
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 +1348,177 @@
return offset;
}
+ // 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 = GetOrComputeImageAddress(cls->GetSuperClass());
+ if (!super->IsInitialized()) {
+ 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 = GetOrComputeImageAddress(cls->GetDirectInterface(i));
+ if (!itf->IsInitialized()) {
+ 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);
+ 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);
+ }
+ }
+ 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);
+ string_offsets.emplace_back(sizeof(ImageHeader) + class_offset, offset.Int32Value());
+ 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;
@@ -1388,13 +1562,24 @@
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);
+ if (is_class_initialized) {
+ copy->SetStatusInternal(ClassStatus::kVisiblyInitialized);
+ } else {
+ // If we fail to initialize clear again.
+ 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..1eaa6eb 100644
--- a/test/845-data-image/src-art/Main.java
+++ b/test/845-data-image/src-art/Main.java
@@ -23,6 +23,15 @@
import java.lang.reflect.Method;
import java.util.concurrent.CyclicBarrier;
+class ClassWithStatics {
+ 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 +207,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 +240,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();