Merge "Add a conditional boolean to ScopedAssertNoThreadSuspension."
diff --git a/Android.bp b/Android.bp
index d0e22fb..0ce7916 100644
--- a/Android.bp
+++ b/Android.bp
@@ -31,10 +31,13 @@
"disassembler",
"imgdiag",
"oatdump",
+ "openjdkjvm",
+ "openjdkjvmti",
"patchoat",
"profman",
"runtime",
"sigchainlib",
+ "simulator",
"test",
"tools/cpp-define-generator",
"tools/dmtracedump",
diff --git a/build/Android.cpplint.mk b/build/Android.cpplint.mk
index 66ac897..2688c04 100644
--- a/build/Android.cpplint.mk
+++ b/build/Android.cpplint.mk
@@ -22,7 +22,7 @@
ART_CPPLINT_QUIET := --quiet
ART_CPPLINT_INGORED := \
runtime/elf.h \
- runtime/openjdkjvmti/include/jvmti.h
+ openjdkjvmti/include/jvmti.h
# This:
# 1) Gets a list of all .h & .cc files in the art directory.
diff --git a/compiler/Android.bp b/compiler/Android.bp
index b721d21..f11d256 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -423,8 +423,11 @@
},
},
+ 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 7b56f3e..1ef3ba7 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 @@
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 e657e3b..cfb56e3 100644
--- a/compiler/dex/verification_results.cc
+++ b/compiler/dex/verification_results.cc
@@ -46,10 +46,6 @@
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 @@
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 ed36e11..6e087c5 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -303,7 +303,6 @@
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 @@
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 @@
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);
@@ -1933,14 +1935,12 @@
// time. So instead we assume these classes still need to be verified at
// runtime.
for (const DexFile* dex_file : dex_files) {
- // Fetch the list of unverified classes and turn it into a set for faster
- // lookups.
- const std::vector<dex::TypeIndex>& unverified_classes =
+ // Fetch the list of unverified classes.
+ const std::set<dex::TypeIndex>& unverified_classes =
verifier_deps->GetUnverifiedClasses(*dex_file);
- std::set<dex::TypeIndex> set(unverified_classes.begin(), unverified_classes.end());
for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) {
const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
- if (set.find(class_def.class_idx_) == set.end()) {
+ if (unverified_classes.find(class_def.class_idx_) == unverified_classes.end()) {
if (compiler_only_verifies) {
// Just update the compiled_classes_ map. The compiler doesn't need to resolve
// the type.
@@ -1980,13 +1980,6 @@
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 +1989,16 @@
// 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 +2023,7 @@
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 +2327,7 @@
// 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 +2354,7 @@
*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 +2447,8 @@
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 +2457,7 @@
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 +2694,14 @@
: 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 +2720,7 @@
{
// 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) {
@@ -2876,9 +2879,9 @@
bool CompilerDriver::GetCompiledClass(ClassReference ref, mirror::Class::Status* status) const {
DCHECK(status != nullptr);
// The table doesn't know if something wasn't inserted. For this case it will return
- // kStatusNotReady. To handle this, just assume anything not verified is not compiled.
+ // kStatusNotReady. To handle this, just assume anything we didn't try to verify is not compiled.
if (!compiled_classes_.Get(DexFileReference(ref.first, ref.second), status) ||
- *status < mirror::Class::kStatusVerified) {
+ *status < mirror::Class::kStatusRetryVerificationAtRuntime) {
return false;
}
return true;
@@ -2910,7 +2913,7 @@
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 +3031,13 @@
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 ecaed83..d9886a2 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -103,15 +103,11 @@
~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 @@
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 10bfd97..4da3e0d 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -42,7 +42,9 @@
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 @@
JNIEnv* env_;
jclass class_;
jmethodID mid_;
+ std::vector<const DexFile*> dex_files_;
};
// Disabled due to 10 second runtime on host
@@ -363,6 +366,40 @@
CheckVerifiedClass(class_loader, "LSecond;");
}
+// Test that a class of status kStatusRetryVerificationAtRuntime is indeed recorded that way in the
+// driver.
+TEST_F(CompilerDriverVerifyTest, RetryVerifcationStatus) {
+ Thread* const self = Thread::Current();
+ jobject class_loader;
+ std::vector<const DexFile*> dex_files;
+ const DexFile* dex_file = nullptr;
+ {
+ ScopedObjectAccess soa(self);
+ class_loader = LoadDex("ProfileTestMultiDex");
+ ASSERT_NE(class_loader, nullptr);
+ dex_files = GetDexFiles(class_loader);
+ ASSERT_GT(dex_files.size(), 0u);
+ dex_file = dex_files.front();
+ }
+ compiler_driver_->SetDexFilesForOatFile(dex_files);
+ ClassReference ref(dex_file, 0u);
+ // Test that the status is read from the compiler driver as expected.
+ for (size_t i = mirror::Class::kStatusRetryVerificationAtRuntime;
+ i < mirror::Class::kStatusMax;
+ ++i) {
+ const mirror::Class::Status expected_status = static_cast<mirror::Class::Status>(i);
+ // Skip unsupported status that are not supposed to be ever recorded.
+ if (expected_status == mirror::Class::kStatusVerifyingAtRuntime ||
+ expected_status == mirror::Class::kStatusInitializing) {
+ continue;
+ }
+ compiler_driver_->RecordClassStatus(ref, expected_status);
+ mirror::Class::Status status = {};
+ ASSERT_TRUE(compiler_driver_->GetCompiledClass(ref, &status));
+ EXPECT_EQ(status, expected_status);
+ }
+}
+
// TODO: need check-cast test (when stub complete & we can throw/catch
} // namespace art
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 76f0ae9..3cacc2c 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -144,6 +144,8 @@
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_test.h b/compiler/image_test.h
index 57d0987..daa4b11 100644
--- a/compiler/image_test.h
+++ b/compiler/image_test.h
@@ -214,7 +214,8 @@
/*compile_app_image*/false,
storage_mode,
oat_filename_vector,
- dex_file_to_oat_index_map));
+ dex_file_to_oat_index_map,
+ /*dirty_image_objects*/nullptr));
{
{
jobject class_loader = nullptr;
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 318009c..9e4971c 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -579,7 +579,12 @@
}
}
- if (klass->GetStatus() == Class::kStatusInitialized) {
+ // Move known dirty objects into their own sections. This includes:
+ // - classes with dirty static fields.
+ if (dirty_image_objects_ != nullptr &&
+ dirty_image_objects_->find(klass->PrettyDescriptor()) != dirty_image_objects_->end()) {
+ bin = kBinKnownDirty;
+ } else if (klass->GetStatus() == Class::kStatusInitialized) {
bin = kBinClassInitialized;
// If the class's static fields are all final, put it into a separate bin
@@ -770,18 +775,18 @@
*result_ = true;
}
- // Record the object visited in case of circular reference.
- visited_->emplace(ref);
if (ref->IsClass()) {
*result_ = *result_ ||
image_writer_->PruneAppImageClassInternal(ref->AsClass(), early_exit_, visited_);
} else {
+ // Record the object visited in case of circular reference.
+ visited_->emplace(ref);
*result_ = *result_ ||
image_writer_->PruneAppImageClassInternal(klass, early_exit_, visited_);
ref->VisitReferences(*this, *this);
+ // Clean up before exit for next call of this function.
+ visited_->erase(ref);
}
- // Clean up before exit for next call of this function.
- visited_->erase(ref);
}
ALWAYS_INLINE void operator() (ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED,
@@ -894,7 +899,7 @@
&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 +1158,22 @@
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;
@@ -2761,7 +2779,8 @@
bool compile_app_image,
ImageHeader::StorageMode image_storage_mode,
const std::vector<const char*>& oat_filenames,
- const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map)
+ const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map,
+ const std::unordered_set<std::string>* dirty_image_objects)
: compiler_driver_(compiler_driver),
global_image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
image_objects_offset_begin_(0),
@@ -2773,7 +2792,8 @@
clean_methods_(0u),
image_storage_mode_(image_storage_mode),
oat_filenames_(oat_filenames),
- dex_file_oat_index_map_(dex_file_oat_index_map) {
+ dex_file_oat_index_map_(dex_file_oat_index_map),
+ dirty_image_objects_(dirty_image_objects) {
CHECK_NE(image_begin, 0U);
std::fill_n(image_methods_, arraysize(image_methods_), nullptr);
CHECK_EQ(compile_app_image, !Runtime::Current()->GetHeap()->GetBootImageSpaces().empty())
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 34bbbad..866e204 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -75,7 +75,8 @@
bool compile_app_image,
ImageHeader::StorageMode image_storage_mode,
const std::vector<const char*>& oat_filenames,
- const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map);
+ const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map,
+ const std::unordered_set<std::string>* dirty_image_objects);
bool PrepareImageAddressSpace();
@@ -159,6 +160,7 @@
// Classify different kinds of bins that objects end up getting packed into during image writing.
// Ordered from dirtiest to cleanest (until ArtMethods).
enum Bin {
+ kBinKnownDirty, // Known dirty objects from --dirty-image-objects list
kBinMiscDirty, // Dex caches, object locks, etc...
kBinClassVerified, // Class verified, but initializers haven't been run
// Unknown mix of clean/dirty:
@@ -599,6 +601,9 @@
// Map of dex files to the indexes of oat files that they were compiled into.
const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map_;
+ // Set of objects known to be dirty in the image. Can be nullptr if there are none.
+ const std::unordered_set<std::string>* dirty_image_objects_;
+
class ComputeLazyFieldsForClassesVisitor;
class FixupClassVisitor;
class FixupRootVisitor;
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 4d258af..d7e3a28 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -1282,9 +1282,12 @@
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/code_generator_vector_arm64.cc b/compiler/optimizing/code_generator_vector_arm64.cc
index f422b9f..9095ecd 100644
--- a/compiler/optimizing/code_generator_vector_arm64.cc
+++ b/compiler/optimizing/code_generator_vector_arm64.cc
@@ -15,7 +15,9 @@
*/
#include "code_generator_arm64.h"
+
#include "mirror/array-inl.h"
+#include "mirror/string.h"
using namespace vixl::aarch64; // NOLINT(build/namespaces)
diff --git a/compiler/optimizing/code_generator_vector_x86.cc b/compiler/optimizing/code_generator_vector_x86.cc
index 14782d7..e7aec76 100644
--- a/compiler/optimizing/code_generator_vector_x86.cc
+++ b/compiler/optimizing/code_generator_vector_x86.cc
@@ -15,7 +15,9 @@
*/
#include "code_generator_x86.h"
+
#include "mirror/array-inl.h"
+#include "mirror/string.h"
namespace art {
namespace x86 {
diff --git a/compiler/optimizing/code_generator_vector_x86_64.cc b/compiler/optimizing/code_generator_vector_x86_64.cc
index 246044e..c7ee81c 100644
--- a/compiler/optimizing/code_generator_vector_x86_64.cc
+++ b/compiler/optimizing/code_generator_vector_x86_64.cc
@@ -15,7 +15,9 @@
*/
#include "code_generator_x86_64.h"
+
#include "mirror/array-inl.h"
+#include "mirror/string.h"
namespace art {
namespace x86_64 {
diff --git a/compiler/optimizing/codegen_test_utils.h b/compiler/optimizing/codegen_test_utils.h
index 1b38acd..cada2e6 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 38a1a8c..0141c26 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -1948,7 +1948,7 @@
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 @@
++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 @@
// 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 62c6713..c4b3a32 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -207,7 +207,7 @@
// 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/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc
index 4cea6df..2669d97 100644
--- a/compiler/optimizing/intrinsics_mips.cc
+++ b/compiler/optimizing/intrinsics_mips.cc
@@ -22,6 +22,7 @@
#include "entrypoints/quick/quick_entrypoints.h"
#include "intrinsics.h"
#include "mirror/array-inl.h"
+#include "mirror/object_array-inl.h"
#include "mirror/string.h"
#include "scoped_thread_state_change-inl.h"
#include "thread.h"
diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc
index d785567..74be954 100644
--- a/compiler/optimizing/intrinsics_mips64.cc
+++ b/compiler/optimizing/intrinsics_mips64.cc
@@ -22,6 +22,7 @@
#include "entrypoints/quick/quick_entrypoints.h"
#include "intrinsics.h"
#include "mirror/array-inl.h"
+#include "mirror/object_array-inl.h"
#include "mirror/string.h"
#include "scoped_thread_state_change-inl.h"
#include "thread.h"
diff --git a/compiler/optimizing/load_store_analysis.h b/compiler/optimizing/load_store_analysis.h
index 86fb8e0..a2c1794 100644
--- a/compiler/optimizing/load_store_analysis.h
+++ b/compiler/optimizing/load_store_analysis.h
@@ -466,6 +466,11 @@
CreateReferenceInfoForReferenceType(new_instance);
}
+ void VisitNewArray(HNewArray* new_array) OVERRIDE {
+ // Any references appearing in the ref_info_array_ so far cannot alias with new_array.
+ CreateReferenceInfoForReferenceType(new_array);
+ }
+
void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* instruction) OVERRIDE {
CreateReferenceInfoForReferenceType(instruction);
}
@@ -478,6 +483,22 @@
CreateReferenceInfoForReferenceType(instruction);
}
+ void VisitInvokeUnresolved(HInvokeUnresolved* instruction) OVERRIDE {
+ CreateReferenceInfoForReferenceType(instruction);
+ }
+
+ void VisitInvokePolymorphic(HInvokePolymorphic* instruction) OVERRIDE {
+ CreateReferenceInfoForReferenceType(instruction);
+ }
+
+ void VisitLoadString(HLoadString* instruction) OVERRIDE {
+ CreateReferenceInfoForReferenceType(instruction);
+ }
+
+ void VisitPhi(HPhi* instruction) OVERRIDE {
+ CreateReferenceInfoForReferenceType(instruction);
+ }
+
void VisitParameterValue(HParameterValue* instruction) OVERRIDE {
CreateReferenceInfoForReferenceType(instruction);
}
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index 274f065..0ef7dcd 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -40,6 +40,8 @@
instruction->RemoveAsUserOfAllInputs();
instruction->RemoveEnvironmentUsers();
instruction->GetBlock()->RemoveInstructionOrPhi(instruction, /*ensure_safety=*/ false);
+ RemoveEnvironmentUses(instruction);
+ ResetEnvironmentInputRecords(instruction);
}
// Detect a goto block and sets succ to the single successor.
@@ -267,6 +269,21 @@
return instruction;
}
+// Check that instructions from the induction sets are fully removed: have no uses
+// and no other instructions use them.
+static bool CheckInductionSetFullyRemoved(ArenaSet<HInstruction*>* iset) {
+ for (HInstruction* instr : *iset) {
+ if (instr->GetBlock() != nullptr ||
+ !instr->GetUses().empty() ||
+ !instr->GetEnvUses().empty() ||
+ HasEnvironmentUsedByOthers(instr)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
//
// Class methods.
//
@@ -448,6 +465,9 @@
for (HInstruction* i : *iset_) {
RemoveFromCycle(i);
}
+
+ // Check that there are no records of the deleted instructions.
+ DCHECK(CheckInductionSetFullyRemoved(iset_));
simplified_ = true;
}
}
diff --git a/compiler/optimizing/loop_optimization_test.cc b/compiler/optimizing/loop_optimization_test.cc
index 5b93506..b5b03d8 100644
--- a/compiler/optimizing/loop_optimization_test.cc
+++ b/compiler/optimizing/loop_optimization_test.cc
@@ -195,4 +195,44 @@
EXPECT_EQ("[[[[[[[[[[][][][][][][][][][]]]]]]]]]]", LoopStructure());
}
+// Check that SimplifyLoop() doesn't invalidate data flow when ordering loop headers'
+// predecessors.
+TEST_F(LoopOptimizationTest, SimplifyLoop) {
+ // Can't use AddLoop as we want special order for blocks predecessors.
+ HBasicBlock* header = new (&allocator_) HBasicBlock(graph_);
+ HBasicBlock* body = new (&allocator_) HBasicBlock(graph_);
+ graph_->AddBlock(header);
+ graph_->AddBlock(body);
+
+ // Control flow: make a loop back edge first in the list of predecessors.
+ entry_block_->RemoveSuccessor(return_block_);
+ body->AddSuccessor(header);
+ entry_block_->AddSuccessor(header);
+ header->AddSuccessor(body);
+ header->AddSuccessor(return_block_);
+ DCHECK(header->GetSuccessors()[1] == return_block_);
+
+ // Data flow.
+ header->AddInstruction(new (&allocator_) HIf(parameter_));
+ body->AddInstruction(new (&allocator_) HGoto());
+
+ HPhi* phi = new (&allocator_) HPhi(&allocator_, 0, 0, Primitive::kPrimInt);
+ HInstruction* add = new (&allocator_) HAdd(Primitive::kPrimInt, phi, parameter_);
+ header->AddPhi(phi);
+ body->AddInstruction(add);
+
+ phi->AddInput(add);
+ phi->AddInput(parameter_);
+
+ graph_->ClearLoopInformation();
+ graph_->ClearDominanceInformation();
+ graph_->BuildDominatorTree();
+
+ // Check that after optimizations in BuildDominatorTree()/SimplifyCFG() phi inputs
+ // are still mapped correctly to the block predecessors.
+ for (size_t i = 0, e = phi->InputCount(); i < e; i++) {
+ HInstruction* input = phi->InputAt(i);
+ ASSERT_TRUE(input->GetBlock()->Dominates(header->GetPredecessors()[i]));
+ }
+}
} // namespace art
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index ca48e08..ddd798b 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -90,7 +90,8 @@
}
}
-static void RemoveEnvironmentUses(HInstruction* instruction) {
+// Remove the environment use records of the instruction for users.
+void RemoveEnvironmentUses(HInstruction* instruction) {
for (HEnvironment* environment = instruction->GetEnvironment();
environment != nullptr;
environment = environment->GetParent()) {
@@ -102,6 +103,35 @@
}
}
+// Return whether the instruction has an environment and it's used by others.
+bool HasEnvironmentUsedByOthers(HInstruction* instruction) {
+ for (HEnvironment* environment = instruction->GetEnvironment();
+ environment != nullptr;
+ environment = environment->GetParent()) {
+ for (size_t i = 0, e = environment->Size(); i < e; ++i) {
+ HInstruction* user = environment->GetInstructionAt(i);
+ if (user != nullptr) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Reset environment records of the instruction itself.
+void ResetEnvironmentInputRecords(HInstruction* instruction) {
+ for (HEnvironment* environment = instruction->GetEnvironment();
+ environment != nullptr;
+ environment = environment->GetParent()) {
+ for (size_t i = 0, e = environment->Size(); i < e; ++i) {
+ DCHECK(environment->GetHolder() == instruction);
+ if (environment->GetInstructionAt(i) != nullptr) {
+ environment->SetRawEnvAt(i, nullptr);
+ }
+ }
+ }
+}
+
static void RemoveAsUser(HInstruction* instruction) {
instruction->RemoveAsUserOfAllInputs();
RemoveEnvironmentUses(instruction);
@@ -328,6 +358,35 @@
}
}
+// Reorder phi inputs to match reordering of the block's predecessors.
+static void FixPhisAfterPredecessorsReodering(HBasicBlock* block, size_t first, size_t second) {
+ for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
+ HPhi* phi = it.Current()->AsPhi();
+ HInstruction* first_instr = phi->InputAt(first);
+ HInstruction* second_instr = phi->InputAt(second);
+ phi->ReplaceInput(first_instr, second);
+ phi->ReplaceInput(second_instr, first);
+ }
+}
+
+// Make sure that the first predecessor of a loop header is the incoming block.
+void HGraph::OrderLoopHeaderPredecessors(HBasicBlock* header) {
+ DCHECK(header->IsLoopHeader());
+ HLoopInformation* info = header->GetLoopInformation();
+ if (info->IsBackEdge(*header->GetPredecessors()[0])) {
+ HBasicBlock* to_swap = header->GetPredecessors()[0];
+ for (size_t pred = 1, e = header->GetPredecessors().size(); pred < e; ++pred) {
+ HBasicBlock* predecessor = header->GetPredecessors()[pred];
+ if (!info->IsBackEdge(*predecessor)) {
+ header->predecessors_[pred] = to_swap;
+ header->predecessors_[0] = predecessor;
+ FixPhisAfterPredecessorsReodering(header, 0, pred);
+ break;
+ }
+ }
+ }
+}
+
void HGraph::SimplifyLoop(HBasicBlock* header) {
HLoopInformation* info = header->GetLoopInformation();
@@ -351,18 +410,7 @@
pre_header->AddSuccessor(header);
}
- // Make sure the first predecessor of a loop header is the incoming block.
- if (info->IsBackEdge(*header->GetPredecessors()[0])) {
- HBasicBlock* to_swap = header->GetPredecessors()[0];
- for (size_t pred = 1, e = header->GetPredecessors().size(); pred < e; ++pred) {
- HBasicBlock* predecessor = header->GetPredecessors()[pred];
- if (!info->IsBackEdge(*predecessor)) {
- header->predecessors_[pred] = to_swap;
- header->predecessors_[0] = predecessor;
- break;
- }
- }
- }
+ OrderLoopHeaderPredecessors(header);
HInstruction* first_instruction = header->GetFirstInstruction();
if (first_instruction != nullptr && first_instruction->IsSuspendCheck()) {
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index fa29378..488d472 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -418,6 +418,7 @@
HBasicBlock* SplitEdge(HBasicBlock* block, HBasicBlock* successor);
void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor);
+ void OrderLoopHeaderPredecessors(HBasicBlock* header);
void SimplifyLoop(HBasicBlock* header);
int32_t GetNextInstructionId() {
@@ -7059,6 +7060,10 @@
return instruction;
}
+void RemoveEnvironmentUses(HInstruction* instruction);
+bool HasEnvironmentUsedByOthers(HInstruction* instruction);
+void ResetEnvironmentInputRecords(HInstruction* instruction);
+
} // namespace art
#endif // ART_COMPILER_OPTIMIZING_NODES_H_
diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc
index aa42fd6..7c6b69f 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -208,8 +208,8 @@
void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) {
if (invoke->IsStaticWithExplicitClinitCheck()) {
- HLoadClass* last_input = invoke->GetInputs().back()->AsLoadClass();
- DCHECK(last_input != nullptr)
+ HInstruction* last_input = invoke->GetInputs().back();
+ DCHECK(last_input->IsLoadClass())
<< "Last input is not HLoadClass. It is " << last_input->DebugName();
// Detach the explicit class initialization check from the invoke.
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index f172e16..561c9ea 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -848,7 +848,7 @@
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 215e967..b19f473 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -46,7 +46,7 @@
// 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/optimizing/scheduler_arm.cc b/compiler/optimizing/scheduler_arm.cc
index ea15790..d6eb6e3 100644
--- a/compiler/optimizing/scheduler_arm.cc
+++ b/compiler/optimizing/scheduler_arm.cc
@@ -20,6 +20,7 @@
#include "code_generator_utils.h"
#include "common_arm.h"
#include "mirror/array-inl.h"
+#include "mirror/string.h"
namespace art {
namespace arm {
diff --git a/compiler/optimizing/scheduler_arm64.cc b/compiler/optimizing/scheduler_arm64.cc
index f54d3f3..510619f 100644
--- a/compiler/optimizing/scheduler_arm64.cc
+++ b/compiler/optimizing/scheduler_arm64.cc
@@ -18,6 +18,7 @@
#include "code_generator_utils.h"
#include "mirror/array-inl.h"
+#include "mirror/string.h"
namespace art {
namespace arm64 {
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 72e2a6c..6538925 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -87,13 +87,13 @@
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 @@
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_) {
@@ -228,8 +229,7 @@
hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_)));
MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr));
for (const DexFile* dex_file : dex_files_) {
- const std::vector<dex::TypeIndex>& unverified_classes = deps.GetUnverifiedClasses(*dex_file);
- std::set<dex::TypeIndex> set(unverified_classes.begin(), unverified_classes.end());
+ const std::set<dex::TypeIndex>& unverified_classes = deps.GetUnverifiedClasses(*dex_file);
for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) {
const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
const char* descriptor = dex_file->GetClassDescriptor(class_def);
@@ -237,7 +237,7 @@
if (cls == nullptr) {
CHECK(soa.Self()->IsExceptionPending());
soa.Self()->ClearException();
- } else if (set.find(class_def.class_idx_) == set.end()) {
+ } else if (unverified_classes.find(class_def.class_idx_) == unverified_classes.end()) {
ASSERT_EQ(cls->GetStatus(), mirror::Class::kStatusVerified);
} else {
ASSERT_LT(cls->GetStatus(), mirror::Class::kStatusVerified);
@@ -1144,6 +1144,39 @@
ASSERT_TRUE(HasUnverifiedClass("LMyClassWithNoSuperButFailures;"));
}
+TEST_F(VerifierDepsTest, UnverifiedOrder) {
+ ScopedObjectAccess soa(Thread::Current());
+ jobject loader = LoadDex("VerifierDeps");
+ std::vector<const DexFile*> dex_files = GetDexFiles(loader);
+ ASSERT_GT(dex_files.size(), 0u);
+ const DexFile* dex_file = dex_files[0];
+ VerifierDeps deps1(dex_files);
+ Thread* const self = Thread::Current();
+ ASSERT_TRUE(self->GetVerifierDeps() == nullptr);
+ self->SetVerifierDeps(&deps1);
+ deps1.MaybeRecordVerificationStatus(*dex_file,
+ dex::TypeIndex(0),
+ verifier::FailureKind::kHardFailure);
+ deps1.MaybeRecordVerificationStatus(*dex_file,
+ dex::TypeIndex(1),
+ verifier::FailureKind::kHardFailure);
+ VerifierDeps deps2(dex_files);
+ self->SetVerifierDeps(nullptr);
+ self->SetVerifierDeps(&deps2);
+ deps2.MaybeRecordVerificationStatus(*dex_file,
+ dex::TypeIndex(1),
+ verifier::FailureKind::kHardFailure);
+ deps2.MaybeRecordVerificationStatus(*dex_file,
+ dex::TypeIndex(0),
+ verifier::FailureKind::kHardFailure);
+ self->SetVerifierDeps(nullptr);
+ std::vector<uint8_t> buffer1;
+ deps1.Encode(dex_files, &buffer1);
+ std::vector<uint8_t> buffer2;
+ deps2.Encode(dex_files, &buffer2);
+ EXPECT_EQ(buffer1, buffer2);
+}
+
TEST_F(VerifierDepsTest, VerifyDeps) {
VerifyDexFile();
@@ -1441,7 +1474,6 @@
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/dex2oat.cc b/dex2oat/dex2oat.cc
index 3cc41a6..ff193e9 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"
@@ -353,6 +355,9 @@
UsageError("");
UsageError(" --debuggable: Produce code debuggable with Java debugger.");
UsageError("");
+ UsageError(" --avoid-storing-invocation: Avoid storing the invocation args in the key value");
+ UsageError(" store. Used to test determinism with different args.");
+ UsageError("");
UsageError(" --runtime-arg <argument>: used to specify various arguments for the runtime,");
UsageError(" such as initial heap size, maximum heap size, and verbose output.");
UsageError(" Use a separate --runtime-arg switch for each argument.");
@@ -406,17 +411,17 @@
UsageError("");
UsageError(" --class-loader-context=<string spec>: a string specifying the intended");
UsageError(" runtime loading context for the compiled dex files.");
- UsageError(" ");
+ UsageError("");
UsageError(" It describes how the class loader chain should be built in order to ensure");
UsageError(" classes are resolved during dex2aot as they would be resolved at runtime.");
UsageError(" This spec will be encoded in the oat file. If at runtime the dex file is");
UsageError(" loaded in a different context, the oat file will be rejected.");
- UsageError(" ");
+ UsageError("");
UsageError(" The chain is interpreted in the natural 'parent order', meaning that class");
UsageError(" loader 'i+1' will be the parent of class loader 'i'.");
UsageError(" The compilation sources will be appended to the classpath of the first class");
UsageError(" loader.");
- UsageError(" ");
+ UsageError("");
UsageError(" E.g. if the context is 'PCL[lib1.dex];DLC[lib2.dex]' and ");
UsageError(" --dex-file=src.dex then dex2oat will setup a PathClassLoader with classpath ");
UsageError(" 'lib1.dex:src.dex' and set its parent to a DelegateLastClassLoader with ");
@@ -426,9 +431,12 @@
UsageError(" with --dex-file are found in the classpath. The source dex files will be");
UsageError(" removed from any class loader's classpath possibly resulting in empty");
UsageError(" class loaders.");
- UsageError(" ");
+ UsageError("");
UsageError(" Example: --class-loader-context=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
UsageError("");
+ UsageError(" --dirty-image-objects=<directory-path>: list of known dirty objects in the image.");
+ UsageError(" The image writer will group them together.");
+ UsageError("");
std::cerr << "See log for usage error information\n";
exit(EXIT_FAILURE);
}
@@ -591,9 +599,9 @@
compiled_methods_zip_filename_(nullptr),
compiled_methods_filename_(nullptr),
passes_to_run_filename_(nullptr),
+ dirty_image_objects_filename_(nullptr),
multi_image_(false),
is_host_(false),
- class_loader_(nullptr),
elf_writers_(),
oat_writers_(),
rodata_(),
@@ -606,6 +614,7 @@
dump_passes_(false),
dump_timing_(false),
dump_slow_timing_(kIsDebugBuild),
+ avoid_storing_invocation_(false),
swap_fd_(kInvalidFd),
app_image_fd_(kInvalidFd),
profile_file_fd_(kInvalidFd),
@@ -1128,14 +1137,16 @@
void InsertCompileOptions(int argc, char** argv) {
std::ostringstream oss;
- for (int i = 0; i < argc; ++i) {
- if (i > 0) {
- oss << ' ';
+ if (!avoid_storing_invocation_) {
+ for (int i = 0; i < argc; ++i) {
+ if (i > 0) {
+ oss << ' ';
+ }
+ oss << argv[i];
}
- oss << argv[i];
+ key_value_store_->Put(OatHeader::kDex2OatCmdLineKey, oss.str());
+ oss.str(""); // Reset.
}
- key_value_store_->Put(OatHeader::kDex2OatCmdLineKey, oss.str());
- oss.str(""); // Reset.
oss << kRuntimeISA;
key_value_store_->Put(OatHeader::kDex2OatHostKey, oss.str());
key_value_store_->Put(
@@ -1266,6 +1277,8 @@
dump_passes_ = true;
} else if (option == "--dump-stats") {
dump_stats_ = true;
+ } else if (option == "--avoid-storing-invocation") {
+ avoid_storing_invocation_ = true;
} else if (option.starts_with("--swap-file=")) {
swap_file_name_ = option.substr(strlen("--swap-file=")).data();
} else if (option.starts_with("--swap-fd=")) {
@@ -1303,9 +1316,11 @@
} else if (option.starts_with("--class-loader-context=")) {
class_loader_context_ = ClassLoaderContext::Create(
option.substr(strlen("--class-loader-context=")).data());
- if (class_loader_context_== nullptr) {
+ if (class_loader_context_ == nullptr) {
Usage("Option --class-loader-context has an incorrect format: %s", option.data());
}
+ } else if (option.starts_with("--dirty-image-objects=")) {
+ dirty_image_objects_filename_ = option.substr(strlen("--dirty-image-objects=")).data();
} else if (!compiler_options_->ParseCompilerOption(option, Usage)) {
Usage("Unknown argument %s", option.data());
}
@@ -1484,14 +1499,6 @@
}
}
- 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();
@@ -1515,7 +1522,8 @@
dex2oat::ReturnCode Setup() {
TimingLogger::ScopedTiming t("dex2oat Setup", timings_);
- if (!PrepareImageClasses() || !PrepareCompiledClasses() || !PrepareCompiledMethods()) {
+ if (!PrepareImageClasses() || !PrepareCompiledClasses() || !PrepareCompiledMethods() ||
+ !PrepareDirtyObjects()) {
return dex2oat::ReturnCode::kOther;
}
@@ -1576,20 +1584,12 @@
}
// Open dex files for class path.
+
if (class_loader_context_ == nullptr) {
- // TODO(calin): Temporary workaround while we transition to use
- // --class-loader-context instead of --runtime-arg -cp
- if (runtime_->GetClassPathString().empty()) {
- class_loader_context_ = std::unique_ptr<ClassLoaderContext>(
- new ClassLoaderContext());
- } else {
- std::string spec = runtime_->GetClassPathString() == OatFile::kSpecialSharedLibrary
- ? OatFile::kSpecialSharedLibrary
- : "PCL[" + runtime_->GetClassPathString() + "]";
- class_loader_context_ = ClassLoaderContext::Create(spec);
- }
+ // If no context was specified use the default one (which is an empty PathClassLoader).
+ class_loader_context_ = std::unique_ptr<ClassLoaderContext>(ClassLoaderContext::Default());
}
- CHECK(class_loader_context_ != nullptr);
+
DCHECK_EQ(oat_writers_.size(), 1u);
// Note: Ideally we would reject context where the source dex files are also
@@ -1660,6 +1660,8 @@
// 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 +1714,11 @@
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 +1729,12 @@
}
}
- // 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 +1747,50 @@
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 +1801,6 @@
}
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 +1866,46 @@
// 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_));
}
- driver_->CompileAll(class_loader_, dex_files_, timings_);
+ // Invoke the compilation.
+ if (ShouldCompileDexFilesIndividually()) {
+ CompileDexFilesIndividually();
+ // Return a null classloader since we already freed released it.
+ return nullptr;
+ }
+ 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
@@ -1947,7 +2009,8 @@
IsAppImage(),
image_storage_mode_,
oat_filenames_,
- dex_file_oat_index_map_));
+ dex_file_oat_index_map_,
+ dirty_image_objects_.get()));
// We need to prepare method offsets in the image address space for direct method patching.
TimingLogger::ScopedTiming t2("dex2oat Prepare image address space", timings_);
@@ -2373,6 +2436,22 @@
return true;
}
+ bool PrepareDirtyObjects() {
+ if (dirty_image_objects_filename_ != nullptr) {
+ dirty_image_objects_.reset(ReadCommentedInputFromFile<std::unordered_set<std::string>>(
+ dirty_image_objects_filename_,
+ nullptr));
+ if (dirty_image_objects_ == nullptr) {
+ LOG(ERROR) << "Failed to create list of dirty objects from '"
+ << dirty_image_objects_filename_ << "'";
+ return false;
+ }
+ } else {
+ dirty_image_objects_.reset(nullptr);
+ }
+ return true;
+ }
+
void PruneNonExistentDexFiles() {
DCHECK_EQ(dex_filenames_.size(), dex_locations_.size());
size_t kept = 0u;
@@ -2790,9 +2869,11 @@
const char* compiled_methods_zip_filename_;
const char* compiled_methods_filename_;
const char* passes_to_run_filename_;
+ const char* dirty_image_objects_filename_;
std::unique_ptr<std::unordered_set<std::string>> image_classes_;
std::unique_ptr<std::unordered_set<std::string>> compiled_classes_;
std::unique_ptr<std::unordered_set<std::string>> compiled_methods_;
+ std::unique_ptr<std::unordered_set<std::string>> dirty_image_objects_;
std::unique_ptr<std::vector<std::string>> passes_to_run_;
bool multi_image_;
bool is_host_;
@@ -2800,8 +2881,6 @@
// 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_;
@@ -2820,6 +2899,7 @@
bool dump_passes_;
bool dump_timing_;
bool dump_slow_timing_;
+ bool avoid_storing_invocation_;
std::string swap_file_name_;
int swap_fd_;
size_t min_dex_files_for_swap_ = kDefaultMinDexFilesForSwap;
@@ -2870,9 +2950,23 @@
#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 +3014,7 @@
}
static dex2oat::ReturnCode CompileApp(Dex2Oat& dex2oat) {
- dex2oat.Compile();
+ ScopedGlobalRef class_loader(dex2oat.Compile());
if (!dex2oat.WriteOutputFiles()) {
dex2oat.EraseOutputFiles();
@@ -3014,7 +3108,6 @@
result = CompileApp(*dex2oat);
}
- dex2oat->Shutdown();
return result;
}
} // namespace art
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index 95fb16d..46c5f58 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -340,6 +340,15 @@
// EXPECT_GE(profile_sizes.oat_size / kRatio, compiled_methods_sizes.oat_size);
EXPECT_GE(profile_sizes.vdex_size / kRatio, compiled_methods_sizes.vdex_size);
}
+ // Test dirty image objects.
+ {
+ ScratchFile classes;
+ GenerateClasses(classes.GetFile(), /*frequency*/ 1u);
+ image_classes_sizes = CompileImageAndGetSizes(
+ {"--dirty-image-objects=" + classes.GetFilename()});
+ classes.Close();
+ std::cout << "Dirty image object sizes " << image_classes_sizes << std::endl;
+ }
}
} // namespace art
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 32877a8..12bceb3 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -134,7 +134,7 @@
}
}
- // Check the input compiler filter against the generated oat file's filter. Mayb be overridden
+ // Check the input compiler filter against the generated oat file's filter. May be overridden
// in subclasses when equality is not expected.
virtual void CheckFilter(CompilerFilter::Filter expected, CompilerFilter::Filter actual) {
EXPECT_EQ(expected, actual);
@@ -153,14 +153,7 @@
std::vector<std::string> argv;
argv.push_back(runtime->GetCompilerExecutable());
- argv.push_back("--runtime-arg");
- argv.push_back("-classpath");
- argv.push_back("--runtime-arg");
- std::string class_path = runtime->GetClassPathString();
- if (class_path == "") {
- class_path = OatFile::kSpecialSharedLibrary;
- }
- argv.push_back(class_path);
+
if (runtime->IsJavaDebuggable()) {
argv.push_back("--debuggable");
}
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index fb8e894..9ffc414 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -333,9 +333,11 @@
std::vector<uint8_t>* remote_contents,
std::vector<uint8_t>* zygote_contents,
const backtrace_map_t& boot_map,
- const ImageHeader& image_header) :
- RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header),
- os_(*os) { }
+ const ImageHeader& image_header,
+ bool dump_dirty_objects)
+ : RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header),
+ os_(*os),
+ dump_dirty_objects_(dump_dirty_objects) { }
void CheckEntrySanity(const uint8_t* current) const
REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -396,7 +398,10 @@
class_data_[klass].AddDirtyObject(entry, entry_remote);
}
- void DiffEntryContents(mirror::Object* entry, uint8_t* remote_bytes, const uint8_t* base_ptr)
+ void DiffEntryContents(mirror::Object* entry,
+ uint8_t* remote_bytes,
+ const uint8_t* base_ptr,
+ bool log_dirty_objects)
REQUIRES_SHARED(Locks::mutator_lock_) {
const char* tabs = " ";
// Attempt to find fields for all dirty bytes.
@@ -453,6 +458,9 @@
}
}
if (!dirty_static_fields.empty()) {
+ if (dump_dirty_objects_ && log_dirty_objects) {
+ dirty_objects_.insert(entry);
+ }
os_ << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n";
for (ArtField* field : dirty_static_fields) {
os_ << tabs << ArtField::PrettyField(field)
@@ -463,6 +471,14 @@
os_ << "\n";
}
+ void DumpDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_) {
+ for (mirror::Object* obj : dirty_objects_) {
+ if (obj->IsClass()) {
+ os_ << "Private dirty object: " << obj->AsClass()->PrettyDescriptor() << "\n";
+ }
+ }
+ }
+
void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
// vector of pairs (size_t count, Class*)
auto dirty_object_class_values =
@@ -592,6 +608,8 @@
};
std::ostream& os_;
+ bool dump_dirty_objects_;
+ std::unordered_set<mirror::Object*> dirty_objects_;
std::map<mirror::Class*, ClassData> class_data_;
DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase);
@@ -720,9 +738,15 @@
std::vector<uint8_t>* remote_contents,
std::vector<uint8_t>* zygote_contents,
const backtrace_map_t& boot_map,
- const ImageHeader& image_header) :
- RegionSpecializedBase<T>(os, remote_contents, zygote_contents, boot_map, image_header),
- os_(*os) {
+ const ImageHeader& image_header,
+ bool dump_dirty_objects)
+ : RegionSpecializedBase<T>(os,
+ remote_contents,
+ zygote_contents,
+ boot_map,
+ image_header,
+ dump_dirty_objects),
+ os_(*os) {
CHECK(remote_contents != nullptr);
CHECK(zygote_contents != nullptr);
}
@@ -773,7 +797,8 @@
DiffDirtyEntries(ProcessType::kRemote,
begin_image_ptr,
RegionCommon<T>::remote_contents_,
- base_ptr);
+ base_ptr,
+ /*log_dirty_objects*/true);
// Print shared dirty after since it's less important.
if (RegionCommon<T>::GetZygoteDirtyEntryCount() != 0) {
// We only reach this point if both pids were specified. Furthermore,
@@ -784,8 +809,10 @@
DiffDirtyEntries(ProcessType::kZygote,
begin_image_ptr,
RegionCommon<T>::zygote_contents_,
- begin_image_ptr);
+ begin_image_ptr,
+ /*log_dirty_objects*/false);
}
+ RegionSpecializedBase<T>::DumpDirtyObjects();
RegionSpecializedBase<T>::DumpDirtyEntries();
RegionSpecializedBase<T>::DumpFalseDirtyEntries();
RegionSpecializedBase<T>::DumpCleanEntries();
@@ -797,7 +824,8 @@
void DiffDirtyEntries(ProcessType process_type,
const uint8_t* begin_image_ptr,
std::vector<uint8_t>* contents,
- const uint8_t* base_ptr)
+ const uint8_t* base_ptr,
+ bool log_dirty_objects)
REQUIRES_SHARED(Locks::mutator_lock_) {
os_ << RegionCommon<T>::dirty_entries_.size() << "\n";
const std::set<T*>& entries =
@@ -808,7 +836,10 @@
uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry);
ptrdiff_t offset = entry_bytes - begin_image_ptr;
uint8_t* remote_bytes = &(*contents)[offset];
- RegionSpecializedBase<T>::DiffEntryContents(entry, remote_bytes, &base_ptr[offset]);
+ RegionSpecializedBase<T>::DiffEntryContents(entry,
+ remote_bytes,
+ &base_ptr[offset],
+ log_dirty_objects);
}
}
@@ -872,12 +903,14 @@
const ImageHeader& image_header,
const std::string& image_location,
pid_t image_diff_pid,
- pid_t zygote_diff_pid)
+ pid_t zygote_diff_pid,
+ bool dump_dirty_objects)
: os_(os),
image_header_(image_header),
image_location_(image_location),
image_diff_pid_(image_diff_pid),
zygote_diff_pid_(zygote_diff_pid),
+ dump_dirty_objects_(dump_dirty_objects),
zygote_pid_only_(false) {}
bool Init() {
@@ -1075,6 +1108,8 @@
}
}
+ 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 +1162,12 @@
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 +1179,19 @@
<< 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;
}
@@ -1187,7 +1240,8 @@
&remote_contents_,
&zygote_contents_,
boot_map_,
- image_header_);
+ image_header_,
+ dump_dirty_objects_);
RemoteProcesses remotes;
if (zygote_pid_only_) {
@@ -1344,6 +1398,7 @@
const std::string image_location_;
pid_t image_diff_pid_; // Dump image diff against boot.art if pid is non-negative
pid_t zygote_diff_pid_; // Dump image diff against zygote boot.art if pid is non-negative
+ bool dump_dirty_objects_; // Adds dumping of objects that are dirty.
bool zygote_pid_only_; // The user only specified a pid for the zygote.
// BacktraceMap used for finding the memory mapping of the image file.
@@ -1371,7 +1426,8 @@
static int DumpImage(Runtime* runtime,
std::ostream* os,
pid_t image_diff_pid,
- pid_t zygote_diff_pid) {
+ pid_t zygote_diff_pid,
+ bool dump_dirty_objects) {
ScopedObjectAccess soa(Thread::Current());
gc::Heap* heap = runtime->GetHeap();
std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces();
@@ -1387,7 +1443,8 @@
image_header,
image_space->GetImageLocation(),
image_diff_pid,
- zygote_diff_pid);
+ zygote_diff_pid,
+ dump_dirty_objects);
if (!img_diag_dumper.Init()) {
return EXIT_FAILURE;
}
@@ -1425,6 +1482,8 @@
*error_msg = "Zygote diff pid out of range";
return kParseError;
}
+ } else if (option == "--dump-dirty-objects") {
+ dump_dirty_objects_ = true;
} else {
return kParseUnknownArgument;
}
@@ -1477,6 +1536,7 @@
" --zygote-diff-pid=<pid>: provide the PID of the zygote whose boot.art you want to diff "
"against.\n"
" Example: --zygote-diff-pid=$(pid zygote)\n"
+ " --dump-dirty-objects: additionally output dirty objects of interest.\n"
"\n";
return usage;
@@ -1485,6 +1545,7 @@
public:
pid_t image_diff_pid_ = -1;
pid_t zygote_diff_pid_ = -1;
+ bool dump_dirty_objects_ = false;
};
struct ImgDiagMain : public CmdlineMain<ImgDiagArgs> {
@@ -1494,7 +1555,8 @@
return DumpImage(runtime,
args_->os_,
args_->image_diff_pid_,
- args_->zygote_diff_pid_) == EXIT_SUCCESS;
+ args_->zygote_diff_pid_,
+ args_->dump_dirty_objects_) == EXIT_SUCCESS;
}
};
diff --git a/runtime/openjdkjvm/Android.bp b/openjdkjvm/Android.bp
similarity index 96%
rename from runtime/openjdkjvm/Android.bp
rename to openjdkjvm/Android.bp
index 37112b6..071b434 100644
--- a/runtime/openjdkjvm/Android.bp
+++ b/openjdkjvm/Android.bp
@@ -18,7 +18,6 @@
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
similarity index 100%
rename from runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
rename to openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/runtime/openjdkjvm/NOTICE b/openjdkjvm/NOTICE
similarity index 100%
rename from runtime/openjdkjvm/NOTICE
rename to openjdkjvm/NOTICE
diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
similarity index 100%
rename from runtime/openjdkjvm/OpenjdkJvm.cc
rename to openjdkjvm/OpenjdkJvm.cc
diff --git a/runtime/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
similarity index 97%
rename from runtime/openjdkjvmti/Android.bp
rename to openjdkjvmti/Android.bp
index aec1bd0..b6b1b56 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -48,7 +48,6 @@
"ti_threadgroup.cc",
"ti_timers.cc",
"transform.cc"],
- include_dirs: ["art/runtime"],
header_libs: ["libopenjdkjvmti_headers"],
shared_libs: [
"libbase",
diff --git a/runtime/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
similarity index 100%
rename from runtime/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
rename to openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/runtime/openjdkjvmti/NOTICE b/openjdkjvmti/NOTICE
similarity index 100%
rename from runtime/openjdkjvmti/NOTICE
rename to openjdkjvmti/NOTICE
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
similarity index 95%
rename from runtime/openjdkjvmti/OpenjdkJvmTi.cc
rename to openjdkjvmti/OpenjdkJvmTi.cc
index 3c1311b..af77072 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -503,112 +503,112 @@
}
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 @@
}
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/README.md b/openjdkjvmti/README.md
similarity index 100%
rename from runtime/openjdkjvmti/README.md
rename to openjdkjvmti/README.md
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
similarity index 96%
rename from runtime/openjdkjvmti/art_jvmti.h
rename to openjdkjvmti/art_jvmti.h
index 4d5bb95..12f4cab 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
-#define ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
+#ifndef ART_OPENJDKJVMTI_ART_JVMTI_H_
+#define ART_OPENJDKJVMTI_ART_JVMTI_H_
#include <memory>
#include <type_traits>
@@ -204,6 +204,10 @@
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 @@
.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,
@@ -258,4 +262,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
+#endif // ART_OPENJDKJVMTI_ART_JVMTI_H_
diff --git a/runtime/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
similarity index 97%
rename from runtime/openjdkjvmti/events-inl.h
rename to openjdkjvmti/events-inl.h
index 43177ab..32dba3e 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
-#define ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
+#ifndef ART_OPENJDKJVMTI_EVENTS_INL_H_
+#define ART_OPENJDKJVMTI_EVENTS_INL_H_
#include <array>
@@ -414,9 +414,10 @@
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,9 +429,12 @@
RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable);
RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
}
+ if (added && caps.can_access_local_variables == 1) {
+ HandleLocalAccessCapabilityAdded();
+ }
}
}
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
+#endif // ART_OPENJDKJVMTI_EVENTS_INL_H_
diff --git a/runtime/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
similarity index 96%
rename from runtime/openjdkjvmti/events.cc
rename to openjdkjvmti/events.cc
index 7a930d4..2944a45 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -610,6 +610,21 @@
}
}
+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/openjdkjvmti/events.h
similarity index 98%
rename from runtime/openjdkjvmti/events.h
rename to openjdkjvmti/events.h
index 5f37dcf..3d05fa1 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_
-#define ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_
+#ifndef ART_OPENJDKJVMTI_EVENTS_H_
+#define ART_OPENJDKJVMTI_EVENTS_H_
#include <bitset>
#include <vector>
@@ -210,6 +210,7 @@
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
@@ -226,4 +227,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_
+#endif // ART_OPENJDKJVMTI_EVENTS_H_
diff --git a/runtime/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc
similarity index 100%
rename from runtime/openjdkjvmti/fixed_up_dex_file.cc
rename to openjdkjvmti/fixed_up_dex_file.cc
diff --git a/runtime/openjdkjvmti/fixed_up_dex_file.h b/openjdkjvmti/fixed_up_dex_file.h
similarity index 94%
rename from runtime/openjdkjvmti/fixed_up_dex_file.h
rename to openjdkjvmti/fixed_up_dex_file.h
index a96ee12..4cb39cf 100644
--- a/runtime/openjdkjvmti/fixed_up_dex_file.h
+++ b/openjdkjvmti/fixed_up_dex_file.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#ifndef ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#define ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
#include <memory>
#include <vector>
@@ -80,4 +80,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#endif // ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
diff --git a/runtime/openjdkjvmti/include/jvmti.h b/openjdkjvmti/include/jvmti.h
similarity index 100%
rename from runtime/openjdkjvmti/include/jvmti.h
rename to openjdkjvmti/include/jvmti.h
diff --git a/runtime/openjdkjvmti/jvmti_allocator.h b/openjdkjvmti/jvmti_allocator.h
similarity index 96%
rename from runtime/openjdkjvmti/jvmti_allocator.h
rename to openjdkjvmti/jvmti_allocator.h
index 44b1cb1..e29e034 100644
--- a/runtime/openjdkjvmti/jvmti_allocator.h
+++ b/openjdkjvmti/jvmti_allocator.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
-#define ART_RUNTIME_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#ifndef ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
#include "base/logging.h"
#include "base/macros.h"
@@ -171,4 +171,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#endif // ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
diff --git a/runtime/openjdkjvmti/jvmti_weak_table-inl.h b/openjdkjvmti/jvmti_weak_table-inl.h
similarity index 98%
rename from runtime/openjdkjvmti/jvmti_weak_table-inl.h
rename to openjdkjvmti/jvmti_weak_table-inl.h
index a640acb..1c82255 100644
--- a/runtime/openjdkjvmti/jvmti_weak_table-inl.h
+++ b/openjdkjvmti/jvmti_weak_table-inl.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
-#define ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#ifndef ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
#include "jvmti_weak_table.h"
@@ -403,4 +403,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#endif // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
diff --git a/runtime/openjdkjvmti/jvmti_weak_table.h b/openjdkjvmti/jvmti_weak_table.h
similarity index 97%
rename from runtime/openjdkjvmti/jvmti_weak_table.h
rename to openjdkjvmti/jvmti_weak_table.h
index a5175a4..5a821c9 100644
--- a/runtime/openjdkjvmti/jvmti_weak_table.h
+++ b/openjdkjvmti/jvmti_weak_table.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#ifndef ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
#include <unordered_map>
@@ -224,4 +224,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#endif // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
diff --git a/runtime/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc
similarity index 100%
rename from runtime/openjdkjvmti/object_tagging.cc
rename to openjdkjvmti/object_tagging.cc
diff --git a/runtime/openjdkjvmti/object_tagging.h b/openjdkjvmti/object_tagging.h
similarity index 94%
rename from runtime/openjdkjvmti/object_tagging.h
rename to openjdkjvmti/object_tagging.h
index ca84e44..b474845 100644
--- a/runtime/openjdkjvmti/object_tagging.h
+++ b/openjdkjvmti/object_tagging.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_OBJECT_TAGGING_H_
-#define ART_RUNTIME_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#ifndef ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#define ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
#include <unordered_map>
@@ -83,4 +83,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#endif // ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
diff --git a/runtime/openjdkjvmti/ti_allocator.cc b/openjdkjvmti/ti_allocator.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_allocator.cc
rename to openjdkjvmti/ti_allocator.cc
diff --git a/runtime/openjdkjvmti/ti_allocator.h b/openjdkjvmti/ti_allocator.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_allocator.h
rename to openjdkjvmti/ti_allocator.h
index 35575c3..776cc5e 100644
--- a/runtime/openjdkjvmti/ti_allocator.h
+++ b/openjdkjvmti/ti_allocator.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#ifndef ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
#include "jni.h"
#include "jvmti.h"
@@ -61,5 +61,5 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#endif // ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
diff --git a/runtime/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_breakpoint.cc
rename to openjdkjvmti/ti_breakpoint.cc
diff --git a/runtime/openjdkjvmti/ti_breakpoint.h b/openjdkjvmti/ti_breakpoint.h
similarity index 94%
rename from runtime/openjdkjvmti/ti_breakpoint.h
rename to openjdkjvmti/ti_breakpoint.h
index c3dbef7..9b08b42 100644
--- a/runtime/openjdkjvmti/ti_breakpoint.h
+++ b/openjdkjvmti/ti_breakpoint.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#ifndef ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#define ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
#include "jni.h"
#include "jvmti.h"
@@ -91,4 +91,4 @@
};
} // namespace std
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#endif // ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
diff --git a/runtime/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_class.cc
rename to openjdkjvmti/ti_class.cc
diff --git a/runtime/openjdkjvmti/ti_class.h b/openjdkjvmti/ti_class.h
similarity index 96%
rename from runtime/openjdkjvmti/ti_class.h
rename to openjdkjvmti/ti_class.h
index 7bb6b3e..dd99e36 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/openjdkjvmti/ti_class.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_H_
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_H_
#include "jni.h"
#include "jvmti.h"
@@ -92,4 +92,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_H_
+#endif // ART_OPENJDKJVMTI_TI_CLASS_H_
diff --git a/runtime/openjdkjvmti/ti_class_definition.cc b/openjdkjvmti/ti_class_definition.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_class_definition.cc
rename to openjdkjvmti/ti_class_definition.cc
diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_class_definition.h
rename to openjdkjvmti/ti_class_definition.h
index 2c268dd..accc456 100644
--- a/runtime/openjdkjvmti/ti_class_definition.h
+++ b/openjdkjvmti/ti_class_definition.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
#include "art_jvmti.h"
@@ -128,4 +128,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#endif // ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_class_loader.cc
rename to openjdkjvmti/ti_class_loader.cc
diff --git a/runtime/openjdkjvmti/ti_class_loader.h b/openjdkjvmti/ti_class_loader.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_class_loader.h
rename to openjdkjvmti/ti_class_loader.h
index af66c5f..767e258 100644
--- a/runtime/openjdkjvmti/ti_class_loader.h
+++ b/openjdkjvmti/ti_class_loader.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
#include <string>
@@ -96,4 +96,4 @@
};
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#endif // ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
diff --git a/runtime/openjdkjvmti/ti_dump.cc b/openjdkjvmti/ti_dump.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_dump.cc
rename to openjdkjvmti/ti_dump.cc
diff --git a/runtime/openjdkjvmti/ti_dump.h b/openjdkjvmti/ti_dump.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_dump.h
rename to openjdkjvmti/ti_dump.h
index 67cb239..323bf56 100644
--- a/runtime/openjdkjvmti/ti_dump.h
+++ b/openjdkjvmti/ti_dump.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
+#ifndef ART_OPENJDKJVMTI_TI_DUMP_H_
+#define ART_OPENJDKJVMTI_TI_DUMP_H_
#include "jni.h"
#include "jvmti.h"
@@ -47,4 +47,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
+#endif // ART_OPENJDKJVMTI_TI_DUMP_H_
diff --git a/runtime/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_field.cc
rename to openjdkjvmti/ti_field.cc
diff --git a/runtime/openjdkjvmti/ti_field.h b/openjdkjvmti/ti_field.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_field.h
rename to openjdkjvmti/ti_field.h
index 880949e..8a229ed 100644
--- a/runtime/openjdkjvmti/ti_field.h
+++ b/openjdkjvmti/ti_field.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
+#ifndef ART_OPENJDKJVMTI_TI_FIELD_H_
+#define ART_OPENJDKJVMTI_TI_FIELD_H_
#include "jni.h"
#include "jvmti.h"
@@ -69,4 +69,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
+#endif // ART_OPENJDKJVMTI_TI_FIELD_H_
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_heap.cc
rename to openjdkjvmti/ti_heap.cc
diff --git a/runtime/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
similarity index 94%
rename from runtime/openjdkjvmti/ti_heap.h
rename to openjdkjvmti/ti_heap.h
index 0c973db..62761b5 100644
--- a/runtime/openjdkjvmti/ti_heap.h
+++ b/openjdkjvmti/ti_heap.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_HEAP_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_HEAP_H_
+#ifndef ART_OPENJDKJVMTI_TI_HEAP_H_
+#define ART_OPENJDKJVMTI_TI_HEAP_H_
#include "jvmti.h"
@@ -70,4 +70,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_HEAP_H_
+#endif // ART_OPENJDKJVMTI_TI_HEAP_H_
diff --git a/runtime/openjdkjvmti/ti_jni.cc b/openjdkjvmti/ti_jni.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_jni.cc
rename to openjdkjvmti/ti_jni.cc
diff --git a/runtime/openjdkjvmti/ti_jni.h b/openjdkjvmti/ti_jni.h
similarity index 94%
rename from runtime/openjdkjvmti/ti_jni.h
rename to openjdkjvmti/ti_jni.h
index 906aab0..590fd54 100644
--- a/runtime/openjdkjvmti/ti_jni.h
+++ b/openjdkjvmti/ti_jni.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
+#ifndef ART_OPENJDKJVMTI_TI_JNI_H_
+#define ART_OPENJDKJVMTI_TI_JNI_H_
#include "jni.h"
#include "jvmti.h"
@@ -55,4 +55,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
+#endif // ART_OPENJDKJVMTI_TI_JNI_H_
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
new file mode 100644
index 0000000..8f72714
--- /dev/null
+++ b/openjdkjvmti/ti_method.cc
@@ -0,0 +1,1071 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_method.h"
+
+#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 {
+
+struct TiMethodCallback : public art::MethodCallback {
+ void RegisterNativeMethod(art::ArtMethod* method,
+ const void* cur_method,
+ /*out*/void** new_method)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
+ art::Thread* thread = art::Thread::Current();
+ art::JNIEnvExt* jnienv = thread->GetJniEnv();
+ ScopedLocalRef<jthread> thread_jni(
+ jnienv, PhaseUtil::IsLivePhase() ? jnienv->AddLocalReference<jthread>(thread->GetPeer())
+ : nullptr);
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
+ thread,
+ static_cast<JNIEnv*>(jnienv),
+ thread_jni.get(),
+ art::jni::EncodeArtMethod(method),
+ const_cast<void*>(cur_method),
+ new_method);
+ }
+ }
+
+ EventHandler* event_handler = nullptr;
+};
+
+TiMethodCallback gMethodCallback;
+
+void MethodUtil::Register(EventHandler* handler) {
+ gMethodCallback.event_handler = handler;
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add method callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
+}
+
+void MethodUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove method callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
+}
+
+jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
+ jmethodID method,
+ jint* size_ptr,
+ unsigned char** bytecode_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 (size_ptr == nullptr || bytecode_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+ if (code_item == nullptr) {
+ *size_ptr = 0;
+ *bytecode_ptr = nullptr;
+ return OK;
+ }
+ // 2 bytes per instruction for dex code.
+ *size_ptr = code_item->insns_size_in_code_units_ * 2;
+ jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr);
+ if (err != OK) {
+ return err;
+ }
+ memcpy(*bytecode_ptr, code_item->insns_, *size_ptr);
+ return OK;
+}
+
+jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jint* size_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 (size_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+ // Use the shorty.
+ art::ArtMethod* base_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+ size_t arg_count = art::ArtMethod::NumArgRegisters(base_method->GetShorty());
+ if (!base_method->IsStatic()) {
+ arg_count++;
+ }
+ *size_ptr = static_cast<jint>(arg_count);
+ return ERR(NONE);
+ }
+
+ DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+ *size_ptr = art_method->GetCodeItem()->ins_size_;
+
+ 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) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+
+ if (max_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+ // This isn't specified as an error case, so return 0.
+ *max_ptr = 0;
+ return ERR(NONE);
+ }
+
+ DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+ *max_ptr = art_method->GetCodeItem()->registers_size_;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodName(jvmtiEnv* env,
+ jmethodID method,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+
+ JvmtiUniquePtr<char[]> name_copy;
+ if (name_ptr != nullptr) {
+ const char* method_name = art_method->GetName();
+ if (method_name == nullptr) {
+ method_name = "<error>";
+ }
+ jvmtiError ret;
+ name_copy = CopyString(env, method_name, &ret);
+ if (name_copy == nullptr) {
+ return ret;
+ }
+ *name_ptr = name_copy.get();
+ }
+
+ JvmtiUniquePtr<char[]> signature_copy;
+ if (signature_ptr != nullptr) {
+ const art::Signature sig = art_method->GetSignature();
+ std::string str = sig.ToString();
+ jvmtiError ret;
+ signature_copy = CopyString(env, str.c_str(), &ret);
+ if (signature_copy == nullptr) {
+ return ret;
+ }
+ *signature_ptr = signature_copy.get();
+ }
+
+ if (generic_ptr != nullptr) {
+ *generic_ptr = nullptr;
+ if (!art_method->GetDeclaringClass()->IsProxyClass()) {
+ art::mirror::ObjectArray<art::mirror::String>* str_array =
+ art::annotations::GetSignatureAnnotationForMethod(art_method);
+ if (str_array != nullptr) {
+ std::ostringstream oss;
+ for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+ oss << str_array->Get(i)->ToModifiedUtf8();
+ }
+ std::string output_string = oss.str();
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
+ if (generic_copy == nullptr) {
+ return ret;
+ }
+ *generic_ptr = generic_copy.release();
+ } else if (soa.Self()->IsExceptionPending()) {
+ // TODO: Should we report an error here?
+ soa.Self()->ClearException();
+ }
+ }
+ }
+
+ // Everything is fine, release the buffers.
+ name_copy.release();
+ signature_copy.release();
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jclass* declaring_class_ptr) {
+ if (declaring_class_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ // Note: No GetInterfaceMethodIfProxy, we want to actual class.
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::mirror::Class* klass = art_method->GetDeclaringClass();
+ *declaring_class_ptr = soa.AddLocalReference<jclass>(klass);
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jlocation* start_location_ptr,
+ jlocation* end_location_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 (start_location_ptr == nullptr || end_location_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+ // This isn't specified as an error case, so return -1/-1 as the RI does.
+ *start_location_ptr = -1;
+ *end_location_ptr = -1;
+ return ERR(NONE);
+ }
+
+ DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+ *start_location_ptr = 0;
+ *end_location_ptr = art_method->GetCodeItem()->insns_size_in_code_units_ - 1;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jint* modifiers_ptr) {
+ if (modifiers_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ uint32_t modifiers = art_method->GetAccessFlags();
+
+ // Note: Keep this code in sync with Executable.fixMethodFlags.
+ if ((modifiers & art::kAccAbstract) != 0) {
+ modifiers &= ~art::kAccNative;
+ }
+ modifiers &= ~art::kAccSynchronized;
+ if ((modifiers & art::kAccDeclaredSynchronized) != 0) {
+ modifiers |= art::kAccSynchronized;
+ }
+ modifiers &= art::kAccJavaFlagsMask;
+
+ *modifiers_ptr = modifiers;
+ return ERR(NONE);
+}
+
+using LineNumberContext = std::vector<jvmtiLineNumberEntry>;
+
+static bool CollectLineNumbers(void* void_context, const art::DexFile::PositionInfo& entry) {
+ LineNumberContext* context = reinterpret_cast<LineNumberContext*>(void_context);
+ jvmtiLineNumberEntry jvmti_entry = { static_cast<jlocation>(entry.address_),
+ static_cast<jint>(entry.line_) };
+ context->push_back(jvmti_entry);
+ return false; // Collect all, no early exit.
+}
+
+jvmtiError MethodUtil::GetLineNumberTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLineNumberEntry** table_ptr) {
+ if (method == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ DCHECK(!art_method->IsRuntimeMethod());
+
+ const art::DexFile::CodeItem* code_item;
+ const art::DexFile* dex_file;
+ {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+
+ if (art_method->IsProxyMethod()) {
+ return ERR(ABSENT_INFORMATION);
+ }
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+ if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ code_item = art_method->GetCodeItem();
+ dex_file = art_method->GetDexFile();
+ DCHECK(code_item != nullptr) << art_method->PrettyMethod() << " " << dex_file->GetLocation();
+ }
+
+ LineNumberContext context;
+ bool success = dex_file->DecodeDebugPositionInfo(code_item, CollectLineNumbers, &context);
+ if (!success) {
+ return ERR(ABSENT_INFORMATION);
+ }
+
+ unsigned char* data;
+ jlong mem_size = context.size() * sizeof(jvmtiLineNumberEntry);
+ jvmtiError alloc_error = env->Allocate(mem_size, &data);
+ if (alloc_error != ERR(NONE)) {
+ return alloc_error;
+ }
+ *table_ptr = reinterpret_cast<jvmtiLineNumberEntry*>(data);
+ memcpy(*table_ptr, context.data(), mem_size);
+ *entry_count_ptr = static_cast<jint>(context.size());
+
+ return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError IsMethodT(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ T test,
+ jboolean* is_t_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ if (is_t_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ *is_t_ptr = test(art_method) ? JNI_TRUE : JNI_FALSE;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) {
+ auto test = [](art::ArtMethod* method) {
+ return method->IsNative();
+ };
+ return IsMethodT(env, m, test, is_native_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) {
+ auto test = [](art::ArtMethod* method) {
+ return method->IsObsolete();
+ };
+ return IsMethodT(env, m, test, is_obsolete_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) {
+ auto test = [](art::ArtMethod* method) {
+ return method->IsSynthetic();
+ };
+ 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/openjdkjvmti/ti_method.h
similarity index 70%
rename from runtime/openjdkjvmti/ti_method.h
rename to openjdkjvmti/ti_method.h
index d95a81b..e3578a4 100644
--- a/runtime/openjdkjvmti/ti_method.h
+++ b/openjdkjvmti/ti_method.h
@@ -29,11 +29,12 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_METHOD_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_METHOD_H_
+#ifndef ART_OPENJDKJVMTI_TI_METHOD_H_
+#define ART_OPENJDKJVMTI_TI_METHOD_H_
#include "jni.h"
#include "jvmti.h"
+#include "primitive.h"
namespace openjdkjvmti {
@@ -80,8 +81,34 @@
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
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_METHOD_H_
+#endif // ART_OPENJDKJVMTI_TI_METHOD_H_
diff --git a/runtime/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_monitor.cc
rename to openjdkjvmti/ti_monitor.cc
diff --git a/runtime/openjdkjvmti/ti_monitor.h b/openjdkjvmti/ti_monitor.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_monitor.h
rename to openjdkjvmti/ti_monitor.h
index 96ccb0d..add089c 100644
--- a/runtime/openjdkjvmti/ti_monitor.h
+++ b/openjdkjvmti/ti_monitor.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
+#ifndef ART_OPENJDKJVMTI_TI_MONITOR_H_
+#define ART_OPENJDKJVMTI_TI_MONITOR_H_
#include "jni.h"
#include "jvmti.h"
@@ -56,4 +56,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
+#endif // ART_OPENJDKJVMTI_TI_MONITOR_H_
diff --git a/runtime/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_object.cc
rename to openjdkjvmti/ti_object.cc
diff --git a/runtime/openjdkjvmti/ti_object.h b/openjdkjvmti/ti_object.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_object.h
rename to openjdkjvmti/ti_object.h
index 09eee61..fa3bd0f 100644
--- a/runtime/openjdkjvmti/ti_object.h
+++ b/openjdkjvmti/ti_object.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
+#ifndef ART_OPENJDKJVMTI_TI_OBJECT_H_
+#define ART_OPENJDKJVMTI_TI_OBJECT_H_
#include "jni.h"
#include "jvmti.h"
@@ -46,4 +46,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
+#endif // ART_OPENJDKJVMTI_TI_OBJECT_H_
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/openjdkjvmti/ti_phase.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_phase.cc
rename to openjdkjvmti/ti_phase.cc
diff --git a/runtime/openjdkjvmti/ti_phase.h b/openjdkjvmti/ti_phase.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_phase.h
rename to openjdkjvmti/ti_phase.h
index a2c0d11..d4ed86b 100644
--- a/runtime/openjdkjvmti/ti_phase.h
+++ b/openjdkjvmti/ti_phase.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+#ifndef ART_OPENJDKJVMTI_TI_PHASE_H_
+#define ART_OPENJDKJVMTI_TI_PHASE_H_
#include "jni.h"
#include "jvmti.h"
@@ -66,4 +66,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+#endif // ART_OPENJDKJVMTI_TI_PHASE_H_
diff --git a/runtime/openjdkjvmti/ti_properties.cc b/openjdkjvmti/ti_properties.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_properties.cc
rename to openjdkjvmti/ti_properties.cc
diff --git a/runtime/openjdkjvmti/ti_properties.h b/openjdkjvmti/ti_properties.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_properties.h
rename to openjdkjvmti/ti_properties.h
index 7073481..187b85d 100644
--- a/runtime/openjdkjvmti/ti_properties.h
+++ b/openjdkjvmti/ti_properties.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
+#ifndef ART_OPENJDKJVMTI_TI_PROPERTIES_H_
+#define ART_OPENJDKJVMTI_TI_PROPERTIES_H_
#include "jni.h"
#include "jvmti.h"
@@ -48,4 +48,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
+#endif // ART_OPENJDKJVMTI_TI_PROPERTIES_H_
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_redefine.cc
rename to openjdkjvmti/ti_redefine.cc
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
similarity index 98%
rename from runtime/openjdkjvmti/ti_redefine.h
rename to openjdkjvmti/ti_redefine.h
index 03b4bf2..984f922 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_REDEFINE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_REDEFINE_H_
+#ifndef ART_OPENJDKJVMTI_TI_REDEFINE_H_
+#define ART_OPENJDKJVMTI_TI_REDEFINE_H_
#include <string>
@@ -265,4 +265,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_REDEFINE_H_
+#endif // ART_OPENJDKJVMTI_TI_REDEFINE_H_
diff --git a/runtime/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_search.cc
rename to openjdkjvmti/ti_search.cc
diff --git a/runtime/openjdkjvmti/ti_search.h b/openjdkjvmti/ti_search.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_search.h
rename to openjdkjvmti/ti_search.h
index cd7b4be..81a28cc 100644
--- a/runtime/openjdkjvmti/ti_search.h
+++ b/openjdkjvmti/ti_search.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
+#ifndef ART_OPENJDKJVMTI_TI_SEARCH_H_
+#define ART_OPENJDKJVMTI_TI_SEARCH_H_
#include <vector>
@@ -50,4 +50,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
+#endif // ART_OPENJDKJVMTI_TI_SEARCH_H_
diff --git a/runtime/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_stack.cc
rename to openjdkjvmti/ti_stack.cc
diff --git a/runtime/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_stack.h
rename to openjdkjvmti/ti_stack.h
index 6a593cf..2e96b82 100644
--- a/runtime/openjdkjvmti/ti_stack.h
+++ b/openjdkjvmti/ti_stack.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_
+#ifndef ART_OPENJDKJVMTI_TI_STACK_H_
+#define ART_OPENJDKJVMTI_TI_STACK_H_
#include "jni.h"
#include "jvmti.h"
@@ -71,4 +71,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_
+#endif // ART_OPENJDKJVMTI_TI_STACK_H_
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
similarity index 81%
rename from runtime/openjdkjvmti/ti_thread.cc
rename to openjdkjvmti/ti_thread.cc
index 9acea2a..6fa73f8 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -159,26 +159,13 @@
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 @@
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 @@
}
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 @@
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 @@
}
}
art::ScopedObjectAccess soa(self);
+ art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
state = GetNativeThreadState(thread, soa);
- break;
+ if (state.art_state == art::ThreadState::kStarting) {
+ break;
+ }
+ DCHECK(state.native_thread != nullptr);
+
+ // 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;
+
+ return ERR(NONE);
} 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);
- }
+ DCHECK_EQ(state.art_state, art::ThreadState::kStarting);
- 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);
+ if (thread == nullptr) {
+ // No native thread, and no Java thread? We must be starting up. Report as wrong phase.
+ return ERR(WRONG_PHASE);
}
- DCHECK(state.native_thread != nullptr);
- // Translate internal thread state to JVMTI and Java state.
- jint jvmti_state = GetJvmtiThreadStateFromInternal(state);
+ art::ScopedObjectAccess soa(self);
- // 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);
}
@@ -571,7 +562,7 @@
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 @@
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::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 @@
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::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);
+ 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 == nullptr) {
- return ERR(INVALID_THREAD);
- }
- if (target == self) {
+ if (target_is_self) {
return SuspendSelf(self);
} else {
- return SuspendOther(self, thread, target);
+ return SuspendOther(self, thread);
}
}
@@ -791,41 +794,56 @@
}
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 @@
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/openjdkjvmti/ti_thread.h
similarity index 89%
rename from runtime/openjdkjvmti/ti_thread.h
rename to openjdkjvmti/ti_thread.h
index 0f7e837..03c49d7 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/openjdkjvmti/ti_thread.h
@@ -29,16 +29,18 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_
+#ifndef ART_OPENJDKJVMTI_TI_THREAD_H_
+#define ART_OPENJDKJVMTI_TI_THREAD_H_
#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 @@
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 @@
// 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_;
@@ -108,4 +113,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_
+#endif // ART_OPENJDKJVMTI_TI_THREAD_H_
diff --git a/runtime/openjdkjvmti/ti_threadgroup.cc b/openjdkjvmti/ti_threadgroup.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_threadgroup.cc
rename to openjdkjvmti/ti_threadgroup.cc
diff --git a/runtime/openjdkjvmti/ti_threadgroup.h b/openjdkjvmti/ti_threadgroup.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_threadgroup.h
rename to openjdkjvmti/ti_threadgroup.h
index c3a0ff5..4911566 100644
--- a/runtime/openjdkjvmti/ti_threadgroup.h
+++ b/openjdkjvmti/ti_threadgroup.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_THREADGROUP_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_THREADGROUP_H_
+#ifndef ART_OPENJDKJVMTI_TI_THREADGROUP_H_
+#define ART_OPENJDKJVMTI_TI_THREADGROUP_H_
#include "jni.h"
#include "jvmti.h"
@@ -57,4 +57,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_THREADGROUP_H_
+#endif // ART_OPENJDKJVMTI_TI_THREADGROUP_H_
diff --git a/runtime/openjdkjvmti/ti_timers.cc b/openjdkjvmti/ti_timers.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_timers.cc
rename to openjdkjvmti/ti_timers.cc
diff --git a/runtime/openjdkjvmti/ti_timers.h b/openjdkjvmti/ti_timers.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_timers.h
rename to openjdkjvmti/ti_timers.h
index 6300678..892205a 100644
--- a/runtime/openjdkjvmti/ti_timers.h
+++ b/openjdkjvmti/ti_timers.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_TIMERS_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_TIMERS_H_
+#ifndef ART_OPENJDKJVMTI_TI_TIMERS_H_
+#define ART_OPENJDKJVMTI_TI_TIMERS_H_
#include "jni.h"
#include "jvmti.h"
@@ -48,4 +48,4 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TI_TIMERS_H_
+#endif // ART_OPENJDKJVMTI_TI_TIMERS_H_
diff --git a/runtime/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
similarity index 100%
rename from runtime/openjdkjvmti/transform.cc
rename to openjdkjvmti/transform.cc
diff --git a/runtime/openjdkjvmti/transform.h b/openjdkjvmti/transform.h
similarity index 94%
rename from runtime/openjdkjvmti/transform.h
rename to openjdkjvmti/transform.h
index ed24068..6bbe60a 100644
--- a/runtime/openjdkjvmti/transform.h
+++ b/openjdkjvmti/transform.h
@@ -29,8 +29,8 @@
* questions.
*/
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_
+#ifndef ART_OPENJDKJVMTI_TRANSFORM_H_
+#define ART_OPENJDKJVMTI_TRANSFORM_H_
#include <string>
@@ -65,5 +65,5 @@
} // namespace openjdkjvmti
-#endif // ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_
+#endif // ART_OPENJDKJVMTI_TRANSFORM_H_
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 8d15c34..d534542 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -25,6 +25,7 @@
defaults: ["art_defaults"],
host_supported: true,
srcs: [
+ "aot_class_linker.cc",
"art_field.cc",
"art_method.cc",
"atomic.cc",
@@ -50,7 +51,6 @@
"class_linker.cc",
"class_loader_context.cc",
"class_table.cc",
- "code_simulator_container.cc",
"common_throws.cc",
"compiler_filter.cc",
"debugger.cc",
@@ -625,9 +625,3 @@
"libvixld-arm64",
],
}
-
-subdirs = [
- "openjdkjvm",
- "openjdkjvmti",
- "simulator",
-]
diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc
new file mode 100644
index 0000000..b1bc3f8
--- /dev/null
+++ b/runtime/aot_class_linker.cc
@@ -0,0 +1,69 @@
+/*
+ * 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();
+ bool strict_mode_ = runtime->IsActiveStrictTransactionMode();
+
+ DCHECK(klass != nullptr);
+ if (klass->IsInitialized() || klass->IsInitializing()) {
+ return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
+ }
+
+ // Don't initialize klass if it's superclass is not initialized, because superclass might abort
+ // the transaction and rolled back after klass's change is commited.
+ if (strict_mode_ && !klass->IsInterface() && klass->HasSuperClass()) {
+ if (klass->GetSuperClass()->GetStatus() == mirror::Class::kStatusInitializing) {
+ runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve "
+ + klass->PrettyTypeOf() + " because it's superclass is not initialized.");
+ return false;
+ }
+ }
+
+ if (strict_mode_) {
+ runtime->EnterTransactionMode(true, klass.Get()->AsClass());
+ }
+ bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
+
+ if (strict_mode_) {
+ 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 0000000..11bea86
--- /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 9a9f125..fad9278 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -154,20 +154,22 @@
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 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 @@
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 631f5e7..7d8deda 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -280,7 +280,7 @@
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 511ac83..cac40e0 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -376,8 +376,11 @@
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 @@
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 @@
// 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/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc
index 0c73ce7..eb8ced0 100644
--- a/runtime/base/unix_file/fd_file.cc
+++ b/runtime/base/unix_file/fd_file.cc
@@ -438,4 +438,30 @@
return true;
}
+int FdFile::Compare(FdFile* other) {
+ int64_t length = GetLength();
+ int64_t length2 = other->GetLength();
+ if (length != length2) {
+ return length < length2 ? -1 : 1;
+ }
+ static const size_t kBufferSize = 4096;
+ std::unique_ptr<uint8_t[]> buffer1(new uint8_t[kBufferSize]);
+ std::unique_ptr<uint8_t[]> buffer2(new uint8_t[kBufferSize]);
+ while (length > 0) {
+ size_t len = std::min(kBufferSize, static_cast<size_t>(length));
+ if (!ReadFully(&buffer1[0], len)) {
+ return -1;
+ }
+ if (!other->ReadFully(&buffer2[0], len)) {
+ return 1;
+ }
+ int result = memcmp(&buffer1[0], &buffer2[0], len);
+ if (result != 0) {
+ return result;
+ }
+ length -= len;
+ }
+ return 0;
+}
+
} // namespace unix_file
diff --git a/runtime/base/unix_file/fd_file.h b/runtime/base/unix_file/fd_file.h
index e07c3fd..91b08bc 100644
--- a/runtime/base/unix_file/fd_file.h
+++ b/runtime/base/unix_file/fd_file.h
@@ -145,6 +145,11 @@
// WARNING: Only use this when you know what you're doing!
void MarkUnchecked();
+ // Compare against another file. Returns 0 if the files are equivalent, otherwise returns -1 or 1
+ // depending on if the lenghts are different. If the lengths are the same, the function returns
+ // the difference of the first byte that differs.
+ int Compare(FdFile* other);
+
protected:
// If the guard state indicates checking (!=kNoCheck), go to the target state "target". Print the
// given warning if the current state is or exceeds warn_threshold.
diff --git a/runtime/base/unix_file/fd_file_test.cc b/runtime/base/unix_file/fd_file_test.cc
index 6aef348..8b1a115 100644
--- a/runtime/base/unix_file/fd_file_test.cc
+++ b/runtime/base/unix_file/fd_file_test.cc
@@ -220,4 +220,58 @@
EXPECT_FALSE(art::OS::FileExists(filename.c_str())) << filename;
}
+TEST_F(FdFileTest, Compare) {
+ std::vector<uint8_t> buffer;
+ constexpr int64_t length = 17 * art::KB;
+ for (size_t i = 0; i < length; ++i) {
+ buffer.push_back(static_cast<uint8_t>(i));
+ }
+
+ auto reset_compare = [&](art::ScratchFile& a, art::ScratchFile& b) {
+ a.GetFile()->ResetOffset();
+ b.GetFile()->ResetOffset();
+ return a.GetFile()->Compare(b.GetFile());
+ };
+
+ art::ScratchFile tmp;
+ EXPECT_TRUE(tmp.GetFile()->WriteFully(&buffer[0], length));
+ EXPECT_EQ(tmp.GetFile()->GetLength(), length);
+
+ art::ScratchFile tmp2;
+ EXPECT_TRUE(tmp2.GetFile()->WriteFully(&buffer[0], length));
+ EXPECT_EQ(tmp2.GetFile()->GetLength(), length);
+
+ // Basic equality check.
+ tmp.GetFile()->ResetOffset();
+ tmp2.GetFile()->ResetOffset();
+ // Files should be the same.
+ EXPECT_EQ(reset_compare(tmp, tmp2), 0);
+
+ // Change a byte near the start.
+ ++buffer[2];
+ art::ScratchFile tmp3;
+ EXPECT_TRUE(tmp3.GetFile()->WriteFully(&buffer[0], length));
+ --buffer[2];
+ EXPECT_NE(reset_compare(tmp, tmp3), 0);
+
+ // Change a byte near the middle.
+ ++buffer[length / 2];
+ art::ScratchFile tmp4;
+ EXPECT_TRUE(tmp4.GetFile()->WriteFully(&buffer[0], length));
+ --buffer[length / 2];
+ EXPECT_NE(reset_compare(tmp, tmp4), 0);
+
+ // Change a byte near the end.
+ ++buffer[length - 5];
+ art::ScratchFile tmp5;
+ EXPECT_TRUE(tmp5.GetFile()->WriteFully(&buffer[0], length));
+ --buffer[length - 5];
+ EXPECT_NE(reset_compare(tmp, tmp5), 0);
+
+ // Reference check
+ art::ScratchFile tmp6;
+ EXPECT_TRUE(tmp6.GetFile()->WriteFully(&buffer[0], length));
+ EXPECT_EQ(reset_compare(tmp, tmp6), 0);
+}
+
} // namespace unix_file
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 0096c37..439ecaf 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -312,6 +312,17 @@
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 e5ed8ae..3ac87c5 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3555,6 +3555,14 @@
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);
}
@@ -4712,7 +4720,7 @@
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,
@@ -4852,11 +4860,16 @@
return WaitForInitializeClass(klass, self, lock);
}
+ // Try to get the oat class's status for this class if the oat file is present. The compiler
+ // tries to validate superclass descriptors, and writes the result into the oat file.
+ // Runtime correctness is guaranteed by classpath checks done on loading. If the classpath
+ // is different at runtime than it was at compile time, the oat file is rejected. So if the
+ // oat file is present, the classpaths must match, and the runtime time check can be skipped.
bool has_oat_class = false;
- const OatFile::OatClass oat_class =
- (Runtime::Current()->IsStarted() && !Runtime::Current()->IsAotCompiler())
- ? OatFile::FindOatClass(klass->GetDexFile(), klass->GetDexClassDefIndex(), &has_oat_class)
- : OatFile::OatClass::Invalid();
+ const Runtime* runtime = Runtime::Current();
+ const OatFile::OatClass oat_class = (runtime->IsStarted() && !runtime->IsAotCompiler())
+ ? OatFile::FindOatClass(klass->GetDexFile(), klass->GetDexClassDefIndex(), &has_oat_class)
+ : OatFile::OatClass::Invalid();
if (oat_class.GetStatus() < mirror::Class::kStatusSuperclassValidated &&
!ValidateSuperClassDescriptors(klass)) {
mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
@@ -4887,7 +4900,9 @@
if (!super_initialized) {
// The super class was verified ahead of entering initializing, we should only be here if
// the super class became erroneous due to initialization.
- CHECK(handle_scope_super->IsErroneous() && self->IsExceptionPending())
+ // For the case of aot compiler, the super class might also be initializing but we don't
+ // want to process circular dependencies in pre-compile.
+ CHECK(self->IsExceptionPending())
<< "Super class initialization failed for "
<< handle_scope_super->PrettyDescriptor()
<< " that has unexpected status " << handle_scope_super->GetStatus()
@@ -5188,12 +5203,12 @@
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;
@@ -5238,7 +5253,7 @@
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);
@@ -5246,7 +5261,7 @@
}
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);
@@ -8510,7 +8525,7 @@
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 +8602,7 @@
if (!method->IsNative()) {
method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
} else {
- SetEntryPointsToCompiledCode(method, GetQuickGenericJniStub());
+ method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
}
}
@@ -8839,16 +8854,6 @@
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 62fb45b..bf14aeb 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -141,7 +141,7 @@
};
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,
@@ -635,9 +635,9 @@
// 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_);
@@ -702,6 +702,14 @@
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;
@@ -891,12 +899,6 @@
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,
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index ff440d7..e7051b3 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -68,6 +68,10 @@
}
}
+std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Default() {
+ return Create("");
+}
+
std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) {
std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext());
if (result->Parse(spec)) {
@@ -263,7 +267,16 @@
return removed_locations;
}
+std::string ClassLoaderContext::EncodeContextForDex2oat(const std::string& base_dir) const {
+ return EncodeContext(base_dir, /*for_dex2oat*/ true);
+}
+
std::string ClassLoaderContext::EncodeContextForOatFile(const std::string& base_dir) const {
+ return EncodeContext(base_dir, /*for_dex2oat*/ false);
+}
+
+std::string ClassLoaderContext::EncodeContext(const std::string& base_dir,
+ bool for_dex2oat) const {
CheckDexFilesOpened("EncodeContextForOatFile");
if (special_shared_library_) {
return OatFile::kSpecialSharedLibrary;
@@ -286,8 +299,17 @@
}
out << GetClassLoaderTypeName(info.type);
out << kClassLoaderOpeningMark;
+ std::set<std::string> seen_locations;
for (size_t k = 0; k < info.opened_dex_files.size(); k++) {
const std::unique_ptr<const DexFile>& dex_file = info.opened_dex_files[k];
+ if (for_dex2oat) {
+ // dex2oat only needs the base location. It cannot accept multidex locations.
+ // So ensure we only add each file once.
+ bool new_insert = seen_locations.insert(dex_file->GetBaseLocation()).second;
+ if (!new_insert) {
+ continue;
+ }
+ }
const std::string& location = dex_file->GetLocation();
if (k > 0) {
out << kClasspathSeparator;
@@ -298,8 +320,11 @@
} else {
out << dex_file->GetLocation().c_str();
}
- out << kDexFileChecksumSeparator;
- out << dex_file->GetLocationChecksum();
+ // dex2oat does not need the checksums.
+ if (!for_dex2oat) {
+ out << kDexFileChecksumSeparator;
+ out << dex_file->GetLocationChecksum();
+ }
}
out << kClassLoaderClosingMark;
}
@@ -593,7 +618,7 @@
}
}
-bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) {
+bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const {
ClassLoaderContext expected_context;
if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) {
LOG(WARNING) << "Invalid class loader context: " << context_spec;
@@ -607,7 +632,8 @@
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;
}
@@ -617,13 +643,15 @@
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;
}
@@ -634,13 +662,15 @@
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.h b/runtime/class_loader_context.h
index 37dd02b..9afa880 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -34,9 +34,6 @@
// Utility class which holds the class loader context used during compilation/verification.
class ClassLoaderContext {
public:
- // Creates an empty context (with no class loaders).
- ClassLoaderContext();
-
~ClassLoaderContext();
// Opens requested class path files and appends them to ClassLoaderInfo::opened_dex_files.
@@ -82,9 +79,16 @@
// (so that it can be read and verified at runtime against the actual class
// loader hierarchy).
// Should only be called if OpenDexFiles() returned true.
- // E.g. if the context is PCL[a.dex:b.dex] this will return "a.dex*a_checksum*b.dex*a_checksum".
+ // E.g. if the context is PCL[a.dex:b.dex] this will return
+ // "PCL[a.dex*a_checksum*b.dex*a_checksum]".
std::string EncodeContextForOatFile(const std::string& base_dir) const;
+ // Encodes the context as a string suitable to be passed to dex2oat.
+ // This is the same as EncodeContextForOatFile but without adding the checksums
+ // and only adding each dex files once (no multidex).
+ // Should only be called if OpenDexFiles() returned true.
+ std::string EncodeContextForDex2oat(const std::string& base_dir) const;
+
// Flattens the opened dex files into the given vector.
// Should only be called if OpenDexFiles() returned true.
std::vector<const DexFile*> FlattenOpenedDexFiles() const;
@@ -94,7 +98,7 @@
// - the number and type of the class loaders from the chain matches
// - the class loader from the same position have the same classpath
// (the order and checksum of the dex files matches)
- bool VerifyClassLoaderContextMatch(const std::string& context_spec);
+ bool VerifyClassLoaderContextMatch(const std::string& context_spec) const;
// Creates the class loader context from the given string.
// The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
@@ -119,6 +123,10 @@
static std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader,
jobjectArray dex_elements);
+ // Returns the default class loader context to be used when none is specified.
+ // This will return a context with a single and empty PathClassLoader.
+ static std::unique_ptr<ClassLoaderContext> Default();
+
private:
enum ClassLoaderType {
kInvalidClassLoader = 0,
@@ -144,6 +152,9 @@
explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
};
+ // Creates an empty context (with no class loaders).
+ ClassLoaderContext();
+
// Constructs an empty context.
// `owns_the_dex_files` specifies whether or not the context will own the opened dex files
// present in the class loader chain. If `owns_the_dex_files` is true then OpenDexFiles cannot
@@ -173,7 +184,15 @@
bool AddInfoToContextFromClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
Handle<mirror::ClassLoader> class_loader,
Handle<mirror::ObjectArray<mirror::Object>> dex_elements)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Encodes the context as a string suitable to be passed to dex2oat or to be added to the
+ // oat file as the class path key.
+ // If for_dex2oat is true, the encoding adds each file once (i.e. it does not add multidex
+ // location). Otherwise, for oat files, the encoding adds all the dex files (including multidex)
+ // together with their checksums.
+ // Should only be called if OpenDexFiles() returned true.
+ std::string EncodeContext(const std::string& base_dir, bool for_dex2oat) const;
// Extracts the class loader type from the given spec.
// Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 2b85188..d4688c1 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -455,6 +455,20 @@
ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
}
+TEST_F(ClassLoaderContextTest, EncodeForDex2oat) {
+ std::string dex1_name = GetTestDexFileName("Main");
+ std::string dex2_name = GetTestDexFileName("MultiDex");
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[" + dex1_name + ":" + dex2_name + "]");
+ ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+ std::vector<std::unique_ptr<const DexFile>> dex1 = OpenTestDexFiles("Main");
+ std::vector<std::unique_ptr<const DexFile>> dex2 = OpenTestDexFiles("MultiDex");
+ std::string encoding = context->EncodeContextForDex2oat("");
+ std::string expected_encoding = "PCL[" + dex1_name + ":" + dex2_name + "]";
+ ASSERT_EQ(expected_encoding, context->EncodeContextForDex2oat(""));
+}
+
// TODO(calin) add a test which creates the context for a class loader together with dex_elements.
TEST_F(ClassLoaderContextTest, CreateContextForClassLoader) {
// The chain is
diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h
index b15d82f..1280466 100644
--- a/runtime/class_table-inl.h
+++ b/runtime/class_table-inl.h
@@ -132,6 +132,13 @@
}
}
+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 8616dfb..a259725 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -250,6 +250,12 @@
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/common_dex_operations.h b/runtime/common_dex_operations.h
index 528db96..fcc5393 100644
--- a/runtime/common_dex_operations.h
+++ b/runtime/common_dex_operations.h
@@ -200,6 +200,11 @@
break;
}
}
+ if (transaction_active) {
+ if (UNLIKELY(self->IsExceptionPending())) {
+ return false;
+ }
+ }
return true;
}
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 5a87ae8..0b7af4e 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -4008,8 +4008,8 @@
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 2b81f0a..4225ab9 100644
--- a/runtime/dex_file_annotations.cc
+++ b/runtime/dex_file_annotations.cc
@@ -695,8 +695,7 @@
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 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 @@
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 @@
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 @@
}
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 01fc9ce..2bf4372 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -45,7 +45,7 @@
}
// 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 @@
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/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 9d672b1..9b24885 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -2431,24 +2431,31 @@
// Non-immune non-moving space. Use the mark bitmap.
accounting::ContinuousSpaceBitmap* mark_bitmap =
heap_mark_bitmap_->GetContinuousSpaceBitmap(from_ref);
- accounting::LargeObjectBitmap* los_bitmap =
- heap_mark_bitmap_->GetLargeObjectBitmap(from_ref);
- CHECK(los_bitmap != nullptr) << "LOS bitmap covers the entire address range";
bool is_los = mark_bitmap == nullptr;
if (!is_los && mark_bitmap->Test(from_ref)) {
// Already marked.
to_ref = from_ref;
- } else if (is_los && los_bitmap->Test(from_ref)) {
- // Already marked in LOS.
- to_ref = from_ref;
} else {
- // Not marked.
- if (IsOnAllocStack(from_ref)) {
- // If on the allocation stack, it's considered marked.
+ accounting::LargeObjectBitmap* los_bitmap =
+ heap_mark_bitmap_->GetLargeObjectBitmap(from_ref);
+ // We may not have a large object space for dex2oat, don't assume it exists.
+ if (los_bitmap == nullptr) {
+ CHECK(heap_->GetLargeObjectsSpace() == nullptr)
+ << "LOS bitmap covers the entire address range " << from_ref
+ << " " << heap_->DumpSpaces();
+ }
+ if (los_bitmap != nullptr && is_los && los_bitmap->Test(from_ref)) {
+ // Already marked in LOS.
to_ref = from_ref;
} else {
// Not marked.
- to_ref = nullptr;
+ if (IsOnAllocStack(from_ref)) {
+ // If on the allocation stack, it's considered marked.
+ to_ref = from_ref;
+ } else {
+ // Not marked.
+ to_ref = nullptr;
+ }
}
}
}
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 90b5def..9969489 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -196,6 +196,10 @@
}
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 @@
// 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 85904ee..f8cb243 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -33,6 +33,7 @@
#include "reflection.h"
#include "stack.h"
#include "thread-inl.h"
+#include "transaction.h"
#include "well_known_classes.h"
namespace art {
@@ -42,7 +43,8 @@
ThrowNullPointerExceptionFromDexPC();
}
-template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check>
+template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
+ bool transaction_active>
bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
uint16_t inst_data) {
const bool is_static = (find_type == StaticObjectRead) || (find_type == StaticPrimitiveRead);
@@ -57,6 +59,13 @@
ObjPtr<mirror::Object> obj;
if (is_static) {
obj = f->GetDeclaringClass();
+ if (transaction_active) {
+ if (Runtime::Current()->GetTransaction()->ReadConstraint(obj.Ptr(), f)) {
+ Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Can't read static fields of "
+ + obj->PrettyTypeOf() + " since it does not belong to clinit's class.");
+ return false;
+ }
+ }
} else {
obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
if (UNLIKELY(obj == nullptr)) {
@@ -102,15 +111,17 @@
}
// Explicitly instantiate all DoFieldGet functions.
-#define EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, _do_check) \
- template bool DoFieldGet<_find_type, _field_type, _do_check>(Thread* self, \
+#define EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, _do_check, _transaction_active) \
+ template bool DoFieldGet<_find_type, _field_type, _do_check, _transaction_active>(Thread* self, \
ShadowFrame& shadow_frame, \
const Instruction* inst, \
uint16_t inst_data)
#define EXPLICIT_DO_FIELD_GET_ALL_TEMPLATE_DECL(_find_type, _field_type) \
- EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, false); \
- EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, true);
+ EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, false, true); \
+ EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, false, false); \
+ EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, true, true); \
+ EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, true, false);
// iget-XXX
EXPLICIT_DO_FIELD_GET_ALL_TEMPLATE_DECL(InstancePrimitiveRead, Primitive::kPrimBoolean)
@@ -261,6 +272,14 @@
ObjPtr<mirror::Object> obj;
if (is_static) {
obj = f->GetDeclaringClass();
+ if (transaction_active) {
+ if (Runtime::Current()->GetTransaction()->WriteConstraint(obj.Ptr(), f)) {
+ Runtime::Current()->AbortTransactionAndThrowAbortError(
+ self, "Can't set fields of " + obj->PrettyTypeOf());
+ return false;
+ }
+ }
+
} else {
obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
if (UNLIKELY(obj == nullptr)) {
@@ -1070,7 +1089,7 @@
// 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_common.h b/runtime/interpreter/interpreter_common.h
index d293aeb..b228e28 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -270,7 +270,8 @@
// Handles iget-XXX and sget-XXX instructions.
// Returns true on success, otherwise throws an exception and returns false.
-template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check>
+template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
+ bool transaction_active = false>
bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
uint16_t inst_data) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 0a2705d..0c5a45f 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -349,7 +349,7 @@
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) {
@@ -1313,50 +1313,50 @@
}
case Instruction::SGET_BOOLEAN: {
PREAMBLE();
- bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimBoolean, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimBoolean, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
case Instruction::SGET_BYTE: {
PREAMBLE();
- bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimByte, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimByte, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
case Instruction::SGET_CHAR: {
PREAMBLE();
- bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimChar, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimChar, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
case Instruction::SGET_SHORT: {
PREAMBLE();
- bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimShort, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimShort, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
case Instruction::SGET: {
PREAMBLE();
- bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimInt, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimInt, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
case Instruction::SGET_WIDE: {
PREAMBLE();
- bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimLong, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimLong, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
case Instruction::SGET_OBJECT: {
PREAMBLE();
- bool success = DoFieldGet<StaticObjectRead, Primitive::kPrimNot, do_access_check>(
- self, shadow_frame, inst, inst_data);
+ bool success = DoFieldGet<StaticObjectRead, Primitive::kPrimNot, do_access_check,
+ transaction_active>(self, shadow_frame, inst, inst_data);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
break;
}
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index a030a51..59373eb 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1600,7 +1600,7 @@
// is the one we expect. We change to the non-obsolete versions in the error message since the
// obsolete version of the method might not be fully initialized yet. This situation can only
// occur when we are in the process of allocating and setting up obsolete methods. Otherwise
- // method and it->second should be identical. (See runtime/openjdkjvmti/ti_redefine.cc for more
+ // method and it->second should be identical. (See openjdkjvmti/ti_redefine.cc for more
// information.)
DCHECK_EQ(it->second->GetNonObsoleteMethod(), method->GetNonObsoleteMethod())
<< ArtMethod::PrettyMethod(method->GetNonObsoleteMethod()) << " "
diff --git a/runtime/mirror/accessible_object.h b/runtime/mirror/accessible_object.h
index a217193..d489f14 100644
--- a/runtime/mirror/accessible_object.h
+++ b/runtime/mirror/accessible_object.h
@@ -17,11 +17,8 @@
#ifndef ART_RUNTIME_MIRROR_ACCESSIBLE_OBJECT_H_
#define ART_RUNTIME_MIRROR_ACCESSIBLE_OBJECT_H_
-#include "class.h"
-#include "gc_root.h"
#include "object.h"
#include "read_barrier_option.h"
-#include "thread.h"
namespace art {
@@ -34,12 +31,6 @@
return OFFSET_OF_OBJECT_MEMBER(AccessibleObject, flag_);
}
- template<bool kTransactionActive>
- void SetAccessible(bool value) REQUIRES_SHARED(Locks::mutator_lock_) {
- UNUSED(padding_);
- return SetFieldBoolean<kTransactionActive>(FlagOffset(), value ? 1u : 0u);
- }
-
bool IsAccessible() REQUIRES_SHARED(Locks::mutator_lock_) {
return GetFieldBoolean(FlagOffset());
}
@@ -47,7 +38,7 @@
private:
uint8_t flag_;
// Padding required for correct alignment of subclasses like Executable, Field, etc.
- uint8_t padding_[1];
+ uint8_t padding_[1] ATTRIBUTE_UNUSED;
DISALLOW_IMPLICIT_CONSTRUCTORS(AccessibleObject);
};
diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h
index 63142d5..2281245 100644
--- a/runtime/mirror/array-inl.h
+++ b/runtime/mirror/array-inl.h
@@ -27,8 +27,7 @@
#include "class.h"
#include "gc/heap-inl.h"
#include "obj_ptr-inl.h"
-#include "object-inl.h"
-#include "thread.h"
+#include "thread-current-inl.h"
namespace art {
namespace mirror {
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 6642869..c775cf4 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -464,14 +464,25 @@
return FindInterfaceMethod(name, signature, pointer_size);
}
+static inline bool IsValidInheritanceCheck(ObjPtr<mirror::Class> klass,
+ ObjPtr<mirror::Class> declaring_class)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (klass->IsArrayClass()) {
+ return declaring_class->IsObjectClass();
+ } else if (klass->IsInterface()) {
+ return declaring_class->IsObjectClass() || declaring_class == klass;
+ } else {
+ return klass->IsSubClass(declaring_class);
+ }
+}
+
static inline bool IsInheritedMethod(ObjPtr<mirror::Class> klass,
ObjPtr<mirror::Class> declaring_class,
ArtMethod& method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK_EQ(declaring_class, method.GetDeclaringClass());
DCHECK_NE(klass, declaring_class);
- DCHECK(klass->IsArrayClass() ? declaring_class->IsObjectClass()
- : klass->IsSubClass(declaring_class));
+ DCHECK(IsValidInheritanceCheck(klass, declaring_class));
uint32_t access_flags = method.GetAccessFlags();
if ((access_flags & (kAccPublic | kAccProtected)) != 0) {
return true;
diff --git a/runtime/mirror/field-inl.h b/runtime/mirror/field-inl.h
index d33df5c..ad48202 100644
--- a/runtime/mirror/field-inl.h
+++ b/runtime/mirror/field-inl.h
@@ -20,7 +20,8 @@
#include "field.h"
#include "art_field-inl.h"
-#include "mirror/dex_cache-inl.h"
+#include "class-inl.h"
+#include "dex_cache-inl.h"
namespace art {
@@ -87,6 +88,10 @@
SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(Field, type_), type);
}
+inline Primitive::Type Field::GetTypeAsPrimitiveType() {
+ return GetType()->GetPrimitiveType();
+}
+
} // namespace mirror
} // namespace art
diff --git a/runtime/mirror/field.h b/runtime/mirror/field.h
index 40186a6..6845575 100644
--- a/runtime/mirror/field.h
+++ b/runtime/mirror/field.h
@@ -20,8 +20,10 @@
#include "accessible_object.h"
#include "base/enums.h"
#include "gc_root.h"
+#include "modifiers.h"
#include "obj_ptr.h"
#include "object.h"
+#include "primitive.h"
#include "read_barrier_option.h"
namespace art {
@@ -69,10 +71,7 @@
return (GetAccessFlags() & kAccVolatile) != 0;
}
- ALWAYS_INLINE Primitive::Type GetTypeAsPrimitiveType()
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return GetType()->GetPrimitiveType();
- }
+ ALWAYS_INLINE Primitive::Type GetTypeAsPrimitiveType() REQUIRES_SHARED(Locks::mutator_lock_);
mirror::Class* GetType() REQUIRES_SHARED(Locks::mutator_lock_) {
return GetFieldObject<mirror::Class>(OFFSET_OF_OBJECT_MEMBER(Field, type_));
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 086925b..4f8952d 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -393,14 +393,6 @@
}
template<VerifyObjectFlags kVerifyFlags, bool kIsVolatile>
-inline uint8_t Object::GetFieldBoolean(MemberOffset field_offset) {
- if (kVerifyFlags & kVerifyThis) {
- VerifyObject(this);
- }
- return GetField<uint8_t, kIsVolatile>(field_offset);
-}
-
-template<VerifyObjectFlags kVerifyFlags, bool kIsVolatile>
inline int8_t Object::GetFieldByte(MemberOffset field_offset) {
if (kVerifyFlags & kVerifyThis) {
VerifyObject(this);
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index 886780f..aedcd66 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -380,7 +380,12 @@
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false>
ALWAYS_INLINE uint8_t GetFieldBoolean(MemberOffset field_offset)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (kVerifyFlags & kVerifyThis) {
+ VerifyObject(this);
+ }
+ return GetField<uint8_t, kIsVolatile>(field_offset);
+ }
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false>
ALWAYS_INLINE int8_t GetFieldByte(MemberOffset field_offset)
diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc
index 2aad12d..f209f1d 100644
--- a/runtime/native/java_lang_reflect_Executable.cc
+++ b/runtime/native/java_lang_reflect_Executable.cc
@@ -260,7 +260,7 @@
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/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc
index 9f59a1f..2e4dd8a 100644
--- a/runtime/native/java_lang_reflect_Field.cc
+++ b/runtime/native/java_lang_reflect_Field.cc
@@ -27,7 +27,7 @@
#include "dex_file_annotations.h"
#include "jni_internal.h"
#include "mirror/class-inl.h"
-#include "mirror/field.h"
+#include "mirror/field-inl.h"
#include "native_util.h"
#include "reflection-inl.h"
#include "scoped_fast_native_object_access-inl.h"
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index dae41c1..3794f51 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -37,6 +37,7 @@
#include "scoped_thread_state_change-inl.h"
#include "utils.h"
#include "vdex_file.h"
+#include "class_loader_context.h"
namespace art {
@@ -225,13 +226,25 @@
}
OatFileAssistant::ResultOfAttemptToUpdate
-OatFileAssistant::MakeUpToDate(bool profile_changed, std::string* error_msg) {
+OatFileAssistant::MakeUpToDate(bool profile_changed,
+ const std::string& class_loader_context,
+ std::string* error_msg) {
CompilerFilter::Filter target;
if (!GetRuntimeCompilerFilterOption(&target, error_msg)) {
return kUpdateNotAttempted;
}
OatFileInfo& info = GetBestInfo();
+ // TODO(calin, jeffhao): the context should really be passed to GetDexOptNeeded: b/62269291.
+ // This is actually not trivial in the current logic as it will interact with the collision
+ // check:
+ // - currently, if the context does not match but we have no collisions we still accept the
+ // oat file.
+ // - if GetDexOptNeeded would return kDex2OatFromScratch for a context mismatch and we make
+ // the oat code up to date the collision check becomes useless.
+ // - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files
+ // loaded in other processes). So it boils down to how far do we want to complicate
+ // the logic in order to enable the use of oat files. Maybe its time to try simplify it.
switch (info.GetDexOptNeeded(target, profile_changed, /*downgrade*/ false)) {
case kNoDexOptNeeded:
return kUpdateSucceeded;
@@ -243,7 +256,7 @@
case kDex2OatForBootImage:
case kDex2OatForRelocation:
case kDex2OatForFilter:
- return GenerateOatFileNoChecks(info, target, error_msg);
+ return GenerateOatFileNoChecks(info, target, class_loader_context, error_msg);
}
UNREACHABLE();
}
@@ -628,7 +641,10 @@
}
OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks(
- OatFileAssistant::OatFileInfo& info, CompilerFilter::Filter filter, std::string* error_msg) {
+ OatFileAssistant::OatFileInfo& info,
+ CompilerFilter::Filter filter,
+ const std::string& class_loader_context,
+ std::string* error_msg) {
CHECK(error_msg != nullptr);
Runtime* runtime = Runtime::Current();
@@ -704,6 +720,7 @@
args.push_back("--oat-fd=" + std::to_string(oat_file->Fd()));
args.push_back("--oat-location=" + oat_file_name);
args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
+ args.push_back("--class-loader-context=" + class_loader_context);
if (!Dex2Oat(args, error_msg)) {
// Manually delete the oat and vdex files. This ensures there is no garbage
@@ -743,14 +760,6 @@
std::vector<std::string> argv;
argv.push_back(runtime->GetCompilerExecutable());
- argv.push_back("--runtime-arg");
- argv.push_back("-classpath");
- argv.push_back("--runtime-arg");
- std::string class_path = runtime->GetClassPathString();
- if (class_path == "") {
- class_path = OatFile::kSpecialSharedLibrary;
- }
- argv.push_back(class_path);
if (runtime->IsJavaDebuggable()) {
argv.push_back("--debuggable");
}
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 320aa4f..5eec943 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -185,12 +185,17 @@
// profile_changed should be true to indicate the profile has recently
// changed for this dex location.
//
+ // If the dex files need to be made up to date, class_loader_context will be
+ // passed to dex2oat.
+ //
// Returns the result of attempting to update the code.
//
// If the result is not kUpdateSucceeded, the value of error_msg will be set
// to a string describing why there was a failure or the update was not
// attempted. error_msg must not be null.
- ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed, std::string* error_msg);
+ ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed,
+ const std::string& class_loader_context,
+ std::string* error_msg);
// Returns an oat file that can be used for loading dex files.
// Returns null if no suitable oat file was found.
@@ -389,7 +394,8 @@
};
// Generate the oat file for the given info from the dex file using the
- // current runtime compiler options and the specified filter.
+ // current runtime compiler options, the specified filter and class loader
+ // context.
// This does not check the current status before attempting to generate the
// oat file.
//
@@ -398,6 +404,7 @@
// attempted. error_msg must not be null.
ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info,
CompilerFilter::Filter target,
+ const std::string& class_loader_context,
std::string* error_msg);
// Return info for the best oat file.
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index c59dafc..e048177 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -27,8 +27,10 @@
#include "art_field-inl.h"
#include "class_linker-inl.h"
+#include "class_loader_context.h"
#include "common_runtime_test.h"
#include "dexopt_test.h"
+#include "oat_file.h"
#include "oat_file_manager.h"
#include "os.h"
#include "scoped_thread_state_change-inl.h"
@@ -37,6 +39,8 @@
namespace art {
+static const std::string kSpecialSharedLibrary = "&";
+
class OatFileAssistantTest : public DexoptTest {};
class OatFileAssistantNoDex2OatTest : public DexoptTest {
@@ -116,7 +120,8 @@
// Trying to make the oat file up to date should not fail or crash.
std::string error_msg;
- EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, oat_file_assistant.MakeUpToDate(false, &error_msg));
+ EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
// Trying to get the best oat file should fail, but not crash.
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -769,7 +774,8 @@
std::string error_msg;
Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+ error_msg;
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
@@ -949,7 +955,7 @@
// We should get kUpdateSucceeded from MakeUpToDate since there's nothing
// that can be done in this situation.
ASSERT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
// Verify it didn't create an oat in the default location (dalvik-cache).
OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false);
@@ -1028,7 +1034,7 @@
std::string error_msg;
Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
EXPECT_TRUE(error_msg.empty());
}
@@ -1175,7 +1181,8 @@
std::string error_msg;
Runtime::Current()->AddCompilerOption("--compiler-filter=quicken");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+ error_msg;
EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
@@ -1183,7 +1190,8 @@
Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg))
+ << error_msg;
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
@@ -1191,7 +1199,7 @@
Runtime::Current()->AddCompilerOption("--compiler-filter=bogus");
EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted,
- oat_file_assistant.MakeUpToDate(false, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
}
TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) {
@@ -1251,7 +1259,8 @@
OatFileAssistant::kDefaultCompilerFilterForDexLoading;
std::string error_msg;
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+ error_msg;
EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(default_filter));
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -1259,6 +1268,50 @@
EXPECT_EQ(default_filter, oat_file->GetCompilerFilter());
}
+TEST_F(OatFileAssistantTest, MakeUpToDateWithSpecialSharedLibrary) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ Copy(GetDexSrc1(), dex_location);
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+
+ const CompilerFilter::Filter default_filter =
+ OatFileAssistant::kDefaultCompilerFilterForDexLoading;
+ std::string error_msg;
+ int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg);
+ EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(default_filter));
+ std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
+ EXPECT_NE(nullptr, oat_file.get());
+ EXPECT_EQ(kSpecialSharedLibrary,
+ oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
+}
+
+TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string context_location = GetScratchDir() + "/ContextDex.jar";
+ Copy(GetDexSrc1(), dex_location);
+ Copy(GetDexSrc2(), context_location);
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+
+ const CompilerFilter::Filter default_filter =
+ OatFileAssistant::kDefaultCompilerFilterForDexLoading;
+ std::string error_msg;
+ std::string context_str = "PCL[" + context_location + "]";
+ int status = oat_file_assistant.MakeUpToDate(false, context_str, &error_msg);
+ EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(default_filter));
+ std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
+ EXPECT_NE(nullptr, oat_file.get());
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create(context_str);
+ context->OpenDexFiles(kRuntimeISA, "");
+ EXPECT_EQ(context->EncodeContextForOatFile(""),
+ oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
+}
+
// TODO: More Tests:
// * Test class linker falls back to unquickened dex for DexNoOat
// * Test class linker falls back to unquickened dex for MultiDexNoOat
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index e950fca..5baf59c 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -329,12 +329,14 @@
// Check for class-def collisions in dex files.
//
-// This first walks the class loader chain, getting all the dex files from the class loader. If
-// the class loader is null or one of the class loaders in the chain is unsupported, we collect
-// dex files from all open non-boot oat files to be safe.
+// This first walks the class loader chain present in the given context, getting all the dex files
+// from the class loader.
//
-// This first checks whether the shared libraries are in the expected order and the oat files
-// have the expected checksums. If so, we exit early. Otherwise, we do the collision check.
+// If the context is null (which means the initial class loader was null or unsupported)
+// this returns false. b/37777332.
+//
+// This first checks whether all class loaders in the context have the same type and
+// classpath. If so, we exit early. Otherwise, we do the collision check.
//
// The collision check works by maintaining a heap with one class from each dex file, sorted by the
// class descriptor. Then a dex-file/class pair is continually removed from the heap and compared
@@ -342,23 +344,11 @@
// the two elements agree on whether their dex file was from an already-loaded oat-file or the
// new oat file. Any disagreement indicates a collision.
bool OatFileManager::HasCollisions(const OatFile* oat_file,
- jobject class_loader,
- jobjectArray dex_elements,
+ const ClassLoaderContext* context,
std::string* error_msg /*out*/) const {
DCHECK(oat_file != nullptr);
DCHECK(error_msg != nullptr);
- // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
- // directly with DexFile APIs instead of using class loaders.
- if (class_loader == nullptr) {
- LOG(WARNING) << "Opening an oat file without a class loader. "
- << "Are you using the deprecated DexFile APIs?";
- return false;
- }
-
- std::unique_ptr<ClassLoaderContext> context =
- ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
-
// The context might be null if there are unrecognized class loaders in the chain or they
// don't meet sensible sanity conditions. In this case we assume that the app knows what it's
// doing and accept the oat file.
@@ -406,6 +396,17 @@
Locks::mutator_lock_->AssertNotHeld(self);
Runtime* const runtime = Runtime::Current();
+ std::unique_ptr<ClassLoaderContext> context;
+ // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
+ // directly with DexFile APIs instead of using class loaders.
+ if (class_loader == nullptr) {
+ LOG(WARNING) << "Opening an oat file without a class loader. "
+ << "Are you using the deprecated DexFile APIs?";
+ context = nullptr;
+ } else {
+ context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
+ }
+
OatFileAssistant oat_file_assistant(dex_location,
kRuntimeISA,
!runtime->IsAotCompiler());
@@ -425,7 +426,12 @@
// Update the oat file on disk if we can, based on the --compiler-filter
// option derived from the current runtime options.
// This may fail, but that's okay. Best effort is all that matters here.
- switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, /*out*/ &error_msg)) {
+
+ const std::string& dex2oat_context = context == nullptr
+ ? OatFile::kSpecialSharedLibrary
+ : context->EncodeContextForDex2oat(/*base_dir*/ "");
+ switch (oat_file_assistant.MakeUpToDate(
+ /*profile_changed*/false, dex2oat_context, /*out*/ &error_msg)) {
case OatFileAssistant::kUpdateFailed:
LOG(WARNING) << error_msg;
break;
@@ -451,7 +457,7 @@
if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) {
// Take the file only if it has no collisions, or we must take it because of preopting.
bool accept_oat_file =
- !HasCollisions(oat_file.get(), class_loader, dex_elements, /*out*/ &error_msg);
+ !HasCollisions(oat_file.get(), context.get(), /*out*/ &error_msg);
if (!accept_oat_file) {
// Failed the collision check. Print warning.
if (Runtime::Current()->IsDexFileFallbackEnabled()) {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 05a5f5b..4523494 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -35,6 +35,7 @@
} // namespace space
} // namespace gc
+class ClassLoaderContext;
class DexFile;
class OatFile;
@@ -105,15 +106,17 @@
void DumpForSigQuit(std::ostream& os);
private:
- // Check that the shared libraries in the given oat file match those in the given class loader and
- // dex elements. If the class loader is null or we do not support one of the class loaders in the
- // chain, compare against all non-boot oat files instead. If the shared libraries are not ok,
- // check for duplicate class definitions of the given oat file against the oat files (either from
- // the class loader and dex elements if possible or all non-boot oat files otherwise).
+ // Check that the class loader context of the given oat file matches the given context.
+ // This will perform a check that all class loaders in the chain have the same type and
+ // classpath.
+ // If the context is null (which means the initial class loader was null or unsupported)
+ // this returns false.
+ // If the context does not validate the method will check for duplicate class definitions of
+ // the given oat file against the oat files (either from the class loaders if possible or all
+ // non-boot oat files otherwise).
// Return true if there are any class definition collisions in the oat_file.
bool HasCollisions(const OatFile* oat_file,
- jobject class_loader,
- jobjectArray dex_elements,
+ const ClassLoaderContext* context,
/*out*/ std::string* error_msg) const
REQUIRES(!Locks::oat_file_manager_lock_);
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
deleted file mode 100644
index ab434d7..0000000
--- a/runtime/openjdkjvmti/ti_method.cc
+++ /dev/null
@@ -1,428 +0,0 @@
-/* Copyright (C) 2016 The Android Open Source Project
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This file implements interfaces from the file jvmti.h. This implementation
- * is licensed under the same terms as the file jvmti.h. The
- * copyright and license information for the file jvmti.h follows.
- *
- * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-#include "ti_method.h"
-
-#include "art_jvmti.h"
-#include "art_method-inl.h"
-#include "base/enums.h"
-#include "dex_file_annotations.h"
-#include "events-inl.h"
-#include "jni_internal.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 "thread-current-inl.h"
-#include "thread_list.h"
-#include "ti_phase.h"
-
-namespace openjdkjvmti {
-
-struct TiMethodCallback : public art::MethodCallback {
- void RegisterNativeMethod(art::ArtMethod* method,
- const void* cur_method,
- /*out*/void** new_method)
- OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
- if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
- art::Thread* thread = art::Thread::Current();
- art::JNIEnvExt* jnienv = thread->GetJniEnv();
- ScopedLocalRef<jthread> thread_jni(
- jnienv, PhaseUtil::IsLivePhase() ? jnienv->AddLocalReference<jthread>(thread->GetPeer())
- : nullptr);
- art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
- event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
- thread,
- static_cast<JNIEnv*>(jnienv),
- thread_jni.get(),
- art::jni::EncodeArtMethod(method),
- const_cast<void*>(cur_method),
- new_method);
- }
- }
-
- EventHandler* event_handler = nullptr;
-};
-
-TiMethodCallback gMethodCallback;
-
-void MethodUtil::Register(EventHandler* handler) {
- gMethodCallback.event_handler = handler;
- art::ScopedThreadStateChange stsc(art::Thread::Current(),
- art::ThreadState::kWaitingForDebuggerToAttach);
- art::ScopedSuspendAll ssa("Add method callback");
- art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
-}
-
-void MethodUtil::Unregister() {
- art::ScopedThreadStateChange stsc(art::Thread::Current(),
- art::ThreadState::kWaitingForDebuggerToAttach);
- art::ScopedSuspendAll ssa("Remove method callback");
- art::Runtime* runtime = art::Runtime::Current();
- runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
-}
-
-jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
- jmethodID method,
- jint* size_ptr,
- unsigned char** bytecode_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 (size_ptr == nullptr || bytecode_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ScopedObjectAccess soa(art::Thread::Current());
- const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
- if (code_item == nullptr) {
- *size_ptr = 0;
- *bytecode_ptr = nullptr;
- return OK;
- }
- // 2 bytes per instruction for dex code.
- *size_ptr = code_item->insns_size_in_code_units_ * 2;
- jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr);
- if (err != OK) {
- return err;
- }
- memcpy(*bytecode_ptr, code_item->insns_, *size_ptr);
- return OK;
-}
-
-jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
- jmethodID method,
- jint* size_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 (size_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ScopedObjectAccess soa(art::Thread::Current());
- if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
- // Use the shorty.
- art::ArtMethod* base_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
- size_t arg_count = art::ArtMethod::NumArgRegisters(base_method->GetShorty());
- if (!base_method->IsStatic()) {
- arg_count++;
- }
- *size_ptr = static_cast<jint>(arg_count);
- return ERR(NONE);
- }
-
- DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
- *size_ptr = art_method->GetCodeItem()->ins_size_;
-
- return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED,
- jmethodID method,
- jint* max_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 (max_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ScopedObjectAccess soa(art::Thread::Current());
- if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
- // This isn't specified as an error case, so return 0.
- *max_ptr = 0;
- return ERR(NONE);
- }
-
- DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
- *max_ptr = art_method->GetCodeItem()->registers_size_;
-
- return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodName(jvmtiEnv* env,
- jmethodID method,
- char** name_ptr,
- char** signature_ptr,
- char** generic_ptr) {
- art::ScopedObjectAccess soa(art::Thread::Current());
- art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
- art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
-
- JvmtiUniquePtr<char[]> name_copy;
- if (name_ptr != nullptr) {
- const char* method_name = art_method->GetName();
- if (method_name == nullptr) {
- method_name = "<error>";
- }
- jvmtiError ret;
- name_copy = CopyString(env, method_name, &ret);
- if (name_copy == nullptr) {
- return ret;
- }
- *name_ptr = name_copy.get();
- }
-
- JvmtiUniquePtr<char[]> signature_copy;
- if (signature_ptr != nullptr) {
- const art::Signature sig = art_method->GetSignature();
- std::string str = sig.ToString();
- jvmtiError ret;
- signature_copy = CopyString(env, str.c_str(), &ret);
- if (signature_copy == nullptr) {
- return ret;
- }
- *signature_ptr = signature_copy.get();
- }
-
- if (generic_ptr != nullptr) {
- *generic_ptr = nullptr;
- if (!art_method->GetDeclaringClass()->IsProxyClass()) {
- art::mirror::ObjectArray<art::mirror::String>* str_array =
- art::annotations::GetSignatureAnnotationForMethod(art_method);
- if (str_array != nullptr) {
- std::ostringstream oss;
- for (int32_t i = 0; i != str_array->GetLength(); ++i) {
- oss << str_array->Get(i)->ToModifiedUtf8();
- }
- std::string output_string = oss.str();
- jvmtiError ret;
- JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
- if (generic_copy == nullptr) {
- return ret;
- }
- *generic_ptr = generic_copy.release();
- } else if (soa.Self()->IsExceptionPending()) {
- // TODO: Should we report an error here?
- soa.Self()->ClearException();
- }
- }
- }
-
- // Everything is fine, release the buffers.
- name_copy.release();
- signature_copy.release();
-
- return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
- jmethodID method,
- jclass* declaring_class_ptr) {
- if (declaring_class_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
- // Note: No GetInterfaceMethodIfProxy, we want to actual class.
-
- art::ScopedObjectAccess soa(art::Thread::Current());
- art::mirror::Class* klass = art_method->GetDeclaringClass();
- *declaring_class_ptr = soa.AddLocalReference<jclass>(klass);
-
- return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
- jmethodID method,
- jlocation* start_location_ptr,
- jlocation* end_location_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 (start_location_ptr == nullptr || end_location_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ScopedObjectAccess soa(art::Thread::Current());
- if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
- // This isn't specified as an error case, so return -1/-1 as the RI does.
- *start_location_ptr = -1;
- *end_location_ptr = -1;
- return ERR(NONE);
- }
-
- DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
- *start_location_ptr = 0;
- *end_location_ptr = art_method->GetCodeItem()->insns_size_in_code_units_ - 1;
-
- return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
- jmethodID method,
- jint* modifiers_ptr) {
- if (modifiers_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
- uint32_t modifiers = art_method->GetAccessFlags();
-
- // Note: Keep this code in sync with Executable.fixMethodFlags.
- if ((modifiers & art::kAccAbstract) != 0) {
- modifiers &= ~art::kAccNative;
- }
- modifiers &= ~art::kAccSynchronized;
- if ((modifiers & art::kAccDeclaredSynchronized) != 0) {
- modifiers |= art::kAccSynchronized;
- }
- modifiers &= art::kAccJavaFlagsMask;
-
- *modifiers_ptr = modifiers;
- return ERR(NONE);
-}
-
-using LineNumberContext = std::vector<jvmtiLineNumberEntry>;
-
-static bool CollectLineNumbers(void* void_context, const art::DexFile::PositionInfo& entry) {
- LineNumberContext* context = reinterpret_cast<LineNumberContext*>(void_context);
- jvmtiLineNumberEntry jvmti_entry = { static_cast<jlocation>(entry.address_),
- static_cast<jint>(entry.line_) };
- context->push_back(jvmti_entry);
- return false; // Collect all, no early exit.
-}
-
-jvmtiError MethodUtil::GetLineNumberTable(jvmtiEnv* env,
- jmethodID method,
- jint* entry_count_ptr,
- jvmtiLineNumberEntry** table_ptr) {
- if (method == nullptr) {
- return ERR(NULL_POINTER);
- }
- art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
- DCHECK(!art_method->IsRuntimeMethod());
-
- const art::DexFile::CodeItem* code_item;
- const art::DexFile* dex_file;
- {
- art::ScopedObjectAccess soa(art::Thread::Current());
-
- if (art_method->IsProxyMethod()) {
- return ERR(ABSENT_INFORMATION);
- }
- if (art_method->IsNative()) {
- return ERR(NATIVE_METHOD);
- }
- if (entry_count_ptr == nullptr || table_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- code_item = art_method->GetCodeItem();
- dex_file = art_method->GetDexFile();
- DCHECK(code_item != nullptr) << art_method->PrettyMethod() << " " << dex_file->GetLocation();
- }
-
- LineNumberContext context;
- bool success = dex_file->DecodeDebugPositionInfo(code_item, CollectLineNumbers, &context);
- if (!success) {
- return ERR(ABSENT_INFORMATION);
- }
-
- unsigned char* data;
- jlong mem_size = context.size() * sizeof(jvmtiLineNumberEntry);
- jvmtiError alloc_error = env->Allocate(mem_size, &data);
- if (alloc_error != ERR(NONE)) {
- return alloc_error;
- }
- *table_ptr = reinterpret_cast<jvmtiLineNumberEntry*>(data);
- memcpy(*table_ptr, context.data(), mem_size);
- *entry_count_ptr = static_cast<jint>(context.size());
-
- return ERR(NONE);
-}
-
-template <typename T>
-static jvmtiError IsMethodT(jvmtiEnv* env ATTRIBUTE_UNUSED,
- jmethodID method,
- T test,
- jboolean* is_t_ptr) {
- if (method == nullptr) {
- return ERR(INVALID_METHODID);
- }
- if (is_t_ptr == nullptr) {
- return ERR(NULL_POINTER);
- }
-
- art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
- *is_t_ptr = test(art_method) ? JNI_TRUE : JNI_FALSE;
-
- return ERR(NONE);
-}
-
-jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) {
- auto test = [](art::ArtMethod* method) {
- return method->IsNative();
- };
- return IsMethodT(env, m, test, is_native_ptr);
-}
-
-jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) {
- auto test = [](art::ArtMethod* method) {
- return method->IsObsolete();
- };
- return IsMethodT(env, m, test, is_obsolete_ptr);
-}
-
-jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) {
- auto test = [](art::ArtMethod* method) {
- return method->IsSynthetic();
- };
- return IsMethodT(env, m, test, is_synthetic_ptr);
-}
-
-} // namespace openjdkjvmti
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 6f1d15c..f28f0ca 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -238,8 +238,7 @@
// 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 @@
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 6fbf64b..a8ccf89 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"
@@ -237,7 +238,7 @@
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),
@@ -1283,7 +1284,11 @@
GetHeap()->EnableObjectValidation();
CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U);
- class_linker_ = new ClassLinker(intern_table_);
+ 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) {
@@ -1829,8 +1834,8 @@
}
void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
- if (preinitialization_transaction_ != nullptr) {
- preinitialization_transaction_->VisitRoots(visitor);
+ for (auto& transaction : preinitialization_transactions_) {
+ transaction->VisitRoots(visitor);
}
}
@@ -2062,28 +2067,32 @@
}
// 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 {
@@ -2091,67 +2100,86 @@
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,
@@ -2160,7 +2188,7 @@
bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteFieldReference(obj,
+ GetTransaction()->RecordWriteFieldReference(obj,
field_offset,
value.Ptr(),
is_volatile);
@@ -2169,38 +2197,38 @@
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) {
@@ -2418,5 +2446,4 @@
GetClassLinker()->VisitClasses(&visitor);
}
}
-
} // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 7e4b896..0c1344e 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -454,16 +454,17 @@
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_);
@@ -841,8 +842,11 @@
// 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_;
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 9e62aa6..e923aff 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -34,11 +34,15 @@
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,11 +105,41 @@
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_;
}
+bool Transaction::WriteConstraint(mirror::Object* obj, ArtField* field) {
+ MutexLock mu(Thread::Current(), log_lock_);
+ if (strict_ // no constraint for boot image
+ && field->IsStatic() // no constraint instance updating
+ && obj != root_) { // modifying other classes' static field, fail
+ return true;
+ }
+ return false;
+}
+
+bool Transaction::ReadConstraint(mirror::Object* obj, ArtField* field) {
+ DCHECK(field->IsStatic());
+ DCHECK(obj->IsClass());
+ MutexLock mu(Thread::Current(), log_lock_);
+ if (!strict_ || // no constraint for boot image
+ obj == root_) { // self-updating, pass
+ return false;
+ }
+ return true;
+}
+
void Transaction::RecordWriteFieldBoolean(mirror::Object* obj,
MemberOffset field_offset,
uint8_t value,
@@ -226,15 +260,17 @@
}
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 +452,7 @@
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 +631,39 @@
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 22518f6..4e9cde5 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -45,7 +45,7 @@
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 @@
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,
@@ -126,6 +135,14 @@
REQUIRES(!log_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ bool ReadConstraint(mirror::Object* obj, ArtField* field)
+ REQUIRES(!log_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ bool WriteConstraint(mirror::Object* obj, ArtField* field)
+ REQUIRES(!log_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
private:
class ObjectLog : public ValueObject {
public:
@@ -289,6 +306,8 @@
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/vdex_file.h b/runtime/vdex_file.h
index 0351fd3..63058cf 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -72,8 +72,8 @@
private:
static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' };
- // Last update: Change method lookup.
- static constexpr uint8_t kVdexVersion[] = { '0', '0', '9', '\0' };
+ // Last update: Use set for unverified_classes_.
+ static constexpr uint8_t kVdexVersion[] = { '0', '1', '0', '\0' };
uint8_t magic_[4];
uint8_t version_[4];
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 6149f0d..312d8df 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2899,10 +2899,12 @@
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 @@
} 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());
@@ -3857,10 +3861,20 @@
// TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_method);
+ bool must_fail = false;
+ // This is traditional and helps with screwy bytecode. It will tell you that, yes, a method
+ // exists, but that it's called incorrectly. This significantly helps debugging, as locally it's
+ // hard to see the differences.
+ // If we don't have res_method here we must fail. Just use this bool to make sure of that with a
+ // DCHECK.
if (res_method == nullptr) {
+ must_fail = true;
// Try to find the method also with the other type for better error reporting below
// but do not store such bogus lookup result in the DexCache or VerifierDeps.
if (klass->IsInterface()) {
+ // NB This is normally not really allowed but we want to get any static or private object
+ // methods for error message purposes. This will never be returned.
+ // TODO We might want to change the verifier to not require this.
res_method = klass->FindClassMethod(dex_cache_.Get(), dex_method_idx, pointer_size);
} else {
// If there was an interface method with the same signature,
@@ -3918,6 +3932,20 @@
}
}
+ // Check specifically for non-public object methods being provided for interface dispatch. This
+ // can occur if we failed to find a method with FindInterfaceMethod but later find one with
+ // FindClassMethod for error message use.
+ if (method_type == METHOD_INTERFACE &&
+ res_method->GetDeclaringClass()->IsObjectClass() &&
+ !res_method->IsPublic()) {
+ Fail(VERIFY_ERROR_NO_METHOD) << "invoke-interface " << klass->PrettyDescriptor() << "."
+ << dex_file_->GetMethodName(method_id) << " "
+ << dex_file_->GetMethodSignature(method_id) << " resolved to "
+ << "non-public object method " << res_method->PrettyMethod() << " "
+ << "but non-public Object methods are excluded from interface "
+ << "method resolution.";
+ return nullptr;
+ }
// Check if access is allowed.
if (!referrer.CanAccessMember(res_method->GetDeclaringClass(), res_method->GetAccessFlags())) {
Fail(VERIFY_ERROR_ACCESS_METHOD) << "illegal method access (call "
@@ -3946,6 +3974,13 @@
"type of " << res_method->PrettyMethod();
return nullptr;
}
+ // Make sure we weren't expecting to fail.
+ DCHECK(!must_fail) << "invoke type (" << method_type << ")"
+ << klass->PrettyDescriptor() << "."
+ << dex_file_->GetMethodName(method_id) << " "
+ << dex_file_->GetMethodSignature(method_id) << " unexpectedly resolved to "
+ << res_method->PrettyMethod() << " without error. Initially this method was "
+ << "not found so we were expecting to fail for some reason.";
return res_method;
}
@@ -5261,10 +5296,12 @@
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 112eec8..0481f24 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(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());
@@ -55,9 +59,7 @@
MergeSets(my_deps->classes_, other_deps.classes_);
MergeSets(my_deps->fields_, other_deps.fields_);
MergeSets(my_deps->methods_, other_deps.methods_);
- for (dex::TypeIndex entry : other_deps.unverified_classes_) {
- my_deps->unverified_classes_.push_back(entry);
- }
+ MergeSets(my_deps->unverified_classes_, other_deps.unverified_classes_);
}
}
@@ -503,7 +505,7 @@
VerifierDeps* thread_deps = GetThreadLocalVerifierDeps();
if (thread_deps != nullptr) {
DexFileDeps* dex_deps = thread_deps->GetDexFileDeps(dex_file);
- dex_deps->unverified_classes_.push_back(type_idx);
+ dex_deps->unverified_classes_.insert(type_idx);
}
}
@@ -582,6 +584,16 @@
return dex::StringIndex(in);
}
+// TODO: Clean this up, if we use a template arg here it confuses the compiler.
+static inline void EncodeTuple(std::vector<uint8_t>* out, const dex::TypeIndex& t) {
+ EncodeUnsignedLeb128(out, Encode(t));
+}
+
+// TODO: Clean this up, if we use a template arg here it confuses the compiler.
+static inline void DecodeTuple(const uint8_t** in, const uint8_t* end, dex::TypeIndex* t) {
+ *t = Decode<dex::TypeIndex>(DecodeUint32WithOverflowCheck(in, end));
+}
+
template<typename T1, typename T2>
static inline void EncodeTuple(std::vector<uint8_t>* out, const std::tuple<T1, T2>& t) {
EncodeUnsignedLeb128(out, Encode(std::get<0>(t)));
@@ -688,13 +700,13 @@
EncodeSet(buffer, deps.classes_);
EncodeSet(buffer, deps.fields_);
EncodeSet(buffer, deps.methods_);
- EncodeUint16Vector(buffer, deps.unverified_classes_);
+ EncodeSet(buffer, deps.unverified_classes_);
}
}
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.
@@ -711,7 +723,7 @@
DecodeSet(&data_start, data_end, &deps->classes_);
DecodeSet(&data_start, data_end, &deps->fields_);
DecodeSet(&data_start, data_end, &deps->methods_);
- DecodeUint16Vector(&data_start, data_end, &deps->unverified_classes_);
+ DecodeSet(&data_start, data_end, &deps->unverified_classes_);
}
CHECK_LE(data_start, data_end);
}
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index b883a9e..4069a11 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -117,10 +117,14 @@
bool ValidateDependencies(Handle<mirror::ClassLoader> class_loader, Thread* self) const
REQUIRES_SHARED(Locks::mutator_lock_);
- const std::vector<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const {
+ const std::set<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const {
return GetDexFileDeps(dex_file)->unverified_classes_;
}
+ bool OutputOnly() const {
+ return output_only_;
+ }
+
private:
static constexpr uint16_t kUnresolvedMarker = static_cast<uint16_t>(-1);
@@ -193,11 +197,13 @@
std::set<MethodResolution> methods_;
// List of classes that were not fully verified in that dex file.
- std::vector<dex::TypeIndex> unverified_classes_;
+ std::set<dex::TypeIndex> unverified_classes_;
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 @@
// 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
similarity index 60%
rename from runtime/simulator/Android.bp
rename to simulator/Android.bp
index 03e3f15..a399289 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 @@
"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 @@
"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
similarity index 92%
rename from runtime/simulator/code_simulator.cc
rename to simulator/code_simulator.cc
index 1a11160..e653dfc 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
similarity index 97%
rename from runtime/simulator/code_simulator_arm64.cc
rename to simulator/code_simulator_arm64.cc
index c7ad1fd..939d2e2 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
similarity index 88%
rename from runtime/simulator/code_simulator_arm64.h
rename to simulator/code_simulator_arm64.h
index 59ea34f..0542593 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 @@
} // 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
similarity index 98%
rename from runtime/code_simulator_container.cc
rename to simulator/code_simulator_container.cc
index d884c58..a5f05dc 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
similarity index 87%
rename from runtime/code_simulator_container.h
rename to simulator/code_simulator_container.h
index 10178ba..31a915e 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 @@
} // 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
similarity index 89%
rename from runtime/simulator/code_simulator.h
rename to simulator/include/code_simulator.h
index bd48909..256ab23 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 @@
} // namespace art
-#endif // ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_
+#endif // ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_
diff --git a/test/075-verification-error/expected.txt b/test/075-verification-error/expected.txt
index 6e4f584..7ccc32c 100644
--- a/test/075-verification-error/expected.txt
+++ b/test/075-verification-error/expected.txt
@@ -10,3 +10,4 @@
Got expected IllegalAccessError (meth-class)
Got expected IllegalAccessError (field-class)
Got expected IllegalAccessError (meth-meth)
+Got expected IncompatibleClassChangeError (interface)
diff --git a/test/075-verification-error/src/BadIfaceImpl.java b/test/075-verification-error/src/BadIfaceImpl.java
new file mode 100644
index 0000000..fa2a970
--- /dev/null
+++ b/test/075-verification-error/src/BadIfaceImpl.java
@@ -0,0 +1,17 @@
+/*
+ * 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 BadIfaceImpl implements BadInterface { }
diff --git a/test/075-verification-error/src/BadInterface.java b/test/075-verification-error/src/BadInterface.java
new file mode 100644
index 0000000..439aba4
--- /dev/null
+++ b/test/075-verification-error/src/BadInterface.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 interface BadInterface {
+ public default Object internalClone() {
+ throw new Error("Should not be called");
+ }
+}
diff --git a/test/075-verification-error/src/Main.java b/test/075-verification-error/src/Main.java
index 3f2881e..13aeaee 100644
--- a/test/075-verification-error/src/Main.java
+++ b/test/075-verification-error/src/Main.java
@@ -28,6 +28,20 @@
testClassNewInstance();
testMissingStuff();
testBadAccess();
+ testBadInterfaceMethod();
+ }
+ /**
+ * Try to create and invoke a non-existant interface method.
+ */
+ static void testBadInterfaceMethod() {
+ BadInterface badiface = new BadIfaceImpl();
+ try {
+ badiface.internalClone();
+ } catch (IncompatibleClassChangeError icce) {
+ // TODO b/64274113 This should really be an NSME
+ System.out.println("Got expected IncompatibleClassChangeError (interface)");
+ if (VERBOSE) System.out.println("--- " + icce);
+ }
}
/**
diff --git a/test/075-verification-error/src2/BadInterface.java b/test/075-verification-error/src2/BadInterface.java
new file mode 100644
index 0000000..5d939cb
--- /dev/null
+++ b/test/075-verification-error/src2/BadInterface.java
@@ -0,0 +1,17 @@
+/*
+ * 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 interface BadInterface { }
+
diff --git a/test/1338-gc-no-los/expected.txt b/test/1338-gc-no-los/expected.txt
new file mode 100644
index 0000000..36bec43
--- /dev/null
+++ b/test/1338-gc-no-los/expected.txt
@@ -0,0 +1 @@
+131072 200 true
diff --git a/test/1338-gc-no-los/info.txt b/test/1338-gc-no-los/info.txt
new file mode 100644
index 0000000..2e27357
--- /dev/null
+++ b/test/1338-gc-no-los/info.txt
@@ -0,0 +1 @@
+Test that the GC works with no large object space. Regression test for b/64393515.
\ No newline at end of file
diff --git a/test/1338-gc-no-los/run b/test/1338-gc-no-los/run
new file mode 100755
index 0000000..f3c43fb
--- /dev/null
+++ b/test/1338-gc-no-los/run
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+./default-run "$@" --runtime-option -XX:LargeObjectSpace=disabled
diff --git a/test/1338-gc-no-los/src-art/Main.java b/test/1338-gc-no-los/src-art/Main.java
new file mode 100644
index 0000000..662fa7e
--- /dev/null
+++ b/test/1338-gc-no-los/src-art/Main.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+import dalvik.system.VMRuntime;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+
+public class Main {
+ public static void main(String[] args) {
+ try {
+ // Allocate a large object.
+ byte[] arr = new byte[128 * 1024];
+ // Allocate a non movable object.
+ byte[] arr2 = (byte[])VMRuntime.getRuntime().newNonMovableArray(Byte.TYPE, 200);
+ // Put the array in a weak reference so that IsMarked is called by the GC.
+ WeakReference weakRef = new WeakReference(arr2);
+ // Do a GC.
+ Runtime.getRuntime().gc();
+ arr[0] = 1;
+ arr2[0] = 1;
+ System.out.println(arr.length + " " + arr2.length + " " + (weakRef.get() != null));
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+}
diff --git a/test/162-method-resolution/expected.txt b/test/162-method-resolution/expected.txt
index 1bf39c9..9b48a4c 100644
--- a/test/162-method-resolution/expected.txt
+++ b/test/162-method-resolution/expected.txt
@@ -41,3 +41,6 @@
Calling Test9User2.test():
Caught java.lang.reflect.InvocationTargetException
caused by java.lang.IncompatibleClassChangeError
+Calling Test10User.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
diff --git a/test/162-method-resolution/jasmin/Test10Base.j b/test/162-method-resolution/jasmin/Test10Base.j
new file mode 100644
index 0000000..628f38d
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test10Base.j
@@ -0,0 +1,25 @@
+; 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.
+
+.class public Test10Base
+.super java/lang/Object
+.implements Test10Interface
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial java/lang/Object/<init>()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test10User.j b/test/162-method-resolution/jasmin/Test10User.j
new file mode 100644
index 0000000..6beadab
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test10User.j
@@ -0,0 +1,36 @@
+; 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.
+
+.class public Test10User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 3
+ .limit locals 3
+ new Test10Base
+ dup
+ invokespecial Test10Base.<init>()V
+ invokestatic Test10User.doInvoke(LTest10Interface;)V
+ return
+.end method
+
+.method public static doInvoke(LTest10Interface;)V
+ .limit stack 3
+ .limit locals 3
+ aload_0
+ invokeinterface Test10Interface.clone()Ljava.lang.Object; 1
+ pop
+ return
+.end method
+
diff --git a/test/162-method-resolution/multidex.jpp b/test/162-method-resolution/multidex.jpp
index 22e3aee..5722f7f 100644
--- a/test/162-method-resolution/multidex.jpp
+++ b/test/162-method-resolution/multidex.jpp
@@ -112,6 +112,16 @@
@@com.android.jack.annotations.ForceInMainDex
class Test9User2
+Test10Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test10Base
+Test10Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test10Interface
+Test10User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test10User
+
Main:
@@com.android.jack.annotations.ForceInMainDex
class Main
diff --git a/test/162-method-resolution/src/Main.java b/test/162-method-resolution/src/Main.java
index fa95aa7..864c878 100644
--- a/test/162-method-resolution/src/Main.java
+++ b/test/162-method-resolution/src/Main.java
@@ -36,6 +36,7 @@
test7();
test8();
test9();
+ test10();
// TODO: How to test that interface method resolution returns the unique
// maximally-specific non-abstract superinterface method if there is one?
@@ -376,6 +377,31 @@
invokeUserTest("Test9User2");
}
+ /*
+ * Test10
+ * -----
+ * Tested function:
+ * public class Test10Base implements Test10Interface { }
+ * public interface Test10Interface { }
+ * Tested invokes:
+ * invoke-interface Test10Interface.clone()Ljava/lang/Object; from Test10Caller in first dex
+ * TODO b/64274113 This should throw a NSME (JLS 13.4.12) but actually throws an ICCE.
+ * expected: Throws NoSuchMethodError (JLS 13.4.12)
+ * actual: Throws IncompatibleClassChangeError
+ *
+ * This test is simulating compiling Test10Interface with "public Object clone()" method, along
+ * with every other class. Then we delete "clone" from Test10Interface only, which under JLS
+ * 13.4.12 is expected to be binary incompatible and throw a NoSuchMethodError.
+ *
+ * Files:
+ * jasmin/Test10Base.j - implements Test10Interface
+ * jasmin/Test10Interface.java - defines empty interface
+ * jasmin/Test10User.j - invokeinterface Test10Interface.clone()Ljava/lang/Object;
+ */
+ private static void test10() throws Exception {
+ invokeUserTest("Test10User");
+ }
+
private static void invokeUserTest(String userName) throws Exception {
System.out.println("Calling " + userName + ".test():");
try {
diff --git a/test/162-method-resolution/src/Test10Interface.java b/test/162-method-resolution/src/Test10Interface.java
new file mode 100644
index 0000000..3c75ea5
--- /dev/null
+++ b/test/162-method-resolution/src/Test10Interface.java
@@ -0,0 +1,18 @@
+/*
+ * 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 interface Test10Interface { }
+
diff --git a/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/test/1911-get-local-var-table/expected.txt
similarity index 100%
copy from runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
copy to 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 0000000..43955e5
--- /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 0000000..51875a7
--- /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 0000000..4e0c9e4
--- /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 0000000..bbb89f7
--- /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 0000000..22e21be
--- /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 0000000..16e62cc
--- /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 0000000..4dd9054
--- /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 0000000..f2c5ce8
--- /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 0000000..87a7b35
--- /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 0000000..51875a7
--- /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 0000000..9ff69f8
--- /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 0000000..bbb89f7
--- /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 0000000..22e21be
--- /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 0000000..2ea2f20
--- /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 0000000..16e62cc
--- /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 0000000..24149f4
--- /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 0000000..23f4992
--- /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 0000000..86ac743
--- /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 0000000..51875a7
--- /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 0000000..45565c2
--- /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 0000000..bbb89f7
--- /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 0000000..22e21be
--- /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 0000000..2ea2f20
--- /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 0000000..16e62cc
--- /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 0000000..417138a
--- /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 0000000..4117942
--- /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 0000000..9fc3d62
--- /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 0000000..03aa59e
--- /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 0000000..51875a7
--- /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 0000000..163221e
--- /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 0000000..bbb89f7
--- /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 0000000..22e21be
--- /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 0000000..2ea2f20
--- /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 0000000..16e62cc
--- /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 0000000..c09f519
--- /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 0000000..de39ca9
--- /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 0000000..c59f50d
--- /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 0000000..51875a7
--- /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 0000000..47e6767
--- /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 0000000..bbb89f7
--- /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 0000000..22e21be
--- /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 0000000..2ea2f20
--- /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 0000000..16e62cc
--- /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 0000000..a99a487
--- /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 0000000..343d493
--- /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 0000000..7342af7
--- /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 0000000..51875a7
--- /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 0000000..7d0cd21
--- /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 0000000..bbb89f7
--- /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 0000000..22e21be
--- /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 0000000..2ea2f20
--- /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 0000000..16e62cc
--- /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 0000000..3e5bce2
--- /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 0000000..4c9efcf
--- /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 0000000..e72034a
--- /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 0000000..51875a7
--- /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 0000000..c055a5c
--- /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 0000000..bbb89f7
--- /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 0000000..b12c3df
--- /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 0000000..16e62cc
--- /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 0000000..def7530
--- /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 810f0fa..6fd9cdd 100644
--- a/test/550-checker-multiply-accumulate/src/Main.java
+++ b/test/550-checker-multiply-accumulate/src/Main.java
@@ -434,7 +434,7 @@
/// 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 @@
/// 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/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java
index aca997e..fc7bcb2 100644
--- a/test/623-checker-loop-regressions/src/Main.java
+++ b/test/623-checker-loop-regressions/src/Main.java
@@ -426,6 +426,21 @@
}
}
+ // Environment of an instruction, removed during SimplifyInduction, should be adjusted.
+ //
+ /// CHECK-START: void Main.inductionMax(int[]) loop_optimization (before)
+ /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: Phi loop:<<Loop>> outer_loop:none
+ //
+ /// CHECK-START: void Main.inductionMax(int[]) loop_optimization (after)
+ /// CHECK-NOT: Phi
+ private static void inductionMax(int[] a) {
+ int s = 0;
+ for (int i = 0; i < 10; i++) {
+ s = Math.max(s, 5);
+ }
+ }
+
public static void main(String[] args) {
expectEquals(10, earlyExitFirst(-1));
for (int i = 0; i <= 10; i++) {
@@ -539,6 +554,8 @@
expectEquals((byte)(i + 1), b1[i]);
}
+ inductionMax(yy);
+
System.out.println("passed");
}
diff --git a/test/660-clinit/expected.txt b/test/660-clinit/expected.txt
index e103a2c..9eb4941 100644
--- a/test/660-clinit/expected.txt
+++ b/test/660-clinit/expected.txt
@@ -1,4 +1,8 @@
JNI_OnLoad called
+A.a: 5
+A.a: 10
+B.b: 10
+C.c: 10
X: 4950
Y: 5730
str: Hello World!
diff --git a/test/660-clinit/profile b/test/660-clinit/profile
new file mode 100644
index 0000000..0239f22
--- /dev/null
+++ b/test/660-clinit/profile
@@ -0,0 +1,10 @@
+LMain;
+LClInit;
+LDay;
+LA;
+LB;
+LC;
+LG;
+LGs;
+LObjectRef;
+
diff --git a/test/660-clinit/run b/test/660-clinit/run
new file mode 100644
index 0000000..d24ef42
--- /dev/null
+++ b/test/660-clinit/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# 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.
+
+exec ${RUN} $@ --profile
diff --git a/test/660-clinit/src/Main.java b/test/660-clinit/src/Main.java
index f547692..f9b068e 100644
--- a/test/660-clinit/src/Main.java
+++ b/test/660-clinit/src/Main.java
@@ -26,7 +26,26 @@
}
expectNotPreInit(Day.class);
- expectNotPreInit(ClInit.class);
+ expectNotPreInit(ClInit.class); // should pass
+ expectNotPreInit(A.class); // should pass
+ expectNotPreInit(B.class); // should fail
+ expectNotPreInit(C.class); // should fail
+ expectNotPreInit(G.class); // should fail
+ expectNotPreInit(Gs.class); // should fail
+ expectNotPreInit(Gss.class); // should fail
+
+ expectNotPreInit(Add.class);
+ expectNotPreInit(Mul.class);
+ expectNotPreInit(ObjectRef.class);
+
+ A x = new A();
+ System.out.println("A.a: " + A.a);
+
+ B y = new B();
+ C z = new C();
+ System.out.println("A.a: " + A.a);
+ System.out.println("B.b: " + B.b);
+ System.out.println("C.c: " + C.c);
ClInit c = new ClInit();
int aa = c.a;
@@ -113,3 +132,59 @@
}
}
+class A {
+ public static int a = 2;
+ static {
+ a = 5; // self-updating, pass
+ }
+}
+
+class B {
+ public static int b;
+ static {
+ A.a = 10; // write other's static field, fail
+ b = A.a; // read other's static field, fail
+ }
+}
+
+class C {
+ public static int c;
+ static {
+ c = A.a; // read other's static field, fail
+ }
+}
+
+class G {
+ static G g;
+ static int i;
+ static {
+ g = new Gss(); // fail because recursive dependency
+ i = A.a; // read other's static field, fail
+ }
+}
+
+// Gs will be successfully initialized as G's status is initializing at that point, which will
+// later aborted but Gs' transaction is already committed.
+// Instantiation of Gs will fail because we try to invoke G's <init>
+// but G's status will be StatusVerified. INVOKE_DIRECT will not initialize class.
+class Gs extends G {} // fail because super class can't be initialized
+class Gss extends Gs {}
+
+// pruned because holding reference to non-image class
+class ObjectRef {
+ static Class<?> klazz[] = new Class<?>[]{Add.class, Mul.class};
+}
+
+// non-image
+class Add {
+ static int exec(int a, int b) {
+ return a + b;
+ }
+}
+
+// non-image
+class Mul {
+ static int exec(int a, int b) {
+ return a * b;
+ }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 44cb4f6..fab664a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -252,8 +252,10 @@
"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 @@
"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 ede485a..e989e39 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -453,6 +453,10 @@
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 09e76fa..5aa7923 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -707,16 +707,16 @@
"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 | no-prebuild",
"description": ["Tests <clinit> for app images, which --no-image, --no-prebuild and",
"--no-dex2oat do not create"]
+ },
+ {
+ "tests": ["961-default-iface-resolution-gen",
+ "964-default-iface-init-gen",
+ "968-default-partial-compile-gen"],
+ "env_vars": {"SANITIZE_HOST": "address"},
+ "description": ["Test hits dex2oat watchdog timeout (60sec) on art-asan"]
}
]
diff --git a/test/run-test b/test/run-test
index 486b465..e6196a0 100755
--- a/test/run-test
+++ b/test/run-test
@@ -45,7 +45,7 @@
export RUN="${progdir}/etc/run-test-jar"
export DEX_LOCATION=/data/run-test/${test_dir}
export NEED_DEX="true"
-export USE_JACK="true"
+export USE_JACK="false"
export USE_DESUGAR="true"
export SMALI_ARGS=""
@@ -926,6 +926,11 @@
tail -n 3000 "$tmp_dir/$strace_output"
echo '####################'
fi
+ if [ "x$target_mode" = "xno" -a "x$SANITIZE_HOST" = "xaddress" ]; then
+ # Run the stack script to symbolize any ASAN aborts on the host for SANITIZE_HOST. The
+ # tools used by the given ABI work for both x86 and x86-64.
+ echo "ABI: 'x86_64'" | cat - "$output" | $ANDROID_BUILD_TOP/development/scripts/stack | tail -n 3000
+ fi
echo ' '
fi
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index b996b04..d45d009 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -33,7 +33,8 @@
'TARGET_ARCH',
'HOST_PREFER_32_BIT',
'HOST_OUT_EXECUTABLES',
- 'ANDROID_JAVA_TOOLCHAIN']
+ 'ANDROID_JAVA_TOOLCHAIN',
+ 'ANDROID_COMPILE_WITH_JACK']
_DUMP_MANY_VARS = None # To be set to a dictionary with above list being the keys,
# and the build variable being the value.
def _dump_many_vars(var_name):
@@ -78,6 +79,15 @@
def _get_build_var(var_name):
return _dump_many_vars(var_name)
+def _get_build_var_boolean(var, default):
+ val = _get_build_var(var)
+ if val:
+ if val == "True" or val == "true":
+ return True
+ if val == "False" or val == "false":
+ return False
+ return default
+
def get_env(key):
return _env.get(key)
@@ -97,7 +107,7 @@
ANDROID_BUILD_TOP = _get_android_build_top()
# Compiling with jack? Possible values in (True, False, 'default')
-ANDROID_COMPILE_WITH_JACK = _getEnvBoolean('ANDROID_COMPILE_WITH_JACK', 'default')
+ANDROID_COMPILE_WITH_JACK = _get_build_var_boolean('ANDROID_COMPILE_WITH_JACK', 'default')
# Directory used for temporary test files on the host.
ART_HOST_TEST_DIR = tempfile.mkdtemp(prefix = 'test-art-')
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index 51d3406..7280102 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 @@
.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 @@
}
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 @@
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 @@
}
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 78d238a..a47a402 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -43,7 +43,7 @@
// 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 0000000..e284b52
--- /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 0000000..f2a8e9a
--- /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/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc
index 7a9d1e0..1f8ceff 100644
--- a/test/ti-agent/trace_helper.cc
+++ b/test/ti-agent/trace_helper.cc
@@ -388,10 +388,12 @@
data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr;
data->in_callback = false;
- void* old_data = nullptr;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+ TraceData* old_data = nullptr;
+ if (JvmtiErrorToException(env, jvmti_env,
+ jvmti_env->GetEnvironmentLocalStorage(
+ reinterpret_cast<void**>(&old_data)))) {
return;
- } else if (old_data != nullptr) {
+ } else if (old_data != nullptr && old_data->test_klass != nullptr) {
ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
return;
@@ -455,6 +457,21 @@
extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing(
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
+ TraceData* data = nullptr;
+ if (JvmtiErrorToException(
+ env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ // If data is null then we haven't ever enabled tracing so we don't need to do anything.
+ if (data == nullptr || data->test_klass == nullptr) {
+ return;
+ }
+ env->DeleteGlobalRef(data->test_klass);
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ // Clear test_klass so we know this isn't being used
+ data->test_klass = nullptr;
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_FIELD_ACCESS,
diff --git a/tools/art b/tools/art
index bc0c85e..dea7ebd 100644
--- a/tools/art
+++ b/tools/art
@@ -17,7 +17,6 @@
# Android (e.g. mksh).
# Globals
-ARCHS={arm,arm64,mips,mips64,x86,x86_64}
ART_BINARY=dalvikvm
DELETE_ANDROID_DATA="no"
LAUNCH_WRAPPER=
@@ -120,26 +119,96 @@
env "$@"
}
+# Parse a colon-separated list into an array (e.g. "foo.dex:bar.dex" -> (foo.dex bar.dex))
+PARSE_CLASSPATH_RESULT=() # Return value will be here due to shell limitations.
+parse_classpath() {
+ local cp="$1"
+ local oldifs=$IFS
+
+ local cp_array
+ cp_array=()
+
+ IFS=":"
+ for part in $cp; do
+ cp_array+=("$part")
+ done
+ IFS=$oldifs
+
+ PARSE_CLASSPATH_RESULT=("${cp_array[@]}")
+}
+
+# Sets 'PARSE_CLASSPATH_RESULT' to an array of class path dex files.
+# e.g. (-cp foo/classes.dex:bar/classes.dex) -> (foo/classes.dex bar/classes.dex)
+find_cp_in_args() {
+ local found="false"
+ local index=0
+ local what
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -cp|-classpath)
+ parse_classpath "$2"
+ # Sets 'PARSE_CLASSPATH_RESULT' to an array of class path dex files.
+ # Subsequent parses will overwrite the preceding.
+ shift
+ ;;
+ esac
+ shift
+ done
+}
+
+# Delete the 'oat' directories relative to the classpath's dex files.
+# e.g. (foo/classes.dex bar/classes.dex) would delete (foo/oat bar/oat) directories.
+cleanup_oat_directory() {
+ local classpath
+ classpath=("$@")
+
+ local dirpath
+
+ for path in "${classpath[@]}"; do
+ dirpath="$(dirname "$path")"
+ [[ -d "$dirpath" ]] && verbose_run rm -rf "$dirpath/oat"
+ done
+}
+
+# Parse -cp <CP>, -classpath <CP>, and $CLASSPATH to find the dex files.
+# Each dex file's directory will have an 'oat' file directory, delete it.
+# Input: Command line arguments to the art script.
+# e.g. -cp foo/classes.dex:bar/classes.dex would delete (foo/oat bar/oat) directories.
+cleanup_oat_directory_for_classpath() {
+ # First try: Use $CLASSPATH environment variable.
+ parse_classpath "$CLASSPATH"
+ # Second try: Look for latest -cp or -classpath arg which will take precedence.
+ find_cp_in_args "$@"
+
+ cleanup_oat_directory "${PARSE_CLASSPATH_RESULT[@]}"
+}
+
+# Attempt to find $ANDROID_ROOT/framework/<isa>/core.art' without knowing what <isa> is.
+function check_if_boot_image_file_exists() {
+ local image_location_dir="$1"
+ local image_location_name="$2"
+
+ # Expand image_files to a list of existing image files on the disk.
+ # If no such files exist, it expands to single element 'dir/*/file' with a literal '*'.
+ local image_files
+ image_files=("$image_location_dir"/*/"$image_location_name") # avoid treating "*" as literal.
+
+ # Array always has at least 1 element. Test explicitly whether the file exists.
+ [[ -e "${image_files[0]}" ]]
+}
+
# Automatically find the boot image location. It uses core.art by default.
# On a real device, it might only have a boot.art, so use that instead when core.art does not exist.
function detect_boot_image_location() {
local image_location_dir="$ANDROID_ROOT/framework"
local image_location_name="core.art"
- local maybe_arch
- local core_image_exists="false"
-
- # Parse ARCHS={a,b,c,d} syntax.
- local array
- IFS=, read -a array <<< "${ARCHS:1:(-1)}";
- for maybe_arch in "${array[@]}"; do
- if [[ -e "$image_location_dir/$maybe_arch/$image_location_name" ]]; then
- core_image_exists="true"
- break
- fi
- done
-
- if [[ "$core_image_exists" == "false" ]]; then
+ # If there are no existing core.art, try to find boot.art.
+ # If there is no boot.art then leave it as-is, assumes -Ximage is explicitly used.
+ # Otherwise let dalvikvm give the error message about an invalid image file.
+ if ! check_if_boot_image_file_exists "$image_location_dir" "core.art" && \
+ check_if_boot_image_file_exists "$image_location_dir" "boot.art"; then
image_location_name="boot.art"
fi
@@ -147,9 +216,15 @@
echo "$image_location"
}
+# Runs dalvikvm, returns its exit code.
+# (Oat directories are cleaned up in between runs)
function run_art() {
local image_location="$(detect_boot_image_location)"
+ local ret
+ # First cleanup any left-over 'oat' files from the last time dalvikvm was run.
+ cleanup_oat_directory_for_classpath "$@"
+ # Run dalvikvm.
verbose_run ANDROID_DATA=$ANDROID_DATA \
ANDROID_ROOT=$ANDROID_ROOT \
LD_LIBRARY_PATH=$LD_LIBRARY_PATH \
@@ -160,6 +235,13 @@
-Xnorelocate \
-Ximage:"$image_location" \
"$@"
+ ret=$?
+
+ # Avoid polluting disk with 'oat' files after dalvikvm has finished.
+ cleanup_oat_directory_for_classpath "$@"
+
+ # Forward exit code of dalvikvm.
+ return $ret
}
while [[ "$1" = "-"* ]]; do
@@ -251,7 +333,7 @@
# by default.
ANDROID_DATA="$ANDROID_DATA/local/tmp/android-data$$"
fi
- mkdir -p $ANDROID_DATA/dalvik-cache/$ARCHS
+ mkdir -p "$ANDROID_DATA"
DELETE_ANDROID_DATA="yes"
fi
@@ -264,7 +346,7 @@
# Create the profile. The runtime expects profiles to be created before
# execution.
PROFILE_PATH="$ANDROID_DATA/primary.prof"
- touch $PROFILE_PATH
+ touch "$PROFILE_PATH"
# Replace the compiler filter with quicken so that we
# can capture the profile.
@@ -282,13 +364,15 @@
EXIT_STATUS=$?
if [ $EXIT_STATUS != 0 ]; then
- cat "$ANDROID_DATA/profile_gen.log"
+ echo "Profile run failed: " >&2
+ cat "$ANDROID_DATA/profile_gen.log" >&2
clean_android_data
exit $EXIT_STATUS
fi
- # Wipe dalvik-cache to prepare it for the next invocation.
- rm -rf $ANDROID_DATA/dalvik-cache/$ARCHS/*
+ # Wipe dalvik-cache so that a subsequent run_art must regenerate it.
+ # Leave $ANDROID_DATA intact since it contains our profile file.
+ rm -rf "$ANDROID_DATA/dalvik-cache"
# Append arguments so next invocation of run_art uses the profile.
EXTRA_OPTIONS+=(-Xcompiler-option --profile-file="$PROFILE_PATH")
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 75694c3..4f99ac3 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -19,6 +19,8 @@
exit 1
fi
+source build/envsetup.sh >&/dev/null # for get_build_var
+
# Logic for setting out_dir from build/make/core/envsetup.mk:
if [[ -z $OUT_DIR ]]; then
if [[ -z $OUT_DIR_COMMON_BASE ]]; then
@@ -30,10 +32,7 @@
out_dir=${OUT_DIR}
fi
-using_jack=true
-if [[ $ANDROID_COMPILE_WITH_JACK == false ]]; then
- using_jack=false
-fi
+using_jack=$(get_build_var ANDROID_COMPILE_WITH_JACK)
java_libraries_dir=${out_dir}/target/common/obj/JAVA_LIBRARIES
common_targets="vogar core-tests apache-harmony-jdwp-tests-hostdex jsr166-tests mockito-target"
@@ -63,7 +62,7 @@
fi
done
-if $using_jack; then
+if [[ $using_jack == "true" ]]; then
common_targets="$common_targets ${out_dir}/host/linux-x86/bin/jack"
fi
diff --git a/tools/cpplint_presubmit.py b/tools/cpplint_presubmit.py
index 4781517..b42a691 100755
--- a/tools/cpplint_presubmit.py
+++ b/tools/cpplint_presubmit.py
@@ -21,7 +21,7 @@
import subprocess
import sys
-IGNORED_FILES = {"runtime/elf.h", "runtime/openjdkjvmti/include/jvmti.h"}
+IGNORED_FILES = {"runtime/elf.h", "openjdkjvmti/include/jvmti.h"}
INTERESTING_SUFFIXES = {".h", ".cc"}
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Executor.java b/tools/dexfuzz/src/dexfuzz/executors/Executor.java
index 074672d..0367a83 100644
--- a/tools/dexfuzz/src/dexfuzz/executors/Executor.java
+++ b/tools/dexfuzz/src/dexfuzz/executors/Executor.java
@@ -114,8 +114,6 @@
commandBuilder.append("--oat-file=output.oat ");
commandBuilder.append("--android-root=").append(device.getAndroidHostOut()).append(" ");
- commandBuilder.append("--runtime-arg -classpath ");
- commandBuilder.append("--runtime-arg ").append(programName).append(" ");
commandBuilder.append("--dex-file=").append(programName).append(" ");
commandBuilder.append("--compiler-filter=quicken --runtime-arg -Xnorelocate ");
diff --git a/tools/generate_cmake_lists.py b/tools/generate_cmake_lists.py
new file mode 100755
index 0000000..6c3ce08
--- /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 016d708..7990c6c 100644
--- a/tools/jfuzz/jfuzz.cc
+++ b/tools/jfuzz/jfuzz.cc
@@ -55,7 +55,7 @@
* 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 @@
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 7e72aa1..58bc737 100755
--- a/tools/jfuzz/run_jfuzz_test.py
+++ b/tools/jfuzz/run_jfuzz_test.py
@@ -524,7 +524,9 @@
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)
diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt
index b4c6f2b..d4a0423 100644
--- a/tools/libcore_gcstress_debug_failures.txt
+++ b/tools/libcore_gcstress_debug_failures.txt
@@ -9,7 +9,9 @@
result: EXEC_FAILED,
modes: [device],
names: ["libcore.icu.TransliteratorTest#testAll",
+ "libcore.icu.RelativeDateTimeFormatterTest#test_bug25821045",
"libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndTimeout",
+ "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndNoTimeout",
"libcore.java.util.TimeZoneTest#testSetDefaultDeadlock",
"org.apache.harmony.tests.java.util.TimerTest#testThrowingTaskKillsTimerThread"]
}
diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh
index 17c84b4..d2322bb 100755
--- a/tools/run-jdwp-tests.sh
+++ b/tools/run-jdwp-tests.sh
@@ -19,24 +19,19 @@
exit 1
fi
-if [ -z "$ANDROID_JAVA_TOOLCHAIN" ] ; then
- source build/envsetup.sh
- setpaths # include platform prebuilt java, javac, etc in $PATH.
-fi
+source build/envsetup.sh >&/dev/null # for get_build_var, setpaths
+setpaths # include platform prebuilt java, javac, etc in $PATH.
if [ -z "$ANDROID_HOST_OUT" ] ; then
ANDROID_HOST_OUT=${OUT_DIR-$ANDROID_BUILD_TOP/out}/host/linux-x86
fi
-using_jack=true
-if [[ $ANDROID_COMPILE_WITH_JACK == false ]]; then
- using_jack=false
-fi
+using_jack=$(get_build_var ANDROID_COMPILE_WITH_JACK)
function jlib_suffix {
local str=$1
local suffix="jar"
- if $using_jack; then
+ if [[ $using_jack == "true" ]]; then
suffix="jack"
fi
echo "$str.$suffix"
@@ -166,7 +161,7 @@
art_debugee="$art_debugee -verbose:jdwp"
fi
-if $using_jack; then
+if [[ $using_jack == "true" ]]; then
toolchain_args="--toolchain jack --language JN --jack-arg -g"
else
toolchain_args="--toolchain jdk --language CUR"
diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh
index d549098..eecdd2f 100755
--- a/tools/run-libcore-tests.sh
+++ b/tools/run-libcore-tests.sh
@@ -19,10 +19,8 @@
exit 1
fi
-if [ -z "$ANDROID_JAVA_TOOLCHAIN" ] ; then
- source build/envsetup.sh
- setpaths # include platform prebuilt java, javac, etc in $PATH.
-fi
+source build/envsetup.sh >&/dev/null # for get_build_var, setpaths
+setpaths # include platform prebuilt java, javac, etc in $PATH.
if [ -z "$ANDROID_PRODUCT_OUT" ] ; then
JAVA_LIBRARIES=out/target/common/obj/JAVA_LIBRARIES
@@ -30,16 +28,13 @@
JAVA_LIBRARIES=${ANDROID_PRODUCT_OUT}/../../common/obj/JAVA_LIBRARIES
fi
-using_jack=true
-if [[ $ANDROID_COMPILE_WITH_JACK == false ]]; then
- using_jack=false
-fi
+using_jack=$(get_build_var ANDROID_COMPILE_WITH_JACK)
function classes_jar_path {
local var="$1"
local suffix="jar"
- if $using_jack; then
+ if [[ $using_jack == "true" ]]; then
suffix="jack"
fi
@@ -151,7 +146,7 @@
vogar_args="$vogar_args --timeout 480"
# Switch between using jack or javac+desugar+dx
-if $using_jack; then
+if [[ $using_jack == "true" ]]; then
vogar_args="$vogar_args --toolchain jack --language JO"
else
vogar_args="$vogar_args --toolchain jdk --language CUR"