// Enforce that we have the right index for runtime methods.
static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == dex::kDexNoIndex, "Wrong runtime-method dex method index");
ArtMethod* ArtMethod::GetSingleImplementation(PointerSize pointer_size) { if (IsInvokable()) { // An invokable method single implementation is itself. returnthis;
}
DCHECK(!IsDefaultConflicting());
ArtMethod* m = reinterpret_cast<ArtMethod*>(GetDataPtrSize(pointer_size));
CHECK(m == nullptr || !m->IsDefaultConflicting()); return m;
}
template <ReadBarrierOption kReadBarrierOption>
ObjPtr<mirror::DexCache> ArtMethod::GetObsoleteDexCache() { // Note: The class redefinition happens with GC disabled, so at the point where we // create obsolete methods, the `ClassExt` and its obsolete methods and dex caches // members are reachable without a read barrier. If we start a GC later, and we // look at these objects without read barriers (`kWithoutReadBarrier`), the method // pointers shall be the same in from-space array as in to-space array (if these // arrays are different) and the dex cache array entry can point to from-space or // to-space `DexCache` but either is a valid result for `kWithoutReadBarrier`.
ScopedAssertNoThreadSuspension ants(__FUNCTION__);
std::optional<ScopedDebugDisallowReadBarriers> sddrb(std::nullopt); if (kIsDebugBuild && kReadBarrierOption == kWithoutReadBarrier) {
sddrb.emplace(Thread::Current());
}
PointerSize pointer_size = kRuntimePointerSize;
DCHECK(!Runtime::Current()->IsAotCompiler()) << PrettyMethod();
DCHECK(IsObsolete());
ObjPtr<mirror::Class> declaring_class = GetDeclaringClass<kReadBarrierOption>();
ObjPtr<mirror::ClassExt> ext =
declaring_class->GetExtData<kDefaultVerifyFlags, kReadBarrierOption>();
ObjPtr<mirror::PointerArray> obsolete_methods(
ext.IsNull() ? nullptr : ext->GetObsoleteMethods<kDefaultVerifyFlags, kReadBarrierOption>());
int32_t len = 0;
ObjPtr<mirror::ObjectArray<mirror::DexCache>> obsolete_dex_caches = nullptr; if (!obsolete_methods.IsNull()) {
len = obsolete_methods->GetLength();
obsolete_dex_caches = ext->GetObsoleteDexCaches<kDefaultVerifyFlags, kReadBarrierOption>(); // FIXME: `ClassExt::SetObsoleteArrays()` is not atomic, so one of the arrays we see here // could be extended for a new class redefinition while the other may be shorter. // Furthermore, there is no synchronization to ensure that copied contents of an old // obsolete array are visible to a thread reading the new array.
DCHECK_EQ(len, obsolete_dex_caches->GetLength())
<< " ext->GetObsoleteDexCaches()=" << obsolete_dex_caches;
} // Using kRuntimePointerSize (instead of using the image's pointer size) is fine since images // should never have obsolete methods in them so they should always be the same.
DCHECK_EQ(pointer_size, Runtime::Current()->GetClassLinker()->GetImagePointerSize()); for (int32_t i = 0; i < len; i++) { if (this == obsolete_methods->GetElementPtrSize<ArtMethod*>(i, pointer_size)) { return obsolete_dex_caches->GetWithoutChecks<kDefaultVerifyFlags, kReadBarrierOption>(i);
}
}
CHECK(declaring_class->IsObsoleteObject())
<< "This non-structurally obsolete method does not appear in the obsolete map of its class: "
<< declaring_class->PrettyClass() << " Searched " << len << " caches.";
CHECK_EQ(this,
std::clamp(this,
&(*declaring_class->GetMethods(pointer_size).begin()),
&(*declaring_class->GetMethods(pointer_size).end())))
<< "class is marked as structurally obsolete method but not found in normal obsolete-map "
<< "despite not being the original method pointer for " << GetDeclaringClass()->PrettyClass(); return declaring_class->template GetDexCache<kDefaultVerifyFlags, kReadBarrierOption>();
}
void ArtMethod::ThrowInvocationTimeError(ObjPtr<mirror::Object> receiver) {
DCHECK(!IsInvokable()); if (IsDefaultConflicting()) {
ThrowIncompatibleClassChangeErrorForMethodConflict(this);
} elseif (GetDeclaringClass()->IsInterface() && receiver != nullptr) { // If this was an interface call, check whether there is a method in the // superclass chain that isn't public. In this situation, we should throw an // IllegalAccessError.
DCHECK(IsAbstract());
ObjPtr<mirror::Class> current = receiver->GetClass();
std::string_view name = GetNameView();
Signature signature = GetSignature(); while (current != nullptr) { for (ArtMethod& method : current->GetDeclaredMethodsSlice(kRuntimePointerSize)) {
ArtMethod* np_method = method.GetInterfaceMethodIfProxy(kRuntimePointerSize); if (!np_method->IsStatic() &&
np_method->GetNameView() == name &&
np_method->GetSignature() == signature) { if (!np_method->IsPublic()) {
ThrowIllegalAccessErrorForImplementingMethod(receiver->GetClass(), np_method, this); return;
} elseif (np_method->IsAbstract()) {
ThrowAbstractMethodError(this, receiver); return;
}
}
}
current = current->GetSuperClass();
}
ThrowAbstractMethodError(this, receiver);
} else {
DCHECK(IsAbstract());
ThrowAbstractMethodError(this, receiver);
}
}
ArtMethod* ArtMethod::FindOverriddenMethod(PointerSize pointer_size) { if (IsStatic()) { return nullptr;
}
ObjPtr<mirror::Class> declaring_class = GetDeclaringClass();
ObjPtr<mirror::Class> super_class = declaring_class->GetSuperClass();
uint16_t method_index = GetMethodIndex();
ArtMethod* result = nullptr; // Did this method override a super class method? If so load the result from the super class' // vtable if (super_class->HasVTable() && method_index < super_class->GetVTableLength()) {
result = super_class->GetVTableEntry(method_index, pointer_size);
} else { // Method didn't override superclass method so search interfaces if (IsProxyMethod()) {
result = GetInterfaceMethodIfProxy(pointer_size);
DCHECK(result != nullptr);
} else {
ObjPtr<mirror::IfTable> iftable = GetDeclaringClass()->GetIfTable(); for (size_t i = 0; i < iftable->Count() && result == nullptr; i++) {
ObjPtr<mirror::Class> interface = iftable->GetInterface(i); for (ArtMethod& interface_method : interface->GetMethods(pointer_size)) { if (!interface_method.IsVirtual()) { continue;
} if (HasSameNameAndSignature(interface_method.GetInterfaceMethodIfProxy(pointer_size))) {
result = &interface_method; break;
}
}
}
}
}
DCHECK(result == nullptr ||
GetInterfaceMethodIfProxy(pointer_size)->HasSameNameAndSignature(
result->GetInterfaceMethodIfProxy(pointer_size))); return result;
}
uint32_t ArtMethod::FindCatchBlock(Handle<mirror::Class> exception_type,
uint32_t dex_pc, bool* has_no_move_exception) { // Set aside the exception while we resolve its type.
Thread* self = Thread::Current();
StackHandleScope<1> hs(self);
Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException()));
self->ClearException(); // Default to handler not found.
uint32_t found_dex_pc = dex::kDexNoIndex; // Iterate over the catch handlers associated with dex_pc.
CodeItemDataAccessor accessor(DexInstructionData()); for (CatchHandlerIterator it(accessor, dex_pc); it.HasNext(); it.Next()) {
dex::TypeIndex iter_type_idx = it.GetHandlerTypeIndex(); // Catch all case if (!iter_type_idx.IsValid()) {
found_dex_pc = it.GetHandlerAddress(); break;
} // Does this catch exception type apply?
ObjPtr<mirror::Class> iter_exception_type = ResolveClassFromTypeIndex(iter_type_idx); if (UNLIKELY(iter_exception_type == nullptr)) { // Now have a NoClassDefFoundError as exception. Ignore in case the exception class was // removed by a pro-guard like tool. // Note: this is not RI behavior. RI would have failed when loading the class.
self->ClearException();
LOG(WARNING) << "Unresolved exception class when finding catch block: "
<< DescriptorToDot(GetTypeDescriptorFromTypeIdx(iter_type_idx));
} elseif (iter_exception_type->IsAssignableFrom(exception_type.Get())) {
found_dex_pc = it.GetHandlerAddress(); break;
}
} if (found_dex_pc != dex::kDexNoIndex) { const Instruction& first_catch_instr = accessor.InstructionAt(found_dex_pc);
*has_no_move_exception = (first_catch_instr.Opcode() != Instruction::MOVE_EXCEPTION);
} // Put the exception back. if (exception != nullptr) {
self->SetException(exception.Get());
} return found_dex_pc;
}
if (kIsDebugBuild) {
self->AssertThreadSuspensionIsAllowable();
CHECK_EQ(ThreadState::kRunnable, self->GetState());
CHECK_STREQ(GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(), shorty);
}
// Push a transition back into managed code onto the linked list in thread.
ManagedStack fragment;
ScopedManagedStackFragment smsf(self, &fragment);
Runtime* runtime = Runtime::Current(); // Call the invoke stub, passing everything as arguments. // If the runtime is not yet started or it is required by the debugger, then perform the // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent // cycling around the various JIT/Interpreter methods that handle method invocation. if (UNLIKELY(!runtime->IsStarted() ||
(self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()))) { if (IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(
self, this, nullptr, args, result, /*stay_in_interpreter=*/ true);
} else {
mirror::Object* receiver = reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr();
art::interpreter::EnterInterpreterFromInvoke(
self, this, receiver, args + 1, result, /*stay_in_interpreter=*/ true);
}
} else {
DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
// Ensure that we won't be accidentally calling quick compiled code when -Xint. if (kIsDebugBuild && runtime->GetInstrumentation()->IsForcedInterpretOnly()) {
CHECK(!runtime->UseJitCompilation()); constvoid* oat_quick_code =
(IsNative() || !IsInvokable() || IsProxyMethod() || IsObsolete())
? nullptr
: GetOatMethodQuickCode(runtime->GetClassLinker()->GetImagePointerSize());
CHECK(oat_quick_code == nullptr || oat_quick_code != GetEntryPointFromQuickCompiledCode())
<< "Don't call compiled code when -Xint " << PrettyMethod();
}
#ifdef ART_USE_SIMULATOR
DCHECK(Runtime::IsSimulatorMode());
CodeSimulator* simulator = Thread::Current()->GetSimExecutor();
simulator->Invoke(this, args, args_size, self, result, shorty, IsStatic()); #else if (!IsStatic()) {
(*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
} else {
(*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
} #endif if (UNLIKELY(self->GetException() == Thread::GetDeoptimizationException())) { // Unusual case where we were running generated code and an // exception was thrown to force the activations to be removed from the // stack. Continue execution in the interpreter.
self->DeoptimizeWithDeoptimizationException(result);
} if (kLogInvocationStartAndReturn) {
LOG(INFO) << StringPrintf("Returned '%s' quick code=%p", PrettyMethod().c_str(),
GetEntryPointFromQuickCompiledCode());
}
} else {
LOG(INFO) << "Not invoking '" << PrettyMethod() << "' code=null"; if (result != nullptr) {
result->SetJ(0);
}
}
}
}
bool ArtMethod::IsSignaturePolymorphic() { // Methods with a polymorphic signature have constraints that they // are native and varargs and belong to either MethodHandle or VarHandle. if (!IsNative() || !IsVarargs()) { returnfalse;
}
ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots =
Runtime::Current()->GetClassLinker()->GetClassRoots();
ObjPtr<mirror::Class> cls = GetDeclaringClass(); return (cls == GetClassRoot<mirror::MethodHandle>(class_roots) ||
cls == GetClassRoot<mirror::VarHandle>(class_roots));
}
// We use the method's DexFile and declaring class name to find the OatMethod for an obsolete // method. This is extremely slow but we need it if we want to be able to have obsolete native // methods since we need this to find the size of its stack frames. // // NB We could (potentially) do this differently and rely on the way the transformation is applied // in order to use the entrypoint to find this information. However, for debugging reasons (most // notably making sure that new invokes of obsolete methods fail) we choose to instead get the data // directly from the dex file. staticconst OatFile::OatMethod FindOatMethodFromDexFileFor(ArtMethod* method, bool* found)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(method->IsObsolete() && method->IsNative()); const DexFile* dex_file = method->GetDexFile();
staticconst OatFile::OatMethod FindOatMethodFor(ArtMethod* method,
PointerSize pointer_size, bool* found)
REQUIRES_SHARED(Locks::mutator_lock_) { if (UNLIKELY(method->IsObsolete())) { // We shouldn't be calling this with obsolete methods except for native obsolete methods for // which we need to use the oat method to figure out how large the quick frame is.
DCHECK(method->IsNative()) << "We should only be finding the OatMethod of obsolete methods in "
<< "order to allow stack walking. Other obsolete methods should "
<< "never need to access this information.";
DCHECK_EQ(pointer_size, kRuntimePointerSize) << "Obsolete method in compiler!"; return FindOatMethodFromDexFileFor(method, found);
} // Although we overwrite the trampoline of non-static methods, we may get here via the resolution // method for direct methods (or virtual methods made direct).
ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
size_t oat_method_index; if (method->IsStatic() || method->IsDirect()) { // Simple case where the oat method index was stashed at load time.
oat_method_index = method->GetMethodIndex();
} else { // Compute the oat_method_index by search for its position in the declared virtual methods.
oat_method_index = declaring_class->NumDirectMethods(); bool found_virtual = false; for (ArtMethod& art_method : declaring_class->GetMethods(pointer_size)) { if (art_method.IsVirtual()) { // Check method index instead of identity in case of duplicate method definitions. if (method->GetDexMethodIndex() == art_method.GetDexMethodIndex()) {
found_virtual = true; break;
}
oat_method_index++;
}
}
CHECK(found_virtual) << "Didn't find oat method index for virtual method: "
<< method->PrettyMethod();
}
DCHECK_EQ(oat_method_index,
GetOatMethodIndexFromMethodIndex(declaring_class->GetDexFile(),
method->GetDeclaringClass()->GetDexClassDefIndex(),
method->GetDexMethodIndex()));
OatFile::OatClass oat_class = OatFile::FindOatClass(declaring_class->GetDexFile(),
declaring_class->GetDexClassDefIndex(),
found); if (!(*found)) { return OatFile::OatMethod::Invalid();
} return oat_class.GetOatMethod(oat_method_index);
}
bool ArtMethod::EqualParameters(Handle<mirror::ObjectArray<mirror::Class>> params) { const DexFile* dex_file = GetDexFile(); constauto& method_id = dex_file->GetMethodId(GetDexMethodIndex()); constauto& proto_id = dex_file->GetMethodPrototype(method_id); const dex::TypeList* proto_params = dex_file->GetProtoParameters(proto_id); auto count = proto_params != nullptr ? proto_params->Size() : 0u; auto param_len = params != nullptr ? params->GetLength() : 0u; if (param_len != count) { returnfalse;
} auto* cl = Runtime::Current()->GetClassLinker(); for (size_t i = 0; i < count; ++i) {
dex::TypeIndex type_idx = proto_params->GetTypeItem(i).type_idx_;
ObjPtr<mirror::Class> type = cl->ResolveType(type_idx, this); if (type == nullptr) {
Thread::Current()->AssertPendingException(); returnfalse;
} if (type != params->GetWithoutChecks(i)) { returnfalse;
}
} returntrue;
}
if (existing_entry_point == GetQuickProxyInvokeHandler()) {
DCHECK(IsProxyMethod() && !IsConstructor()); // The proxy entry point does not have any method header. return nullptr;
}
// We should not reach here with a pc of 0. pc can be 0 for downcalls when walking the stack. // For native methods this case is handled by the caller by checking the quick frame tag. See // StackVisitor::WalkStack for more details. For non-native methods pc can be 0 only for runtime // methods or proxy invoke handlers which are handled earlier.
DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod();
// Check whether the current entry point contains this pc. We need to manually // check some entrypoints in case they are trampolines in the oat file. if (!class_linker->IsQuickGenericJniStub(existing_entry_point) &&
!class_linker->IsQuickResolutionStub(existing_entry_point) &&
!class_linker->IsQuickToInterpreterBridge(existing_entry_point) &&
!OatQuickMethodHeader::IsStub( reinterpret_cast<const uint8_t*>(existing_entry_point)).value_or(true)) {
OatQuickMethodHeader* method_header =
OatQuickMethodHeader::FromEntryPoint(existing_entry_point);
if (method_header->Contains(pc)) { return method_header;
}
}
if (OatQuickMethodHeader::IsNterpPc(pc)) { return OatQuickMethodHeader::NterpMethodHeader;
}
// Check whether the pc is in the JIT code cache.
jit::Jit* jit = runtime->GetJit(); if (jit != nullptr) {
jit::JitCodeCache* code_cache = jit->GetCodeCache();
OatQuickMethodHeader* method_header = code_cache->LookupMethodHeader(pc, this); if (method_header != nullptr) {
DCHECK(method_header->Contains(pc)); return method_header;
} else { if (kIsDebugBuild && code_cache->ContainsPc(reinterpret_cast<constvoid*>(pc))) {
LOG(FATAL)
<< PrettyMethod()
<< ", pc=" << std::hex << pc
<< ", entry_point=" << std::hex << reinterpret_cast<uintptr_t>(existing_entry_point)
<< ", copy=" << std::boolalpha << IsCopied()
<< ", proxy=" << std::boolalpha << IsProxyMethod()
<< ", is_native=" << std::boolalpha << IsNative();
}
}
}
// The code has to be in an oat file. bool found;
OatFile::OatMethod oat_method =
FindOatMethodFor(this, class_linker->GetImagePointerSize(), &found); if (!found) { if (!IsNative()) {
PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT);
MemMap::DumpMaps(LOG_STREAM(FATAL_WITHOUT_ABORT), /* terse= */ true);
LOG(FATAL)
<< PrettyMethod()
<< " pc=" << pc
<< ", entrypoint= " << std::hex << reinterpret_cast<uintptr_t>(existing_entry_point)
<< ", jit= " << jit;
} // We are running the GenericJNI stub. The entrypoint may point // to different entrypoints, to a JIT-compiled JNI stub, or to a shared boot // image stub.
DCHECK(class_linker->IsQuickGenericJniStub(existing_entry_point) ||
class_linker->IsQuickResolutionStub(existing_entry_point) ||
(jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)) ||
(class_linker->FindBootJniStub(this) != nullptr))
<< " method: " << PrettyMethod()
<< " entrypoint: " << existing_entry_point
<< " size: " << OatQuickMethodHeader::FromEntryPoint(existing_entry_point)->GetCodeSize()
<< " pc: " << reinterpret_cast<constvoid*>(pc); return nullptr;
} constvoid* oat_entry_point = oat_method.GetQuickCode(); if (oat_entry_point == nullptr || class_linker->IsQuickGenericJniStub(oat_entry_point)) { if (kIsDebugBuild && !IsNative()) {
PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT);
MemMap::DumpMaps(LOG_STREAM(FATAL_WITHOUT_ABORT), /* terse= */ true);
LOG(FATAL)
<< PrettyMethod()
<< std::hex
<< " pc=" << pc
<< ", entrypoint= " << reinterpret_cast<uintptr_t>(existing_entry_point)
<< ", jit= " << jit
<< ", nterp_start= "
<< reinterpret_cast<uintptr_t>(OatQuickMethodHeader::NterpImpl.data())
<< ", nterp_end= "
<< reinterpret_cast<uintptr_t>(
OatQuickMethodHeader::NterpImpl.data() + OatQuickMethodHeader::NterpImpl.size());
} return nullptr;
}
OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(oat_entry_point); // We could have existing Oat code for native methods but we may not use it if the runtime is java // debuggable or when profiling boot class path. There is no easy way to check if the pc // corresponds to QuickGenericJniStub. Since we have eliminated all the other cases, if the pc // doesn't correspond to the AOT code then we must be running QuickGenericJniStub. if (IsNative() && !method_header->Contains(pc)) {
DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod(); return nullptr;
}
void ArtMethod::SetIntrinsic(Intrinsics intrinsic) { // Currently we only do intrinsics for static/final methods or methods of final // classes. We don't set kHasSingleImplementation for those methods.
DCHECK(IsStatic() || IsFinal() || GetDeclaringClass()->IsFinal()) << "Potential conflict with kAccSingleImplementation"; static constexpr int kAccFlagsShift = CTZ(kAccIntrinsicBits);
uint32_t intrinsic_u32 = enum_cast<uint32_t>(intrinsic);
DCHECK_LE(intrinsic_u32, kAccIntrinsicBits >> kAccFlagsShift);
uint32_t intrinsic_bits = intrinsic_u32 << kAccFlagsShift;
uint32_t new_value = (GetAccessFlags() & ~kAccIntrinsicBits) | kAccIntrinsic | intrinsic_bits;
#ifdef ART_TARGET_ANDROID // Recompute flags instead of getting them from the current access flags because // access flags may have been changed to deduplicate warning messages (b/129063331). // For host builds, the flags from the api list (i.e. hiddenapi::CreateRuntimeFlags) might not // have the right value.
uint32_t hiddenapi_flags = hiddenapi::CreateRuntimeFlags(this); #endif
SetAccessFlags(new_value); // Intrinsics are considered hot from the first call.
SetHotCounter();
// Re-apply hidden API access flags now that the method is not an intrinsic.
SetAccessFlags(GetAccessFlags() | hiddenapi_runtime_flags);
DCHECK_EQ(hiddenapi_runtime_flags, hiddenapi::GetRuntimeFlags(this));
}
// If the entry point of the method we are copying from is from JIT code, we just // put the entry point of the new method to interpreter or GenericJNI. We could set // the entry point to the JIT code, but this would require taking the JIT code cache // lock to notify it, which we do not want at this level.
Runtime* runtime = Runtime::Current(); constvoid* entry_point = GetEntryPointFromQuickCompiledCodePtrSize(image_pointer_size); if (runtime->UseJitCompilation()) { if (runtime->GetJit()->GetCodeCache()->ContainsPc(entry_point)) {
SetNativePointer(EntryPointFromQuickCompiledCodeOffset(image_pointer_size),
src->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge(),
image_pointer_size);
}
}
ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); if (interpreter::IsNterpSupported() && class_linker->IsNterpEntryPoint(entry_point)) { // If the entrypoint is nterp, it's too early to check if the new method // will support it. So for simplicity, use the interpreter bridge.
SetNativePointer(EntryPointFromQuickCompiledCodeOffset(image_pointer_size),
GetQuickToInterpreterBridge(),
image_pointer_size);
}
// Clear the data pointer, it will be set if needed by the caller. if (!src->HasCodeItem() && !src->IsNative()) {
SetDataPtrSize(nullptr, image_pointer_size);
} // Clear hotness to let the JIT properly decide when to compile this method.
ResetCounter(jit::Jit::GetInitialHotnessThreshold());
}
bool ArtMethod::IsImagePointerSize(PointerSize pointer_size) { // Hijack this function to get access to PtrSizedFieldsOffset. // // Ensure that PrtSizedFieldsOffset is correct. We rely here on usually having both 32-bit and // 64-bit builds.
static_assert(std::is_standard_layout<ArtMethod>::value, "ArtMethod is not standard layout.");
static_assert(
(sizeof(void*) != 4) ||
(offsetof(ArtMethod, ptr_sized_fields_) == PtrSizedFieldsOffset(PointerSize::k32)), "Unexpected 32-bit class layout.");
static_assert(
(sizeof(void*) != 8) ||
(offsetof(ArtMethod, ptr_sized_fields_) == PtrSizedFieldsOffset(PointerSize::k64)), "Unexpected 64-bit class layout.");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.