diff options
153 files changed, 7117 insertions, 567 deletions
diff --git a/Android.bp b/Android.bp index d0e22fb873..cb72082e6d 100644 --- a/Android.bp +++ b/Android.bp @@ -31,10 +31,12 @@ subdirs = [ "disassembler", "imgdiag", "oatdump", + "openjdkjvm", "patchoat", "profman", "runtime", "sigchainlib", + "simulator", "test", "tools/cpp-define-generator", "tools/dmtracedump", diff --git a/compiler/Android.bp b/compiler/Android.bp index b721d210fe..f11d25675c 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -423,8 +423,11 @@ art_cc_test { }, }, + header_libs: ["libart_simulator_headers"], + shared_libs: [ "libartd-compiler", + "libartd-simulator-container", "libvixld-arm", "libvixld-arm64", diff --git a/compiler/dex/dex_to_dex_decompiler_test.cc b/compiler/dex/dex_to_dex_decompiler_test.cc index 7b56f3ec1a..1ef3ba7c00 100644 --- a/compiler/dex/dex_to_dex_decompiler_test.cc +++ b/compiler/dex/dex_to_dex_decompiler_test.cc @@ -29,6 +29,7 @@ #include "scoped_thread_state_change-inl.h" #include "thread.h" #include "verifier/method_verifier-inl.h" +#include "verifier/verifier_deps.h" namespace art { @@ -39,6 +40,11 @@ class DexToDexDecompilerTest : public CommonCompilerTest { TimingLogger::ScopedTiming t(__FUNCTION__, &timings); compiler_options_->boot_image_ = false; compiler_options_->SetCompilerFilter(CompilerFilter::kQuicken); + // Create the main VerifierDeps, here instead of in the compiler since we want to aggregate + // the results for all the dex files, not just the results for the current dex file. + Runtime::Current()->GetCompilerCallbacks()->SetVerifierDeps( + new verifier::VerifierDeps(GetDexFiles(class_loader))); + compiler_driver_->SetDexFilesForOatFile(GetDexFiles(class_loader)); compiler_driver_->CompileAll(class_loader, GetDexFiles(class_loader), &timings); } diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc index e657e3bc86..cfb56e30a8 100644 --- a/compiler/dex/verification_results.cc +++ b/compiler/dex/verification_results.cc @@ -46,10 +46,6 @@ VerificationResults::~VerificationResults() { void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method_verifier) { DCHECK(method_verifier != nullptr); - if (!compiler_options_->IsAnyCompilationEnabled()) { - // Verified methods are only required for quickening and compilation. - return; - } MethodReference ref = method_verifier->GetMethodReference(); std::unique_ptr<const VerifiedMethod> verified_method(VerifiedMethod::Create(method_verifier)); if (verified_method == nullptr) { @@ -104,7 +100,6 @@ void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method const VerifiedMethod* VerificationResults::GetVerifiedMethod(MethodReference ref) { const VerifiedMethod* ret = nullptr; - DCHECK(compiler_options_->IsAnyCompilationEnabled()); if (atomic_verified_methods_.Get(DexFileReference(ref.dex_file, ref.dex_method_index), &ret)) { return ret; } diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index ed36e111ff..0b1bce62c9 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -303,7 +303,6 @@ CompilerDriver::CompilerDriver( timings_logger_(timer), compiler_context_(nullptr), support_boot_image_fixup_(true), - dex_files_for_oat_file_(nullptr), compiled_method_storage_(swap_fd), profile_compilation_info_(profile_compilation_info), max_arena_alloc_(0), @@ -924,8 +923,11 @@ void CompilerDriver::PreCompile(jobject class_loader, VLOG(compiler) << "Verify: " << GetMemoryUsageString(false); if (had_hard_verifier_failure_ && GetCompilerOptions().AbortOnHardVerifierFailure()) { - LOG(FATAL) << "Had a hard failure verifying all classes, and was asked to abort in such " - << "situations. Please check the log."; + // Avoid dumping threads. Even if we shut down the thread pools, there will still be three + // instances of this thread's stack. + LOG(FATAL_WITHOUT_ABORT) << "Had a hard failure verifying all classes, and was asked to abort " + << "in such situations. Please check the log."; + abort(); } if (compiler_options_->IsAnyCompilationEnabled()) { @@ -1912,8 +1914,8 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, TimingLogger* timings) { verifier::VerifierDeps* verifier_deps = Runtime::Current()->GetCompilerCallbacks()->GetVerifierDeps(); - // If there is an existing `VerifierDeps`, try to use it for fast verification. - if (verifier_deps == nullptr) { + // If there exist VerifierDeps that aren't the ones we just created to output, use them to verify. + if (verifier_deps == nullptr || verifier_deps->OutputOnly()) { return false; } TimingLogger::ScopedTiming t("Fast Verify", timings); @@ -1980,13 +1982,6 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, void CompilerDriver::Verify(jobject jclass_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) { - // Always add the dex files to compiled_classes_. This happens for all compiler filters. - for (const DexFile* dex_file : dex_files) { - if (!compiled_classes_.HaveDexFile(dex_file)) { - compiled_classes_.AddDexFile(dex_file, dex_file->NumClassDefs()); - } - } - if (FastVerify(jclass_loader, dex_files, timings)) { return; } @@ -1996,14 +1991,16 @@ void CompilerDriver::Verify(jobject jclass_loader, // non boot image compilation. The verifier will need it to record the new dependencies. // Then dex2oat can update the vdex file with these new dependencies. if (!GetCompilerOptions().IsBootImage()) { + // Dex2oat creates the verifier deps. // Create the main VerifierDeps, and set it to this thread. - verifier::VerifierDeps* verifier_deps = new verifier::VerifierDeps(dex_files); - Runtime::Current()->GetCompilerCallbacks()->SetVerifierDeps(verifier_deps); + verifier::VerifierDeps* verifier_deps = + Runtime::Current()->GetCompilerCallbacks()->GetVerifierDeps(); + CHECK(verifier_deps != nullptr); Thread::Current()->SetVerifierDeps(verifier_deps); // Create per-thread VerifierDeps to avoid contention on the main one. // We will merge them after verification. for (ThreadPoolWorker* worker : parallel_thread_pool_->GetWorkers()) { - worker->GetThread()->SetVerifierDeps(new verifier::VerifierDeps(dex_files)); + worker->GetThread()->SetVerifierDeps(new verifier::VerifierDeps(dex_files_for_oat_file_)); } } @@ -2028,7 +2025,7 @@ void CompilerDriver::Verify(jobject jclass_loader, for (ThreadPoolWorker* worker : parallel_thread_pool_->GetWorkers()) { verifier::VerifierDeps* thread_deps = worker->GetThread()->GetVerifierDeps(); worker->GetThread()->SetVerifierDeps(nullptr); - verifier_deps->MergeWith(*thread_deps, dex_files);; + verifier_deps->MergeWith(*thread_deps, dex_files_for_oat_file_); delete thread_deps; } Thread::Current()->SetVerifierDeps(nullptr); @@ -2332,7 +2329,7 @@ class InitializeClassVisitor : public CompilationVisitor { // checks in Thread::AssertThreadSuspensionIsAllowable. Runtime* const runtime = Runtime::Current(); // Run the class initializer in transaction mode. - runtime->EnterTransactionMode(klass.Get()); + runtime->EnterTransactionMode(is_app_image, klass.Get()); bool success = manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, true, true); // TODO we detach transaction from runtime to indicate we quit the transactional @@ -2359,7 +2356,7 @@ class InitializeClassVisitor : public CompilationVisitor { *file_log << exception->Dump() << "\n"; } soa.Self()->ClearException(); - runtime->RollbackAndExitTransactionMode(); + runtime->RollbackAllTransactions(); CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored"; } else if (is_boot_image) { // For boot image, we want to put the updated status in the oat class since we can't @@ -2452,7 +2449,8 @@ class InitializeClassVisitor : public CompilationVisitor { bool ResolveTypesOfMethods(Thread* self, ArtMethod* m) REQUIRES_SHARED(Locks::mutator_lock_) { - auto rtn_type = m->GetReturnType(true); // return value is discarded because resolve will be done internally. + // Return value of ResolveReturnType() is discarded because resolve will be done internally. + ObjPtr<mirror::Class> rtn_type = m->ResolveReturnType(); if (rtn_type == nullptr) { self->ClearException(); return false; @@ -2461,7 +2459,7 @@ class InitializeClassVisitor : public CompilationVisitor { if (types != nullptr) { for (uint32_t i = 0; i < types->Size(); ++i) { dex::TypeIndex param_type_idx = types->GetTypeItem(i).type_idx_; - auto param_type = m->GetClassFromTypeIndex(param_type_idx, true); + ObjPtr<mirror::Class> param_type = m->ResolveClassFromTypeIndex(param_type_idx); if (param_type == nullptr) { self->ClearException(); return false; @@ -2698,7 +2696,14 @@ void CompilerDriver::Compile(jobject class_loader, : profile_compilation_info_->DumpInfo(&dex_files)); } - DCHECK(current_dex_to_dex_methods_ == nullptr); + current_dex_to_dex_methods_ = nullptr; + Thread* const self = Thread::Current(); + { + // Clear in case we aren't the first call to Compile. + MutexLock mu(self, dex_to_dex_references_lock_); + dex_to_dex_references_.clear(); + } + for (const DexFile* dex_file : dex_files) { CHECK(dex_file != nullptr); CompileDexFile(class_loader, @@ -2717,7 +2722,7 @@ void CompilerDriver::Compile(jobject class_loader, { // From this point on, we shall not modify dex_to_dex_references_, so // just grab a reference to it that we use without holding the mutex. - MutexLock lock(Thread::Current(), dex_to_dex_references_lock_); + MutexLock lock(self, dex_to_dex_references_lock_); dex_to_dex_references = ArrayRef<DexFileMethodSet>(dex_to_dex_references_); } for (const auto& method_set : dex_to_dex_references) { @@ -2910,7 +2915,7 @@ void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status if (kIsDebugBuild) { // Check to make sure it's not a dex file for an oat file we are compiling since these // should always succeed. These do not include classes in for used libraries. - for (const DexFile* dex_file : *dex_files_for_oat_file_) { + for (const DexFile* dex_file : GetDexFilesForOatFile()) { CHECK_NE(dex_ref.dex_file, dex_file) << dex_ref.dex_file->GetLocation(); } } @@ -3028,4 +3033,13 @@ void CompilerDriver::FreeThreadPools() { single_thread_pool_.reset(); } +void CompilerDriver::SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) { + dex_files_for_oat_file_ = dex_files; + for (const DexFile* dex_file : dex_files) { + if (!compiled_classes_.HaveDexFile(dex_file)) { + compiled_classes_.AddDexFile(dex_file, dex_file->NumClassDefs()); + } + } +} + } // namespace art diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index ecaed83e57..d9886a2fba 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -103,15 +103,11 @@ class CompilerDriver { ~CompilerDriver(); // Set dex files that will be stored in the oat file after being compiled. - void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) { - dex_files_for_oat_file_ = &dex_files; - } + void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files); // Get dex file that will be stored in the oat file after being compiled. ArrayRef<const DexFile* const> GetDexFilesForOatFile() const { - return (dex_files_for_oat_file_ != nullptr) - ? ArrayRef<const DexFile* const>(*dex_files_for_oat_file_) - : ArrayRef<const DexFile* const>(); + return ArrayRef<const DexFile* const>(dex_files_for_oat_file_); } void CompileAll(jobject class_loader, @@ -532,7 +528,7 @@ class CompilerDriver { bool support_boot_image_fixup_; // List of dex files that will be stored in the oat file. - const std::vector<const DexFile*>* dex_files_for_oat_file_; + std::vector<const DexFile*> dex_files_for_oat_file_; CompiledMethodStorage compiled_method_storage_; diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index 10bfd972f0..fee6afb91f 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -42,7 +42,9 @@ class CompilerDriverTest : public CommonCompilerTest { void CompileAll(jobject class_loader) REQUIRES(!Locks::mutator_lock_) { TimingLogger timings("CompilerDriverTest::CompileAll", false, false); TimingLogger::ScopedTiming t(__FUNCTION__, &timings); - compiler_driver_->CompileAll(class_loader, GetDexFiles(class_loader), &timings); + dex_files_ = GetDexFiles(class_loader); + compiler_driver_->SetDexFilesForOatFile(dex_files_);; + compiler_driver_->CompileAll(class_loader, dex_files_, &timings); t.NewTiming("MakeAllExecutable"); MakeAllExecutable(class_loader); } @@ -95,6 +97,7 @@ class CompilerDriverTest : public CommonCompilerTest { JNIEnv* env_; jclass class_; jmethodID mid_; + std::vector<const DexFile*> dex_files_; }; // Disabled due to 10 second runtime on host diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index 76f0ae9202..3cacc2cad7 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -144,6 +144,8 @@ bool CompilerOptions::ParseCompilerOption(const StringPiece& option, UsageFn Usa ParseDouble(option.data(), '=', 0.0, 100.0, &top_k_profile_threshold_, Usage); } else if (option == "--abort-on-hard-verifier-error") { abort_on_hard_verifier_failure_ = true; + } else if (option == "--no-abort-on-hard-verifier-error") { + abort_on_hard_verifier_failure_ = false; } else if (option.starts_with("--dump-init-failures=")) { ParseDumpInitFailures(option, Usage); } else if (option.starts_with("--dump-cfg=")) { diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 318009c606..fc7cd016b2 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -894,7 +894,7 @@ bool ImageWriter::PruneAppImageClassInternal( &my_early_exit, visited); // Remove the class if the dex file is not in the set of dex files. This happens for classes that - // are from uses library if there is no profile. b/30688277 + // are from uses-library if there is no profile. b/30688277 mirror::DexCache* dex_cache = klass->GetDexCache(); if (dex_cache != nullptr) { result = result || @@ -1153,9 +1153,22 @@ void ImageWriter::PruneNonImageClasses() { Thread* self = Thread::Current(); ScopedAssertNoThreadSuspension sa(__FUNCTION__); - // Clear class table strong roots so that dex caches can get pruned. We require pruning the class - // path dex caches. - class_linker->ClearClassTableStrongRoots(); + // Prune uses-library dex caches. Only prune the uses-library dex caches since we want to make + // sure the other ones don't get unloaded before the OatWriter runs. + class_linker->VisitClassTables( + [&](ClassTable* table) REQUIRES_SHARED(Locks::mutator_lock_) { + table->RemoveStrongRoots( + [&](GcRoot<mirror::Object> root) REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Object> obj = root.Read(); + if (obj->IsDexCache()) { + // Return true if the dex file is not one of the ones in the map. + return dex_file_oat_index_map_.find(obj->AsDexCache()->GetDexFile()) == + dex_file_oat_index_map_.end(); + } + // Return false to avoid removing. + return false; + }); + }); // Remove the undesired classes from the class roots. ObjPtr<mirror::ClassLoader> class_loader; diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 4d258af843..d7e3a28777 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -1282,9 +1282,12 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { bool StartClass(const DexFile* dex_file, size_t class_def_index) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { OatDexMethodVisitor::StartClass(dex_file, class_def_index); - if (dex_cache_ == nullptr || dex_cache_->GetDexFile() != dex_file) { - dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file); - DCHECK(dex_cache_ != nullptr); + if (writer_->GetCompilerDriver()->GetCompilerOptions().IsAotCompilationEnabled()) { + // Only need to set the dex cache if we have compilation. Other modes might have unloaded it. + if (dex_cache_ == nullptr || dex_cache_->GetDexFile() != dex_file) { + dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file); + DCHECK(dex_cache_ != nullptr); + } } return true; } diff --git a/compiler/optimizing/codegen_test_utils.h b/compiler/optimizing/codegen_test_utils.h index 1b38acd8b0..cada2e679b 100644 --- a/compiler/optimizing/codegen_test_utils.h +++ b/compiler/optimizing/codegen_test_utils.h @@ -28,6 +28,7 @@ #include "arch/x86/instruction_set_features_x86.h" #include "arch/x86/registers_x86.h" #include "arch/x86_64/instruction_set_features_x86_64.h" +#include "code_simulator.h" #include "code_simulator_container.h" #include "common_compiler_test.h" #include "graph_checker.h" diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 38a1a8c024..0141c26352 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -1948,7 +1948,7 @@ static bool IsReferenceTypeRefinement(ReferenceTypeInfo declared_rti, declared_rti.IsStrictSupertypeOf(actual_rti); } -ReferenceTypeInfo HInliner::GetClassRTI(mirror::Class* klass) { +ReferenceTypeInfo HInliner::GetClassRTI(ObjPtr<mirror::Class> klass) { return ReferenceTypePropagation::IsAdmissible(klass) ? ReferenceTypeInfo::Create(handles_->NewHandle(klass)) : graph_->GetInexactObjectRti(); @@ -1976,9 +1976,8 @@ bool HInliner::ArgumentTypesMoreSpecific(HInvoke* invoke_instruction, ArtMethod* ++param_idx, ++input_idx) { HInstruction* input = invoke_instruction->InputAt(input_idx); if (input->GetType() == Primitive::kPrimNot) { - mirror::Class* param_cls = resolved_method->GetClassFromTypeIndex( - param_list->GetTypeItem(param_idx).type_idx_, - /* resolve */ false); + ObjPtr<mirror::Class> param_cls = resolved_method->LookupResolvedClassFromTypeIndex( + param_list->GetTypeItem(param_idx).type_idx_); if (IsReferenceTypeRefinement(GetClassRTI(param_cls), /* declared_can_be_null */ true, input)) { @@ -2027,7 +2026,7 @@ void HInliner::FixUpReturnReferenceType(ArtMethod* resolved_method, // TODO: we could be more precise by merging the phi inputs but that requires // some functionality from the reference type propagation. DCHECK(return_replacement->IsPhi()); - mirror::Class* cls = resolved_method->GetReturnType(false /* resolve */); + ObjPtr<mirror::Class> cls = resolved_method->LookupResolvedReturnType(); return_replacement->SetReferenceTypeInfo(GetClassRTI(cls)); } } diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index 62c6713208..c4b3a32d91 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -207,7 +207,7 @@ class HInliner : public HOptimization { // Creates an instance of ReferenceTypeInfo from `klass` if `klass` is // admissible (see ReferenceTypePropagation::IsAdmissible for details). // Otherwise returns inexact Object RTI. - ReferenceTypeInfo GetClassRTI(mirror::Class* klass) REQUIRES_SHARED(Locks::mutator_lock_); + ReferenceTypeInfo GetClassRTI(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); bool ArgumentTypesMoreSpecific(HInvoke* invoke_instruction, ArtMethod* resolved_method) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index f172e16ff9..561c9eafa2 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -848,7 +848,7 @@ void ReferenceTypePropagation::RTPVisitor::VisitInvoke(HInvoke* instr) { ScopedObjectAccess soa(Thread::Current()); ArtMethod* method = instr->GetResolvedMethod(); - mirror::Class* klass = (method == nullptr) ? nullptr : method->GetReturnType(/* resolve */ false); + ObjPtr<mirror::Class> klass = (method == nullptr) ? nullptr : method->LookupResolvedReturnType(); SetClassAsTypeInfo(instr, klass, /* is_exact */ false); } diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h index 215e96786b..b19f473e27 100644 --- a/compiler/optimizing/reference_type_propagation.h +++ b/compiler/optimizing/reference_type_propagation.h @@ -46,7 +46,7 @@ class ReferenceTypePropagation : public HOptimization { // Returns true if klass is admissible to the propagation: non-null and resolved. // For an array type, we also check if the component type is admissible. - static bool IsAdmissible(mirror::Class* klass) REQUIRES_SHARED(Locks::mutator_lock_) { + static bool IsAdmissible(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { return klass != nullptr && klass->IsResolved() && (!klass->IsArrayClass() || IsAdmissible(klass->GetComponentType())); diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc index 24e34508d1..2cbabcfb32 100644 --- a/compiler/utils/mips/assembler_mips.cc +++ b/compiler/utils/mips/assembler_mips.cc @@ -2920,6 +2920,102 @@ void MipsAssembler::IlvrD(VectorRegister wd, VectorRegister ws, VectorRegister w static_cast<FRegister>(wt)); } +void MipsAssembler::MaddvB(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x1, 0x0, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MaddvH(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x1, 0x1, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MaddvW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x1, 0x2, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MaddvD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x1, 0x3, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MsubvB(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x0, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MsubvH(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x1, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MsubvW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x2, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::MsubvD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x3, wt, ws, wd, 0x12), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::FmaddW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x0, wt, ws, wd, 0x1b), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::FmaddD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x1, wt, ws, wd, 0x1b), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::FmsubW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x2, wt, ws, wd, 0x1b), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + +void MipsAssembler::FmsubD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + DsFsmInstrFff(EmitMsa3R(0x2, 0x3, wt, ws, wd, 0x1b), + static_cast<FRegister>(wd), + static_cast<FRegister>(ws), + static_cast<FRegister>(wt)); +} + void MipsAssembler::ReplicateFPToVectorRegister(VectorRegister dst, FRegister src, bool is_double) { diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h index e42bb3fa3d..a7ff931e7e 100644 --- a/compiler/utils/mips/assembler_mips.h +++ b/compiler/utils/mips/assembler_mips.h @@ -613,6 +613,19 @@ class MipsAssembler FINAL : public Assembler, public JNIMacroAssembler<PointerSi void IlvrW(VectorRegister wd, VectorRegister ws, VectorRegister wt); void IlvrD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvB(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvH(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvB(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvH(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmaddW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmaddD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmsubW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmsubD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + // Helper for replicating floating point value in all destination elements. void ReplicateFPToVectorRegister(VectorRegister dst, FRegister src, bool is_double); diff --git a/compiler/utils/mips/assembler_mips32r6_test.cc b/compiler/utils/mips/assembler_mips32r6_test.cc index 6ee2a5cb79..b72a14e906 100644 --- a/compiler/utils/mips/assembler_mips32r6_test.cc +++ b/compiler/utils/mips/assembler_mips32r6_test.cc @@ -1752,6 +1752,66 @@ TEST_F(AssemblerMIPS32r6Test, IlvrD) { DriverStr(RepeatVVV(&mips::MipsAssembler::IlvrD, "ilvr.d ${reg1}, ${reg2}, ${reg3}"), "ilvr.d"); } +TEST_F(AssemblerMIPS32r6Test, MaddvB) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MaddvB, "maddv.b ${reg1}, ${reg2}, ${reg3}"), + "maddv.b"); +} + +TEST_F(AssemblerMIPS32r6Test, MaddvH) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MaddvH, "maddv.h ${reg1}, ${reg2}, ${reg3}"), + "maddv.h"); +} + +TEST_F(AssemblerMIPS32r6Test, MaddvW) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MaddvW, "maddv.w ${reg1}, ${reg2}, ${reg3}"), + "maddv.w"); +} + +TEST_F(AssemblerMIPS32r6Test, MaddvD) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MaddvD, "maddv.d ${reg1}, ${reg2}, ${reg3}"), + "maddv.d"); +} + +TEST_F(AssemblerMIPS32r6Test, MsubvB) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MsubvB, "msubv.b ${reg1}, ${reg2}, ${reg3}"), + "msubv.b"); +} + +TEST_F(AssemblerMIPS32r6Test, MsubvH) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MsubvH, "msubv.h ${reg1}, ${reg2}, ${reg3}"), + "msubv.h"); +} + +TEST_F(AssemblerMIPS32r6Test, MsubvW) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MsubvW, "msubv.w ${reg1}, ${reg2}, ${reg3}"), + "msubv.w"); +} + +TEST_F(AssemblerMIPS32r6Test, MsubvD) { + DriverStr(RepeatVVV(&mips::MipsAssembler::MsubvD, "msubv.d ${reg1}, ${reg2}, ${reg3}"), + "msubv.d"); +} + +TEST_F(AssemblerMIPS32r6Test, FmaddW) { + DriverStr(RepeatVVV(&mips::MipsAssembler::FmaddW, "fmadd.w ${reg1}, ${reg2}, ${reg3}"), + "fmadd.w"); +} + +TEST_F(AssemblerMIPS32r6Test, FmaddD) { + DriverStr(RepeatVVV(&mips::MipsAssembler::FmaddD, "fmadd.d ${reg1}, ${reg2}, ${reg3}"), + "fmadd.d"); +} + +TEST_F(AssemblerMIPS32r6Test, FmsubW) { + DriverStr(RepeatVVV(&mips::MipsAssembler::FmsubW, "fmsub.w ${reg1}, ${reg2}, ${reg3}"), + "fmsub.w"); +} + +TEST_F(AssemblerMIPS32r6Test, FmsubD) { + DriverStr(RepeatVVV(&mips::MipsAssembler::FmsubD, "fmsub.d ${reg1}, ${reg2}, ${reg3}"), + "fmsub.d"); +} + #undef __ } // namespace art diff --git a/compiler/utils/mips64/assembler_mips64.cc b/compiler/utils/mips64/assembler_mips64.cc index 90398540f8..7a1beb656b 100644 --- a/compiler/utils/mips64/assembler_mips64.cc +++ b/compiler/utils/mips64/assembler_mips64.cc @@ -1899,6 +1899,66 @@ void Mips64Assembler::IlvrD(VectorRegister wd, VectorRegister ws, VectorRegister EmitMsa3R(0x5, 0x3, wt, ws, wd, 0x14); } +void Mips64Assembler::MaddvB(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x1, 0x0, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MaddvH(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x1, 0x1, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MaddvW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x1, 0x2, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MaddvD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x1, 0x3, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MsubvB(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x0, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MsubvH(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x1, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MsubvW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x2, wt, ws, wd, 0x12); +} + +void Mips64Assembler::MsubvD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x3, wt, ws, wd, 0x12); +} + +void Mips64Assembler::FmaddW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x0, wt, ws, wd, 0x1b); +} + +void Mips64Assembler::FmaddD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x1, wt, ws, wd, 0x1b); +} + +void Mips64Assembler::FmsubW(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x2, wt, ws, wd, 0x1b); +} + +void Mips64Assembler::FmsubD(VectorRegister wd, VectorRegister ws, VectorRegister wt) { + CHECK(HasMsa()); + EmitMsa3R(0x2, 0x3, wt, ws, wd, 0x1b); +} + void Mips64Assembler::ReplicateFPToVectorRegister(VectorRegister dst, FpuRegister src, bool is_double) { diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h index 5e88033743..c39d120bce 100644 --- a/compiler/utils/mips64/assembler_mips64.h +++ b/compiler/utils/mips64/assembler_mips64.h @@ -796,6 +796,19 @@ class Mips64Assembler FINAL : public Assembler, public JNIMacroAssembler<Pointer void IlvrW(VectorRegister wd, VectorRegister ws, VectorRegister wt); void IlvrD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvB(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvH(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MaddvD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvB(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvH(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void MsubvD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmaddW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmaddD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmsubW(VectorRegister wd, VectorRegister ws, VectorRegister wt); + void FmsubD(VectorRegister wd, VectorRegister ws, VectorRegister wt); + // Helper for replicating floating point value in all destination elements. void ReplicateFPToVectorRegister(VectorRegister dst, FpuRegister src, bool is_double); diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc index bdf9598ee7..021e335697 100644 --- a/compiler/utils/mips64/assembler_mips64_test.cc +++ b/compiler/utils/mips64/assembler_mips64_test.cc @@ -3340,6 +3340,66 @@ TEST_F(AssemblerMIPS64Test, IlvrD) { "ilvr.d"); } +TEST_F(AssemblerMIPS64Test, MaddvB) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MaddvB, "maddv.b ${reg1}, ${reg2}, ${reg3}"), + "maddv.b"); +} + +TEST_F(AssemblerMIPS64Test, MaddvH) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MaddvH, "maddv.h ${reg1}, ${reg2}, ${reg3}"), + "maddv.h"); +} + +TEST_F(AssemblerMIPS64Test, MaddvW) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MaddvW, "maddv.w ${reg1}, ${reg2}, ${reg3}"), + "maddv.w"); +} + +TEST_F(AssemblerMIPS64Test, MaddvD) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MaddvD, "maddv.d ${reg1}, ${reg2}, ${reg3}"), + "maddv.d"); +} + +TEST_F(AssemblerMIPS64Test, MsubvB) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MsubvB, "msubv.b ${reg1}, ${reg2}, ${reg3}"), + "msubv.b"); +} + +TEST_F(AssemblerMIPS64Test, MsubvH) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MsubvH, "msubv.h ${reg1}, ${reg2}, ${reg3}"), + "msubv.h"); +} + +TEST_F(AssemblerMIPS64Test, MsubvW) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MsubvW, "msubv.w ${reg1}, ${reg2}, ${reg3}"), + "msubv.w"); +} + +TEST_F(AssemblerMIPS64Test, MsubvD) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::MsubvD, "msubv.d ${reg1}, ${reg2}, ${reg3}"), + "msubv.d"); +} + +TEST_F(AssemblerMIPS64Test, FmaddW) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::FmaddW, "fmadd.w ${reg1}, ${reg2}, ${reg3}"), + "fmadd.w"); +} + +TEST_F(AssemblerMIPS64Test, FmaddD) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::FmaddD, "fmadd.d ${reg1}, ${reg2}, ${reg3}"), + "fmadd.d"); +} + +TEST_F(AssemblerMIPS64Test, FmsubW) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::FmsubW, "fmsub.w ${reg1}, ${reg2}, ${reg3}"), + "fmsub.w"); +} + +TEST_F(AssemblerMIPS64Test, FmsubD) { + DriverStr(RepeatVVV(&mips64::Mips64Assembler::FmsubD, "fmsub.d ${reg1}, ${reg2}, ${reg3}"), + "fmsub.d"); +} + #undef __ } // namespace art diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index 72e2a6ce9f..e9f3f8022d 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -87,13 +87,13 @@ class VerifierDepsTest : public CommonCompilerTest { TimingLogger timings("Verify", false, false); // The compiler driver handles the verifier deps in the callbacks, so // remove what this class did for unit testing. - verifier_deps_.reset(nullptr); + if (deps == nullptr) { + // Create some verifier deps by default if they are not already specified. + deps = new verifier::VerifierDeps(dex_files_); + verifier_deps_.reset(deps); + } callbacks_->SetVerifierDeps(deps); compiler_driver_->Verify(class_loader_, dex_files_, &timings); - // The compiler driver may have updated the VerifierDeps in the callback object. - if (callbacks_->GetVerifierDeps() != deps) { - verifier_deps_.reset(callbacks_->GetVerifierDeps()); - } callbacks_->SetVerifierDeps(nullptr); // Clear entries in the verification results to avoid hitting a DCHECK that // we always succeed inserting a new entry after verifying. @@ -128,6 +128,7 @@ class VerifierDepsTest : public CommonCompilerTest { for (const DexFile* dex_file : dex_files_) { compiler_driver_->GetVerificationResults()->AddDexFile(dex_file); } + compiler_driver_->SetDexFilesForOatFile(dex_files_); } void LoadDexFile(ScopedObjectAccess* soa) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -1441,7 +1442,6 @@ TEST_F(VerifierDepsTest, CompilerDriver) { ASSERT_FALSE(verifier_deps_ == nullptr); ASSERT_FALSE(verifier_deps_->Equals(decoded_deps)); } else { - ASSERT_TRUE(verifier_deps_ == nullptr); VerifyClassStatus(decoded_deps); } } diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp index 346f5a7ef5..0d453efc0c 100644 --- a/dex2oat/Android.bp +++ b/dex2oat/Android.bp @@ -30,15 +30,6 @@ cc_defaults { android: { // Use the 32-bit version of dex2oat on devices compile_multilib: "prefer32", - - sanitize: { - // ASan slows down dex2oat by ~3.5x, which translates into - // extremely slow first boot. Disabled to help speed up - // SANITIZE_TARGET mode. - // Bug: 22233158 - address: false, - coverage: false, - }, }, }, diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 3cc41a6b29..0826fa1488 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -65,8 +65,10 @@ #include "elf_writer_quick.h" #include "gc/space/image_space.h" #include "gc/space/space-inl.h" +#include "gc/verification.h" #include "image_writer.h" #include "interpreter/unstarted_runtime.h" +#include "java_vm_ext.h" #include "jit/profile_compilation_info.h" #include "leb128.h" #include "linker/buffered_output_stream.h" @@ -593,7 +595,6 @@ class Dex2Oat FINAL { passes_to_run_filename_(nullptr), multi_image_(false), is_host_(false), - class_loader_(nullptr), elf_writers_(), oat_writers_(), rodata_(), @@ -1484,14 +1485,6 @@ class Dex2Oat FINAL { } } - void Shutdown() { - ScopedObjectAccess soa(Thread::Current()); - for (jobject dex_cache : dex_caches_) { - soa.Env()->DeleteLocalRef(dex_cache); - } - dex_caches_.clear(); - } - void LoadClassProfileDescriptors() { if (profile_compilation_info_ != nullptr && IsImage()) { Runtime* runtime = Runtime::Current(); @@ -1660,6 +1653,8 @@ class Dex2Oat FINAL { // If we need to downgrade the compiler-filter for size reasons. if (!IsBootImage() && IsVeryLarge(dex_files_)) { + // If we need to downgrade the compiler-filter for size reasons, do that early before we read + // it below for creating verification callbacks. if (!CompilerFilter::IsAsGoodAs(kLargeAppFilter, compiler_options_->GetCompilerFilter())) { LOG(INFO) << "Very large app, downgrading to verify."; // Note: this change won't be reflected in the key-value store, as that had to be @@ -1712,13 +1707,11 @@ class Dex2Oat FINAL { Thread* self = Thread::Current(); WellKnownClasses::Init(self->GetJniEnv()); - ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); if (!IsBootImage()) { constexpr bool kSaveDexInput = false; if (kSaveDexInput) { SaveDexInput(); } - class_loader_ = class_loader_context_->CreateClassLoader(dex_files_); } // Ensure opened dex files are writable for dex-to-dex transformations. @@ -1729,24 +1722,12 @@ class Dex2Oat FINAL { } } - // Ensure that the dex caches stay live since we don't want class unloading - // to occur during compilation. - for (const auto& dex_file : dex_files_) { - ScopedObjectAccess soa(self); - dex_caches_.push_back(soa.AddLocalReference<jobject>( - class_linker->RegisterDexFile(*dex_file, - soa.Decode<mirror::ClassLoader>(class_loader_).Ptr()))); - if (dex_caches_.back() == nullptr) { - soa.Self()->AssertPendingException(); - soa.Self()->ClearException(); - PLOG(ERROR) << "Failed to register dex file."; - return dex2oat::ReturnCode::kOther; - } - // Pre-register dex files so that we can access verification results without locks during - // compilation and verification. - if (verification_results_ != nullptr) { - // Verification results are only required for modes that have any compilation. Avoid - // adding the dex files if possible to prevent allocating large arrays. + // Verification results are only required for modes that have any compilation. Avoid + // adding the dex files if possible to prevent allocating large arrays. + if (verification_results_ != nullptr) { + for (const auto& dex_file : dex_files_) { + // Pre-register dex files so that we can access verification results without locks during + // compilation and verification. verification_results_->AddDexFile(dex_file); } } @@ -1759,13 +1740,50 @@ class Dex2Oat FINAL { return IsImage() && oat_fd_ != kInvalidFd; } - // Create and invoke the compiler driver. This will compile all the dex files. - void Compile() { + // Doesn't return the class loader since it's not meant to be used for image compilation. + void CompileDexFilesIndividually() { + CHECK(!IsImage()) << "Not supported with image"; + for (const DexFile* dex_file : dex_files_) { + std::vector<const DexFile*> dex_files(1u, dex_file); + VLOG(compiler) << "Compiling " << dex_file->GetLocation(); + jobject class_loader = CompileDexFiles(dex_files); + CHECK(class_loader != nullptr); + ScopedObjectAccess soa(Thread::Current()); + // Unload class loader to free RAM. + jweak weak_class_loader = soa.Env()->vm->AddWeakGlobalRef( + soa.Self(), + soa.Decode<mirror::ClassLoader>(class_loader)); + soa.Env()->vm->DeleteGlobalRef(soa.Self(), class_loader); + runtime_->GetHeap()->CollectGarbage(/*clear_soft_references*/ true); + ObjPtr<mirror::ClassLoader> decoded_weak = soa.Decode<mirror::ClassLoader>(weak_class_loader); + if (decoded_weak != nullptr) { + LOG(FATAL) << "Failed to unload class loader, path from root set: " + << runtime_->GetHeap()->GetVerification()->FirstPathFromRootSet(decoded_weak); + } + VLOG(compiler) << "Unloaded classloader"; + } + } + + bool ShouldCompileDexFilesIndividually() const { + // Compile individually if we are not building an image, not using any compilation, and are + // using multidex. + // This means extract, verify, and quicken will use the individual compilation mode (to reduce + // RAM used by the compiler). + // TODO: Still do it for app images to get testing coverage. Note that this will generate empty + // app images. + return !IsImage() && + dex_files_.size() > 1 && + !CompilerFilter::IsAnyCompilationEnabled(compiler_options_->GetCompilerFilter()); + } + + // Set up and create the compiler driver and then invoke it to compile all the dex files. + jobject Compile() { + ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); + TimingLogger::ScopedTiming t("dex2oat Compile", timings_); compiler_phases_timings_.reset(new CumulativeLogger("compilation times")); // Find the dex files we should not inline from. - std::vector<std::string> no_inline_filters; Split(no_inline_from_string_, ',', &no_inline_filters); @@ -1776,7 +1794,6 @@ class Dex2Oat FINAL { } if (!no_inline_filters.empty()) { - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); std::vector<const DexFile*> class_path_files; if (!IsBootImage()) { // The class loader context is used only for apps. @@ -1842,8 +1859,46 @@ class Dex2Oat FINAL { // experimentation. TimingLogger::ScopedTiming time_unquicken("Unquicken", timings_); VdexFile::Unquicken(dex_files_, input_vdex_file_->GetQuickeningInfo()); + } else { + // Create the main VerifierDeps, here instead of in the compiler since we want to aggregate + // the results for all the dex files, not just the results for the current dex file. + callbacks_->SetVerifierDeps(new verifier::VerifierDeps(dex_files_)); + } + // Invoke the compilation. + if (ShouldCompileDexFilesIndividually()) { + CompileDexFilesIndividually(); + // Return a null classloader since we already freed released it. + return nullptr; } - driver_->CompileAll(class_loader_, dex_files_, timings_); + return CompileDexFiles(dex_files_); + } + + // Create the class loader, use it to compile, and return. + jobject CompileDexFiles(const std::vector<const DexFile*>& dex_files) { + ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); + + jobject class_loader = nullptr; + if (!IsBootImage()) { + class_loader = class_loader_context_->CreateClassLoader(dex_files_); + } + + // Register dex caches and key them to the class loader so that they only unload when the + // class loader unloads. + for (const auto& dex_file : dex_files) { + ScopedObjectAccess soa(Thread::Current()); + // Registering the dex cache adds a strong root in the class loader that prevents the dex + // cache from being unloaded early. + ObjPtr<mirror::DexCache> dex_cache = class_linker->RegisterDexFile( + *dex_file, + soa.Decode<mirror::ClassLoader>(class_loader)); + if (dex_cache == nullptr) { + soa.Self()->AssertPendingException(); + LOG(FATAL) << "Failed to register dex file " << dex_file->GetLocation() << " " + << soa.Self()->GetException()->Dump(); + } + } + driver_->CompileAll(class_loader, dex_files, timings_); + return class_loader; } // Notes on the interleaving of creating the images and oat files to @@ -2800,8 +2855,6 @@ class Dex2Oat FINAL { // Dex files we are compiling, does not include the class path dex files. std::vector<const DexFile*> dex_files_; std::string no_inline_from_string_; - std::vector<jobject> dex_caches_; - jobject class_loader_; std::vector<std::unique_ptr<ElfWriter>> elf_writers_; std::vector<std::unique_ptr<OatWriter>> oat_writers_; @@ -2870,9 +2923,23 @@ static void b13564922() { #endif } +class ScopedGlobalRef { + public: + explicit ScopedGlobalRef(jobject obj) : obj_(obj) {} + ~ScopedGlobalRef() { + if (obj_ != nullptr) { + ScopedObjectAccess soa(Thread::Current()); + soa.Env()->vm->DeleteGlobalRef(soa.Self(), obj_); + } + } + + private: + jobject obj_; +}; + static dex2oat::ReturnCode CompileImage(Dex2Oat& dex2oat) { dex2oat.LoadClassProfileDescriptors(); - dex2oat.Compile(); + ScopedGlobalRef class_loader(dex2oat.Compile()); if (!dex2oat.WriteOutputFiles()) { dex2oat.EraseOutputFiles(); @@ -2920,7 +2987,7 @@ static dex2oat::ReturnCode CompileImage(Dex2Oat& dex2oat) { } static dex2oat::ReturnCode CompileApp(Dex2Oat& dex2oat) { - dex2oat.Compile(); + ScopedGlobalRef class_loader(dex2oat.Compile()); if (!dex2oat.WriteOutputFiles()) { dex2oat.EraseOutputFiles(); @@ -3014,7 +3081,6 @@ static dex2oat::ReturnCode Dex2oat(int argc, char** argv) { result = CompileApp(*dex2oat); } - dex2oat->Shutdown(); return result; } } // namespace art diff --git a/disassembler/disassembler_mips.cc b/disassembler/disassembler_mips.cc index 7cb216e766..1a395a45d2 100644 --- a/disassembler/disassembler_mips.cc +++ b/disassembler/disassembler_mips.cc @@ -477,6 +477,10 @@ static const MipsInstruction gMipsInstructions[] = { { kMsaSpecialMask | (0xf << 2), kMsa | (0x8 << 2), "ld", "kw" }, { kMsaSpecialMask | (0xf << 2), kMsa | (0x9 << 2), "st", "kw" }, { kMsaMask | (0x7 << 23), kMsa | (0x5 << 23) | 0x14, "ilvr", "Vkmn" }, + { kMsaMask | (0x7 << 23), kMsa | (0x1 << 23) | 0x12, "maddv", "Vkmn" }, + { kMsaMask | (0x7 << 23), kMsa | (0x2 << 23) | 0x12, "msubv", "Vkmn" }, + { kMsaMask | (0xf << 22), kMsa | (0x4 << 22) | 0x1b, "fmadd", "Ukmn" }, + { kMsaMask | (0xf << 22), kMsa | (0x5 << 22) | 0x1b, "fmsub", "Ukmn" }, }; static uint32_t ReadU32(const uint8_t* ptr) { diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index fb8e894581..7ef24c700e 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -1075,6 +1075,8 @@ class ImgDiagDumper { } } + std::vector<size_t> private_dirty_pages_for_section(ImageHeader::kSectionCount, 0u); + // Iterate through one byte at a time. ptrdiff_t page_off_begin = image_header_.GetImageBegin() - image_begin; for (uintptr_t begin = boot_map_.start; begin != boot_map_.end; ++begin) { @@ -1127,6 +1129,12 @@ class ImgDiagDumper { if (is_dirty && is_private) { mapping_data->private_dirty_pages++; + for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) { + const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i); + if (image_header_.GetImageSection(section).Contains(offset)) { + ++private_dirty_pages_for_section[i]; + } + } } } } @@ -1138,7 +1146,19 @@ class ImgDiagDumper { << mapping_data->dirty_pages << " pages are dirty;\n " << mapping_data->false_dirty_pages << " pages are false dirty;\n " << mapping_data->private_pages << " pages are private;\n " - << mapping_data->private_dirty_pages << " pages are Private_Dirty\n "; + << mapping_data->private_dirty_pages << " pages are Private_Dirty\n " + << "\n"; + + size_t total_private_dirty_pages = std::accumulate(private_dirty_pages_for_section.begin(), + private_dirty_pages_for_section.end(), + 0u); + os << "Image sections (total private dirty pages " << total_private_dirty_pages << ")\n"; + for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) { + const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i); + os << section << " " << image_header_.GetImageSection(section) + << " private dirty pages=" << private_dirty_pages_for_section[i] << "\n"; + } + os << "\n"; return true; } diff --git a/runtime/openjdkjvm/Android.bp b/openjdkjvm/Android.bp index 37112b61e7..071b4348f8 100644 --- a/runtime/openjdkjvm/Android.bp +++ b/openjdkjvm/Android.bp @@ -18,7 +18,6 @@ cc_defaults { defaults: ["art_defaults"], host_supported: true, srcs: ["OpenjdkJvm.cc"], - include_dirs: ["art/runtime"], shared_libs: [ "libbase", "libnativehelper" diff --git a/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION index e69de29bb2..e69de29bb2 100644 --- a/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION +++ b/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION diff --git a/runtime/openjdkjvm/NOTICE b/openjdkjvm/NOTICE index 700a206a6c..700a206a6c 100644 --- a/runtime/openjdkjvm/NOTICE +++ b/openjdkjvm/NOTICE diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc index b212ea1c20..b212ea1c20 100644 --- a/runtime/openjdkjvm/OpenjdkJvm.cc +++ b/openjdkjvm/OpenjdkJvm.cc diff --git a/runtime/Android.bp b/runtime/Android.bp index 8d15c349dd..952f8bf0c3 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -25,6 +25,7 @@ cc_defaults { defaults: ["art_defaults"], host_supported: true, srcs: [ + "aot_class_linker.cc", "art_field.cc", "art_method.cc", "atomic.cc", @@ -50,7 +51,6 @@ cc_defaults { "class_linker.cc", "class_loader_context.cc", "class_table.cc", - "code_simulator_container.cc", "common_throws.cc", "compiler_filter.cc", "debugger.cc", @@ -627,7 +627,5 @@ art_cc_test { } subdirs = [ - "openjdkjvm", "openjdkjvmti", - "simulator", ] diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc new file mode 100644 index 0000000000..871604bcaf --- /dev/null +++ b/runtime/aot_class_linker.cc @@ -0,0 +1,58 @@ +/* + * 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 "aot_class_linker.h" + +#include "handle_scope-inl.h" +#include "mirror/class.h" +#include "mirror/object-inl.h" +#include "runtime.h" + +namespace art { + +AotClassLinker::AotClassLinker(InternTable *intern_table) : ClassLinker(intern_table) {} + +AotClassLinker::~AotClassLinker() {} + +// Wrap the original InitializeClass with creation of transaction when in strict mode. +bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, + bool can_init_statics, bool can_init_parents) { + Runtime* const runtime = Runtime::Current(); + + DCHECK(klass != nullptr); + if (klass->IsInitialized() || klass->IsInitializing()) { + return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents); + } + + if (runtime->IsActiveStrictTransactionMode()) { + runtime->EnterTransactionMode(true, klass.Get()->AsClass()); + } + bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents); + + if (runtime->IsActiveStrictTransactionMode()) { + if (success) { + // Exit Transaction if success. + runtime->ExitTransactionMode(); + } else { + // If not successfully initialized, the last transaction must abort. Don't rollback + // immediately, leave the cleanup to compiler driver which needs abort message and exception. + DCHECK(runtime->IsTransactionAborted()); + DCHECK(self->IsExceptionPending()); + } + } + return success; +} +} // namespace art diff --git a/runtime/aot_class_linker.h b/runtime/aot_class_linker.h new file mode 100644 index 0000000000..11bea86fc4 --- /dev/null +++ b/runtime/aot_class_linker.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_AOT_CLASS_LINKER_H_ +#define ART_RUNTIME_AOT_CLASS_LINKER_H_ + +#include "class_linker.h" + +namespace art { +// AotClassLinker is only used for AOT compiler, which includes some logic for class initialization +// which will only be used in pre-compilation. +class AotClassLinker : public ClassLinker { + public: + explicit AotClassLinker(InternTable *intern_table); + ~AotClassLinker(); + + bool InitializeClass(Thread *self, + Handle<mirror::Class> klass, + bool can_run_clinit, + bool can_init_parents) + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); +}; +} // namespace art + +#endif // ART_RUNTIME_AOT_CLASS_LINKER_H_ diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 9a9f125718..fad927804e 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -154,20 +154,22 @@ inline bool ArtMethod::HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType return GetDexCacheResolvedMethods(pointer_size) == other_cache; } -inline mirror::Class* ArtMethod::GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve) { - // TODO: Refactor this function into two functions, Resolve...() and Lookup...() - // so that we can properly annotate it with no-suspension possible / suspension possible. +inline ObjPtr<mirror::Class> ArtMethod::LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) { ObjPtr<mirror::DexCache> dex_cache = GetDexCache(); ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx); if (UNLIKELY(type == nullptr)) { - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - if (resolve) { - type = class_linker->ResolveType(type_idx, this); - CHECK(type != nullptr || Thread::Current()->IsExceptionPending()); - } else { - type = class_linker->LookupResolvedType( - *dex_cache->GetDexFile(), type_idx, dex_cache, GetClassLoader()); - } + type = Runtime::Current()->GetClassLinker()->LookupResolvedType( + *dex_cache->GetDexFile(), type_idx, dex_cache, GetClassLoader()); + } + return type.Ptr(); +} + +inline ObjPtr<mirror::Class> ArtMethod::ResolveClassFromTypeIndex(dex::TypeIndex type_idx) { + ObjPtr<mirror::DexCache> dex_cache = GetDexCache(); + ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx); + if (UNLIKELY(type == nullptr)) { + type = Runtime::Current()->GetClassLinker()->ResolveType(type_idx, this); + CHECK(type != nullptr || Thread::Current()->IsExceptionPending()); } return type.Ptr(); } @@ -294,7 +296,7 @@ inline const DexFile::CodeItem* ArtMethod::GetCodeItem() { inline bool ArtMethod::IsResolvedTypeIdx(dex::TypeIndex type_idx) { DCHECK(!IsProxyMethod()); - return GetClassFromTypeIndex(type_idx, /* resolve */ false) != nullptr; + return LookupResolvedClassFromTypeIndex(type_idx) != nullptr; } inline int32_t ArtMethod::GetLineNumFromDexPC(uint32_t dex_pc) { @@ -403,13 +405,20 @@ inline void ArtMethod::SetDexCacheResolvedMethods(mirror::MethodDexCacheType* ne pointer_size); } -inline mirror::Class* ArtMethod::GetReturnType(bool resolve) { +inline dex::TypeIndex ArtMethod::GetReturnTypeIndex() { DCHECK(!IsProxyMethod()); const DexFile* dex_file = GetDexFile(); const DexFile::MethodId& method_id = dex_file->GetMethodId(GetDexMethodIndex()); const DexFile::ProtoId& proto_id = dex_file->GetMethodPrototype(method_id); - dex::TypeIndex return_type_idx = proto_id.return_type_idx_; - return GetClassFromTypeIndex(return_type_idx, resolve); + return proto_id.return_type_idx_; +} + +inline ObjPtr<mirror::Class> ArtMethod::LookupResolvedReturnType() { + return LookupResolvedClassFromTypeIndex(GetReturnTypeIndex()); +} + +inline ObjPtr<mirror::Class> ArtMethod::ResolveReturnType() { + return ResolveClassFromTypeIndex(GetReturnTypeIndex()); } inline bool ArtMethod::HasSingleImplementation() { diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 631f5e7250..7d8dedab6f 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -280,7 +280,7 @@ uint32_t ArtMethod::FindCatchBlock(Handle<mirror::Class> exception_type, break; } // Does this catch exception type apply? - mirror::Class* iter_exception_type = GetClassFromTypeIndex(iter_type_idx, true /* resolve */); + ObjPtr<mirror::Class> iter_exception_type = ResolveClassFromTypeIndex(iter_type_idx); if (UNLIKELY(iter_exception_type == nullptr)) { // Now have a NoClassDefFoundError as exception. Ignore in case the exception class was // removed by a pro-guard like tool. diff --git a/runtime/art_method.h b/runtime/art_method.h index 511ac8359c..cac40e011a 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -376,8 +376,11 @@ class ArtMethod FINAL { PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); - // Get the Class* from the type index into this method's dex cache. - mirror::Class* GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve) + // Lookup the Class* from the type index into this method's dex cache. + ObjPtr<mirror::Class> LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) + REQUIRES_SHARED(Locks::mutator_lock_); + // Resolve the Class* from the type index into this method's dex cache. + ObjPtr<mirror::Class> ResolveClassFromTypeIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_); // Returns true if this method has the same name and signature of the other method. @@ -592,9 +595,11 @@ class ArtMethod FINAL { const char* GetTypeDescriptorFromTypeIdx(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_); - // May cause thread suspension due to GetClassFromTypeIdx calling ResolveType this caused a large - // number of bugs at call sites. - mirror::Class* GetReturnType(bool resolve) REQUIRES_SHARED(Locks::mutator_lock_); + // Lookup return type. + ObjPtr<mirror::Class> LookupResolvedReturnType() REQUIRES_SHARED(Locks::mutator_lock_); + // Resolve return type. May cause thread suspension due to GetClassFromTypeIdx + // calling ResolveType this caused a large number of bugs at call sites. + ObjPtr<mirror::Class> ResolveReturnType() REQUIRES_SHARED(Locks::mutator_lock_); mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_); @@ -748,6 +753,8 @@ class ArtMethod FINAL { // Compare given pointer size to the image pointer size. static bool IsImagePointerSize(PointerSize pointer_size); + dex::TypeIndex GetReturnTypeIndex() REQUIRES_SHARED(Locks::mutator_lock_); + template<typename T> ALWAYS_INLINE T GetNativePointer(MemberOffset offset, PointerSize pointer_size) const { static_assert(std::is_pointer<T>::value, "T must be a pointer type"); diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index 0096c37b33..439ecaf28e 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -312,6 +312,17 @@ inline mirror::Class* ClassLinker::GetClassRoot(ClassRoot class_root) { return klass.Ptr(); } +template <class Visitor> +inline void ClassLinker::VisitClassTables(const Visitor& visitor) { + Thread* const self = Thread::Current(); + WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); + for (const ClassLoaderData& data : class_loaders_) { + if (data.class_table != nullptr) { + visitor(data.class_table); + } + } +} + } // namespace art #endif // ART_RUNTIME_CLASS_LINKER_INL_H_ diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 74c04d19b6..1219f6f39f 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -370,7 +370,10 @@ ClassLinker::ClassLinker(InternTable* intern_table) quick_imt_conflict_trampoline_(nullptr), quick_generic_jni_trampoline_(nullptr), quick_to_interpreter_bridge_trampoline_(nullptr), - image_pointer_size_(kRuntimePointerSize) { + image_pointer_size_(kRuntimePointerSize), + cha_(Runtime::Current()->IsAotCompiler() ? nullptr : new ClassHierarchyAnalysis()) { + // For CHA disabled during Aot, see b/34193647. + CHECK(intern_table_ != nullptr); static_assert(kFindArrayCacheSize == arraysize(find_array_class_cache_), "Array cache size wrong."); @@ -1138,49 +1141,6 @@ class FixupArtMethodArrayVisitor : public ArtMethodVisitor { const ImageHeader& header_; }; -class VerifyClassInTableArtMethodVisitor : public ArtMethodVisitor { - public: - explicit VerifyClassInTableArtMethodVisitor(ClassTable* table) : table_(table) {} - - virtual void Visit(ArtMethod* method) - REQUIRES_SHARED(Locks::mutator_lock_, Locks::classlinker_classes_lock_) { - ObjPtr<mirror::Class> klass = method->GetDeclaringClass(); - if (klass != nullptr && !Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass)) { - CHECK_EQ(table_->LookupByDescriptor(klass), klass) << mirror::Class::PrettyClass(klass); - } - } - - private: - ClassTable* const table_; -}; - -class VerifyDirectInterfacesInTableClassVisitor { - public: - explicit VerifyDirectInterfacesInTableClassVisitor(ObjPtr<mirror::ClassLoader> class_loader) - : class_loader_(class_loader), self_(Thread::Current()) { } - - bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { - if (!klass->IsPrimitive() && klass->GetClassLoader() == class_loader_) { - classes_.push_back(klass); - } - return true; - } - - void Check() const REQUIRES_SHARED(Locks::mutator_lock_) { - for (ObjPtr<mirror::Class> klass : classes_) { - for (uint32_t i = 0, num = klass->NumDirectInterfaces(); i != num; ++i) { - CHECK(klass->GetDirectInterface(self_, klass, i) != nullptr) - << klass->PrettyDescriptor() << " iface #" << i; - } - } - } - - private: - ObjPtr<mirror::ClassLoader> class_loader_; - Thread* self_; - std::vector<ObjPtr<mirror::Class>> classes_; -}; - class VerifyDeclaringClassVisitor : public ArtMethodVisitor { public: VerifyDeclaringClassVisitor() REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) @@ -1763,6 +1723,63 @@ class ImageSanityChecks FINAL { std::vector<const ImageSection*> runtime_method_sections_; }; +static void VerifyAppImage(const ImageHeader& header, + const Handle<mirror::ClassLoader>& class_loader, + const Handle<mirror::ObjectArray<mirror::DexCache> >& dex_caches, + ClassTable* class_table, gc::space::ImageSpace* space) + REQUIRES_SHARED(Locks::mutator_lock_) { + { + class VerifyClassInTableArtMethodVisitor : public ArtMethodVisitor { + public: + explicit VerifyClassInTableArtMethodVisitor(ClassTable* table) : table_(table) {} + + virtual void Visit(ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_, Locks::classlinker_classes_lock_) { + ObjPtr<mirror::Class> klass = method->GetDeclaringClass(); + if (klass != nullptr && !Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass)) { + CHECK_EQ(table_->LookupByDescriptor(klass), klass) << mirror::Class::PrettyClass(klass); + } + } + + private: + ClassTable* const table_; + }; + VerifyClassInTableArtMethodVisitor visitor(class_table); + header.VisitPackedArtMethods(&visitor, space->Begin(), kRuntimePointerSize); + } + { + // Verify that all direct interfaces of classes in the class table are also resolved. + std::vector<ObjPtr<mirror::Class>> classes; + auto verify_direct_interfaces_in_table = [&](ObjPtr<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!klass->IsPrimitive() && klass->GetClassLoader() == class_loader.Get()) { + classes.push_back(klass); + } + return true; + }; + class_table->Visit(verify_direct_interfaces_in_table); + Thread* self = Thread::Current(); + for (ObjPtr<mirror::Class> klass : classes) { + for (uint32_t i = 0, num = klass->NumDirectInterfaces(); i != num; ++i) { + CHECK(klass->GetDirectInterface(self, klass, i) != nullptr) + << klass->PrettyDescriptor() << " iface #" << i; + } + } + } + // Check that all non-primitive classes in dex caches are also in the class table. + for (int32_t i = 0; i < dex_caches->GetLength(); i++) { + ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get(i); + mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes(); + for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) { + ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read(); + if (klass != nullptr && !klass->IsPrimitive()) { + CHECK(class_table->Contains(klass)) + << klass->PrettyDescriptor() << " " << dex_cache->GetDexFile()->GetLocation(); + } + } + } +} + bool ClassLinker::AddImageSpace( gc::space::ImageSpace* space, Handle<mirror::ClassLoader> class_loader, @@ -2016,28 +2033,13 @@ bool ClassLinker::AddImageSpace( WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); class_table->AddClassSet(std::move(temp_set)); } + if (kIsDebugBuild && app_image) { // This verification needs to happen after the classes have been added to the class loader. // Since it ensures classes are in the class table. - VerifyClassInTableArtMethodVisitor visitor2(class_table); - header.VisitPackedArtMethods(&visitor2, space->Begin(), kRuntimePointerSize); - // Verify that all direct interfaces of classes in the class table are also resolved. - VerifyDirectInterfacesInTableClassVisitor visitor(class_loader.Get()); - class_table->Visit(visitor); - visitor.Check(); - // Check that all non-primitive classes in dex caches are also in the class table. - for (int32_t i = 0; i < dex_caches->GetLength(); i++) { - ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get(i); - mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes(); - for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) { - ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read(); - if (klass != nullptr && !klass->IsPrimitive()) { - CHECK(class_table->Contains(klass)) << klass->PrettyDescriptor() - << " " << dex_cache->GetDexFile()->GetLocation(); - } - } - } + VerifyAppImage(header, class_loader, dex_caches, class_table, space); } + VLOG(class_linker) << "Adding image space took " << PrettyDuration(NanoTime() - start_time); return true; } @@ -2315,16 +2317,15 @@ void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data) { JavaVMExt* const vm = runtime->GetJavaVM(); vm->DeleteWeakGlobalRef(self, data.weak_root); // Notify the JIT that we need to remove the methods and/or profiling info. - ClassHierarchyAnalysis* const cha = runtime->GetClassHierarchyAnalysis(); if (runtime->GetJit() != nullptr) { jit::JitCodeCache* code_cache = runtime->GetJit()->GetCodeCache(); if (code_cache != nullptr) { // For the JIT case, RemoveMethodsIn removes the CHA dependencies. code_cache->RemoveMethodsIn(self, *data.allocator); } - } else { + } else if (cha_ != nullptr) { // If we don't have a JIT, we need to manually remove the CHA dependencies manually. - cha->RemoveDependenciesForLinearAlloc(data.allocator); + cha_->RemoveDependenciesForLinearAlloc(data.allocator); } delete data.allocator; delete data.class_table; @@ -3494,7 +3495,8 @@ void ClassLinker::AppendToBootClassPath(const DexFile& dex_file, ObjPtr<mirror::DexCache> dex_cache) { CHECK(dex_cache != nullptr) << dex_file.GetLocation(); boot_class_path_.push_back(&dex_file); - RegisterBootClassPathDexFile(dex_file, dex_cache); + WriterMutexLock mu(Thread::Current(), *Locks::dex_lock_); + RegisterDexFileLocked(dex_file, dex_cache, /* class_loader */ nullptr); } void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file, @@ -3553,6 +3555,14 @@ void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file, data.resolved_methods = dex_cache->GetResolvedMethods(); data.class_table = ClassTableForClassLoader(class_loader); DCHECK(data.class_table != nullptr); + // Make sure to hold the dex cache live in the class table. This case happens for the boot class + // path dex caches without an image. + data.class_table->InsertStrongRoot(dex_cache); + if (class_loader != nullptr) { + // Since we added a strong root to the class table, do the write barrier as required for + // remembered sets and generational GCs. + Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader); + } dex_caches_.push_back(data); } @@ -3677,12 +3687,6 @@ ObjPtr<mirror::DexCache> ClassLinker::RegisterDexFile(const DexFile& dex_file, return h_dex_cache.Get(); } -void ClassLinker::RegisterBootClassPathDexFile(const DexFile& dex_file, - ObjPtr<mirror::DexCache> dex_cache) { - WriterMutexLock mu(Thread::Current(), *Locks::dex_lock_); - RegisterDexFileLocked(dex_file, dex_cache, /* class_loader */ nullptr); -} - bool ClassLinker::IsDexFileRegistered(Thread* self, const DexFile& dex_file) { ReaderMutexLock mu(self, *Locks::dex_lock_); return DecodeDexCache(self, FindDexCacheDataLocked(dex_file)) != nullptr; @@ -4716,7 +4720,7 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons CHECK_STREQ(np->GetName(), prototype->GetName()); CHECK_STREQ(np->GetShorty(), prototype->GetShorty()); // More complex sanity - via dex cache - CHECK_EQ(np->GetReturnType(true /* resolve */), prototype->GetReturnType(true /* resolve */)); + CHECK_EQ(np->ResolveReturnType(), prototype->ResolveReturnType()); } bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass, bool can_init_statics, @@ -5192,12 +5196,12 @@ static bool HasSameSignatureWithDifferentClassLoaders(Thread* self, REQUIRES_SHARED(Locks::mutator_lock_) { { StackHandleScope<1> hs(self); - Handle<mirror::Class> return_type(hs.NewHandle(method1->GetReturnType(true /* resolve */))); + Handle<mirror::Class> return_type(hs.NewHandle(method1->ResolveReturnType())); if (UNLIKELY(return_type == nullptr)) { ThrowSignatureCheckResolveReturnTypeException(klass, super_klass, method1, method1); return false; } - ObjPtr<mirror::Class> other_return_type = method2->GetReturnType(true /* resolve */); + ObjPtr<mirror::Class> other_return_type = method2->ResolveReturnType(); if (UNLIKELY(other_return_type == nullptr)) { ThrowSignatureCheckResolveReturnTypeException(klass, super_klass, method1, method2); return false; @@ -5242,7 +5246,7 @@ static bool HasSameSignatureWithDifferentClassLoaders(Thread* self, StackHandleScope<1> hs(self); dex::TypeIndex param_type_idx = types1->GetTypeItem(i).type_idx_; Handle<mirror::Class> param_type(hs.NewHandle( - method1->GetClassFromTypeIndex(param_type_idx, true /* resolve */))); + method1->ResolveClassFromTypeIndex(param_type_idx))); if (UNLIKELY(param_type == nullptr)) { ThrowSignatureCheckResolveArgException(klass, super_klass, method1, method1, i, param_type_idx); @@ -5250,7 +5254,7 @@ static bool HasSameSignatureWithDifferentClassLoaders(Thread* self, } dex::TypeIndex other_param_type_idx = types2->GetTypeItem(i).type_idx_; ObjPtr<mirror::Class> other_param_type = - method2->GetClassFromTypeIndex(other_param_type_idx, true /* resolve */); + method2->ResolveClassFromTypeIndex(other_param_type_idx); if (UNLIKELY(other_param_type == nullptr)) { ThrowSignatureCheckResolveArgException(klass, super_klass, method1, method2, i, other_param_type_idx); @@ -5487,7 +5491,9 @@ bool ClassLinker::LinkClass(Thread* self, // Update CHA info based on whether we override methods. // Have to do this before setting the class as resolved which allows // instantiation of klass. - Runtime::Current()->GetClassHierarchyAnalysis()->UpdateAfterLoadingOf(klass); + if (cha_ != nullptr) { + cha_->UpdateAfterLoadingOf(klass); + } // This will notify waiters on klass that saw the not yet resolved // class in the class_table_ during EnsureResolved. @@ -5535,7 +5541,9 @@ bool ClassLinker::LinkClass(Thread* self, // Update CHA info based on whether we override methods. // Have to do this before setting the class as resolved which allows // instantiation of klass. - Runtime::Current()->GetClassHierarchyAnalysis()->UpdateAfterLoadingOf(h_new_class); + if (cha_ != nullptr) { + cha_->UpdateAfterLoadingOf(h_new_class); + } // This will notify waiters on temp class that saw the not yet resolved class in the // class_table_ during EnsureResolved. @@ -8510,7 +8518,7 @@ mirror::MethodHandle* ClassLinker::ResolveMethodHandleForMethod( it.Next(); } - Handle<mirror::Class> return_type = hs.NewHandle(target_method->GetReturnType(true)); + Handle<mirror::Class> return_type = hs.NewHandle(target_method->ResolveReturnType()); if (UNLIKELY(return_type.IsNull())) { DCHECK(self->IsExceptionPending()); return nullptr; @@ -8587,7 +8595,7 @@ void ClassLinker::SetEntryPointsToInterpreter(ArtMethod* method) const { if (!method->IsNative()) { method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); } else { - SetEntryPointsToCompiledCode(method, GetQuickGenericJniStub()); + method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub()); } } @@ -8839,16 +8847,6 @@ void ClassLinker::DropFindArrayClassCache() { find_array_class_cache_next_victim_ = 0; } -void ClassLinker::ClearClassTableStrongRoots() const { - Thread* const self = Thread::Current(); - WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); - for (const ClassLoaderData& data : class_loaders_) { - if (data.class_table != nullptr) { - data.class_table->ClearStrongRoots(); - } - } -} - void ClassLinker::VisitClassLoaders(ClassLoaderVisitor* visitor) const { Thread* const self = Thread::Current(); for (const ClassLoaderData& data : class_loaders_) { diff --git a/runtime/class_linker.h b/runtime/class_linker.h index cb28187839..bf14aebb52 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -60,6 +60,7 @@ namespace mirror { using MethodDexCacheType = std::atomic<MethodDexCachePair>; } // namespace mirror +class ClassHierarchyAnalysis; class ClassTable; template<class T> class Handle; class ImtConflictTable; @@ -140,7 +141,7 @@ class ClassLinker { }; explicit ClassLinker(InternTable* intern_table); - ~ClassLinker(); + virtual ~ClassLinker(); // Initialize class linker by bootstraping from dex files. bool InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> boot_class_path, @@ -396,9 +397,6 @@ class ClassLinker { ObjPtr<mirror::ClassLoader> class_loader) REQUIRES(!Locks::dex_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - void RegisterBootClassPathDexFile(const DexFile& dex_file, ObjPtr<mirror::DexCache> dex_cache) - REQUIRES(!Locks::dex_lock_) - REQUIRES_SHARED(Locks::mutator_lock_); const std::vector<const DexFile*>& GetBootClassPath() { return boot_class_path_; @@ -637,9 +635,9 @@ class ClassLinker { // Create the IMT and conflict tables for a class. void FillIMTAndConflictTables(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); - // Clear class table strong roots (other than classes themselves). This is done by dex2oat to - // allow pruning dex caches. - void ClearClassTableStrongRoots() const + // Visit all of the class tables. This is used by dex2oat to allow pruning dex caches. + template <class Visitor> + void VisitClassTables(const Visitor& visitor) REQUIRES(!Locks::classlinker_classes_lock_) REQUIRES_SHARED(Locks::mutator_lock_); @@ -672,6 +670,10 @@ class ClassLinker { bool ValidateSuperClassDescriptors(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); + ClassHierarchyAnalysis* GetClassHierarchyAnalysis() { + return cha_.get(); + } + struct DexCacheData { // Construct an invalid data object. DexCacheData() @@ -700,6 +702,14 @@ class ClassLinker { ClassTable* class_table; }; + protected: + virtual bool InitializeClass(Thread* self, + Handle<mirror::Class> klass, + bool can_run_clinit, + bool can_init_parents) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + private: class LinkInterfaceMethodsHelper; @@ -718,7 +728,7 @@ class ClassLinker { REQUIRES(!Locks::dex_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - static void DeleteClassLoader(Thread* self, const ClassLoaderData& data) + void DeleteClassLoader(Thread* self, const ClassLoaderData& data) REQUIRES_SHARED(Locks::mutator_lock_); void VisitClassesInternal(ClassVisitor* visitor) @@ -889,12 +899,6 @@ class ClassLinker { REQUIRES(!Locks::dex_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - bool InitializeClass(Thread* self, - Handle<mirror::Class> klass, - bool can_run_clinit, - bool can_init_parents) - REQUIRES_SHARED(Locks::mutator_lock_) - REQUIRES(!Locks::dex_lock_); bool InitializeDefaultInterfaceRecursive(Thread* self, Handle<mirror::Class> klass, bool can_run_clinit, @@ -1268,6 +1272,8 @@ class ClassLinker { // Image pointer size. PointerSize image_pointer_size_; + std::unique_ptr<ClassHierarchyAnalysis> cha_; + class FindVirtualMethodHolderVisitor; friend class AppImageClassLoadersAndDexCachesHelper; diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc index eab3b86d3d..92d0f8d5ae 100644 --- a/runtime/class_loader_context.cc +++ b/runtime/class_loader_context.cc @@ -145,6 +145,10 @@ ClassLoaderContext::ExtractClassLoaderType(const std::string& class_loader_spec) // ClasspathElem is the path of dex/jar/apk file. bool ClassLoaderContext::Parse(const std::string& spec, bool parse_checksums) { if (spec.empty()) { + // By default we load the dex files in a PathClassLoader. + // So an empty spec is equivalent to an empty PathClassLoader (this happens when running + // tests) + class_loader_chain_.push_back(ClassLoaderInfo(kPathClassLoader)); return true; } @@ -265,12 +269,16 @@ std::string ClassLoaderContext::EncodeContextForOatFile(const std::string& base_ return OatFile::kSpecialSharedLibrary; } + std::ostringstream out; if (class_loader_chain_.empty()) { - return ""; + // We can get in this situation if the context was created with a class path containing the + // source dex files which were later removed (happens during run-tests). + out << GetClassLoaderTypeName(kPathClassLoader) + << kClassLoaderOpeningMark + << kClassLoaderClosingMark; + return out.str(); } - std::ostringstream out; - for (size_t i = 0; i < class_loader_chain_.size(); i++) { const ClassLoaderInfo& info = class_loader_chain_[i]; if (i > 0) { @@ -599,7 +607,8 @@ bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& contex if (expected_context.class_loader_chain_.size() != class_loader_chain_.size()) { LOG(WARNING) << "ClassLoaderContext size mismatch. expected=" << expected_context.class_loader_chain_.size() - << ", actual=" << class_loader_chain_.size(); + << ", actual=" << class_loader_chain_.size() + << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } @@ -609,13 +618,15 @@ bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& contex if (info.type != expected_info.type) { LOG(WARNING) << "ClassLoaderContext type mismatch for position " << i << ". expected=" << GetClassLoaderTypeName(expected_info.type) - << ", found=" << GetClassLoaderTypeName(info.type); + << ", found=" << GetClassLoaderTypeName(info.type) + << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } if (info.classpath.size() != expected_info.classpath.size()) { LOG(WARNING) << "ClassLoaderContext classpath size mismatch for position " << i << ". expected=" << expected_info.classpath.size() - << ", found=" << info.classpath.size(); + << ", found=" << info.classpath.size() + << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } @@ -626,13 +637,15 @@ bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& contex if (info.classpath[k] != expected_info.classpath[k]) { LOG(WARNING) << "ClassLoaderContext classpath element mismatch for position " << i << ". expected=" << expected_info.classpath[k] - << ", found=" << info.classpath[k]; + << ", found=" << info.classpath[k] + << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } if (info.checksums[k] != expected_info.checksums[k]) { LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch for position " << i << ". expected=" << expected_info.checksums[k] - << ", found=" << info.checksums[k]; + << ", found=" << info.checksums[k] + << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } } diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc index 5655aecbe5..2b85188e38 100644 --- a/runtime/class_loader_context_test.cc +++ b/runtime/class_loader_context_test.cc @@ -161,6 +161,19 @@ class ClassLoaderContextTest : public CommonRuntimeTest { } }; +TEST_F(ClassLoaderContextTest, ParseValidEmptyContext) { + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(""); + // An empty context should create a single empty PathClassLoader. + VerifyContextSize(context.get(), 1); + VerifyClassLoaderPCL(context.get(), 0, ""); +} + +TEST_F(ClassLoaderContextTest, ParseValidSharedLibraryContext) { + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create("&"); + // An shared library context should have no class loader in the chain. + VerifyContextSize(context.get(), 0); +} + TEST_F(ClassLoaderContextTest, ParseValidContextPCL) { std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create("PCL[a.dex]"); @@ -312,6 +325,34 @@ TEST_F(ClassLoaderContextTest, CreateClassLoaderWithEmptyContext) { soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader)); } +TEST_F(ClassLoaderContextTest, CreateClassLoaderWithSharedLibraryContext) { + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create("&"); + + ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, "")); + + std::vector<std::unique_ptr<const DexFile>> compilation_sources = OpenTestDexFiles("MultiDex"); + + std::vector<const DexFile*> compilation_sources_raw = + MakeNonOwningPointerVector(compilation_sources); + jobject jclass_loader = context->CreateClassLoader(compilation_sources_raw); + ASSERT_TRUE(jclass_loader != nullptr); + + ScopedObjectAccess soa(Thread::Current()); + + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader = hs.NewHandle( + soa.Decode<mirror::ClassLoader>(jclass_loader)); + + // A shared library context should create a single PathClassLoader with only the compilation + // sources. + VerifyClassLoaderDexFiles(soa, + class_loader, + WellKnownClasses::dalvik_system_PathClassLoader, + compilation_sources_raw); + ASSERT_TRUE(class_loader->GetParent()->GetClass() == + soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader)); +} + TEST_F(ClassLoaderContextTest, CreateClassLoaderWithComplexChain) { // Setup the context. std::vector<std::unique_ptr<const DexFile>> classpath_dex_a = OpenTestDexFiles("ForClassLoaderA"); diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h index b15d82f5e4..1280466a91 100644 --- a/runtime/class_table-inl.h +++ b/runtime/class_table-inl.h @@ -132,6 +132,13 @@ inline ClassTable::TableSlot::TableSlot(ObjPtr<mirror::Class> klass, uint32_t de } } +template <typename Filter> +inline void ClassTable::RemoveStrongRoots(const Filter& filter) { + WriterMutexLock mu(Thread::Current(), lock_); + strong_roots_.erase(std::remove_if(strong_roots_.begin(), strong_roots_.end(), filter), + strong_roots_.end()); +} + } // namespace art #endif // ART_RUNTIME_CLASS_TABLE_INL_H_ diff --git a/runtime/class_table.h b/runtime/class_table.h index 8616dfba93..a259725399 100644 --- a/runtime/class_table.h +++ b/runtime/class_table.h @@ -250,6 +250,12 @@ class ClassTable { REQUIRES(!lock_) REQUIRES_SHARED(Locks::mutator_lock_); + // Filter strong roots (other than classes themselves). + template <typename Filter> + void RemoveStrongRoots(const Filter& filter) + REQUIRES(!lock_) + REQUIRES_SHARED(Locks::mutator_lock_); + ReaderWriterMutex& GetLock() { return lock_; } diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 5a87ae8420..0b7af4e856 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -4008,8 +4008,8 @@ JDWP::JdwpError Dbg::PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thr if (shorty[i + 1] == 'L') { // Did we really get an argument of an appropriate reference type? - mirror::Class* parameter_type = - m->GetClassFromTypeIndex(types->GetTypeItem(i).type_idx_, true /* resolve */); + ObjPtr<mirror::Class> parameter_type = + m->ResolveClassFromTypeIndex(types->GetTypeItem(i).type_idx_); mirror::Object* argument = gRegistry->Get<mirror::Object*>(arg_values[i], &error); if (error != JDWP::ERR_NONE) { return JDWP::ERR_INVALID_OBJECT; diff --git a/runtime/dex_file_annotations.cc b/runtime/dex_file_annotations.cc index 2b81f0a99a..4225ab9b6e 100644 --- a/runtime/dex_file_annotations.cc +++ b/runtime/dex_file_annotations.cc @@ -695,8 +695,7 @@ mirror::Object* CreateAnnotationMember(const ClassData& klass, if (annotation_method == nullptr) { return nullptr; } - Handle<mirror::Class> method_return(hs.NewHandle( - annotation_method->GetReturnType(true /* resolve */))); + Handle<mirror::Class> method_return(hs.NewHandle(annotation_method->ResolveReturnType())); DexFile::AnnotationValue annotation_value; if (!ProcessAnnotationValue<false>(klass, @@ -762,15 +761,16 @@ const DexFile::AnnotationItem* GetAnnotationItemFromAnnotationSet( } const uint8_t* annotation = annotation_item->annotation_; uint32_t type_index = DecodeUnsignedLeb128(&annotation); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); mirror::Class* resolved_class; if (lookup_in_resolved_boot_classes) { + // Note: We cannot use ClassLinker::LookupResolvedType() because the current DexCache + // may not be registered with the boot class path ClassLoader and we must not pollute + // the DexCache with classes that are not in the associated ClassLoader's ClassTable. + const char* descriptor = dex_file.StringByTypeIdx(dex::TypeIndex(type_index)); ObjPtr<mirror::Class> looked_up_class = - Runtime::Current()->GetClassLinker()->LookupResolvedType( - klass.GetDexFile(), - dex::TypeIndex(type_index), - klass.GetDexCache(), - // Force the use of the bootstrap class loader. - static_cast<mirror::ClassLoader*>(nullptr)); + class_linker->LookupClass(self, descriptor, /* class_loader */ nullptr); resolved_class = looked_up_class.Ptr(); if (resolved_class == nullptr) { // If `resolved_class` is null, this is fine: just ignore that @@ -779,8 +779,8 @@ const DexFile::AnnotationItem* GetAnnotationItemFromAnnotationSet( continue; } } else { - StackHandleScope<2> hs(Thread::Current()); - resolved_class = Runtime::Current()->GetClassLinker()->ResolveType( + StackHandleScope<2> hs(self); + resolved_class = class_linker->ResolveType( klass.GetDexFile(), dex::TypeIndex(type_index), hs.NewHandle(klass.GetDexCache()), @@ -789,8 +789,8 @@ const DexFile::AnnotationItem* GetAnnotationItemFromAnnotationSet( std::string temp; LOG(WARNING) << StringPrintf("Unable to resolve %s annotation class %d", klass.GetRealClass()->GetDescriptor(&temp), type_index); - CHECK(Thread::Current()->IsExceptionPending()); - Thread::Current()->ClearException(); + CHECK(self->IsExceptionPending()); + self->ClearException(); continue; } } @@ -1073,7 +1073,7 @@ mirror::Object* GetAnnotationDefaultValue(ArtMethod* method) { } DexFile::AnnotationValue annotation_value; StackHandleScope<1> hs(Thread::Current()); - Handle<mirror::Class> return_type(hs.NewHandle(method->GetReturnType(true /* resolve */))); + Handle<mirror::Class> return_type(hs.NewHandle(method->ResolveReturnType())); if (!ProcessAnnotationValue<false>(klass, &annotation, &annotation_value, diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index 01fc9ce668..2bf4372b1f 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -45,7 +45,7 @@ void CheckReferenceResult(Handle<mirror::Object> o, Thread* self) { } // Make sure that the result is an instance of the type this method was expected to return. ArtMethod* method = self->GetCurrentMethod(nullptr); - mirror::Class* return_type = method->GetReturnType(true /* resolve */); + ObjPtr<mirror::Class> return_type = method->ResolveReturnType(); if (!o->InstanceOf(return_type)) { Runtime::Current()->GetJavaVM()->JniAbortF(nullptr, @@ -108,7 +108,7 @@ JValue InvokeProxyInvocationHandler(ScopedObjectAccessAlreadyRunnable& soa, cons ArtMethod* interface_method = soa.Decode<mirror::Method>(interface_method_jobj)->GetArtMethod(); // This can cause thread suspension. - mirror::Class* result_type = interface_method->GetReturnType(true /* resolve */); + ObjPtr<mirror::Class> result_type = interface_method->ResolveReturnType(); ObjPtr<mirror::Object> result_ref = soa.Decode<mirror::Object>(result); JValue result_unboxed; if (!UnboxPrimitiveForResult(result_ref.Ptr(), result_type, &result_unboxed)) { diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 90b5def9fe..9969489648 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -196,6 +196,10 @@ class Instrumentation { } bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_); + bool CanDeoptimize() { + return deoptimization_enabled_; + } + // Executes everything with interpreter. void DeoptimizeEverything(const char* key) REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) @@ -457,8 +461,7 @@ class Instrumentation { // This is used by the debugger to cause a deoptimization of the thread's stack after updating // local variable(s). void InstrumentThreadStack(Thread* thread) - REQUIRES_SHARED(Locks::mutator_lock_) - REQUIRES(!Locks::thread_list_lock_); + REQUIRES_SHARED(Locks::mutator_lock_); static size_t ComputeFrameId(Thread* self, size_t frame_depth, diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 85904ee4ed..136d0c6b64 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -1070,7 +1070,7 @@ static inline bool DoCallCommon(ArtMethod* called_method, // Preserve o since it is used below and GetClassFromTypeIndex may cause thread // suspension. HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&o); - arg_type = method->GetClassFromTypeIndex(type_idx, true /* resolve */); + arg_type = method->ResolveClassFromTypeIndex(type_idx); if (arg_type == nullptr) { CHECK(self->IsExceptionPending()); return false; diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 0a2705d5f7..bdb83326fd 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -349,7 +349,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, const size_t ref_idx = inst->VRegA_11x(inst_data); ObjPtr<mirror::Object> obj_result = shadow_frame.GetVRegReference(ref_idx); if (do_assignability_check && obj_result != nullptr) { - ObjPtr<mirror::Class> return_type = method->GetReturnType(true /* resolve */); + ObjPtr<mirror::Class> return_type = method->ResolveReturnType(); // Re-load since it might have moved. obj_result = shadow_frame.GetVRegReference(ref_idx); if (return_type == nullptr) { diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 2aa2ff2ef2..a030a51473 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -642,7 +642,7 @@ void JitCodeCache::FreeAllMethodHeaders( // method_headers are expected to be in the executable region. { MutexLock mu(Thread::Current(), *Locks::cha_lock_); - Runtime::Current()->GetClassHierarchyAnalysis() + Runtime::Current()->GetClassLinker()->GetClassHierarchyAnalysis() ->RemoveDependentsWithMethodHeaders(method_headers); } @@ -977,7 +977,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, << "Should not be using cha on debuggable apps/runs!"; for (ArtMethod* single_impl : cha_single_implementation_list) { - Runtime::Current()->GetClassHierarchyAnalysis()->AddDependency( + Runtime::Current()->GetClassLinker()->GetClassHierarchyAnalysis()->AddDependency( single_impl, method, method_header); } diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc index 2aad12d3b8..f209f1d73a 100644 --- a/runtime/native/java_lang_reflect_Executable.cc +++ b/runtime/native/java_lang_reflect_Executable.cc @@ -260,7 +260,7 @@ static jobject Executable_getMethodReturnTypeInternal(JNIEnv* env, jobject javaM ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize); - ObjPtr<mirror::Class> return_type(method->GetReturnType(true /* resolve */)); + ObjPtr<mirror::Class> return_type(method->ResolveReturnType()); if (return_type.IsNull()) { CHECK(soa.Self()->IsExceptionPending()); return nullptr; diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 3c1311b18a..af770724ab 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -503,112 +503,112 @@ class JvmtiFunctions { } static jvmtiError GetLocalObject(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jobject* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jobject* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalInstance(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jobject* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jobject* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalInstance(env, thread, depth, value_ptr); } static jvmtiError GetLocalInt(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jint* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jint* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalLong(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jlong* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jlong* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalFloat(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jfloat* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jfloat* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalDouble(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jdouble* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jdouble* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError SetLocalObject(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jobject value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jobject value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalInt(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jint value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jint value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalLong(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jlong value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jlong value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalFloat(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jfloat value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jfloat value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalDouble(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jdouble value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jdouble value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } @@ -904,12 +904,12 @@ class JvmtiFunctions { } static jvmtiError GetLocalVariableTable(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jint* entry_count_ptr ATTRIBUTE_UNUSED, - jvmtiLocalVariableEntry** table_ptr ATTRIBUTE_UNUSED) { + jmethodID method, + jint* entry_count_ptr, + jvmtiLocalVariableEntry** table_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariableTable(env, method, entry_count_ptr, table_ptr); } static jvmtiError GetBytecodes(jvmtiEnv* env, diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 4d5bb95f2c..ce30c244dc 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -204,6 +204,10 @@ static inline jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, ALWAYS_INLINE static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) { + if (src == nullptr) { + JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, 0, error); + return ret; + } size_t len = strlen(src) + 1; JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error); if (ret != nullptr) { @@ -227,8 +231,8 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_get_source_file_name = 1, .can_get_line_numbers = 1, .can_get_source_debug_extension = 1, - .can_access_local_variables = 0, - .can_maintain_original_method_order = 0, + .can_access_local_variables = 1, + .can_maintain_original_method_order = 1, .can_generate_single_step_events = 1, .can_generate_exception_events = 0, .can_generate_frame_pop_events = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 43177ab845..91e40553f0 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -414,9 +414,10 @@ inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, bool added) { ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable : ArtJvmtiEvent::kClassFileLoadHookRetransformable; - return caps.can_retransform_classes == 1 && - IsEventEnabledAnywhere(event) && - env->event_masks.IsEnabledAnywhere(event); + return (added && caps.can_access_local_variables == 1) || + (caps.can_retransform_classes == 1 && + IsEventEnabledAnywhere(event) && + env->event_masks.IsEnabledAnywhere(event)); } inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, @@ -428,6 +429,9 @@ inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable); RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); } + if (added && caps.can_access_local_variables == 1) { + HandleLocalAccessCapabilityAdded(); + } } } diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 7a930d4163..2944a453e0 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -610,6 +610,21 @@ static void SetupTraceListener(JvmtiMethodTraceListener* listener, } } +void EventHandler::HandleLocalAccessCapabilityAdded() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative); + art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); + art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(), + art::gc::kGcCauseInstrumentation, + art::gc::kCollectorTypeInstrumentation); + art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true); + // TODO This should be disabled when there are no environments using it. + if (!instr->CanDeoptimize()) { + instr->EnableDeoptimization(); + } + // TODO We should be able to support can_access_local_variables without this. + instr->DeoptimizeEverything("jvmti-local-variable-access"); +} + // Handle special work for the given event type, if necessary. void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { switch (event) { diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h index 5f37dcf0a7..617519eaa3 100644 --- a/runtime/openjdkjvmti/events.h +++ b/runtime/openjdkjvmti/events.h @@ -210,6 +210,7 @@ class EventHandler { unsigned char** new_class_data) const; void HandleEventType(ArtJvmtiEvent event, bool enable); + void HandleLocalAccessCapabilityAdded(); // List of all JvmTiEnv objects that have been created, in their creation order. // NB Some elements might be null representing envs that have been deleted. They should be skipped diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc index ab434d7d9a..8f727147a4 100644 --- a/runtime/openjdkjvmti/ti_method.cc +++ b/runtime/openjdkjvmti/ti_method.cc @@ -34,16 +34,22 @@ #include "art_jvmti.h" #include "art_method-inl.h" #include "base/enums.h" +#include "base/mutex-inl.h" #include "dex_file_annotations.h" #include "events-inl.h" #include "jni_internal.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "mirror/object-inl.h" #include "mirror/object_array-inl.h" #include "modifiers.h" #include "nativehelper/ScopedLocalRef.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" +#include "stack.h" #include "thread-current-inl.h" #include "thread_list.h" +#include "ti_thread.h" #include "ti_phase.h" namespace openjdkjvmti { @@ -159,6 +165,106 @@ jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED, return ERR(NONE); } +jvmtiError MethodUtil::GetLocalVariableTable(jvmtiEnv* env, + jmethodID method, + jint* entry_count_ptr, + jvmtiLocalVariableEntry** table_ptr) { + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + art::ArtMethod* art_method = art::jni::DecodeArtMethod(method); + + if (art_method->IsNative()) { + return ERR(NATIVE_METHOD); + } + + if (entry_count_ptr == nullptr || table_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::ScopedObjectAccess soa(art::Thread::Current()); + const art::DexFile* dex_file = art_method->GetDexFile(); + const art::DexFile::CodeItem* code_item = art_method->GetCodeItem(); + // TODO code_item == nullptr means that the method is abstract (or native, but we check that + // earlier). We should check what is returned by the RI in this situation since it's not clear + // what the appropriate return value is from the spec. + if (dex_file == nullptr || code_item == nullptr) { + return ERR(ABSENT_INFORMATION); + } + + struct LocalVariableContext { + explicit LocalVariableContext(jvmtiEnv* jenv) : env_(jenv), variables_(), err_(OK) {} + + static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) { + reinterpret_cast<LocalVariableContext*>(raw_ctx)->Insert(entry); + } + + void Insert(const art::DexFile::LocalInfo& entry) { + if (err_ != OK) { + return; + } + JvmtiUniquePtr<char[]> name_str = CopyString(env_, entry.name_, &err_); + if (err_ != OK) { + return; + } + JvmtiUniquePtr<char[]> sig_str = CopyString(env_, entry.descriptor_, &err_); + if (err_ != OK) { + return; + } + JvmtiUniquePtr<char[]> generic_sig_str = CopyString(env_, entry.signature_, &err_); + if (err_ != OK) { + return; + } + variables_.push_back({ + .start_location = static_cast<jlocation>(entry.start_address_), + .length = static_cast<jint>(entry.end_address_ - entry.start_address_), + .name = name_str.release(), + .signature = sig_str.release(), + .generic_signature = generic_sig_str.release(), + .slot = entry.reg_, + }); + } + + jvmtiError Release(jint* out_entry_count_ptr, jvmtiLocalVariableEntry** out_table_ptr) { + jlong table_size = sizeof(jvmtiLocalVariableEntry) * variables_.size(); + if (err_ != OK || + (err_ = env_->Allocate(table_size, + reinterpret_cast<unsigned char**>(out_table_ptr))) != OK) { + Cleanup(); + return err_; + } else { + *out_entry_count_ptr = variables_.size(); + memcpy(*out_table_ptr, variables_.data(), table_size); + return OK; + } + } + + void Cleanup() { + for (jvmtiLocalVariableEntry& e : variables_) { + env_->Deallocate(reinterpret_cast<unsigned char*>(e.name)); + env_->Deallocate(reinterpret_cast<unsigned char*>(e.signature)); + env_->Deallocate(reinterpret_cast<unsigned char*>(e.generic_signature)); + } + } + + jvmtiEnv* env_; + std::vector<jvmtiLocalVariableEntry> variables_; + jvmtiError err_; + }; + + LocalVariableContext context(env); + if (!dex_file->DecodeDebugLocalInfo(code_item, + art_method->IsStatic(), + art_method->GetDexMethodIndex(), + LocalVariableContext::Callback, + &context)) { + // Something went wrong with decoding the debug information. It might as well not be there. + return ERR(ABSENT_INFORMATION); + } else { + return context.Release(entry_count_ptr, table_ptr); + } +} + jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED, jmethodID method, jint* max_ptr) { @@ -425,4 +531,541 @@ jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* i return IsMethodT(env, m, test, is_synthetic_ptr); } +struct FindFrameAtDepthVisitor : art::StackVisitor { + public: + FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth) + REQUIRES_SHARED(art::Locks::mutator_lock_) + : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames), + found_frame_(false), + cnt_(0), + depth_(static_cast<size_t>(depth)) { } + + bool FoundFrame() { + return found_frame_; + } + + bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { + if (GetMethod()->IsRuntimeMethod()) { + return true; + } + if (cnt_ == depth_) { + // We found our frame, exit. + found_frame_ = true; + return false; + } else { + cnt_++; + return true; + } + } + + private: + bool found_frame_; + size_t cnt_; + size_t depth_; +}; + +class CommonLocalVariableClosure : public art::Closure { + public: + CommonLocalVariableClosure(art::Thread* caller, + jint depth, + jint slot) + : result_(ERR(INTERNAL)), caller_(caller), depth_(depth), slot_(slot) {} + + void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current()); + std::unique_ptr<art::Context> context(art::Context::Create()); + FindFrameAtDepthVisitor visitor(self, context.get(), depth_); + visitor.WalkStack(); + if (!visitor.FoundFrame()) { + // Must have been a bad depth. + result_ = ERR(NO_MORE_FRAMES); + return; + } + art::ArtMethod* method = visitor.GetMethod(); + if (method->IsNative() || !visitor.IsShadowFrame()) { + // TODO We really should support get/set for non-shadow frames. + result_ = ERR(OPAQUE_FRAME); + return; + } else if (method->GetCodeItem()->registers_size_ <= slot_) { + result_ = ERR(INVALID_SLOT); + return; + } + uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false); + if (pc == art::DexFile::kDexNoIndex) { + // Cannot figure out current PC. + result_ = ERR(OPAQUE_FRAME); + return; + } + std::string descriptor; + art::Primitive::Type slot_type = art::Primitive::kPrimVoid; + jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type); + if (err != OK) { + result_ = err; + return; + } + + err = GetTypeError(method, slot_type, descriptor); + if (err != OK) { + result_ = err; + return; + } + result_ = Execute(method, visitor); + } + + jvmtiError GetResult() const { + return result_; + } + + protected: + virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) + REQUIRES(art::Locks::mutator_lock_) = 0; + virtual jvmtiError GetTypeError(art::ArtMethod* method, + art::Primitive::Type type, + const std::string& descriptor) + REQUIRES(art::Locks::mutator_lock_) = 0; + + jvmtiError GetSlotType(art::ArtMethod* method, + uint32_t dex_pc, + /*out*/std::string* descriptor, + /*out*/art::Primitive::Type* type) + REQUIRES(art::Locks::mutator_lock_) { + const art::DexFile* dex_file = method->GetDexFile(); + const art::DexFile::CodeItem* code_item = method->GetCodeItem(); + if (dex_file == nullptr || code_item == nullptr) { + return ERR(OPAQUE_FRAME); + } + + struct GetLocalVariableInfoContext { + explicit GetLocalVariableInfoContext(jint slot, + uint32_t pc, + std::string* out_descriptor, + art::Primitive::Type* out_type) + : found_(false), jslot_(slot), pc_(pc), descriptor_(out_descriptor), type_(out_type) { + *descriptor_ = ""; + *type_ = art::Primitive::kPrimVoid; + } + + static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) { + reinterpret_cast<GetLocalVariableInfoContext*>(raw_ctx)->Handle(entry); + } + + void Handle(const art::DexFile::LocalInfo& entry) { + if (found_) { + return; + } else if (entry.start_address_ <= pc_ && + entry.end_address_ > pc_ && + entry.reg_ == jslot_) { + found_ = true; + *type_ = art::Primitive::GetType(entry.descriptor_[0]); + *descriptor_ = entry.descriptor_; + } + return; + } + + bool found_; + jint jslot_; + uint32_t pc_; + std::string* descriptor_; + art::Primitive::Type* type_; + }; + + GetLocalVariableInfoContext context(slot_, dex_pc, descriptor, type); + if (!dex_file->DecodeDebugLocalInfo(code_item, + method->IsStatic(), + method->GetDexMethodIndex(), + GetLocalVariableInfoContext::Callback, + &context) || !context.found_) { + // Something went wrong with decoding the debug information. It might as well not be there. + return ERR(INVALID_SLOT); + } else { + return OK; + } + } + + jvmtiError result_; + art::Thread* caller_; + jint depth_; + jint slot_; +}; + +class GetLocalVariableClosure : public CommonLocalVariableClosure { + public: + GetLocalVariableClosure(art::Thread* caller, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue* val) + : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {} + + protected: + jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED, + art::Primitive::Type slot_type, + const std::string& descriptor ATTRIBUTE_UNUSED) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (slot_type) { + case art::Primitive::kPrimByte: + case art::Primitive::kPrimChar: + case art::Primitive::kPrimInt: + case art::Primitive::kPrimShort: + case art::Primitive::kPrimBoolean: + return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimLong: + case art::Primitive::kPrimFloat: + case art::Primitive::kPrimDouble: + case art::Primitive::kPrimNot: + return type_ == slot_type ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected primitive type " << slot_type; + UNREACHABLE(); + } + } + + jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (type_) { + case art::Primitive::kPrimNot: { + uint32_t ptr_val; + if (!visitor.GetVReg(method, + static_cast<uint16_t>(slot_), + art::kReferenceVReg, + &ptr_val)) { + return ERR(OPAQUE_FRAME); + } + art::ObjPtr<art::mirror::Object> obj(reinterpret_cast<art::mirror::Object*>(ptr_val)); + val_->l = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj); + break; + } + case art::Primitive::kPrimInt: + case art::Primitive::kPrimFloat: { + if (!visitor.GetVReg(method, + static_cast<uint16_t>(slot_), + type_ == art::Primitive::kPrimFloat ? art::kFloatVReg : art::kIntVReg, + reinterpret_cast<uint32_t*>(&val_->i))) { + return ERR(OPAQUE_FRAME); + } + break; + } + case art::Primitive::kPrimDouble: + case art::Primitive::kPrimLong: { + auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg; + auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg; + if (!visitor.GetVRegPair(method, + static_cast<uint16_t>(slot_), + lo_type, + high_type, + reinterpret_cast<uint64_t*>(&val_->j))) { + return ERR(OPAQUE_FRAME); + } + break; + } + default: { + LOG(FATAL) << "unexpected register type " << type_; + UNREACHABLE(); + } + } + return OK; + } + + private: + art::Primitive::Type type_; + jvalue* val_; +}; + +jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue* val) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + GetLocalVariableClosure c(self, depth, slot, type, val); + if (!target->RequestSynchronousCheckpoint(&c)) { + return ERR(THREAD_NOT_ALIVE); + } else { + return c.GetResult(); + } +} + +class SetLocalVariableClosure : public CommonLocalVariableClosure { + public: + SetLocalVariableClosure(art::Thread* caller, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue val) + : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {} + + protected: + jvmtiError GetTypeError(art::ArtMethod* method, + art::Primitive::Type slot_type, + const std::string& descriptor) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (slot_type) { + case art::Primitive::kPrimNot: { + if (type_ != art::Primitive::kPrimNot) { + return ERR(TYPE_MISMATCH); + } else if (val_.l == nullptr) { + return OK; + } else { + art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker(); + art::ObjPtr<art::mirror::Class> set_class = + caller_->DecodeJObject(val_.l)->GetClass(); + art::ObjPtr<art::mirror::ClassLoader> loader = + method->GetDeclaringClass()->GetClassLoader(); + art::ObjPtr<art::mirror::Class> slot_class = + cl->LookupClass(caller_, descriptor.c_str(), loader); + DCHECK(!slot_class.IsNull()); + return slot_class->IsAssignableFrom(set_class) ? OK : ERR(TYPE_MISMATCH); + } + } + case art::Primitive::kPrimByte: + case art::Primitive::kPrimChar: + case art::Primitive::kPrimInt: + case art::Primitive::kPrimShort: + case art::Primitive::kPrimBoolean: + return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimLong: + case art::Primitive::kPrimFloat: + case art::Primitive::kPrimDouble: + return type_ == slot_type ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected primitive type " << slot_type; + UNREACHABLE(); + } + } + + jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (type_) { + case art::Primitive::kPrimNot: { + uint32_t ptr_val; + art::ObjPtr<art::mirror::Object> obj(caller_->DecodeJObject(val_.l)); + ptr_val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(obj.Ptr())); + if (!visitor.SetVReg(method, + static_cast<uint16_t>(slot_), + ptr_val, + art::kReferenceVReg)) { + return ERR(OPAQUE_FRAME); + } + break; + } + case art::Primitive::kPrimInt: + case art::Primitive::kPrimFloat: { + if (!visitor.SetVReg(method, + static_cast<uint16_t>(slot_), + static_cast<uint32_t>(val_.i), + type_ == art::Primitive::kPrimFloat ? art::kFloatVReg + : art::kIntVReg)) { + return ERR(OPAQUE_FRAME); + } + break; + } + case art::Primitive::kPrimDouble: + case art::Primitive::kPrimLong: { + auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg; + auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg; + if (!visitor.SetVRegPair(method, + static_cast<uint16_t>(slot_), + static_cast<uint64_t>(val_.j), + lo_type, + high_type)) { + return ERR(OPAQUE_FRAME); + } + break; + } + default: { + LOG(FATAL) << "unexpected register type " << type_; + UNREACHABLE(); + } + } + return OK; + } + + private: + art::Primitive::Type type_; + jvalue val_; +}; + +jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue val) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + SetLocalVariableClosure c(self, depth, slot, type, val); + if (!target->RequestSynchronousCheckpoint(&c)) { + return ERR(THREAD_NOT_ALIVE); + } else { + return c.GetResult(); + } +} + +class GetLocalInstanceClosure : public art::Closure { + public: + GetLocalInstanceClosure(art::Thread* caller, jint depth, jobject* val) + : result_(ERR(INTERNAL)), + caller_(caller), + depth_(depth), + val_(val) {} + + void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current()); + std::unique_ptr<art::Context> context(art::Context::Create()); + FindFrameAtDepthVisitor visitor(self, context.get(), depth_); + visitor.WalkStack(); + if (!visitor.FoundFrame()) { + // Must have been a bad depth. + result_ = ERR(NO_MORE_FRAMES); + return; + } + art::ArtMethod* method = visitor.GetMethod(); + if (!visitor.IsShadowFrame() && !method->IsNative() && !method->IsProxyMethod()) { + // TODO We really should support get/set for non-shadow frames. + result_ = ERR(OPAQUE_FRAME); + return; + } + result_ = OK; + art::ObjPtr<art::mirror::Object> obj = visitor.GetThisObject(); + *val_ = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj); + } + + jvmtiError GetResult() const { + return result_; + } + + private: + jvmtiError result_; + art::Thread* caller_; + jint depth_; + jobject* val_; +}; + +jvmtiError MethodUtil::GetLocalInstance(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint depth, + jobject* data) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + GetLocalInstanceClosure c(self, depth, data); + if (!target->RequestSynchronousCheckpoint(&c)) { + return ERR(THREAD_NOT_ALIVE); + } else { + return c.GetResult(); + } +} + +#define FOR_JVMTI_JVALUE_TYPES(fn) \ + fn(jint, art::Primitive::kPrimInt, i) \ + fn(jlong, art::Primitive::kPrimLong, j) \ + fn(jfloat, art::Primitive::kPrimFloat, f) \ + fn(jdouble, art::Primitive::kPrimDouble, d) \ + fn(jobject, art::Primitive::kPrimNot, l) + +namespace impl { + +template<typename T> void WriteJvalue(T, jvalue*); +template<typename T> void ReadJvalue(jvalue, T*); +template<typename T> art::Primitive::Type GetJNIType(); + +#define JNI_TYPE_CHAR(type, prim, id) \ +template<> art::Primitive::Type GetJNIType<type>() { \ + return prim; \ +} + +FOR_JVMTI_JVALUE_TYPES(JNI_TYPE_CHAR); + +#undef JNI_TYPE_CHAR + +#define RW_JVALUE(type, prim, id) \ + template<> void ReadJvalue<type>(jvalue in, type* out) { \ + *out = in.id; \ + } \ + template<> void WriteJvalue<type>(type in, jvalue* out) { \ + out->id = in; \ + } + +FOR_JVMTI_JVALUE_TYPES(RW_JVALUE); + +#undef RW_JVALUE + +} // namespace impl + +template<typename T> +jvmtiError MethodUtil::SetLocalVariable(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + T data) { + jvalue v = {.j = 0}; + art::Primitive::Type type = impl::GetJNIType<T>(); + impl::WriteJvalue(data, &v); + return SetLocalVariableGeneric(env, thread, depth, slot, type, v); +} + +template<typename T> +jvmtiError MethodUtil::GetLocalVariable(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + T* data) { + if (data == nullptr) { + return ERR(NULL_POINTER); + } + jvalue v = {.j = 0}; + art::Primitive::Type type = impl::GetJNIType<T>(); + jvmtiError err = GetLocalVariableGeneric(env, thread, depth, slot, type, &v); + if (err != OK) { + return err; + } else { + impl::ReadJvalue(v, data); + return OK; + } +} + +#define GET_SET_LV(type, prim, id) \ + template jvmtiError MethodUtil::GetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type*); \ + template jvmtiError MethodUtil::SetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type); + +FOR_JVMTI_JVALUE_TYPES(GET_SET_LV); + +#undef GET_SET_LV + +#undef FOR_JVMTI_JVALUE_TYPES + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h index d95a81b63b..aabaedb932 100644 --- a/runtime/openjdkjvmti/ti_method.h +++ b/runtime/openjdkjvmti/ti_method.h @@ -34,6 +34,7 @@ #include "jni.h" #include "jvmti.h" +#include "primitive.h" namespace openjdkjvmti { @@ -80,6 +81,32 @@ class MethodUtil { static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr); static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr); static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr); + static jvmtiError GetLocalVariableTable(jvmtiEnv* env, + jmethodID method, + jint* entry_count_ptr, + jvmtiLocalVariableEntry** table_ptr); + + template<typename T> + static jvmtiError SetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T data); + + template<typename T> + static jvmtiError GetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T* data); + + static jvmtiError GetLocalInstance(jvmtiEnv* env, jthread thread, jint depth, jobject* data); + + private: + static jvmtiError SetLocalVariableGeneric(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue value); + static jvmtiError GetLocalVariableGeneric(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue* value); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 9acea2a288..6fa73f8a8c 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -159,26 +159,13 @@ jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* return ERR(NONE); } -static art::Thread* GetNativeThreadLocked(jthread thread, - const art::ScopedObjectAccessAlreadyRunnable& soa) - REQUIRES_SHARED(art::Locks::mutator_lock_) - REQUIRES(art::Locks::thread_list_lock_) { - if (thread == nullptr) { - return art::Thread::Current(); - } - - return art::Thread::FromManagedThread(soa, thread); -} - // Get the native thread. The spec says a null object denotes the current thread. -static art::Thread* GetNativeThread(jthread thread, - const art::ScopedObjectAccessAlreadyRunnable& soa) - REQUIRES_SHARED(art::Locks::mutator_lock_) { +art::Thread* ThreadUtil::GetNativeThread(jthread thread, + const art::ScopedObjectAccessAlreadyRunnable& soa) { if (thread == nullptr) { return art::Thread::Current(); } - art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); return art::Thread::FromManagedThread(soa, thread); } @@ -190,18 +177,20 @@ jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadI return JVMTI_ERROR_WRONG_PHASE; } - art::ScopedObjectAccess soa(art::Thread::Current()); + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); - art::Thread* self = GetNativeThread(thread, soa); - if (self == nullptr && thread == nullptr) { + art::Thread* target = GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { return ERR(INVALID_THREAD); } JvmtiUniquePtr<char[]> name_uptr; - if (self != nullptr) { + if (target != nullptr) { // Have a native thread object, this thread is alive. std::string name; - self->GetThreadName(name); + target->GetThreadName(name); jvmtiError name_result; name_uptr = CopyString(env, name.c_str(), &name_result); if (name_uptr == nullptr) { @@ -209,11 +198,11 @@ jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadI } info_ptr->name = name_uptr.get(); - info_ptr->priority = self->GetNativePriority(); + info_ptr->priority = target->GetNativePriority(); - info_ptr->is_daemon = self->IsDaemon(); + info_ptr->is_daemon = target->IsDaemon(); - art::ObjPtr<art::mirror::Object> peer = self->GetPeerFromOtherThread(); + art::ObjPtr<art::mirror::Object> peer = target->GetPeerFromOtherThread(); // ThreadGroup. if (peer != nullptr) { @@ -310,9 +299,8 @@ struct InternalThreadState { static InternalThreadState GetNativeThreadState(jthread thread, const art::ScopedObjectAccessAlreadyRunnable& soa) REQUIRES_SHARED(art::Locks::mutator_lock_) - REQUIRES(art::Locks::user_code_suspension_lock_) { + REQUIRES(art::Locks::thread_list_lock_, art::Locks::user_code_suspension_lock_) { art::Thread* self = nullptr; - art::MutexLock tll_mu(soa.Self(), *art::Locks::thread_list_lock_); if (thread == nullptr) { self = art::Thread::Current(); } else { @@ -456,43 +444,46 @@ jvmtiError ThreadUtil::GetThreadState(jvmtiEnv* env ATTRIBUTE_UNUSED, } } art::ScopedObjectAccess soa(self); + art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_); state = GetNativeThreadState(thread, soa); - break; - } while (true); - - if (state.art_state == art::ThreadState::kStarting) { - if (thread == nullptr) { - // No native thread, and no Java thread? We must be starting up. Report as wrong phase. - return ERR(WRONG_PHASE); + if (state.art_state == art::ThreadState::kStarting) { + break; } + DCHECK(state.native_thread != nullptr); - art::ScopedObjectAccess soa(self); + // Translate internal thread state to JVMTI and Java state. + jint jvmti_state = GetJvmtiThreadStateFromInternal(state); + + // Java state is derived from nativeGetState. + // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly + // different mask if a thread got suspended due to user-code. However, this is for + // consistency with the Java view. + jint java_state = GetJavaStateFromInternal(state); + + *thread_state_ptr = jvmti_state | java_state; - // Need to read the Java "started" field to know whether this is starting or terminated. - art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread); - art::ObjPtr<art::mirror::Class> klass = peer->GetClass(); - art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z"); - CHECK(started_field != nullptr); - bool started = started_field->GetBoolean(peer) != 0; - constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW; - constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED | - JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED; - *thread_state_ptr = started ? kTerminatedState : kStartedState; return ERR(NONE); - } - DCHECK(state.native_thread != nullptr); + } while (true); - // Translate internal thread state to JVMTI and Java state. - jint jvmti_state = GetJvmtiThreadStateFromInternal(state); + DCHECK_EQ(state.art_state, art::ThreadState::kStarting); - // Java state is derived from nativeGetState. - // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly - // different mask if a thread got suspended due to user-code. However, this is for - // consistency with the Java view. - jint java_state = GetJavaStateFromInternal(state); + if (thread == nullptr) { + // No native thread, and no Java thread? We must be starting up. Report as wrong phase. + return ERR(WRONG_PHASE); + } - *thread_state_ptr = jvmti_state | java_state; + art::ScopedObjectAccess soa(self); + // Need to read the Java "started" field to know whether this is starting or terminated. + art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread); + art::ObjPtr<art::mirror::Class> klass = peer->GetClass(); + art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z"); + CHECK(started_field != nullptr); + bool started = started_field->GetBoolean(peer) != 0; + constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW; + constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED | + JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED; + *thread_state_ptr = started ? kTerminatedState : kStartedState; return ERR(NONE); } @@ -571,7 +562,7 @@ jvmtiError ThreadUtil::SetThreadLocalStorage(jvmtiEnv* env, jthread thread, cons art::Thread* self = art::Thread::Current(); art::ScopedObjectAccess soa(self); art::MutexLock mu(self, *art::Locks::thread_list_lock_); - art::Thread* target = GetNativeThreadLocked(thread, soa); + art::Thread* target = GetNativeThread(thread, soa); if (target == nullptr && thread == nullptr) { return ERR(INVALID_THREAD); } @@ -600,7 +591,7 @@ jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env, art::Thread* self = art::Thread::Current(); art::ScopedObjectAccess soa(self); art::MutexLock mu(self, *art::Locks::thread_list_lock_); - art::Thread* target = GetNativeThreadLocked(thread, soa); + art::Thread* target = GetNativeThread(thread, soa); if (target == nullptr && thread == nullptr) { return ERR(INVALID_THREAD); } @@ -700,8 +691,7 @@ jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env, } jvmtiError ThreadUtil::SuspendOther(art::Thread* self, - jthread target_jthread, - const art::Thread* target) { + jthread target_jthread) { // Loop since we need to bail out and try again if we would end up getting suspended while holding // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we // release the lock, wait to get resumed and try again. @@ -714,33 +704,43 @@ jvmtiError ThreadUtil::SuspendOther(art::Thread* self, SuspendCheck(self); art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_); { - art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_); + art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_); // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by // a user-code suspension. We retry and do another SuspendCheck to clear this. if (self->GetUserCodeSuspendCount() != 0) { continue; - } else if (target->GetUserCodeSuspendCount() != 0) { - return ERR(THREAD_SUSPENDED); } + // We are not going to be suspended by user code from now on. } - bool timeout = true; - while (timeout) { + { + art::ScopedObjectAccess soa(self); + art::MutexLock thread_list_mu(self, *art::Locks::thread_list_lock_); + art::Thread* target = GetNativeThread(target_jthread, soa); art::ThreadState state = target->GetState(); if (state == art::ThreadState::kTerminated || state == art::ThreadState::kStarting) { return ERR(THREAD_NOT_ALIVE); - } - art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer( - target_jthread, - /* request_suspension */ true, - art::SuspendReason::kForUserCode, - &timeout); - if (ret_target == nullptr && !timeout) { - // TODO It would be good to get more information about why exactly the thread failed to - // suspend. - return ERR(INTERNAL); + } else { + art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_); + if (target->GetUserCodeSuspendCount() != 0) { + return ERR(THREAD_SUSPENDED); + } } } - return OK; + bool timeout = true; + art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer( + target_jthread, + /* request_suspension */ true, + art::SuspendReason::kForUserCode, + &timeout); + if (ret_target == nullptr && !timeout) { + // TODO It would be good to get more information about why exactly the thread failed to + // suspend. + return ERR(INTERNAL); + } else if (!timeout) { + // we didn't time out and got a result. + return OK; + } + // We timed out. Just go around and try again. } while (true); UNREACHABLE(); } @@ -769,18 +769,21 @@ jvmtiError ThreadUtil::SuspendSelf(art::Thread* self) { jvmtiError ThreadUtil::SuspendThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) { art::Thread* self = art::Thread::Current(); - art::Thread* target; + bool target_is_self = false; { art::ScopedObjectAccess soa(self); - target = GetNativeThread(thread, soa); - } - if (target == nullptr) { - return ERR(INVALID_THREAD); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + art::Thread* target = GetNativeThread(thread, soa); + if (target == nullptr) { + return ERR(INVALID_THREAD); + } else if (target == self) { + target_is_self = true; + } } - if (target == self) { + if (target_is_self) { return SuspendSelf(self); } else { - return SuspendOther(self, thread, target); + return SuspendOther(self, thread); } } @@ -791,41 +794,56 @@ jvmtiError ThreadUtil::ResumeThread(jvmtiEnv* env ATTRIBUTE_UNUSED, } art::Thread* self = art::Thread::Current(); art::Thread* target; - { - // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't - // have the 'suspend_lock' locked here. - art::ScopedObjectAccess soa(self); - target = GetNativeThread(thread, soa); - } - if (target == nullptr) { - return ERR(INVALID_THREAD); - } else if (target == self) { - // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so we - // can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs about - // current state since it's all concurrent. - return ERR(THREAD_NOT_SUSPENDED); - } - // Now that we know we aren't getting suspended ourself (since we have a mutator lock) we lock the - // suspend_lock to start suspending. - art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_); - { - // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really cannot - // tell why resume failed. - art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_); - if (target->GetUserCodeSuspendCount() == 0) { - return ERR(THREAD_NOT_SUSPENDED); + // Retry until we know we won't get suspended by user code while resuming something. + do { + SuspendCheck(self); + art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_); + { + art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_); + // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by + // a user-code suspension. We retry and do another SuspendCheck to clear this. + if (self->GetUserCodeSuspendCount() != 0) { + continue; + } } - } - if (target->GetState() == art::ThreadState::kTerminated) { - return ERR(THREAD_NOT_ALIVE); - } - DCHECK(target != self); - if (!art::Runtime::Current()->GetThreadList()->Resume(target, art::SuspendReason::kForUserCode)) { - // TODO Give a better error. - // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure. - return ERR(INTERNAL); - } - return OK; + // From now on we know we cannot get suspended by user-code. + { + // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't + // have the 'suspend_lock' locked here. + art::ScopedObjectAccess soa(self); + art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_); + target = GetNativeThread(thread, soa); + if (target == nullptr) { + return ERR(INVALID_THREAD); + } else if (target == self) { + // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so + // we can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs + // about current state since it's all concurrent. + return ERR(THREAD_NOT_SUSPENDED); + } else if (target->GetState() == art::ThreadState::kTerminated) { + return ERR(THREAD_NOT_ALIVE); + } + // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really + // cannot tell why resume failed. + { + art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_); + if (target->GetUserCodeSuspendCount() == 0) { + return ERR(THREAD_NOT_SUSPENDED); + } + } + } + // It is okay that we don't have a thread_list_lock here since we know that the thread cannot + // die since it is currently held suspended by a SuspendReason::kForUserCode suspend. + DCHECK(target != self); + if (!art::Runtime::Current()->GetThreadList()->Resume(target, + art::SuspendReason::kForUserCode)) { + // TODO Give a better error. + // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure. + return ERR(INTERNAL); + } else { + return OK; + } + } while (true); } // Suspends all the threads in the list at the same time. Getting this behavior is a little tricky @@ -851,6 +869,7 @@ jvmtiError ThreadUtil::SuspendThreadList(jvmtiEnv* env, for (jint i = 0; i < request_count; i++) { { art::ScopedObjectAccess soa(self); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); if (threads[i] == nullptr || GetNativeThread(threads[i], soa) == self) { current_thread_indexes.push_back(i); continue; diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h index 0f7e8379fd..bf566380c0 100644 --- a/runtime/openjdkjvmti/ti_thread.h +++ b/runtime/openjdkjvmti/ti_thread.h @@ -35,10 +35,12 @@ #include "jni.h" #include "jvmti.h" +#include "base/macros.h" #include "base/mutex.h" namespace art { class ArtField; +class ScopedObjectAccessAlreadyRunnable; class Thread; } // namespace art @@ -86,6 +88,11 @@ class ThreadUtil { const jthread* threads, jvmtiError* results); + static art::Thread* GetNativeThread(jthread thread, + const art::ScopedObjectAccessAlreadyRunnable& soa) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(art::Locks::thread_list_lock_); + private: // We need to make sure only one thread tries to suspend threads at a time so we can get the // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a @@ -98,9 +105,7 @@ class ThreadUtil { // cause the thread to wake up if the thread is suspended for the debugger or gc or something. static jvmtiError SuspendSelf(art::Thread* self) REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_); - static jvmtiError SuspendOther(art::Thread* self, - jthread target_jthread, - const art::Thread* target) + static jvmtiError SuspendOther(art::Thread* self, jthread target_jthread) REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_); static art::ArtField* context_class_loader_; diff --git a/runtime/reflection.cc b/runtime/reflection.cc index 6f1d15c767..f28f0cabe2 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -238,8 +238,7 @@ class ArgArray { // TODO: The method's parameter's type must have been previously resolved, yet // we've seen cases where it's not b/34440020. ObjPtr<mirror::Class> dst_class( - m->GetClassFromTypeIndex(classes->GetTypeItem(args_offset).type_idx_, - true /* resolve */)); + m->ResolveClassFromTypeIndex(classes->GetTypeItem(args_offset).type_idx_)); if (dst_class.Ptr() == nullptr) { CHECK(self->IsExceptionPending()); return false; @@ -378,7 +377,7 @@ static void CheckMethodArguments(JavaVMExt* vm, ArtMethod* m, uint32_t* args) Thread* const self = Thread::Current(); for (uint32_t i = 0; i < num_params; i++) { dex::TypeIndex type_idx = params->GetTypeItem(i).type_idx_; - ObjPtr<mirror::Class> param_type(m->GetClassFromTypeIndex(type_idx, true /* resolve */)); + ObjPtr<mirror::Class> param_type(m->ResolveClassFromTypeIndex(type_idx)); if (param_type == nullptr) { CHECK(self->IsExceptionPending()); LOG(ERROR) << "Internal error: unresolvable type for argument type in JNI invoke: " diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 27124190c7..a8ccf89cb2 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -38,6 +38,7 @@ #include "android-base/strings.h" +#include "aot_class_linker.h" #include "arch/arm/quick_method_frame_info_arm.h" #include "arch/arm/registers_arm.h" #include "arch/arm64/quick_method_frame_info_arm64.h" @@ -63,7 +64,6 @@ #include "base/stl_util.h" #include "base/systrace.h" #include "base/unix_file/fd_file.h" -#include "cha.h" #include "class_linker-inl.h" #include "compiler_callbacks.h" #include "debugger.h" @@ -238,7 +238,7 @@ Runtime::Runtime() system_thread_group_(nullptr), system_class_loader_(nullptr), dump_gc_performance_on_shutdown_(false), - preinitialization_transaction_(nullptr), + preinitialization_transactions_(), verify_(verifier::VerifyMode::kNone), allow_dex_file_fallback_(true), target_sdk_version_(0), @@ -259,8 +259,7 @@ Runtime::Runtime() pruned_dalvik_cache_(false), // Initially assume we perceive jank in case the process state is never updated. process_state_(kProcessStateJankPerceptible), - zygote_no_threads_(false), - cha_(nullptr) { + zygote_no_threads_(false) { static_assert(Runtime::kCalleeSaveSize == static_cast<uint32_t>(CalleeSaveType::kLastCalleeSaveType), "Unexpected size"); @@ -382,7 +381,6 @@ Runtime::~Runtime() { delete monitor_list_; delete monitor_pool_; delete class_linker_; - delete cha_; delete heap_; delete intern_table_; delete oat_file_manager_; @@ -1286,8 +1284,11 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { GetHeap()->EnableObjectValidation(); CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U); - class_linker_ = new ClassLinker(intern_table_); - cha_ = new ClassHierarchyAnalysis; + if (UNLIKELY(IsAotCompiler())) { + class_linker_ = new AotClassLinker(intern_table_); + } else { + class_linker_ = new ClassLinker(intern_table_); + } if (GetHeap()->HasBootImageSpace()) { bool result = class_linker_->InitFromBootImage(&error_msg); if (!result) { @@ -1833,8 +1834,8 @@ void Runtime::VisitConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) { } void Runtime::VisitTransactionRoots(RootVisitor* visitor) { - if (preinitialization_transaction_ != nullptr) { - preinitialization_transaction_->VisitRoots(visitor); + for (auto& transaction : preinitialization_transactions_) { + transaction->VisitRoots(visitor); } } @@ -2066,28 +2067,32 @@ void Runtime::RegisterAppInfo(const std::vector<std::string>& code_paths, } // Transaction support. +bool Runtime::IsActiveTransaction() const { + return !preinitialization_transactions_.empty() && !GetTransaction()->IsRollingBack(); +} + void Runtime::EnterTransactionMode() { DCHECK(IsAotCompiler()); DCHECK(!IsActiveTransaction()); - preinitialization_transaction_ = std::make_unique<Transaction>(); + preinitialization_transactions_.push_back(std::make_unique<Transaction>()); } -void Runtime::EnterTransactionMode(mirror::Class* root) { +void Runtime::EnterTransactionMode(bool strict, mirror::Class* root) { DCHECK(IsAotCompiler()); - preinitialization_transaction_ = std::make_unique<Transaction>(root); + preinitialization_transactions_.push_back(std::make_unique<Transaction>(strict, root)); } void Runtime::ExitTransactionMode() { DCHECK(IsAotCompiler()); - preinitialization_transaction_ = nullptr; + DCHECK(IsActiveTransaction()); + preinitialization_transactions_.pop_back(); } void Runtime::RollbackAndExitTransactionMode() { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - std::unique_ptr<Transaction> rollback_transaction_= std::move(preinitialization_transaction_); - ExitTransactionMode(); - rollback_transaction_->Rollback(); + preinitialization_transactions_.back()->Rollback(); + preinitialization_transactions_.pop_back(); } bool Runtime::IsTransactionAborted() const { @@ -2095,67 +2100,86 @@ bool Runtime::IsTransactionAborted() const { return false; } else { DCHECK(IsAotCompiler()); - return preinitialization_transaction_->IsAborted(); + return GetTransaction()->IsAborted(); } } +void Runtime::RollbackAllTransactions() { + // If transaction is aborted, all transactions will be kept in the list. + // Rollback and exit all of them. + while (IsActiveTransaction()) { + RollbackAndExitTransactionMode(); + } +} + +bool Runtime::IsActiveStrictTransactionMode() const { + return IsActiveTransaction() && GetTransaction()->IsStrict(); +} + +const std::unique_ptr<Transaction>& Runtime::GetTransaction() const { + DCHECK(!preinitialization_transactions_.empty()); + return preinitialization_transactions_.back(); +} + void Runtime::AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); // Throwing an exception may cause its class initialization. If we mark the transaction // aborted before that, we may warn with a false alarm. Throwing the exception before // marking the transaction aborted avoids that. - preinitialization_transaction_->ThrowAbortError(self, &abort_message); - preinitialization_transaction_->Abort(abort_message); + // But now the transaction can be nested, and abort the transaction will relax the constraints + // for constructing stack trace. + GetTransaction()->Abort(abort_message); + GetTransaction()->ThrowAbortError(self, &abort_message); } void Runtime::ThrowTransactionAbortError(Thread* self) { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); // Passing nullptr means we rethrow an exception with the earlier transaction abort message. - preinitialization_transaction_->ThrowAbortError(self, nullptr); + GetTransaction()->ThrowAbortError(self, nullptr); } void Runtime::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, uint8_t value, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile); + GetTransaction()->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile); } void Runtime::RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset, int8_t value, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteFieldByte(obj, field_offset, value, is_volatile); + GetTransaction()->RecordWriteFieldByte(obj, field_offset, value, is_volatile); } void Runtime::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset, uint16_t value, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteFieldChar(obj, field_offset, value, is_volatile); + GetTransaction()->RecordWriteFieldChar(obj, field_offset, value, is_volatile); } void Runtime::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset, int16_t value, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteFieldShort(obj, field_offset, value, is_volatile); + GetTransaction()->RecordWriteFieldShort(obj, field_offset, value, is_volatile); } void Runtime::RecordWriteField32(mirror::Object* obj, MemberOffset field_offset, uint32_t value, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteField32(obj, field_offset, value, is_volatile); + GetTransaction()->RecordWriteField32(obj, field_offset, value, is_volatile); } void Runtime::RecordWriteField64(mirror::Object* obj, MemberOffset field_offset, uint64_t value, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteField64(obj, field_offset, value, is_volatile); + GetTransaction()->RecordWriteField64(obj, field_offset, value, is_volatile); } void Runtime::RecordWriteFieldReference(mirror::Object* obj, @@ -2164,7 +2188,7 @@ void Runtime::RecordWriteFieldReference(mirror::Object* obj, bool is_volatile) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteFieldReference(obj, + GetTransaction()->RecordWriteFieldReference(obj, field_offset, value.Ptr(), is_volatile); @@ -2173,38 +2197,38 @@ void Runtime::RecordWriteFieldReference(mirror::Object* obj, void Runtime::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWriteArray(array, index, value); + GetTransaction()->RecordWriteArray(array, index, value); } void Runtime::RecordStrongStringInsertion(ObjPtr<mirror::String> s) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordStrongStringInsertion(s); + GetTransaction()->RecordStrongStringInsertion(s); } void Runtime::RecordWeakStringInsertion(ObjPtr<mirror::String> s) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWeakStringInsertion(s); + GetTransaction()->RecordWeakStringInsertion(s); } void Runtime::RecordStrongStringRemoval(ObjPtr<mirror::String> s) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordStrongStringRemoval(s); + GetTransaction()->RecordStrongStringRemoval(s); } void Runtime::RecordWeakStringRemoval(ObjPtr<mirror::String> s) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordWeakStringRemoval(s); + GetTransaction()->RecordWeakStringRemoval(s); } void Runtime::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx) const { DCHECK(IsAotCompiler()); DCHECK(IsActiveTransaction()); - preinitialization_transaction_->RecordResolveString(dex_cache, string_idx); + GetTransaction()->RecordResolveString(dex_cache, string_idx); } void Runtime::SetFaultMessage(const std::string& message) { @@ -2422,5 +2446,4 @@ void Runtime::DeoptimizeBootImage() { GetClassLinker()->VisitClasses(&visitor); } } - } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index 9424596c8a..0c1344e11a 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -73,7 +73,6 @@ namespace verifier { class ArenaPool; class ArtMethod; enum class CalleeSaveType: uint32_t; -class ClassHierarchyAnalysis; class ClassLinker; class CompilerCallbacks; class DexFile; @@ -455,16 +454,17 @@ class Runtime { const std::string& profile_output_filename); // Transaction support. - bool IsActiveTransaction() const { - return preinitialization_transaction_ != nullptr; - } + bool IsActiveTransaction() const; void EnterTransactionMode(); - void EnterTransactionMode(mirror::Class* root); + void EnterTransactionMode(bool strict, mirror::Class* root); void ExitTransactionMode(); + void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_); // Transaction rollback and exit transaction are always done together, it's convenience to // do them in one function. void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_); bool IsTransactionAborted() const; + const std::unique_ptr<Transaction>& GetTransaction() const; + bool IsActiveStrictTransactionMode() const; void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) REQUIRES_SHARED(Locks::mutator_lock_); @@ -650,10 +650,6 @@ class Runtime { void AddSystemWeakHolder(gc::AbstractSystemWeakHolder* holder); void RemoveSystemWeakHolder(gc::AbstractSystemWeakHolder* holder); - ClassHierarchyAnalysis* GetClassHierarchyAnalysis() { - return cha_; - } - void AttachAgent(const std::string& agent_arg); const std::list<ti::Agent>& GetAgents() const { @@ -846,8 +842,11 @@ class Runtime { // If true, then we dump the GC cumulative timings on shutdown. bool dump_gc_performance_on_shutdown_; - // Transaction used for pre-initializing classes at compilation time. - std::unique_ptr<Transaction> preinitialization_transaction_; + // Transactions used for pre-initializing classes at compilation time. + // Support nested transactions, maintain a list containing all transactions. Transactions are + // handled under a stack discipline. Because GC needs to go over all transactions, we choose list + // as substantial data structure instead of stack. + std::list<std::unique_ptr<Transaction>> preinitialization_transactions_; // If kNone, verification is disabled. kEnable by default. verifier::VerifyMode verify_; @@ -944,8 +943,6 @@ class Runtime { // Generic system-weak holders. std::vector<gc::AbstractSystemWeakHolder*> system_weak_holders_; - ClassHierarchyAnalysis* cha_; - std::unique_ptr<RuntimeCallbacks> callbacks_; std::atomic<uint32_t> deoptimization_counts_[ diff --git a/runtime/transaction.cc b/runtime/transaction.cc index 9e62aa6b55..50deb1f913 100644 --- a/runtime/transaction.cc +++ b/runtime/transaction.cc @@ -34,11 +34,15 @@ namespace art { static constexpr bool kEnableTransactionStats = false; Transaction::Transaction() - : log_lock_("transaction log lock", kTransactionLogLock), aborted_(false) { + : log_lock_("transaction log lock", kTransactionLogLock), + aborted_(false), + rolling_back_(false), + strict_(false) { CHECK(Runtime::Current()->IsAotCompiler()); } -Transaction::Transaction(mirror::Class* root) : Transaction() { +Transaction::Transaction(bool strict, mirror::Class* root) : Transaction() { + strict_ = strict; root_ = root; } @@ -101,6 +105,15 @@ bool Transaction::IsAborted() { return aborted_; } +bool Transaction::IsRollingBack() { + return rolling_back_; +} + +bool Transaction::IsStrict() { + MutexLock mu(Thread::Current(), log_lock_); + return strict_; +} + const std::string& Transaction::GetAbortMessage() { MutexLock mu(Thread::Current(), log_lock_); return abort_message_; @@ -226,15 +239,17 @@ void Transaction::LogInternedString(InternStringLog&& log) { } void Transaction::Rollback() { - CHECK(!Runtime::Current()->IsActiveTransaction()); Thread* self = Thread::Current(); self->AssertNoPendingException(); MutexLock mu1(self, *Locks::intern_table_lock_); MutexLock mu2(self, log_lock_); + rolling_back_ = true; + CHECK(!Runtime::Current()->IsActiveTransaction()); UndoObjectModifications(); UndoArrayModifications(); UndoInternStringTableModifications(); UndoResolveStringModifications(); + rolling_back_ = false; } void Transaction::UndoObjectModifications() { @@ -416,7 +431,7 @@ void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj, const FieldValue& field_value) const { // TODO We may want to abort a transaction while still being in transaction mode. In this case, // we'd need to disable the check. - constexpr bool kCheckTransaction = true; + constexpr bool kCheckTransaction = false; switch (field_value.kind) { case kBoolean: if (UNLIKELY(field_value.is_volatile)) { @@ -595,30 +610,39 @@ void Transaction::ArrayLog::UndoArrayWrite(mirror::Array* array, uint64_t value) const { // TODO We may want to abort a transaction while still being in transaction mode. In this case, // we'd need to disable the check. + constexpr bool kCheckTransaction = false; switch (array_type) { case Primitive::kPrimBoolean: - array->AsBooleanArray()->SetWithoutChecks<false>(index, static_cast<uint8_t>(value)); + array->AsBooleanArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<uint8_t>(value)); break; case Primitive::kPrimByte: - array->AsByteArray()->SetWithoutChecks<false>(index, static_cast<int8_t>(value)); + array->AsByteArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<int8_t>(value)); break; case Primitive::kPrimChar: - array->AsCharArray()->SetWithoutChecks<false>(index, static_cast<uint16_t>(value)); + array->AsCharArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<uint16_t>(value)); break; case Primitive::kPrimShort: - array->AsShortArray()->SetWithoutChecks<false>(index, static_cast<int16_t>(value)); + array->AsShortArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<int16_t>(value)); break; case Primitive::kPrimInt: - array->AsIntArray()->SetWithoutChecks<false>(index, static_cast<int32_t>(value)); + array->AsIntArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<int32_t>(value)); break; case Primitive::kPrimFloat: - array->AsFloatArray()->SetWithoutChecks<false>(index, static_cast<float>(value)); + array->AsFloatArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<float>(value)); break; case Primitive::kPrimLong: - array->AsLongArray()->SetWithoutChecks<false>(index, static_cast<int64_t>(value)); + array->AsLongArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<int64_t>(value)); break; case Primitive::kPrimDouble: - array->AsDoubleArray()->SetWithoutChecks<false>(index, static_cast<double>(value)); + array->AsDoubleArray()->SetWithoutChecks<false, kCheckTransaction>( + index, static_cast<double>(value)); break; case Primitive::kPrimNot: LOG(FATAL) << "ObjectArray should be treated as Object"; diff --git a/runtime/transaction.h b/runtime/transaction.h index 22518f6c7e..64349de2e9 100644 --- a/runtime/transaction.h +++ b/runtime/transaction.h @@ -45,7 +45,7 @@ class Transaction FINAL { static constexpr const char* kAbortExceptionSignature = "Ldalvik/system/TransactionAbortError;"; Transaction(); - explicit Transaction(mirror::Class* root); + explicit Transaction(bool strict, mirror::Class* root); ~Transaction(); void Abort(const std::string& abort_message) @@ -56,6 +56,15 @@ class Transaction FINAL { REQUIRES_SHARED(Locks::mutator_lock_); bool IsAborted() REQUIRES(!log_lock_); + // If the transaction is rollbacking. Transactions will set this flag when they start rollbacking, + // because the nested transaction should be disabled when rollbacking to restore the memory. + bool IsRollingBack(); + + // If the transaction is in strict mode, then all access of static fields will be constrained, + // one class's clinit will not be allowed to read or modify another class's static fields, unless + // the transaction is aborted. + bool IsStrict() REQUIRES(!log_lock_); + // Record object field changes. void RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, @@ -289,6 +298,8 @@ class Transaction FINAL { std::list<InternStringLog> intern_string_logs_ GUARDED_BY(log_lock_); std::list<ResolveStringLog> resolve_string_logs_ GUARDED_BY(log_lock_); bool aborted_ GUARDED_BY(log_lock_); + bool rolling_back_; // Single thread, no race. + bool strict_ GUARDED_BY(log_lock_); std::string abort_message_ GUARDED_BY(log_lock_); mirror::Class* root_ GUARDED_BY(log_lock_); diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 6149f0d94e..0c460db7da 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -2899,10 +2899,12 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { ArtMethod* called_method = VerifyInvocationArgs(inst, type, is_range); const RegType* return_type = nullptr; if (called_method != nullptr) { - mirror::Class* return_type_class = called_method->GetReturnType(can_load_classes_); + ObjPtr<mirror::Class> return_type_class = can_load_classes_ + ? called_method->ResolveReturnType() + : called_method->LookupResolvedReturnType(); if (return_type_class != nullptr) { return_type = &FromClass(called_method->GetReturnTypeDescriptor(), - return_type_class, + return_type_class.Ptr(), return_type_class->CannotBeAssignedFromOtherTypes()); } else { DCHECK(!can_load_classes_ || self_->IsExceptionPending()); @@ -2942,10 +2944,12 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { } else { is_constructor = called_method->IsConstructor(); return_type_descriptor = called_method->GetReturnTypeDescriptor(); - mirror::Class* return_type_class = called_method->GetReturnType(can_load_classes_); + ObjPtr<mirror::Class> return_type_class = can_load_classes_ + ? called_method->ResolveReturnType() + : called_method->LookupResolvedReturnType(); if (return_type_class != nullptr) { return_type = &FromClass(return_type_descriptor, - return_type_class, + return_type_class.Ptr(), return_type_class->CannotBeAssignedFromOtherTypes()); } else { DCHECK(!can_load_classes_ || self_->IsExceptionPending()); @@ -5261,10 +5265,12 @@ InstructionFlags* MethodVerifier::CurrentInsnFlags() { const RegType& MethodVerifier::GetMethodReturnType() { if (return_type_ == nullptr) { if (mirror_method_ != nullptr) { - mirror::Class* return_type_class = mirror_method_->GetReturnType(can_load_classes_); + ObjPtr<mirror::Class> return_type_class = can_load_classes_ + ? mirror_method_->ResolveReturnType() + : mirror_method_->LookupResolvedReturnType(); if (return_type_class != nullptr) { return_type_ = &FromClass(mirror_method_->GetReturnTypeDescriptor(), - return_type_class, + return_type_class.Ptr(), return_type_class->CannotBeAssignedFromOtherTypes()); } else { DCHECK(!can_load_classes_ || self_->IsExceptionPending()); diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc index 112eec847d..470b0b3d58 100644 --- a/runtime/verifier/verifier_deps.cc +++ b/runtime/verifier/verifier_deps.cc @@ -33,7 +33,8 @@ namespace art { namespace verifier { -VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files) { +VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files, bool output_only) + : output_only_(output_only) { for (const DexFile* dex_file : dex_files) { DCHECK(GetDexFileDeps(*dex_file) == nullptr); std::unique_ptr<DexFileDeps> deps(new DexFileDeps()); @@ -41,6 +42,9 @@ VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files) { } } +VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files) + : VerifierDeps(dex_files, /*output_only*/ true) {} + void VerifierDeps::MergeWith(const VerifierDeps& other, const std::vector<const DexFile*>& dex_files) { DCHECK(dex_deps_.size() == other.dex_deps_.size()); @@ -694,7 +698,7 @@ void VerifierDeps::Encode(const std::vector<const DexFile*>& dex_files, VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files, ArrayRef<const uint8_t> data) - : VerifierDeps(dex_files) { + : VerifierDeps(dex_files, /*output_only*/ false) { if (data.empty()) { // Return eagerly, as the first thing we expect from VerifierDeps data is // the number of created strings, even if there is no dependency. diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h index b883a9e642..2d452f6d6b 100644 --- a/runtime/verifier/verifier_deps.h +++ b/runtime/verifier/verifier_deps.h @@ -121,6 +121,10 @@ class VerifierDeps { return GetDexFileDeps(dex_file)->unverified_classes_; } + bool OutputOnly() const { + return output_only_; + } + private: static constexpr uint16_t kUnresolvedMarker = static_cast<uint16_t>(-1); @@ -198,6 +202,8 @@ class VerifierDeps { bool Equals(const DexFileDeps& rhs) const; }; + VerifierDeps(const std::vector<const DexFile*>& dex_files, bool output_only); + // Finds the DexFileDep instance associated with `dex_file`, or nullptr if // `dex_file` is not reported as being compiled. DexFileDeps* GetDexFileDeps(const DexFile& dex_file); @@ -321,6 +327,9 @@ class VerifierDeps { // Map from DexFiles into dependencies collected from verification of their methods. std::map<const DexFile*, std::unique_ptr<DexFileDeps>> dex_deps_; + // Output only signifies if we are using the verifier deps to verify or just to generate them. + const bool output_only_; + friend class VerifierDepsTest; ART_FRIEND_TEST(VerifierDepsTest, StringToId); ART_FRIEND_TEST(VerifierDepsTest, EncodeDecode); diff --git a/runtime/simulator/Android.bp b/simulator/Android.bp index 03e3f1562a..a39928985a 100644 --- a/runtime/simulator/Android.bp +++ b/simulator/Android.bp @@ -14,6 +14,12 @@ // limitations under the License. // +cc_library_headers { + name: "libart_simulator_headers", + host_supported: true, + export_include_dirs: ["include"], +} + cc_defaults { name: "libart_simulator_defaults", host_supported: true, @@ -29,8 +35,8 @@ cc_defaults { "liblog", ], cflags: ["-DVIXL_INCLUDE_SIMULATOR_AARCH64"], - export_include_dirs: ["."], - include_dirs: ["art/runtime"], + + header_libs: ["libart_simulator_headers"], } art_cc_library { @@ -53,3 +59,38 @@ art_cc_library { "libvixld-arm64", ], } + +cc_defaults { + name: "libart_simulator_container_defaults", + host_supported: true, + + defaults: ["art_defaults"], + srcs: [ + "code_simulator_container.cc", + ], + shared_libs: [ + "libbase", + ], + + header_libs: ["libart_simulator_headers"], + export_include_dirs: ["."], // TODO: Consider a proper separation. +} + +art_cc_library { + name: "libart-simulator-container", + defaults: ["libart_simulator_container_defaults"], + shared_libs: [ + "libart", + ], +} + +art_cc_library { + name: "libartd-simulator-container", + defaults: [ + "art_debug_defaults", + "libart_simulator_container_defaults", + ], + shared_libs: [ + "libartd", + ], +} diff --git a/runtime/simulator/code_simulator.cc b/simulator/code_simulator.cc index 1a1116050e..e653dfc4fe 100644 --- a/runtime/simulator/code_simulator.cc +++ b/simulator/code_simulator.cc @@ -14,8 +14,9 @@ * limitations under the License. */ -#include "simulator/code_simulator.h" -#include "simulator/code_simulator_arm64.h" +#include "code_simulator.h" + +#include "code_simulator_arm64.h" namespace art { diff --git a/runtime/simulator/code_simulator_arm64.cc b/simulator/code_simulator_arm64.cc index c7ad1fd8aa..939d2e287f 100644 --- a/runtime/simulator/code_simulator_arm64.cc +++ b/simulator/code_simulator_arm64.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "simulator/code_simulator_arm64.h" +#include "code_simulator_arm64.h" #include "base/logging.h" diff --git a/runtime/simulator/code_simulator_arm64.h b/simulator/code_simulator_arm64.h index 59ea34fb80..0542593eb2 100644 --- a/runtime/simulator/code_simulator_arm64.h +++ b/simulator/code_simulator_arm64.h @@ -14,11 +14,10 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_ARM64_H_ -#define ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_ARM64_H_ +#ifndef ART_SIMULATOR_CODE_SIMULATOR_ARM64_H_ +#define ART_SIMULATOR_CODE_SIMULATOR_ARM64_H_ #include "memory" -#include "simulator/code_simulator.h" // TODO(VIXL): Make VIXL compile with -Wshadow. #pragma GCC diagnostic push @@ -26,6 +25,8 @@ #include "aarch64/simulator-aarch64.h" #pragma GCC diagnostic pop +#include "code_simulator.h" + namespace art { namespace arm64 { @@ -55,4 +56,4 @@ class CodeSimulatorArm64 : public CodeSimulator { } // namespace arm64 } // namespace art -#endif // ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_ARM64_H_ +#endif // ART_SIMULATOR_CODE_SIMULATOR_ARM64_H_ diff --git a/runtime/code_simulator_container.cc b/simulator/code_simulator_container.cc index d884c58782..a5f05dc8fc 100644 --- a/runtime/code_simulator_container.cc +++ b/simulator/code_simulator_container.cc @@ -17,6 +17,8 @@ #include <dlfcn.h> #include "code_simulator_container.h" + +#include "code_simulator.h" #include "globals.h" namespace art { diff --git a/runtime/code_simulator_container.h b/simulator/code_simulator_container.h index 10178bac67..31a915e4f1 100644 --- a/runtime/code_simulator_container.h +++ b/simulator/code_simulator_container.h @@ -14,15 +14,16 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_CODE_SIMULATOR_CONTAINER_H_ -#define ART_RUNTIME_CODE_SIMULATOR_CONTAINER_H_ +#ifndef ART_SIMULATOR_CODE_SIMULATOR_CONTAINER_H_ +#define ART_SIMULATOR_CODE_SIMULATOR_CONTAINER_H_ #include "arch/instruction_set.h" #include "base/logging.h" -#include "simulator/code_simulator.h" namespace art { +class CodeSimulator; + // This container dynamically opens and closes libart-simulator. class CodeSimulatorContainer { public: @@ -52,4 +53,4 @@ class CodeSimulatorContainer { } // namespace art -#endif // ART_RUNTIME_CODE_SIMULATOR_CONTAINER_H_ +#endif // ART_SIMULATOR_CODE_SIMULATOR_CONTAINER_H_ diff --git a/runtime/simulator/code_simulator.h b/simulator/include/code_simulator.h index bd48909e41..256ab23aa4 100644 --- a/runtime/simulator/code_simulator.h +++ b/simulator/include/code_simulator.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_ -#define ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_ +#ifndef ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_ +#define ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_ #include "arch/instruction_set.h" @@ -43,4 +43,4 @@ extern "C" CodeSimulator* CreateCodeSimulator(InstructionSet target_isa); } // namespace art -#endif // ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_ +#endif // ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_ diff --git a/test/1911-get-local-var-table/expected.txt b/test/1911-get-local-var-table/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/1911-get-local-var-table/expected.txt diff --git a/test/1911-get-local-var-table/info.txt b/test/1911-get-local-var-table/info.txt new file mode 100644 index 0000000000..43955e5456 --- /dev/null +++ b/test/1911-get-local-var-table/info.txt @@ -0,0 +1 @@ +Tests getting local variable table from JVMTI diff --git a/test/1911-get-local-var-table/run b/test/1911-get-local-var-table/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1911-get-local-var-table/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/1911-get-local-var-table/src/Main.java b/test/1911-get-local-var-table/src/Main.java new file mode 100644 index 0000000000..4e0c9e4506 --- /dev/null +++ b/test/1911-get-local-var-table/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.Test1911.run(); + } +} diff --git a/test/1911-get-local-var-table/src/art/Breakpoint.java b/test/1911-get-local-var-table/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1911-get-local-var-table/src/art/Locals.java b/test/1911-get-local-var-table/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1911-get-local-var-table/src/art/Suspension.java b/test/1911-get-local-var-table/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1911-get-local-var-table/src/art/Test1911.java b/test/1911-get-local-var-table/src/art/Test1911.java new file mode 100644 index 0000000000..4dd9054bf0 --- /dev/null +++ b/test/1911-get-local-var-table/src/art/Test1911.java @@ -0,0 +1,218 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashSet; +import java.util.Set; + +public class Test1911 { + // Class/dex file containing the following class. + // + // CLASS_BYTES generated with java version 1.8.0_45: javac -g art/Target.java + // DEX_BYTES generated with dx version 1.14: dx --dex --output=./classes.dex art/Target.class + // + // package art; + // import java.util.ArrayList; + // public class Target { + // public int zzz; + // public Target(int xxx) { + // int q = xxx * 4; + // zzz = q; + // } + // public static void doNothing(Object... objs) { doNothing(objs); } + // public void doSomething(int x) { + // doNothing(this); + // int y = x + 3; + // for (int z = 0; z < y * x; z++) { + // float q = y - z; + // double i = 0.3d * q; + // doNothing(q, i); + // } + // Object o = new Object(); + // ArrayList<Integer> i = new ArrayList<>(); + // int p = 4 | x; + // long q = 3 * p; + // doNothing(p, q, o, i); + // } + // } + public static byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQARgoABAAuCQANAC8KAA0AMAcAMQY/0zMzMzMzMwoAMgAzCgA0ADUHADYKAAkALgoA" + + "NwA4CgA5ADoHADsBAAN6enoBAAFJAQAGPGluaXQ+AQAEKEkpVgEABENvZGUBAA9MaW5lTnVtYmVy" + + "VGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAMTGFydC9UYXJnZXQ7AQADeHh4AQAB" + + "cQEACWRvTm90aGluZwEAFihbTGphdmEvbGFuZy9PYmplY3Q7KVYBAARvYmpzAQATW0xqYXZhL2xh" + + "bmcvT2JqZWN0OwEAC2RvU29tZXRoaW5nAQABRgEAAWkBAAFEAQABegEAAXgBAAF5AQABbwEAEkxq" + + "YXZhL2xhbmcvT2JqZWN0OwEAFUxqYXZhL3V0aWwvQXJyYXlMaXN0OwEAAXABAAFKAQAWTG9jYWxW" + + "YXJpYWJsZVR5cGVUYWJsZQEAKkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdl" + + "cjs+OwEADVN0YWNrTWFwVGFibGUBAApTb3VyY2VGaWxlAQALVGFyZ2V0LmphdmEMABAAPAwADgAP" + + "DAAZABoBABBqYXZhL2xhbmcvT2JqZWN0BwA9DAA+AD8HAEAMAD4AQQEAE2phdmEvdXRpbC9BcnJh" + + "eUxpc3QHAEIMAD4AQwcARAwAPgBFAQAKYXJ0L1RhcmdldAEAAygpVgEAD2phdmEvbGFuZy9GbG9h" + + "dAEAB3ZhbHVlT2YBABQoRilMamF2YS9sYW5nL0Zsb2F0OwEAEGphdmEvbGFuZy9Eb3VibGUBABUo" + + "RClMamF2YS9sYW5nL0RvdWJsZTsBABFqYXZhL2xhbmcvSW50ZWdlcgEAFihJKUxqYXZhL2xhbmcv" + + "SW50ZWdlcjsBAA5qYXZhL2xhbmcvTG9uZwEAEyhKKUxqYXZhL2xhbmcvTG9uZzsAIQANAAQAAAAB" + + "AAEADgAPAAAAAwABABAAEQABABIAAABYAAIAAwAAAA4qtwABGwdoPSoctQACsQAAAAIAEwAAABIA" + + "BAAAAAUABAAGAAgABwANAAgAFAAAACAAAwAAAA4AFQAWAAAAAAAOABcADwABAAgABgAYAA8AAgCJ" + + "ABkAGgABABIAAAAvAAEAAQAAAAUquAADsQAAAAIAEwAAAAYAAQAAAAkAFAAAAAwAAQAAAAUAGwAc" + + "AAAAAQAdABEAAQASAAABWAAFAAgAAACCBL0ABFkDKlO4AAMbBmA9Az4dHBtoogAvHB1khjgEFAAF" + + "FwSNazkFBb0ABFkDFwS4AAdTWQQYBbgACFO4AAOEAwGn/9C7AARZtwABTrsACVm3AAo6BAcbgDYF" + + "BhUFaIU3Bge9AARZAxUFuAALU1kEFga4AAxTWQUtU1kGGQRTuAADsQAAAAQAEwAAADYADQAAAAsA" + + "CwAMAA8ADQAYAA4AHgAPACcAEAA+AA0ARAASAEwAEwBVABQAWgAVAGEAFgCBABcAFAAAAGYACgAe" + + "ACAAGAAeAAQAJwAXAB8AIAAFABEAMwAhAA8AAwAAAIIAFQAWAAAAAACCACIADwABAA8AcwAjAA8A" + + "AgBMADYAJAAlAAMAVQAtAB8AJgAEAFoAKAAnAA8ABQBhACEAGAAoAAYAKQAAAAwAAQBVAC0AHwAq" + + "AAQAKwAAAAoAAv0AEQEB+gAyAAEALAAAAAIALQ=="); + public static byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCQtgjEV631Ma/btYyIy2IzqHWNN+nZiwl0BQAAcAAAAHhWNBIAAAAAAAAAANQEAAAk" + + "AAAAcAAAAA0AAAAAAQAABwAAADQBAAABAAAAiAEAAAkAAACQAQAAAQAAANgBAAB8AwAA+AEAAB4D" + + "AAAmAwAAKQMAACwDAAAvAwAAMgMAADYDAAA6AwAAPgMAAEIDAABQAwAAZAMAAHcDAACMAwAAngMA" + + "ALIDAADJAwAA9QMAAAIEAAAFBAAACQQAAA0EAAAiBAAALQQAADoEAAA9BAAAQAQAAEYEAABJBAAA" + + "TAQAAFIEAABbBAAAXgQAAGMEAABmBAAAaQQAAAEAAAACAAAAAwAAAAQAAAAJAAAACgAAAAsAAAAM" + + "AAAADQAAAA4AAAAPAAAAEgAAABUAAAAFAAAABQAAAPgCAAAGAAAABgAAAAADAAAHAAAABwAAAAgD" + + "AAAIAAAACAAAABADAAASAAAACwAAAAAAAAATAAAACwAAAAgDAAAUAAAACwAAABgDAAAEAAIAIwAA" + + "AAQABQAAAAAABAAGABYAAAAEAAUAFwAAAAUAAAAeAAAABgABAB4AAAAHAAIAHgAAAAgAAwAeAAAA" + + "CQAEAAAAAAAKAAQAAAAAAAQAAAABAAAACQAAAAAAAAARAAAAAAAAAL4EAAAAAAAAAwACAAEAAABu" + + "BAAACAAAAHAQBwABANoAAgRZEAAADgABAAEAAQAAAHsEAAAEAAAAcRABAAAADgAQAAIAAgAAAIEE" + + "AABcAAAAEhkjmQwAEgpNDgkKcRABAAkA2AUPAxIIkgkFDzWYJACRCQUIgpYYCjMzMzMzM9M/iWyt" + + "AAoMEikjmQwAEgpxEAQABgAMC00LCQoSGnEgAwAQAAwLTQsJCnEQAQAJANgICAEo2yIDCQBwEAcA" + + "AwAiAgoAcBAIAAIA3gQPBNoJBAOBlhJJI5kMABIKcRAFAAQADAtNCwkKEhpxIAYAdgAMC00LCQoS" + + "Kk0DCQoSOk0CCQpxEAEACQAOAAEAAAAAAAAAAQAAAAEAAAABAAAAAgAAAAEAAAADAAAAAQAAAAwA" + + "Bjxpbml0PgABRAABRgABSQABSgACTEQAAkxGAAJMSQACTEoADExhcnQvVGFyZ2V0OwASTGphdmEv" + + "bGFuZy9Eb3VibGU7ABFMamF2YS9sYW5nL0Zsb2F0OwATTGphdmEvbGFuZy9JbnRlZ2VyOwAQTGph" + + "dmEvbGFuZy9Mb25nOwASTGphdmEvbGFuZy9PYmplY3Q7ABVMamF2YS91dGlsL0FycmF5TGlzdDsA" + + "KkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdlcjs+OwALVGFyZ2V0LmphdmEA" + + "AVYAAlZJAAJWTAATW0xqYXZhL2xhbmcvT2JqZWN0OwAJZG9Ob3RoaW5nAAtkb1NvbWV0aGluZwAB" + + "aQABbwAEb2JqcwABcAABcQAEdGhpcwAHdmFsdWVPZgABeAADeHh4AAF5AAF6AAN6enoABQEhBw48" + + "LQMAHQMtAAkBGwcOAAsBIAcOli0DBSIDAQEDCCMDSzwDBh0ChwMAGQEBFAtABQAFBloDAxoKWgQC" + + "GQsRLQMEHAM8AwYdBAEaDwAAAQIBAAEAgYAE+AMBiQGYBAIBsAQADQAAAAAAAAABAAAAAAAAAAEA" + + "AAAkAAAAcAAAAAIAAAANAAAAAAEAAAMAAAAHAAAANAEAAAQAAAABAAAAiAEAAAUAAAAJAAAAkAEA" + + "AAYAAAABAAAA2AEAAAEgAAADAAAA+AEAAAEQAAAFAAAA+AIAAAIgAAAkAAAAHgMAAAMgAAADAAAA" + + "bgQAAAAgAAABAAAAvgQAAAAQAAABAAAA1AQAAA=="); + + + // The variables of the functions in the above Target class. + public static Set<Locals.VariableDescription>[] CONSTRUCTOR_VARIABLES = new Set[] { + // RI Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(8, 6, "q", "I", null, 2), + new Locals.VariableDescription(0, 14, "xxx", "I", null, 1), + new Locals.VariableDescription(0, 14, "this", "Lart/Target;", null, 0))), + // ART Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 8, "this", "Lart/Target;", null, 1), + new Locals.VariableDescription(5, 3, "q", "I", null, 0), + new Locals.VariableDescription(0, 8, "xxx", "I", null, 2))), + }; + + public static Set<Locals.VariableDescription>[] DO_NOTHING_VARIABLES = new Set[] { + // RI Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 5, "objs", "[Ljava/lang/Object;", null, 0))), + // ART Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 4, "objs", "[Ljava/lang/Object;", null, 0))), + }; + + public static Set<Locals.VariableDescription>[] DO_SOMETHING_VARIABLES = new Set[] { + // RI Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(0, 130, "x", "I", null, 1), + new Locals.VariableDescription(76, 54, "o", "Ljava/lang/Object;", null, 3), + new Locals.VariableDescription(30, 32, "q", "F", null, 4), + new Locals.VariableDescription(39, 23, "i", "D", null, 5), + new Locals.VariableDescription(17, 51, "z", "I", null, 3), + new Locals.VariableDescription(15, 115, "y", "I", null, 2), + new Locals.VariableDescription(90, 40, "p", "I", null, 5), + new Locals.VariableDescription(97, 33, "q", "J", null, 6), + new Locals.VariableDescription(0, 130, "this", "Lart/Target;", null, 0), + new Locals.VariableDescription(85, + 45, + "i", + "Ljava/util/ArrayList;", + "Ljava/util/ArrayList<Ljava/lang/Integer;>;", + 4))), + // ART Local variable table + new HashSet<>(Arrays.asList( + new Locals.VariableDescription(19, 31, "q", "F", null, 6), + new Locals.VariableDescription(55, 37, "o", "Ljava/lang/Object;", null, 3), + new Locals.VariableDescription(0, 92, "this", "Lart/Target;", null, 14), + new Locals.VariableDescription(12, 80, "z", "I", null, 8), + new Locals.VariableDescription(11, 81, "y", "I", null, 5), + new Locals.VariableDescription(62, 30, "p", "I", null, 4), + new Locals.VariableDescription(0, 92, "x", "I", null, 15), + new Locals.VariableDescription(27, 23, "i", "D", null, 0), + new Locals.VariableDescription(65, 27, "q", "J", null, 6), + new Locals.VariableDescription(60, + 32, + "i", + "Ljava/util/ArrayList;", + "Ljava/util/ArrayList<Ljava/lang/Integer;>;", + 2))), + }; + + // Get a classloader that can load the Target class. + public static ClassLoader getClassLoader() throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class); + // We are on art since we got the InMemoryDexClassLoader. + return (ClassLoader)ctor.newInstance( + ByteBuffer.wrap(DEX_BYTES), Test1911.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. + return new ClassLoader(Test1911.class.getClassLoader()) { + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals("art.Target")) { + return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length); + } else { + return super.findClass(name); + } + } + }; + } + } + + public static void CheckLocalVariableTable(Executable m, + Set<Locals.VariableDescription>[] possible_vars) { + Set<Locals.VariableDescription> real_vars = + new HashSet<>(Arrays.asList(Locals.GetLocalVariableTable(m))); + for (Set<Locals.VariableDescription> pos : possible_vars) { + if (pos.equals(real_vars)) { + return; + } + } + System.out.println("Unexpected variables for " + m); + System.out.println("Received: " + real_vars); + System.out.println("Expected one of:"); + for (Object pos : possible_vars) { + System.out.println("\t" + pos); + } + } + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + Class<?> target = getClassLoader().loadClass("art.Target"); + CheckLocalVariableTable(target.getDeclaredConstructor(Integer.TYPE), + CONSTRUCTOR_VARIABLES); + CheckLocalVariableTable(target.getDeclaredMethod("doNothing", (new Object[0]).getClass()), + DO_NOTHING_VARIABLES); + CheckLocalVariableTable(target.getDeclaredMethod("doSomething", Integer.TYPE), + DO_SOMETHING_VARIABLES); + } +} + diff --git a/test/1912-get-set-local-primitive/expected.txt b/test/1912-get-set-local-primitive/expected.txt new file mode 100644 index 0000000000..f2c5ce8640 --- /dev/null +++ b/test/1912-get-set-local-primitive/expected.txt @@ -0,0 +1,108 @@ +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) got value: 42 + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) set value: 2147483647 + Value is '2147483647' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) got value: 9001 + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) set value: 9223372036854775807 + Value is '9223372036854775807' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '9001' (class: class java.lang.Long) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) got value: 1.618 + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) set value: 9.2 + Value is '9.2' (class: class java.lang.Float) +Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '1.618' (class: class java.lang.Float) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetInt" on remote thread. +"GetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetLong" on remote thread. +"GetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetFloat" on remote thread. +"GetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetDouble" on remote thread. +"GetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) got value: 3.1415 + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetInt" on remote thread. +"SetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetLong" on remote thread. +"SetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetFloat" on remote thread. +"SetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH + Value is '3.1415' (class: class java.lang.Double) +Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetDouble" on remote thread. +"SetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) set value: 12.4 + Value is '12.4' (class: class java.lang.Double) +Running public static void art.Test1912.BooleanMethod(java.lang.Runnable) with "SetIntBoolSize" on remote thread. +"SetIntBoolSize" on public static void art.Test1912.BooleanMethod(java.lang.Runnable) set value: 1 + Value is 'true' (class: class java.lang.Boolean) +Running public static void art.Test1912.ByteMethod(java.lang.Runnable) with "SetIntByteSize" on remote thread. +"SetIntByteSize" on public static void art.Test1912.ByteMethod(java.lang.Runnable) set value: 126 + Value is '126' (class: class java.lang.Byte) +Running public static void art.Test1912.CharMethod(java.lang.Runnable) with "SetIntCharSize" on remote thread. +"SetIntCharSize" on public static void art.Test1912.CharMethod(java.lang.Runnable) set value: 65534 + Value is '<Char: -1>' (class: class java.lang.String) +Running public static void art.Test1912.ShortMethod(java.lang.Runnable) with "SetIntShortSize" on remote thread. +"SetIntShortSize" on public static void art.Test1912.ShortMethod(java.lang.Runnable) set value: 32766 + Value is '32766' (class: class java.lang.Short) diff --git a/test/1912-get-set-local-primitive/info.txt b/test/1912-get-set-local-primitive/info.txt new file mode 100644 index 0000000000..87a7b35333 --- /dev/null +++ b/test/1912-get-set-local-primitive/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get/set Local variable primitives. + diff --git a/test/1912-get-set-local-primitive/run b/test/1912-get-set-local-primitive/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1912-get-set-local-primitive/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/1912-get-set-local-primitive/src/Main.java b/test/1912-get-set-local-primitive/src/Main.java new file mode 100644 index 0000000000..9ff69f8a05 --- /dev/null +++ b/test/1912-get-set-local-primitive/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.Test1912.run(); + } +} diff --git a/test/1912-get-set-local-primitive/src/art/Breakpoint.java b/test/1912-get-set-local-primitive/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1912-get-set-local-primitive/src/art/Locals.java b/test/1912-get-set-local-primitive/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1912-get-set-local-primitive/src/art/StackTrace.java b/test/1912-get-set-local-primitive/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1912-get-set-local-primitive/src/art/Suspension.java b/test/1912-get-set-local-primitive/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1912-get-set-local-primitive/src/art/Test1912.java b/test/1912-get-set-local-primitive/src/art/Test1912.java new file mode 100644 index 0000000000..24149f4cbe --- /dev/null +++ b/test/1912-get-set-local-primitive/src/art/Test1912.java @@ -0,0 +1,260 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +// TODO Rename test to set-get-local-prim + +public class Test1912 { + public static final String TARGET_VAR = "TARGET"; + + + public static void reportValue(Object val) { + if (val instanceof Character) { + val = "<Char: " + Character.getNumericValue(((Character)val).charValue()) + ">"; + } + System.out.println("\tValue is '" + val + "' (class: " + val.getClass() + ")"); + } + + public static void BooleanMethod(Runnable safepoint) { + boolean TARGET = false; + safepoint.run(); + reportValue(TARGET); + } + public static void ByteMethod(Runnable safepoint) { + byte TARGET = 8; + safepoint.run(); + reportValue(TARGET); + } + public static void CharMethod(Runnable safepoint) { + char TARGET = 'q'; + safepoint.run(); + reportValue(TARGET); + } + public static void ShortMethod(Runnable safepoint) { + short TARGET = 321; + safepoint.run(); + reportValue(TARGET); + } + public static void IntMethod(Runnable safepoint) { + int TARGET = 42; + safepoint.run(); + reportValue(TARGET); + } + public static void LongMethod(Runnable safepoint) { + long TARGET = 9001; + safepoint.run(); + reportValue(TARGET); + } + public static void FloatMethod(Runnable safepoint) { + float TARGET = 1.618f; + safepoint.run(); + reportValue(TARGET); + } + public static void DoubleMethod(Runnable safepoint) { + double TARGET = 3.1415d; + safepoint.run(); + reportValue(TARGET); + } + + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + Locals.VariableDescription TARGET_desc, + int depth) throws Exception; + } + + public static interface SetterFunction { + public void SetVar(Thread t, int depth, int slot, Object v); + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth, int slot); + } + + public static SafepointFunction NamedSet( + final String type, final SetterFunction get, final Object v) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + get.SetVar(t, depth, desc.slot, v); + System.out.println(this + " on " + method + " set value: " + v); + } catch (Exception e) { + System.out.println( + this + " on " + method + " failed to set value " + v + " due to " + e.getMessage()); + } + } + public String toString() { + return "\"Set" + type + "\""; + } + }; + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + Object res = get.GetVar(t, depth, desc.slot); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Method target; + + public TestCase(Method target) { + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(null, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + Locals.VariableDescription desc = findTargetVar(frame.current_location); + safepoint.invoke(remote, target, desc, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private Locals.VariableDescription findTargetVar(long loc) { + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc); + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + public static Method getMethod(String name) throws Exception { + return Test1912.class.getDeclaredMethod(name, Runnable.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(getMethod("IntMethod")), + new TestCase(getMethod("LongMethod")), + new TestCase(getMethod("FloatMethod")), + new TestCase(getMethod("DoubleMethod")), + }; + + final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] { + NamedGet("Int", Locals::GetLocalVariableInt), + NamedGet("Long", Locals::GetLocalVariableLong), + NamedGet("Float", Locals::GetLocalVariableFloat), + NamedGet("Double", Locals::GetLocalVariableDouble), + NamedSet("Int", Locals::SetLocalVariableInt, Integer.MAX_VALUE), + NamedSet("Long", Locals::SetLocalVariableLong, Long.MAX_VALUE), + NamedSet("Float", Locals::SetLocalVariableFloat, 9.2f), + NamedSet("Double", Locals::SetLocalVariableDouble, 12.4d), + }; + + for (TestCase t: MAIN_TEST_CASES) { + for (SafepointFunction s : SAFEPOINTS) { + t.exec(s); + } + } + + // Test int for small values. + new TestCase(getMethod("BooleanMethod")).exec( + NamedSet("IntBoolSize", Locals::SetLocalVariableInt, 1)); + new TestCase(getMethod("ByteMethod")).exec( + NamedSet("IntByteSize", Locals::SetLocalVariableInt, Byte.MAX_VALUE - 1)); + + new TestCase(getMethod("CharMethod")).exec( + NamedSet("IntCharSize", Locals::SetLocalVariableInt, Character.MAX_VALUE - 1)); + new TestCase(getMethod("ShortMethod")).exec( + NamedSet("IntShortSize", Locals::SetLocalVariableInt, Short.MAX_VALUE - 1)); + } +} + diff --git a/test/1913-get-set-local-objects/expected.txt b/test/1913-get-set-local-objects/expected.txt new file mode 100644 index 0000000000..23f49920c9 --- /dev/null +++ b/test/1913-get-set-local-objects/expected.txt @@ -0,0 +1,72 @@ +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) got value: TestClass1("ObjectMethod") + Value is 'TestClass1("ObjectMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: null + Value is 'null' (class: NULL) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1") + Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")") + Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2("Set TestClass2") + Value is 'TestClass2("Set TestClass2")' (class: class art.Test1913$TestClass2) +Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")") + Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) got value: TestClass1("InterfaceMethod") + Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: null + Value is 'null' (class: NULL) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1") + Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")") + Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH + Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")") + Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) got value: TestClass1("SpecificClassMethod") + Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: null + Value is 'null' (class: NULL) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1") + Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")") + Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH + Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH + Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "GetGetObject" on remote thread. +"GetGetObject" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetNull" on remote thread. +"SetNull" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value null due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1" on remote thread. +"SetTestClass1" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1("Set TestClass1") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread. +"SetTestClass1ext" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1ext("TestClass1("Set TestClass1ext")") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2" on remote thread. +"SetTestClass2" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) +Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread. +"SetTestClass2impl" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH + Value is '42' (class: class java.lang.Integer) diff --git a/test/1913-get-set-local-objects/info.txt b/test/1913-get-set-local-objects/info.txt new file mode 100644 index 0000000000..86ac7435ec --- /dev/null +++ b/test/1913-get-set-local-objects/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get and set local variable object. + diff --git a/test/1913-get-set-local-objects/run b/test/1913-get-set-local-objects/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1913-get-set-local-objects/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/1913-get-set-local-objects/src/Main.java b/test/1913-get-set-local-objects/src/Main.java new file mode 100644 index 0000000000..45565c2897 --- /dev/null +++ b/test/1913-get-set-local-objects/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.Test1913.run(); + } +} diff --git a/test/1913-get-set-local-objects/src/art/Breakpoint.java b/test/1913-get-set-local-objects/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1913-get-set-local-objects/src/art/Locals.java b/test/1913-get-set-local-objects/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1913-get-set-local-objects/src/art/StackTrace.java b/test/1913-get-set-local-objects/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1913-get-set-local-objects/src/art/Suspension.java b/test/1913-get-set-local-objects/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1913-get-set-local-objects/src/art/Test1913.java b/test/1913-get-set-local-objects/src/art/Test1913.java new file mode 100644 index 0000000000..417138a194 --- /dev/null +++ b/test/1913-get-set-local-objects/src/art/Test1913.java @@ -0,0 +1,251 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1913 { + public static final String TARGET_VAR = "TARGET"; + + public static interface TestInterface { + public default void doNothing() {} + } + public static class TestClass1 implements TestInterface { + public String id; + public TestClass1(String id) { this.id = id; } + public String toString() { return String.format("TestClass1(\"%s\")", id); } + } + + public static class TestClass1ext extends TestClass1 { + public TestClass1ext(String id) { super(id); } + public String toString() { return String.format("TestClass1ext(\"%s\")", super.toString()); } + } + public static class TestClass2 { + public String id; + public TestClass2(String id) { this.id = id; } + public String toString() { return String.format("TestClass2(\"%s\")", id); } + } + public static class TestClass2impl extends TestClass2 implements TestInterface { + public TestClass2impl(String id) { super(id); } + public String toString() { return String.format("TestClass2impl(\"%s\")", super.toString()); } + } + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "' (class: " + + (val != null ? val.getClass() : "NULL") + ")"); + } + + public static void PrimitiveMethod(Runnable safepoint) { + int TARGET = 42; + safepoint.run(); + reportValue(TARGET); + } + + // b/64115302: Needed to make sure that DX doesn't change the type of TARGET to TestClass1. + private static Object AsObject(Object o) { return o; } + public static void ObjectMethod(Runnable safepoint) { + Object TARGET = AsObject(new TestClass1("ObjectMethod")); + safepoint.run(); + reportValue(TARGET); + } + + public static void InterfaceMethod(Runnable safepoint) { + TestInterface TARGET = new TestClass1("InterfaceMethod"); + safepoint.run(); + reportValue(TARGET); + } + + public static void SpecificClassMethod(Runnable safepoint) { + TestClass1 TARGET = new TestClass1("SpecificClassMethod"); + safepoint.run(); + reportValue(TARGET); + } + + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + Locals.VariableDescription TARGET_desc, + int depth) throws Exception; + } + + public static interface SetterFunction { + public void SetVar(Thread t, int depth, int slot, Object v); + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth, int slot); + } + + public static SafepointFunction NamedSet( + final String type, final SetterFunction get, final Object v) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + get.SetVar(t, depth, desc.slot, v); + System.out.println(this + " on " + method + " set value: " + v); + } catch (Exception e) { + System.out.println( + this + " on " + method + " failed to set value " + v + " due to " + e.getMessage()); + } + } + public String toString() { + return "\"Set" + type + "\""; + } + }; + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) { + try { + Object res = get.GetVar(t, depth, desc.slot); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Method target; + + public TestCase(Method target) { + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(null, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + Locals.VariableDescription desc = findTargetVar(frame.current_location); + safepoint.invoke(remote, target, desc, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private Locals.VariableDescription findTargetVar(long loc) { + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc); + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + public static Method getMethod(String name) throws Exception { + return Test1913.class.getDeclaredMethod(name, Runnable.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(getMethod("ObjectMethod")), + new TestCase(getMethod("InterfaceMethod")), + new TestCase(getMethod("SpecificClassMethod")), + new TestCase(getMethod("PrimitiveMethod")), + }; + + final SetterFunction set_obj = Locals::SetLocalVariableObject; + final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] { + NamedGet("GetObject", Locals::GetLocalVariableObject), + NamedSet("Null", set_obj, null), + NamedSet("TestClass1", set_obj, new TestClass1("Set TestClass1")), + NamedSet("TestClass1ext", set_obj, new TestClass1ext("Set TestClass1ext")), + NamedSet("TestClass2", set_obj, new TestClass2("Set TestClass2")), + NamedSet("TestClass2impl", set_obj, new TestClass2impl("Set TestClass2impl")), + }; + + for (TestCase t: MAIN_TEST_CASES) { + for (SafepointFunction s : SAFEPOINTS) { + t.exec(s); + } + } + } +} + diff --git a/test/1914-get-local-instance/expected.txt b/test/1914-get-local-instance/expected.txt new file mode 100644 index 0000000000..4117942392 --- /dev/null +++ b/test/1914-get-local-instance/expected.txt @@ -0,0 +1,12 @@ +Running public static void art.Test1914.StaticMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public static void art.Test1914.StaticMethod(java.lang.Runnable) got value: null + Value is 'null' (class: NULL) +Running public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) got value: null + Value is 'null' (class: NULL) +Running public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) got value: TargetClass("InstanceMethodObject") + Value is 'TargetClass("InstanceMethodObject")' (class: class art.Test1914$TargetClass) +Running public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) got value: TargetClass("NativeInstanceMethodObject") + Value is 'TargetClass("NativeInstanceMethodObject")' (class: class art.Test1914$TargetClass) diff --git a/test/1914-get-local-instance/info.txt b/test/1914-get-local-instance/info.txt new file mode 100644 index 0000000000..9fc3d62cd6 --- /dev/null +++ b/test/1914-get-local-instance/info.txt @@ -0,0 +1,2 @@ +Test for jvmti get local instance + diff --git a/test/1914-get-local-instance/local_instance.cc b/test/1914-get-local-instance/local_instance.cc new file mode 100644 index 0000000000..03aa59e484 --- /dev/null +++ b/test/1914-get-local-instance/local_instance.cc @@ -0,0 +1,68 @@ +/* + * 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 <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1914LocalInstance { + +extern "C" JNIEXPORT void Java_art_Test1914_00024TargetClass_NativeInstanceMethod( + JNIEnv* env, jobject thiz, jobject run) { + ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable")); + if (env->ExceptionCheck()) { return; } + jmethodID method = env->GetMethodID(runnable.get(), "run", "()V"); + if (env->ExceptionCheck()) { return; } + env->CallVoidMethod(run, method); + if (env->ExceptionCheck()) { return; } + ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914")); + if (env->ExceptionCheck()) { return; } + jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V"); + if (env->ExceptionCheck()) { return; } + env->CallStaticVoidMethod(Test1914.get(), report, thiz); +} + +extern "C" JNIEXPORT void Java_art_Test1914_NativeStaticMethod( + JNIEnv* env, jclass, jobject run) { + ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable")); + if (env->ExceptionCheck()) { return; } + jmethodID method = env->GetMethodID(runnable.get(), "run", "()V"); + if (env->ExceptionCheck()) { return; } + env->CallVoidMethod(run, method); + if (env->ExceptionCheck()) { return; } + ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914")); + if (env->ExceptionCheck()) { return; } + jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V"); + if (env->ExceptionCheck()) { return; } + env->CallStaticVoidMethod(Test1914.get(), report, nullptr); +} + +} // namespace Test1914LocalInstance +} // namespace art + diff --git a/test/1914-get-local-instance/run b/test/1914-get-local-instance/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1914-get-local-instance/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/1914-get-local-instance/src/Main.java b/test/1914-get-local-instance/src/Main.java new file mode 100644 index 0000000000..163221e12f --- /dev/null +++ b/test/1914-get-local-instance/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.Test1914.run(); + } +} diff --git a/test/1914-get-local-instance/src/art/Breakpoint.java b/test/1914-get-local-instance/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1914-get-local-instance/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1914-get-local-instance/src/art/Locals.java b/test/1914-get-local-instance/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1914-get-local-instance/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1914-get-local-instance/src/art/StackTrace.java b/test/1914-get-local-instance/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1914-get-local-instance/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1914-get-local-instance/src/art/Suspension.java b/test/1914-get-local-instance/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1914-get-local-instance/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1914-get-local-instance/src/art/Test1914.java b/test/1914-get-local-instance/src/art/Test1914.java new file mode 100644 index 0000000000..c09f519db8 --- /dev/null +++ b/test/1914-get-local-instance/src/art/Test1914.java @@ -0,0 +1,182 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1914 { + public static final String TARGET_VAR = "TARGET"; + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "' (class: " + + (val != null ? val.getClass() : "NULL") + ")"); + } + + public static void StaticMethod(Runnable safepoint) { + safepoint.run(); + reportValue(null); + } + + public static native void NativeStaticMethod(Runnable safepoint); + + public static class TargetClass { + public String id; + public String toString() { return String.format("TargetClass(\"%s\")", id); } + public TargetClass(String id) { this.id = id; } + + public void InstanceMethod(Runnable safepoint) { + safepoint.run(); + reportValue(this); + } + + public native void NativeInstanceMethod(Runnable safepoint); + } + + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + int depth) throws Exception; + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth); + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, int depth) { + try { + Object res = get.GetVar(t, depth); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Object thiz; + public final Method target; + + public TestCase(Method target) { + this(null, target); + } + public TestCase(Object thiz, Method target) { + this.thiz = thiz; + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(thiz, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + safepoint.invoke(remote, target, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + + public static Method getMethod(Class<?> klass, String name) throws Exception { + return klass.getDeclaredMethod(name, Runnable.class); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final TestCase[] MAIN_TEST_CASES = new TestCase[] { + new TestCase(null, getMethod(Test1914.class, "StaticMethod")), + new TestCase(null, getMethod(Test1914.class, "NativeStaticMethod")), + new TestCase(new TargetClass("InstanceMethodObject"), + getMethod(TargetClass.class, "InstanceMethod")), + new TestCase(new TargetClass("NativeInstanceMethodObject"), + getMethod(TargetClass.class, "NativeInstanceMethod")), + }; + + for (TestCase t: MAIN_TEST_CASES) { + t.exec(NamedGet("This", Locals::GetLocalInstance)); + } + } +} + diff --git a/test/1915-get-set-local-current-thread/expected.txt b/test/1915-get-set-local-current-thread/expected.txt new file mode 100644 index 0000000000..de39ca9554 --- /dev/null +++ b/test/1915-get-set-local-current-thread/expected.txt @@ -0,0 +1,5 @@ +GetLocalInt on current thread! +From GetLocalInt(), value is 42 + Value is '42' +SetLocalInt on current thread! + Value is '1337' diff --git a/test/1915-get-set-local-current-thread/info.txt b/test/1915-get-set-local-current-thread/info.txt new file mode 100644 index 0000000000..c59f50de3b --- /dev/null +++ b/test/1915-get-set-local-current-thread/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get/set Local variable on current thread. + diff --git a/test/1915-get-set-local-current-thread/run b/test/1915-get-set-local-current-thread/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1915-get-set-local-current-thread/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/1915-get-set-local-current-thread/src/Main.java b/test/1915-get-set-local-current-thread/src/Main.java new file mode 100644 index 0000000000..47e676704f --- /dev/null +++ b/test/1915-get-set-local-current-thread/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.Test1915.run(); + } +} diff --git a/test/1915-get-set-local-current-thread/src/art/Breakpoint.java b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1915-get-set-local-current-thread/src/art/Locals.java b/test/1915-get-set-local-current-thread/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1915-get-set-local-current-thread/src/art/StackTrace.java b/test/1915-get-set-local-current-thread/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1915-get-set-local-current-thread/src/art/Suspension.java b/test/1915-get-set-local-current-thread/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1915-get-set-local-current-thread/src/art/Test1915.java b/test/1915-get-set-local-current-thread/src/art/Test1915.java new file mode 100644 index 0000000000..a99a487ea6 --- /dev/null +++ b/test/1915-get-set-local-current-thread/src/art/Test1915.java @@ -0,0 +1,105 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1915 { + public static final int SET_VALUE = 1337; + public static final String TARGET_VAR = "TARGET"; + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "'"); + } + public static interface ThrowRunnable { + public void run() throws Exception; + } + + public static void IntMethod(ThrowRunnable safepoint) throws Exception { + int TARGET = 42; + safepoint.run(); + reportValue(TARGET); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + final Method target = Test1915.class.getDeclaredMethod("IntMethod", ThrowRunnable.class); + // Get Variable. + System.out.println("GetLocalInt on current thread!"); + IntMethod(() -> { + StackTrace.StackFrameData frame = FindStackFrame(target); + int depth = FindExpectedFrameDepth(frame); + int slot = FindSlot(frame); + int value = Locals.GetLocalVariableInt(Thread.currentThread(), depth, slot); + System.out.println("From GetLocalInt(), value is " + value); + }); + // Set Variable. + System.out.println("SetLocalInt on current thread!"); + IntMethod(() -> { + StackTrace.StackFrameData frame = FindStackFrame(target); + int depth = FindExpectedFrameDepth(frame); + int slot = FindSlot(frame); + Locals.SetLocalVariableInt(Thread.currentThread(), depth, slot, SET_VALUE); + }); + } + + public static int FindSlot(StackTrace.StackFrameData frame) throws Exception { + long loc = frame.current_location; + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var.slot; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc); + } + + public static int FindExpectedFrameDepth(StackTrace.StackFrameData frame) throws Exception { + // Adjust the 'frame' depth since it is modified by: + // +1 for Get/SetLocalVariableInt in future. + // -1 for FindStackFrame + // -1 for GetStackTrace + // -1 for GetStackTraceNative + // ------------------------------ + // -2 + return frame.depth - 2; + } + + private static StackTrace.StackFrameData FindStackFrame(Method target) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target); + } +} + diff --git a/test/1916-get-set-current-frame/expected.txt b/test/1916-get-set-current-frame/expected.txt new file mode 100644 index 0000000000..343d49310e --- /dev/null +++ b/test/1916-get-set-current-frame/expected.txt @@ -0,0 +1,4 @@ +From GetLocalInt(), value is 42 + Value is '42' +Setting TARGET to 1337 + Value is '1337' diff --git a/test/1916-get-set-current-frame/info.txt b/test/1916-get-set-current-frame/info.txt new file mode 100644 index 0000000000..7342af7e71 --- /dev/null +++ b/test/1916-get-set-current-frame/info.txt @@ -0,0 +1,2 @@ +Tests for jvmti get/set Local variable in the currently executing method frame. + diff --git a/test/1916-get-set-current-frame/run b/test/1916-get-set-current-frame/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1916-get-set-current-frame/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/1916-get-set-current-frame/src/Main.java b/test/1916-get-set-current-frame/src/Main.java new file mode 100644 index 0000000000..7d0cd21772 --- /dev/null +++ b/test/1916-get-set-current-frame/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.Test1916.run(); + } +} diff --git a/test/1916-get-set-current-frame/src/art/Breakpoint.java b/test/1916-get-set-current-frame/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1916-get-set-current-frame/src/art/Locals.java b/test/1916-get-set-current-frame/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1916-get-set-current-frame/src/art/StackTrace.java b/test/1916-get-set-current-frame/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1916-get-set-current-frame/src/art/Suspension.java b/test/1916-get-set-current-frame/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1916-get-set-current-frame/src/art/Test1916.java b/test/1916-get-set-current-frame/src/art/Test1916.java new file mode 100644 index 0000000000..3e5bce2619 --- /dev/null +++ b/test/1916-get-set-current-frame/src/art/Test1916.java @@ -0,0 +1,148 @@ +/* + * 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.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1916 { + public static final int SET_VALUE = 1337; + public static final String TARGET_VAR = "TARGET"; + + public static void reportValue(Object val) { + System.out.println("\tValue is '" + val + "'"); + } + + public static class IntRunner implements Runnable { + private volatile boolean continueBusyLoop; + private volatile boolean inBusyLoop; + public IntRunner() { + this.continueBusyLoop = true; + this.inBusyLoop = false; + } + public void run() { + int TARGET = 42; + // We will suspend the thread during this loop. + while (continueBusyLoop) { + inBusyLoop = true; + } + reportValue(TARGET); + } + public void waitForBusyLoopStart() { while (!inBusyLoop) {} } + public void finish() { continueBusyLoop = false; } + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + runGet(); + runSet(); + } + + public static void runGet() throws Exception { + Method target = IntRunner.class.getDeclaredMethod("run"); + // Get Int + IntRunner int_runner = new IntRunner(); + Thread target_get = new Thread(int_runner, "GetLocalInt - Target"); + target_get.start(); + int_runner.waitForBusyLoopStart(); + try { + Suspension.suspend(target_get); + } catch (Exception e) { + System.out.println("FAIL: got " + e); + e.printStackTrace(); + int_runner.finish(); + target_get.join(); + return; + } + try { + StackTrace.StackFrameData frame = FindStackFrame(target_get, target); + int depth = frame.depth; + if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); } + int slot = FindSlot(frame); + int value = Locals.GetLocalVariableInt(target_get, depth, slot); + System.out.println("From GetLocalInt(), value is " + value); + } finally { + Suspension.resume(target_get); + int_runner.finish(); + target_get.join(); + } + } + + public static void runSet() throws Exception { + Method target = IntRunner.class.getDeclaredMethod("run"); + // Set Int + IntRunner int_runner = new IntRunner(); + Thread target_set = new Thread(int_runner, "SetLocalInt - Target"); + target_set.start(); + int_runner.waitForBusyLoopStart(); + try { + Suspension.suspend(target_set); + } catch (Exception e) { + System.out.println("FAIL: got " + e); + e.printStackTrace(); + int_runner.finish(); + target_set.join(); + return; + } + try { + StackTrace.StackFrameData frame = FindStackFrame(target_set, target); + int depth = frame.depth; + if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); } + int slot = FindSlot(frame); + System.out.println("Setting TARGET to " + SET_VALUE); + Locals.SetLocalVariableInt(target_set, depth, slot, SET_VALUE); + } finally { + Suspension.resume(target_set); + int_runner.finish(); + target_set.join(); + } + } + + public static int FindSlot(StackTrace.StackFrameData frame) throws Exception { + long loc = frame.current_location; + for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(TARGET_VAR)) { + return var.slot; + } + } + throw new Error( + "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc); + } + + private static StackTrace.StackFrameData FindStackFrame(Thread thr, Method target) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target)) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } +} + diff --git a/test/1917-get-stack-frame/expected.txt b/test/1917-get-stack-frame/expected.txt new file mode 100644 index 0000000000..4c9efcf157 --- /dev/null +++ b/test/1917-get-stack-frame/expected.txt @@ -0,0 +1,33 @@ +Recurring 5 times +'private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)' line: -1 +'public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)' line: 60 +'public void art.Test1917$StackTraceGenerator.run()' line: 82 +'public void art.Test1917$RecurCount.doRecur(int)' line: 104 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.run()' line: 97 +'public static void art.Test1917.run() throws java.lang.Exception' line: 133 +Recurring 5 times on another thread +'private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)' line: -1 +'public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)' line: 60 +'public void art.Test1917$StackTraceGenerator.run()' line: 82 +'public void art.Test1917$RecurCount.doRecur(int)' line: 104 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.run()' line: 97 +Recurring 5 times on another thread. Stack trace from main thread! +'public void java.util.concurrent.Semaphore.acquire() throws java.lang.InterruptedException' line: <NOT-DETERMINISTIC> +'public void art.Test1917$ThreadPauser.run()' line: 46 +'public void art.Test1917$RecurCount.doRecur(int)' line: 104 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.doRecur(int)' line: 102 +'public void art.Test1917$RecurCount.run()' line: 97 diff --git a/test/1917-get-stack-frame/info.txt b/test/1917-get-stack-frame/info.txt new file mode 100644 index 0000000000..e72034adef --- /dev/null +++ b/test/1917-get-stack-frame/info.txt @@ -0,0 +1 @@ +Tests stack frame functions of jvmti diff --git a/test/1917-get-stack-frame/run b/test/1917-get-stack-frame/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1917-get-stack-frame/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/1917-get-stack-frame/src/Main.java b/test/1917-get-stack-frame/src/Main.java new file mode 100644 index 0000000000..c055a5c540 --- /dev/null +++ b/test/1917-get-stack-frame/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.Test1917.run(); + } +} diff --git a/test/1917-get-stack-frame/src/art/Breakpoint.java b/test/1917-get-stack-frame/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1917-get-stack-frame/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1917-get-stack-frame/src/art/StackTrace.java b/test/1917-get-stack-frame/src/art/StackTrace.java new file mode 100644 index 0000000000..b12c3df66b --- /dev/null +++ b/test/1917-get-stack-frame/src/art/StackTrace.java @@ -0,0 +1,67 @@ +/* + * 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.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1917-get-stack-frame/src/art/Suspension.java b/test/1917-get-stack-frame/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1917-get-stack-frame/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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 Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1917-get-stack-frame/src/art/Test1917.java b/test/1917-get-stack-frame/src/art/Test1917.java new file mode 100644 index 0000000000..def7530bff --- /dev/null +++ b/test/1917-get-stack-frame/src/art/Test1917.java @@ -0,0 +1,150 @@ +/* + * 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.Method; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.Vector; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1917 { + public final static boolean TEST_PRINT_ALL = false; + + public static class ThreadPauser implements Runnable { + public Semaphore sem_wakeup_main = new Semaphore(0); + public Semaphore sem_wait = new Semaphore(0); + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + while (!sem_wait.hasQueuedThreads()) {} + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public static class StackTraceGenerator implements Runnable { + private final Thread thr; + private final Consumer<StackTrace.StackFrameData> con; + public StackTraceGenerator(Thread thr, Consumer<StackTrace.StackFrameData> con) { + this.thr = thr; + this.con = con; + } + + public StackTraceGenerator(Consumer<StackTrace.StackFrameData> con) { + this(null, con); + } + + public Thread getThread() { + if (thr == null) { + return Thread.currentThread(); + } else { + return thr; + } + } + public void run() { + for (StackTrace.StackFrameData s : StackTrace.GetStackTrace(getThread())) { + con.accept(s); + } + } + } + + public static class RecurCount implements Runnable { + private final int cnt; + private final Runnable then; + public RecurCount(int cnt, Runnable then) { + this.cnt = cnt; + this.then = then; + } + + public void run() { + doRecur(0); + } + + public void doRecur(int n) { + if (n < cnt) { + doRecur(n + 1); + } else { + then.run(); + } + } + } + + public static Consumer<StackTrace.StackFrameData> makePrintStackFramesConsumer() + throws Exception { + final Method end_method = Test1917.class.getDeclaredMethod("run"); + return new Consumer<StackTrace.StackFrameData>() { + public void accept(StackTrace.StackFrameData data) { + if (TEST_PRINT_ALL) { + System.out.println(data); + } else { + Package p = data.method.getDeclaringClass().getPackage(); + // Filter out anything to do with the testing harness. + if (p != null && p.equals(Test1917.class.getPackage())) { + System.out.printf("'%s' line: %d\n", + data.method, + Breakpoint.locationToLine(data.method, data.current_location)); + } else if (data.method.getDeclaringClass().equals(Semaphore.class)) { + System.out.printf("'%s' line: <NOT-DETERMINISTIC>\n", data.method); + } + } + } + }; + } + + public static void run() throws Exception { + System.out.println("Recurring 5 times"); + new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())).run(); + + System.out.println("Recurring 5 times on another thread"); + Thread thr = new Thread( + new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer()))); + thr.start(); + thr.join(); + + System.out.println("Recurring 5 times on another thread. Stack trace from main thread!"); + ThreadPauser pause = new ThreadPauser(); + Thread thr2 = new Thread(new RecurCount(5, pause)); + thr2.start(); + pause.waitForOtherThreadToPause(); + new StackTraceGenerator(thr2, makePrintStackFramesConsumer()).run(); + pause.wakeupOtherThread(); + thr2.join(); + } +} diff --git a/test/550-checker-multiply-accumulate/src/Main.java b/test/550-checker-multiply-accumulate/src/Main.java index 810f0faaa6..6fd9cdd010 100644 --- a/test/550-checker-multiply-accumulate/src/Main.java +++ b/test/550-checker-multiply-accumulate/src/Main.java @@ -434,7 +434,7 @@ public class Main { /// CHECK-DAG: VecMultiplyAccumulate kind:Add loop:<<Loop>> outer_loop:none /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier_arm64 (after) - /// CHECK-NOT: VecMull + /// CHECK-NOT: VecMul /// CHECK-NOT: VecAdd public static void SimdMulAdd(int[] array1, int[] array2) { for (int j = 0; j < 100; j++) { @@ -452,7 +452,7 @@ public class Main { /// CHECK-DAG: VecMultiplyAccumulate kind:Sub loop:<<Loop>> outer_loop:none /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier_arm64 (after) - /// CHECK-NOT: VecMull + /// CHECK-NOT: VecMul /// CHECK-NOT: VecSub public static void SimdMulSub(int[] array1, int[] array2) { for (int j = 0; j < 100; j++) { diff --git a/test/616-cha/src/Main.java b/test/616-cha/src/Main.java index beea90a57d..27da7ccec2 100644 --- a/test/616-cha/src/Main.java +++ b/test/616-cha/src/Main.java @@ -187,7 +187,12 @@ public class Main { System.loadLibrary(args[0]); // CHeck some boot-image methods. - assertSingleImplementation(java.util.ArrayList.class, "size", true); + + // We would want to have this, but currently setting single-implementation in the boot image + // does not work well with app images. b/34193647 + final boolean ARRAYLIST_SIZE_EXPECTED = false; + assertSingleImplementation(java.util.ArrayList.class, "size", ARRAYLIST_SIZE_EXPECTED); + // java.util.LinkedHashMap overrides get(). assertSingleImplementation(java.util.HashMap.class, "get", false); diff --git a/test/Android.bp b/test/Android.bp index 44cb4f6e8a..fab664a3e2 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -252,8 +252,10 @@ art_cc_defaults { "ti-agent/test_env.cc", "ti-agent/breakpoint_helper.cc", "ti-agent/common_helper.cc", + "ti-agent/locals_helper.cc", "ti-agent/redefinition_helper.cc", "ti-agent/suspension_helper.cc", + "ti-agent/stack_trace_helper.cc", "ti-agent/trace_helper.cc", // This is the list of non-special OnLoad things and excludes BCI and anything that depends // on ART internals. @@ -292,6 +294,7 @@ art_cc_defaults { "1905-suspend-native/native_suspend.cc", "1908-suspend-native-resume-self/native_suspend_resume.cc", "1909-per-agent-tls/agent_tls.cc", + "1914-get-local-instance/local_instance.cc", ], shared_libs: [ "libbase", diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index ede485a81b..e989e39bf3 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -453,6 +453,10 @@ fi if [ "$USE_JVM" = "y" ]; then export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64 + # Some jvmti tests are flaky without -Xint on the RI. + if [ "$IS_JVMTI_TEST" = "y" ]; then + FLAGS="${FLAGS} -Xint" + fi # Xmx is necessary since we don't pass down the ART flags to JVM. # We pass the classes2 path whether it's used (src-multidex) or not. cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}" diff --git a/test/knownfailures.json b/test/knownfailures.json index 304b760bb0..5a67fbcc45 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -707,15 +707,9 @@ "variant": "gcstress & jit & target" }, { - "tests": ["004-JniTest"], - "description": [ "Tests failing with --build-with-javac-dx since the new annotation", - "lookup changes" ], - "bug": "b/63089991", - "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"} - }, - { "tests": "660-clinit", - "variant": "no-image | no-dex2oat", - "description": "Tests <clinit> for app images, which --no-image and --no-dex2oat do not create" + "variant": "no-image | no-dex2oat | no-prebuild", + "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild and", + "--no-dex2oat do not create"] } ] diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc index 51d3406a35..7280102c6f 100644 --- a/test/ti-agent/jvmti_helper.cc +++ b/test/ti-agent/jvmti_helper.cc @@ -15,6 +15,7 @@ */ #include "jvmti_helper.h" +#include "test_env.h" #include <dlfcn.h> @@ -57,7 +58,7 @@ static const jvmtiCapabilities standard_caps = { .can_get_line_numbers = 1, .can_get_source_debug_extension = 1, .can_access_local_variables = 0, - .can_maintain_original_method_order = 0, + .can_maintain_original_method_order = 1, .can_generate_single_step_events = 1, .can_generate_exception_events = 0, .can_generate_frame_pop_events = 0, @@ -90,6 +91,11 @@ jvmtiCapabilities GetStandardCapabilities() { } void SetStandardCapabilities(jvmtiEnv* env) { + if (IsJVM()) { + // RI is more strict about adding capabilities at runtime then ART so just give it everything. + SetAllCapabilities(env); + return; + } jvmtiCapabilities caps = GetStandardCapabilities(); CheckJvmtiError(env, env->AddCapabilities(&caps)); } @@ -100,7 +106,7 @@ void SetAllCapabilities(jvmtiEnv* env) { CheckJvmtiError(env, env->AddCapabilities(&caps)); } -bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) { +bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error) { if (error == JVMTI_ERROR_NONE) { return false; } @@ -112,11 +118,11 @@ bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) { } char* err; - CheckJvmtiError(jvmti_env, jvmti_env->GetErrorName(error, &err)); + CheckJvmtiError(jvmtienv, jvmtienv->GetErrorName(error, &err)); env->ThrowNew(rt_exception.get(), err); - Deallocate(jvmti_env, err); + Deallocate(jvmtienv, err); return true; } diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h index 78d238a980..a47a4028a9 100644 --- a/test/ti-agent/jvmti_helper.h +++ b/test/ti-agent/jvmti_helper.h @@ -43,7 +43,7 @@ void CheckJvmtiError(jvmtiEnv* env, jvmtiError error); // Convert the given error to a RuntimeException with a message derived from the error. Returns // true on error, false if error is JVMTI_ERROR_NONE. -bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error); +bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error); class JvmtiDeleter { public: diff --git a/test/ti-agent/locals_helper.cc b/test/ti-agent/locals_helper.cc new file mode 100644 index 0000000000..e284b52846 --- /dev/null +++ b/test/ti-agent/locals_helper.cc @@ -0,0 +1,210 @@ +/* + * 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 "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace common_locals { + +static void DeallocateContents(jvmtiLocalVariableEntry* vars, jint nvars) { + for (jint i = 0; i < nvars; i++) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].name)); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].signature)); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].generic_signature)); + } +} + +extern "C" JNIEXPORT void Java_art_Locals_EnableLocalVariableAccess(JNIEnv* env, jclass) { + jvmtiCapabilities caps; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCapabilities(&caps))) { + return; + } + caps.can_access_local_variables = 1; + JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableObject(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jobject val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalObject(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableDouble(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jdouble val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalDouble(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableFloat(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jfloat val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalFloat(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableLong(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jlong val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalLong(t, depth, slot, val)); +} + +extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableInt(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot, + jint val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalInt(t, depth, slot, val)); +} + +extern "C" JNIEXPORT jdouble Java_art_Locals_GetLocalVariableDouble(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jdouble ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalDouble(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jfloat Java_art_Locals_GetLocalVariableFloat(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jfloat ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalFloat(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jlong Java_art_Locals_GetLocalVariableLong(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jlong ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalLong(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jint Java_art_Locals_GetLocalVariableInt(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jint ret = 0; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInt(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalInstance(JNIEnv* env, + jclass, + jthread t, + jint depth) { + jobject ret = nullptr; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInstance(t, depth, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalVariableObject(JNIEnv* env, + jclass, + jthread t, + jint depth, + jint slot) { + jobject ret = nullptr; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalObject(t, depth, slot, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobjectArray Java_art_Locals_GetLocalVariableTable(JNIEnv* env, + jclass, + jobject m) { + jmethodID method = env->FromReflectedMethod(m); + if (env->ExceptionCheck()) { + return nullptr; + } + ScopedLocalRef<jclass> klass(env, env->FindClass("art/Locals$VariableDescription")); + if (env->ExceptionCheck()) { + return nullptr; + } + jint nvars; + jvmtiLocalVariableEntry* vars = nullptr; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetLocalVariableTable(method, &nvars, &vars))) { + return nullptr; + } + jobjectArray vars_array = env->NewObjectArray(nvars, klass.get(), nullptr); + if (env->ExceptionCheck()) { + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return nullptr; + } + + jmethodID constructor = env->GetMethodID( + klass.get(), "<init>", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); + if (env->ExceptionCheck()) { + return nullptr; + } + for (jint i = 0; i < nvars; i++) { + ScopedLocalRef<jstring> name_string(env, env->NewStringUTF(vars[i].name)); + ScopedLocalRef<jstring> sig_string(env, env->NewStringUTF(vars[i].signature)); + ScopedLocalRef<jstring> generic_sig_string(env, env->NewStringUTF(vars[i].generic_signature)); + jobject var_obj = env->NewObject(klass.get(), + constructor, + vars[i].start_location, + vars[i].length, + name_string.get(), + sig_string.get(), + generic_sig_string.get(), + vars[i].slot); + if (env->ExceptionCheck()) { + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return nullptr; + } + env->SetObjectArrayElement(vars_array, i, var_obj); + if (env->ExceptionCheck()) { + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return nullptr; + } + } + + DeallocateContents(vars, nvars); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars)); + return vars_array; +} + +} // namespace common_locals +} // namespace art diff --git a/test/ti-agent/stack_trace_helper.cc b/test/ti-agent/stack_trace_helper.cc new file mode 100644 index 0000000000..f2a8e9a318 --- /dev/null +++ b/test/ti-agent/stack_trace_helper.cc @@ -0,0 +1,99 @@ + +/* + * 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 "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace common_stack_trace { + +extern "C" JNIEXPORT jint JNICALL Java_art_StackTrace_GetStackDepth( + JNIEnv* env, jclass, jthread thr) { + jint ret; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &ret)); + return ret; +} + +extern "C" JNIEXPORT jobjectArray Java_art_StackTrace_nativeGetStackTrace(JNIEnv* env, + jclass, + jthread thr) { + jint depth; + ScopedLocalRef<jclass> klass(env, env->FindClass("art/StackTrace$StackFrameData")); + if (env->ExceptionCheck()) { + return nullptr; + } + jmethodID constructor = env->GetMethodID( + klass.get(), "<init>", "(Ljava/lang/Thread;Ljava/lang/reflect/Executable;JI)V"); + if (env->ExceptionCheck()) { + return nullptr; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &depth))) { + return nullptr; + } + // Just give some extra space. + depth += 10; + jvmtiFrameInfo* frames; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->Allocate(depth * sizeof(jvmtiFrameInfo), + reinterpret_cast<unsigned char**>(&frames)))) { + return nullptr; + } + jint nframes = 0; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetStackTrace(thr, 0, depth, frames, &nframes))) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + jobjectArray frames_array = env->NewObjectArray(nframes, klass.get(), nullptr); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + for (jint i = 0; i < nframes; i++) { + jobject jmethod = GetJavaMethod(jvmti_env, env, frames[i].method); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + jobject frame_obj = env->NewObject(klass.get(), + constructor, + thr, + jmethod, + frames[i].location, + i); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + env->SetObjectArrayElement(frames_array, i, frame_obj); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return nullptr; + } + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames)); + return frames_array; +} + +} // namespace common_stack_trace +} // namespace art diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README index a635fe9928..1f74262eb4 100644 --- a/tools/dexfuzz/README +++ b/tools/dexfuzz/README @@ -145,7 +145,7 @@ OppositeBranchChanger 40 PoolIndexChanger 30 RandomBranchChanger 30 RandomInstructionGenerator 30 -RegisterClobber 40 +RegisterClobber 10 SwitchBranchShifter 30 TryBlockShifter 40 ValuePrinter 40 diff --git a/tools/dexfuzz/src/dexfuzz/DexFuzz.java b/tools/dexfuzz/src/dexfuzz/DexFuzz.java index d37bd34016..2b3b8e7753 100644 --- a/tools/dexfuzz/src/dexfuzz/DexFuzz.java +++ b/tools/dexfuzz/src/dexfuzz/DexFuzz.java @@ -33,9 +33,9 @@ import dexfuzz.listeners.UpdatingConsoleListener; * Entrypoint class for dexfuzz. */ public class DexFuzz { - // Last version update 1.5: added register clobber mutator. + // Last version update 1.7: changed the likelihood of RegisterClobber. private static int majorVersion = 1; - private static int minorVersion = 5; + private static int minorVersion = 7; private static int seedChangeVersion = 0; /** diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/NewArrayLengthChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/NewArrayLengthChanger.java index aba7971b48..e640b4e2ef 100644 --- a/tools/dexfuzz/src/dexfuzz/program/mutators/NewArrayLengthChanger.java +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/NewArrayLengthChanger.java @@ -28,8 +28,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -// This mutation might change the length of an array but can also change the -// value of the register in every place it is used. public class NewArrayLengthChanger extends CodeMutator { /** * Every CodeMutator has an AssociatedMutation, representing the @@ -116,20 +114,46 @@ public class NewArrayLengthChanger extends CodeMutator { MutatableCode mutatableCode = mutation.mutatableCode; MInsn newArrayInsn = newArrayLengthInsns.get(mutation.newArrayToChangeIdx); int newArrayInsnIdx = mutatableCode.getInstructionIndex(newArrayInsn); + // If the original new-array instruction is no longer present + // in the code (as indicated by a negative index), we make a + // best effort to find any other new-array instruction to + // apply the mutation to. If that effort fails, we simply + // bail by doing nothing. + if (newArrayInsnIdx < 0) { + newArrayInsnIdx = scanNewArray(mutatableCode); + if (newArrayInsnIdx == -1) { + return; + } + } MInsn newInsn = new MInsn(); newInsn.insn = new Instruction(); newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.CONST_16); + mutatableCode.allocateTemporaryVRegs(1); + newArrayInsn.insn.vregB = mutatableCode.getTemporaryVReg(0); newInsn.insn.vregA = (int) newArrayInsn.insn.vregB; // New length chosen randomly between 1 to 100. newInsn.insn.vregB = rng.nextInt(100); mutatableCode.insertInstructionAt(newInsn, newArrayInsnIdx); Log.info("Changed the length of the array to " + newInsn.insn.vregB); stats.incrementStat("Changed length of new array"); + mutatableCode.finishedUsingTemporaryVRegs(); } private boolean isNewArray(MInsn mInsn) { Opcode opcode = mInsn.insn.info.opcode; return opcode == Opcode.NEW_ARRAY; } + + // Return the index of first new-array in the method, -1 otherwise. + private int scanNewArray(MutatableCode mutatableCode) { + int idx = 0; + for (MInsn mInsn : mutatableCode.getInstructions()) { + if (isNewArray(mInsn)) { + return idx; + } + idx++; + } + return -1; + } }
\ No newline at end of file diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java b/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java index 11da1d4d39..90f4f0f089 100644 --- a/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java +++ b/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java @@ -61,7 +61,7 @@ public class RegisterClobber extends CodeMutator{ public RegisterClobber(Random rng, MutationStats stats, List<Mutation> mutations) { super(rng, stats, mutations); - likelihood = 40; + likelihood = 10; } @Override @@ -90,6 +90,7 @@ public class RegisterClobber extends CodeMutator{ newInsn.insn = new Instruction(); newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.CONST_16); newInsn.insn.vregA = i; + // Used zero because it may also apply to objects, resulting in fewer verification failures. newInsn.insn.vregB = 0; mutatableCode.insertInstructionAt(newInsn, mutation.regClobberIdx + i); } diff --git a/tools/generate_cmake_lists.py b/tools/generate_cmake_lists.py new file mode 100755 index 0000000000..6c3ce085ee --- /dev/null +++ b/tools/generate_cmake_lists.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# 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. + + +""" +./generate_cmake_lists.py --project-name <project-name> --arch <arch> + +- project-name - name of the new project +- arch - arch type. make generates seperate CMakeLists files for + each architecture. To avoid collision in targets, only one of + them can be included in the super project. + +The primary objective of this file is to generate CMakeLists files +for CLion setup. + +Steps to setup CLion. +1) Open the generated CMakeList file in CLion as a project. +2) Change the project root ANDROID_BUILD_TOP. +(Also, exclude projects that you don't bother about. This will make +the indexing faster). +""" + +import sys +import os +import subprocess +import argparse + +def get_android_build_top(): + path_to_top = os.environ.get('ANDROID_BUILD_TOP') + if not path_to_top: + # nothing set. try to guess it based on the relative path of this env.py file. + this_file_path = os.path.realpath(__file__) + path_to_top = os.path.join(os.path.dirname(this_file_path), '../..') + path_to_top = os.path.realpath(path_to_top) + + if not os.path.exists(os.path.join(path_to_top, 'build/envsetup.sh')): + print path_to_top + raise AssertionError("geneate_cmake_lists.py must be located inside an android source tree") + + return path_to_top + +def main(): + # Parse arguments + parser = argparse.ArgumentParser(description="Generate CMakeLists files for ART") + parser.add_argument('--project-name', dest="project_name", required=True, + help='name of the project') + parser.add_argument('--arch', dest="arch", required=True, help='arch') + args = parser.parse_args() + project_name = args.project_name + arch = args.arch + + # Invoke make to generate CMakeFiles + os.environ['SOONG_GEN_CMAKEFILES']='1' + os.environ['SOONG_GEN_CMAKEFILES_DEBUG']='1' + + ANDROID_BUILD_TOP = get_android_build_top() + + subprocess.check_output(('make -j64 -C %s') % (ANDROID_BUILD_TOP), shell=True) + + out_art_cmakelists_dir = os.path.join(ANDROID_BUILD_TOP, + 'out/development/ide/clion/art') + + # Prepare a list of directories containing generated CMakeLists files for sub projects. + cmake_sub_dirs = set() + for root, dirs, files in os.walk(out_art_cmakelists_dir): + for name in files: + if name == 'CMakeLists.txt': + if (os.path.samefile(root, out_art_cmakelists_dir)): + continue + if arch not in root: + continue + cmake_sub_dir = cmake_sub_dirs.add(root.replace(out_art_cmakelists_dir, + '.')) + + # Generate CMakeLists file. + f = open(os.path.join(out_art_cmakelists_dir, 'CMakeLists.txt'), 'w') + f.write('cmake_minimum_required(VERSION 3.6)\n') + f.write('project(%s)\n' % (project_name)) + + for dr in cmake_sub_dirs: + f.write('add_subdirectory(%s)\n' % (dr)) + + +if __name__ == '__main__': + main() diff --git a/tools/jfuzz/jfuzz.cc b/tools/jfuzz/jfuzz.cc index 016d708565..7990c6cf8e 100644 --- a/tools/jfuzz/jfuzz.cc +++ b/tools/jfuzz/jfuzz.cc @@ -55,7 +55,7 @@ static constexpr const char* kRelOps[] = { "==", "!=", ">", ">=", "<", "<=" * to preserve the property that a given version of JFuzz yields the same * fuzzed program for a deterministic random seed. */ -const char* VERSION = "1.3"; +const char* VERSION = "1.4"; /* * Maximum number of array dimensions, together with corresponding maximum size @@ -804,13 +804,6 @@ class JFuzz { return emitAssignment(); // fall back } - // TODO: remove this - // The jack bug b/28862040 prevents generating while/do-while loops because otherwise - // we get dozens of reports on the same issue per nightly/ run. - if (true) { - return emitAssignment(); - } - bool isWhile = random1(2) == 1; fputs("{\n", out_); indentation_ += 2; diff --git a/tools/jfuzz/run_jfuzz_test.py b/tools/jfuzz/run_jfuzz_test.py index 7e72aa1d92..58bc7374d3 100755 --- a/tools/jfuzz/run_jfuzz_test.py +++ b/tools/jfuzz/run_jfuzz_test.py @@ -524,7 +524,9 @@ class JFuzzTester(object): jfuzz_args = ['\'-{0}\''.format(arg) for arg in jfuzz_cmd_str.strip().split(' -')][1:] wrapped_args = ['--jfuzz_arg={0}'.format(opt) for opt in jfuzz_args] - repro_cmd_str = (os.path.basename(__file__) + ' --num_tests 1 ' + + repro_cmd_str = (os.path.basename(__file__) + + ' --num_tests=1 ' + + ('--use_dx ' if self._use_dx else '') + ' '.join(wrapped_args)) comment = 'jfuzz {0}\nReproduce test:\n{1}\nReproduce divergence:\n{2}\n'.format( jfuzz_ver, jfuzz_cmd_str, repro_cmd_str) |