diff options
84 files changed, 3255 insertions, 1153 deletions
diff --git a/build/Android.bp b/build/Android.bp index ed6de3546f..289834beb8 100644 --- a/build/Android.bp +++ b/build/Android.bp @@ -153,6 +153,12 @@ art_global_defaults { // No exceptions. "-misc-noexcept-move-constructor", ], + + tidy_flags: [ + // The static analyzer treats DCHECK as always enabled; we sometimes get + // false positives when we use DCHECKs with code that relies on NDEBUG. + "-extra-arg=-UNDEBUG", + ], } art_debug_defaults { diff --git a/compiler/dex/dex_to_dex_decompiler_test.cc b/compiler/dex/dex_to_dex_decompiler_test.cc index 7eaba960af..88426a3b5f 100644 --- a/compiler/dex/dex_to_dex_decompiler_test.cc +++ b/compiler/dex/dex_to_dex_decompiler_test.cc @@ -82,13 +82,7 @@ class DexToDexDecompilerTest : public CommonCompilerTest { continue; } ClassDataItemIterator it(*updated_dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); // Unquicken each method. while (it.HasNextDirectMethod()) { diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index ea53ef0670..622448fc59 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -808,13 +808,7 @@ static void ResolveConstStrings(CompilerDriver* driver, } ClassDataItemIterator it(*dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); bool compilation_enabled = driver->IsClassToCompile( dex_file->StringByTypeIdx(class_def.class_idx_)); @@ -1661,9 +1655,7 @@ bool CompilerDriver::RequiresConstructorBarrier(const DexFile& dex_file, return false; } ClassDataItemIterator it(dex_file, class_data); - while (it.HasNextStaticField()) { - it.Next(); - } + it.SkipStaticFields(); // We require a constructor barrier if there are final instance fields. while (it.HasNextInstanceField()) { if (it.MemberIsFinal()) { @@ -1873,13 +1865,7 @@ static void PopulateVerifiedMethods(const DexFile& dex_file, return; } ClassDataItemIterator it(dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); while (it.HasNextDirectMethod()) { verification_results->CreateVerifiedMethodFor(MethodReference(&dex_file, it.GetMemberIndex())); @@ -2778,13 +2764,7 @@ class CompileClassVisitor : public CompilationVisitor { GetDexToDexCompilationLevel(soa.Self(), *driver, jclass_loader, dex_file, class_def); ClassDataItemIterator it(dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); bool compilation_enabled = driver->IsClassToCompile( dex_file.StringByTypeIdx(class_def.class_idx_)); diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 1df9c4887c..f7465c0d5f 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -1676,12 +1676,7 @@ bool OatWriter::VisitDexMethods(DexMethodVisitor* visitor) { const uint8_t* class_data = dex_file->GetClassData(class_def); if (class_data != nullptr) { // ie not an empty class, such as a marker interface ClassDataItemIterator it(*dex_file, class_data); - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); size_t class_def_method_index = 0u; while (it.HasNextDirectMethod()) { if (!visitor->VisitMethod(class_def_method_index, it)) { diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index dd09fed06e..7e616a7af0 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -151,9 +151,7 @@ class VerifierDepsTest : public CommonCompilerTest { CHECK(class_data != nullptr); ClassDataItemIterator it(*primary_dex_file_, class_data); - while (it.HasNextStaticField() || it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); ArtMethod* method = nullptr; while (it.HasNextDirectMethod()) { diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc index 1541d7b39e..df0169f7d0 100644 --- a/dexdump/dexdump.cc +++ b/dexdump/dexdump.cc @@ -1376,12 +1376,7 @@ static void dumpCfg(const DexFile* dex_file, int idx) { return; } ClassDataItemIterator it(*dex_file, class_data); - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); while (it.HasNextDirectMethod()) { dumpCfg(dex_file, it.GetMemberIndex(), diff --git a/dexdump/dexdump_cfg.cc b/dexdump/dexdump_cfg.cc index 9e581280da..9c0429ff2b 100644 --- a/dexdump/dexdump_cfg.cc +++ b/dexdump/dexdump_cfg.cc @@ -373,10 +373,7 @@ void DumpMethodCFG(const DexFile* dex_file, uint32_t dex_method_idx, std::ostrea } ClassDataItemIterator it(*dex_file, class_data); - // Skip fields - while (it.HasNextStaticField() || it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); // Find method, and dump it. while (it.HasNextDirectMethod() || it.HasNextVirtualMethod()) { diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index 22f0cb042e..f886de24b6 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -1557,7 +1557,7 @@ void DexLayout::LayoutStringData(const DexFile* dex_file) { (method->GetAccessFlags() & kAccConstructor) != 0 && (method->GetAccessFlags() & kAccStatic) != 0; const bool method_executed = is_clinit || - info_->GetMethodHotness(MethodReference(dex_file, method_id->GetIndex())).HasAnyFlags(); + info_->GetMethodHotness(MethodReference(dex_file, method_id->GetIndex())).IsInProfile(); if (!method_executed) { continue; } @@ -1712,7 +1712,7 @@ int32_t DexLayout::LayoutCodeItems(const DexFile* dex_file, state = kCodeItemStateExecStartupOnly; } else if (is_clinit) { state = kCodeItemStateClinit; - } else if (hotness.HasAnyFlags()) { + } else if (hotness.IsInProfile()) { state = kCodeItemStateExec; } code_items[state].insert(code_item); diff --git a/dexlist/dexlist.cc b/dexlist/dexlist.cc index efe1aad7c6..29707af704 100644 --- a/dexlist/dexlist.cc +++ b/dexlist/dexlist.cc @@ -149,9 +149,7 @@ void dumpClass(const DexFile* pDexFile, u4 idx) { const u1* pEncodedData = pDexFile->GetClassData(pClassDef); if (pEncodedData != nullptr) { ClassDataItemIterator pClassData(*pDexFile, pEncodedData); - // Skip the fields. - for (; pClassData.HasNextStaticField(); pClassData.Next()) {} - for (; pClassData.HasNextInstanceField(); pClassData.Next()) {} + pClassData.SkipAllFields(); // Direct methods. for (; pClassData.HasNextDirectMethod(); pClassData.Next()) { dumpMethod(pDexFile, fileName, diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 9b95de2fb0..d8bafc011a 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -246,8 +246,7 @@ class OatSymbolizer FINAL { // might be a static initializer. ClassDataItemIterator it(dex_file, class_data); uint32_t class_method_idx = 0; - for (; it.HasNextStaticField(); it.Next()) { /* skip */ } - for (; it.HasNextInstanceField(); it.Next()) { /* skip */ } + it.SkipAllFields(); for (; it.HasNextDirectMethod() || it.HasNextVirtualMethod(); it.Next()) { WalkOatMethod(oat_class.GetOatMethod(class_method_idx++), dex_file, @@ -769,7 +768,7 @@ class OatDumper { const uint8_t* class_data = dex_file->GetClassData(class_def); if (class_data != nullptr) { ClassDataItemIterator it(*dex_file, class_data); - SkipAllFields(it); + it.SkipAllFields(); uint32_t class_method_index = 0; while (it.HasNextDirectMethod()) { AddOffsets(oat_class.GetOatMethod(class_method_index++)); @@ -856,7 +855,7 @@ class OatDumper { return; } ClassDataItemIterator it(dex_file, class_data); - SkipAllFields(it); + it.SkipAllFields(); while (it.HasNextDirectMethod()) { WalkCodeItem(dex_file, it.GetMethodCodeItem()); it.Next(); @@ -1076,15 +1075,6 @@ class OatDumper { return true; } - static void SkipAllFields(ClassDataItemIterator& it) { - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } - } - bool DumpOatClass(VariableIndentationOutputStream* vios, const OatFile::OatClass& oat_class, const DexFile& dex_file, const DexFile::ClassDef& class_def, bool* stop_analysis) { @@ -1096,7 +1086,7 @@ class OatDumper { return success; } ClassDataItemIterator it(dex_file, class_data); - SkipAllFields(it); + it.SkipAllFields(); uint32_t class_method_index = 0; while (it.HasNextDirectMethod()) { if (!DumpOatMethod(vios, class_def, class_method_index, oat_class, dex_file, @@ -1405,6 +1395,54 @@ class OatDumper { method_info); } + static int GetOutVROffset(uint16_t out_num, InstructionSet isa) { + // According to stack model, the first out is above the Method referernce. + return static_cast<size_t>(InstructionSetPointerSize(isa)) + out_num * sizeof(uint32_t); + } + + static uint32_t GetVRegOffsetFromQuickCode(const DexFile::CodeItem* code_item, + uint32_t core_spills, + uint32_t fp_spills, + size_t frame_size, + int reg, + InstructionSet isa) { + PointerSize pointer_size = InstructionSetPointerSize(isa); + if (kIsDebugBuild) { + auto* runtime = Runtime::Current(); + if (runtime != nullptr) { + CHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), pointer_size); + } + } + DCHECK_ALIGNED(frame_size, kStackAlignment); + DCHECK_NE(reg, -1); + int spill_size = POPCOUNT(core_spills) * GetBytesPerGprSpillLocation(isa) + + POPCOUNT(fp_spills) * GetBytesPerFprSpillLocation(isa) + + sizeof(uint32_t); // Filler. + int num_regs = code_item->registers_size_ - code_item->ins_size_; + int temp_threshold = code_item->registers_size_; + const int max_num_special_temps = 1; + if (reg == temp_threshold) { + // The current method pointer corresponds to special location on stack. + return 0; + } else if (reg >= temp_threshold + max_num_special_temps) { + /* + * Special temporaries may have custom locations and the logic above deals with that. + * However, non-special temporaries are placed relative to the outs. + */ + int temps_start = code_item->outs_size_ * sizeof(uint32_t) + + static_cast<size_t>(pointer_size) /* art method */; + int relative_offset = (reg - (temp_threshold + max_num_special_temps)) * sizeof(uint32_t); + return temps_start + relative_offset; + } else if (reg < num_regs) { + int locals_start = frame_size - spill_size - num_regs * sizeof(uint32_t); + return locals_start + (reg * sizeof(uint32_t)); + } else { + // Handle ins. + return frame_size + ((reg - num_regs) * sizeof(uint32_t)) + + static_cast<size_t>(pointer_size) /* art method */; + } + } + void DumpVregLocations(std::ostream& os, const OatFile::OatMethod& oat_method, const DexFile::CodeItem* code_item) { if (code_item != nullptr) { @@ -1424,13 +1462,12 @@ class OatDumper { os << "\n\tlocals:"; } - uint32_t offset = StackVisitor::GetVRegOffsetFromQuickCode( - code_item, - oat_method.GetCoreSpillMask(), - oat_method.GetFpSpillMask(), - oat_method.GetFrameSizeInBytes(), - reg, - GetInstructionSet()); + uint32_t offset = GetVRegOffsetFromQuickCode(code_item, + oat_method.GetCoreSpillMask(), + oat_method.GetFpSpillMask(), + oat_method.GetFrameSizeInBytes(), + reg, + GetInstructionSet()); os << " v" << reg << "[sp + #" << offset << "]"; } @@ -1439,7 +1476,7 @@ class OatDumper { os << "\n\touts:"; } - uint32_t offset = StackVisitor::GetOutVROffset(out_reg, GetInstructionSet()); + uint32_t offset = GetOutVROffset(out_reg, GetInstructionSet()); os << " v" << out_reg << "[sp + #" << offset << "]"; } diff --git a/profman/profman.cc b/profman/profman.cc index b46378e354..f763b8ea05 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -772,9 +772,7 @@ class ProfMan FINAL { const uint8_t* class_data = dex_file->GetClassData(*class_def); if (class_data != nullptr) { ClassDataItemIterator it(*dex_file, class_data); - while (it.HasNextStaticField() || it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); while (it.HasNextDirectMethod() || it.HasNextVirtualMethod()) { if (it.GetMethodCodeItemOffset() != 0) { // Add all of the methods that have code to the profile. @@ -851,7 +849,7 @@ class ProfMan FINAL { if (!profile->AddMethodIndex(static_cast<Hotness::Flag>(flags), ref)) { return false; } - DCHECK(profile->GetMethodHotness(ref).HasAnyFlags()); + DCHECK(profile->GetMethodHotness(ref).IsInProfile()); } return true; } diff --git a/runtime/Android.bp b/runtime/Android.bp index 26e52e012e..20f95c0c74 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -105,7 +105,10 @@ cc_defaults { "interpreter/interpreter_common.cc", "interpreter/interpreter_intrinsics.cc", "interpreter/interpreter_switch_impl.cc", + "interpreter/lock_count_data.cc", + "interpreter/shadow_frame.cc", "interpreter/unstarted_runtime.cc", + "java_frame_root_info.cc", "java_vm_ext.cc", "jdwp/jdwp_event.cc", "jdwp/jdwp_expand_buf.cc", diff --git a/runtime/art_method.cc b/runtime/art_method.cc index d591e0992c..32946ef0b4 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -433,13 +433,7 @@ static uint32_t GetOatMethodIndexFromMethodIndex(const DexFile& dex_file, const uint8_t* class_data = dex_file.GetClassData(class_def); CHECK(class_data != nullptr); ClassDataItemIterator it(dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); // Process methods size_t class_def_method_index = 0; while (it.HasNextDirectMethod()) { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 0fa25d15d2..71558e1820 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -3019,13 +3019,7 @@ void ClassLinker::FixupStaticTrampolines(ObjPtr<mirror::Class> klass) { // There should always be class data if there were direct methods. CHECK(class_data != nullptr) << klass->PrettyDescriptor(); ClassDataItemIterator it(dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); bool has_oat_class; OatFile::OatClass oat_class = OatFile::FindOatClass(dex_file, klass->GetDexClassDefIndex(), @@ -5451,25 +5445,10 @@ static void CountMethodsAndFields(ClassDataItemIterator& dex_data, size_t* direct_methods, size_t* static_fields, size_t* instance_fields) { - *virtual_methods = *direct_methods = *static_fields = *instance_fields = 0; - - while (dex_data.HasNextStaticField()) { - dex_data.Next(); - (*static_fields)++; - } - while (dex_data.HasNextInstanceField()) { - dex_data.Next(); - (*instance_fields)++; - } - while (dex_data.HasNextDirectMethod()) { - (*direct_methods)++; - dex_data.Next(); - } - while (dex_data.HasNextVirtualMethod()) { - (*virtual_methods)++; - dex_data.Next(); - } - DCHECK(!dex_data.HasNext()); + *static_fields = dex_data.NumStaticFields(); + *instance_fields = dex_data.NumInstanceFields(); + *direct_methods = dex_data.NumDirectMethods(); + *virtual_methods = dex_data.NumVirtualMethods(); } static void DumpClass(std::ostream& os, diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h index 133ddb0721..528db96dd5 100644 --- a/runtime/common_dex_operations.h +++ b/runtime/common_dex_operations.h @@ -62,7 +62,7 @@ inline void PerformCall(Thread* self, } template<Primitive::Type field_type> -static ALWAYS_INLINE void DoFieldGetCommon(Thread* self, +static ALWAYS_INLINE bool DoFieldGetCommon(Thread* self, const ShadowFrame& shadow_frame, ObjPtr<mirror::Object> obj, ArtField* field, @@ -85,6 +85,9 @@ static ALWAYS_INLINE void DoFieldGetCommon(Thread* self, shadow_frame.GetMethod(), shadow_frame.GetDexPC(), field); + if (UNLIKELY(self->IsExceptionPending())) { + return false; + } } switch (field_type) { @@ -113,6 +116,7 @@ static ALWAYS_INLINE void DoFieldGetCommon(Thread* self, LOG(FATAL) << "Unreachable " << field_type; break; } + return true; } template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active> @@ -120,7 +124,7 @@ ALWAYS_INLINE bool DoFieldPutCommon(Thread* self, const ShadowFrame& shadow_frame, ObjPtr<mirror::Object> obj, ArtField* field, - const JValue& value) + JValue& value) REQUIRES_SHARED(Locks::mutator_lock_) { field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self); @@ -128,15 +132,22 @@ ALWAYS_INLINE bool DoFieldPutCommon(Thread* self, // the field from the base of the object, we need to look for it first. instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldWriteListeners())) { - StackHandleScope<1> hs(self); - // Wrap in handle wrapper in case the listener does thread suspension. + StackHandleScope<2> hs(self); + // Save this and return value (if needed) in case the instrumentation causes a suspend. HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj)); ObjPtr<mirror::Object> this_object = field->IsStatic() ? nullptr : obj; - instrumentation->FieldWriteEvent(self, this_object.Ptr(), + mirror::Object* fake_root = nullptr; + HandleWrapper<mirror::Object> ret(hs.NewHandleWrapper<mirror::Object>( + field_type == Primitive::kPrimNot ? value.GetGCRoot() : &fake_root)); + instrumentation->FieldWriteEvent(self, + this_object.Ptr(), shadow_frame.GetMethod(), shadow_frame.GetDexPC(), field, value); + if (UNLIKELY(self->IsExceptionPending())) { + return false; + } } switch (field_type) { diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index b267e5f22a..6d1158260a 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -656,13 +656,7 @@ uint32_t DexFile::FindCodeItemOffset(const DexFile::ClassDef& class_def, const uint8_t* class_data = GetClassData(class_def); CHECK(class_data != nullptr); ClassDataItemIterator it(*this, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); while (it.HasNextDirectMethod()) { if (it.GetMemberIndex() == method_idx) { return it.GetMethodCodeItemOffset(); diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 3249edbe83..81a39afbee 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -1343,6 +1343,30 @@ class ClassDataItemIterator { bool HasNextVirtualMethod() const { return pos_ >= EndOfDirectMethodsPos() && pos_ < EndOfVirtualMethodsPos(); } + void SkipStaticFields() { + while (HasNextStaticField()) { + Next(); + } + } + void SkipInstanceFields() { + while (HasNextInstanceField()) { + Next(); + } + } + void SkipAllFields() { + SkipStaticFields(); + SkipInstanceFields(); + } + void SkipDirectMethods() { + while (HasNextDirectMethod()) { + Next(); + } + } + void SkipVirtualMethods() { + while (HasNextVirtualMethod()) { + Next(); + } + } bool HasNext() const { return pos_ < EndOfVirtualMethodsPos(); } diff --git a/runtime/dex_file_tracking_registrar.cc b/runtime/dex_file_tracking_registrar.cc index 848e2f3cd8..d958568e55 100644 --- a/runtime/dex_file_tracking_registrar.cc +++ b/runtime/dex_file_tracking_registrar.cc @@ -137,10 +137,7 @@ void DexFileTrackingRegistrar::SetAllCodeItemRegistration(bool should_poison) { const uint8_t* class_data = dex_file_->GetClassData(cd); if (class_data != nullptr) { ClassDataItemIterator cdit(*dex_file_, class_data); - // Skipping Fields - while (cdit.HasNextStaticField() || cdit.HasNextInstanceField()) { - cdit.Next(); - } + cdit.SkipAllFields(); while (cdit.HasNextDirectMethod()) { const DexFile::CodeItem* code_item = cdit.GetMethodCodeItem(); if (code_item != nullptr) { @@ -160,10 +157,7 @@ void DexFileTrackingRegistrar::SetAllInsnsRegistration(bool should_poison) { const uint8_t* class_data = dex_file_->GetClassData(cd); if (class_data != nullptr) { ClassDataItemIterator cdit(*dex_file_, class_data); - // Skipping Fields - while (cdit.HasNextStaticField() || cdit.HasNextInstanceField()) { - cdit.Next(); - } + cdit.SkipAllFields(); while (cdit.HasNextDirectMethod()) { const DexFile::CodeItem* code_item = cdit.GetMethodCodeItem(); if (code_item != nullptr) { @@ -184,10 +178,7 @@ void DexFileTrackingRegistrar::SetCodeItemRegistration(const char* class_name, b const uint8_t* class_data = dex_file_->GetClassData(cd); if (class_data != nullptr) { ClassDataItemIterator cdit(*dex_file_, class_data); - // Skipping Fields - while (cdit.HasNextStaticField() || cdit.HasNextInstanceField()) { - cdit.Next(); - } + cdit.SkipAllFields(); while (cdit.HasNextDirectMethod()) { const DexFile::MethodId& methodid_item = dex_file_->GetMethodId(cdit.GetMemberIndex()); const char * methodid_name = dex_file_->GetMethodName(methodid_item); diff --git a/runtime/dex_method_iterator.h b/runtime/dex_method_iterator.h index 7fae277c14..8a4bed31b1 100644 --- a/runtime/dex_method_iterator.h +++ b/runtime/dex_method_iterator.h @@ -66,13 +66,7 @@ class DexMethodIterator { } if (it_.get() == nullptr) { it_.reset(new ClassDataItemIterator(GetDexFileInternal(), class_data_)); - // Skip fields - while (GetIterator().HasNextStaticField()) { - GetIterator().Next(); - } - while (GetIterator().HasNextInstanceField()) { - GetIterator().Next(); - } + GetIterator().SkipAllFields(); direct_method_ = true; } if (direct_method_ && GetIterator().HasNextDirectMethod()) { diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index d06ac23d3c..1b36c3f12b 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -66,7 +66,11 @@ bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst } JValue result; - DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result); + if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result))) { + // Instrumentation threw an error! + CHECK(self->IsExceptionPending()); + return false; + } uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data); switch (field_type) { case Primitive::kPrimBoolean: @@ -149,14 +153,18 @@ bool DoIGetQuick(ShadowFrame& shadow_frame, const Instruction* inst, uint16_t in field_offset.Uint32Value()); DCHECK(f != nullptr); DCHECK(!f->IsStatic()); - StackHandleScope<1> hs(Thread::Current()); + Thread* self = Thread::Current(); + StackHandleScope<1> hs(self); // Save obj in case the instrumentation event has thread suspension. HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&obj); - instrumentation->FieldReadEvent(Thread::Current(), + instrumentation->FieldReadEvent(self, obj.Ptr(), shadow_frame.GetMethod(), shadow_frame.GetDexPC(), f); + if (UNLIKELY(self->IsExceptionPending())) { + return false; + } } // Note: iget-x-quick instructions are only for non-volatile fields. const uint32_t vregA = inst->VRegA_22c(inst_data); @@ -322,15 +330,22 @@ bool DoIPutQuick(const ShadowFrame& shadow_frame, const Instruction* inst, uint1 DCHECK(f != nullptr); DCHECK(!f->IsStatic()); JValue field_value = GetFieldValue<field_type>(shadow_frame, vregA); - StackHandleScope<1> hs(Thread::Current()); + Thread* self = Thread::Current(); + StackHandleScope<2> hs(self); // Save obj in case the instrumentation event has thread suspension. HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&obj); - instrumentation->FieldWriteEvent(Thread::Current(), + mirror::Object* fake_root = nullptr; + HandleWrapper<mirror::Object> ret(hs.NewHandleWrapper<mirror::Object>( + field_type == Primitive::kPrimNot ? field_value.GetGCRoot() : &fake_root)); + instrumentation->FieldWriteEvent(self, obj.Ptr(), shadow_frame.GetMethod(), shadow_frame.GetDexPC(), f, field_value); + if (UNLIKELY(self->IsExceptionPending())) { + return false; + } } // Note: iput-x-quick instructions are only for non-volatile fields. switch (field_type) { diff --git a/runtime/interpreter/lock_count_data.cc b/runtime/interpreter/lock_count_data.cc new file mode 100644 index 0000000000..64b59cd390 --- /dev/null +++ b/runtime/interpreter/lock_count_data.cc @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lock_count_data.h" + +#include <algorithm> +#include <string> + +#include "android-base/logging.h" +#include "mirror/object-inl.h" +#include "thread.h" + +namespace art { + +void LockCountData::AddMonitor(Thread* self, mirror::Object* obj) { + if (obj == nullptr) { + return; + } + + // If there's an error during enter, we won't have locked the monitor. So check there's no + // exception. + if (self->IsExceptionPending()) { + return; + } + + if (monitors_ == nullptr) { + monitors_.reset(new std::vector<mirror::Object*>()); + } + monitors_->push_back(obj); +} + +void LockCountData::RemoveMonitorOrThrow(Thread* self, const mirror::Object* obj) { + if (obj == nullptr) { + return; + } + bool found_object = false; + if (monitors_ != nullptr) { + // We need to remove one pointer to ref, as duplicates are used for counting recursive locks. + // We arbitrarily choose the first one. + auto it = std::find(monitors_->begin(), monitors_->end(), obj); + if (it != monitors_->end()) { + monitors_->erase(it); + found_object = true; + } + } + if (!found_object) { + // The object wasn't found. Time for an IllegalMonitorStateException. + // The order here isn't fully clear. Assume that any other pending exception is swallowed. + // TODO: Maybe make already pending exception a suppressed exception. + self->ClearException(); + self->ThrowNewExceptionF("Ljava/lang/IllegalMonitorStateException;", + "did not lock monitor on object of type '%s' before unlocking", + const_cast<mirror::Object*>(obj)->PrettyTypeOf().c_str()); + } +} + +// Helper to unlock a monitor. Must be NO_THREAD_SAFETY_ANALYSIS, as we can't statically show +// that the object was locked. +void MonitorExitHelper(Thread* self, mirror::Object* obj) NO_THREAD_SAFETY_ANALYSIS { + DCHECK(self != nullptr); + DCHECK(obj != nullptr); + obj->MonitorExit(self); +} + +bool LockCountData::CheckAllMonitorsReleasedOrThrow(Thread* self) { + DCHECK(self != nullptr); + if (monitors_ != nullptr) { + if (!monitors_->empty()) { + // There may be an exception pending, if the method is terminating abruptly. Clear it. + // TODO: Should we add this as a suppressed exception? + self->ClearException(); + + // OK, there are monitors that are still locked. To enforce structured locking (and avoid + // deadlocks) we unlock all of them before we raise the IllegalMonitorState exception. + for (mirror::Object* obj : *monitors_) { + MonitorExitHelper(self, obj); + // If this raised an exception, ignore. TODO: Should we add this as suppressed + // exceptions? + if (self->IsExceptionPending()) { + self->ClearException(); + } + } + // Raise an exception, just give the first object as the sample. + mirror::Object* first = (*monitors_)[0]; + self->ThrowNewExceptionF("Ljava/lang/IllegalMonitorStateException;", + "did not unlock monitor on object of type '%s'", + mirror::Object::PrettyTypeOf(first).c_str()); + + // To make sure this path is not triggered again, clean out the monitors. + monitors_->clear(); + + return false; + } + } + return true; +} + +} // namespace art diff --git a/runtime/interpreter/lock_count_data.h b/runtime/interpreter/lock_count_data.h new file mode 100644 index 0000000000..64874a5db7 --- /dev/null +++ b/runtime/interpreter/lock_count_data.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_INTERPRETER_LOCK_COUNT_DATA_H_ +#define ART_RUNTIME_INTERPRETER_LOCK_COUNT_DATA_H_ + +#include <memory> +#include <vector> + +#include "base/mutex.h" + +namespace art { + +namespace mirror { + class Object; +} // namespace mirror + +class Thread; + +// Counting locks by storing object pointers into a vector. Duplicate entries mark recursive locks. +// The vector will be visited with the ShadowFrame during GC (so all the locked-on objects are +// thread roots). +// Note: implementation is split so that the call sites may be optimized to no-ops in case no +// lock counting is necessary. The actual implementation is in the cc file to avoid +// dependencies. +class LockCountData { + public: + // Add the given object to the list of monitors, that is, objects that have been locked. This + // will not throw (but be skipped if there is an exception pending on entry). + void AddMonitor(Thread* self, mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); + + // Try to remove the given object from the monitor list, indicating an unlock operation. + // This will throw an IllegalMonitorStateException (clearing any already pending exception), in + // case that there wasn't a lock recorded for the object. + void RemoveMonitorOrThrow(Thread* self, + const mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); + + // Check whether all acquired monitors have been released. This will potentially throw an + // IllegalMonitorStateException, clearing any already pending exception. Returns true if the + // check shows that everything is OK wrt/ lock counting, false otherwise. + bool CheckAllMonitorsReleasedOrThrow(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_); + + template <typename T, typename... Args> + void VisitMonitors(T visitor, Args&&... args) REQUIRES_SHARED(Locks::mutator_lock_) { + if (monitors_ != nullptr) { + // Visitors may change the Object*. Be careful with the foreach loop. + for (mirror::Object*& obj : *monitors_) { + visitor(/* inout */ &obj, std::forward<Args>(args)...); + } + } + } + + private: + // Stores references to the locked-on objects. As noted, this should be visited during thread + // marking. + std::unique_ptr<std::vector<mirror::Object*>> monitors_; +}; + +} // namespace art + +#endif // ART_RUNTIME_INTERPRETER_LOCK_COUNT_DATA_H_ diff --git a/runtime/interpreter/shadow_frame.cc b/runtime/interpreter/shadow_frame.cc new file mode 100644 index 0000000000..ab154cf767 --- /dev/null +++ b/runtime/interpreter/shadow_frame.cc @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "shadow_frame.h" + +#include "art_method-inl.h" + +namespace art { + +mirror::Object* ShadowFrame::GetThisObject() const { + ArtMethod* m = GetMethod(); + if (m->IsStatic()) { + return nullptr; + } else if (m->IsNative()) { + return GetVRegReference(0); + } else { + const DexFile::CodeItem* code_item = m->GetCodeItem(); + CHECK(code_item != nullptr) << ArtMethod::PrettyMethod(m); + uint16_t reg = code_item->registers_size_ - code_item->ins_size_; + return GetVRegReference(reg); + } +} + +mirror::Object* ShadowFrame::GetThisObject(uint16_t num_ins) const { + ArtMethod* m = GetMethod(); + if (m->IsStatic()) { + return nullptr; + } else { + return GetVRegReference(NumberOfVRegs() - num_ins); + } +} + +} // namespace art diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h new file mode 100644 index 0000000000..69b2382cbc --- /dev/null +++ b/runtime/interpreter/shadow_frame.h @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_INTERPRETER_SHADOW_FRAME_H_ +#define ART_RUNTIME_INTERPRETER_SHADOW_FRAME_H_ + +#include <cstring> +#include <stdint.h> +#include <string> + +#include "base/macros.h" +#include "base/mutex.h" +#include "dex_file.h" +#include "lock_count_data.h" +#include "read_barrier.h" +#include "stack_reference.h" +#include "verify_object.h" + +namespace art { + +namespace mirror { + class Object; +} // namespace mirror + +class ArtMethod; +class ShadowFrame; +class Thread; +union JValue; + +// Forward declaration. Just calls the destructor. +struct ShadowFrameDeleter; +using ShadowFrameAllocaUniquePtr = std::unique_ptr<ShadowFrame, ShadowFrameDeleter>; + +// ShadowFrame has 2 possible layouts: +// - interpreter - separate VRegs and reference arrays. References are in the reference array. +// - JNI - just VRegs, but where every VReg holds a reference. +class ShadowFrame { + public: + // Compute size of ShadowFrame in bytes assuming it has a reference array. + static size_t ComputeSize(uint32_t num_vregs) { + return sizeof(ShadowFrame) + (sizeof(uint32_t) * num_vregs) + + (sizeof(StackReference<mirror::Object>) * num_vregs); + } + + // Create ShadowFrame in heap for deoptimization. + static ShadowFrame* CreateDeoptimizedFrame(uint32_t num_vregs, ShadowFrame* link, + ArtMethod* method, uint32_t dex_pc) { + uint8_t* memory = new uint8_t[ComputeSize(num_vregs)]; + return CreateShadowFrameImpl(num_vregs, link, method, dex_pc, memory); + } + + // Delete a ShadowFrame allocated on the heap for deoptimization. + static void DeleteDeoptimizedFrame(ShadowFrame* sf) { + sf->~ShadowFrame(); // Explicitly destruct. + uint8_t* memory = reinterpret_cast<uint8_t*>(sf); + delete[] memory; + } + + // Create a shadow frame in a fresh alloca. This needs to be in the context of the caller. + // Inlining doesn't work, the compiler will still undo the alloca. So this needs to be a macro. +#define CREATE_SHADOW_FRAME(num_vregs, link, method, dex_pc) ({ \ + size_t frame_size = ShadowFrame::ComputeSize(num_vregs); \ + void* alloca_mem = alloca(frame_size); \ + ShadowFrameAllocaUniquePtr( \ + ShadowFrame::CreateShadowFrameImpl((num_vregs), (link), (method), (dex_pc), \ + (alloca_mem))); \ + }) + + ~ShadowFrame() {} + + // TODO(iam): Clean references array up since they're always there, + // we don't need to do conditionals. + bool HasReferenceArray() const { + return true; + } + + uint32_t NumberOfVRegs() const { + return number_of_vregs_; + } + + uint32_t GetDexPC() const { + return (dex_pc_ptr_ == nullptr) ? dex_pc_ : dex_pc_ptr_ - code_item_->insns_; + } + + int16_t GetCachedHotnessCountdown() const { + return cached_hotness_countdown_; + } + + void SetCachedHotnessCountdown(int16_t cached_hotness_countdown) { + cached_hotness_countdown_ = cached_hotness_countdown; + } + + int16_t GetHotnessCountdown() const { + return hotness_countdown_; + } + + void SetHotnessCountdown(int16_t hotness_countdown) { + hotness_countdown_ = hotness_countdown; + } + + void SetDexPC(uint32_t dex_pc) { + dex_pc_ = dex_pc; + dex_pc_ptr_ = nullptr; + } + + ShadowFrame* GetLink() const { + return link_; + } + + void SetLink(ShadowFrame* frame) { + DCHECK_NE(this, frame); + link_ = frame; + } + + int32_t GetVReg(size_t i) const { + DCHECK_LT(i, NumberOfVRegs()); + const uint32_t* vreg = &vregs_[i]; + return *reinterpret_cast<const int32_t*>(vreg); + } + + // Shorts are extended to Ints in VRegs. Interpreter intrinsics needs them as shorts. + int16_t GetVRegShort(size_t i) const { + return static_cast<int16_t>(GetVReg(i)); + } + + uint32_t* GetVRegAddr(size_t i) { + return &vregs_[i]; + } + + uint32_t* GetShadowRefAddr(size_t i) { + DCHECK(HasReferenceArray()); + DCHECK_LT(i, NumberOfVRegs()); + return &vregs_[i + NumberOfVRegs()]; + } + + void SetCodeItem(const DexFile::CodeItem* code_item) { + code_item_ = code_item; + } + + const DexFile::CodeItem* GetCodeItem() const { + return code_item_; + } + + float GetVRegFloat(size_t i) const { + DCHECK_LT(i, NumberOfVRegs()); + // NOTE: Strict-aliasing? + const uint32_t* vreg = &vregs_[i]; + return *reinterpret_cast<const float*>(vreg); + } + + int64_t GetVRegLong(size_t i) const { + DCHECK_LT(i, NumberOfVRegs()); + const uint32_t* vreg = &vregs_[i]; + typedef const int64_t unaligned_int64 __attribute__ ((aligned (4))); + return *reinterpret_cast<unaligned_int64*>(vreg); + } + + double GetVRegDouble(size_t i) const { + DCHECK_LT(i, NumberOfVRegs()); + const uint32_t* vreg = &vregs_[i]; + typedef const double unaligned_double __attribute__ ((aligned (4))); + return *reinterpret_cast<unaligned_double*>(vreg); + } + + // Look up the reference given its virtual register number. + // If this returns non-null then this does not mean the vreg is currently a reference + // on non-moving collectors. Check that the raw reg with GetVReg is equal to this if not certain. + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + mirror::Object* GetVRegReference(size_t i) const REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK_LT(i, NumberOfVRegs()); + mirror::Object* ref; + if (HasReferenceArray()) { + ref = References()[i].AsMirrorPtr(); + } else { + const uint32_t* vreg_ptr = &vregs_[i]; + ref = reinterpret_cast<const StackReference<mirror::Object>*>(vreg_ptr)->AsMirrorPtr(); + } + if (kUseReadBarrier) { + ReadBarrier::AssertToSpaceInvariant(ref); + } + if (kVerifyFlags & kVerifyReads) { + VerifyObject(ref); + } + return ref; + } + + // Get view of vregs as range of consecutive arguments starting at i. + uint32_t* GetVRegArgs(size_t i) { + return &vregs_[i]; + } + + void SetVReg(size_t i, int32_t val) { + DCHECK_LT(i, NumberOfVRegs()); + uint32_t* vreg = &vregs_[i]; + *reinterpret_cast<int32_t*>(vreg) = val; + // This is needed for moving collectors since these can update the vreg references if they + // happen to agree with references in the reference array. + if (kMovingCollector && HasReferenceArray()) { + References()[i].Clear(); + } + } + + void SetVRegFloat(size_t i, float val) { + DCHECK_LT(i, NumberOfVRegs()); + uint32_t* vreg = &vregs_[i]; + *reinterpret_cast<float*>(vreg) = val; + // This is needed for moving collectors since these can update the vreg references if they + // happen to agree with references in the reference array. + if (kMovingCollector && HasReferenceArray()) { + References()[i].Clear(); + } + } + + void SetVRegLong(size_t i, int64_t val) { + DCHECK_LT(i, NumberOfVRegs()); + uint32_t* vreg = &vregs_[i]; + typedef int64_t unaligned_int64 __attribute__ ((aligned (4))); + *reinterpret_cast<unaligned_int64*>(vreg) = val; + // This is needed for moving collectors since these can update the vreg references if they + // happen to agree with references in the reference array. + if (kMovingCollector && HasReferenceArray()) { + References()[i].Clear(); + References()[i + 1].Clear(); + } + } + + void SetVRegDouble(size_t i, double val) { + DCHECK_LT(i, NumberOfVRegs()); + uint32_t* vreg = &vregs_[i]; + typedef double unaligned_double __attribute__ ((aligned (4))); + *reinterpret_cast<unaligned_double*>(vreg) = val; + // This is needed for moving collectors since these can update the vreg references if they + // happen to agree with references in the reference array. + if (kMovingCollector && HasReferenceArray()) { + References()[i].Clear(); + References()[i + 1].Clear(); + } + } + + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + void SetVRegReference(size_t i, mirror::Object* val) REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK_LT(i, NumberOfVRegs()); + if (kVerifyFlags & kVerifyWrites) { + VerifyObject(val); + } + if (kUseReadBarrier) { + ReadBarrier::AssertToSpaceInvariant(val); + } + uint32_t* vreg = &vregs_[i]; + reinterpret_cast<StackReference<mirror::Object>*>(vreg)->Assign(val); + if (HasReferenceArray()) { + References()[i].Assign(val); + } + } + + void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_) { + DCHECK(method != nullptr); + DCHECK(method_ != nullptr); + method_ = method; + } + + ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(method_ != nullptr); + return method_; + } + + mirror::Object* GetThisObject() const REQUIRES_SHARED(Locks::mutator_lock_); + + mirror::Object* GetThisObject(uint16_t num_ins) const REQUIRES_SHARED(Locks::mutator_lock_); + + bool Contains(StackReference<mirror::Object>* shadow_frame_entry_obj) const { + if (HasReferenceArray()) { + return ((&References()[0] <= shadow_frame_entry_obj) && + (shadow_frame_entry_obj <= (&References()[NumberOfVRegs() - 1]))); + } else { + uint32_t* shadow_frame_entry = reinterpret_cast<uint32_t*>(shadow_frame_entry_obj); + return ((&vregs_[0] <= shadow_frame_entry) && + (shadow_frame_entry <= (&vregs_[NumberOfVRegs() - 1]))); + } + } + + LockCountData& GetLockCountData() { + return lock_count_data_; + } + + static size_t LockCountDataOffset() { + return OFFSETOF_MEMBER(ShadowFrame, lock_count_data_); + } + + static size_t LinkOffset() { + return OFFSETOF_MEMBER(ShadowFrame, link_); + } + + static size_t MethodOffset() { + return OFFSETOF_MEMBER(ShadowFrame, method_); + } + + static size_t DexPCOffset() { + return OFFSETOF_MEMBER(ShadowFrame, dex_pc_); + } + + static size_t NumberOfVRegsOffset() { + return OFFSETOF_MEMBER(ShadowFrame, number_of_vregs_); + } + + static size_t VRegsOffset() { + return OFFSETOF_MEMBER(ShadowFrame, vregs_); + } + + static size_t ResultRegisterOffset() { + return OFFSETOF_MEMBER(ShadowFrame, result_register_); + } + + static size_t DexPCPtrOffset() { + return OFFSETOF_MEMBER(ShadowFrame, dex_pc_ptr_); + } + + static size_t CodeItemOffset() { + return OFFSETOF_MEMBER(ShadowFrame, code_item_); + } + + static size_t CachedHotnessCountdownOffset() { + return OFFSETOF_MEMBER(ShadowFrame, cached_hotness_countdown_); + } + + static size_t HotnessCountdownOffset() { + return OFFSETOF_MEMBER(ShadowFrame, hotness_countdown_); + } + + // Create ShadowFrame for interpreter using provided memory. + static ShadowFrame* CreateShadowFrameImpl(uint32_t num_vregs, + ShadowFrame* link, + ArtMethod* method, + uint32_t dex_pc, + void* memory) { + return new (memory) ShadowFrame(num_vregs, link, method, dex_pc, true); + } + + const uint16_t* GetDexPCPtr() { + return dex_pc_ptr_; + } + + void SetDexPCPtr(uint16_t* dex_pc_ptr) { + dex_pc_ptr_ = dex_pc_ptr; + } + + JValue* GetResultRegister() { + return result_register_; + } + + private: + ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method, + uint32_t dex_pc, bool has_reference_array) + : link_(link), + method_(method), + result_register_(nullptr), + dex_pc_ptr_(nullptr), + code_item_(nullptr), + number_of_vregs_(num_vregs), + dex_pc_(dex_pc), + cached_hotness_countdown_(0), + hotness_countdown_(0) { + // TODO(iam): Remove this parameter, it's an an artifact of portable removal + DCHECK(has_reference_array); + if (has_reference_array) { + memset(vregs_, 0, num_vregs * (sizeof(uint32_t) + sizeof(StackReference<mirror::Object>))); + } else { + memset(vregs_, 0, num_vregs * sizeof(uint32_t)); + } + } + + const StackReference<mirror::Object>* References() const { + DCHECK(HasReferenceArray()); + const uint32_t* vreg_end = &vregs_[NumberOfVRegs()]; + return reinterpret_cast<const StackReference<mirror::Object>*>(vreg_end); + } + + StackReference<mirror::Object>* References() { + return const_cast<StackReference<mirror::Object>*>( + const_cast<const ShadowFrame*>(this)->References()); + } + + // Link to previous shadow frame or null. + ShadowFrame* link_; + ArtMethod* method_; + JValue* result_register_; + const uint16_t* dex_pc_ptr_; + const DexFile::CodeItem* code_item_; + LockCountData lock_count_data_; // This may contain GC roots when lock counting is active. + const uint32_t number_of_vregs_; + uint32_t dex_pc_; + int16_t cached_hotness_countdown_; + int16_t hotness_countdown_; + + // This is a two-part array: + // - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4 + // bytes. + // - [number_of_vregs..number_of_vregs*2) holds only reference registers. Each element here is + // ptr-sized. + // In other words when a primitive is stored in vX, the second (reference) part of the array will + // be null. When a reference is stored in vX, the second (reference) part of the array will be a + // copy of vX. + uint32_t vregs_[0]; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ShadowFrame); +}; + +struct ShadowFrameDeleter { + inline void operator()(ShadowFrame* frame) { + if (frame != nullptr) { + frame->~ShadowFrame(); + } + } +}; + +} // namespace art + +#endif // ART_RUNTIME_INTERPRETER_SHADOW_FRAME_H_ diff --git a/runtime/java_frame_root_info.cc b/runtime/java_frame_root_info.cc new file mode 100644 index 0000000000..dd3be5d415 --- /dev/null +++ b/runtime/java_frame_root_info.cc @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "java_frame_root_info.h" + +#include "stack.h" + +namespace art { + +void JavaFrameRootInfo::Describe(std::ostream& os) const { + const StackVisitor* visitor = stack_visitor_; + CHECK(visitor != nullptr); + os << "Type=" << GetType() << " thread_id=" << GetThreadId() << " location=" << + visitor->DescribeLocation() << " vreg=" << vreg_; +} + +} // namespace art diff --git a/runtime/java_frame_root_info.h b/runtime/java_frame_root_info.h new file mode 100644 index 0000000000..25ac6e2a31 --- /dev/null +++ b/runtime/java_frame_root_info.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_JAVA_FRAME_ROOT_INFO_H_ +#define ART_RUNTIME_JAVA_FRAME_ROOT_INFO_H_ + +#include <iosfwd> + +#include "base/macros.h" +#include "base/mutex.h" +#include "gc_root.h" + +namespace art { + +class StackVisitor; + +class JavaFrameRootInfo FINAL : public RootInfo { + public: + JavaFrameRootInfo(uint32_t thread_id, const StackVisitor* stack_visitor, size_t vreg) + : RootInfo(kRootJavaFrame, thread_id), stack_visitor_(stack_visitor), vreg_(vreg) { + } + void Describe(std::ostream& os) const OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_); + + size_t GetVReg() const { + return vreg_; + } + const StackVisitor* GetVisitor() const { + return stack_visitor_; + } + + private: + const StackVisitor* const stack_visitor_; + const size_t vreg_; +}; + +} // namespace art + +#endif // ART_RUNTIME_JAVA_FRAME_ROOT_INFO_H_ diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 175563ab9c..960030d577 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -1154,6 +1154,8 @@ bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) { // Note that the number of elements should be very small, so this should not // be a performance issue. for (const DexFileData* other_dex_data : other.info_) { + // verify_checksum is false because we want to differentiate between a missing dex data and + // a mismatched checksum. const DexFileData* dex_data = FindDexData(other_dex_data->profile_key, 0u, /* verify_checksum */ false); @@ -1251,11 +1253,11 @@ std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> ProfileCompila uint32_t dex_checksum, uint16_t dex_method_index) const { MethodHotness hotness(GetMethodHotness(dex_location, dex_checksum, dex_method_index)); - const InlineCacheMap* inline_caches = hotness.GetInlineCacheMap(); - if (inline_caches == nullptr) { + if (!hotness.IsHot()) { return nullptr; } - + const InlineCacheMap* inline_caches = hotness.GetInlineCacheMap(); + DCHECK(inline_caches != nullptr); std::unique_ptr<OfflineProfileMethodInfo> pmi(new OfflineProfileMethodInfo(inline_caches)); pmi->dex_references.resize(info_.size()); @@ -1596,6 +1598,38 @@ ProfileCompilationInfo::DexFileData::FindOrAddMethod(uint16_t method_index) { InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile)))->second); } +// Mark a method as executed at least once. +void ProfileCompilationInfo::DexFileData::AddMethod(MethodHotness::Flag flags, size_t index) { + if ((flags & MethodHotness::kFlagStartup) != 0) { + method_bitmap.StoreBit(MethodBitIndex(/*startup*/ true, index), /*value*/ true); + } + if ((flags & MethodHotness::kFlagPostStartup) != 0) { + method_bitmap.StoreBit(MethodBitIndex(/*startup*/ false, index), /*value*/ true); + } + if ((flags & MethodHotness::kFlagHot) != 0) { + method_map.FindOrAdd( + index, + InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile))); + } +} + +ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::DexFileData::GetHotnessInfo( + uint32_t dex_method_index) const { + MethodHotness ret; + if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ true, dex_method_index))) { + ret.AddFlag(MethodHotness::kFlagStartup); + } + if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ false, dex_method_index))) { + ret.AddFlag(MethodHotness::kFlagPostStartup); + } + auto it = method_map.find(dex_method_index); + if (it != method_map.end()) { + ret.SetInlineCacheMap(&it->second); + ret.AddFlag(MethodHotness::kFlagHot); + } + return ret; +} + ProfileCompilationInfo::DexPcData* ProfileCompilationInfo::FindOrAddDexPc(InlineCacheMap* inline_cache, uint32_t dex_pc) { return &(inline_cache->FindOrAdd(dex_pc, DexPcData(&arena_))->second); diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index b2d541f896..8d1e578875 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -198,10 +198,14 @@ class ProfileCompilationInfo { return flags_; } - bool HasAnyFlags() const { + bool IsInProfile() const { return flags_ != 0; } + private: + const InlineCacheMap* inline_cache_map_ = nullptr; + uint8_t flags_ = 0; + const InlineCacheMap* GetInlineCacheMap() const { return inline_cache_map_; } @@ -210,9 +214,7 @@ class ProfileCompilationInfo { inline_cache_map_ = info; } - private: - const InlineCacheMap* inline_cache_map_ = nullptr; - uint8_t flags_ = 0; + friend class ProfileCompilationInfo; }; // Encodes the full set of inline caches for a given method. @@ -421,19 +423,7 @@ class ProfileCompilationInfo { } // Mark a method as executed at least once. - void AddMethod(MethodHotness::Flag flags, size_t index) { - if ((flags & MethodHotness::kFlagStartup) != 0) { - method_bitmap.StoreBit(MethodBitIndex(/*startup*/ true, index), /*value*/ true); - } - if ((flags & MethodHotness::kFlagPostStartup) != 0) { - method_bitmap.StoreBit(MethodBitIndex(/*startup*/ false, index), /*value*/ true); - } - if ((flags & MethodHotness::kFlagHot) != 0) { - method_map.FindOrAdd( - index, - InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile))); - } - } + void AddMethod(MethodHotness::Flag flags, size_t index); void MergeBitmap(const DexFileData& other) { DCHECK_EQ(bitmap_storage.size(), other.bitmap_storage.size()); @@ -442,21 +432,7 @@ class ProfileCompilationInfo { } } - MethodHotness GetHotnessInfo(uint32_t dex_method_index) const { - MethodHotness ret; - if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ true, dex_method_index))) { - ret.AddFlag(MethodHotness::kFlagStartup); - } - if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ false, dex_method_index))) { - ret.AddFlag(MethodHotness::kFlagPostStartup); - } - auto it = method_map.find(dex_method_index); - if (it != method_map.end()) { - ret.SetInlineCacheMap(&it->second); - ret.AddFlag(MethodHotness::kFlagHot); - } - return ret; - } + MethodHotness GetHotnessInfo(uint32_t dex_method_index) const; // The arena used to allocate new inline cache maps. ArenaAllocator* arena_; diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index c3a34156ad..1ba98acab8 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -868,8 +868,8 @@ TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) { test_info.AddMethodIndex(Hotness::kFlagStartup, kDex2, kChecksum2, 2, kNumMethods); test_info.AddMethodIndex(Hotness::kFlagPostStartup, kDex2, kChecksum2, 4, kNumMethods); auto run_test = [](const ProfileCompilationInfo& info) { - EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 2).HasAnyFlags()); - EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 4).HasAnyFlags()); + EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 2).IsInProfile()); + EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 4).IsInProfile()); EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 1).IsStartup()); EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 3).IsStartup()); EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 5).IsPostStartup()); @@ -931,9 +931,9 @@ TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) { } EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), 6)).IsPostStartup()); // Check that methods that shouldn't have been touched are OK. - EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), 0)).HasAnyFlags()); - EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 4)).HasAnyFlags()); - EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 7)).HasAnyFlags()); + EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), 0)).IsInProfile()); + EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 4)).IsInProfile()); + EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 7)).IsInProfile()); EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 1)).IsPostStartup()); EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 4)).IsStartup()); EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 6)).IsStartup()); diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index 94363c6f2d..b41bc78170 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -681,7 +681,7 @@ bool ProfileSaver::HasSeenMethod(const std::string& profile, if (!info.Load(profile, /*clear_if_invalid*/false)) { return false; } - return info.GetMethodHotness(MethodReference(dex_file, method_idx)).HasAnyFlags(); + return info.GetMethodHotness(MethodReference(dex_file, method_idx)).IsInProfile(); } return false; } diff --git a/runtime/managed_stack-inl.h b/runtime/managed_stack-inl.h index f3f31cf8e8..bdf8100cc0 100644 --- a/runtime/managed_stack-inl.h +++ b/runtime/managed_stack-inl.h @@ -23,7 +23,7 @@ #include <stdint.h> #include <string> -#include "stack.h" +#include "interpreter/shadow_frame.h" namespace art { diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc index 090bac1173..f0d3cae4b4 100644 --- a/runtime/method_handles.cc +++ b/runtime/method_handles.cc @@ -822,7 +822,7 @@ inline bool DoFieldPutForInvokePolymorphic(Thread* self, ObjPtr<mirror::Object>& obj, ArtField* field, Primitive::Type field_type, - const JValue& value) + JValue& value) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!Runtime::Current()->IsActiveTransaction()); static const bool kTransaction = false; // Not in a transaction. diff --git a/runtime/method_handles.h b/runtime/method_handles.h index e02e62052c..55680f09e7 100644 --- a/runtime/method_handles.h +++ b/runtime/method_handles.h @@ -21,9 +21,9 @@ #include "dex_instruction.h" #include "handle.h" +#include "interpreter/shadow_frame.h" #include "jvalue.h" #include "mirror/class.h" -#include "stack.h" namespace art { @@ -32,8 +32,6 @@ namespace mirror { class MethodType; } // namespace mirror -class ShadowFrame; - // Returns true if there is a possible conversion from |from| to |to| // for a MethodHandle parameter. bool IsParameterTypeConvertible(ObjPtr<mirror::Class> from, diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 3ec5b323c8..0896210f1c 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -635,36 +635,28 @@ class JvmtiFunctions { return ERR(NOT_IMPLEMENTED); } - static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - jfieldID field ATTRIBUTE_UNUSED) { + static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_field_access_events); - return ERR(NOT_IMPLEMENTED); + return FieldUtil::SetFieldAccessWatch(env, klass, field); } - static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - jfieldID field ATTRIBUTE_UNUSED) { + static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_field_access_events); - return ERR(NOT_IMPLEMENTED); + return FieldUtil::ClearFieldAccessWatch(env, klass, field); } - static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - jfieldID field ATTRIBUTE_UNUSED) { + static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_field_modification_events); - return ERR(NOT_IMPLEMENTED); + return FieldUtil::SetFieldModificationWatch(env, klass, field); } - static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - jfieldID field ATTRIBUTE_UNUSED) { + static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_field_modification_events); - return ERR(NOT_IMPLEMENTED); + return FieldUtil::ClearFieldModificationWatch(env, klass, field); } static jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr) { @@ -694,12 +686,10 @@ class JvmtiFunctions { return ClassUtil::GetClassStatus(env, klass, status_ptr); } - static jvmtiError GetSourceFileName(jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - char** source_name_ptr ATTRIBUTE_UNUSED) { + static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_get_source_file_name); - return ERR(NOT_IMPLEMENTED); + return ClassUtil::GetSourceFileName(env, klass, source_name_ptr); } static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr) { @@ -774,11 +764,11 @@ class JvmtiFunctions { } static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - char** source_debug_extension_ptr ATTRIBUTE_UNUSED) { + jclass klass, + char** source_debug_extension_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_get_source_debug_extension); - return ERR(NOT_IMPLEMENTED); + return ClassUtil::GetSourceDebugExtension(env, klass, source_debug_extension_ptr); } static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 369b2d7c00..b5f12191e6 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -34,6 +34,7 @@ #include <memory> #include <type_traits> +#include <unordered_set> #include <jni.h> @@ -46,6 +47,10 @@ #include "jni_env_ext.h" #include "jvmti.h" +namespace art { +class ArtField; +} + namespace openjdkjvmti { class ObjectTagTable; @@ -62,6 +67,15 @@ struct ArtJvmTiEnv : public jvmtiEnv { // Tagging is specific to the jvmtiEnv. std::unique_ptr<ObjectTagTable> object_tag_table; + // Set of watched fields is unique to each jvmtiEnv. + // TODO It might be good to follow the RI and only let one jvmtiEnv ever have the watch caps so + // we can record this on the field directly. We could do this either using free access-flag bits + // or by putting a list in the ClassExt of a field's DeclaringClass. + // TODO Maybe just have an extension to let one put a watch on every field, that would probably be + // good enough maybe since you probably want either a few or all/almost all of them. + std::unordered_set<art::ArtField*> access_watched_fields; + std::unordered_set<art::ArtField*> modify_watched_fields; + ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler); static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) { @@ -194,8 +208,8 @@ static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, const jvmtiCapabilities kPotentialCapabilities = { .can_tag_objects = 1, - .can_generate_field_modification_events = 0, - .can_generate_field_access_events = 0, + .can_generate_field_modification_events = 1, + .can_generate_field_access_events = 1, .can_get_bytecodes = 0, .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, @@ -204,9 +218,9 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_pop_frame = 0, .can_redefine_classes = 1, .can_signal_thread = 0, - .can_get_source_file_name = 0, + .can_get_source_file_name = 1, .can_get_line_numbers = 1, - .can_get_source_debug_extension = 0, + .can_get_source_debug_extension = 1, .can_access_local_variables = 0, .can_maintain_original_method_order = 0, .can_generate_single_step_events = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index cb7e6a9ad0..af99233f90 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -20,6 +20,7 @@ #include <array> #include "events.h" +#include "jni_internal.h" #include "ScopedLocalRef.h" #include "art_jvmti.h" @@ -216,6 +217,71 @@ inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, A } } +// Need to give custom specializations for FieldAccess and FieldModification since they need to +// filter out which particular fields agents want to get notified on. +// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This +// could make the system more performant. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field, + char type_char, + jvalue val) const { + for (ArtJvmTiEnv* env : envs) { + if (env != nullptr && + ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) && + env->modify_watched_fields.find( + art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) { + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); + auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env); + (*callback)(env, + jnienv, + jni_thread, + method, + location, + field_klass, + object, + field, + type_char, + val); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } + } + } +} + +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field) const { + for (ArtJvmTiEnv* env : envs) { + if (env != nullptr && + ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) && + env->access_watched_fields.find( + art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) { + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); + auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env); + (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } + } + } +} + // Need to give a custom specialization for NativeMethodBind since it has to deal with an out // variable. template <> diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 90bc122220..989b9af591 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -33,6 +33,7 @@ #include "art_jvmti.h" #include "art_method-inl.h" +#include "art_field-inl.h" #include "base/logging.h" #include "gc/allocation_listener.h" #include "gc/gc_pause_listener.h" @@ -433,24 +434,92 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat } // Call-back for when we read from a field. - void FieldRead(art::Thread* self ATTRIBUTE_UNUSED, - art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, - art::ArtMethod* method ATTRIBUTE_UNUSED, - uint32_t dex_pc ATTRIBUTE_UNUSED, - art::ArtField* field ATTRIBUTE_UNUSED) + void FieldRead(art::Thread* self, + art::Handle<art::mirror::Object> this_object, + art::ArtMethod* method, + uint32_t dex_pc, + art::ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { - return; + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldAccess)) { + art::JNIEnvExt* jnienv = self->GetJniEnv(); + // DCHECK(!self->IsExceptionPending()); + ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get())); + ScopedLocalRef<jobject> fklass(jnienv, + AddLocalRef<jobject>(jnienv, + field->GetDeclaringClass().Ptr())); + RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self, + jnienv, + art::jni::EncodeArtMethod(method), + static_cast<jlocation>(dex_pc), + static_cast<jclass>(fklass.get()), + this_ref.get(), + art::jni::EncodeArtField(field)); + } + } + + void FieldWritten(art::Thread* self, + art::Handle<art::mirror::Object> this_object, + art::ArtMethod* method, + uint32_t dex_pc, + art::ArtField* field, + art::Handle<art::mirror::Object> new_val) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) { + art::JNIEnvExt* jnienv = self->GetJniEnv(); + // DCHECK(!self->IsExceptionPending()); + ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get())); + ScopedLocalRef<jobject> fklass(jnienv, + AddLocalRef<jobject>(jnienv, + field->GetDeclaringClass().Ptr())); + ScopedLocalRef<jobject> fval(jnienv, AddLocalRef<jobject>(jnienv, new_val.Get())); + jvalue val; + val.l = fval.get(); + RunEventCallback<ArtJvmtiEvent::kFieldModification>( + self, + jnienv, + art::jni::EncodeArtMethod(method), + static_cast<jlocation>(dex_pc), + static_cast<jclass>(fklass.get()), + field->IsStatic() ? nullptr : this_ref.get(), + art::jni::EncodeArtField(field), + 'L', // type_char + val); + } } // Call-back for when we write into a field. - void FieldWritten(art::Thread* self ATTRIBUTE_UNUSED, - art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, - art::ArtMethod* method ATTRIBUTE_UNUSED, - uint32_t dex_pc ATTRIBUTE_UNUSED, - art::ArtField* field ATTRIBUTE_UNUSED, - const art::JValue& field_value ATTRIBUTE_UNUSED) + void FieldWritten(art::Thread* self, + art::Handle<art::mirror::Object> this_object, + art::ArtMethod* method, + uint32_t dex_pc, + art::ArtField* field, + const art::JValue& field_value) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { - return; + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) { + art::JNIEnvExt* jnienv = self->GetJniEnv(); + DCHECK(!self->IsExceptionPending()); + ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get())); + ScopedLocalRef<jobject> fklass(jnienv, + AddLocalRef<jobject>(jnienv, + field->GetDeclaringClass().Ptr())); + char type_char = art::Primitive::Descriptor(field->GetTypeAsPrimitiveType())[0]; + jvalue val; + // 64bit integer is the largest value in the union so we should be fine simply copying it into + // the union. + val.j = field_value.GetJ(); + RunEventCallback<ArtJvmtiEvent::kFieldModification>( + self, + jnienv, + art::jni::EncodeArtMethod(method), + static_cast<jlocation>(dex_pc), + static_cast<jclass>(fklass.get()), + field->IsStatic() ? nullptr : this_ref.get(), // nb static field modification get given + // the class as this_object for some + // reason. + art::jni::EncodeArtField(field), + type_char, + val); + } } // Call-back when an exception is caught. @@ -490,15 +559,20 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { case ArtJvmtiEvent::kMethodExit: return art::instrumentation::Instrumentation::kMethodExited | art::instrumentation::Instrumentation::kMethodUnwind; + case ArtJvmtiEvent::kFieldModification: + return art::instrumentation::Instrumentation::kFieldWritten; + case ArtJvmtiEvent::kFieldAccess: + return art::instrumentation::Instrumentation::kFieldRead; default: LOG(FATAL) << "Unknown event "; return 0; } } -static void SetupMethodTraceListener(JvmtiMethodTraceListener* listener, - ArtJvmtiEvent event, - bool enable) { +static void SetupTraceListener(JvmtiMethodTraceListener* listener, + ArtJvmtiEvent event, + bool enable) { + art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative); uint32_t new_events = GetInstrumentationEventsFor(event); art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(), @@ -529,7 +603,9 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { case ArtJvmtiEvent::kMethodEntry: case ArtJvmtiEvent::kMethodExit: - SetupMethodTraceListener(method_trace_listener_.get(), event, enable); + case ArtJvmtiEvent::kFieldAccess: + case ArtJvmtiEvent::kFieldModification: + SetupTraceListener(method_trace_listener_.get(), event, enable); return; default: diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 332181497c..0ac08d9cb8 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -1017,4 +1017,61 @@ jvmtiError ClassUtil::GetClassVersionNumbers(jvmtiEnv* env ATTRIBUTE_UNUSED, return ERR(NONE); } +jvmtiError ClassUtil::GetSourceFileName(jvmtiEnv* env, jclass jklass, char** source_name_ptr) { + art::ScopedObjectAccess soa(art::Thread::Current()); + if (jklass == nullptr) { + return ERR(INVALID_CLASS); + } + art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass); + if (!jklass_obj->IsClass()) { + return ERR(INVALID_CLASS); + } + art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass(); + if (klass->IsPrimitive() || klass->IsArrayClass()) { + return ERR(ABSENT_INFORMATION); + } + JvmtiUniquePtr<char[]> source_copy; + const char* file_name = klass->GetSourceFile(); + if (file_name == nullptr) { + return ERR(ABSENT_INFORMATION); + } + jvmtiError ret; + source_copy = CopyString(env, file_name, &ret); + if (source_copy == nullptr) { + return ret; + } + *source_name_ptr = source_copy.release(); + return OK; +} + +jvmtiError ClassUtil::GetSourceDebugExtension(jvmtiEnv* env, + jclass jklass, + char** source_debug_extension_ptr) { + art::ScopedObjectAccess soa(art::Thread::Current()); + if (jklass == nullptr) { + return ERR(INVALID_CLASS); + } + art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass); + if (!jklass_obj->IsClass()) { + return ERR(INVALID_CLASS); + } + art::StackHandleScope<1> hs(art::Thread::Current()); + art::Handle<art::mirror::Class> klass(hs.NewHandle(jklass_obj->AsClass())); + if (klass->IsPrimitive() || klass->IsArrayClass()) { + return ERR(ABSENT_INFORMATION); + } + JvmtiUniquePtr<char[]> ext_copy; + const char* data = art::annotations::GetSourceDebugExtension(klass); + if (data == nullptr) { + return ERR(ABSENT_INFORMATION); + } + jvmtiError ret; + ext_copy = CopyString(env, data, &ret); + if (ext_copy == nullptr) { + return ret; + } + *source_debug_extension_ptr = ext_copy.release(); + return OK; +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h index aa2260f035..7bb6b3e5de 100644 --- a/runtime/openjdkjvmti/ti_class.h +++ b/runtime/openjdkjvmti/ti_class.h @@ -82,6 +82,12 @@ class ClassUtil { jclass klass, jint* minor_version_ptr, jint* major_version_ptr); + + static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr); + + static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, + jclass klass, + char** source_debug_extension_ptr); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_field.cc b/runtime/openjdkjvmti/ti_field.cc index 342d8be2b0..32c064e89c 100644 --- a/runtime/openjdkjvmti/ti_field.cc +++ b/runtime/openjdkjvmti/ti_field.cc @@ -187,4 +187,68 @@ jvmtiError FieldUtil::IsFieldSynthetic(jvmtiEnv* env ATTRIBUTE_UNUSED, return ERR(NONE); } +jvmtiError FieldUtil::SetFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (klass == nullptr) { + return ERR(INVALID_CLASS); + } + if (field == nullptr) { + return ERR(INVALID_FIELDID); + } + auto res_pair = env->modify_watched_fields.insert(art::jni::DecodeArtField(field)); + if (!res_pair.second) { + // Didn't get inserted because it's already present! + return ERR(DUPLICATE); + } + return OK; +} + +jvmtiError FieldUtil::ClearFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (klass == nullptr) { + return ERR(INVALID_CLASS); + } + if (field == nullptr) { + return ERR(INVALID_FIELDID); + } + auto pos = env->modify_watched_fields.find(art::jni::DecodeArtField(field)); + if (pos == env->modify_watched_fields.end()) { + return ERR(NOT_FOUND); + } + env->modify_watched_fields.erase(pos); + return OK; +} + +jvmtiError FieldUtil::SetFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (klass == nullptr) { + return ERR(INVALID_CLASS); + } + if (field == nullptr) { + return ERR(INVALID_FIELDID); + } + auto res_pair = env->access_watched_fields.insert(art::jni::DecodeArtField(field)); + if (!res_pair.second) { + // Didn't get inserted because it's already present! + return ERR(DUPLICATE); + } + return OK; +} + +jvmtiError FieldUtil::ClearFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (klass == nullptr) { + return ERR(INVALID_CLASS); + } + if (field == nullptr) { + return ERR(INVALID_FIELDID); + } + auto pos = env->access_watched_fields.find(art::jni::DecodeArtField(field)); + if (pos == env->access_watched_fields.end()) { + return ERR(NOT_FOUND); + } + env->access_watched_fields.erase(pos); + return OK; +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_field.h b/runtime/openjdkjvmti/ti_field.h index 9a29f81d76..880949eecb 100644 --- a/runtime/openjdkjvmti/ti_field.h +++ b/runtime/openjdkjvmti/ti_field.h @@ -60,6 +60,11 @@ class FieldUtil { jclass klass, jfieldID field, jboolean* is_synthetic_ptr); + + static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field); + static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field); + static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field); + static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc index 319b1c2a9c..b3bc6764c9 100644 --- a/runtime/openjdkjvmti/ti_heap.cc +++ b/runtime/openjdkjvmti/ti_heap.cc @@ -23,6 +23,7 @@ #include "class_linker.h" #include "gc/heap.h" #include "gc_root-inl.h" +#include "java_frame_root_info.h" #include "jni_env_ext.h" #include "jni_internal.h" #include "jvmti_weak_table-inl.h" diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 341de0df72..5422f48664 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -601,9 +601,7 @@ bool Redefiner::ClassRedefinition::CheckSameMethods() { } // Skip all of the fields. We should have already checked this. - while (new_iter.HasNextStaticField() || new_iter.HasNextInstanceField()) { - new_iter.Next(); - } + new_iter.SkipAllFields(); // Check each of the methods. NB we don't need to specifically check for removals since the 2 dex // files have the same number of methods, which means there must be an equal amount of additions // and removals. diff --git a/runtime/stack.cc b/runtime/stack.cc index eec0460015..19df0d26a1 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -27,6 +27,7 @@ #include "entrypoints/runtime_asm_entrypoints.h" #include "gc/space/image_space.h" #include "gc/space/space-inl.h" +#include "interpreter/shadow_frame.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "linear_alloc.h" @@ -39,7 +40,6 @@ #include "runtime.h" #include "thread.h" #include "thread_list.h" -#include "verify_object.h" namespace art { @@ -47,29 +47,6 @@ using android::base::StringPrintf; static constexpr bool kDebugStackWalk = false; -mirror::Object* ShadowFrame::GetThisObject() const { - ArtMethod* m = GetMethod(); - if (m->IsStatic()) { - return nullptr; - } else if (m->IsNative()) { - return GetVRegReference(0); - } else { - const DexFile::CodeItem* code_item = m->GetCodeItem(); - CHECK(code_item != nullptr) << ArtMethod::PrettyMethod(m); - uint16_t reg = code_item->registers_size_ - code_item->ins_size_; - return GetVRegReference(reg); - } -} - -mirror::Object* ShadowFrame::GetThisObject(uint16_t num_ins) const { - ArtMethod* m = GetMethod(); - if (m->IsStatic()) { - return nullptr; - } else { - return GetVRegReference(NumberOfVRegs() - num_ins); - } -} - StackVisitor::StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind, @@ -97,9 +74,10 @@ StackVisitor::StackVisitor(Thread* thread, } } -InlineInfo StackVisitor::GetCurrentInlineInfo() const { - const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader(); - uint32_t native_pc_offset = method_header->NativeQuickPcOffset(cur_quick_frame_pc_); +static InlineInfo GetCurrentInlineInfo(const OatQuickMethodHeader* method_header, + uintptr_t cur_quick_frame_pc) + REQUIRES_SHARED(Locks::mutator_lock_) { + uint32_t native_pc_offset = method_header->NativeQuickPcOffset(cur_quick_frame_pc); CodeInfo code_info = method_header->GetOptimizedCodeInfo(); CodeInfoEncoding encoding = code_info.ExtractEncoding(); StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding); @@ -113,7 +91,8 @@ ArtMethod* StackVisitor::GetMethod() const { } else if (cur_quick_frame_ != nullptr) { if (IsInInlinedFrame()) { size_t depth_in_stack_map = current_inlining_depth_ - 1; - InlineInfo inline_info = GetCurrentInlineInfo(); + InlineInfo inline_info = GetCurrentInlineInfo(GetCurrentOatQuickMethodHeader(), + cur_quick_frame_pc_); const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader(); CodeInfoEncoding encoding = method_header->GetOptimizedCodeInfo().ExtractEncoding(); MethodInfo method_info = method_header->GetOptimizedMethodInfo(); @@ -138,8 +117,8 @@ uint32_t StackVisitor::GetDexPc(bool abort_on_failure) const { size_t depth_in_stack_map = current_inlining_depth_ - 1; const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader(); CodeInfoEncoding encoding = method_header->GetOptimizedCodeInfo().ExtractEncoding(); - return GetCurrentInlineInfo().GetDexPcAtDepth(encoding.inline_info.encoding, - depth_in_stack_map); + return GetCurrentInlineInfo(GetCurrentOatQuickMethodHeader(), cur_quick_frame_pc_). + GetDexPcAtDepth(encoding.inline_info.encoding, depth_in_stack_map); } else if (cur_oat_quick_method_header_ == nullptr) { return DexFile::kDexNoIndex; } else { @@ -924,134 +903,4 @@ void StackVisitor::WalkStack(bool include_transitions) { template void StackVisitor::WalkStack<StackVisitor::CountTransitions::kYes>(bool); template void StackVisitor::WalkStack<StackVisitor::CountTransitions::kNo>(bool); -void JavaFrameRootInfo::Describe(std::ostream& os) const { - const StackVisitor* visitor = stack_visitor_; - CHECK(visitor != nullptr); - os << "Type=" << GetType() << " thread_id=" << GetThreadId() << " location=" << - visitor->DescribeLocation() << " vreg=" << vreg_; -} - -int StackVisitor::GetVRegOffsetFromQuickCode(const DexFile::CodeItem* code_item, - uint32_t core_spills, uint32_t fp_spills, - size_t frame_size, int reg, InstructionSet isa) { - PointerSize pointer_size = InstructionSetPointerSize(isa); - if (kIsDebugBuild) { - auto* runtime = Runtime::Current(); - if (runtime != nullptr) { - CHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), pointer_size); - } - } - DCHECK_ALIGNED(frame_size, kStackAlignment); - DCHECK_NE(reg, -1); - int spill_size = POPCOUNT(core_spills) * GetBytesPerGprSpillLocation(isa) - + POPCOUNT(fp_spills) * GetBytesPerFprSpillLocation(isa) - + sizeof(uint32_t); // Filler. - int num_regs = code_item->registers_size_ - code_item->ins_size_; - int temp_threshold = code_item->registers_size_; - const int max_num_special_temps = 1; - if (reg == temp_threshold) { - // The current method pointer corresponds to special location on stack. - return 0; - } else if (reg >= temp_threshold + max_num_special_temps) { - /* - * Special temporaries may have custom locations and the logic above deals with that. - * However, non-special temporaries are placed relative to the outs. - */ - int temps_start = code_item->outs_size_ * sizeof(uint32_t) - + static_cast<size_t>(pointer_size) /* art method */; - int relative_offset = (reg - (temp_threshold + max_num_special_temps)) * sizeof(uint32_t); - return temps_start + relative_offset; - } else if (reg < num_regs) { - int locals_start = frame_size - spill_size - num_regs * sizeof(uint32_t); - return locals_start + (reg * sizeof(uint32_t)); - } else { - // Handle ins. - return frame_size + ((reg - num_regs) * sizeof(uint32_t)) - + static_cast<size_t>(pointer_size) /* art method */; - } -} - -void LockCountData::AddMonitor(Thread* self, mirror::Object* obj) { - if (obj == nullptr) { - return; - } - - // If there's an error during enter, we won't have locked the monitor. So check there's no - // exception. - if (self->IsExceptionPending()) { - return; - } - - if (monitors_ == nullptr) { - monitors_.reset(new std::vector<mirror::Object*>()); - } - monitors_->push_back(obj); -} - -void LockCountData::RemoveMonitorOrThrow(Thread* self, const mirror::Object* obj) { - if (obj == nullptr) { - return; - } - bool found_object = false; - if (monitors_ != nullptr) { - // We need to remove one pointer to ref, as duplicates are used for counting recursive locks. - // We arbitrarily choose the first one. - auto it = std::find(monitors_->begin(), monitors_->end(), obj); - if (it != monitors_->end()) { - monitors_->erase(it); - found_object = true; - } - } - if (!found_object) { - // The object wasn't found. Time for an IllegalMonitorStateException. - // The order here isn't fully clear. Assume that any other pending exception is swallowed. - // TODO: Maybe make already pending exception a suppressed exception. - self->ClearException(); - self->ThrowNewExceptionF("Ljava/lang/IllegalMonitorStateException;", - "did not lock monitor on object of type '%s' before unlocking", - const_cast<mirror::Object*>(obj)->PrettyTypeOf().c_str()); - } -} - -// Helper to unlock a monitor. Must be NO_THREAD_SAFETY_ANALYSIS, as we can't statically show -// that the object was locked. -void MonitorExitHelper(Thread* self, mirror::Object* obj) NO_THREAD_SAFETY_ANALYSIS { - DCHECK(self != nullptr); - DCHECK(obj != nullptr); - obj->MonitorExit(self); -} - -bool LockCountData::CheckAllMonitorsReleasedOrThrow(Thread* self) { - DCHECK(self != nullptr); - if (monitors_ != nullptr) { - if (!monitors_->empty()) { - // There may be an exception pending, if the method is terminating abruptly. Clear it. - // TODO: Should we add this as a suppressed exception? - self->ClearException(); - - // OK, there are monitors that are still locked. To enforce structured locking (and avoid - // deadlocks) we unlock all of them before we raise the IllegalMonitorState exception. - for (mirror::Object* obj : *monitors_) { - MonitorExitHelper(self, obj); - // If this raised an exception, ignore. TODO: Should we add this as suppressed - // exceptions? - if (self->IsExceptionPending()) { - self->ClearException(); - } - } - // Raise an exception, just give the first object as the sample. - mirror::Object* first = (*monitors_)[0]; - self->ThrowNewExceptionF("Ljava/lang/IllegalMonitorStateException;", - "did not unlock monitor on object of type '%s'", - mirror::Object::PrettyTypeOf(first).c_str()); - - // To make sure this path is not triggered again, clean out the monitors. - monitors_->clear(); - - return false; - } - } - return true; -} - } // namespace art diff --git a/runtime/stack.h b/runtime/stack.h index fd86f5d2b1..4ef9487724 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -20,15 +20,9 @@ #include <stdint.h> #include <string> -#include "arch/instruction_set.h" #include "base/macros.h" #include "base/mutex.h" -#include "dex_file.h" -#include "gc_root.h" #include "quick/quick_method_frame_info.h" -#include "read_barrier.h" -#include "stack_reference.h" -#include "verify_object.h" namespace art { @@ -39,11 +33,8 @@ namespace mirror { class ArtMethod; class Context; class HandleScope; -class InlineInfo; class OatQuickMethodHeader; -class ScopedObjectAccess; class ShadowFrame; -class StackVisitor; class Thread; union JValue; @@ -62,455 +53,60 @@ enum VRegKind { }; std::ostream& operator<<(std::ostream& os, const VRegKind& rhs); -// Forward declaration. Just calls the destructor. -struct ShadowFrameDeleter; -using ShadowFrameAllocaUniquePtr = std::unique_ptr<ShadowFrame, ShadowFrameDeleter>; - // Size in bytes of the should_deoptimize flag on stack. // We just need 4 bytes for our purpose regardless of the architecture. Frame size // calculation will automatically do alignment for the final frame size. static constexpr size_t kShouldDeoptimizeFlagSize = 4; -// Counting locks by storing object pointers into a vector. Duplicate entries mark recursive locks. -// The vector will be visited with the ShadowFrame during GC (so all the locked-on objects are -// thread roots). -// Note: implementation is split so that the call sites may be optimized to no-ops in case no -// lock counting is necessary. The actual implementation is in the cc file to avoid -// dependencies. -class LockCountData { - public: - // Add the given object to the list of monitors, that is, objects that have been locked. This - // will not throw (but be skipped if there is an exception pending on entry). - void AddMonitor(Thread* self, mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); - - // Try to remove the given object from the monitor list, indicating an unlock operation. - // This will throw an IllegalMonitorStateException (clearing any already pending exception), in - // case that there wasn't a lock recorded for the object. - void RemoveMonitorOrThrow(Thread* self, - const mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); - - // Check whether all acquired monitors have been released. This will potentially throw an - // IllegalMonitorStateException, clearing any already pending exception. Returns true if the - // check shows that everything is OK wrt/ lock counting, false otherwise. - bool CheckAllMonitorsReleasedOrThrow(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_); - - template <typename T, typename... Args> - void VisitMonitors(T visitor, Args&&... args) REQUIRES_SHARED(Locks::mutator_lock_) { - if (monitors_ != nullptr) { - // Visitors may change the Object*. Be careful with the foreach loop. - for (mirror::Object*& obj : *monitors_) { - visitor(/* inout */ &obj, std::forward<Args>(args)...); - } - } - } - - private: - // Stores references to the locked-on objects. As noted, this should be visited during thread - // marking. - std::unique_ptr<std::vector<mirror::Object*>> monitors_; -}; - -// ShadowFrame has 2 possible layouts: -// - interpreter - separate VRegs and reference arrays. References are in the reference array. -// - JNI - just VRegs, but where every VReg holds a reference. -class ShadowFrame { - public: - // Compute size of ShadowFrame in bytes assuming it has a reference array. - static size_t ComputeSize(uint32_t num_vregs) { - return sizeof(ShadowFrame) + (sizeof(uint32_t) * num_vregs) + - (sizeof(StackReference<mirror::Object>) * num_vregs); - } - - // Create ShadowFrame in heap for deoptimization. - static ShadowFrame* CreateDeoptimizedFrame(uint32_t num_vregs, ShadowFrame* link, - ArtMethod* method, uint32_t dex_pc) { - uint8_t* memory = new uint8_t[ComputeSize(num_vregs)]; - return CreateShadowFrameImpl(num_vregs, link, method, dex_pc, memory); - } - - // Delete a ShadowFrame allocated on the heap for deoptimization. - static void DeleteDeoptimizedFrame(ShadowFrame* sf) { - sf->~ShadowFrame(); // Explicitly destruct. - uint8_t* memory = reinterpret_cast<uint8_t*>(sf); - delete[] memory; - } - - // Create a shadow frame in a fresh alloca. This needs to be in the context of the caller. - // Inlining doesn't work, the compiler will still undo the alloca. So this needs to be a macro. -#define CREATE_SHADOW_FRAME(num_vregs, link, method, dex_pc) ({ \ - size_t frame_size = ShadowFrame::ComputeSize(num_vregs); \ - void* alloca_mem = alloca(frame_size); \ - ShadowFrameAllocaUniquePtr( \ - ShadowFrame::CreateShadowFrameImpl((num_vregs), (link), (method), (dex_pc), \ - (alloca_mem))); \ - }) - - ~ShadowFrame() {} - - // TODO(iam): Clean references array up since they're always there, - // we don't need to do conditionals. - bool HasReferenceArray() const { - return true; - } - - uint32_t NumberOfVRegs() const { - return number_of_vregs_; - } - - uint32_t GetDexPC() const { - return (dex_pc_ptr_ == nullptr) ? dex_pc_ : dex_pc_ptr_ - code_item_->insns_; - } - - int16_t GetCachedHotnessCountdown() const { - return cached_hotness_countdown_; - } - - void SetCachedHotnessCountdown(int16_t cached_hotness_countdown) { - cached_hotness_countdown_ = cached_hotness_countdown; - } - - int16_t GetHotnessCountdown() const { - return hotness_countdown_; - } - - void SetHotnessCountdown(int16_t hotness_countdown) { - hotness_countdown_ = hotness_countdown; - } - - void SetDexPC(uint32_t dex_pc) { - dex_pc_ = dex_pc; - dex_pc_ptr_ = nullptr; - } - - ShadowFrame* GetLink() const { - return link_; - } - - void SetLink(ShadowFrame* frame) { - DCHECK_NE(this, frame); - link_ = frame; - } - - int32_t GetVReg(size_t i) const { - DCHECK_LT(i, NumberOfVRegs()); - const uint32_t* vreg = &vregs_[i]; - return *reinterpret_cast<const int32_t*>(vreg); - } - - // Shorts are extended to Ints in VRegs. Interpreter intrinsics needs them as shorts. - int16_t GetVRegShort(size_t i) const { - return static_cast<int16_t>(GetVReg(i)); - } - - uint32_t* GetVRegAddr(size_t i) { - return &vregs_[i]; - } - - uint32_t* GetShadowRefAddr(size_t i) { - DCHECK(HasReferenceArray()); - DCHECK_LT(i, NumberOfVRegs()); - return &vregs_[i + NumberOfVRegs()]; - } - - void SetCodeItem(const DexFile::CodeItem* code_item) { - code_item_ = code_item; - } - - const DexFile::CodeItem* GetCodeItem() const { - return code_item_; - } - - float GetVRegFloat(size_t i) const { - DCHECK_LT(i, NumberOfVRegs()); - // NOTE: Strict-aliasing? - const uint32_t* vreg = &vregs_[i]; - return *reinterpret_cast<const float*>(vreg); - } - - int64_t GetVRegLong(size_t i) const { - DCHECK_LT(i, NumberOfVRegs()); - const uint32_t* vreg = &vregs_[i]; - typedef const int64_t unaligned_int64 __attribute__ ((aligned (4))); - return *reinterpret_cast<unaligned_int64*>(vreg); - } - - double GetVRegDouble(size_t i) const { - DCHECK_LT(i, NumberOfVRegs()); - const uint32_t* vreg = &vregs_[i]; - typedef const double unaligned_double __attribute__ ((aligned (4))); - return *reinterpret_cast<unaligned_double*>(vreg); - } - - // Look up the reference given its virtual register number. - // If this returns non-null then this does not mean the vreg is currently a reference - // on non-moving collectors. Check that the raw reg with GetVReg is equal to this if not certain. - template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> - mirror::Object* GetVRegReference(size_t i) const REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK_LT(i, NumberOfVRegs()); - mirror::Object* ref; - if (HasReferenceArray()) { - ref = References()[i].AsMirrorPtr(); - } else { - const uint32_t* vreg_ptr = &vregs_[i]; - ref = reinterpret_cast<const StackReference<mirror::Object>*>(vreg_ptr)->AsMirrorPtr(); - } - if (kUseReadBarrier) { - ReadBarrier::AssertToSpaceInvariant(ref); - } - if (kVerifyFlags & kVerifyReads) { - VerifyObject(ref); - } - return ref; - } - - // Get view of vregs as range of consecutive arguments starting at i. - uint32_t* GetVRegArgs(size_t i) { - return &vregs_[i]; - } - - void SetVReg(size_t i, int32_t val) { - DCHECK_LT(i, NumberOfVRegs()); - uint32_t* vreg = &vregs_[i]; - *reinterpret_cast<int32_t*>(vreg) = val; - // This is needed for moving collectors since these can update the vreg references if they - // happen to agree with references in the reference array. - if (kMovingCollector && HasReferenceArray()) { - References()[i].Clear(); - } - } - - void SetVRegFloat(size_t i, float val) { - DCHECK_LT(i, NumberOfVRegs()); - uint32_t* vreg = &vregs_[i]; - *reinterpret_cast<float*>(vreg) = val; - // This is needed for moving collectors since these can update the vreg references if they - // happen to agree with references in the reference array. - if (kMovingCollector && HasReferenceArray()) { - References()[i].Clear(); - } - } - - void SetVRegLong(size_t i, int64_t val) { - DCHECK_LT(i, NumberOfVRegs()); - uint32_t* vreg = &vregs_[i]; - typedef int64_t unaligned_int64 __attribute__ ((aligned (4))); - *reinterpret_cast<unaligned_int64*>(vreg) = val; - // This is needed for moving collectors since these can update the vreg references if they - // happen to agree with references in the reference array. - if (kMovingCollector && HasReferenceArray()) { - References()[i].Clear(); - References()[i + 1].Clear(); - } - } - - void SetVRegDouble(size_t i, double val) { - DCHECK_LT(i, NumberOfVRegs()); - uint32_t* vreg = &vregs_[i]; - typedef double unaligned_double __attribute__ ((aligned (4))); - *reinterpret_cast<unaligned_double*>(vreg) = val; - // This is needed for moving collectors since these can update the vreg references if they - // happen to agree with references in the reference array. - if (kMovingCollector && HasReferenceArray()) { - References()[i].Clear(); - References()[i + 1].Clear(); - } - } - - template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> - void SetVRegReference(size_t i, mirror::Object* val) REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK_LT(i, NumberOfVRegs()); - if (kVerifyFlags & kVerifyWrites) { - VerifyObject(val); - } - if (kUseReadBarrier) { - ReadBarrier::AssertToSpaceInvariant(val); - } - uint32_t* vreg = &vregs_[i]; - reinterpret_cast<StackReference<mirror::Object>*>(vreg)->Assign(val); - if (HasReferenceArray()) { - References()[i].Assign(val); - } - } - - void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_) { - DCHECK(method != nullptr); - DCHECK(method_ != nullptr); - method_ = method; - } - - ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(method_ != nullptr); - return method_; - } - - mirror::Object* GetThisObject() const REQUIRES_SHARED(Locks::mutator_lock_); - - mirror::Object* GetThisObject(uint16_t num_ins) const REQUIRES_SHARED(Locks::mutator_lock_); - - bool Contains(StackReference<mirror::Object>* shadow_frame_entry_obj) const { - if (HasReferenceArray()) { - return ((&References()[0] <= shadow_frame_entry_obj) && - (shadow_frame_entry_obj <= (&References()[NumberOfVRegs() - 1]))); - } else { - uint32_t* shadow_frame_entry = reinterpret_cast<uint32_t*>(shadow_frame_entry_obj); - return ((&vregs_[0] <= shadow_frame_entry) && - (shadow_frame_entry <= (&vregs_[NumberOfVRegs() - 1]))); - } - } - - LockCountData& GetLockCountData() { - return lock_count_data_; - } - - static size_t LockCountDataOffset() { - return OFFSETOF_MEMBER(ShadowFrame, lock_count_data_); - } - - static size_t LinkOffset() { - return OFFSETOF_MEMBER(ShadowFrame, link_); - } - - static size_t MethodOffset() { - return OFFSETOF_MEMBER(ShadowFrame, method_); - } - - static size_t DexPCOffset() { - return OFFSETOF_MEMBER(ShadowFrame, dex_pc_); - } - - static size_t NumberOfVRegsOffset() { - return OFFSETOF_MEMBER(ShadowFrame, number_of_vregs_); - } - - static size_t VRegsOffset() { - return OFFSETOF_MEMBER(ShadowFrame, vregs_); - } - - static size_t ResultRegisterOffset() { - return OFFSETOF_MEMBER(ShadowFrame, result_register_); - } - - static size_t DexPCPtrOffset() { - return OFFSETOF_MEMBER(ShadowFrame, dex_pc_ptr_); - } - - static size_t CodeItemOffset() { - return OFFSETOF_MEMBER(ShadowFrame, code_item_); - } - - static size_t CachedHotnessCountdownOffset() { - return OFFSETOF_MEMBER(ShadowFrame, cached_hotness_countdown_); - } - - static size_t HotnessCountdownOffset() { - return OFFSETOF_MEMBER(ShadowFrame, hotness_countdown_); - } - - // Create ShadowFrame for interpreter using provided memory. - static ShadowFrame* CreateShadowFrameImpl(uint32_t num_vregs, - ShadowFrame* link, - ArtMethod* method, - uint32_t dex_pc, - void* memory) { - return new (memory) ShadowFrame(num_vregs, link, method, dex_pc, true); - } - - const uint16_t* GetDexPCPtr() { - return dex_pc_ptr_; - } - - void SetDexPCPtr(uint16_t* dex_pc_ptr) { - dex_pc_ptr_ = dex_pc_ptr; - } - - JValue* GetResultRegister() { - return result_register_; - } - - private: - ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method, - uint32_t dex_pc, bool has_reference_array) - : link_(link), - method_(method), - result_register_(nullptr), - dex_pc_ptr_(nullptr), - code_item_(nullptr), - number_of_vregs_(num_vregs), - dex_pc_(dex_pc), - cached_hotness_countdown_(0), - hotness_countdown_(0) { - // TODO(iam): Remove this parameter, it's an an artifact of portable removal - DCHECK(has_reference_array); - if (has_reference_array) { - memset(vregs_, 0, num_vregs * (sizeof(uint32_t) + sizeof(StackReference<mirror::Object>))); - } else { - memset(vregs_, 0, num_vregs * sizeof(uint32_t)); - } - } - - const StackReference<mirror::Object>* References() const { - DCHECK(HasReferenceArray()); - const uint32_t* vreg_end = &vregs_[NumberOfVRegs()]; - return reinterpret_cast<const StackReference<mirror::Object>*>(vreg_end); - } - - StackReference<mirror::Object>* References() { - return const_cast<StackReference<mirror::Object>*>( - const_cast<const ShadowFrame*>(this)->References()); - } - - // Link to previous shadow frame or null. - ShadowFrame* link_; - ArtMethod* method_; - JValue* result_register_; - const uint16_t* dex_pc_ptr_; - const DexFile::CodeItem* code_item_; - LockCountData lock_count_data_; // This may contain GC roots when lock counting is active. - const uint32_t number_of_vregs_; - uint32_t dex_pc_; - int16_t cached_hotness_countdown_; - int16_t hotness_countdown_; - - // This is a two-part array: - // - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4 - // bytes. - // - [number_of_vregs..number_of_vregs*2) holds only reference registers. Each element here is - // ptr-sized. - // In other words when a primitive is stored in vX, the second (reference) part of the array will - // be null. When a reference is stored in vX, the second (reference) part of the array will be a - // copy of vX. - uint32_t vregs_[0]; - - DISALLOW_IMPLICIT_CONSTRUCTORS(ShadowFrame); -}; - -struct ShadowFrameDeleter { - inline void operator()(ShadowFrame* frame) { - if (frame != nullptr) { - frame->~ShadowFrame(); - } - } -}; - -class JavaFrameRootInfo FINAL : public RootInfo { - public: - JavaFrameRootInfo(uint32_t thread_id, const StackVisitor* stack_visitor, size_t vreg) - : RootInfo(kRootJavaFrame, thread_id), stack_visitor_(stack_visitor), vreg_(vreg) { - } - void Describe(std::ostream& os) const OVERRIDE - REQUIRES_SHARED(Locks::mutator_lock_); - - size_t GetVReg() const { - return vreg_; - } - const StackVisitor* GetVisitor() const { - return stack_visitor_; - } - - private: - const StackVisitor* const stack_visitor_; - const size_t vreg_; -}; +/* + * Our current stack layout. + * The Dalvik registers come first, followed by the + * Method*, followed by other special temporaries if any, followed by + * regular compiler temporary. As of now we only have the Method* as + * as a special compiler temporary. + * A compiler temporary can be thought of as a virtual register that + * does not exist in the dex but holds intermediate values to help + * optimizations and code generation. A special compiler temporary is + * one whose location in frame is well known while non-special ones + * do not have a requirement on location in frame as long as code + * generator itself knows how to access them. + * + * TODO: Update this documentation? + * + * +-------------------------------+ + * | IN[ins-1] | {Note: resides in caller's frame} + * | . | + * | IN[0] | + * | caller's ArtMethod | ... ArtMethod* + * +===============================+ {Note: start of callee's frame} + * | core callee-save spill | {variable sized} + * +-------------------------------+ + * | fp callee-save spill | + * +-------------------------------+ + * | filler word | {For compatibility, if V[locals-1] used as wide + * +-------------------------------+ + * | V[locals-1] | + * | V[locals-2] | + * | . | + * | . | ... (reg == 2) + * | V[1] | ... (reg == 1) + * | V[0] | ... (reg == 0) <---- "locals_start" + * +-------------------------------+ + * | stack alignment padding | {0 to (kStackAlignWords-1) of padding} + * +-------------------------------+ + * | Compiler temp region | ... (reg >= max_num_special_temps) + * | . | + * | . | + * | V[max_num_special_temps + 1] | + * | V[max_num_special_temps + 0] | + * +-------------------------------+ + * | OUT[outs-1] | + * | OUT[outs-2] | + * | . | + * | OUT[0] | + * | ArtMethod* | ... (reg == num_total_code_regs == special_temp_value) <<== sp, 16-byte aligned + * +===============================+ + */ class StackVisitor { public: @@ -619,80 +215,10 @@ class StackVisitor { uintptr_t* GetGPRAddress(uint32_t reg) const; - // This is a fast-path for getting/setting values in a quick frame. - uint32_t* GetVRegAddrFromQuickCode(ArtMethod** cur_quick_frame, - const DexFile::CodeItem* code_item, - uint32_t core_spills, uint32_t fp_spills, size_t frame_size, - uint16_t vreg) const { - int offset = GetVRegOffsetFromQuickCode( - code_item, core_spills, fp_spills, frame_size, vreg, kRuntimeISA); - DCHECK_EQ(cur_quick_frame, GetCurrentQuickFrame()); - uint8_t* vreg_addr = reinterpret_cast<uint8_t*>(cur_quick_frame) + offset; - return reinterpret_cast<uint32_t*>(vreg_addr); - } - uintptr_t GetReturnPc() const REQUIRES_SHARED(Locks::mutator_lock_); void SetReturnPc(uintptr_t new_ret_pc) REQUIRES_SHARED(Locks::mutator_lock_); - /* - * Return sp-relative offset for a Dalvik virtual register, compiler - * spill or Method* in bytes using Method*. - * Note that (reg == -1) denotes an invalid Dalvik register. For the - * positive values, the Dalvik registers come first, followed by the - * Method*, followed by other special temporaries if any, followed by - * regular compiler temporary. As of now we only have the Method* as - * as a special compiler temporary. - * A compiler temporary can be thought of as a virtual register that - * does not exist in the dex but holds intermediate values to help - * optimizations and code generation. A special compiler temporary is - * one whose location in frame is well known while non-special ones - * do not have a requirement on location in frame as long as code - * generator itself knows how to access them. - * - * +-------------------------------+ - * | IN[ins-1] | {Note: resides in caller's frame} - * | . | - * | IN[0] | - * | caller's ArtMethod | ... ArtMethod* - * +===============================+ {Note: start of callee's frame} - * | core callee-save spill | {variable sized} - * +-------------------------------+ - * | fp callee-save spill | - * +-------------------------------+ - * | filler word | {For compatibility, if V[locals-1] used as wide - * +-------------------------------+ - * | V[locals-1] | - * | V[locals-2] | - * | . | - * | . | ... (reg == 2) - * | V[1] | ... (reg == 1) - * | V[0] | ... (reg == 0) <---- "locals_start" - * +-------------------------------+ - * | stack alignment padding | {0 to (kStackAlignWords-1) of padding} - * +-------------------------------+ - * | Compiler temp region | ... (reg >= max_num_special_temps) - * | . | - * | . | - * | V[max_num_special_temps + 1] | - * | V[max_num_special_temps + 0] | - * +-------------------------------+ - * | OUT[outs-1] | - * | OUT[outs-2] | - * | . | - * | OUT[0] | - * | ArtMethod* | ... (reg == num_total_code_regs == special_temp_value) <<== sp, 16-byte aligned - * +===============================+ - */ - static int GetVRegOffsetFromQuickCode(const DexFile::CodeItem* code_item, - uint32_t core_spills, uint32_t fp_spills, - size_t frame_size, int reg, InstructionSet isa); - - static int GetOutVROffset(uint16_t out_num, InstructionSet isa) { - // According to stack model, the first out is above the Method referernce. - return static_cast<size_t>(InstructionSetPointerSize(isa)) + out_num * sizeof(uint32_t); - } - bool IsInInlinedFrame() const { return current_inlining_depth_ != 0; } @@ -774,8 +300,6 @@ class StackVisitor { void SanityCheckFrame() const REQUIRES_SHARED(Locks::mutator_lock_); - InlineInfo GetCurrentInlineInfo() const REQUIRES_SHARED(Locks::mutator_lock_); - Thread* const thread_; const StackWalkKind walk_kind_; ShadowFrame* cur_shadow_frame_; diff --git a/runtime/thread.cc b/runtime/thread.cc index 789f571253..4ddf217ca1 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -58,6 +58,8 @@ #include "gc_root.h" #include "handle_scope-inl.h" #include "indirect_reference_table-inl.h" +#include "interpreter/shadow_frame.h" +#include "java_frame_root_info.h" #include "java_vm_ext.h" #include "jni_internal.h" #include "mirror/class_loader.h" diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc index 5e9a2faab0..464af04cd5 100644 --- a/runtime/vdex_file.cc +++ b/runtime/vdex_file.cc @@ -185,13 +185,7 @@ void VdexFile::Unquicken(const std::vector<const DexFile*>& dex_files, continue; } ClassDataItemIterator it(*dex_file, class_data); - // Skip fields - while (it.HasNextStaticField()) { - it.Next(); - } - while (it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); while (it.HasNextDirectMethod()) { const DexFile::CodeItem* code_item = it.GetMethodCodeItem(); diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 12f791c1f1..9b652553df 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -300,9 +300,7 @@ FailureKind MethodVerifier::VerifyClass(Thread* self, return FailureKind::kNoFailure; } ClassDataItemIterator it(*dex_file, class_data); - while (it.HasNextStaticField() || it.HasNextInstanceField()) { - it.Next(); - } + it.SkipAllFields(); ClassLinker* linker = Runtime::Current()->GetClassLinker(); // Direct methods. MethodVerifier::FailureData data1 = VerifyMethods<true>(self, @@ -1986,10 +1984,7 @@ static uint32_t GetFirstFinalInstanceFieldIndex(const DexFile& dex_file, dex::Ty const uint8_t* class_data = dex_file.GetClassData(*class_def); DCHECK(class_data != nullptr); ClassDataItemIterator it(dex_file, class_data); - // Skip static fields. - while (it.HasNextStaticField()) { - it.Next(); - } + it.SkipStaticFields(); while (it.HasNextInstanceField()) { if ((it.GetFieldAccessFlags() & kAccFinal) != 0) { return it.GetMemberIndex(); diff --git a/test/701-easy-div-rem/build b/test/701-easy-div-rem/build index d83ee82b47..affb432b41 100644 --- a/test/701-easy-div-rem/build +++ b/test/701-easy-div-rem/build @@ -21,4 +21,4 @@ set -e mkdir src python ./genMain.py -./default-build +./default-build "$@" diff --git a/test/702-LargeBranchOffset/build b/test/702-LargeBranchOffset/build index 20030fa466..dab7b0d263 100644 --- a/test/702-LargeBranchOffset/build +++ b/test/702-LargeBranchOffset/build @@ -20,4 +20,4 @@ set -e # Write out the source file. cpp -P src/Main.java.in src/Main.java -./default-build +./default-build "$@" diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt index d3d9249b1f..30ad532f6c 100644 --- a/test/988-method-trace/expected.txt +++ b/test/988-method-trace/expected.txt @@ -1,4 +1,5 @@ -<= public static native void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +<= public static void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> => art.Test988$IterOp() .=> public java.lang.Object() .<= public java.lang.Object() -> <null: null> @@ -142,10 +143,10 @@ fibonacci(5)=5 ......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() ......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - at art.Test988.iter_fibonacci(Test988.java:207) - at art.Test988$IterOp.applyAsInt(Test988.java:202) - at art.Test988.doFibTest(Test988.java:295) - at art.Test988.run(Test988.java:265) + at art.Test988.iter_fibonacci(Test988.java:209) + at art.Test988$IterOp.applyAsInt(Test988.java:204) + at art.Test988.doFibTest(Test988.java:297) + at art.Test988.run(Test988.java:267) at Main.main(Main.java:19) > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> @@ -162,10 +163,10 @@ fibonacci(5)=5 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - at art.Test988.iter_fibonacci(Test988.java:207) - at art.Test988$IterOp.applyAsInt(Test988.java:202) - at art.Test988.doFibTest(Test988.java:295) - at art.Test988.run(Test988.java:265) + at art.Test988.iter_fibonacci(Test988.java:209) + at art.Test988$IterOp.applyAsInt(Test988.java:204) + at art.Test988.doFibTest(Test988.java:297) + at art.Test988.run(Test988.java:267) at Main.main(Main.java:19) .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> @@ -243,10 +244,10 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() ......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - at art.Test988.fibonacci(Test988.java:229) - at art.Test988$RecurOp.applyAsInt(Test988.java:224) - at art.Test988.doFibTest(Test988.java:295) - at art.Test988.run(Test988.java:266) + at art.Test988.fibonacci(Test988.java:231) + at art.Test988$RecurOp.applyAsInt(Test988.java:226) + at art.Test988.doFibTest(Test988.java:297) + at art.Test988.run(Test988.java:268) at Main.main(Main.java:19) > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> @@ -263,14 +264,14 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - at art.Test988.fibonacci(Test988.java:229) - at art.Test988$RecurOp.applyAsInt(Test988.java:224) - at art.Test988.doFibTest(Test988.java:295) - at art.Test988.run(Test988.java:266) + at art.Test988.fibonacci(Test988.java:231) + at art.Test988$RecurOp.applyAsInt(Test988.java:226) + at art.Test988.doFibTest(Test988.java:297) + at art.Test988.run(Test988.java:268) at Main.main(Main.java:19) .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> => public static native java.lang.Thread java.lang.Thread.currentThread() <= public static native java.lang.Thread java.lang.Thread.currentThread() -> <class java.lang.Thread: <non-deterministic>> -=> public static native void art.Trace.disableMethodTracing(java.lang.Thread) +=> public static native void art.Trace.disableTracing(java.lang.Thread) diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java index 37ff136b6c..6a45c0eaa2 100644 --- a/test/988-method-trace/src/art/Test988.java +++ b/test/988-method-trace/src/art/Test988.java @@ -194,7 +194,9 @@ public class Test988 { } private static List<Printable> results = new ArrayList<>(); - private static int cnt = 1; + // Starts with => enableMethodTracing + // .=> enableTracing + private static int cnt = 2; // Iterative version static final class IterOp implements IntUnaryOperator { @@ -253,7 +255,7 @@ public class Test988 { public static void run() throws Exception { // call this here so it is linked. It doesn't actually do anything here. loadAllClasses(); - Trace.disableMethodTracing(Thread.currentThread()); + Trace.disableTracing(Thread.currentThread()); Trace.enableMethodTracing( Test988.class, Test988.class.getDeclaredMethod("notifyMethodEntry", Object.class), @@ -265,7 +267,7 @@ public class Test988 { doFibTest(-19, new IterOp()); doFibTest(-19, new RecurOp()); // Turn off method tracing so we don't have to deal with print internals. - Trace.disableMethodTracing(Thread.currentThread()); + Trace.disableTracing(Thread.currentThread()); printResults(); } diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java index 3370996df3..9c27c9f69e 100644 --- a/test/988-method-trace/src/art/Trace.java +++ b/test/988-method-trace/src/art/Trace.java @@ -16,10 +16,34 @@ package art; +import java.lang.reflect.Field; import java.lang.reflect.Method; public class Trace { - public static native void enableMethodTracing( - Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr); - public static native void disableMethodTracing(Thread thr); + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); } diff --git a/test/989-method-trace-throw/src/art/Test989.java b/test/989-method-trace-throw/src/art/Test989.java index 18421bd08b..4feb29cf9d 100644 --- a/test/989-method-trace-throw/src/art/Test989.java +++ b/test/989-method-trace-throw/src/art/Test989.java @@ -56,7 +56,7 @@ public class Test989 { // to an infinite loop on the RI. private static void disableTraceForRI() { if (!System.getProperty("java.vm.name").equals("Dalvik")) { - Trace.disableMethodTracing(Thread.currentThread()); + Trace.disableTracing(Thread.currentThread()); } } @@ -158,7 +158,7 @@ public class Test989 { private static void maybeDisableTracing() throws Exception { if (DISABLE_TRACING) { - Trace.disableMethodTracing(Thread.currentThread()); + Trace.disableTracing(Thread.currentThread()); } } @@ -179,7 +179,7 @@ public class Test989 { } private static void setEntry(MethodTracer type) throws Exception { if (DISABLE_TRACING || !System.getProperty("java.vm.name").equals("Dalvik")) { - Trace.disableMethodTracing(Thread.currentThread()); + Trace.disableTracing(Thread.currentThread()); setupTracing(); } currentTracer = type; @@ -274,7 +274,7 @@ public class Test989 { maybeDisableTracing(); System.out.println("Finished!"); - Trace.disableMethodTracing(Thread.currentThread()); + Trace.disableTracing(Thread.currentThread()); } private static final class throwAClass implements MyRunnable { diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java index 3370996df3..9c27c9f69e 100644 --- a/test/989-method-trace-throw/src/art/Trace.java +++ b/test/989-method-trace-throw/src/art/Trace.java @@ -16,10 +16,34 @@ package art; +import java.lang.reflect.Field; import java.lang.reflect.Method; public class Trace { - public static native void enableMethodTracing( - Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr); - public static native void disableMethodTracing(Thread thr); + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); } diff --git a/test/990-field-trace/expected.txt b/test/990-field-trace/expected.txt new file mode 100644 index 0000000000..cceb008383 --- /dev/null +++ b/test/990-field-trace/expected.txt @@ -0,0 +1,52 @@ +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 1 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: tc1 (type: class java.lang.String) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method art.Test990$TestClass2(). New value: 0 (type: class java.lang.Long) +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass2 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 1337 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass2 in method public art.Test990$TestClass1(int,java.lang.Object). New value: TESTING (type: class java.lang.String) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public art.Test990$TestClass2(long). New value: 2 (type: class java.lang.Long) +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 3 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: TestClass1 { abc: "tc1", xyz: 1, foobar: 0 } (type: class art.Test990$TestClass1) +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 4 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: TestClass1 { abc: "TESTING", xyz: 1337, foobar: 0 } (type: class art.Test990$TestClass2) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int). New value: 1 (type: class java.lang.Integer) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int). New value: 2 (type: class java.lang.Integer) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 1 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 3 (type: class java.lang.Long) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 2 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int). New value: 1 (type: class java.lang.Integer) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 4 (type: class java.lang.Long) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 3 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 5 (type: class java.lang.Long) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 4 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 6 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int). New value: 1 (type: class java.lang.Integer) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 5 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 7 (type: class java.lang.Long) diff --git a/test/990-field-trace/info.txt b/test/990-field-trace/info.txt new file mode 100644 index 0000000000..67d164e3bf --- /dev/null +++ b/test/990-field-trace/info.txt @@ -0,0 +1 @@ +Tests field access and modification watches in JVMTI diff --git a/test/990-field-trace/run b/test/990-field-trace/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/990-field-trace/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/990-field-trace/src/Main.java b/test/990-field-trace/src/Main.java new file mode 100644 index 0000000000..cb14f5d511 --- /dev/null +++ b/test/990-field-trace/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test990.run(); + } +} diff --git a/test/990-field-trace/src/art/Test990.java b/test/990-field-trace/src/art/Test990.java new file mode 100644 index 0000000000..d766876412 --- /dev/null +++ b/test/990-field-trace/src/art/Test990.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Vector; +import java.util.function.Function; + +public class Test990 { + + // Fields of these classes are accessed/modified differently in the RI and ART so we ignore them. + static Collection<Class<?>> IGNORED_CLASSES = Arrays.asList(new Class<?>[] { + ClassLoader.class, + Vector.class, + }); + + static interface Printable { public void Print(); } + + static final class FieldWrite implements Printable { + private Executable method; + private Object target; + private Field f; + private String initialValue; + private Class<?> initialValueType; + + public FieldWrite(Executable method, Object target, Field f, Object v) { + this.method = method; + this.target = target; + this.f = f; + this.initialValue = genericToString(v); + this.initialValueType = v != null ? v.getClass() : null; + } + + @Override + public void Print() { + System.out.println("MODIFY of " + f + " on object of" + + " type: " + (target == null ? null : target.getClass()) + + " in method " + method + + ". New value: " + initialValue + " (type: " + initialValueType + ")"); + } + } + + static final class FieldRead implements Printable { + private Executable method; + private Object target; + private Field f; + + public FieldRead(Executable method, Object target, Field f) { + this.method = method; + this.target = target; + this.f = f; + } + + @Override + public void Print() { + System.out.println("ACCESS of " + f + " on object of" + + " type " + (target == null ? null : target.getClass()) + + " in method " + method); + } + } + + private static String genericToString(Object val) { + if (val == null) { + return "null"; + } else if (val.getClass().isArray()) { + return arrayToString(val); + } else if (val instanceof Throwable) { + StringWriter w = new StringWriter(); + ((Throwable) val).printStackTrace(new PrintWriter(w)); + return w.toString(); + } else { + return val.toString(); + } + } + + private static String charArrayToString(char[] src) { + String[] res = new String[src.length]; + for (int i = 0; i < src.length; i++) { + if (Character.isISOControl(src[i])) { + res[i] = Character.getName(src[i]); + } else { + res[i] = Character.toString(src[i]); + } + } + return Arrays.toString(res); + } + + private static String arrayToString(Object val) { + Class<?> klass = val.getClass(); + if ((new Object[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString( + Arrays.stream((Object[])val).map(new Function<Object, String>() { + public String apply(Object o) { + return genericToString(o); + } + }).toArray()); + } else if ((new byte[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((byte[])val); + } else if ((new char[0]).getClass().isAssignableFrom(klass)) { + return charArrayToString((char[])val); + } else if ((new short[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((short[])val); + } else if ((new int[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((int[])val); + } else if ((new long[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((long[])val); + } else if ((new float[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((float[])val); + } else if ((new double[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((double[])val); + } else { + throw new Error("Unknown type " + klass); + } + } + + private static List<Printable> results = new ArrayList<>(); + + public static void notifyFieldModify( + Executable m, long location, Class<?> f_klass, Object target, Field f, Object value) { + if (IGNORED_CLASSES.contains(f_klass)) { + return; + } + results.add(new FieldWrite(m, target, f, value)); + } + + public static void notifyFieldAccess( + Executable m, long location, Class<?> f_klass, Object target, Field f) { + if (IGNORED_CLASSES.contains(f_klass)) { + return; + } + results.add(new FieldRead(m, target, f)); + } + + static class TestClass1 { + Object abc; + int xyz; + int foobar; + public TestClass1(int xyz, Object abc) { + this.xyz = xyz; + this.abc = abc; + } + + public void tweak(int def) { + if (def == xyz) { + foobar++; + } + } + public String toString() { + return "TestClass1 { abc: \"" + genericToString(abc) + "\", xyz: " + xyz + + ", foobar: " + foobar + " }"; + } + } + + static class TestClass2 extends TestClass1 { + static long TOTAL = 0; + long baz; + public TestClass2(long baz) { + super(1337, "TESTING"); + this.baz = baz; + } + + public void tweak(int def) { + TOTAL++; + super.tweak(def); + baz++; + } + + public String toString() { + return "TestClass2 { super: \"%s\", TOTAL: %d, baz: %d }".format( + super.toString(), TOTAL, baz); + } + } + + + public static void run() throws Exception { + Trace.disableTracing(Thread.currentThread()); + Trace.enableFieldTracing( + Test990.class, + Test990.class.getDeclaredMethod("notifyFieldAccess", + Executable.class, Long.TYPE, Class.class, Object.class, Field.class), + Test990.class.getDeclaredMethod("notifyFieldModify", + Executable.class, Long.TYPE, Class.class, Object.class, Field.class, Object.class), + Thread.currentThread()); + Trace.watchAllFieldAccesses(); + Trace.watchAllFieldModifications(); + TestClass1 t1 = new TestClass1(1, "tc1"); + TestClass1 t2 = new TestClass2(2); + TestClass1 t3 = new TestClass1(3, t1); + TestClass1 t4 = new TestClass1(4, t2); + t1.tweak(1); + t1.tweak(1); + t2.tweak(12); + t2.tweak(1337); + t2.tweak(12); + t2.tweak(1338); + t1.tweak(t3.foobar); + t4.tweak((int)((TestClass2)t2).baz); + t4.tweak((int)TestClass2.TOTAL); + t2.tweak((int)TestClass2.TOTAL); + + // Turn off tracing so we don't have to deal with print internals. + Trace.disableTracing(Thread.currentThread()); + printResults(); + } + + public static void printResults() { + for (Printable p : results) { + p.Print(); + } + } +} diff --git a/test/990-field-trace/src/art/Trace.java b/test/990-field-trace/src/art/Trace.java new file mode 100644 index 0000000000..9c27c9f69e --- /dev/null +++ b/test/990-field-trace/src/art/Trace.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/991-field-trace-2/expected.txt b/test/991-field-trace-2/expected.txt new file mode 100644 index 0000000000..8da8ffdae6 --- /dev/null +++ b/test/991-field-trace-2/expected.txt @@ -0,0 +1,118 @@ +Test is class art.Test991$DoNothingFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ThrowReadFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowReadFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +Caught error. art.Test991$TestError: Throwing error during access +Final state: xyz = 0 +Test is class art.Test991$ThrowWriteFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Caught error. art.Test991$TestError: Throwing error during modify +Final state: xyz = 0 +Test is class art.Test991$ModifyDuringReadFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 20 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 21 (type: class java.lang.Integer) +Final state: xyz = 21 +Test is class art.Test991$ModifyDuringWriteFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadAndWriteFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 10 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 11 (type: class java.lang.Integer) +Final state: xyz = 11 +Test is class art.Test991$DoNothingFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ThrowReadFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ThrowWriteFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringWriteFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadAndWriteFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$DoNothingFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ThrowReadFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowReadFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +Caught error. art.Test991$TestError: Throwing error during access +Final state: xyz = 0 +Test is class art.Test991$ThrowWriteFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Caught error. art.Test991$TestError: Throwing error during modify +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 20 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 21 (type: class java.lang.Integer) +Final state: xyz = 21 +Test is class art.Test991$ModifyDuringWriteFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadAndWriteFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer + ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 10 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer + MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 11 (type: class java.lang.Integer) +Final state: xyz = 11 diff --git a/test/991-field-trace-2/field_trace.cc b/test/991-field-trace-2/field_trace.cc new file mode 100644 index 0000000000..823f9fd9c8 --- /dev/null +++ b/test/991-field-trace-2/field_trace.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#include "android-base/macros.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test991FieldTrace { + +extern "C" JNIEXPORT void JNICALL Java_art_Test991_doNativeReadWrite( + JNIEnv* env, jclass klass, jobject testclass) { + CHECK(testclass != nullptr); + ScopedLocalRef<jclass> testclass_klass(env, env->GetObjectClass(testclass)); + jmethodID notifyMethod = env->GetStaticMethodID(klass, "doPrintNativeNotification", "(I)V"); + if (env->ExceptionCheck()) { + return; + } + jfieldID xyz_field = env->GetFieldID(testclass_klass.get(), "xyz", "I"); + if (env->ExceptionCheck()) { + return; + } + jint val = env->GetIntField(testclass, xyz_field); + if (env->ExceptionCheck()) { + return; + } + env->CallStaticVoidMethod(klass, notifyMethod, val); + if (env->ExceptionCheck()) { + return; + } + val += 1; + env->SetIntField(testclass, xyz_field, val); +} + +} // namespace Test991FieldTrace +} // namespace art + diff --git a/test/991-field-trace-2/info.txt b/test/991-field-trace-2/info.txt new file mode 100644 index 0000000000..c2a1b68a56 --- /dev/null +++ b/test/991-field-trace-2/info.txt @@ -0,0 +1,5 @@ +Tests field access and modification watches in JVMTI. + +This test specifically examines how the runtime responds to exceptions occurring +while handling these events. It also verifies the situations in which these +events are sent. diff --git a/test/991-field-trace-2/run b/test/991-field-trace-2/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/991-field-trace-2/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/991-field-trace-2/src/Main.java b/test/991-field-trace-2/src/Main.java new file mode 100644 index 0000000000..d945a5c183 --- /dev/null +++ b/test/991-field-trace-2/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test991.run(); + } +} diff --git a/test/991-field-trace-2/src/art/Test991.java b/test/991-field-trace-2/src/art/Test991.java new file mode 100644 index 0000000000..644f4e10ed --- /dev/null +++ b/test/991-field-trace-2/src/art/Test991.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +public class Test991 { + static List<Field> WATCH_FIELDS = Arrays.asList(TestClass1.class.getDeclaredFields()); + + static FieldTracer TRACE = null; + + static abstract class FieldTracer { + public final void notifyFieldAccess( + Executable method, long location, Class<?> f_klass, Object target, Field f) { + System.out.println("FieldTracer: " + this.getClass()); + System.out.println("\tACCESS of " + f + " on object of" + + " type: " + (target == null ? null : target.getClass()) + + " in method " + method); + handleFieldAccess(method, location, f_klass, target, f); + } + + public final void notifyFieldModify( + Executable method, long location, Class<?> f_klass, Object target, Field f, Object value) { + System.out.println("FieldTracer: " + this.getClass()); + System.out.println("\tMODIFY of " + f + " on object of" + + " type: " + (target == null ? null : target.getClass()) + + " in method " + method + + ". New value: " + value + " (type: " + value.getClass() + ")"); + handleFieldModify(method, location, f_klass, target, f, value); + } + + public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) {} + public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) {} + } + + private static class TestError extends Error { + private static final long serialVersionUID = 0; + public TestError(String s) { super(s); } + } + static class DoNothingFieldTracer extends FieldTracer {} + static class ThrowReadFieldTracer extends FieldTracer { + @Override + public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) { + throw new TestError("Throwing error during access"); + } + } + static class ThrowWriteFieldTracer extends FieldTracer { + @Override + public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) { + throw new TestError("Throwing error during modify"); + } + } + static class ModifyDuringReadAndWriteFieldTracer extends FieldTracer { + @Override + public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) { + // NB This is only safe because the agent doesn't send recursive access/modification events up + // to the java layer here. + ((TestClass1)t).xyz += 100; + } + @Override + public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) { + // NB This is only safe because the agent doesn't send recursive access/modification events up + // to the java layer here. + ((TestClass1)t).xyz += 10; + } + } + + static class ModifyDuringWriteFieldTracer extends FieldTracer { + @Override + public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) { + // NB This is only safe because the agent doesn't send recursive access/modification events up + // to the java layer here. + ((TestClass1)t).xyz += 200; + } + } + + static class ModifyDuringReadFieldTracer extends FieldTracer { + @Override + public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) { + // NB This is only safe because the agent doesn't send recursive access/modification events up + // to the java layer here. + ((TestClass1)t).xyz += 20; + } + } + + public static void notifyFieldModify( + Executable m, long location, Class<?> f_klass, Object target, Field f, Object value) { + if (TRACE != null) { + TRACE.notifyFieldModify(m, location, f_klass, target, f, value); + } + } + + public static void notifyFieldAccess( + Executable m, long location, Class<?> f_klass, Object target, Field f) { + if (TRACE != null) { + TRACE.notifyFieldAccess(m, location, f_klass, target, f); + } + } + + public static class TestClass1 { + public int xyz; + public TestClass1(int xyz) { + this.xyz = xyz; + } + } + + public static int readFieldUntraced(TestClass1 target) { + FieldTracer tmp = TRACE; + TRACE = null; + int res = target.xyz; + TRACE = tmp; + return res; + } + + public static class JavaReadWrite implements Consumer<TestClass1> { + public void accept(TestClass1 t1) { + int val = t1.xyz; + System.out.println("normal read: xyz = " + val); + t1.xyz = val + 1; + } + } + + public static class ReflectiveReadWrite implements Consumer<TestClass1> { + public void accept(TestClass1 t1) { + try { + Field f = t1.getClass().getDeclaredField("xyz"); + int val = f.getInt(t1); + System.out.println("reflective read: xyz = " + val); + f.setInt(t1, val + 1); + } catch (IllegalAccessException iae) { + throw new InternalError("Could not set field xyz", iae); + } catch (NoSuchFieldException nsfe) { + throw new InternalError("Could not find field xyz", nsfe); + } + } + } + + public static class NativeReadWrite implements Consumer<TestClass1> { + public void accept(TestClass1 t1) { + doNativeReadWrite(t1); + } + } + + public static TestClass1 createTestClassNonTraced() { + FieldTracer tmp = TRACE; + TRACE = null; + TestClass1 n = new TestClass1(0); + TRACE = tmp; + return n; + } + + public static void run() throws Exception { + Trace.disableTracing(Thread.currentThread()); + Trace.enableFieldTracing( + Test991.class, + Test991.class.getDeclaredMethod("notifyFieldAccess", + Executable.class, Long.TYPE, Class.class, Object.class, Field.class), + Test991.class.getDeclaredMethod("notifyFieldModify", + Executable.class, Long.TYPE, Class.class, Object.class, Field.class, Object.class), + Thread.currentThread()); + for (Field f : WATCH_FIELDS) { + Trace.watchFieldAccess(f); + Trace.watchFieldModification(f); + } + FieldTracer[] tracers = new FieldTracer[] { + new DoNothingFieldTracer(), + new ThrowReadFieldTracer(), + new ThrowWriteFieldTracer(), + new ModifyDuringReadFieldTracer(), + new ModifyDuringWriteFieldTracer(), + new ModifyDuringReadAndWriteFieldTracer(), + }; + Consumer<TestClass1>[] field_modification = new Consumer[] { + new JavaReadWrite(), + new ReflectiveReadWrite(), + new NativeReadWrite(), + }; + for (Consumer<TestClass1> c : field_modification) { + for (FieldTracer trace : tracers) { + System.out.println("Test is " + trace.getClass() + " & " + c.getClass()); + TestClass1 t1 = createTestClassNonTraced(); + TRACE = trace; + System.out.println("Initial state: xyz = " + readFieldUntraced(t1)); + try { + c.accept(t1); + } catch (TestError e) { + System.out.println("Caught error. " + e); + } finally { + System.out.println("Final state: xyz = " + readFieldUntraced(t1)); + } + } + } + Trace.disableTracing(Thread.currentThread()); + } + + public static native void doNativeReadWrite(TestClass1 t1); + + public static void doPrintNativeNotification(int val) { + System.out.println("native read: xyz = " + val); + } +} diff --git a/test/991-field-trace-2/src/art/Trace.java b/test/991-field-trace-2/src/art/Trace.java new file mode 100644 index 0000000000..9c27c9f69e --- /dev/null +++ b/test/991-field-trace-2/src/art/Trace.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/992-source-data/expected.txt b/test/992-source-data/expected.txt new file mode 100644 index 0000000000..480d8a4fe7 --- /dev/null +++ b/test/992-source-data/expected.txt @@ -0,0 +1,10 @@ +class art.Test992 is defined in file "Test992.java" +class art.Test992$Target1 is defined in file "Test992.java" +class art.Test2 is defined in file "Test2.java" +int does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION +class java.lang.Integer is defined in file "Integer.java" +class java.lang.Object is defined in file "Object.java" +interface java.lang.Runnable is defined in file "Runnable.java" +class [Ljava.lang.Object; does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION +class [I does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION +null does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_INVALID_CLASS diff --git a/test/992-source-data/info.txt b/test/992-source-data/info.txt new file mode 100644 index 0000000000..5d487a4bde --- /dev/null +++ b/test/992-source-data/info.txt @@ -0,0 +1 @@ +Tests that we can get the source file of a class from JVMTI. diff --git a/test/992-source-data/run b/test/992-source-data/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/992-source-data/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./default-run "$@" --jvmti diff --git a/test/992-source-data/source_file.cc b/test/992-source-data/source_file.cc new file mode 100644 index 0000000000..3e8989e403 --- /dev/null +++ b/test/992-source-data/source_file.cc @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test992SourceFile { + +extern "C" JNIEXPORT +jstring JNICALL Java_art_Test992_getSourceFileName(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jclass target) { + char* file = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetSourceFileName(target, &file))) { + return nullptr; + } + jstring ret = env->NewStringUTF(file); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(file)); + return ret; +} + +} // namespace Test992SourceFile +} // namespace art + diff --git a/test/992-source-data/src/Main.java b/test/992-source-data/src/Main.java new file mode 100644 index 0000000000..31106f41fb --- /dev/null +++ b/test/992-source-data/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test992.run(); + } +} diff --git a/test/992-source-data/src/art/Test2.java b/test/992-source-data/src/art/Test2.java new file mode 100644 index 0000000000..dbb1089c5e --- /dev/null +++ b/test/992-source-data/src/art/Test2.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Test2 {} diff --git a/test/992-source-data/src/art/Test992.java b/test/992-source-data/src/art/Test992.java new file mode 100644 index 0000000000..db6ea73856 --- /dev/null +++ b/test/992-source-data/src/art/Test992.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.util.Base64; + +public class Test992 { + + static class Target1 { } + + public static void run() { + doTest(Test992.class); + doTest(Target1.class); + doTest(Test2.class); + doTest(Integer.TYPE); + doTest(Integer.class); + doTest(Object.class); + doTest(Runnable.class); + doTest(new Object[0].getClass()); + doTest(new int[0].getClass()); + doTest(null); + } + + public static void doTest(Class<?> k) { + try { + System.out.println(k + " is defined in file \"" + getSourceFileName(k) + "\""); + } catch (Exception e) { + System.out.println(k + " does not have a known source file because " + e); + } + } + + public static native String getSourceFileName(Class<?> k) throws Exception; +} diff --git a/test/Android.bp b/test/Android.bp index 0937c62469..9e6ecffe79 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -279,6 +279,8 @@ art_cc_defaults { "986-native-method-bind/native_bind.cc", "987-agent-bind/agent_bind.cc", "989-method-trace-throw/method_trace.cc", + "991-field-trace-2/field_trace.cc", + "992-source-data/source_file.cc", ], shared_libs: [ "libbase", diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 8aacc8c9b7..c44fb97c50 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,7 @@ TEST_VDEX="n" TEST_IS_NDEBUG="n" APP_IMAGE="y" JVMTI_STRESS="n" +JVMTI_FIELD_STRESS="n" JVMTI_TRACE_STRESS="n" JVMTI_REDEFINE_STRESS="n" VDEX_FILTER="" @@ -159,6 +160,10 @@ while true; do JVMTI_STRESS="y" JVMTI_REDEFINE_STRESS="y" shift + elif [ "x$1" = "x--jvmti-field-stress" ]; then + JVMTI_STRESS="y" + JVMTI_FIELD_STRESS="y" + shift elif [ "x$1" = "x--jvmti-trace-stress" ]; then JVMTI_STRESS="y" JVMTI_TRACE_STRESS="y" @@ -415,6 +420,9 @@ if [[ "$JVMTI_STRESS" = "y" ]]; then agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}" fi fi + if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then + agent_args="${agent_args},field" + fi if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then agent_args="${agent_args},trace" fi diff --git a/test/knownfailures.json b/test/knownfailures.json index 3a211bdce5..a3b8dd6cf0 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -511,7 +511,7 @@ "645-checker-abs-simd", "706-checker-scheduler"], "description": ["Checker tests are not compatible with jvmti."], - "variant": "jvmti-stress | redefine-stress | trace-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress" }, { "tests": [ @@ -550,7 +550,7 @@ "981-dedup-original-dex" ], "description": ["Tests that require exact knowledge of the number of plugins and agents."], - "variant": "jvmti-stress | redefine-stress | trace-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress" }, { "tests": [ @@ -585,6 +585,13 @@ }, { "tests": [ + "004-ThreadStress" + ], + "description": "The thread stress test just takes too long with field-stress", + "variant": "jvmti-stress | field-stress" + }, + { + "tests": [ "031-class-attributes", "911-get-stack-trace" ], diff --git a/test/run-test b/test/run-test index 1b6df16dae..044f63fa1e 100755 --- a/test/run-test +++ b/test/run-test @@ -144,6 +144,7 @@ basic_verify="false" gc_verify="false" gc_stress="false" jvmti_trace_stress="false" +jvmti_field_stress="false" jvmti_redefine_stress="false" strace="false" always_clean="no" @@ -244,6 +245,9 @@ while true; do elif [ "x$1" = "x--jvmti-redefine-stress" ]; then jvmti_redefine_stress="true" shift + elif [ "x$1" = "x--jvmti-field-stress" ]; then + jvmti_field_stress="true" + shift elif [ "x$1" = "x--jvmti-trace-stress" ]; then jvmti_trace_stress="true" shift @@ -460,6 +464,9 @@ fi if [ "$jvmti_redefine_stress" = "true" ]; then run_args="${run_args} --no-app-image --jvmti-redefine-stress" fi +if [ "$jvmti_field_stress" = "true" ]; then + run_args="${run_args} --no-app-image --jvmti-field-stress" +fi if [ "$jvmti_trace_stress" = "true" ]; then run_args="${run_args} --no-app-image --jvmti-trace-stress" fi diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index 344507115b..b6a5963cf4 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -147,7 +147,8 @@ def gather_test_info(): VARIANT_TYPE_DICT['relocate'] = {'relocate-npatchoat', 'relocate', 'no-relocate'} VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'} VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'} - VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress'} + VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress', + 'field-stress'} VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing', 'regalloc_gc', 'speed-profile'} @@ -437,7 +438,9 @@ def run_tests(tests): options_test += ' --debuggable' if jvmti == 'jvmti-stress': - options_test += ' --jvmti-trace-stress --jvmti-redefine-stress' + options_test += ' --jvmti-trace-stress --jvmti-redefine-stress --jvmti-field-stress' + elif jvmti == 'field-stress': + options_test += ' --jvmti-field-stress' elif jvmti == 'trace-stress': options_test += ' --jvmti-trace-stress' elif jvmti == 'redefine-stress': @@ -960,6 +963,8 @@ def parse_option(): JVMTI_TYPES.add('jvmti-stress') if options['redefine_stress']: JVMTI_TYPES.add('redefine-stress') + if options['field_stress']: + JVMTI_TYPES.add('field-stress') if options['trace_stress']: JVMTI_TYPES.add('trace-stress') if options['no_jvmti']: diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 6eaa5c37df..4fe58db169 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -78,9 +78,23 @@ struct TraceData { jclass test_klass; jmethodID enter_method; jmethodID exit_method; + jmethodID field_access; + jmethodID field_modify; bool in_callback; + bool access_watch_on_load; + bool modify_watch_on_load; }; +static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { + jint mods = 0; + if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) { + return nullptr; + } + + bool is_static = (mods & kAccStatic) != 0; + return env->ToReflectedField(field_klass, f, is_static); +} + static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &mods))) { @@ -97,21 +111,9 @@ static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { return res; } -static jobject GetJavaValue(jvmtiEnv* jvmtienv, - JNIEnv* env, - jmethodID m, - jvalue value) { - char *fname, *fsig, *fgen; - if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { - return nullptr; - } - std::string type(fsig); - type = type.substr(type.find(")") + 1); - jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); - jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); - jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); +static jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) { std::string name; - switch (type[0]) { + switch (type) { case 'V': return nullptr; case '[': @@ -146,7 +148,7 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, return nullptr; } std::ostringstream oss; - oss << "(" << type[0] << ")L" << name << ";"; + oss << "(" << type << ")L" << name << ";"; std::string args = oss.str(); jclass target = env->FindClass(name.c_str()); jmethodID valueOfMethod = env->GetStaticMethodID(target, "valueOf", args.c_str()); @@ -157,6 +159,98 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, return res; } +static jobject GetJavaValue(jvmtiEnv* jvmtienv, + JNIEnv* env, + jmethodID m, + jvalue value) { + char *fname, *fsig, *fgen; + if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { + return nullptr; + } + std::string type(fsig); + type = type.substr(type.find(")") + 1); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); + return GetJavaValueByType(env, type[0], value); +} + +static void fieldAccessCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback) { + // Don't do callback for either of these to prevent an infinite loop. + return; + } + CHECK(data->field_access != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); + jnienv->CallStaticVoidMethod(data->test_klass, + data->field_access, + method_arg, + static_cast<jlong>(location), + field_klass, + object, + field_arg); + jnienv->DeleteLocalRef(method_arg); + jnienv->DeleteLocalRef(field_arg); + data->in_callback = false; +} + +static void fieldModificationCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field, + char type_char, + jvalue new_value) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback) { + // Don't do callback recursively to prevent an infinite loop. + return; + } + CHECK(data->field_modify != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); + jobject value = GetJavaValueByType(jnienv, type_char, new_value); + if (jnienv->ExceptionCheck()) { + data->in_callback = false; + jnienv->DeleteLocalRef(method_arg); + jnienv->DeleteLocalRef(field_arg); + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, + data->field_modify, + method_arg, + static_cast<jlong>(location), + field_klass, + object, + field_arg, + value); + jnienv->DeleteLocalRef(method_arg); + jnienv->DeleteLocalRef(field_arg); + data->in_callback = false; +} + static void methodExitCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, @@ -172,6 +266,7 @@ static void methodExitCB(jvmtiEnv* jvmti, // Don't do callback for either of these to prevent an infinite loop. return; } + CHECK(data->exit_method != nullptr); data->in_callback = true; jobject method_arg = GetJavaMethod(jvmti, jnienv, method); jobject result = @@ -198,6 +293,7 @@ static void methodEntryCB(jvmtiEnv* jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { return; } + CHECK(data->enter_method != nullptr); if (method == data->exit_method || method == data->enter_method || data->in_callback) { // Don't do callback for either of these to prevent an infinite loop. return; @@ -212,12 +308,179 @@ static void methodEntryCB(jvmtiEnv* jvmti, data->in_callback = false; } -extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing( +static void classPrepareCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jclass klass) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->access_watch_on_load || data->modify_watch_on_load) { + jint nfields; + jfieldID* fields; + if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetClassFields(klass, &nfields, &fields))) { + return; + } + for (jint i = 0; i < nfields; i++) { + jfieldID f = fields[i]; + // Ignore errors + if (data->access_watch_on_load) { + jvmti->SetFieldAccessWatch(klass, f); + } + + if (data->modify_watch_on_load) { + jvmti->SetFieldModificationWatch(klass, f); + } + } + jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldAccesses(JNIEnv* env) { + TraceData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + data->access_watch_on_load = true; + // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr))) { + return; + } + jint nklasses; + jclass* klasses; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { + return; + } + for (jint i = 0; i < nklasses; i++) { + jclass k = klasses[i]; + + jint nfields; + jfieldID* fields; + jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); + if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { + continue; + } else if (JvmtiErrorToException(env, jvmti_env, err)) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); + return; + } + for (jint j = 0; j < nfields; j++) { + jvmti_env->SetFieldAccessWatch(k, fields[j]); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldModifications(JNIEnv* env) { + TraceData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + data->modify_watch_on_load = true; + // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr))) { + return; + } + jint nklasses; + jclass* klasses; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { + return; + } + for (jint i = 0; i < nklasses; i++) { + jclass k = klasses[i]; + + jint nfields; + jfieldID* fields; + jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); + if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { + continue; + } else if (JvmtiErrorToException(env, jvmti_env, err)) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); + return; + } + for (jint j = 0; j < nfields; j++) { + jvmti_env->SetFieldModificationWatch(k, fields[j]); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +} + +static bool GetFieldAndClass(JNIEnv* env, + jobject ref_field, + jclass* out_klass, + jfieldID* out_field) { + *out_field = env->FromReflectedField(ref_field); + if (env->ExceptionCheck()) { + return false; + } + jclass field_klass = env->FindClass("java/lang/reflect/Field"); + if (env->ExceptionCheck()) { + return false; + } + jmethodID get_declaring_class_method = + env->GetMethodID(field_klass, "getDeclaringClass", "()Ljava/lang/Class;"); + if (env->ExceptionCheck()) { + env->DeleteLocalRef(field_klass); + return false; + } + *out_klass = static_cast<jclass>(env->CallObjectMethod(ref_field, get_declaring_class_method)); + if (env->ExceptionCheck()) { + *out_klass = nullptr; + env->DeleteLocalRef(field_klass); + return false; + } + env->DeleteLocalRef(field_klass); + return true; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldModification( + JNIEnv* env, + jclass trace ATTRIBUTE_UNUSED, + jobject field_obj) { + jfieldID field; + jclass klass; + if (!GetFieldAndClass(env, field_obj, &klass, &field)) { + return; + } + + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(klass, field)); + env->DeleteLocalRef(klass); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess( + JNIEnv* env, + jclass trace ATTRIBUTE_UNUSED, + jobject field_obj) { + jfieldID field; + jclass klass; + if (!GetFieldAndClass(env, field_obj, &klass, &field)) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(klass, field)); + env->DeleteLocalRef(klass); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( JNIEnv* env, jclass trace ATTRIBUTE_UNUSED, jclass klass, jobject enter, jobject exit, + jobject field_access, + jobject field_modify, jthread thr) { TraceData* data = nullptr; if (JvmtiErrorToException(env, @@ -228,8 +491,10 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing( } memset(data, 0, sizeof(TraceData)); data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); - data->enter_method = env->FromReflectedMethod(enter); - data->exit_method = env->FromReflectedMethod(exit); + data->enter_method = enter != nullptr ? env->FromReflectedMethod(enter) : nullptr; + data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr; + data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr; + data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr; data->in_callback = false; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { @@ -240,29 +505,62 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing( memset(&cb, 0, sizeof(cb)); cb.MethodEntry = methodEntryCB; cb.MethodExit = methodExitCB; + cb.FieldAccess = fieldAccessCB; + cb.FieldModification = fieldModificationCB; + cb.ClassPrepare = classPrepareCB; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { return; } - if (JvmtiErrorToException(env, + if (enter != nullptr && + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, thr))) { return; } - if (JvmtiErrorToException(env, + if (exit != nullptr && + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, thr))) { return; } + if (field_access != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FIELD_ACCESS, + thr))) { + return; + } + if (field_modify != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FIELD_MODIFICATION, + thr))) { + return; + } } -extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableMethodTracing( +extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) { if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FIELD_ACCESS, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FIELD_MODIFICATION, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_ENTRY, thr))) { return; diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc index 76f894325b..40fcc4f11d 100644 --- a/test/ti-stress/stress.cc +++ b/test/ti-stress/stress.cc @@ -39,6 +39,7 @@ struct StressData { bool vm_class_loader_initialized; bool trace_stress; bool redefine_stress; + bool field_stress; }; static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) { @@ -127,15 +128,33 @@ class ScopedClassInfo { : jvmtienv_(jvmtienv), class_(c), name_(nullptr), - generic_(nullptr) {} + generic_(nullptr), + file_(nullptr), + debug_ext_(nullptr) {} ~ScopedClassInfo() { - jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); - jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); + if (class_ != nullptr) { + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_)); + } } bool Init() { - return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE; + if (class_ == nullptr) { + name_ = const_cast<char*>("<NONE>"); + generic_ = const_cast<char*>("<NONE>"); + return true; + } else { + jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_); + jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_); + return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE && + ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && + ret1 != JVMTI_ERROR_INVALID_CLASS && + ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && + ret2 != JVMTI_ERROR_INVALID_CLASS; + } } jclass GetClass() const { @@ -147,12 +166,28 @@ class ScopedClassInfo { const char* GetGeneric() const { return generic_; } + const char* GetSourceDebugExtension() const { + if (debug_ext_ == nullptr) { + return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>"; + } else { + return debug_ext_; + } + } + const char* GetSourceFileName() const { + if (file_ == nullptr) { + return "<UNKNOWN_FILE>"; + } else { + return file_; + } + } private: jvmtiEnv* jvmtienv_; jclass class_; char* name_; char* generic_; + char* file_; + char* debug_ext_; }; class ScopedMethodInfo { @@ -165,7 +200,8 @@ class ScopedMethodInfo { class_info_(nullptr), name_(nullptr), signature_(nullptr), - generic_(nullptr) {} + generic_(nullptr), + first_line_(-1) {} ~ScopedMethodInfo() { env_->DeleteLocalRef(declaring_class_); @@ -179,6 +215,18 @@ class ScopedMethodInfo { return false; } class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); + jint nlines; + jvmtiLineNumberEntry* lines; + jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines); + if (err == JVMTI_ERROR_NONE) { + if (nlines > 0) { + first_line_ = lines[0].line_number; + } + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines)); + } else if (err != JVMTI_ERROR_ABSENT_INFORMATION && + err != JVMTI_ERROR_NATIVE_METHOD) { + return false; + } return class_info_->Init() && (jvmtienv_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE); } @@ -203,6 +251,10 @@ class ScopedMethodInfo { return generic_; } + jint GetFirstLine() const { + return first_line_; + } + private: jvmtiEnv* jvmtienv_; JNIEnv* env_; @@ -212,16 +264,84 @@ class ScopedMethodInfo { char* name_; char* signature_; char* generic_; + jint first_line_; friend std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m); }; +class ScopedFieldInfo { + public: + ScopedFieldInfo(jvmtiEnv* jvmtienv, jclass field_klass, jfieldID field) + : jvmtienv_(jvmtienv), + declaring_class_(field_klass), + field_(field), + class_info_(nullptr), + name_(nullptr), + type_(nullptr), + generic_(nullptr) {} + + ~ScopedFieldInfo() { + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(type_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); + } + + bool Init() { + class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); + return class_info_->Init() && + (jvmtienv_->GetFieldName( + declaring_class_, field_, &name_, &type_, &generic_) == JVMTI_ERROR_NONE); + } + + const ScopedClassInfo& GetDeclaringClassInfo() const { + return *class_info_; + } + + jclass GetDeclaringClass() const { + return declaring_class_; + } + + const char* GetName() const { + return name_; + } + + const char* GetType() const { + return type_; + } + + const char* GetGeneric() const { + return generic_; + } + + private: + jvmtiEnv* jvmtienv_; + jclass declaring_class_; + jfieldID field_; + std::unique_ptr<ScopedClassInfo> class_info_; + char* name_; + char* type_; + char* generic_; + + friend std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m); +}; + +std::ostream& operator<<(std::ostream &os, const ScopedFieldInfo* m) { + return os << *m; +} + +std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m) { + return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() + << ":" << m.GetType(); +} + std::ostream& operator<<(std::ostream &os, const ScopedMethodInfo* m) { return os << *m; } std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m) { - return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature(); + return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature() + << " (source: " << m.GetDeclaringClassInfo().GetSourceFileName() << ":" + << m.GetFirstLine() << ")"; } static void doJvmtiMethodBind(jvmtiEnv* jvmtienv, @@ -304,6 +424,100 @@ static std::string GetValOf(jvmtiEnv* env, JNIEnv* jnienv, std::string type, jva } } +void JNICALL FieldAccessHook(jvmtiEnv* jvmtienv, + JNIEnv* env, + jthread thread, + jmethodID m, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field) { + ScopedThreadInfo info(jvmtienv, env, thread); + ScopedMethodInfo method_info(jvmtienv, env, m); + ScopedFieldInfo field_info(jvmtienv, field_klass, field); + jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr; + ScopedClassInfo obj_class_info(jvmtienv, oklass); + if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) { + LOG(ERROR) << "Unable to get callback info!"; + return; + } + LOG(INFO) << "ACCESS field \"" << field_info << "\" on object of " + << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info + << "\" at location 0x" << std::hex << location << ". Thread is \"" + << info.GetName() << "\"."; + env->DeleteLocalRef(oklass); +} + +static std::string PrintJValue(jvmtiEnv* jvmtienv, JNIEnv* env, char type, jvalue new_value) { + std::ostringstream oss; + switch (type) { + case 'L': { + jobject nv = new_value.l; + if (nv == nullptr) { + oss << "\"null\""; + } else { + jclass nv_klass = env->GetObjectClass(nv); + ScopedClassInfo nv_class_info(jvmtienv, nv_klass); + if (!nv_class_info.Init()) { + oss << "with unknown type"; + } else { + oss << "of type \"" << nv_class_info.GetName() << "\""; + } + env->DeleteLocalRef(nv_klass); + } + break; + } + case 'Z': { + if (new_value.z) { + oss << "true"; + } else { + oss << "false"; + } + break; + } +#define SEND_VALUE(chr, sym, type) \ + case chr: { \ + oss << static_cast<type>(new_value.sym); \ + break; \ + } + SEND_VALUE('B', b, int8_t); + SEND_VALUE('C', c, uint16_t); + SEND_VALUE('S', s, int16_t); + SEND_VALUE('I', i, int32_t); + SEND_VALUE('J', j, int64_t); + SEND_VALUE('F', f, float); + SEND_VALUE('D', d, double); +#undef SEND_VALUE + } + return oss.str(); +} + +void JNICALL FieldModificationHook(jvmtiEnv* jvmtienv, + JNIEnv* env, + jthread thread, + jmethodID m, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field, + char type, + jvalue new_value) { + ScopedThreadInfo info(jvmtienv, env, thread); + ScopedMethodInfo method_info(jvmtienv, env, m); + ScopedFieldInfo field_info(jvmtienv, field_klass, field); + jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr; + ScopedClassInfo obj_class_info(jvmtienv, oklass); + if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) { + LOG(ERROR) << "Unable to get callback info!"; + return; + } + LOG(INFO) << "MODIFY field \"" << field_info << "\" on object of " + << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info + << "\" at location 0x" << std::hex << location << std::dec << ". New value is " + << PrintJValue(jvmtienv, env, type, new_value) << ". Thread is \"" + << info.GetName() << "\"."; + env->DeleteLocalRef(oklass); +} void JNICALL MethodExitHook(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread, @@ -342,14 +556,34 @@ void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread, jclass klass) { - ScopedThreadInfo info(jvmtienv, env, thread); - ScopedClassInfo class_info(jvmtienv, klass); - if (!class_info.Init()) { - LOG(ERROR) << "Unable to get class info!"; - return; + StressData* data = nullptr; + CHECK_EQ(jvmtienv->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)), + JVMTI_ERROR_NONE); + if (data->field_stress) { + jint nfields; + jfieldID* fields; + if (jvmtienv->GetClassFields(klass, &nfields, &fields) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get a classes fields!"; + return; + } + for (jint i = 0; i < nfields; i++) { + jfieldID f = fields[i]; + // Ignore errors + jvmtienv->SetFieldAccessWatch(klass, f); + jvmtienv->SetFieldModificationWatch(klass, f); + } + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fields)); + } + if (data->trace_stress) { + ScopedThreadInfo info(jvmtienv, env, thread); + ScopedClassInfo class_info(jvmtienv, klass); + if (!class_info.Init()) { + LOG(ERROR) << "Unable to get class info!"; + return; + } + LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \"" + << info.GetName() << "\""; } - LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \"" - << info.GetName() << "\""; } // The hook we are using. @@ -402,7 +636,7 @@ static std::string GetOption(const std::string& in) { } // Options are -// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace] +// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace,][field] static void ReadOptions(StressData* data, char* options) { std::string ops(options); CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress"; @@ -411,6 +645,8 @@ static void ReadOptions(StressData* data, char* options) { std::string cur = GetOption(ops); if (cur == "trace") { data->trace_stress = true; + } else if (cur == "field") { + data->field_stress = true; } else if (cur == "redefine") { data->redefine_stress = true; ops = AdvanceOption(ops); @@ -451,18 +687,54 @@ static void JNICALL PerformFinalSetupVMInit(jvmtiEnv *jvmti_env, jni_env->DeleteLocalRef(klass); data->vm_class_loader_initialized = true; } - if (data->trace_stress) { - if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_METHOD_ENTRY, - nullptr) != JVMTI_ERROR_NONE) { - LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!"; +} + +static bool WatchAllFields(JavaVM* vm, jvmtiEnv* jvmti) { + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Couldn't set prepare event!"; + return false; + } + // TODO We really shouldn't need to do this step here. + jint nklass; + jclass* klasses; + if (jvmti->GetLoadedClasses(&nklass, &klasses) != JVMTI_ERROR_NONE) { + LOG(WARNING) << "Couldn't get loaded classes! Ignoring."; + return true; + } + JNIEnv* jni = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6)) { + LOG(ERROR) << "Unable to get jni env. Ignoring and potentially leaking jobjects."; + return false; + } + for (jint i = 0; i < nklass; i++) { + jclass k = klasses[i]; + ScopedClassInfo sci(jvmti, k); + if (sci.Init()) { + LOG(INFO) << "NOTE: class " << sci.GetName() << " already loaded."; } - if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_METHOD_EXIT, - nullptr) != JVMTI_ERROR_NONE) { - LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!"; + jint nfields; + jfieldID* fields; + jvmtiError err = jvmti->GetClassFields(k, &nfields, &fields); + if (err == JVMTI_ERROR_NONE) { + for (jint j = 0; j < nfields; j++) { + jfieldID f = fields[j]; + if (jvmti->SetFieldModificationWatch(k, f) != JVMTI_ERROR_NONE || + jvmti->SetFieldAccessWatch(k, f) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to set watches on a field."; + return false; + } + } + } else if (err != JVMTI_ERROR_CLASS_NOT_PREPARED) { + LOG(ERROR) << "Unexpected error getting class fields!"; + return false; } + jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields)); + jni->DeleteLocalRef(k); } + jvmti->Deallocate(reinterpret_cast<unsigned char*>(klasses)); + return true; } extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, @@ -501,23 +773,27 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, cb.VMInit = PerformFinalSetupVMInit; cb.MethodEntry = MethodEntryHook; cb.MethodExit = MethodExitHook; + cb.FieldAccess = FieldAccessHook; + cb.FieldModification = FieldModificationHook; cb.ClassPrepare = ClassPrepareHook; if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set class file load hook cb!"; return 1; } if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_NATIVE_METHOD_BIND, - nullptr) != JVMTI_ERROR_NONE) { - LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!"; - return 1; - } - if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr) != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!"; return 1; } + if (data->redefine_stress) { + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!"; + return 1; + } + } if (data->trace_stress) { if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, @@ -525,12 +801,39 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, LOG(ERROR) << "Unable to enable CLASS_PREPARE event!"; return 1; } + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_NATIVE_METHOD_BIND, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!"; + return 1; + } + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_ENTRY, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!"; + return 1; + } + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_EXIT, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!"; + return 1; + } } - if (data->redefine_stress) { + if (data->field_stress) { if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + JVMTI_EVENT_FIELD_MODIFICATION, nullptr) != JVMTI_ERROR_NONE) { - LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!"; + LOG(ERROR) << "Unable to enable FIELD_MODIFICATION event!"; + return 1; + } + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FIELD_ACCESS, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable FIELD_ACCESS event!"; + return 1; + } + if (!WatchAllFields(vm, jvmti)) { return 1; } } diff --git a/tools/runtime_memusage/prune_sanitizer_output.py b/tools/runtime_memusage/prune_sanitizer_output.py index 222c3c77b9..d95b2ced1c 100755 --- a/tools/runtime_memusage/prune_sanitizer_output.py +++ b/tools/runtime_memusage/prune_sanitizer_output.py @@ -21,86 +21,73 @@ from __future__ import division from __future__ import print_function import argparse +import re import os -import sys - -STACK_DIVIDER = 65 * '=' - - -def has_min_lines(trace, stack_min_size): - """Checks if the trace has a minimum amount of levels in trace.""" - # Line containing 'use-after-poison' contains address accessed, which is - # useful for extracting Dex File offsets - string_checks = ['use-after-poison', 'READ'] - required_checks = string_checks + ['#%d ' % line_ctr - for line_ctr in - range(stack_min_size) - ] - try: - trace_indices = [trace.index(check) for check in required_checks] - return all(trace_indices[trace_ind] < trace_indices[trace_ind + 1] - for trace_ind in range(len(trace_indices) - 1)) - except ValueError: - return False - return True - - -def prune_exact(trace, stack_min_size): - """Removes all of trace that comes after the (stack_min_size)th trace.""" - string_checks = ['use-after-poison', 'READ'] - required_checks = string_checks + ['#%d ' % line_ctr - for line_ctr in - range(stack_min_size) - ] - trace_indices = [trace.index(check) for check in required_checks] - new_line_index = trace.index("\n", trace_indices[-1]) - return trace[:new_line_index + 1] - - -def make_unique(trace): - """Removes overlapping line numbers and lines out of order.""" - string_checks = ['use-after-poison', 'READ'] - hard_checks = string_checks + ['#%d ' % line_ctr - for line_ctr in range(100) - ] - last_ind = -1 - for str_check in hard_checks: - try: - location_ind = trace.index(str_check) - if last_ind > location_ind: - trace = trace[:trace[:location_ind].find("\n") + 1] - last_ind = location_ind - try: - next_location_ind = trace.index(str_check, location_ind + 1) - trace = trace[:next_location_ind] - except ValueError: - pass - except ValueError: - pass - return trace - - -def parse_args(argv): - """Parses arguments passed in.""" - parser = argparse.ArgumentParser() - parser.add_argument('-d', action='store', - default="", dest="out_dir_name", type=is_directory, - help='Output Directory') - parser.add_argument('-e', action='store_true', - default=False, dest='check_exact', - help='Forces each trace to be cut to have ' - 'minimum number of lines') - parser.add_argument('-m', action='store', - default=4, dest='stack_min_size', type=int, - help='minimum number of lines a trace should have') - parser.add_argument('trace_file', action='store', - type=argparse.FileType('r'), - help='File only containing lines that are related to ' - 'Sanitizer traces') - return parser.parse_args(argv) - - -def is_directory(path_name): + +STACK_DIVIDER = 65 * "=" + + +def match_to_int(match): + """Returns trace line number matches as integers for sorting. + Maps other matches to negative integers. + """ + # Hard coded string are necessary since each trace must have the address + # accessed, which is printed before trace lines. + if match == "use-after-poison": + return -2 + elif match == "READ": + return -1 + # Cutting off non-integer part of match + return int(match[1:-1]) + + +def clean_trace_if_valid(trace, stack_min_size, prune_exact): + """Cleans trace if it meets a certain standard. Returns None otherwise.""" + # Sample input: + # trace: + # "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a... + # ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock) + # ... #0 0x71281013b3 (/data/asan/system/lib64/libart.so+0x2263b3) + # ... #1 0x71280fe6b7 (/data/asan/system/lib64/libart.so+0x2236b7) + # ... #3 0x71280c22ef (/data/asan/system/lib64/libart.so+0x1e72ef) + # ... #2 0x712810a84f (/data/asan/system/lib64/libart.so+0x22f84f)" + # + # stack_min_size: 2 + # prune_exact: False + # + # Sample output: + # + # "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a... + # ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock) + # ... #0 0x71281013b3 (/data/asan/system/lib64/libart.so+0x2263b3) + # ... #1 0x71280fe6b7 (/data/asan/system/lib64/libart.so+0x2236b7) + # " + + # Adds a newline character if not present at the end of trace + trace = trace if trace[-1] == "\n" else trace + "\n" + trace_line_matches = [(match_to_int(match.group()), match.start()) + for match in re.finditer("#[0-9]+ " + "|use-after-poison" + "|READ", trace) + ] + # Finds the first index where the line number ordering isn't in sequence or + # returns the number of matches if it everything is in order. + bad_line_no = next((i - 2 for i, match in enumerate(trace_line_matches) + if i - 2 != match[0]), len(trace_line_matches) - 2) + # If the number ordering breaks after minimum stack size, then the trace is + # still valid. + if bad_line_no >= stack_min_size: + # Added if the trace is already clean + trace_line_matches.append((trace_line_matches[-1][0] + 1, len(trace))) + bad_match = trace_line_matches[bad_line_no + 2] + if prune_exact: + bad_match = trace_line_matches[stack_min_size + 2] + # Up to new-line that comes before bad line number + return trace[:trace.rindex("\n", 0, bad_match[1]) + 1] + return None + + +def extant_directory(path_name): """Checks if a path is an actual directory.""" if not os.path.isdir(path_name): dir_error = "%s is not a directory" % (path_name) @@ -108,15 +95,32 @@ def is_directory(path_name): return path_name -def main(argv=None): +def parse_args(): + """Parses arguments passed in.""" + parser = argparse.ArgumentParser() + parser.add_argument("-d", action="store", + default="", dest="out_dir_name", type=extant_directory, + help="Output Directory") + parser.add_argument("-e", action="store_true", + default=False, dest="check_exact", + help="Forces each trace to be cut to have " + "minimum number of lines") + parser.add_argument("-m", action="store", + default=4, dest="stack_min_size", type=int, + help="minimum number of lines a trace should have") + parser.add_argument("trace_file", action="store", + type=argparse.FileType("r"), + help="File only containing lines that are related to " + "Sanitizer traces") + return parser.parse_args() + + +def main(): """Parses arguments and cleans up traces using other functions.""" stack_min_size = 4 check_exact = False - if argv is None: - argv = sys.argv - - parsed_argv = parse_args(argv[1:]) + parsed_argv = parse_args() stack_min_size = parsed_argv.stack_min_size check_exact = parsed_argv.check_exact out_dir_name = parsed_argv.out_dir_name @@ -124,28 +128,15 @@ def main(argv=None): trace_split = trace_file.read().split(STACK_DIVIDER) trace_file.close() - # if flag -e is enabled - if check_exact: - trace_prune_split = [prune_exact(trace, stack_min_size) - for trace in trace_split if - has_min_lines(trace, stack_min_size) - ] - trace_unique_split = [make_unique(trace) - for trace in trace_prune_split - ] - else: - trace_unique_split = [make_unique(trace) - for trace in trace_split if - has_min_lines(trace, stack_min_size) - ] - # has_min_lines is called again because removing lines can prune too much - trace_clean_split = [trace for trace - in trace_unique_split if - has_min_lines(trace, - stack_min_size) + trace_clean_split = [clean_trace_if_valid(trace, + stack_min_size, + check_exact) + for trace in trace_split ] + trace_clean_split = [trace for trace in trace_clean_split + if trace is not None] - outfile = os.path.join(out_dir_name, trace_file.name + '_filtered') + outfile = os.path.join(out_dir_name, trace_file.name + "_filtered") with open(outfile, "w") as output_file: output_file.write(STACK_DIVIDER.join(trace_clean_split)) diff --git a/tools/runtime_memusage/symbol_trace_info.py b/tools/runtime_memusage/symbol_trace_info.py index 57ed6ce954..e539be2217 100755 --- a/tools/runtime_memusage/symbol_trace_info.py +++ b/tools/runtime_memusage/symbol_trace_info.py @@ -120,23 +120,23 @@ def is_directory(path_name): def parse_args(argv): """Parses arguments passed in.""" parser = argparse.ArgumentParser() - parser.add_argument('-d', action='store', + parser.add_argument("-d", action="store", default="", dest="out_dir_name", type=is_directory, - help='Output Directory') - parser.add_argument('sanitizer_trace', action='store', - type=argparse.FileType('r'), - help='File containing sanitizer traces filtered by ' - 'prune_sanitizer_output.py') - parser.add_argument('symbol_trace', action='store', - type=argparse.FileType('r'), - help='File containing symbolized traces that match ' - 'sanitizer_trace') - parser.add_argument('dex_starts', action='store', - type=argparse.FileType('r'), - help='File containing starting addresses of Dex Files') - parser.add_argument('categories', action='store', nargs='*', - help='Keywords expected to show in large amounts of' - ' symbolized traces') + help="Output Directory") + parser.add_argument("sanitizer_trace", action="store", + type=argparse.FileType("r"), + help="File containing sanitizer traces filtered by " + "prune_sanitizer_output.py") + parser.add_argument("symbol_trace", action="store", + type=argparse.FileType("r"), + help="File containing symbolized traces that match " + "sanitizer_trace") + parser.add_argument("dex_starts", action="store", + type=argparse.FileType("r"), + help="File containing starting addresses of Dex Files") + parser.add_argument("categories", action="store", nargs="*", + help="Keywords expected to show in large amounts of" + " symbolized traces") return parser.parse_args(argv) @@ -177,6 +177,10 @@ def read_data(parsed_argv): for line in dex_start_file_data if "RegisterDexFile" in line ] + # Dex File Starting addresses must be sorted because bisect requires sorted + # lists. + data_lists["dex_start_list"].sort() + return data_lists, categories, symbol_file_split @@ -210,5 +214,5 @@ def main(argv=None): print_categories(categories, symbol_file_split, parsed_argv.out_dir_name) -if __name__ == '__main__': +if __name__ == "__main__": main() |