Merge "ART: Add zero-padding to allocation request for String."
diff --git a/Android.mk b/Android.mk
index 49b61bb..8859d3a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -87,6 +87,7 @@
include $(art_path)/patchoat/Android.mk
include $(art_path)/dalvikvm/Android.mk
include $(art_path)/tools/Android.mk
+include $(art_path)/tools/ahat/Android.mk
include $(art_path)/tools/dexfuzz/Android.mk
include $(art_path)/sigchainlib/Android.mk
@@ -240,7 +241,7 @@
# Dexdump/list regression test.
.PHONY: test-art-host-dexdump
-test-art-host-dexdump: $(addprefix $(HOST_OUT_EXECUTABLES)/, dexdump2 dexlist2)
+test-art-host-dexdump: $(addprefix $(HOST_OUT_EXECUTABLES)/, dexdump2 dexlist)
ANDROID_HOST_OUT=$(realpath $(HOST_OUT)) art/test/dexdump/run-all-tests
# Valgrind. Currently only 32b gtests.
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 1db654a..c88d677 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -106,15 +106,14 @@
dexdump2
# The dexlist test requires an image and the dexlist utility.
-# TODO: rename into dexlist when migration completes
ART_GTEST_dexlist_test_HOST_DEPS := \
$(HOST_CORE_IMAGE_default_no-pic_64) \
$(HOST_CORE_IMAGE_default_no-pic_32) \
- $(HOST_OUT_EXECUTABLES)/dexlist2
+ $(HOST_OUT_EXECUTABLES)/dexlist
ART_GTEST_dexlist_test_TARGET_DEPS := \
$(TARGET_CORE_IMAGE_default_no-pic_64) \
$(TARGET_CORE_IMAGE_default_no-pic_32) \
- dexlist2
+ dexlist
# The imgdiag test has dependencies on core.oat since it needs to load it during the test.
# For the host, also add the installed tool (in the base size, that should suffice). For the
diff --git a/compiler/dex/mir_method_info.cc b/compiler/dex/mir_method_info.cc
index be913fe..31c3808 100644
--- a/compiler/dex/mir_method_info.cc
+++ b/compiler/dex/mir_method_info.cc
@@ -105,7 +105,8 @@
// Don't devirt if we are in a different dex file since we can't have direct invokes in
// another dex file unless we always put a direct / patch pointer.
devirt_target = nullptr;
- current_dex_cache.Assign(runtime->GetClassLinker()->FindDexCache(*it->target_dex_file_));
+ current_dex_cache.Assign(runtime->GetClassLinker()->FindDexCache(
+ soa.Self(), *it->target_dex_file_));
CHECK(current_dex_cache.Get() != nullptr);
DexCompilationUnit cu(
mUnit->GetCompilationUnit(), mUnit->GetClassLoader(), mUnit->GetClassLinker(),
diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc
index 6e73ae7..3642b82 100644
--- a/compiler/dex/quick/quick_compiler.cc
+++ b/compiler/dex/quick/quick_compiler.cc
@@ -679,11 +679,8 @@
return nullptr;
}
- if (driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow()) {
- return nullptr;
- }
-
DCHECK(driver->GetCompilerOptions().IsCompilationEnabled());
+ DCHECK(!driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow());
Runtime* const runtime = Runtime::Current();
ClassLinker* const class_linker = runtime->GetClassLinker();
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc
index 273b1628..8eb37cf 100644
--- a/compiler/dex/verified_method.cc
+++ b/compiler/dex/verified_method.cc
@@ -37,11 +37,21 @@
namespace art {
+VerifiedMethod::VerifiedMethod(uint32_t encountered_error_types,
+ bool has_runtime_throw,
+ const SafeMap<uint32_t, std::set<uint32_t>>& string_init_pc_reg_map)
+ : encountered_error_types_(encountered_error_types),
+ has_runtime_throw_(has_runtime_throw),
+ string_init_pc_reg_map_(string_init_pc_reg_map) {
+}
+
const VerifiedMethod* VerifiedMethod::Create(verifier::MethodVerifier* method_verifier,
bool compile) {
- std::unique_ptr<VerifiedMethod> verified_method(new VerifiedMethod);
- verified_method->has_verification_failures_ = method_verifier->HasFailures();
- verified_method->has_runtime_throw_ = method_verifier->HasInstructionThatWillThrow();
+ std::unique_ptr<VerifiedMethod> verified_method(
+ new VerifiedMethod(method_verifier->GetEncounteredFailureTypes(),
+ method_verifier->HasInstructionThatWillThrow(),
+ method_verifier->GetStringInitPcRegMap()));
+
if (compile) {
/* Generate a register map. */
if (!verified_method->GenerateGcMap(method_verifier)) {
@@ -66,8 +76,6 @@
verified_method->GenerateSafeCastSet(method_verifier);
}
- verified_method->SetStringInitPcRegMap(method_verifier->GetStringInitPcRegMap());
-
return verified_method.release();
}
diff --git a/compiler/dex/verified_method.h b/compiler/dex/verified_method.h
index f7d6d67..74fcb07 100644
--- a/compiler/dex/verified_method.h
+++ b/compiler/dex/verified_method.h
@@ -72,22 +72,25 @@
// Returns true if there were any errors during verification.
bool HasVerificationFailures() const {
- return has_verification_failures_;
+ return encountered_error_types_ != 0;
+ }
+
+ uint32_t GetEncounteredVerificationFailures() const {
+ return encountered_error_types_;
}
bool HasRuntimeThrow() const {
return has_runtime_throw_;
}
- void SetStringInitPcRegMap(SafeMap<uint32_t, std::set<uint32_t>>& string_init_pc_reg_map) {
- string_init_pc_reg_map_ = string_init_pc_reg_map;
- }
const SafeMap<uint32_t, std::set<uint32_t>>& GetStringInitPcRegMap() const {
return string_init_pc_reg_map_;
}
private:
- VerifiedMethod() = default;
+ VerifiedMethod(uint32_t encountered_error_types,
+ bool has_runtime_throw,
+ const SafeMap<uint32_t, std::set<uint32_t>>& string_init_pc_reg_map);
/*
* Generate the GC map for a method that has just been verified (i.e. we're doing this as part of
@@ -124,12 +127,12 @@
DequickenMap dequicken_map_;
SafeCastSet safe_cast_set_;
- bool has_verification_failures_ = false;
- bool has_runtime_throw_ = false;
+ const uint32_t encountered_error_types_;
+ const bool has_runtime_throw_;
// Copy of mapping generated by verifier of dex PCs of string init invocations
// to the set of other registers that the receiver has been copied into.
- SafeMap<uint32_t, std::set<uint32_t>> string_init_pc_reg_map_;
+ const SafeMap<uint32_t, std::set<uint32_t>> string_init_pc_reg_map_;
};
} // namespace art
diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h
index 83f391d..8f1987a 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -31,7 +31,7 @@
namespace art {
inline mirror::DexCache* CompilerDriver::GetDexCache(const DexCompilationUnit* mUnit) {
- return mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile(), false);
+ return mUnit->GetClassLinker()->FindDexCache(Thread::Current(), *mUnit->GetDexFile(), false);
}
inline mirror::ClassLoader* CompilerDriver::GetClassLoader(ScopedObjectAccess& soa,
@@ -87,7 +87,7 @@
}
inline mirror::DexCache* CompilerDriver::FindDexCache(const DexFile* dex_file) {
- return Runtime::Current()->GetClassLinker()->FindDexCache(*dex_file, false);
+ return Runtime::Current()->GetClassLinker()->FindDexCache(Thread::Current(), *dex_file, false);
}
inline ArtField* CompilerDriver::ResolveField(
@@ -339,7 +339,8 @@
// Sharpen a virtual call into a direct call. The method_idx is into referrer's
// dex cache, check that this resolved method is where we expect it.
CHECK_EQ(target_method->dex_file, mUnit->GetDexFile());
- DCHECK_EQ(dex_cache.Get(), mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile(), false));
+ DCHECK_EQ(dex_cache.Get(), mUnit->GetClassLinker()->FindDexCache(
+ soa.Self(), *mUnit->GetDexFile(), false));
CHECK_EQ(referrer_class->GetDexCache()->GetResolvedMethod(
target_method->dex_method_index, pointer_size),
resolved_method) << PrettyMethod(resolved_method);
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index d38677e..6d3a960 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -590,14 +590,18 @@
} else if ((access_flags & kAccAbstract) != 0) {
// Abstract methods don't have code.
} else {
- bool has_verified_method = driver->GetVerificationResults()
- ->GetVerifiedMethod(method_ref) != nullptr;
+ const VerifiedMethod* verified_method =
+ driver->GetVerificationResults()->GetVerifiedMethod(method_ref);
bool compile = compilation_enabled &&
// Basic checks, e.g., not <clinit>.
driver->GetVerificationResults()
->IsCandidateForCompilation(method_ref, access_flags) &&
// Did not fail to create VerifiedMethod metadata.
- has_verified_method &&
+ verified_method != nullptr &&
+ // Do not have failures that should punt to the interpreter.
+ !verified_method->HasRuntimeThrow() &&
+ (verified_method->GetEncounteredVerificationFailures() &
+ (verifier::VERIFY_ERROR_FORCE_INTERPRETER | verifier::VERIFY_ERROR_LOCKING)) == 0 &&
// Is eligable for compilation by methods-to-compile filter.
driver->IsMethodToCompile(method_ref);
if (compile) {
@@ -620,7 +624,7 @@
method_idx,
class_loader,
dex_file,
- has_verified_method
+ (verified_method != nullptr)
? dex_to_dex_compilation_level
: optimizer::DexToDexCompilationLevel::kRequired);
}
@@ -1171,7 +1175,7 @@
{
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
- dex_file, false);
+ soa.Self(), dex_file, false);
mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
if (resolved_class == nullptr) {
// Erroneous class.
@@ -1197,7 +1201,8 @@
ScopedObjectAccess soa(Thread::Current());
StackHandleScope<1> hs(soa.Self());
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file, false)));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+ soa.Self(), dex_file, false)));
class_linker->ResolveString(dex_file, string_idx, dex_cache);
result = true;
}
@@ -1223,7 +1228,8 @@
*equals_referrers_class = false;
}
ScopedObjectAccess soa(Thread::Current());
- mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file, false);
+ mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
+ soa.Self(), dex_file, false);
// Get type from dex cache assuming it was populated by the verifier
mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
if (resolved_class == nullptr) {
@@ -1261,7 +1267,7 @@
uint32_t type_idx) {
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
- dex_file, false);
+ soa.Self(), dex_file, false);
// Get type from dex cache assuming it was populated by the verifier.
mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
if (resolved_class == nullptr) {
@@ -1290,7 +1296,8 @@
uintptr_t* direct_type_ptr, bool* out_is_finalizable) {
ScopedObjectAccess soa(Thread::Current());
Runtime* runtime = Runtime::Current();
- mirror::DexCache* dex_cache = runtime->GetClassLinker()->FindDexCache(dex_file, false);
+ mirror::DexCache* dex_cache = runtime->GetClassLinker()->FindDexCache(
+ soa.Self(), dex_file, false);
mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
if (resolved_class == nullptr) {
return false;
@@ -1419,7 +1426,8 @@
{
StackHandleScope<2> hs(soa.Self());
Handle<mirror::DexCache> dex_cache_handle(
- hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile(), false)));
+ hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(
+ soa.Self(), *mUnit->GetDexFile(), false)));
Handle<mirror::ClassLoader> class_loader_handle(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())));
resolved_field =
@@ -1469,7 +1477,8 @@
{
StackHandleScope<2> hs(soa.Self());
Handle<mirror::DexCache> dex_cache_handle(
- hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile(), false)));
+ hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(
+ soa.Self(), *mUnit->GetDexFile(), false)));
Handle<mirror::ClassLoader> class_loader_handle(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())));
resolved_field =
@@ -1655,7 +1664,8 @@
// Try to resolve the method and compiling method's class.
StackHandleScope<3> hs(soa.Self());
Handle<mirror::DexCache> dex_cache(
- hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile(), false)));
+ hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(
+ soa.Self(), *mUnit->GetDexFile(), false)));
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())));
uint32_t method_idx = target_method->dex_method_index;
@@ -1907,7 +1917,8 @@
StackHandleScope<2> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader)));
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file, false)));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+ soa.Self(), dex_file, false)));
// Resolve the class.
mirror::Class* klass = class_linker->ResolveType(dex_file, class_def.class_idx_, dex_cache,
class_loader);
@@ -2086,7 +2097,8 @@
* This is to ensure the class is structurally sound for compilation. An unsound class
* will be rejected by the verifier and later skipped during compilation in the compiler.
*/
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file, false)));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+ soa.Self(), dex_file, false)));
std::string error_msg;
if (verifier::MethodVerifier::VerifyClass(soa.Self(), &dex_file, dex_cache, class_loader,
&class_def, true, &error_msg) ==
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index e35d07d..1107599 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -108,7 +108,7 @@
ScopedObjectAccess soa(Thread::Current());
ASSERT_TRUE(java_lang_dex_file_ != nullptr);
const DexFile& dex = *java_lang_dex_file_;
- mirror::DexCache* dex_cache = class_linker_->FindDexCache(dex);
+ mirror::DexCache* dex_cache = class_linker_->FindDexCache(soa.Self(), dex);
EXPECT_EQ(dex.NumStringIds(), dex_cache->NumStrings());
for (size_t i = 0; i < dex_cache->NumStrings(); i++) {
const mirror::String* string = dex_cache->GetResolvedString(i);
@@ -210,8 +210,8 @@
CompileAll(class_loader);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- StackHandleScope<1> hs(self);
ScopedObjectAccess soa(self);
+ StackHandleScope<1> hs(self);
Handle<mirror::ClassLoader> h_loader(hs.NewHandle(
reinterpret_cast<mirror::ClassLoader*>(self->DecodeJObject(class_loader))));
mirror::Class* klass = class_linker->FindClass(self, "LStaticLeafMethods;", h_loader);
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 0e0b224..fdf904d 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -617,7 +617,8 @@
// Unchecked as we hold mutator_lock_ on entry.
ScopedObjectAccessUnchecked soa(Thread::Current());
StackHandleScope<1> hs(soa.Self());
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->FindDexCache(*dex_file_)));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->FindDexCache(
+ Thread::Current(), *dex_file_)));
ArtMethod* method = linker->ResolveMethod(
*dex_file_, it.GetMemberIndex(), dex_cache, NullHandle<mirror::ClassLoader>(), nullptr,
invoke_type);
@@ -668,7 +669,7 @@
SHARED_REQUIRES(Locks::mutator_lock_) {
OatDexMethodVisitor::StartClass(dex_file, class_def_index);
if (dex_cache_ == nullptr || dex_cache_->GetDexFile() != dex_file) {
- dex_cache_ = class_linker_->FindDexCache(*dex_file);
+ dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file);
}
return true;
}
@@ -798,7 +799,8 @@
SHARED_REQUIRES(Locks::mutator_lock_) {
MethodReference ref = patch.TargetMethod();
mirror::DexCache* dex_cache =
- (dex_file_ == ref.dex_file) ? dex_cache_ : class_linker_->FindDexCache(*ref.dex_file);
+ (dex_file_ == ref.dex_file) ? dex_cache_ : class_linker_->FindDexCache(
+ Thread::Current(), *ref.dex_file);
ArtMethod* method = dex_cache->GetResolvedMethod(
ref.dex_method_index, class_linker_->GetImagePointerSize());
CHECK(method != nullptr);
@@ -832,7 +834,7 @@
mirror::Class* GetTargetType(const LinkerPatch& patch)
SHARED_REQUIRES(Locks::mutator_lock_) {
mirror::DexCache* dex_cache = (dex_file_ == patch.TargetTypeDexFile())
- ? dex_cache_ : class_linker_->FindDexCache(*patch.TargetTypeDexFile());
+ ? dex_cache_ : class_linker_->FindDexCache(Thread::Current(), *patch.TargetTypeDexFile());
mirror::Class* type = dex_cache->GetResolvedType(patch.TargetTypeIndex());
CHECK(type != nullptr);
return type;
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index 7b42db8..23ab94e 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -902,7 +902,7 @@
StackHandleScope<4> hs(soa.Self());
Handle<mirror::DexCache> dex_cache(hs.NewHandle(
dex_compilation_unit_->GetClassLinker()->FindDexCache(
- *dex_compilation_unit_->GetDexFile())));
+ soa.Self(), *dex_compilation_unit_->GetDexFile())));
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader())));
ArtMethod* resolved_method = compiler_driver_->ResolveMethod(
@@ -912,7 +912,7 @@
const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile();
Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle(
- outer_compilation_unit_->GetClassLinker()->FindDexCache(outer_dex_file)));
+ outer_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), outer_dex_file)));
Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass()));
// The index at which the method's class is stored in the DexCache's type array.
@@ -1228,7 +1228,7 @@
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader*>(compilation_unit.GetClassLoader())));
Handle<mirror::DexCache> dex_cache(hs.NewHandle(
- compilation_unit.GetClassLinker()->FindDexCache(dex_file)));
+ compilation_unit.GetClassLinker()->FindDexCache(soa.Self(), dex_file)));
return driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, &compilation_unit);
}
@@ -1245,7 +1245,8 @@
ScopedObjectAccess soa(Thread::Current());
StackHandleScope<4> hs(soa.Self());
Handle<mirror::DexCache> dex_cache(hs.NewHandle(
- dex_compilation_unit_->GetClassLinker()->FindDexCache(*dex_compilation_unit_->GetDexFile())));
+ dex_compilation_unit_->GetClassLinker()->FindDexCache(
+ soa.Self(), *dex_compilation_unit_->GetDexFile())));
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader())));
Handle<mirror::Class> cls(hs.NewHandle(compiler_driver_->ResolveClass(
@@ -1264,7 +1265,8 @@
ScopedObjectAccess soa(Thread::Current());
StackHandleScope<4> hs(soa.Self());
Handle<mirror::DexCache> dex_cache(hs.NewHandle(
- dex_compilation_unit_->GetClassLinker()->FindDexCache(*dex_compilation_unit_->GetDexFile())));
+ dex_compilation_unit_->GetClassLinker()->FindDexCache(
+ soa.Self(), *dex_compilation_unit_->GetDexFile())));
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader())));
ArtField* resolved_field = compiler_driver_->ResolveField(
@@ -1277,7 +1279,7 @@
const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile();
Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle(
- outer_compilation_unit_->GetClassLinker()->FindDexCache(outer_dex_file)));
+ outer_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), outer_dex_file)));
Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass()));
// The index at which the field's class is stored in the DexCache's type array.
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index ff90f32..112d42e 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -182,10 +182,10 @@
ArtMethod* resolved_method;
if (invoke_instruction->IsInvokeStaticOrDirect()) {
MethodReference ref = invoke_instruction->AsInvokeStaticOrDirect()->GetTargetMethod();
- resolved_method = class_linker->FindDexCache(*ref.dex_file)->GetResolvedMethod(
+ resolved_method = class_linker->FindDexCache(soa.Self(), *ref.dex_file)->GetResolvedMethod(
ref.dex_method_index, class_linker->GetImagePointerSize());
} else {
- resolved_method = class_linker->FindDexCache(caller_dex_file)->GetResolvedMethod(
+ resolved_method = class_linker->FindDexCache(soa.Self(), caller_dex_file)->GetResolvedMethod(
method_index, class_linker->GetImagePointerSize());
}
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index df6e550..0ac26de 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -132,6 +132,12 @@
// with
// ADD tmp, a, b
// NEG dst, tmp
+ // Note that we cannot optimize `(-a) + (-b)` to `-(a + b)` for floating-point.
+ // When `a` is `-0.0` and `b` is `0.0`, the former expression yields `0.0`,
+ // while the later yields `-0.0`.
+ if (!Primitive::IsIntegralType(binop->GetType())) {
+ return false;
+ }
binop->ReplaceInput(left_neg->GetInput(), 0);
binop->ReplaceInput(right_neg->GetInput(), 1);
left_neg->GetBlock()->RemoveInstruction(left_neg);
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index f6bbace..6f251e8 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -540,11 +540,14 @@
CompilerDriver* compiler_driver,
const DexCompilationUnit& dex_compilation_unit,
PassObserver* pass_observer) const {
- StackHandleScopeCollection handles(Thread::Current());
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScopeCollection handles(soa.Self());
+ soa.Self()->TransitionFromRunnableToSuspended(kNative);
RunOptimizations(graph, compiler_driver, compilation_stats_.get(),
dex_compilation_unit, pass_observer, &handles);
if (graph->HasTryCatch()) {
+ soa.Self()->TransitionFromSuspendedToRunnable();
return nullptr;
}
@@ -582,6 +585,8 @@
ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()),
ArrayRef<const LinkerPatch>(linker_patches));
pass_observer->DumpDisassembly();
+
+ soa.Self()->TransitionFromSuspendedToRunnable();
return compiled_method;
}
@@ -709,7 +714,8 @@
ScopedObjectAccess soa(Thread::Current());
StackHandleScope<4> hs(soa.Self());
ClassLinker* class_linker = dex_compilation_unit.GetClassLinker();
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+ soa.Self(), dex_file)));
Handle<mirror::ClassLoader> loader(hs.NewHandle(
soa.Decode<mirror::ClassLoader*>(class_loader)));
ArtMethod* art_method = compiler_driver->ResolveMethod(
@@ -795,8 +801,8 @@
const DexFile& dex_file) const {
CompilerDriver* compiler_driver = GetCompilerDriver();
CompiledMethod* method = nullptr;
- if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) &&
- !compiler_driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow()) {
+ DCHECK(!compiler_driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow());
+ if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file)) {
method = TryCompile(code_item, access_flags, invoke_type, class_def_idx,
method_idx, jclass_loader, dex_file);
} else {
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index 824f28e..516638b 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -79,6 +79,8 @@
: HOptimization(graph, name),
handles_(handles),
worklist_(graph->GetArena(), kDefaultWorklistSize) {
+ // Mutator lock is required for NewHandle, but annotalysis ignores constructors.
+ ScopedObjectAccess soa(Thread::Current());
ClassLinker* linker = Runtime::Current()->GetClassLinker();
object_class_handle_ = handles_->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangObject));
string_class_handle_ = handles_->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangString));
@@ -87,7 +89,6 @@
handles_->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangThrowable));
if (kIsDebugBuild) {
- ScopedObjectAccess soa(Thread::Current());
DCHECK(ReferenceTypeInfo::IsValidHandle(object_class_handle_));
DCHECK(ReferenceTypeInfo::IsValidHandle(class_class_handle_));
DCHECK(ReferenceTypeInfo::IsValidHandle(string_class_handle_));
@@ -362,7 +363,8 @@
if (kIsDebugBuild) {
ScopedObjectAccess soa(Thread::Current());
ClassLinker* cl = Runtime::Current()->GetClassLinker();
- mirror::DexCache* dex_cache = cl->FindDexCache(instr->AsInvoke()->GetDexFile(), false);
+ mirror::DexCache* dex_cache = cl->FindDexCache(
+ soa.Self(), instr->AsInvoke()->GetDexFile(), false);
ArtMethod* method = dex_cache->GetResolvedMethod(
instr->AsInvoke()->GetDexMethodIndex(), cl->GetImagePointerSize());
DCHECK(method != nullptr);
@@ -393,7 +395,8 @@
DCHECK_EQ(instr->GetType(), Primitive::kPrimNot);
ScopedObjectAccess soa(Thread::Current());
- mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file);
+ mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
+ soa.Self(), dex_file, false);
// Get type from dex cache assuming it was populated by the verifier.
SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact);
}
@@ -431,7 +434,7 @@
ScopedObjectAccess soa(Thread::Current());
ClassLinker* cl = Runtime::Current()->GetClassLinker();
- mirror::DexCache* dex_cache = cl->FindDexCache(info.GetDexFile());
+ mirror::DexCache* dex_cache = cl->FindDexCache(soa.Self(), info.GetDexFile(), false);
ArtField* field = cl->GetResolvedField(info.GetFieldIndex(), dex_cache);
// TODO: There are certain cases where we can't resolve the field.
// b/21914925 is open to keep track of a repro case for this issue.
@@ -450,7 +453,7 @@
void RTPVisitor::VisitLoadClass(HLoadClass* instr) {
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache =
- Runtime::Current()->GetClassLinker()->FindDexCache(instr->GetDexFile());
+ Runtime::Current()->GetClassLinker()->FindDexCache(soa.Self(), instr->GetDexFile(), false);
// Get type from dex cache assuming it was populated by the verifier.
mirror::Class* resolved_class = dex_cache->GetResolvedType(instr->GetTypeIndex());
// TODO: investigating why we are still getting unresolved classes: b/22821472.
@@ -633,7 +636,7 @@
ScopedObjectAccess soa(Thread::Current());
ClassLinker* cl = Runtime::Current()->GetClassLinker();
- mirror::DexCache* dex_cache = cl->FindDexCache(instr->GetDexFile());
+ mirror::DexCache* dex_cache = cl->FindDexCache(soa.Self(), instr->GetDexFile());
ArtMethod* method = dex_cache->GetResolvedMethod(
instr->GetDexMethodIndex(), cl->GetImagePointerSize());
mirror::Class* klass = (method == nullptr) ? nullptr : method->GetReturnType(false);
diff --git a/compiler/utils/assembler.h b/compiler/utils/assembler.h
index 3097cd5..64d76b8 100644
--- a/compiler/utils/assembler.h
+++ b/compiler/utils/assembler.h
@@ -53,9 +53,11 @@
}
namespace x86 {
class X86Assembler;
+ class NearLabel;
}
namespace x86_64 {
class X86_64Assembler;
+ class NearLabel;
}
class ExternalLabel {
@@ -126,7 +128,9 @@
friend class mips::MipsAssembler;
friend class mips64::Mips64Assembler;
friend class x86::X86Assembler;
+ friend class x86::NearLabel;
friend class x86_64::X86_64Assembler;
+ friend class x86_64::NearLabel;
DISALLOW_COPY_AND_ASSIGN(Label);
};
diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc
index 9b3d792..a03f857 100644
--- a/compiler/utils/x86/assembler_x86.cc
+++ b/compiler/utils/x86/assembler_x86.cc
@@ -1510,6 +1510,38 @@
}
+void X86Assembler::j(Condition condition, NearLabel* label) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ if (label->IsBound()) {
+ static const int kShortSize = 2;
+ int offset = label->Position() - buffer_.Size();
+ CHECK_LE(offset, 0);
+ CHECK(IsInt<8>(offset - kShortSize));
+ EmitUint8(0x70 + condition);
+ EmitUint8((offset - kShortSize) & 0xFF);
+ } else {
+ EmitUint8(0x70 + condition);
+ EmitLabelLink(label);
+ }
+}
+
+
+void X86Assembler::jecxz(NearLabel* label) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ if (label->IsBound()) {
+ static const int kShortSize = 2;
+ int offset = label->Position() - buffer_.Size();
+ CHECK_LE(offset, 0);
+ CHECK(IsInt<8>(offset - kShortSize));
+ EmitUint8(0xE3);
+ EmitUint8((offset - kShortSize) & 0xFF);
+ } else {
+ EmitUint8(0xE3);
+ EmitLabelLink(label);
+ }
+}
+
+
void X86Assembler::jmp(Register reg) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xFF);
@@ -1543,6 +1575,22 @@
}
+void X86Assembler::jmp(NearLabel* label) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ if (label->IsBound()) {
+ static const int kShortSize = 2;
+ int offset = label->Position() - buffer_.Size();
+ CHECK_LE(offset, 0);
+ CHECK(IsInt<8>(offset - kShortSize));
+ EmitUint8(0xEB);
+ EmitUint8((offset - kShortSize) & 0xFF);
+ } else {
+ EmitUint8(0xEB);
+ EmitLabelLink(label);
+ }
+}
+
+
void X86Assembler::repne_scasw() {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0x66);
@@ -1675,6 +1723,21 @@
}
+void X86Assembler::Bind(NearLabel* label) {
+ int bound = buffer_.Size();
+ CHECK(!label->IsBound()); // Labels can only be bound once.
+ while (label->IsLinked()) {
+ int position = label->LinkPosition();
+ uint8_t delta = buffer_.Load<uint8_t>(position);
+ int offset = bound - (position + 1);
+ CHECK(IsInt<8>(offset));
+ buffer_.Store<int8_t>(position, offset);
+ label->position_ = delta != 0u ? label->position_ - delta : 0;
+ }
+ label->BindTo(bound);
+}
+
+
void X86Assembler::EmitOperand(int reg_or_opcode, const Operand& operand) {
CHECK_GE(reg_or_opcode, 0);
CHECK_LT(reg_or_opcode, 8);
@@ -1736,6 +1799,21 @@
}
+void X86Assembler::EmitLabelLink(NearLabel* label) {
+ CHECK(!label->IsBound());
+ int position = buffer_.Size();
+ if (label->IsLinked()) {
+ // Save the delta in the byte that we have to play with.
+ uint32_t delta = position - label->LinkPosition();
+ CHECK(IsUint<8>(delta));
+ EmitUint8(delta & 0xFF);
+ } else {
+ EmitUint8(0);
+ }
+ label->LinkTo(position);
+}
+
+
void X86Assembler::EmitGenericShift(int reg_or_opcode,
const Operand& operand,
const Immediate& imm) {
diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h
index a9227f3..0c90f28 100644
--- a/compiler/utils/x86/assembler_x86.h
+++ b/compiler/utils/x86/assembler_x86.h
@@ -203,6 +203,30 @@
};
+// This is equivalent to the Label class, used in a slightly different context. We
+// inherit the functionality of the Label class, but prevent unintended
+// derived-to-base conversions by making the base class private.
+class NearLabel : private Label {
+ public:
+ NearLabel() : Label() {}
+
+ // Expose the Label routines that we need.
+ using Label::Position;
+ using Label::LinkPosition;
+ using Label::IsBound;
+ using Label::IsUnused;
+ using Label::IsLinked;
+
+ private:
+ using Label::BindTo;
+ using Label::LinkTo;
+
+ friend class x86::X86Assembler;
+
+ DISALLOW_COPY_AND_ASSIGN(NearLabel);
+};
+
+
class X86Assembler FINAL : public Assembler {
public:
X86Assembler() {}
@@ -464,10 +488,13 @@
void hlt();
void j(Condition condition, Label* label);
+ void j(Condition condition, NearLabel* label);
+ void jecxz(NearLabel* label);
void jmp(Register reg);
void jmp(const Address& address);
void jmp(Label* label);
+ void jmp(NearLabel* label);
void repne_scasw();
void repe_cmpsw();
@@ -506,6 +533,7 @@
int PreferredLoopAlignment() { return 16; }
void Align(int alignment, int offset);
void Bind(Label* label);
+ void Bind(NearLabel* label);
//
// Overridden common assembler high-level functionality
@@ -652,6 +680,7 @@
void EmitComplex(int rm, const Operand& operand, const Immediate& immediate);
void EmitLabel(Label* label, int instruction_size);
void EmitLabelLink(Label* label);
+ void EmitLabelLink(NearLabel* label);
void EmitGenericShift(int rm, const Operand& operand, const Immediate& imm);
void EmitGenericShift(int rm, const Operand& operand, Register shifter);
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index 731b5f4..9ac54af 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -243,4 +243,43 @@
DriverStr(expected, "bsrl_address");
}
+/////////////////
+// Near labels //
+/////////////////
+
+TEST_F(AssemblerX86Test, Jecxz) {
+ x86::NearLabel target;
+ GetAssembler()->jecxz(&target);
+ GetAssembler()->addl(x86::EDI, x86::Address(x86::ESP, 4));
+ GetAssembler()->Bind(&target);
+ const char* expected =
+ "jecxz 1f\n"
+ "addl 4(%ESP),%EDI\n"
+ "1:\n";
+
+ DriverStr(expected, "jecxz");
+}
+
+TEST_F(AssemblerX86Test, NearLabel) {
+ // Test both forward and backward branches.
+ x86::NearLabel start, target;
+ GetAssembler()->Bind(&start);
+ GetAssembler()->j(x86::kEqual, &target);
+ GetAssembler()->jmp(&target);
+ GetAssembler()->jecxz(&target);
+ GetAssembler()->addl(x86::EDI, x86::Address(x86::ESP, 4));
+ GetAssembler()->Bind(&target);
+ GetAssembler()->j(x86::kNotEqual, &start);
+ GetAssembler()->jmp(&start);
+ const char* expected =
+ "1: je 2f\n"
+ "jmp 2f\n"
+ "jecxz 2f\n"
+ "addl 4(%ESP),%EDI\n"
+ "2: jne 1b\n"
+ "jmp 1b\n";
+
+ DriverStr(expected, "near_label");
+}
+
} // namespace art
diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc
index dc61c99..88ea990 100644
--- a/compiler/utils/x86_64/assembler_x86_64.cc
+++ b/compiler/utils/x86_64/assembler_x86_64.cc
@@ -1971,6 +1971,38 @@
}
+void X86_64Assembler::j(Condition condition, NearLabel* label) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ if (label->IsBound()) {
+ static const int kShortSize = 2;
+ int offset = label->Position() - buffer_.Size();
+ CHECK_LE(offset, 0);
+ CHECK(IsInt<8>(offset - kShortSize));
+ EmitUint8(0x70 + condition);
+ EmitUint8((offset - kShortSize) & 0xFF);
+ } else {
+ EmitUint8(0x70 + condition);
+ EmitLabelLink(label);
+ }
+}
+
+
+void X86_64Assembler::jrcxz(NearLabel* label) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ if (label->IsBound()) {
+ static const int kShortSize = 2;
+ int offset = label->Position() - buffer_.Size();
+ CHECK_LE(offset, 0);
+ CHECK(IsInt<8>(offset - kShortSize));
+ EmitUint8(0xE3);
+ EmitUint8((offset - kShortSize) & 0xFF);
+ } else {
+ EmitUint8(0xE3);
+ EmitLabelLink(label);
+ }
+}
+
+
void X86_64Assembler::jmp(CpuRegister reg) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitOptionalRex32(reg);
@@ -2006,6 +2038,22 @@
}
+void X86_64Assembler::jmp(NearLabel* label) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ if (label->IsBound()) {
+ static const int kShortSize = 2;
+ int offset = label->Position() - buffer_.Size();
+ CHECK_LE(offset, 0);
+ CHECK(IsInt<8>(offset - kShortSize));
+ EmitUint8(0xEB);
+ EmitUint8((offset - kShortSize) & 0xFF);
+ } else {
+ EmitUint8(0xEB);
+ EmitLabelLink(label);
+ }
+}
+
+
void X86_64Assembler::rep_movsw() {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0x66);
@@ -2187,6 +2235,21 @@
}
+void X86_64Assembler::Bind(NearLabel* label) {
+ int bound = buffer_.Size();
+ CHECK(!label->IsBound()); // Labels can only be bound once.
+ while (label->IsLinked()) {
+ int position = label->LinkPosition();
+ uint8_t delta = buffer_.Load<uint8_t>(position);
+ int offset = bound - (position + 1);
+ CHECK(IsInt<8>(offset));
+ buffer_.Store<int8_t>(position, offset);
+ label->position_ = delta != 0u ? label->position_ - delta : 0;
+ }
+ label->BindTo(bound);
+}
+
+
void X86_64Assembler::EmitOperand(uint8_t reg_or_opcode, const Operand& operand) {
CHECK_GE(reg_or_opcode, 0);
CHECK_LT(reg_or_opcode, 8);
@@ -2256,6 +2319,21 @@
}
+void X86_64Assembler::EmitLabelLink(NearLabel* label) {
+ CHECK(!label->IsBound());
+ int position = buffer_.Size();
+ if (label->IsLinked()) {
+ // Save the delta in the byte that we have to play with.
+ uint32_t delta = position - label->LinkPosition();
+ CHECK(IsUint<8>(delta));
+ EmitUint8(delta & 0xFF);
+ } else {
+ EmitUint8(0);
+ }
+ label->LinkTo(position);
+}
+
+
void X86_64Assembler::EmitGenericShift(bool wide,
int reg_or_opcode,
CpuRegister reg,
diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h
index da42213..c38aba5 100644
--- a/compiler/utils/x86_64/assembler_x86_64.h
+++ b/compiler/utils/x86_64/assembler_x86_64.h
@@ -302,6 +302,30 @@
};
+// This is equivalent to the Label class, used in a slightly different context. We
+// inherit the functionality of the Label class, but prevent unintended
+// derived-to-base conversions by making the base class private.
+class NearLabel : private Label {
+ public:
+ NearLabel() : Label() {}
+
+ // Expose the Label routines that we need.
+ using Label::Position;
+ using Label::LinkPosition;
+ using Label::IsBound;
+ using Label::IsUnused;
+ using Label::IsLinked;
+
+ private:
+ using Label::BindTo;
+ using Label::LinkTo;
+
+ friend class x86_64::X86_64Assembler;
+
+ DISALLOW_COPY_AND_ASSIGN(NearLabel);
+};
+
+
class X86_64Assembler FINAL : public Assembler {
public:
X86_64Assembler() {}
@@ -588,10 +612,13 @@
void hlt();
void j(Condition condition, Label* label);
+ void j(Condition condition, NearLabel* label);
+ void jrcxz(NearLabel* label);
void jmp(CpuRegister reg);
void jmp(const Address& address);
void jmp(Label* label);
+ void jmp(NearLabel* label);
X86_64Assembler* lock();
void cmpxchgl(const Address& address, CpuRegister reg);
@@ -639,6 +666,7 @@
int PreferredLoopAlignment() { return 16; }
void Align(int alignment, int offset);
void Bind(Label* label);
+ void Bind(NearLabel* label);
//
// Overridden common assembler high-level functionality
@@ -809,6 +837,7 @@
void EmitComplex(uint8_t rm, const Operand& operand, const Immediate& immediate);
void EmitLabel(Label* label, int instruction_size);
void EmitLabelLink(Label* label);
+ void EmitLabelLink(NearLabel* label);
void EmitGenericShift(bool wide, int rm, CpuRegister reg, const Immediate& imm);
void EmitGenericShift(bool wide, int rm, CpuRegister operand, CpuRegister shifter);
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index 8673f03..9e64b47 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -1179,6 +1179,47 @@
DriverStr(expected, "bsrq_address");
}
+/////////////////
+// Near labels //
+/////////////////
+
+TEST_F(AssemblerX86_64Test, Jrcxz) {
+ x86_64::NearLabel target;
+ GetAssembler()->jrcxz(&target);
+ GetAssembler()->addl(x86_64::CpuRegister(x86_64::RDI),
+ x86_64::Address(x86_64::CpuRegister(x86_64::RSP), 4));
+ GetAssembler()->Bind(&target);
+ const char* expected =
+ "jrcxz 1f\n"
+ "addl 4(%RSP),%EDI\n"
+ "1:\n";
+
+ DriverStr(expected, "jrcxz");
+}
+
+TEST_F(AssemblerX86_64Test, NearLabel) {
+ // Test both forward and backward branches.
+ x86_64::NearLabel start, target;
+ GetAssembler()->Bind(&start);
+ GetAssembler()->j(x86_64::kEqual, &target);
+ GetAssembler()->jmp(&target);
+ GetAssembler()->jrcxz(&target);
+ GetAssembler()->addl(x86_64::CpuRegister(x86_64::RDI),
+ x86_64::Address(x86_64::CpuRegister(x86_64::RSP), 4));
+ GetAssembler()->Bind(&target);
+ GetAssembler()->j(x86_64::kNotEqual, &start);
+ GetAssembler()->jmp(&start);
+ const char* expected =
+ "1: je 2f\n"
+ "jmp 2f\n"
+ "jrcxz 2f\n"
+ "addl 4(%RSP),%EDI\n"
+ "2: jne 1b\n"
+ "jmp 1b\n";
+
+ DriverStr(expected, "near_label");
+}
+
std::string setcc_test_fn(AssemblerX86_64Test::Base* assembler_test,
x86_64::X86_64Assembler* assembler) {
// From Condition
diff --git a/dexlist/Android.mk b/dexlist/Android.mk
index 9fbd847..6ec6c97 100755
--- a/dexlist/Android.mk
+++ b/dexlist/Android.mk
@@ -14,8 +14,6 @@
# TODO(ajcbik): Art-i-fy this makefile
-# TODO(ajcbik): rename dexlist2 into dexlist when Dalvik version is removed
-
LOCAL_PATH:= $(call my-dir)
dexlist_src_files := dexlist.cc
@@ -33,7 +31,7 @@
LOCAL_C_INCLUDES := $(dexlist_c_includes)
LOCAL_CFLAGS += -Wall
LOCAL_SHARED_LIBRARIES += $(dexlist_libraries)
-LOCAL_MODULE := dexlist2
+LOCAL_MODULE := dexlist
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
include $(BUILD_EXECUTABLE)
@@ -49,6 +47,6 @@
LOCAL_C_INCLUDES := $(dexlist_c_includes)
LOCAL_CFLAGS += -Wall
LOCAL_SHARED_LIBRARIES += $(dexlist_libraries)
-LOCAL_MODULE := dexlist2
+LOCAL_MODULE := dexlist
LOCAL_MULTILIB := $(ART_MULTILIB_OVERRIDE_host)
include $(BUILD_HOST_EXECUTABLE)
diff --git a/dexlist/dexlist.cc b/dexlist/dexlist.cc
index d8fd242..1d0f75e 100644
--- a/dexlist/dexlist.cc
+++ b/dexlist/dexlist.cc
@@ -235,7 +235,7 @@
gOptions.outputFileName = optarg;
break;
case 'm':
- // If -m x.y.z is given, then find all instances of the
+ // If -m p.c.m is given, then find all instances of the
// fully-qualified method name. This isn't really what
// dexlist is for, but it's easy to do it here.
{
diff --git a/dexlist/dexlist_test.cc b/dexlist/dexlist_test.cc
index 7b1b63d..82179de 100644
--- a/dexlist/dexlist_test.cc
+++ b/dexlist/dexlist_test.cc
@@ -42,12 +42,11 @@
// Runs test with given arguments.
bool Exec(const std::vector<std::string>& args, std::string* error_msg) {
- // TODO(ajcbik): dexlist2 -> dexlist
std::string file_path = GetTestAndroidRoot();
if (IsHost()) {
- file_path += "/bin/dexlist2";
+ file_path += "/bin/dexlist";
} else {
- file_path += "/xbin/dexlist2";
+ file_path += "/xbin/dexlist";
}
EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
std::vector<std::string> exec_argv = { file_path };
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 1950d56..c553a18 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -78,6 +78,21 @@
"kClassRoots",
};
+// Map is so that we don't allocate multiple dex files for the same OatDexFile.
+static std::map<const OatFile::OatDexFile*,
+ std::unique_ptr<const DexFile>> opened_dex_files;
+
+const DexFile* OpenDexFile(const OatFile::OatDexFile* oat_dex_file, std::string* error_msg) {
+ DCHECK(oat_dex_file != nullptr);
+ auto it = opened_dex_files.find(oat_dex_file);
+ if (it != opened_dex_files.end()) {
+ return it->second.get();
+ }
+ const DexFile* ret = oat_dex_file->OpenDexFile(error_msg).release();
+ opened_dex_files.emplace(oat_dex_file, std::unique_ptr<const DexFile>(ret));
+ return ret;
+}
+
class OatSymbolizer FINAL {
public:
class RodataWriter FINAL : public CodeOutput {
@@ -159,8 +174,8 @@
void WalkOatDexFile(const OatFile::OatDexFile* oat_dex_file, Callback callback) {
std::string error_msg;
- std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(&error_msg));
- if (dex_file.get() == nullptr) {
+ const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg);
+ if (dex_file == nullptr) {
return;
}
for (size_t class_def_index = 0;
@@ -172,7 +187,7 @@
switch (type) {
case kOatClassAllCompiled:
case kOatClassSomeCompiled:
- WalkOatClass(oat_class, *dex_file.get(), class_def, callback);
+ WalkOatClass(oat_class, *dex_file, class_def, callback);
break;
case kOatClassNoneCompiled:
@@ -504,8 +519,8 @@
const OatFile::OatDexFile* oat_dex_file = oat_dex_files_[i];
CHECK(oat_dex_file != nullptr);
std::string error_msg;
- std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(&error_msg));
- if (dex_file.get() == nullptr) {
+ const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg);
+ if (dex_file == nullptr) {
LOG(WARNING) << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation()
<< "': " << error_msg;
} else {
@@ -533,8 +548,8 @@
const OatFile::OatDexFile* oat_dex_file = oat_dex_files_[i];
CHECK(oat_dex_file != nullptr);
std::string error_msg;
- std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(&error_msg));
- if (dex_file.get() == nullptr) {
+ const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg);
+ if (dex_file == nullptr) {
LOG(WARNING) << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation()
<< "': " << error_msg;
continue;
@@ -593,8 +608,8 @@
// Create the verifier early.
std::string error_msg;
- std::unique_ptr<const DexFile> dex_file(oat_dex_file.OpenDexFile(&error_msg));
- if (dex_file.get() == nullptr) {
+ const DexFile* const dex_file = OpenDexFile(&oat_dex_file, &error_msg);
+ if (dex_file == nullptr) {
os << "NOT FOUND: " << error_msg << "\n\n";
os << std::flush;
return false;
@@ -621,7 +636,7 @@
<< " (" << oat_class.GetType() << ")\n";
// TODO: include bitmap here if type is kOatClassSomeCompiled?
if (options_.list_classes_) continue;
- if (!DumpOatClass(&vios, oat_class, *(dex_file.get()), class_def, &stop_analysis)) {
+ if (!DumpOatClass(&vios, oat_class, *dex_file, class_def, &stop_analysis)) {
success = false;
}
if (stop_analysis) {
@@ -638,7 +653,7 @@
std::string error_msg;
std::string dex_file_location = oat_dex_file.GetDexFileLocation();
- std::unique_ptr<const DexFile> dex_file(oat_dex_file.OpenDexFile(&error_msg));
+ const DexFile* const dex_file = OpenDexFile(&oat_dex_file, &error_msg);
if (dex_file == nullptr) {
os << "Failed to open dex file '" << dex_file_location << "': " << error_msg;
return false;
@@ -2337,21 +2352,17 @@
ScopedObjectAccess soa(self);
ClassLinker* class_linker = runtime->GetClassLinker();
class_linker->RegisterOatFile(oat_file);
- std::vector<std::unique_ptr<const DexFile>> dex_files;
+ std::vector<const DexFile*> class_path;
for (const OatFile::OatDexFile* odf : oat_file->GetOatDexFiles()) {
std::string error_msg;
- std::unique_ptr<const DexFile> dex_file = odf->OpenDexFile(&error_msg);
+ const DexFile* const dex_file = OpenDexFile(odf, &error_msg);
CHECK(dex_file != nullptr) << error_msg;
class_linker->RegisterDexFile(*dex_file);
- dex_files.push_back(std::move(dex_file));
+ class_path.push_back(dex_file);
}
// Need a class loader.
// Fake that we're a compiler.
- std::vector<const DexFile*> class_path;
- for (auto& dex_file : dex_files) {
- class_path.push_back(dex_file.get());
- }
jobject class_loader = class_linker->CreatePathClassLoader(self, class_path);
// Use the class loader while dumping.
diff --git a/runtime/base/hash_set_test.cc b/runtime/base/hash_set_test.cc
index 4ef1f9e..6d2c5e0 100644
--- a/runtime/base/hash_set_test.cc
+++ b/runtime/base/hash_set_test.cc
@@ -17,9 +17,11 @@
#include "hash_set.h"
#include <map>
+#include <forward_list>
#include <sstream>
#include <string>
#include <unordered_set>
+#include <vector>
#include <gtest/gtest.h>
#include "hash_map.h"
@@ -258,4 +260,59 @@
ASSERT_EQ(it->second, 124);
}
+struct IsEmptyFnVectorInt {
+ void MakeEmpty(std::vector<int>& item) const {
+ item.clear();
+ }
+ bool IsEmpty(const std::vector<int>& item) const {
+ return item.empty();
+ }
+};
+
+template <typename T>
+size_t HashIntSequence(T begin, T end) {
+ size_t hash = 0;
+ for (auto iter = begin; iter != end; ++iter) {
+ hash = hash * 2 + *iter;
+ }
+ return hash;
+};
+
+struct VectorIntHashEquals {
+ std::size_t operator()(const std::vector<int>& item) const {
+ return HashIntSequence(item.begin(), item.end());
+ }
+
+ std::size_t operator()(const std::forward_list<int>& item) const {
+ return HashIntSequence(item.begin(), item.end());
+ }
+
+ bool operator()(const std::vector<int>& a, const std::vector<int>& b) const {
+ return a == b;
+ }
+
+ bool operator()(const std::vector<int>& a, const std::forward_list<int>& b) const {
+ auto aiter = a.begin();
+ auto biter = b.begin();
+ while (aiter != a.end() && biter != b.end()) {
+ if (*aiter != *biter) {
+ return false;
+ }
+ aiter++;
+ biter++;
+ }
+ return (aiter == a.end() && biter == b.end());
+ }
+};
+
+TEST_F(HashSetTest, TestLookupByAlternateKeyType) {
+ HashSet<std::vector<int>, IsEmptyFnVectorInt, VectorIntHashEquals, VectorIntHashEquals> hash_set;
+ hash_set.Insert(std::vector<int>({1, 2, 3, 4}));
+ hash_set.Insert(std::vector<int>({4, 2}));
+ ASSERT_EQ(hash_set.end(), hash_set.Find(std::vector<int>({1, 1, 1, 1})));
+ ASSERT_NE(hash_set.end(), hash_set.Find(std::vector<int>({1, 2, 3, 4})));
+ ASSERT_EQ(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 1, 1, 1})));
+ ASSERT_NE(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 2, 3, 4})));
+}
+
} // namespace art
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 5f2caef..6bf203c 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -64,6 +64,7 @@
kJdwpSocketLock,
kRegionSpaceRegionLock,
kTransactionLogLock,
+ kMarkSweepMarkStackLock,
kJniWeakGlobalsLock,
kReferenceQueueSoftReferencesLock,
kReferenceQueuePhantomReferencesLock,
@@ -80,7 +81,6 @@
kArenaPoolLock,
kDexFileMethodInlinerLock,
kDexFileToMethodInlinerMapLock,
- kMarkSweepMarkStackLock,
kInternTableLock,
kOatFileSecondaryLookupLock,
kTracingUniqueMethodsLock,
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3b505e6..e78914c 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1834,11 +1834,15 @@
klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
}
if (UNLIKELY(klass.Get() == nullptr)) {
- CHECK(self->IsExceptionPending()); // Expect an OOME.
+ self->AssertPendingOOMException();
return nullptr;
}
- klass->SetDexCache(RegisterDexFile(dex_file));
-
+ mirror::DexCache* dex_cache = RegisterDexFile(dex_file);
+ if (dex_cache == nullptr) {
+ self->AssertPendingOOMException();
+ return nullptr;
+ }
+ klass->SetDexCache(dex_cache);
SetupClass(dex_file, dex_class_def, klass, class_loader.Get());
// Mark the string class by setting its access flag.
@@ -2498,7 +2502,7 @@
Thread* self = Thread::Current();
{
ReaderMutexLock mu(self, dex_lock_);
- mirror::DexCache* dex_cache = FindDexCacheLocked(dex_file, true);
+ mirror::DexCache* dex_cache = FindDexCacheLocked(self, dex_file, true);
if (dex_cache != nullptr) {
return dex_cache;
}
@@ -2508,13 +2512,15 @@
// get to a suspend point.
StackHandleScope<1> hs(self);
Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(AllocDexCache(self, dex_file)));
- CHECK(h_dex_cache.Get() != nullptr) << "Failed to allocate dex cache for "
- << dex_file.GetLocation();
WriterMutexLock mu(self, dex_lock_);
- mirror::DexCache* dex_cache = FindDexCacheLocked(dex_file, true);
+ mirror::DexCache* dex_cache = FindDexCacheLocked(self, dex_file, true);
if (dex_cache != nullptr) {
return dex_cache;
}
+ if (h_dex_cache.Get() == nullptr) {
+ self->AssertPendingOOMException();
+ return nullptr;
+ }
RegisterDexFileLocked(dex_file, h_dex_cache);
return h_dex_cache.Get();
}
@@ -2525,19 +2531,27 @@
RegisterDexFileLocked(dex_file, dex_cache);
}
-mirror::DexCache* ClassLinker::FindDexCache(const DexFile& dex_file, bool allow_failure) {
- Thread* const self = Thread::Current();
+mirror::DexCache* ClassLinker::FindDexCache(Thread* self,
+ const DexFile& dex_file,
+ bool allow_failure) {
ReaderMutexLock mu(self, dex_lock_);
- return FindDexCacheLocked(dex_file, allow_failure);
+ return FindDexCacheLocked(self, dex_file, allow_failure);
}
-mirror::DexCache* ClassLinker::FindDexCacheLocked(const DexFile& dex_file, bool allow_failure) {
- Thread* const self = Thread::Current();
+mirror::DexCache* ClassLinker::FindDexCacheLocked(Thread* self,
+ const DexFile& dex_file,
+ bool allow_failure) {
// Search assuming unique-ness of dex file.
- for (jobject weak_root : dex_caches_) {
- mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
- if (dex_cache != nullptr && dex_cache->GetDexFile() == &dex_file) {
- return dex_cache;
+ JavaVMExt* const vm = self->GetJniEnv()->vm;
+ {
+ MutexLock mu(self, vm->WeakGlobalsLock());
+ for (jobject weak_root : dex_caches_) {
+ DCHECK_EQ(GetIndirectRefKind(weak_root), kWeakGlobal);
+ mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(
+ vm->DecodeWeakGlobalLocked(self, weak_root));
+ if (dex_cache != nullptr && dex_cache->GetDexFile() == &dex_file) {
+ return dex_cache;
+ }
}
}
if (allow_failure) {
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index cc56e8b..2a7162b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -309,7 +309,9 @@
void VisitRoots(RootVisitor* visitor, VisitRootFlags flags)
REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
- mirror::DexCache* FindDexCache(const DexFile& dex_file, bool allow_failure = false)
+ mirror::DexCache* FindDexCache(Thread* self,
+ const DexFile& dex_file,
+ bool allow_failure = false)
REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
void FixupDexCaches(ArtMethod* resolution_method)
REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
@@ -559,7 +561,9 @@
void RegisterDexFileLocked(const DexFile& dex_file, Handle<mirror::DexCache> dex_cache)
REQUIRES(dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
- mirror::DexCache* FindDexCacheLocked(const DexFile& dex_file, bool allow_failure)
+ mirror::DexCache* FindDexCacheLocked(Thread* self,
+ const DexFile& dex_file,
+ bool allow_failure)
REQUIRES(dex_lock_)
SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 0d1c875..c3191fa 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -355,7 +355,7 @@
TestRootVisitor visitor;
class_linker_->VisitRoots(&visitor, kVisitRootFlagAllRoots);
// Verify the dex cache has resolution methods in all resolved method slots
- mirror::DexCache* dex_cache = class_linker_->FindDexCache(dex);
+ mirror::DexCache* dex_cache = class_linker_->FindDexCache(Thread::Current(), dex);
auto* resolved_methods = dex_cache->GetResolvedMethods();
for (size_t i = 0; i < static_cast<size_t>(resolved_methods->GetLength()); i++) {
EXPECT_TRUE(resolved_methods->GetElementPtrSize<ArtMethod*>(i, sizeof(void*)) != nullptr)
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 0cbbb79..8d34f5a 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -1191,6 +1191,10 @@
if (error != JDWP::ERR_NONE) {
return error;
}
+ // Check if the object's type is compatible with the array's type.
+ if (o != nullptr && !o->InstanceOf(oa->GetClass()->GetComponentType())) {
+ return JDWP::ERR_TYPE_MISMATCH;
+ }
oa->Set<false>(offset + i, o);
}
}
@@ -2094,6 +2098,7 @@
case kWaitingInMainSignalCatcherLoop:
case kWaitingPerformingGc:
case kWaitingWeakGcRootRead:
+ case kWaitingForGcThreadFlip:
case kWaiting:
return JDWP::TS_WAIT;
// Don't add a 'default' here so the compiler can spot incompatible enum changes.
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 65e946f..a5bc60a 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -261,8 +261,10 @@
gc_barrier_->Init(self, 0);
ThreadFlipVisitor thread_flip_visitor(this, heap_->use_tlab_);
FlipCallback flip_callback(this);
+ heap_->ThreadFlipBegin(self); // Sync with JNI critical calls.
size_t barrier_count = Runtime::Current()->FlipThreadRoots(
&thread_flip_visitor, &flip_callback, this);
+ heap_->ThreadFlipEnd(self);
{
ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
gc_barrier_->Increment(self, barrier_count);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index b8c4478..aec8d63 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -161,6 +161,8 @@
zygote_creation_lock_("zygote creation lock", kZygoteCreationLock),
zygote_space_(nullptr),
large_object_threshold_(large_object_threshold),
+ disable_thread_flip_count_(0),
+ thread_flip_running_(false),
collector_type_running_(kCollectorTypeNone),
last_gc_type_(collector::kGcTypeNone),
next_gc_type_(collector::kGcTypePartial),
@@ -480,6 +482,9 @@
gc_complete_lock_ = new Mutex("GC complete lock");
gc_complete_cond_.reset(new ConditionVariable("GC complete condition variable",
*gc_complete_lock_));
+ thread_flip_lock_ = new Mutex("GC thread flip lock");
+ thread_flip_cond_.reset(new ConditionVariable("GC thread flip condition variable",
+ *thread_flip_lock_));
task_processor_.reset(new TaskProcessor());
reference_processor_.reset(new ReferenceProcessor());
pending_task_lock_ = new Mutex("Pending task lock");
@@ -770,6 +775,71 @@
--disable_moving_gc_count_;
}
+void Heap::IncrementDisableThreadFlip(Thread* self) {
+ // Supposed to be called by mutators. If thread_flip_running_ is true, block. Otherwise, go ahead.
+ CHECK(kUseReadBarrier);
+ ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+ MutexLock mu(self, *thread_flip_lock_);
+ bool has_waited = false;
+ uint64_t wait_start = NanoTime();
+ while (thread_flip_running_) {
+ has_waited = true;
+ thread_flip_cond_->Wait(self);
+ }
+ ++disable_thread_flip_count_;
+ if (has_waited) {
+ uint64_t wait_time = NanoTime() - wait_start;
+ total_wait_time_ += wait_time;
+ if (wait_time > long_pause_log_threshold_) {
+ LOG(INFO) << __FUNCTION__ << " blocked for " << PrettyDuration(wait_time);
+ }
+ }
+}
+
+void Heap::DecrementDisableThreadFlip(Thread* self) {
+ // Supposed to be called by mutators. Decrement disable_thread_flip_count_ and potentially wake up
+ // the GC waiting before doing a thread flip.
+ CHECK(kUseReadBarrier);
+ MutexLock mu(self, *thread_flip_lock_);
+ CHECK_GT(disable_thread_flip_count_, 0U);
+ --disable_thread_flip_count_;
+ thread_flip_cond_->Broadcast(self);
+}
+
+void Heap::ThreadFlipBegin(Thread* self) {
+ // Supposed to be called by GC. Set thread_flip_running_ to be true. If disable_thread_flip_count_
+ // > 0, block. Otherwise, go ahead.
+ CHECK(kUseReadBarrier);
+ ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+ MutexLock mu(self, *thread_flip_lock_);
+ bool has_waited = false;
+ uint64_t wait_start = NanoTime();
+ CHECK(!thread_flip_running_);
+ // Set this to true before waiting so that a new mutator entering a JNI critical won't starve GC.
+ thread_flip_running_ = true;
+ while (disable_thread_flip_count_ > 0) {
+ has_waited = true;
+ thread_flip_cond_->Wait(self);
+ }
+ if (has_waited) {
+ uint64_t wait_time = NanoTime() - wait_start;
+ total_wait_time_ += wait_time;
+ if (wait_time > long_pause_log_threshold_) {
+ LOG(INFO) << __FUNCTION__ << " blocked for " << PrettyDuration(wait_time);
+ }
+ }
+}
+
+void Heap::ThreadFlipEnd(Thread* self) {
+ // Supposed to be called by GC. Set thread_flip_running_ to false and potentially wake up mutators
+ // waiting before doing a JNI critical.
+ CHECK(kUseReadBarrier);
+ MutexLock mu(self, *thread_flip_lock_);
+ CHECK(thread_flip_running_);
+ thread_flip_running_ = false;
+ thread_flip_cond_->Broadcast(self);
+}
+
void Heap::UpdateProcessState(ProcessState process_state) {
if (process_state_ != process_state) {
process_state_ = process_state;
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index d94f109..85688ae 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -300,6 +300,12 @@
void IncrementDisableMovingGC(Thread* self) REQUIRES(!*gc_complete_lock_);
void DecrementDisableMovingGC(Thread* self) REQUIRES(!*gc_complete_lock_);
+ // Temporarily disable thread flip for JNI critical calls.
+ void IncrementDisableThreadFlip(Thread* self) REQUIRES(!*thread_flip_lock_);
+ void DecrementDisableThreadFlip(Thread* self) REQUIRES(!*thread_flip_lock_);
+ void ThreadFlipBegin(Thread* self) REQUIRES(!*thread_flip_lock_);
+ void ThreadFlipEnd(Thread* self) REQUIRES(!*thread_flip_lock_);
+
// Clear all of the mark bits, doesn't clear bitmaps which have the same live bits as mark bits.
void ClearMarkedObjects() REQUIRES(Locks::heap_bitmap_lock_);
@@ -1065,6 +1071,12 @@
Mutex* gc_complete_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
std::unique_ptr<ConditionVariable> gc_complete_cond_ GUARDED_BY(gc_complete_lock_);
+ // Used to synchronize between JNI critical calls and the thread flip of the CC collector.
+ Mutex* thread_flip_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ std::unique_ptr<ConditionVariable> thread_flip_cond_ GUARDED_BY(thread_flip_lock_);
+ size_t disable_thread_flip_count_ GUARDED_BY(thread_flip_lock_);
+ bool thread_flip_running_ GUARDED_BY(thread_flip_lock_);
+
// Reference processor;
std::unique_ptr<ReferenceProcessor> reference_processor_;
diff --git a/runtime/gc/reference_queue_test.cc b/runtime/gc/reference_queue_test.cc
index 888c0d2..ab921d9 100644
--- a/runtime/gc/reference_queue_test.cc
+++ b/runtime/gc/reference_queue_test.cc
@@ -27,11 +27,11 @@
TEST_F(ReferenceQueueTest, EnqueueDequeue) {
Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
StackHandleScope<20> hs(self);
Mutex lock("Reference queue lock");
ReferenceQueue queue(&lock);
ASSERT_TRUE(queue.IsEmpty());
- ScopedObjectAccess soa(self);
ASSERT_EQ(queue.GetLength(), 0U);
auto ref_class = hs.NewHandle(
Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/WeakReference;",
@@ -58,10 +58,10 @@
TEST_F(ReferenceQueueTest, Dump) {
Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
StackHandleScope<20> hs(self);
Mutex lock("Reference queue lock");
ReferenceQueue queue(&lock);
- ScopedObjectAccess soa(self);
queue.Dump(LOG(INFO));
auto weak_ref_class = hs.NewHandle(
Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/WeakReference;",
diff --git a/runtime/handle_scope-inl.h b/runtime/handle_scope-inl.h
index 222083b..ca206ef 100644
--- a/runtime/handle_scope-inl.h
+++ b/runtime/handle_scope-inl.h
@@ -19,8 +19,9 @@
#include "handle_scope.h"
+#include "base/mutex.h"
#include "handle.h"
-#include "thread.h"
+#include "thread-inl.h"
#include "verify_object-inl.h"
namespace art {
@@ -29,6 +30,9 @@
inline StackHandleScope<kNumReferences>::StackHandleScope(Thread* self, mirror::Object* fill_value)
: HandleScope(self->GetTopHandleScope(), kNumReferences), self_(self), pos_(0) {
DCHECK_EQ(self, Thread::Current());
+ if (kDebugLocking) {
+ Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+ }
static_assert(kNumReferences >= 1, "StackHandleScope must contain at least 1 reference");
// TODO: Figure out how to use a compile assert.
CHECK_EQ(&storage_[0], GetReferences());
@@ -42,6 +46,9 @@
inline StackHandleScope<kNumReferences>::~StackHandleScope() {
HandleScope* top_handle_scope = self_->PopHandleScope();
DCHECK_EQ(top_handle_scope, this);
+ if (kDebugLocking) {
+ Locks::mutator_lock_->AssertSharedHeld(self_);
+ }
}
inline size_t HandleScope::SizeOf(uint32_t num_references) {
@@ -59,6 +66,9 @@
inline mirror::Object* HandleScope::GetReference(size_t i) const {
DCHECK_LT(i, number_of_references_);
+ if (kDebugLocking) {
+ Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+ }
return GetReferences()[i].AsMirrorPtr();
}
@@ -73,6 +83,9 @@
}
inline void HandleScope::SetReference(size_t i, mirror::Object* object) {
+ if (kDebugLocking) {
+ Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+ }
DCHECK_LT(i, number_of_references_);
GetReferences()[i].Assign(object);
}
@@ -104,6 +117,9 @@
template<size_t kNumReferences>
inline void StackHandleScope<kNumReferences>::SetReference(size_t i, mirror::Object* object) {
+ if (kDebugLocking) {
+ Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+ }
DCHECK_LT(i, kNumReferences);
VerifyObject(object);
GetReferences()[i].Assign(object);
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index ee6b020..e2094dc 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -765,8 +765,9 @@
okay = !file_output.Errors();
if (okay) {
- // Check for expected size.
- CHECK_EQ(file_output.SumLength(), overall_size);
+ // Check for expected size. Output is expected to be less-or-equal than first phase, see
+ // b/23521263.
+ DCHECK_LE(file_output.SumLength(), overall_size);
}
output_ = nullptr;
}
@@ -810,8 +811,8 @@
// Write the dump.
ProcessHeap(true);
- // Check for expected size.
- CHECK_EQ(net_output.SumLength(), overall_size + kChunkHeaderSize);
+ // Check for expected size. See DumpToFile for comment.
+ DCHECK_LE(net_output.SumLength(), overall_size + kChunkHeaderSize);
output_ = nullptr;
return true;
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
index 2fd0517..ef7a924 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -578,6 +578,13 @@
mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) {
MutexLock mu(self, weak_globals_lock_);
+ return DecodeWeakGlobalLocked(self, ref);
+}
+
+mirror::Object* JavaVMExt::DecodeWeakGlobalLocked(Thread* self, IndirectRef ref) {
+ if (kDebugLocking) {
+ weak_globals_lock_.AssertHeld(self);
+ }
while (UNLIKELY((!kUseReadBarrier && !allow_new_weak_globals_) ||
(kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
weak_globals_add_condition_.WaitHoldingLocks(self);
diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h
index d70fc47..e80266f 100644
--- a/runtime/java_vm_ext.h
+++ b/runtime/java_vm_ext.h
@@ -133,7 +133,16 @@
SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!globals_lock_);
mirror::Object* DecodeWeakGlobal(Thread* self, IndirectRef ref)
- SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!weak_globals_lock_);
+ SHARED_REQUIRES(Locks::mutator_lock_)
+ REQUIRES(!weak_globals_lock_);
+
+ mirror::Object* DecodeWeakGlobalLocked(Thread* self, IndirectRef ref)
+ SHARED_REQUIRES(Locks::mutator_lock_)
+ REQUIRES(weak_globals_lock_);
+
+ Mutex& WeakGlobalsLock() RETURN_CAPABILITY(weak_globals_lock_) {
+ return weak_globals_lock_;
+ }
void UpdateWeakGlobal(Thread* self, IndirectRef ref, mirror::Object* result)
SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!weak_globals_lock_);
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 6a716b5..6bc1829 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -1729,7 +1729,13 @@
if (heap->IsMovableObject(s)) {
StackHandleScope<1> hs(soa.Self());
HandleWrapper<mirror::String> h(hs.NewHandleWrapper(&s));
- heap->IncrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->IncrementDisableMovingGC(soa.Self());
+ } else {
+ // For the CC collector, we only need to wait for the thread flip rather than the whole GC
+ // to occur thanks to the to-space invariant.
+ heap->IncrementDisableThreadFlip(soa.Self());
+ }
}
if (is_copy != nullptr) {
*is_copy = JNI_FALSE;
@@ -1744,7 +1750,11 @@
gc::Heap* heap = Runtime::Current()->GetHeap();
mirror::String* s = soa.Decode<mirror::String*>(java_string);
if (heap->IsMovableObject(s)) {
- heap->DecrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->DecrementDisableMovingGC(soa.Self());
+ } else {
+ heap->DecrementDisableThreadFlip(soa.Self());
+ }
}
}
@@ -1891,7 +1901,13 @@
}
gc::Heap* heap = Runtime::Current()->GetHeap();
if (heap->IsMovableObject(array)) {
- heap->IncrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->IncrementDisableMovingGC(soa.Self());
+ } else {
+ // For the CC collector, we only need to wait for the thread flip rather than the whole GC
+ // to occur thanks to the to-space invariant.
+ heap->IncrementDisableThreadFlip(soa.Self());
+ }
// Re-decode in case the object moved since IncrementDisableGC waits for GC to complete.
array = soa.Decode<mirror::Array*>(java_array);
}
@@ -2437,7 +2453,11 @@
delete[] reinterpret_cast<uint64_t*>(elements);
} else if (heap->IsMovableObject(array)) {
// Non copy to a movable object must means that we had disabled the moving GC.
- heap->DecrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->DecrementDisableMovingGC(soa.Self());
+ } else {
+ heap->DecrementDisableThreadFlip(soa.Self());
+ }
}
}
}
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index e1173bb..69112b1 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -290,15 +290,13 @@
static void CommonWaitSetup(MonitorTest* test, ClassLinker* class_linker, uint64_t create_sleep,
int64_t c_millis, bool c_expected, bool interrupt, uint64_t use_sleep,
int64_t u_millis, bool u_expected, const char* pool_name) {
+ Thread* const self = Thread::Current();
+ ScopedObjectAccess soa(self);
// First create the object we lock. String is easiest.
- StackHandleScope<3> hs(Thread::Current());
- {
- ScopedObjectAccess soa(Thread::Current());
- test->object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(Thread::Current(),
- "hello, world!"));
- test->watchdog_object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(Thread::Current(),
- "hello, world!"));
- }
+ StackHandleScope<3> hs(soa.Self());
+ test->object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "hello, world!"));
+ test->watchdog_object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self,
+ "hello, world!"));
// Create the barrier used to synchronize.
test->barrier_ = std::unique_ptr<Barrier>(new Barrier(2));
@@ -308,23 +306,17 @@
// Fill the heap.
std::unique_ptr<StackHandleScope<kMaxHandles>> hsp;
std::vector<MutableHandle<mirror::Object>> handles;
- {
- Thread* self = Thread::Current();
- ScopedObjectAccess soa(self);
- // Our job: Fill the heap, then try Wait.
- FillHeap(self, class_linker, &hsp, &handles);
+ // Our job: Fill the heap, then try Wait.
+ FillHeap(soa.Self(), class_linker, &hsp, &handles);
- // Now release everything.
- auto it = handles.begin();
- auto end = handles.end();
+ // Now release everything.
+ for (MutableHandle<mirror::Object>& h : handles) {
+ h.Assign(nullptr);
+ }
- for ( ; it != end; ++it) {
- it->Assign(nullptr);
- }
- } // Need to drop the mutator lock to allow barriers.
-
- Thread* self = Thread::Current();
+ // Need to drop the mutator lock to allow barriers.
+ soa.Self()->TransitionFromRunnableToSuspended(kNative);
ThreadPool thread_pool(pool_name, 3);
thread_pool.AddTask(self, new CreateTask(test, create_sleep, c_millis, c_expected));
if (interrupt) {
@@ -336,19 +328,19 @@
thread_pool.StartWorkers(self);
// Wait on completion barrier.
- test->complete_barrier_->Wait(Thread::Current());
+ test->complete_barrier_->Wait(self);
test->completed_ = true;
// Wake the watchdog.
{
- ScopedObjectAccess soa(Thread::Current());
-
+ ScopedObjectAccess soa2(self);
test->watchdog_object_.Get()->MonitorEnter(self); // Lock the object.
test->watchdog_object_.Get()->NotifyAll(self); // Wake up waiting parties.
test->watchdog_object_.Get()->MonitorExit(self); // Release the lock.
}
thread_pool.StopWorkers(self);
+ soa.Self()->TransitionFromSuspendedToRunnable();
}
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 9bd320c..3b84bfa 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -171,7 +171,7 @@
if (array == nullptr) {
ScopedObjectAccess soa(env);
for (auto& dex_file : dex_files) {
- if (Runtime::Current()->GetClassLinker()->FindDexCache(*dex_file, true) != nullptr) {
+ if (linker->FindDexCache(soa.Self(), *dex_file, true) != nullptr) {
dex_file.release();
}
}
@@ -208,8 +208,9 @@
//
// TODO: The Runtime should support unloading of classes and freeing of the
// dex files for those unloaded classes rather than leaking dex files here.
- for (auto& dex_file : *dex_files) {
- if (Runtime::Current()->GetClassLinker()->FindDexCache(*dex_file, true) == nullptr) {
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ for (const DexFile* dex_file : *dex_files) {
+ if (class_linker->FindDexCache(soa.Self(), *dex_file, true) == nullptr) {
delete dex_file;
}
}
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 5a9c43b..4f95723 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -428,9 +428,10 @@
return;
}
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ Thread* const self = Thread::Current();
for (const DexFile* dex_file : class_linker->GetBootClassPath()) {
CHECK(dex_file != nullptr);
- mirror::DexCache* const dex_cache = class_linker->FindDexCache(*dex_file, true);
+ mirror::DexCache* const dex_cache = class_linker->FindDexCache(self, *dex_file, true);
// If dex cache was deallocated, just continue.
if (dex_cache == nullptr) {
continue;
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index c76f6ee..c75ff78 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -90,6 +90,7 @@
case kWaitingForMethodTracingStart: return kJavaWaiting;
case kWaitingForVisitObjects: return kJavaWaiting;
case kWaitingWeakGcRootRead: return kJavaWaiting;
+ case kWaitingForGcThreadFlip: return kJavaWaiting;
case kSuspended: return kJavaRunnable;
// Don't add a 'default' here so the compiler can spot incompatible enum changes.
}
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 49451ad..a9dc16d 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -235,6 +235,9 @@
self->GetJniEnv()->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
WellKnownClasses::java_lang_Daemons_stop);
}
+
+ Trace::Shutdown();
+
if (attach_shutdown_thread) {
DetachCurrentThread();
self = nullptr;
@@ -245,8 +248,6 @@
BackgroundMethodSamplingProfiler::Shutdown();
}
- Trace::Shutdown();
-
// Make sure to let the GC complete if it is running.
heap_->WaitForGcToComplete(gc::kGcCauseBackground, self);
heap_->DeleteThreadPool();
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index a11d213..8f2f70f 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -44,6 +44,7 @@
kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects
kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects
kWaitingWeakGcRootRead, // WAITING TS_WAIT waiting on the GC to read a weak root
+ kWaitingForGcThreadFlip, // WAITING TS_WAIT waiting on the GC thread flip (CC collector) to finish
kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code
kNative, // RUNNABLE TS_RUNNING running in a JNI native method
kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger
diff --git a/runtime/trace.cc b/runtime/trace.cc
index 7579d8d..4ab5c0e 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -638,10 +638,11 @@
const std::map<const DexFile*, DexIndexBitSet*>& seen_methods,
std::set<ArtMethod*>* visited_methods) SHARED_REQUIRES(Locks::mutator_lock_) {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ Thread* const self = Thread::Current();
for (auto& e : seen_methods) {
DexIndexBitSet* bit_set = e.second;
// TODO: Visit trace methods as roots.
- mirror::DexCache* dex_cache = class_linker->FindDexCache(*e.first, false);
+ mirror::DexCache* dex_cache = class_linker->FindDexCache(self, *e.first, false);
for (uint32_t i = 0; i < bit_set->size(); ++i) {
if ((*bit_set)[i]) {
visited_methods->insert(dex_cache->GetResolvedMethod(i, sizeof(void*)));
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 5b73f10..4f921bd 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -416,6 +416,7 @@
have_any_pending_runtime_throw_failure_(false),
new_instance_count_(0),
monitor_enter_count_(0),
+ encountered_failure_types_(0),
can_load_classes_(can_load_classes),
allow_soft_failures_(allow_soft_failures),
need_precise_constants_(need_precise_constants),
@@ -587,6 +588,9 @@
}
std::ostream& MethodVerifier::Fail(VerifyError error) {
+ // Mark the error type as encountered.
+ encountered_failure_types_ |= static_cast<uint32_t>(error);
+
switch (error) {
case VERIFY_ERROR_NO_CLASS:
case VERIFY_ERROR_NO_FIELD:
@@ -597,6 +601,7 @@
case VERIFY_ERROR_INSTANTIATION:
case VERIFY_ERROR_CLASS_CHANGE:
case VERIFY_ERROR_FORCE_INTERPRETER:
+ case VERIFY_ERROR_LOCKING:
if (Runtime::Current()->IsAotCompiler() || !can_load_classes_) {
// If we're optimistically running verification at compile time, turn NO_xxx, ACCESS_xxx,
// class change and instantiation errors into soft verification errors so that we re-verify
@@ -627,12 +632,14 @@
}
}
break;
+
// Indication that verification should be retried at runtime.
case VERIFY_ERROR_BAD_CLASS_SOFT:
if (!allow_soft_failures_) {
have_pending_hard_failure_ = true;
}
break;
+
// Hard verification failures at compile time will still fail at runtime, so the class is
// marked as rejected to prevent it from being compiled.
case VERIFY_ERROR_BAD_CLASS_HARD: {
@@ -1653,6 +1660,33 @@
return DexFile::kDexNoIndex;
}
+// Setup a register line for the given return instruction.
+static void AdjustReturnLine(MethodVerifier* verifier,
+ const Instruction* ret_inst,
+ RegisterLine* line) {
+ Instruction::Code opcode = ret_inst->Opcode();
+
+ switch (opcode) {
+ case Instruction::RETURN_VOID:
+ case Instruction::RETURN_VOID_NO_BARRIER:
+ SafelyMarkAllRegistersAsConflicts(verifier, line);
+ break;
+
+ case Instruction::RETURN:
+ case Instruction::RETURN_OBJECT:
+ line->MarkAllRegistersAsConflictsExcept(verifier, ret_inst->VRegA_11x());
+ break;
+
+ case Instruction::RETURN_WIDE:
+ line->MarkAllRegistersAsConflictsExceptWide(verifier, ret_inst->VRegA_11x());
+ break;
+
+ default:
+ LOG(FATAL) << "Unknown return opcode " << opcode;
+ UNREACHABLE();
+ }
+}
+
bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
// If we're doing FindLocksAtDexPc, check whether we're at the dex pc we care about.
// We want the state _before_ the instruction, for the case where the dex pc we're
@@ -3074,10 +3108,9 @@
} else if (have_pending_runtime_throw_failure_) {
/* checking interpreter will throw, mark following code as unreachable */
opcode_flags = Instruction::kThrow;
- have_any_pending_runtime_throw_failure_ = true;
- // Reset the pending_runtime_throw flag. The flag is a global to decouple Fail and is per
- // instruction.
- have_pending_runtime_throw_failure_ = false;
+ // Note: the flag must be reset as it is only global to decouple Fail and is semantically per
+ // instruction. However, RETURN checking may throw LOCKING errors, so we clear at the
+ // very end.
}
/*
* If we didn't just set the result register, clear it out. This ensures that you can only use
@@ -3246,16 +3279,7 @@
if (insn_flags_[next_insn_idx].IsReturn()) {
// For returns we only care about the operand to the return, all other registers are dead.
const Instruction* ret_inst = Instruction::At(code_item_->insns_ + next_insn_idx);
- Instruction::Code opcode = ret_inst->Opcode();
- if (opcode == Instruction::RETURN_VOID || opcode == Instruction::RETURN_VOID_NO_BARRIER) {
- SafelyMarkAllRegistersAsConflicts(this, work_line_.get());
- } else {
- if (opcode == Instruction::RETURN_WIDE) {
- work_line_->MarkAllRegistersAsConflictsExceptWide(this, ret_inst->VRegA_11x());
- } else {
- work_line_->MarkAllRegistersAsConflictsExcept(this, ret_inst->VRegA_11x());
- }
- }
+ AdjustReturnLine(this, ret_inst, work_line_.get());
}
RegisterLine* next_line = reg_table_.GetLine(next_insn_idx);
if (next_line != nullptr) {
@@ -3276,9 +3300,7 @@
/* If we're returning from the method, make sure monitor stack is empty. */
if ((opcode_flags & Instruction::kReturn) != 0) {
- if (!work_line_->VerifyMonitorStackEmpty(this)) {
- return false;
- }
+ work_line_->VerifyMonitorStackEmpty(this);
}
/*
@@ -3298,6 +3320,12 @@
DCHECK_LT(*start_guess, code_item_->insns_size_in_code_units_);
DCHECK(insn_flags_[*start_guess].IsOpcode());
+ if (have_pending_runtime_throw_failure_) {
+ have_any_pending_runtime_throw_failure_ = true;
+ // Reset the pending_runtime_throw flag now.
+ have_pending_runtime_throw_failure_ = false;
+ }
+
return true;
} // NOLINT(readability/fn_size)
@@ -4421,31 +4449,15 @@
* there's nothing to "merge". Copy the registers over and mark it as changed. (This is the
* only way a register can transition out of "unknown", so this is not just an optimization.)
*/
- if (!insn_flags_[next_insn].IsReturn()) {
- target_line->CopyFromLine(merge_line);
- } else {
+ target_line->CopyFromLine(merge_line);
+ if (insn_flags_[next_insn].IsReturn()) {
// Verify that the monitor stack is empty on return.
- if (!merge_line->VerifyMonitorStackEmpty(this)) {
- return false;
- }
+ merge_line->VerifyMonitorStackEmpty(this);
+
// For returns we only care about the operand to the return, all other registers are dead.
// Initialize them as conflicts so they don't add to GC and deoptimization information.
const Instruction* ret_inst = Instruction::At(code_item_->insns_ + next_insn);
- Instruction::Code opcode = ret_inst->Opcode();
- if (opcode == Instruction::RETURN_VOID || opcode == Instruction::RETURN_VOID_NO_BARRIER) {
- // Explicitly copy the this-initialized flag from the merge-line, as we didn't copy its
- // state. Must be done before SafelyMarkAllRegistersAsConflicts as that will do the
- // super-constructor-call checking.
- target_line->CopyThisInitialized(*merge_line);
- SafelyMarkAllRegistersAsConflicts(this, target_line);
- } else {
- target_line->CopyFromLine(merge_line);
- if (opcode == Instruction::RETURN_WIDE) {
- target_line->MarkAllRegistersAsConflictsExceptWide(this, ret_inst->VRegA_11x());
- } else {
- target_line->MarkAllRegistersAsConflictsExcept(this, ret_inst->VRegA_11x());
- }
- }
+ AdjustReturnLine(this, ret_inst, target_line);
}
} else {
std::unique_ptr<RegisterLine> copy(gDebugVerify ?
diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h
index 21f8543..b57abf5 100644
--- a/runtime/verifier/method_verifier.h
+++ b/runtime/verifier/method_verifier.h
@@ -67,17 +67,17 @@
* to be rewritten to fail at runtime.
*/
enum VerifyError {
- VERIFY_ERROR_BAD_CLASS_HARD, // VerifyError; hard error that skips compilation.
- VERIFY_ERROR_BAD_CLASS_SOFT, // VerifyError; soft error that verifies again at runtime.
+ VERIFY_ERROR_BAD_CLASS_HARD = 1, // VerifyError; hard error that skips compilation.
+ VERIFY_ERROR_BAD_CLASS_SOFT = 2, // VerifyError; soft error that verifies again at runtime.
- VERIFY_ERROR_NO_CLASS, // NoClassDefFoundError.
- VERIFY_ERROR_NO_FIELD, // NoSuchFieldError.
- VERIFY_ERROR_NO_METHOD, // NoSuchMethodError.
- VERIFY_ERROR_ACCESS_CLASS, // IllegalAccessError.
- VERIFY_ERROR_ACCESS_FIELD, // IllegalAccessError.
- VERIFY_ERROR_ACCESS_METHOD, // IllegalAccessError.
- VERIFY_ERROR_CLASS_CHANGE, // IncompatibleClassChangeError.
- VERIFY_ERROR_INSTANTIATION, // InstantiationError.
+ VERIFY_ERROR_NO_CLASS = 4, // NoClassDefFoundError.
+ VERIFY_ERROR_NO_FIELD = 8, // NoSuchFieldError.
+ VERIFY_ERROR_NO_METHOD = 16, // NoSuchMethodError.
+ VERIFY_ERROR_ACCESS_CLASS = 32, // IllegalAccessError.
+ VERIFY_ERROR_ACCESS_FIELD = 64, // IllegalAccessError.
+ VERIFY_ERROR_ACCESS_METHOD = 128, // IllegalAccessError.
+ VERIFY_ERROR_CLASS_CHANGE = 256, // IncompatibleClassChangeError.
+ VERIFY_ERROR_INSTANTIATION = 512, // InstantiationError.
// For opcodes that don't have complete verifier support (such as lambda opcodes),
// we need a way to continue execution at runtime without attempting to re-verify
// (since we know it will fail no matter what). Instead, run as the interpreter
@@ -85,25 +85,14 @@
// on the fly.
//
// TODO: Once all new opcodes have implemented full verifier support, this can be removed.
- VERIFY_ERROR_FORCE_INTERPRETER, // Skip the verification phase at runtime;
- // force the interpreter to do access checks.
- // (sets a soft fail at compile time).
+ VERIFY_ERROR_FORCE_INTERPRETER = 1024, // Skip the verification phase at runtime;
+ // force the interpreter to do access checks.
+ // (sets a soft fail at compile time).
+ VERIFY_ERROR_LOCKING = 2048, // Could not guarantee balanced locking. This should be
+ // punted to the interpreter with access checks.
};
std::ostream& operator<<(std::ostream& os, const VerifyError& rhs);
-/*
- * Identifies the type of reference in the instruction that generated the verify error
- * (e.g. VERIFY_ERROR_ACCESS_CLASS could come from a method, field, or class reference).
- *
- * This must fit in two bits.
- */
-enum VerifyErrorRefType {
- VERIFY_ERROR_REF_CLASS = 0,
- VERIFY_ERROR_REF_FIELD = 1,
- VERIFY_ERROR_REF_METHOD = 2,
-};
-const int kVerifyErrorRefTypeShift = 6;
-
// We don't need to store the register data for many instructions, because we either only need
// it at branch points (for verification) or GC points and branches (for verification +
// type-precise register analysis).
@@ -291,6 +280,10 @@
return string_init_pc_reg_map_;
}
+ uint32_t GetEncounteredFailureTypes() {
+ return encountered_failure_types_;
+ }
+
private:
// Private constructor for dumping.
MethodVerifier(Thread* self, const DexFile* dex_file, Handle<mirror::DexCache> dex_cache,
@@ -753,6 +746,9 @@
size_t new_instance_count_;
size_t monitor_enter_count_;
+ // Bitset of the encountered failure types. Bits are according to the values in VerifyError.
+ uint32_t encountered_failure_types_;
+
const bool can_load_classes_;
// Converts soft failures to hard failures when false. Only false when the compiler isn't
diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h
index bee5834..1df2428 100644
--- a/runtime/verifier/register_line-inl.h
+++ b/runtime/verifier/register_line-inl.h
@@ -25,6 +25,10 @@
namespace art {
namespace verifier {
+// Should we dump a warning on failures to verify balanced locking? That would be an indication to
+// developers that their code will be slow.
+static constexpr bool kDumpLockFailures = true;
+
inline const RegType& RegisterLine::GetRegisterType(MethodVerifier* verifier, uint32_t vsrc) const {
// The register index was validated during the static pass, so we don't need to check it here.
DCHECK_LT(vsrc, num_regs_);
@@ -167,12 +171,14 @@
return true;
}
-inline bool RegisterLine::VerifyMonitorStackEmpty(MethodVerifier* verifier) const {
+inline void RegisterLine::VerifyMonitorStackEmpty(MethodVerifier* verifier) const {
if (MonitorStackDepth() != 0) {
- verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "expected empty monitor stack";
- return false;
- } else {
- return true;
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "expected empty monitor stack in "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
}
}
diff --git a/runtime/verifier/register_line.cc b/runtime/verifier/register_line.cc
index bb6df76..33c90e3 100644
--- a/runtime/verifier/register_line.cc
+++ b/runtime/verifier/register_line.cc
@@ -344,14 +344,22 @@
verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-enter on non-object ("
<< reg_type << ")";
} else if (monitors_.size() >= 32) {
- verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-enter stack overflow: "
- << monitors_.size();
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "monitor-enter stack overflow while verifying "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
} else {
if (SetRegToLockDepth(reg_idx, monitors_.size())) {
monitors_.push_back(insn_idx);
} else {
- verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unexpected monitor-enter on register v" <<
- reg_idx;
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "unexpected monitor-enter on register v" << reg_idx << " in "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
}
}
}
@@ -361,16 +369,21 @@
if (!reg_type.IsReferenceTypes()) {
verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-exit on non-object (" << reg_type << ")";
} else if (monitors_.empty()) {
- verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-exit stack underflow";
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "monitor-exit stack underflow while verifying "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
} else {
monitors_.pop_back();
if (!IsSetLockDepth(reg_idx, monitors_.size())) {
- // Bug 3215458: Locks and unlocks are on objects, if that object is a literal then before
- // format "036" the constant collector may create unlocks on the same object but referenced
- // via different registers.
- ((verifier->DexFileVersion() >= 36) ? verifier->Fail(VERIFY_ERROR_BAD_CLASS_SOFT)
- : verifier->LogVerifyInfo())
- << "monitor-exit not unlocking the top of the monitor stack";
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "monitor-exit not unlocking the top of the monitor stack while verifying "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
} else {
// Record the register was unlocked
ClearRegToLockDepth(reg_idx, monitors_.size());
@@ -392,8 +405,13 @@
}
if (monitors_.size() > 0 || incoming_line->monitors_.size() > 0) {
if (monitors_.size() != incoming_line->monitors_.size()) {
- LOG(WARNING) << "mismatched stack depths (depth=" << MonitorStackDepth()
- << ", incoming depth=" << incoming_line->MonitorStackDepth() << ")";
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "mismatched stack depths (depth=" << MonitorStackDepth()
+ << ", incoming depth=" << incoming_line->MonitorStackDepth() << ") in "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
} else if (reg_to_lock_depths_ != incoming_line->reg_to_lock_depths_) {
for (uint32_t idx = 0; idx < num_regs_; idx++) {
size_t depths = reg_to_lock_depths_.count(idx);
@@ -402,14 +420,35 @@
if (depths == 0 || incoming_depths == 0) {
reg_to_lock_depths_.erase(idx);
} else {
- LOG(WARNING) << "mismatched stack depths for register v" << idx
- << ": " << depths << " != " << incoming_depths;
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "mismatched stack depths for register v" << idx
+ << ": " << depths << " != " << incoming_depths << " in "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
+ break;
+ }
+ } else if (depths > 0) {
+ // Check whether they're actually the same levels.
+ uint32_t locked_levels = reg_to_lock_depths_.find(idx)->second;
+ uint32_t incoming_locked_levels = incoming_line->reg_to_lock_depths_.find(idx)->second;
+ if (locked_levels != incoming_locked_levels) {
+ verifier->Fail(VERIFY_ERROR_LOCKING);
+ if (kDumpLockFailures) {
+ LOG(WARNING) << "mismatched lock levels for register v" << idx << ": "
+ << std::hex << locked_levels << std::dec << " != "
+ << std::hex << incoming_locked_levels << std::dec << " in "
+ << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+ *verifier->GetMethodReference().dex_file);
+ }
break;
}
}
}
}
}
+
// Check whether "this" was initialized in both paths.
if (this_initialized_ && !incoming_line->this_initialized_) {
this_initialized_ = false;
diff --git a/runtime/verifier/register_line.h b/runtime/verifier/register_line.h
index 41f1e28..46db1c6 100644
--- a/runtime/verifier/register_line.h
+++ b/runtime/verifier/register_line.h
@@ -185,7 +185,9 @@
// Compare two register lines. Returns 0 if they match.
// Using this for a sort is unwise, since the value can change based on machine endianness.
int CompareLine(const RegisterLine* line2) const {
- DCHECK(monitors_ == line2->monitors_);
+ if (monitors_ != line2->monitors_) {
+ return 1;
+ }
// TODO: DCHECK(reg_to_lock_depths_ == line2->reg_to_lock_depths_);
return memcmp(&line_, &line2->line_, num_regs_ * sizeof(uint16_t));
}
@@ -298,8 +300,8 @@
}
// We expect no monitors to be held at certain points, such a method returns. Verify the stack
- // is empty, failing and returning false if not.
- bool VerifyMonitorStackEmpty(MethodVerifier* verifier) const;
+ // is empty, queueing a LOCKING error else.
+ void VerifyMonitorStackEmpty(MethodVerifier* verifier) const;
bool MergeRegisters(MethodVerifier* verifier, const RegisterLine* incoming_line)
SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/test/002-sleep/src/Main.java b/test/002-sleep/src/Main.java
index c1a2d83..55032fd 100644
--- a/test/002-sleep/src/Main.java
+++ b/test/002-sleep/src/Main.java
@@ -2,8 +2,8 @@
static public void main(String[] args) throws Exception {
int millis = 1000;
- if (args.length != 0) {
- millis = Integer.parseInt(args[0]);
+ if (args.length > 1) {
+ millis = Integer.parseInt(args[1]);
}
System.out.println("Sleeping " + millis + " msec...");
diff --git a/test/004-JniTest/src/Main.java b/test/004-JniTest/src/Main.java
index 810dda0..dd88db0 100644
--- a/test/004-JniTest/src/Main.java
+++ b/test/004-JniTest/src/Main.java
@@ -20,7 +20,7 @@
public class Main {
public static void main(String[] args) {
- System.loadLibrary("arttest");
+ System.loadLibrary(args[0]);
testFindClassOnAttachedNativeThread();
testFindFieldOnAttachedNativeThread();
testReflectFieldGetFromAttachedNativeThreadNative();
diff --git a/test/004-ReferenceMap/src/Main.java b/test/004-ReferenceMap/src/Main.java
index f9a5498..dacd748 100644
--- a/test/004-ReferenceMap/src/Main.java
+++ b/test/004-ReferenceMap/src/Main.java
@@ -36,11 +36,8 @@
}
native int refmap(int x);
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
Main rm = new Main();
rm.f();
}
diff --git a/test/004-SignalTest/src/Main.java b/test/004-SignalTest/src/Main.java
index 8b1f49b..6266918 100644
--- a/test/004-SignalTest/src/Main.java
+++ b/test/004-SignalTest/src/Main.java
@@ -24,8 +24,7 @@
}
public static void main(String[] args) {
- System.loadLibrary("arttest");
-
+ System.loadLibrary(args[0]);
System.out.println("init signal test");
initSignalTest();
try {
diff --git a/test/004-StackWalk/src/Main.java b/test/004-StackWalk/src/Main.java
index 9a1d0ab..883ce2c 100644
--- a/test/004-StackWalk/src/Main.java
+++ b/test/004-StackWalk/src/Main.java
@@ -87,11 +87,8 @@
native int stackmap(int x);
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
Main st = new Main();
st.$noinline$f();
}
diff --git a/test/004-ThreadStress/src/Main.java b/test/004-ThreadStress/src/Main.java
index d5b389f..4eeae2f 100644
--- a/test/004-ThreadStress/src/Main.java
+++ b/test/004-ThreadStress/src/Main.java
@@ -310,7 +310,8 @@
boolean dumpMap = false;
if (args != null) {
- for (int i = 0; i < args.length; i++) {
+ // args[0] is libarttest
+ for (int i = 1; i < args.length; i++) {
if (args[i].equals("-n")) {
i++;
numberOfThreads = Integer.parseInt(args[i]);
diff --git a/test/004-UnsafeTest/src/Main.java b/test/004-UnsafeTest/src/Main.java
index 818f5d9..c93db50 100644
--- a/test/004-UnsafeTest/src/Main.java
+++ b/test/004-UnsafeTest/src/Main.java
@@ -18,10 +18,6 @@
import sun.misc.Unsafe;
public class Main {
- static {
- System.loadLibrary("arttest");
- }
-
private static void check(int actual, int expected, String msg) {
if (actual != expected) {
System.out.println(msg + " : " + actual + " != " + expected);
@@ -51,6 +47,7 @@
}
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
Unsafe unsafe = getUnsafe();
check(unsafe.arrayBaseOffset(boolean[].class), vmArrayBaseOffset(boolean[].class),
"Unsafe.arrayBaseOffset(boolean[])");
diff --git a/test/051-thread/src/Main.java b/test/051-thread/src/Main.java
index b81273e..2e26b22 100644
--- a/test/051-thread/src/Main.java
+++ b/test/051-thread/src/Main.java
@@ -20,11 +20,8 @@
* Test some basic thread stuff.
*/
public class Main {
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
System.out.println("thread test starting");
testThreadCapacity();
testThreadDaemons();
diff --git a/test/082-inline-execute/src/Main.java b/test/082-inline-execute/src/Main.java
index bd606a6..08ccf0e 100644
--- a/test/082-inline-execute/src/Main.java
+++ b/test/082-inline-execute/src/Main.java
@@ -594,6 +594,54 @@
Assert.assertEquals(Math.ceil(-2.5), -2.0d, 0.0);
Assert.assertEquals(Math.ceil(-2.9), -2.0d, 0.0);
Assert.assertEquals(Math.ceil(-3.0), -3.0d, 0.0);
+ // 2^52 - 1.5
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)),
+ Double.longBitsToDouble(0x432FFFFFFFFFFFFEl), 0.0);
+ // 2^52 - 0.5
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x432FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x4330000000000000l), 0.0);
+ // 2^52
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x4330000000000000l)),
+ Double.longBitsToDouble(0x4330000000000000l), 0.0);
+ // 2^53 - 1
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x433FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x433FFFFFFFFFFFFFl), 0.0);
+ // 2^53
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x4340000000000000l)),
+ Double.longBitsToDouble(0x4340000000000000l), 0.0);
+ // 2^63 - 2^10
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl), 0.0);
+ // 2^63
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x43E0000000000000l)),
+ Double.longBitsToDouble(0x43E0000000000000l), 0.0);
+ // 2^64
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x43F0000000000000l)),
+ Double.longBitsToDouble(0x43F0000000000000l), 0.0);
+ // -(2^52 - 1.5)
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC32FFFFFFFFFFFFDl)),
+ Double.longBitsToDouble(0xC32FFFFFFFFFFFFCl), 0.0);
+ // -(2^52 - 0.5)
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC32FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC32FFFFFFFFFFFFEl), 0.0);
+ // -2^52
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC330000000000000l)),
+ Double.longBitsToDouble(0xC330000000000000l), 0.0);
+ // -(2^53 - 1)
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl), 0.0);
+ // -2^53
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC340000000000000l)),
+ Double.longBitsToDouble(0xC340000000000000l), 0.0);
+ // -(2^63 - 2^10)
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl), 0.0);
+ // -2^63
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC3E0000000000000l)),
+ Double.longBitsToDouble(0xC3E0000000000000l), 0.0);
+ // -2^64
+ Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC3F0000000000000l)),
+ Double.longBitsToDouble(0xC3F0000000000000l), 0.0);
Assert.assertEquals(Math.ceil(Double.NaN), Double.NaN, 0.0);
Assert.assertEquals(Math.ceil(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, 0.0);
Assert.assertEquals(Math.ceil(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, 0.0);
@@ -613,6 +661,54 @@
Assert.assertEquals(Math.floor(-2.5), -3.0d, 0.0);
Assert.assertEquals(Math.floor(-2.9), -3.0d, 0.0);
Assert.assertEquals(Math.floor(-3.0), -3.0d, 0.0);
+ // 2^52 - 1.5
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)),
+ Double.longBitsToDouble(0x432FFFFFFFFFFFFCl), 0.0);
+ // 2^52 - 0.5
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x432FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x432FFFFFFFFFFFFEl), 0.0);
+ // 2^52
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x4330000000000000l)),
+ Double.longBitsToDouble(0x4330000000000000l), 0.0);
+ // 2^53 - 1
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x433FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x433FFFFFFFFFFFFFl), 0.0);
+ // 2^53
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x4340000000000000l)),
+ Double.longBitsToDouble(0x4340000000000000l), 0.0);
+ // 2^63 - 2^10
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl), 0.0);
+ // 2^63
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x43E0000000000000l)),
+ Double.longBitsToDouble(0x43E0000000000000l), 0.0);
+ // 2^64
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x43F0000000000000l)),
+ Double.longBitsToDouble(0x43F0000000000000l), 0.0);
+ // -(2^52 - 1.5)
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC32FFFFFFFFFFFFDl)),
+ Double.longBitsToDouble(0xC32FFFFFFFFFFFFEl), 0.0);
+ // -(2^52 - 0.5)
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC32FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC330000000000000l), 0.0);
+ // -2^52
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC330000000000000l)),
+ Double.longBitsToDouble(0xC330000000000000l), 0.0);
+ // -(2^53 - 1)
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl), 0.0);
+ // -2^53
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC340000000000000l)),
+ Double.longBitsToDouble(0xC340000000000000l), 0.0);
+ // -(2^63 - 2^10)
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl), 0.0);
+ // -2^63
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC3E0000000000000l)),
+ Double.longBitsToDouble(0xC3E0000000000000l), 0.0);
+ // -2^64
+ Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC3F0000000000000l)),
+ Double.longBitsToDouble(0xC3F0000000000000l), 0.0);
Assert.assertEquals(Math.floor(Double.NaN), Double.NaN, 0.0);
Assert.assertEquals(Math.floor(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, 0.0);
Assert.assertEquals(Math.floor(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, 0.0);
@@ -632,6 +728,54 @@
Assert.assertEquals(Math.rint(-2.5), -2.0d, 0.0);
Assert.assertEquals(Math.rint(-2.9), -3.0d, 0.0);
Assert.assertEquals(Math.rint(-3.0), -3.0d, 0.0);
+ // 2^52 - 1.5
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)),
+ Double.longBitsToDouble(0x432FFFFFFFFFFFFCl), 0.0);
+ // 2^52 - 0.5
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x432FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x4330000000000000l), 0.0);
+ // 2^52
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x4330000000000000l)),
+ Double.longBitsToDouble(0x4330000000000000l), 0.0);
+ // 2^53 - 1
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x433FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x433FFFFFFFFFFFFFl), 0.0);
+ // 2^53
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x4340000000000000l)),
+ Double.longBitsToDouble(0x4340000000000000l), 0.0);
+ // 2^63 - 2^10
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl), 0.0);
+ // 2^63
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x43E0000000000000l)),
+ Double.longBitsToDouble(0x43E0000000000000l), 0.0);
+ // 2^64
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x43F0000000000000l)),
+ Double.longBitsToDouble(0x43F0000000000000l), 0.0);
+ // -(2^52 - 1.5)
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC32FFFFFFFFFFFFDl)),
+ Double.longBitsToDouble(0xC32FFFFFFFFFFFFCl), 0.0);
+ // -(2^52 - 0.5)
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC32FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC330000000000000l), 0.0);
+ // -2^52
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC330000000000000l)),
+ Double.longBitsToDouble(0xC330000000000000l), 0.0);
+ // -(2^53 - 1)
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl), 0.0);
+ // -2^53
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC340000000000000l)),
+ Double.longBitsToDouble(0xC340000000000000l), 0.0);
+ // -(2^63 - 2^10)
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl)),
+ Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl), 0.0);
+ // -2^63
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC3E0000000000000l)),
+ Double.longBitsToDouble(0xC3E0000000000000l), 0.0);
+ // -2^64
+ Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC3F0000000000000l)),
+ Double.longBitsToDouble(0xC3F0000000000000l), 0.0);
Assert.assertEquals(Math.rint(Double.NaN), Double.NaN, 0.0);
Assert.assertEquals(Math.rint(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, 0.0);
Assert.assertEquals(Math.rint(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, 0.0);
diff --git a/test/088-monitor-verification/expected.txt b/test/088-monitor-verification/expected.txt
index 07f5b0b..13b8c73 100644
--- a/test/088-monitor-verification/expected.txt
+++ b/test/088-monitor-verification/expected.txt
@@ -1,7 +1,12 @@
recursiveSync ok
nestedMayThrow ok
constantLock ok
-excessiveNesting ok
notNested ok
twoPath ok
triplet ok
+OK
+TooDeep
+NotStructuredOverUnlock
+NotStructuredUnderUnlock
+UnbalancedJoin
+UnbalancedStraight
diff --git a/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali b/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali
new file mode 100644
index 0000000..aa0c2d5
--- /dev/null
+++ b/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali
@@ -0,0 +1,21 @@
+.class public LNotStructuredOverUnlock;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+ # Lock twice, but unlock thrice.
+
+ monitor-enter v2 # 1
+ monitor-enter v2 # 2
+
+ monitor-exit v2 # 1
+ monitor-exit v2 # 2
+ monitor-exit v2 # 3
+
+ return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali b/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali
new file mode 100644
index 0000000..2c31fda
--- /dev/null
+++ b/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali
@@ -0,0 +1,21 @@
+.class public LNotStructuredUnderUnlock;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+ # Lock thrice, but only unlock twice.
+
+ monitor-enter v2 # 1
+ monitor-enter v2 # 2
+ monitor-enter v2 # 3
+
+ monitor-exit v2 # 1
+ monitor-exit v2 # 2
+
+ return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/OK.smali b/test/088-monitor-verification/smali/OK.smali
new file mode 100644
index 0000000..596798d
--- /dev/null
+++ b/test/088-monitor-verification/smali/OK.smali
@@ -0,0 +1,68 @@
+.class public LOK;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {v1, v2}, LOK;->runNoMonitors(Ljava/lang/Object;Ljava/lang/Object;)V
+
+ invoke-static {v1, v2}, LOK;->runStraightLine(Ljava/lang/Object;Ljava/lang/Object;)V
+
+ invoke-static {v1, v2}, LOK;->runBalancedJoin(Ljava/lang/Object;Ljava/lang/Object;)V
+
+ return-void
+
+.end method
+
+
+
+.method public static runNoMonitors(Ljava/lang/Object;Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsManaged()V
+
+ return-void
+
+.end method
+
+.method public static runStraightLine(Ljava/lang/Object;Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsManaged()V
+
+ monitor-enter v1 # 1
+ monitor-enter v2 # 2
+
+ monitor-exit v2 # 2
+ monitor-exit v1 # 1
+
+ return-void
+
+.end method
+
+.method public static runBalancedJoin(Ljava/lang/Object;Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsManaged()V
+
+ monitor-enter v1 # 1
+
+ if-eqz v2, :Lnull
+
+:LnotNull
+
+ monitor-enter v2 # 2
+ goto :Lend
+
+:Lnull
+ monitor-enter v2 # 2
+
+:Lend
+
+ monitor-exit v2 # 2
+ monitor-exit v1 # 1
+
+ return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/TooDeep.smali b/test/088-monitor-verification/smali/TooDeep.smali
new file mode 100644
index 0000000..1a8f2f0
--- /dev/null
+++ b/test/088-monitor-verification/smali/TooDeep.smali
@@ -0,0 +1,82 @@
+.class public LTooDeep;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;)V
+ .registers 3
+
+ # Lock depth is 33, which is more than the verifier supports. This should have been punted to
+ # the interpreter.
+ invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+ monitor-enter v2 # 1
+ monitor-enter v2 # 2
+ monitor-enter v2 # 3
+ monitor-enter v2 # 4
+ monitor-enter v2 # 5
+ monitor-enter v2 # 6
+ monitor-enter v2 # 7
+ monitor-enter v2 # 8
+ monitor-enter v2 # 9
+ monitor-enter v2 # 10
+ monitor-enter v2 # 11
+ monitor-enter v2 # 12
+ monitor-enter v2 # 13
+ monitor-enter v2 # 14
+ monitor-enter v2 # 15
+ monitor-enter v2 # 16
+ monitor-enter v2 # 17
+ monitor-enter v2 # 18
+ monitor-enter v2 # 19
+ monitor-enter v2 # 20
+ monitor-enter v2 # 21
+ monitor-enter v2 # 22
+ monitor-enter v2 # 23
+ monitor-enter v2 # 24
+ monitor-enter v2 # 25
+ monitor-enter v2 # 26
+ monitor-enter v2 # 27
+ monitor-enter v2 # 28
+ monitor-enter v2 # 29
+ monitor-enter v2 # 30
+ monitor-enter v2 # 31
+ monitor-enter v2 # 32
+ monitor-enter v2 # 33
+
+ monitor-exit v2 # 1
+ monitor-exit v2 # 2
+ monitor-exit v2 # 3
+ monitor-exit v2 # 4
+ monitor-exit v2 # 5
+ monitor-exit v2 # 6
+ monitor-exit v2 # 7
+ monitor-exit v2 # 8
+ monitor-exit v2 # 9
+ monitor-exit v2 # 10
+ monitor-exit v2 # 11
+ monitor-exit v2 # 12
+ monitor-exit v2 # 13
+ monitor-exit v2 # 14
+ monitor-exit v2 # 15
+ monitor-exit v2 # 16
+ monitor-exit v2 # 17
+ monitor-exit v2 # 18
+ monitor-exit v2 # 19
+ monitor-exit v2 # 20
+ monitor-exit v2 # 21
+ monitor-exit v2 # 22
+ monitor-exit v2 # 23
+ monitor-exit v2 # 24
+ monitor-exit v2 # 25
+ monitor-exit v2 # 26
+ monitor-exit v2 # 27
+ monitor-exit v2 # 28
+ monitor-exit v2 # 29
+ monitor-exit v2 # 30
+ monitor-exit v2 # 31
+ monitor-exit v2 # 32
+ monitor-exit v2 # 33
+
+ return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/UnbalancedJoin.smali b/test/088-monitor-verification/smali/UnbalancedJoin.smali
new file mode 100644
index 0000000..da8f773
--- /dev/null
+++ b/test/088-monitor-verification/smali/UnbalancedJoin.smali
@@ -0,0 +1,31 @@
+.class public LUnbalancedJoin;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+ if-eqz v2, :Lnull
+
+:LnotNull
+
+ monitor-enter v1 # 1
+ monitor-enter v2 # 2
+ goto :Lend
+
+:Lnull
+ monitor-enter v2 # 1
+ monitor-enter v1 # 2
+
+:Lend
+
+ # Lock levels are "opposite" for the joined flows.
+
+ monitor-exit v2 # 2
+ monitor-exit v1 # 1
+
+ return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/UnbalancedStraight.smali b/test/088-monitor-verification/smali/UnbalancedStraight.smali
new file mode 100644
index 0000000..68edb6c
--- /dev/null
+++ b/test/088-monitor-verification/smali/UnbalancedStraight.smali
@@ -0,0 +1,18 @@
+.class public LUnbalancedStraight;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;Ljava/lang/Object;)V
+ .registers 3
+
+ invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+ monitor-enter v1 # 1
+ monitor-enter v2 # 2
+
+ monitor-exit v1 # 1 Unbalanced unlock.
+ monitor-exit v2 # 2
+
+ return-void
+
+.end method
diff --git a/test/088-monitor-verification/src/Main.java b/test/088-monitor-verification/src/Main.java
index b60c71e..53b72e9 100644
--- a/test/088-monitor-verification/src/Main.java
+++ b/test/088-monitor-verification/src/Main.java
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
/*
* Entry point and tests that are expected to succeed.
@@ -23,6 +26,7 @@
* Drives tests.
*/
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
Main m = new Main();
m.recursiveSync(0);
@@ -38,11 +42,6 @@
System.out.println("constantLock ok");
m.notExcessiveNesting();
- try {
- TooDeep.excessiveNesting();
- System.err.println("excessiveNesting did not throw");
- } catch (VerifyError ve) {}
- System.out.println("excessiveNesting ok");
m.notNested();
System.out.println("notNested ok");
@@ -55,6 +54,8 @@
m.triplet(obj1, obj2, 0);
System.out.println("triplet ok");
+
+ runSmaliTests();
}
/**
@@ -216,4 +217,62 @@
doNothing(localObj);
}
+
+ // Smali testing code.
+ private static void runSmaliTests() {
+ runTest("OK", new Object[] { new Object(), new Object() }, null);
+ runTest("TooDeep", new Object[] { new Object() }, null);
+ runTest("NotStructuredOverUnlock", new Object[] { new Object() },
+ IllegalMonitorStateException.class);
+ runTest("NotStructuredUnderUnlock", new Object[] { new Object() }, null);
+ // TODO: new IllegalMonitorStateException());
+ runTest("UnbalancedJoin", new Object[] { new Object(), new Object() }, null);
+ runTest("UnbalancedStraight", new Object[] { new Object(), new Object() }, null);
+ }
+
+ private static void runTest(String className, Object[] parameters, Class<?> excType) {
+ System.out.println(className);
+ try {
+ Class<?> c = Class.forName(className);
+
+ Method[] methods = c.getDeclaredMethods();
+
+ // For simplicity we assume that test methods are not overloaded. So searching by name
+ // will give us the method we need to run.
+ Method method = null;
+ for (Method m : methods) {
+ if (m.getName().equals("run")) {
+ method = m;
+ break;
+ }
+ }
+
+ if (method == null) {
+ System.out.println("Could not find test method for " + className);
+ } else if (!Modifier.isStatic(method.getModifiers())) {
+ System.out.println("Test method for " + className + " is not static.");
+ } else {
+ method.invoke(null, parameters);
+ if (excType != null) {
+ System.out.println("Expected an exception in " + className);
+ }
+ }
+ } catch (Throwable exc) {
+ if (excType == null) {
+ System.out.println("Did not expect exception " + exc + " for " + className);
+ exc.printStackTrace(System.out);
+ } else if (exc instanceof InvocationTargetException && exc.getCause() != null &&
+ exc.getCause().getClass().equals(excType)) {
+ // Expected exception is wrapped in InvocationTargetException.
+ } else if (!excType.equals(exc.getClass())) {
+ System.out.println("Expected " + excType.getName() + ", but got " + exc.getClass());
+ } else {
+ // Expected exception, do nothing.
+ }
+ }
+ }
+
+ // Helpers for the smali code.
+ public static native void assertCallerIsInterpreted();
+ public static native void assertCallerIsManaged();
}
diff --git a/test/088-monitor-verification/src/TooDeep.java b/test/088-monitor-verification/src/TooDeep.java
deleted file mode 100644
index 76192e5..0000000
--- a/test/088-monitor-verification/src/TooDeep.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-
-/**
- * The class has a method with too many levels of nested "synchronized"
- * blocks. The verifier will reject it.
- *
- * (It would be perfectly okay if the verifier *didn't* reject this.
- * The goal here is just to exercise the failure path. It also serves
- * as a check to see if the monitor checks are enabled.)
- */
-public class TooDeep {
-
- public static void excessiveNesting() {
- synchronized (TooDeep.class) { // 1
- synchronized (TooDeep.class) { // 2
- synchronized (TooDeep.class) { // 3
- synchronized (TooDeep.class) { // 4
- synchronized (TooDeep.class) { // 5
- synchronized (TooDeep.class) { // 6
- synchronized (TooDeep.class) { // 7
- synchronized (TooDeep.class) { // 8
- synchronized (TooDeep.class) { // 9
- synchronized (TooDeep.class) { // 10
- synchronized (TooDeep.class) { // 11
- synchronized (TooDeep.class) { // 12
- synchronized (TooDeep.class) { // 13
- synchronized (TooDeep.class) { // 14
- synchronized (TooDeep.class) { // 15
- synchronized (TooDeep.class) { // 16
- synchronized (TooDeep.class) { // 17
- synchronized (TooDeep.class) { // 18
- synchronized (TooDeep.class) { // 19
- synchronized (TooDeep.class) { // 20
- synchronized (TooDeep.class) { // 21
- synchronized (TooDeep.class) { // 22
- synchronized (TooDeep.class) { // 23
- synchronized (TooDeep.class) { // 24
- synchronized (TooDeep.class) { // 25
- synchronized (TooDeep.class) { // 26
- synchronized (TooDeep.class) { // 27
- synchronized (TooDeep.class) { // 28
- synchronized (TooDeep.class) { // 29
- synchronized (TooDeep.class) { // 30
- synchronized (TooDeep.class) { // 31
- synchronized (TooDeep.class) { // 32
- synchronized (TooDeep.class) { // 33
- }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
- }
-}
diff --git a/test/088-monitor-verification/stack_inspect.cc b/test/088-monitor-verification/stack_inspect.cc
new file mode 100644
index 0000000..e2899c3
--- /dev/null
+++ b/test/088-monitor-verification/stack_inspect.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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 "jni.h"
+
+#include "base/logging.h"
+#include "dex_file-inl.h"
+#include "mirror/class-inl.h"
+#include "nth_caller_visitor.h"
+#include "runtime.h"
+#include "scoped_thread_state_change.h"
+#include "stack.h"
+#include "thread-inl.h"
+
+namespace art {
+
+// public static native void assertCallerIsInterpreted();
+
+extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsInterpreted(JNIEnv* env, jclass) {
+ LOG(INFO) << "assertCallerIsInterpreted";
+
+ ScopedObjectAccess soa(env);
+ NthCallerVisitor caller(soa.Self(), 1, false);
+ caller.WalkStack();
+ CHECK(caller.caller != nullptr);
+ LOG(INFO) << PrettyMethod(caller.caller);
+ CHECK(caller.GetCurrentShadowFrame() != nullptr);
+}
+
+// public static native void assertCallerIsManaged();
+
+extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsManaged(JNIEnv* env, jclass cls) {
+ // Note: needs some smarts to not fail if there is no managed code, at all.
+ LOG(INFO) << "assertCallerIsManaged";
+
+ ScopedObjectAccess soa(env);
+
+ mirror::Class* klass = soa.Decode<mirror::Class*>(cls);
+ const DexFile& dex_file = klass->GetDexFile();
+ const OatFile::OatDexFile* oat_dex_file = dex_file.GetOatDexFile();
+ if (oat_dex_file == nullptr) {
+ // No oat file, this must be a test configuration that doesn't compile at all. Ignore that the
+ // result will be that we're running the interpreter.
+ return;
+ }
+
+ NthCallerVisitor caller(soa.Self(), 1, false);
+ caller.WalkStack();
+ CHECK(caller.caller != nullptr);
+ LOG(INFO) << PrettyMethod(caller.caller);
+
+ if (caller.GetCurrentShadowFrame() == nullptr) {
+ // Not a shadow frame, this looks good.
+ return;
+ }
+
+ // This could be an interpret-only or a verify-at-runtime compilation, or a read-barrier variant,
+ // or... It's not really safe to just reject now. Let's look at the access flags. If the method
+ // was successfully verified, its access flags should be set to mark it preverified, except when
+ // we're running soft-fail tests.
+ if (Runtime::Current()->IsVerificationSoftFail()) {
+ // Soft-fail config. Everything should be running with interpreter access checks, potentially.
+ return;
+ }
+ CHECK(caller.caller->IsPreverified());
+}
+
+} // namespace art
diff --git a/test/101-fibonacci/src/Main.java b/test/101-fibonacci/src/Main.java
index 3773e1b..c594edb 100644
--- a/test/101-fibonacci/src/Main.java
+++ b/test/101-fibonacci/src/Main.java
@@ -43,7 +43,7 @@
}
public static void main(String[] args) {
- String arg = (args.length > 0) ? args[0] : "10";
+ String arg = (args.length > 1) ? args[1] : "10";
try {
int x = Integer.parseInt(arg);
int y = fibonacci(x);
diff --git a/test/115-native-bridge/run b/test/115-native-bridge/run
index 32a9975..ea2045b 100644
--- a/test/115-native-bridge/run
+++ b/test/115-native-bridge/run
@@ -20,7 +20,9 @@
LIBPATH=$(echo ${ARGS} | sed -r 's/.*Djava.library.path=([^ ]*) .*/\1/')
ln -s ${LIBPATH}/libnativebridgetest.so .
touch libarttest.so
+touch libarttestd.so
ln -s ${LIBPATH}/libarttest.so libarttest2.so
+ln -s ${LIBPATH}/libarttestd.so libarttestd2.so
# pwd likely has /, so it's a pain to put that into a sed rule.
LEFT=$(echo ${ARGS} | sed -r 's/-Djava.library.path.*//')
diff --git a/test/115-native-bridge/src/NativeBridgeMain.java b/test/115-native-bridge/src/NativeBridgeMain.java
index 25390f7..c298b1b 100644
--- a/test/115-native-bridge/src/NativeBridgeMain.java
+++ b/test/115-native-bridge/src/NativeBridgeMain.java
@@ -189,7 +189,7 @@
static public void main(String[] args) throws Exception {
System.out.println("Ready for native bridge tests.");
- System.loadLibrary("arttest");
+ System.loadLibrary(args[0]);
Main.main(null);
}
diff --git a/test/116-nodex2oat/src/Main.java b/test/116-nodex2oat/src/Main.java
index 37ac9d5..086ffb9 100644
--- a/test/116-nodex2oat/src/Main.java
+++ b/test/116-nodex2oat/src/Main.java
@@ -16,6 +16,7 @@
public class Main {
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
System.out.println(
"Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + ".");
@@ -26,10 +27,6 @@
}
}
- static {
- System.loadLibrary("arttest");
- }
-
private native static boolean hasOat();
private native static boolean isDex2OatEnabled();
diff --git a/test/117-nopatchoat/src/Main.java b/test/117-nopatchoat/src/Main.java
index 7bc9dbb..223e120 100644
--- a/test/117-nopatchoat/src/Main.java
+++ b/test/117-nopatchoat/src/Main.java
@@ -16,6 +16,8 @@
public class Main {
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+
boolean executable_correct = (isPic() ?
hasExecutableOat() == true :
hasExecutableOat() == isDex2OatEnabled());
@@ -41,10 +43,6 @@
return ret.substring(0, ret.length() - 1);
}
- static {
- System.loadLibrary("arttest");
- }
-
private native static boolean isDex2OatEnabled();
private native static boolean isPic();
diff --git a/test/118-noimage-dex2oat/src/Main.java b/test/118-noimage-dex2oat/src/Main.java
index 9bf5bb3..dba9166 100644
--- a/test/118-noimage-dex2oat/src/Main.java
+++ b/test/118-noimage-dex2oat/src/Main.java
@@ -19,6 +19,7 @@
public class Main {
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
boolean hasImage = hasImage();
String instructionSet = VMRuntime.getCurrentInstructionSet();
boolean isBootClassPathOnDisk = VMRuntime.isBootClassPathOnDisk(instructionSet);
@@ -41,10 +42,6 @@
testB18485243();
}
- static {
- System.loadLibrary("arttest");
- }
-
private native static boolean hasImage();
private native static boolean isImageDex2OatEnabled();
diff --git a/test/119-noimage-patchoat/src/Main.java b/test/119-noimage-patchoat/src/Main.java
index 11c736a..6a70f58 100644
--- a/test/119-noimage-patchoat/src/Main.java
+++ b/test/119-noimage-patchoat/src/Main.java
@@ -16,6 +16,7 @@
public class Main {
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
boolean hasImage = hasImage();
System.out.println(
"Has image is " + hasImage + ", is image dex2oat enabled is "
@@ -28,10 +29,6 @@
}
}
- static {
- System.loadLibrary("arttest");
- }
-
private native static boolean hasImage();
private native static boolean isImageDex2OatEnabled();
diff --git a/test/131-structural-change/src/Main.java b/test/131-structural-change/src/Main.java
index 8dfa280..6cbbd12 100644
--- a/test/131-structural-change/src/Main.java
+++ b/test/131-structural-change/src/Main.java
@@ -23,6 +23,7 @@
*/
public class Main {
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
new Main().run();
}
@@ -49,9 +50,5 @@
System.out.println("Done.");
}
- static {
- System.loadLibrary("arttest");
- }
-
private native static boolean hasOat();
}
diff --git a/test/134-nodex2oat-nofallback/src/Main.java b/test/134-nodex2oat-nofallback/src/Main.java
index 37ac9d5..086ffb9 100644
--- a/test/134-nodex2oat-nofallback/src/Main.java
+++ b/test/134-nodex2oat-nofallback/src/Main.java
@@ -16,6 +16,7 @@
public class Main {
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
System.out.println(
"Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + ".");
@@ -26,10 +27,6 @@
}
}
- static {
- System.loadLibrary("arttest");
- }
-
private native static boolean hasOat();
private native static boolean isDex2OatEnabled();
diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java
index 6cd187a..dc3ef7e 100644
--- a/test/137-cfi/src/Main.java
+++ b/test/137-cfi/src/Main.java
@@ -41,6 +41,7 @@
}
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
boolean secondary = false;
if (args.length > 0 && args[args.length - 1].equals("--secondary")) {
secondary = true;
@@ -48,10 +49,6 @@
new Main(secondary).run();
}
- static {
- System.loadLibrary("arttest");
- }
-
private void run() {
if (secondary) {
if (!TEST_REMOTE_UNWINDING) {
diff --git a/test/139-register-natives/src/Main.java b/test/139-register-natives/src/Main.java
index 35b2f9c..8dd2131 100644
--- a/test/139-register-natives/src/Main.java
+++ b/test/139-register-natives/src/Main.java
@@ -16,15 +16,12 @@
public class Main {
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
testRegistration1();
testRegistration2();
testRegistration3();
}
- static {
- System.loadLibrary("arttest");
- }
-
// Test that a subclass' method is registered instead of a superclass' method.
private static void testRegistration1() {
registerNatives(TestSub.class);
diff --git a/test/454-get-vreg/src/Main.java b/test/454-get-vreg/src/Main.java
index df07d44..95d4190 100644
--- a/test/454-get-vreg/src/Main.java
+++ b/test/454-get-vreg/src/Main.java
@@ -36,11 +36,8 @@
return 42;
}
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
Main rm = new Main();
if (rm.testSimpleVReg(1, 1.0f, (short)2, true, (byte)3, 'c') != 43) {
throw new Error("Expected 43");
diff --git a/test/455-set-vreg/src/Main.java b/test/455-set-vreg/src/Main.java
index 2172d92..4db9d66 100644
--- a/test/455-set-vreg/src/Main.java
+++ b/test/455-set-vreg/src/Main.java
@@ -40,11 +40,8 @@
native void doNativeCallSetVReg();
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
Main rm = new Main();
int intExpected = 5 - 4 - 3 - 2 - 1;
int intResult = rm.testIntVReg(0, 0, 0, 0, 0);
diff --git a/test/457-regs/src/Main.java b/test/457-regs/src/Main.java
index 0d82033..3b8df44 100644
--- a/test/457-regs/src/Main.java
+++ b/test/457-regs/src/Main.java
@@ -22,6 +22,8 @@
class InnerClass {}
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
Class<?> c = Class.forName("PhiLiveness");
Method m = c.getMethod("mergeOk", boolean.class, byte.class);
m.invoke(null, new Boolean(true), new Byte((byte)2));
@@ -38,8 +40,4 @@
m = c.getMethod("phiAllEquivalents", Main.class);
m.invoke(null, new Main());
}
-
- static {
- System.loadLibrary("arttest");
- }
}
diff --git a/test/461-get-reference-vreg/src/Main.java b/test/461-get-reference-vreg/src/Main.java
index a94c6fb..f7d4356 100644
--- a/test/461-get-reference-vreg/src/Main.java
+++ b/test/461-get-reference-vreg/src/Main.java
@@ -38,11 +38,8 @@
native int doNativeCallRef();
static native int doStaticNativeCallRef();
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
Main rm = new Main();
if (rm.testThisWithInstanceCall() != 1) {
throw new Error("Expected 1");
diff --git a/test/466-get-live-vreg/src/Main.java b/test/466-get-live-vreg/src/Main.java
index 851506b..d036a24 100644
--- a/test/466-get-live-vreg/src/Main.java
+++ b/test/466-get-live-vreg/src/Main.java
@@ -48,11 +48,8 @@
static native void doStaticNativeCallLiveVreg();
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
if (testLiveArgument(staticField3) != staticField3) {
throw new Error("Expected " + staticField3);
}
diff --git a/test/474-fp-sub-neg/expected.txt b/test/474-fp-sub-neg/expected.txt
index 1c15abb..1c7ded3 100644
--- a/test/474-fp-sub-neg/expected.txt
+++ b/test/474-fp-sub-neg/expected.txt
@@ -1,6 +1,13 @@
-0.0
-0.0
-0.0
-0.0
0.0
0.0
+0.0
+0.0
+-0.0
+-0.0
+0.0
+0.0
+0.0
+0.0
+d 0.0
diff --git a/test/474-fp-sub-neg/src/Main.java b/test/474-fp-sub-neg/src/Main.java
index c190e8e..796d56c 100644
--- a/test/474-fp-sub-neg/src/Main.java
+++ b/test/474-fp-sub-neg/src/Main.java
@@ -17,33 +17,58 @@
public class Main {
public static void floatTest() {
float f = 0;
+ float nf = -0;
float fc = 1f;
for (int i = 0; i < 2; i++) {
f -= fc;
f = -f;
+ nf -= fc;
+ nf = -nf;
}
System.out.println(f);
+ System.out.println(nf);
System.out.println(f + 0f);
System.out.println(f - (-0f));
+ System.out.println(-f - (-nf));
+ System.out.println(-f + (-nf));
}
public static void doubleTest() {
double d = 0;
+ double nd = -0;
double dc = 1f;
for (int i = 0; i < 2; i++) {
d -= dc;
d = -d;
+ nd -= dc;
+ nd = -nd;
}
System.out.println(d);
+ System.out.println(nd);
System.out.println(d + 0f);
System.out.println(d - (-0f));
+ System.out.println(-d - (-nd));
+ System.out.println(-d + (-nd));
+ }
+
+ public static void bug_1() {
+ int i4=18, i3=-48959;
+ float d;
+ float f=-0.0f;
+ float a=0.0f;
+
+ d = -f + (-a);
+ f += i4 * i3;
+
+ System.out.println("d " + d);
}
public static void main(String[] args) {
doubleTest();
floatTest();
+ bug_1();
}
}
diff --git a/test/497-inlining-and-class-loader/expected.txt b/test/497-inlining-and-class-loader/expected.txt
index 3e1d85e..f5b9fe0 100644
--- a/test/497-inlining-and-class-loader/expected.txt
+++ b/test/497-inlining-and-class-loader/expected.txt
@@ -1,7 +1,7 @@
java.lang.Exception
- at Main.$noinline$bar(Main.java:127)
+ at Main.$noinline$bar(Main.java:124)
at Level2.$inline$bar(Level1.java:25)
at Level1.$inline$bar(Level1.java:19)
at LoadedByMyClassLoader.bar(Main.java:82)
at java.lang.reflect.Method.invoke(Native Method)
- at Main.main(Main.java:101)
+ at Main.main(Main.java:98)
diff --git a/test/497-inlining-and-class-loader/src/Main.java b/test/497-inlining-and-class-loader/src/Main.java
index 0f7eb59..832b1f0 100644
--- a/test/497-inlining-and-class-loader/src/Main.java
+++ b/test/497-inlining-and-class-loader/src/Main.java
@@ -84,11 +84,8 @@
}
class Main {
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
// Clone resolved methods, to restore the original version just
// before we walk the stack in $noinline$bar.
savedResolvedMethods = cloneResolvedMethods(Main.class);
diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk
index fcb9f8a..82f8c79 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -25,6 +25,7 @@
004-StackWalk/stack_walk_jni.cc \
004-UnsafeTest/unsafe_test.cc \
051-thread/thread_test.cc \
+ 088-monitor-verification/stack_inspect.cc \
116-nodex2oat/nodex2oat.cc \
117-nopatchoat/nopatchoat.cc \
118-noimage-dex2oat/noimage-dex2oat.cc \
@@ -38,8 +39,10 @@
497-inlining-and-class-loader/clear_dex_cache.cc
ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so
+ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttestd.so
ifdef TARGET_2ND_ARCH
ART_TARGET_LIBARTTEST_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttest.so
+ ART_TARGET_LIBARTTEST_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttestd.so
endif
# $(1): target or host
@@ -49,17 +52,23 @@
$$(error expected target or host for argument 1, received $(1))
endif
endif
+ ifneq ($(2),d)
+ ifneq ($(2),)
+ $$(error d or empty for argument 2, received $(2))
+ endif
+ endif
art_target_or_host := $(1)
+ suffix := $(2)
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION)
- LOCAL_MODULE := libarttest
+ LOCAL_MODULE := libarttest$$(suffix)
ifeq ($$(art_target_or_host),target)
LOCAL_MODULE_TAGS := tests
endif
LOCAL_SRC_FILES := $(LIBARTTEST_COMMON_SRC_FILES)
- LOCAL_SHARED_LIBRARIES += libartd libbacktrace
+ LOCAL_SHARED_LIBRARIES += libart$$(suffix) libbacktrace
LOCAL_C_INCLUDES += $(ART_C_INCLUDES) art/runtime
LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk
LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.libarttest.mk
@@ -84,13 +93,16 @@
# Clear locally used variables.
art_target_or_host :=
+ suffix :=
endef
ifeq ($(ART_BUILD_TARGET),true)
- $(eval $(call build-libarttest,target))
+ $(eval $(call build-libarttest,target,))
+ $(eval $(call build-libarttest,target,d))
endif
ifeq ($(ART_BUILD_HOST),true)
- $(eval $(call build-libarttest,host))
+ $(eval $(call build-libarttest,host,))
+ $(eval $(call build-libarttest,host,d))
endif
# Clear locally used variables.
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 4e6df6c..439e423 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -378,41 +378,6 @@
$(PICTEST_TYPES),$(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_TRACING_RUN_TESTS),$(ALL_ADDRESS_SIZES))
endif
-TEST_ART_BROKEN_TRACING_RUN_TESTS :=
-
-# The following tests use libarttest.so, which is linked against libartd.so, so will
-# not work when libart.so is the one loaded.
-# TODO: Find a way to run these tests in ndebug mode.
-TEST_ART_BROKEN_NDEBUG_TESTS := \
- 004-JniTest \
- 004-ReferenceMap \
- 004-SignalTest \
- 004-StackWalk \
- 004-UnsafeTest \
- 051-thread \
- 115-native-bridge \
- 116-nodex2oat \
- 117-nopatchoat \
- 118-noimage-dex2oat \
- 119-noimage-patchoat \
- 131-structural-change \
- 137-cfi \
- 139-register-natives \
- 454-get-vreg \
- 455-set-vreg \
- 457-regs \
- 461-get-reference-vreg \
- 466-get-live-vreg \
- 497-inlining-and-class-loader \
-
-ifneq (,$(filter ndebug,$(RUN_TYPES)))
- ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),ndebug,$(PREBUILD_TYPES), \
- $(COMPILER_TYPES), $(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES),$(IMAGE_TYPES), \
- $(PICTEST_TYPES),$(DEBUGGABLE_TYPES),$(TEST_ART_BROKEN_NDEBUG_TESTS),$(ALL_ADDRESS_SIZES))
-endif
-
-TEST_ART_BROKEN_NDEBUG_TESTS :=
-
# Known broken tests for the interpreter.
# CFI unwinding expects managed frames.
TEST_ART_BROKEN_INTERPRETER_RUN_TESTS := \
@@ -602,8 +567,10 @@
# Also need libarttest.
TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so
+TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttestd.so
ifdef TARGET_2ND_ARCH
TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttest.so
+TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttestd.so
endif
# Also need libnativebridgetest.
@@ -617,12 +584,14 @@
ART_TEST_HOST_RUN_TEST_DEPENDENCIES := \
$(ART_HOST_EXECUTABLES) \
$(ART_HOST_OUT_SHARED_LIBRARIES)/libarttest$(ART_HOST_SHLIB_EXTENSION) \
+ $(ART_HOST_OUT_SHARED_LIBRARIES)/libarttestd$(ART_HOST_SHLIB_EXTENSION) \
$(ART_HOST_OUT_SHARED_LIBRARIES)/libnativebridgetest$(ART_HOST_SHLIB_EXTENSION) \
$(ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION)
ifneq ($(HOST_PREFER_32_BIT),true)
ART_TEST_HOST_RUN_TEST_DEPENDENCIES += \
$(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libarttest$(ART_HOST_SHLIB_EXTENSION) \
+ $(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libarttestd$(ART_HOST_SHLIB_EXTENSION) \
$(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libnativebridgetest$(ART_HOST_SHLIB_EXTENSION) \
$(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION)
endif
diff --git a/test/StackWalk2/StackWalk2.java b/test/StackWalk2/StackWalk2.java
index a879b46..5e7b22c 100644
--- a/test/StackWalk2/StackWalk2.java
+++ b/test/StackWalk2/StackWalk2.java
@@ -50,11 +50,8 @@
native int refmap2(int x);
- static {
- System.loadLibrary("arttest");
- }
-
public static void main(String[] args) {
+ System.loadLibrary(args[0]);
StackWalk2 st = new StackWalk2();
st.f();
}
diff --git a/test/dexdump/run-all-tests b/test/dexdump/run-all-tests
index d9f1e96..9cf7ab6 100755
--- a/test/dexdump/run-all-tests
+++ b/test/dexdump/run-all-tests
@@ -43,7 +43,7 @@
DEXDFLAGS2="-l xml"
# Set up dexlist binary and flags to test.
-DEXL="${ANDROID_HOST_OUT}/bin/dexlist2"
+DEXL="${ANDROID_HOST_OUT}/bin/dexlist"
DEXLFLAGS=""
# Run the tests.
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index a1af577..39dc030 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -47,6 +47,7 @@
DEX_VERIFY=""
USE_DEX2OAT_AND_PATCHOAT="y"
INSTRUCTION_SET_FEATURES=""
+ARGS=""
while true; do
if [ "x$1" = "x--quiet" ]; then
@@ -60,6 +61,14 @@
fi
LIB="$1"
shift
+ elif [ "x$1" = "x--testlib" ]; then
+ shift
+ if [ "x$1" = "x" ]; then
+ echo "$0 missing argument to --testlib" 1>&2
+ exit 1
+ fi
+ ARGS="${ARGS} $1"
+ shift
elif [ "x$1" = "x-Xcompiler-option" ]; then
shift
option="$1"
@@ -369,7 +378,7 @@
$INT_OPTS \
$DEBUGGER_OPTS \
$DALVIKVM_BOOT_OPT \
- -cp $DEX_LOCATION/$TEST_NAME.jar$SECONDARY_DEX $MAIN"
+ -cp $DEX_LOCATION/$TEST_NAME.jar$SECONDARY_DEX $MAIN $ARGS"
# Remove whitespace.
dex2oat_cmdline=$(echo $dex2oat_cmdline)
diff --git a/test/run-test b/test/run-test
index 84c818b..424c2e4 100755
--- a/test/run-test
+++ b/test/run-test
@@ -119,6 +119,7 @@
cfg_output="graph.cfg"
strace_output="strace-output.txt"
lib="libartd.so"
+testlib="arttestd"
run_args="--quiet"
build_args=""
@@ -164,6 +165,7 @@
shift
elif [ "x$1" = "x-O" ]; then
lib="libart.so"
+ testlib="arttest"
shift
elif [ "x$1" = "x--dalvik" ]; then
lib="libdvm.so"
@@ -644,6 +646,10 @@
fi
fi
+if [ "$runtime" != "jvm" ]; then
+ run_args="${run_args} --testlib ${testlib}"
+fi
+
# To cause tests to fail fast, limit the file sizes created by dx, dex2oat and ART output to 2MB.
build_file_size_limit=2048
run_file_size_limit=2048
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
new file mode 100644
index 0000000..3c1522c
--- /dev/null
+++ b/tools/ahat/Android.mk
@@ -0,0 +1,58 @@
+#
+# Copyright (C) 2015 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# --- ahat.jar ----------------
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAR_MANIFEST := src/manifest.txt
+LOCAL_JAVA_RESOURCE_FILES := \
+ $(LOCAL_PATH)/src/help.html \
+ $(LOCAL_PATH)/src/style.css
+
+LOCAL_STATIC_JAVA_LIBRARIES := perflib-prebuilt guavalib trove-prebuilt
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := ahat
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# --- ahat script ----------------
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := ahat
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/ahat $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
+
+ahat: $(LOCAL_BUILT_MODULE)
+
+# --- ahat-test.jar --------------
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, test)
+LOCAL_JAR_MANIFEST := test/manifest.txt
+LOCAL_STATIC_JAVA_LIBRARIES := ahat junit
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := ahat-tests
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+ahat-test: $(LOCAL_BUILT_MODULE)
+ java -jar $<
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
new file mode 100644
index 0000000..a8e3884
--- /dev/null
+++ b/tools/ahat/README.txt
@@ -0,0 +1,110 @@
+AHAT - Android Heap Analysis Tool
+
+Usage:
+ java -jar ahat.jar [-p port] FILE
+ Launch an http server for viewing the given Android heap-dump FILE.
+
+ Options:
+ -p <port>
+ Serve pages on the given port. Defaults to 7100.
+
+TODO:
+ * Add more tips to the help page.
+ - Note that only 'app' heap matters, not 'zygote' or 'image'.
+ - Say what a dex cache is.
+ - Recommend how to start looking at a heap dump.
+ - Say how to enable allocation sites.
+ - Where to submit feedback, questions, and bug reports.
+ * Submit perflib fix for getting stack traces, then uncomment that code in
+ AhatSnapshot to use that.
+ * Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
+ * Filter out RootObjs in mSnapshot.getGCRoots, not RootsHandler.
+ * Let user re-sort sites objects info by clicking column headers.
+ * Let user re-sort "Objects" list.
+ * Show site context and heap and class filter in "Objects" view?
+ * Have a menu at the top of an object view with links to the sections?
+ * Include ahat version and hprof file in the menu at the top of the page?
+ * Heaped Table
+ - Make sortable by clicking on headers.
+ - Use consistent order for heap columns.
+ Sometimes I see "app" first, sometimes last (from one heap dump to
+ another) How about, always sort by name?
+ * For long strings, limit the string length shown in the summary view to
+ something reasonable. Say 50 chars, then add a "..." at the end.
+ * For string summaries, if the string is an offset into a bigger byte array,
+ make sure to show just the part that's in the bigger byte array, not the
+ entire byte array.
+ * For HeapTable with single heap shown, the heap name isn't centered?
+ * Consistently document functions.
+ * Should help be part of an AhatHandler, that automatically gets the menu and
+ stylesheet link rather than duplicating that?
+ * Show version number with --version.
+ * Show somewhere where to send bugs.
+ * /objects query takes a long time to load without parameters.
+ * Include a link to /objects in the overview and menu?
+ * Turn on LOCAL_JAVACFLAGS := -Xlint:unchecked -Werror
+ * Use hex for object ids in URLs?
+ * In general, all tables and descriptions should show a limited amount to
+ start, and only show more when requested by the user.
+ * Don't have handlers inherit from HttpHandler
+ - because they should be independent from http.
+
+ * [low priority] by site allocations won't line up if the stack has been
+ truncated. Is there any way to manually line them up in that case?
+
+ * [low priority] Have a switch to choose whether unreachable objects are
+ ignored or not? Is there any interest in what's unreachable, or is it only
+ reachable objects that people care about?
+
+ * [low priority] Have a way to diff two heap dumps by site.
+ This should be pretty easy to do, actually. The interface is the real
+ question. Maybe: augment each byte count field on every page with the diff
+ if a baseline has been provided, and allow the user to sort by the diff.
+
+Things to Test:
+ * That we can open a hprof without an 'app' heap and show a tabulation of
+ objects normally sorted by 'app' heap by default.
+ * Visit /objects without parameters and verify it doesn't throw an exception.
+ * Visit /objects with an invalid site, verify it doesn't throw an exception.
+ * That we can view an array with 3 million elements in a reasonably short
+ amount of time (not more than 1 second?)
+ * That we can view the list of all objects in a reasonably short amount of
+ time.
+ * That we don't show the 'extra' column in the DominatedList if we are
+ showing all the instances.
+
+Reported Issues:
+ * Request to be able to sort tables by size.
+ * Hangs on showing large arrays, where hat does not hang.
+ - Solution is probably to not show all the array elements by default.
+
+Perflib Requests:
+ * Class objects should have java.lang.Class as their class object, not null.
+ * ArrayInstance should have asString() to get the string, without requiring a
+ length function.
+ * Document that getHeapIndex returns -1 for no such heap.
+ * Look up totalRetainedSize for a heap by Heap object, not by a separate heap
+ index.
+ * What's the difference between getId and getUniqueId?
+ * I see objects with duplicate references.
+ * Don't store stack trace by heap (CL 157252)
+ * A way to get overall retained size by heap.
+ * A method Instance.isReachable()
+
+Things to move to perflib:
+ * Extracting the string from a String Instance.
+ * Extracting bitmap data from bitmap instances.
+ * Adding up allocations by stack frame.
+ * Computing, for each instance, the other instances it dominates.
+
+Release History:
+ 0.1ss Aug 04, 2015
+ Enable stack allocations code (using custom modified perflib).
+ Sort objects in 'objects/' with default sort.
+
+ 0.1-stacks Aug 03, 2015
+ Enable stack allocations code (using custom modified perflib).
+
+ 0.1 July 30, 2015
+ Initial Release
+
diff --git a/tools/ahat/ahat b/tools/ahat/ahat
new file mode 100755
index 0000000..77c1d6e
--- /dev/null
+++ b/tools/ahat/ahat
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 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.
+#
+
+#
+# Wrapper script for calling ahat
+java -jar ${ANDROID_HOST_OUT}/framework/ahat.jar "$@"
diff --git a/tools/ahat/src/AhatHandler.java b/tools/ahat/src/AhatHandler.java
new file mode 100644
index 0000000..2da02f8
--- /dev/null
+++ b/tools/ahat/src/AhatHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * AhatHandler.
+ *
+ * Common base class of all the ahat HttpHandlers.
+ */
+abstract class AhatHandler implements HttpHandler {
+
+ protected AhatSnapshot mSnapshot;
+
+ public AhatHandler(AhatSnapshot snapshot) {
+ mSnapshot = snapshot;
+ }
+
+ public abstract void handle(Doc doc, Query query) throws IOException;
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
+ exchange.sendResponseHeaders(200, 0);
+ PrintStream ps = new PrintStream(exchange.getResponseBody());
+ try {
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+ DocString menu = new DocString();
+ menu.appendLink(DocString.uri("/"), DocString.text("overview"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("roots"), DocString.text("roots"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("sites"), DocString.text("allocations"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("help"), DocString.text("help"));
+ doc.menu(menu);
+ handle(doc, new Query(exchange.getRequestURI()));
+ doc.close();
+ } catch (RuntimeException e) {
+ // Print runtime exceptions to standard error for debugging purposes,
+ // because otherwise they are swallowed and not reported.
+ System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
+ e.printStackTrace();
+ throw e;
+ }
+ ps.close();
+ }
+}
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
new file mode 100644
index 0000000..2437d03
--- /dev/null
+++ b/tools/ahat/src/AhatSnapshot.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.heap.StackFrame;
+import com.android.tools.perflib.heap.StackTrace;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper over the perflib snapshot that provides the behavior we use in
+ * ahat.
+ */
+class AhatSnapshot {
+ private Snapshot mSnapshot;
+ private List<Heap> mHeaps;
+
+ // Map from Instance to the list of Instances it immediately dominates.
+ private Map<Instance, List<Instance>> mDominated;
+
+ private Site mRootSite;
+ private Map<Heap, Long> mHeapSizes;
+
+ public AhatSnapshot(Snapshot snapshot) {
+ mSnapshot = snapshot;
+ mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
+ mDominated = new HashMap<Instance, List<Instance>>();
+ mRootSite = new Site("ROOT");
+ mHeapSizes = new HashMap<Heap, Long>();
+
+ ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
+ for (Heap heap : mHeaps) {
+ long total = 0;
+ for (Instance inst : Iterables.concat(heap.getClasses(), heap.getInstances())) {
+ Instance dominator = inst.getImmediateDominator();
+ if (dominator != null) {
+ total += inst.getSize();
+
+ // Properly label the class of a class object.
+ if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) {
+ inst.setClassId(javaLangClass.getId());
+ }
+
+ // Update dominated instances.
+ List<Instance> instances = mDominated.get(dominator);
+ if (instances == null) {
+ instances = new ArrayList<Instance>();
+ mDominated.put(dominator, instances);
+ }
+ instances.add(inst);
+
+ // Update sites.
+ List<StackFrame> path = Collections.emptyList();
+ StackTrace stack = getStack(inst);
+ int stackId = getStackTraceSerialNumber(stack);
+ if (stack != null) {
+ StackFrame[] frames = getStackFrames(stack);
+ if (frames != null && frames.length > 0) {
+ path = Lists.reverse(Arrays.asList(frames));
+ }
+ }
+ mRootSite.add(stackId, 0, path.iterator(), inst);
+ }
+ }
+ mHeapSizes.put(heap, total);
+ }
+ }
+
+ public Instance findInstance(long id) {
+ return mSnapshot.findInstance(id);
+ }
+
+ public int getHeapIndex(Heap heap) {
+ return mSnapshot.getHeapIndex(heap);
+ }
+
+ public Heap getHeap(String name) {
+ return mSnapshot.getHeap(name);
+ }
+
+ public Collection<RootObj> getGCRoots() {
+ return mSnapshot.getGCRoots();
+ }
+
+ public List<Heap> getHeaps() {
+ return mHeaps;
+ }
+
+ public Site getRootSite() {
+ return mRootSite;
+ }
+
+ /**
+ * Look up the site at which the given object was allocated.
+ */
+ public Site getSiteForInstance(Instance inst) {
+ Site site = mRootSite;
+ StackTrace stack = getStack(inst);
+ if (stack != null) {
+ StackFrame[] frames = getStackFrames(stack);
+ if (frames != null) {
+ List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
+ site = mRootSite.getChild(path.iterator());
+ }
+ }
+ return site;
+ }
+
+ /**
+ * Return a list of those objects immediately dominated by the given
+ * instance.
+ */
+ public List<Instance> getDominated(Instance inst) {
+ return mDominated.get(inst);
+ }
+
+ /**
+ * Return the total size of reachable objects allocated on the given heap.
+ */
+ public long getHeapSize(Heap heap) {
+ return mHeapSizes.get(heap);
+ }
+
+ /**
+ * Return the class name for the given class object.
+ * classObj may be null, in which case "(class unknown)" is returned.
+ */
+ public static String getClassName(ClassObj classObj) {
+ if (classObj == null) {
+ return "(class unknown)";
+ }
+ return classObj.getClassName();
+ }
+
+ // Return the stack where the given instance was allocated.
+ private static StackTrace getStack(Instance inst) {
+ // TODO: return inst.getStack() once perflib is fixed.
+ return null;
+ }
+
+ // Return the list of stack frames for a stack trace.
+ private static StackFrame[] getStackFrames(StackTrace stack) {
+ // TODO: Use stack.getFrames() once perflib is fixed.
+ return null;
+ }
+
+ // Return the serial number of the given stack trace.
+ private static int getStackTraceSerialNumber(StackTrace stack) {
+ // TODO: Use stack.getSerialNumber() once perflib is fixed.
+ return 0;
+ }
+
+ // Get the site associated with the given stack id and depth.
+ // Returns the root site if no such site found.
+ // depth of -1 means the full stack.
+ public Site getSite(int stackId, int depth) {
+ Site site = mRootSite;
+ StackTrace stack = mSnapshot.getStackTrace(stackId);
+ if (stack != null) {
+ StackFrame[] frames = getStackFrames(stack);
+ if (frames != null) {
+ List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
+ if (depth >= 0) {
+ path = path.subList(0, depth);
+ }
+ site = mRootSite.getChild(path.iterator());
+ }
+ }
+ return site;
+ }
+}
diff --git a/tools/ahat/src/BitmapHandler.java b/tools/ahat/src/BitmapHandler.java
new file mode 100644
index 0000000..0f567e3
--- /dev/null
+++ b/tools/ahat/src/BitmapHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Instance;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import javax.imageio.ImageIO;
+
+class BitmapHandler implements HttpHandler {
+ private AhatSnapshot mSnapshot;
+
+ public BitmapHandler(AhatSnapshot snapshot) {
+ mSnapshot = snapshot;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ Query query = new Query(exchange.getRequestURI());
+ long id = query.getLong("id", 0);
+ BufferedImage bitmap = null;
+ Instance inst = mSnapshot.findInstance(id);
+ if (inst != null) {
+ bitmap = InstanceUtils.asBitmap(inst);
+ }
+
+ if (bitmap != null) {
+ exchange.getResponseHeaders().add("Content-Type", "image/png");
+ exchange.sendResponseHeaders(200, 0);
+ OutputStream os = exchange.getResponseBody();
+ ImageIO.write(bitmap, "png", os);
+ os.close();
+ } else {
+ exchange.getResponseHeaders().add("Content-Type", "text/html");
+ exchange.sendResponseHeaders(404, 0);
+ PrintStream ps = new PrintStream(exchange.getResponseBody());
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+ doc.big(DocString.text("No bitmap found for the given request."));
+ doc.close();
+ }
+ } catch (RuntimeException e) {
+ // Print runtime exceptions to standard error for debugging purposes,
+ // because otherwise they are swallowed and not reported.
+ System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
+ e.printStackTrace();
+ throw e;
+ }
+ }
+}
diff --git a/tools/ahat/src/Column.java b/tools/ahat/src/Column.java
new file mode 100644
index 0000000..b7f2829
--- /dev/null
+++ b/tools/ahat/src/Column.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+/**
+ * Configuration of a Doc table column.
+ */
+class Column {
+ public DocString heading;
+ public Align align;
+
+ public static enum Align {
+ LEFT, RIGHT
+ };
+
+ public Column(DocString heading, Align align) {
+ this.heading = heading;
+ this.align = align;
+ }
+
+ /**
+ * Construct a left-aligned column with a simple heading.
+ */
+ public Column(String heading) {
+ this(DocString.text(heading), Align.LEFT);
+ }
+
+ /**
+ * Construct a column with a simple heading.
+ */
+ public Column(String heading, Align align) {
+ this(DocString.text(heading), align);
+ }
+}
diff --git a/tools/ahat/src/Doc.java b/tools/ahat/src/Doc.java
new file mode 100644
index 0000000..7fa70de
--- /dev/null
+++ b/tools/ahat/src/Doc.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import java.util.List;
+
+/**
+ * An interface for rendering a page of content to the user.
+ */
+interface Doc extends AutoCloseable {
+ /**
+ * Output the title of the page.
+ */
+ public void title(String format, Object... args);
+
+ /**
+ * Print a line of text for a page menu.
+ */
+ public void menu(DocString string);
+
+ /**
+ * Start a new section with the given title.
+ */
+ public void section(String title);
+
+ /**
+ * Print a line of text in a normal font.
+ */
+ public void println(DocString string);
+
+ /**
+ * Print a line of text in a large font that is easy to see and click on.
+ */
+ public void big(DocString string);
+
+ /**
+ * Start a table with the given columns.
+ *
+ * An IllegalArgumentException is thrown if no columns are provided.
+ *
+ * This should be followed by calls to the 'row' method to fill in the table
+ * contents and the 'end' method to end the table.
+ */
+ public void table(Column... columns);
+
+ /**
+ * Start a table with the following heading structure:
+ * | description | c2 | c3 | ... |
+ * | h1 | h2 | ... | | | |
+ *
+ * Where subcols describes h1, h2, ...
+ * and cols describes c2, c3, ...
+ *
+ * This should be followed by calls to the 'row' method to fill in the table
+ * contents and the 'end' method to end the table.
+ */
+ public void table(DocString description, List<Column> subcols, List<Column> cols);
+
+ /**
+ * Add a row to the currently active table.
+ * The number of values must match the number of columns provided for the
+ * currently active table.
+ */
+ public void row(DocString... values);
+
+ /**
+ * Start a new description list.
+ *
+ * This should be followed by calls to description() and finally a call to
+ * end().
+ */
+ public void descriptions();
+
+ /**
+ * Add a description to the currently active description list.
+ */
+ public void description(DocString key, DocString value);
+
+ /**
+ * End the currently active table or description list.
+ */
+ public void end();
+}
diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/DocString.java
new file mode 100644
index 0000000..1d997dc
--- /dev/null
+++ b/tools/ahat/src/DocString.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.google.common.html.HtmlEscapers;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * A class representing a small string of document content consisting of text,
+ * links, images, etc.
+ */
+class DocString {
+ private StringBuilder mStringBuilder;
+
+ public DocString() {
+ mStringBuilder = new StringBuilder();
+ }
+
+ /**
+ * Construct a new DocString, initialized with the given text.
+ * Format arguments are supported.
+ */
+ public static DocString text(String format, Object... args) {
+ DocString doc = new DocString();
+ return doc.append(format, args);
+ }
+
+ /**
+ * Construct a new DocString, initialized with the given link.
+ */
+ public static DocString link(URI uri, DocString content) {
+ DocString doc = new DocString();
+ return doc.appendLink(uri, content);
+
+ }
+
+ /**
+ * Construct a new DocString initialized with the given image.
+ */
+ public static DocString image(URI uri, String alt) {
+ return (new DocString()).appendImage(uri, alt);
+ }
+
+ /**
+ * Append literal text to the given doc string.
+ * Format arguments are supported.
+ * Returns this object.
+ */
+ public DocString append(String format, Object... args) {
+ String text = String.format(format, args);
+ mStringBuilder.append(HtmlEscapers.htmlEscaper().escape(text));
+ return this;
+ }
+
+ public DocString append(DocString str) {
+ mStringBuilder.append(str.html());
+ return this;
+ }
+
+ public DocString appendLink(URI uri, DocString content) {
+ mStringBuilder.append("<a href=\"");
+ mStringBuilder.append(uri.toASCIIString());
+ mStringBuilder.append("\">");
+ mStringBuilder.append(content.html());
+ mStringBuilder.append("</a>");
+ return this;
+ }
+
+ public DocString appendImage(URI uri, String alt) {
+ mStringBuilder.append("<img alt=\"");
+ mStringBuilder.append(HtmlEscapers.htmlEscaper().escape(alt));
+ mStringBuilder.append("\" src=\"");
+ mStringBuilder.append(uri.toASCIIString());
+ mStringBuilder.append("\" />");
+ return this;
+ }
+
+ public DocString appendThumbnail(URI uri, String alt) {
+ mStringBuilder.append("<img height=\"16\" alt=\"");
+ mStringBuilder.append(HtmlEscapers.htmlEscaper().escape(alt));
+ mStringBuilder.append("\" src=\"");
+ mStringBuilder.append(uri.toASCIIString());
+ mStringBuilder.append("\" />");
+ return this;
+ }
+
+ /**
+ * Convenience function for constructing a URI from a string with a uri
+ * known to be valid. Format arguments are supported.
+ */
+ public static URI uri(String format, Object... args) {
+ String uriString = String.format(format, args);
+ try {
+ return new URI(uriString);
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Known good uri has syntax error: " + uriString, e);
+ }
+ }
+
+ /**
+ * Render the DocString as html.
+ */
+ public String html() {
+ return mStringBuilder.toString();
+ }
+}
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
new file mode 100644
index 0000000..53d1073
--- /dev/null
+++ b/tools/ahat/src/DominatedList.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for rendering a list of instances dominated by a single instance in a
+ * pretty way.
+ */
+class DominatedList {
+ private static final int kIncrAmount = 100;
+ private static final int kDefaultShown = 100;
+
+ /**
+ * Render a table to the given HtmlWriter showing a pretty list of
+ * instances.
+ *
+ * Rather than show all of the instances (which may be very many), we use
+ * the query parameter "dominated" to specify a limited number of
+ * instances to show. The 'uri' parameter should be the current page URI, so
+ * that we can add links to "show more" and "show less" objects that go to
+ * the same page with only the number of objects adjusted.
+ */
+ public static void render(final AhatSnapshot snapshot, Doc doc,
+ Collection<Instance> instances, Query query) {
+ List<Instance> insts = new ArrayList<Instance>(instances);
+ Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
+
+ int numInstancesToShow = getNumInstancesToShow(query, insts.size());
+ List<Instance> shown = new ArrayList<Instance>(insts.subList(0, numInstancesToShow));
+ List<Instance> hidden = insts.subList(numInstancesToShow, insts.size());
+
+ // Add 'null' as a marker for "all the rest of the objects".
+ if (!hidden.isEmpty()) {
+ shown.add(null);
+ }
+ HeapTable.render(doc, new TableConfig(snapshot, hidden), snapshot, shown);
+
+ if (insts.size() > kDefaultShown) {
+ printMenu(doc, query, numInstancesToShow, insts.size());
+ }
+ }
+
+ private static class TableConfig implements HeapTable.TableConfig<Instance> {
+ AhatSnapshot mSnapshot;
+
+ // Map from heap name to the total size of the instances not shown in the
+ // table.
+ Map<Heap, Long> mHiddenSizes;
+
+ public TableConfig(AhatSnapshot snapshot, List<Instance> hidden) {
+ mSnapshot = snapshot;
+ mHiddenSizes = new HashMap<Heap, Long>();
+ for (Heap heap : snapshot.getHeaps()) {
+ mHiddenSizes.put(heap, 0L);
+ }
+
+ if (!hidden.isEmpty()) {
+ for (Instance inst : hidden) {
+ for (Heap heap : snapshot.getHeaps()) {
+ int index = snapshot.getHeapIndex(heap);
+ long size = inst.getRetainedSize(index);
+ mHiddenSizes.put(heap, mHiddenSizes.get(heap) + size);
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getHeapsDescription() {
+ return "Bytes Retained by Heap";
+ }
+
+ @Override
+ public long getSize(Instance element, Heap heap) {
+ if (element == null) {
+ return mHiddenSizes.get(heap);
+ }
+ int index = mSnapshot.getHeapIndex(heap);
+ return element.getRetainedSize(index);
+ }
+
+ @Override
+ public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
+ HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+ public String getDescription() {
+ return "Object";
+ }
+
+ public DocString render(Instance element) {
+ if (element == null) {
+ return DocString.text("...");
+ } else {
+ return Value.render(element);
+ }
+ }
+ };
+ return Collections.singletonList(value);
+ }
+ }
+
+ // Figure out how many objects to show based on the query parameter.
+ // The resulting value is guaranteed to be at least zero, and no greater
+ // than the number of total objects.
+ private static int getNumInstancesToShow(Query query, int totalNumInstances) {
+ String value = query.get("dominated", null);
+ try {
+ int count = Math.min(totalNumInstances, Integer.parseInt(value));
+ return Math.max(0, count);
+ } catch (NumberFormatException e) {
+ // We can't parse the value as a number. Ignore it.
+ }
+ return Math.min(kDefaultShown, totalNumInstances);
+ }
+
+ // Print a menu line after the table to control how many objects are shown.
+ // It has the form:
+ // (showing X of Y objects - show none - show less - show more - show all)
+ private static void printMenu(Doc doc, Query query, int shown, int all) {
+ DocString menu = new DocString();
+ menu.append("(%d of %d objects shown - ", shown, all);
+ if (shown > 0) {
+ int less = Math.max(0, shown - kIncrAmount);
+ menu.appendLink(query.with("dominated", 0), DocString.text("show none"));
+ menu.append(" - ");
+ menu.appendLink(query.with("dominated", less), DocString.text("show less"));
+ menu.append(" - ");
+ } else {
+ menu.append("show none - show less - ");
+ }
+ if (shown < all) {
+ int more = Math.min(shown + kIncrAmount, all);
+ menu.appendLink(query.with("dominated", more), DocString.text("show more"));
+ menu.append(" - ");
+ menu.appendLink(query.with("dominated", all), DocString.text("show all"));
+ menu.append(")");
+ } else {
+ menu.append("show more - show all)");
+ }
+ doc.println(menu);
+ }
+}
+
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
new file mode 100644
index 0000000..60bb387
--- /dev/null
+++ b/tools/ahat/src/HeapTable.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Heap;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for rendering a table that includes sizes of some kind for each heap.
+ */
+class HeapTable {
+ /**
+ * Configuration for a value column of a heap table.
+ */
+ public static interface ValueConfig<T> {
+ public String getDescription();
+ public DocString render(T element);
+ }
+
+ /**
+ * Configuration for the HeapTable.
+ */
+ public static interface TableConfig<T> {
+ public String getHeapsDescription();
+ public long getSize(T element, Heap heap);
+ public List<ValueConfig<T>> getValueConfigs();
+ }
+
+ public static <T> void render(Doc doc, TableConfig<T> config,
+ AhatSnapshot snapshot, List<T> elements) {
+ // Only show the heaps that have non-zero entries.
+ List<Heap> heaps = new ArrayList<Heap>();
+ for (Heap heap : snapshot.getHeaps()) {
+ if (hasNonZeroEntry(snapshot, heap, config, elements)) {
+ heaps.add(heap);
+ }
+ }
+
+ List<ValueConfig<T>> values = config.getValueConfigs();
+
+ // Print the heap and values descriptions.
+ boolean showTotal = heaps.size() > 1;
+ List<Column> subcols = new ArrayList<Column>();
+ for (Heap heap : heaps) {
+ subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
+ }
+ if (showTotal) {
+ subcols.add(new Column("Total", Column.Align.RIGHT));
+ }
+ List<Column> cols = new ArrayList<Column>();
+ for (ValueConfig value : values) {
+ cols.add(new Column(value.getDescription()));
+ }
+ doc.table(DocString.text(config.getHeapsDescription()), subcols, cols);
+
+ // Print the entries.
+ ArrayList<DocString> vals = new ArrayList<DocString>();
+ for (T elem : elements) {
+ vals.clear();
+ long total = 0;
+ for (Heap heap : heaps) {
+ long size = config.getSize(elem, heap);
+ total += size;
+ vals.add(DocString.text("%,14d", size));
+ }
+ if (showTotal) {
+ vals.add(DocString.text("%,14d", total));
+ }
+
+ for (ValueConfig<T> value : values) {
+ vals.add(value.render(elem));
+ }
+ doc.row(vals.toArray(new DocString[0]));
+ }
+ doc.end();
+ }
+
+ // Returns true if the given heap has a non-zero size entry.
+ public static <T> boolean hasNonZeroEntry(AhatSnapshot snapshot, Heap heap,
+ TableConfig<T> config, List<T> elements) {
+ if (snapshot.getHeapSize(heap) > 0) {
+ for (T element : elements) {
+ if (config.getSize(element, heap) > 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+
diff --git a/tools/ahat/src/HtmlDoc.java b/tools/ahat/src/HtmlDoc.java
new file mode 100644
index 0000000..5ccbacb
--- /dev/null
+++ b/tools/ahat/src/HtmlDoc.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import java.io.PrintStream;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * An Html implementation of Doc.
+ */
+public class HtmlDoc implements Doc {
+ private PrintStream ps;
+ private Column[] mCurrentTableColumns;
+
+ /**
+ * Create an HtmlDoc that writes to the given print stream.
+ * @param title - The main page title.
+ * @param style - A URI link to a stylesheet to link to.
+ */
+ public HtmlDoc(PrintStream ps, DocString title, URI style) {
+ this.ps = ps;
+
+ ps.println("<!DOCTYPE html>");
+ ps.println("<html>");
+ ps.println("<head>");
+ ps.format("<title>%s</title>\n", title.html());
+ ps.format("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n",
+ style.toASCIIString());
+ ps.println("</head>");
+ ps.println("<body>");
+ }
+
+ @Override
+ public void title(String format, Object... args) {
+ ps.print("<h1>");
+ ps.print(DocString.text(String.format(format, args)).html());
+ ps.println("</h1>");
+ }
+
+ @Override
+ public void menu(DocString string) {
+ ps.format("<div class=\"menu\">%s</div>", string.html());
+ }
+
+ @Override
+ public void section(String title) {
+ ps.print("<h2>");
+ ps.print(DocString.text(title).html());
+ ps.println(":</h2>");
+ }
+
+ @Override
+ public void println(DocString string) {
+ ps.print(string.html());
+ ps.println("<br />");
+ }
+
+ @Override
+ public void big(DocString str) {
+ ps.print("<h2>");
+ ps.print(str.html());
+ ps.println("</h2>");
+ }
+
+ @Override
+ public void table(Column... columns) {
+ if (columns.length == 0) {
+ throw new IllegalArgumentException("No columns specified");
+ }
+
+ mCurrentTableColumns = columns;
+ ps.println("<table>");
+ for (int i = 0; i < columns.length - 1; i++) {
+ ps.format("<th>%s</th>", columns[i].heading.html());
+ }
+
+ // Align the last header to the left so it's easier to see if the last
+ // column is very wide.
+ ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+ }
+
+ @Override
+ public void table(DocString description, List<Column> subcols, List<Column> cols) {
+ mCurrentTableColumns = new Column[subcols.size() + cols.size()];
+ int j = 0;
+ for (Column col : subcols) {
+ mCurrentTableColumns[j] = col;
+ j++;
+ }
+ for (Column col : cols) {
+ mCurrentTableColumns[j] = col;
+ j++;
+ }
+
+ ps.println("<table>");
+ ps.format("<tr><th colspan=\"%d\">%s</th>", subcols.size(), description.html());
+ for (int i = 0; i < cols.size() - 1; i++) {
+ ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+ }
+ if (!cols.isEmpty()) {
+ // Align the last column header to the left so it can still be seen if
+ // the last column is very wide.
+ ps.format("<th align=\"left\" rowspan=\"2\">%s</th>",
+ cols.get(cols.size() - 1).heading.html());
+ }
+ ps.println("</tr>");
+
+ ps.print("<tr>");
+ for (Column subcol : subcols) {
+ ps.format("<th>%s</th>", subcol.heading.html());
+ }
+ ps.println("</tr>");
+ }
+
+ @Override
+ public void row(DocString... values) {
+ if (mCurrentTableColumns == null) {
+ throw new IllegalStateException("table method must be called before row");
+ }
+
+ if (mCurrentTableColumns.length != values.length) {
+ throw new IllegalArgumentException(String.format(
+ "Wrong number of row values. Expected %d, but got %d",
+ mCurrentTableColumns.length, values.length));
+ }
+
+ ps.print("<tr>");
+ for (int i = 0; i < values.length; i++) {
+ ps.print("<td");
+ if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
+ ps.print(" align=\"right\"");
+ }
+ ps.format(">%s</td>", values[i].html());
+ }
+ ps.println("</tr>");
+ }
+
+ @Override
+ public void descriptions() {
+ ps.println("<table>");
+ }
+
+ @Override
+ public void description(DocString key, DocString value) {
+ ps.format("<tr><th align=\"left\">%s:</th><td>%s</td></tr>", key.html(), value.html());
+ }
+
+ @Override
+ public void end() {
+ ps.println("</table>");
+ mCurrentTableColumns = null;
+ }
+
+ @Override
+ public void close() {
+ ps.println("</body>");
+ ps.println("</html>");
+ ps.close();
+ }
+}
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
new file mode 100644
index 0000000..7ee3ff2
--- /dev/null
+++ b/tools/ahat/src/InstanceUtils.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Type;
+import java.awt.image.BufferedImage;
+
+/**
+ * Utilities for extracting information from hprof instances.
+ */
+class InstanceUtils {
+ /**
+ * Returns true if the given instance is an instance of a class with the
+ * given name.
+ */
+ public static boolean isInstanceOfClass(Instance inst, String className) {
+ ClassObj cls = inst.getClassObj();
+ return (cls != null && className.equals(cls.getClassName()));
+ }
+
+ /**
+ * Read the char[] value from an hprof Instance.
+ * Returns null if the object can't be interpreted as a char[].
+ */
+ private static char[] asCharArray(Instance inst) {
+ if (! (inst instanceof ArrayInstance)) {
+ return null;
+ }
+
+ ArrayInstance array = (ArrayInstance) inst;
+ if (array.getArrayType() != Type.CHAR) {
+ return null;
+ }
+ return array.asCharArray(0, array.getValues().length);
+ }
+
+ /**
+ * Read the byte[] value from an hprof Instance.
+ * Returns null if the instance is not a byte array.
+ */
+ private static byte[] asByteArray(Instance inst) {
+ if (! (inst instanceof ArrayInstance)) {
+ return null;
+ }
+
+ ArrayInstance array = (ArrayInstance)inst;
+ if (array.getArrayType() != Type.BYTE) {
+ return null;
+ }
+
+ Object[] objs = array.getValues();
+ byte[] bytes = new byte[objs.length];
+ for (int i = 0; i < objs.length; i++) {
+ Byte b = (Byte)objs[i];
+ bytes[i] = b.byteValue();
+ }
+ return bytes;
+ }
+
+
+ // Read the string value from an hprof Instance.
+ // Returns null if the object can't be interpreted as a string.
+ public static String asString(Instance inst) {
+ if (!isInstanceOfClass(inst, "java.lang.String")) {
+ return null;
+ }
+ char[] value = getCharArrayField(inst, "value");
+ return (value == null) ? null : new String(value);
+ }
+
+ /**
+ * Read the bitmap data for the given android.graphics.Bitmap object.
+ * Returns null if the object isn't for android.graphics.Bitmap or the
+ * bitmap data couldn't be read.
+ */
+ public static BufferedImage asBitmap(Instance inst) {
+ if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
+ return null;
+ }
+
+ Integer width = getIntField(inst, "mWidth");
+ if (width == null) {
+ return null;
+ }
+
+ Integer height = getIntField(inst, "mHeight");
+ if (height == null) {
+ return null;
+ }
+
+ byte[] buffer = getByteArrayField(inst, "mBuffer");
+ if (buffer == null) {
+ return null;
+ }
+
+ // Convert the raw data to an image
+ // Convert BGRA to ABGR
+ int[] abgr = new int[height * width];
+ for (int i = 0; i < abgr.length; i++) {
+ abgr[i] = (
+ (((int)buffer[i * 4 + 3] & 0xFF) << 24) +
+ (((int)buffer[i * 4 + 0] & 0xFF) << 16) +
+ (((int)buffer[i * 4 + 1] & 0xFF) << 8) +
+ ((int)buffer[i * 4 + 2] & 0xFF));
+ }
+
+ BufferedImage bitmap = new BufferedImage(
+ width, height, BufferedImage.TYPE_4BYTE_ABGR);
+ bitmap.setRGB(0, 0, width, height, abgr, 0, width);
+ return bitmap;
+ }
+
+ /**
+ * Read a field of an instance.
+ * Returns null if the field value is null or if the field couldn't be read.
+ */
+ private static Object getField(Instance inst, String fieldName) {
+ if (!(inst instanceof ClassInstance)) {
+ return null;
+ }
+
+ ClassInstance clsinst = (ClassInstance) inst;
+ Object value = null;
+ int count = 0;
+ for (ClassInstance.FieldValue field : clsinst.getValues()) {
+ if (fieldName.equals(field.getField().getName())) {
+ value = field.getValue();
+ count++;
+ }
+ }
+ return count == 1 ? value : null;
+ }
+
+ /**
+ * Read a reference field of an instance.
+ * Returns null if the field value is null, or if the field couldn't be read.
+ */
+ private static Instance getRefField(Instance inst, String fieldName) {
+ Object value = getField(inst, fieldName);
+ if (!(value instanceof Instance)) {
+ return null;
+ }
+ return (Instance)value;
+ }
+
+ /**
+ * Read an int field of an instance.
+ * The field is assumed to be an int type.
+ * Returns null if the field value is not an int or could not be read.
+ */
+ private static Integer getIntField(Instance inst, String fieldName) {
+ Object value = getField(inst, fieldName);
+ if (!(value instanceof Integer)) {
+ return null;
+ }
+ return (Integer)value;
+ }
+
+ /**
+ * Read the given field from the given instance.
+ * The field is assumed to be a byte[] field.
+ * Returns null if the field value is null, not a byte[] or could not be read.
+ */
+ private static byte[] getByteArrayField(Instance inst, String fieldName) {
+ Object value = getField(inst, fieldName);
+ if (!(value instanceof Instance)) {
+ return null;
+ }
+ return asByteArray((Instance)value);
+ }
+
+ private static char[] getCharArrayField(Instance inst, String fieldName) {
+ Object value = getField(inst, fieldName);
+ if (!(value instanceof Instance)) {
+ return null;
+ }
+ return asCharArray((Instance)value);
+ }
+
+ // Return the bitmap instance associated with this object, or null if there
+ // is none. This works for android.graphics.Bitmap instances and their
+ // underlying Byte[] instances.
+ public static Instance getAssociatedBitmapInstance(Instance inst) {
+ ClassObj cls = inst.getClassObj();
+ if (cls == null) {
+ return null;
+ }
+
+ if ("android.graphics.Bitmap".equals(cls.getClassName())) {
+ return inst;
+ }
+
+ if (inst instanceof ArrayInstance) {
+ ArrayInstance array = (ArrayInstance)inst;
+ if (array.getArrayType() == Type.BYTE && inst.getHardReferences().size() == 1) {
+ Instance ref = inst.getHardReferences().get(0);
+ ClassObj clsref = ref.getClassObj();
+ if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
+ return ref;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Assuming inst represents a DexCache object, return the dex location for
+ * that dex cache. Returns null if the given instance doesn't represent a
+ * DexCache object or the location could not be found.
+ */
+ public static String getDexCacheLocation(Instance inst) {
+ if (isInstanceOfClass(inst, "java.lang.DexCache")) {
+ Instance location = getRefField(inst, "location");
+ if (location != null) {
+ return asString(location);
+ }
+ }
+ return null;
+ }
+}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
new file mode 100644
index 0000000..2e2ddd2
--- /dev/null
+++ b/tools/ahat/src/Main.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.HprofParser;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.heap.io.HprofBuffer;
+import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
+import com.sun.net.httpserver.HttpServer;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executors;
+
+public class Main {
+
+ public static void help(PrintStream out) {
+ out.println("java -jar ahat.jar [-p port] FILE");
+ out.println(" Launch an http server for viewing "
+ + "the given Android heap-dump FILE.");
+ out.println("");
+ out.println("Options:");
+ out.println(" -p <port>");
+ out.println(" Serve pages on the given port. Defaults to 7100.");
+ out.println("");
+ }
+
+ public static void main(String[] args) throws IOException {
+ int port = 7100;
+ for (String arg : args) {
+ if (arg.equals("--help")) {
+ help(System.out);
+ return;
+ }
+ }
+
+ File hprof = null;
+ for (int i = 0; i < args.length; i++) {
+ if ("-p".equals(args[i]) && i + 1 < args.length) {
+ i++;
+ port = Integer.parseInt(args[i]);
+ } else {
+ if (hprof != null) {
+ System.err.println("multiple input files.");
+ help(System.err);
+ return;
+ }
+ hprof = new File(args[i]);
+ }
+ }
+
+ if (hprof == null) {
+ System.err.println("no input file.");
+ help(System.err);
+ return;
+ }
+
+ System.out.println("Reading hprof file...");
+ HprofBuffer buffer = new MemoryMappedFileBuffer(hprof);
+ Snapshot snapshot = (new HprofParser(buffer)).parse();
+
+ System.out.println("Computing Dominators...");
+ snapshot.computeDominators();
+
+ System.out.println("Processing snapshot for ahat...");
+ AhatSnapshot ahat = new AhatSnapshot(snapshot);
+
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+ InetSocketAddress addr = new InetSocketAddress(loopback, port);
+ HttpServer server = HttpServer.create(addr, 0);
+ server.createContext("/", new OverviewHandler(ahat, hprof));
+ server.createContext("/roots", new RootsHandler(ahat));
+ server.createContext("/object", new ObjectHandler(ahat));
+ server.createContext("/objects", new ObjectsHandler(ahat));
+ server.createContext("/site", new SiteHandler(ahat));
+ server.createContext("/bitmap", new BitmapHandler(ahat));
+ server.createContext("/help", new StaticHandler("help.html", "text/html"));
+ server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
+ server.setExecutor(Executors.newFixedThreadPool(1));
+ System.out.println("Server started on localhost:" + port);
+ server.start();
+ }
+}
+
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
new file mode 100644
index 0000000..eecd7d1
--- /dev/null
+++ b/tools/ahat/src/ObjectHandler.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+class ObjectHandler extends AhatHandler {
+ public ObjectHandler(AhatSnapshot snapshot) {
+ super(snapshot);
+ }
+
+ @Override
+ public void handle(Doc doc, Query query) throws IOException {
+ long id = query.getLong("id", 0);
+ Instance inst = mSnapshot.findInstance(id);
+ if (inst == null) {
+ doc.println(DocString.text("No object with id %08xl", id));
+ return;
+ }
+
+ doc.title("Object %08x", inst.getUniqueId());
+ doc.big(Value.render(inst));
+
+ printAllocationSite(doc, inst);
+ printDominatorPath(doc, inst);
+
+ doc.section("Object Info");
+ ClassObj cls = inst.getClassObj();
+ doc.descriptions();
+ doc.description(DocString.text("Class"), Value.render(cls));
+ doc.description(DocString.text("Size"), DocString.text("%d", inst.getSize()));
+ doc.description(
+ DocString.text("Retained Size"),
+ DocString.text("%d", inst.getTotalRetainedSize()));
+ doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
+ doc.end();
+
+ printBitmap(doc, inst);
+ if (inst instanceof ClassInstance) {
+ printClassInstanceFields(doc, (ClassInstance)inst);
+ } else if (inst instanceof ArrayInstance) {
+ printArrayElements(doc, (ArrayInstance)inst);
+ } else if (inst instanceof ClassObj) {
+ printClassInfo(doc, (ClassObj)inst);
+ }
+ printReferences(doc, inst);
+ printDominatedObjects(doc, query, inst);
+ }
+
+ private static void printClassInstanceFields(Doc doc, ClassInstance inst) {
+ doc.section("Fields");
+ doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
+ for (ClassInstance.FieldValue field : inst.getValues()) {
+ doc.row(
+ DocString.text(field.getField().getType().toString()),
+ DocString.text(field.getField().getName()),
+ Value.render(field.getValue()));
+ }
+ doc.end();
+ }
+
+ private static void printArrayElements(Doc doc, ArrayInstance array) {
+ doc.section("Array Elements");
+ doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
+ Object[] elements = array.getValues();
+ for (int i = 0; i < elements.length; i++) {
+ doc.row(DocString.text("%d", i), Value.render(elements[i]));
+ }
+ doc.end();
+ }
+
+ private static void printClassInfo(Doc doc, ClassObj clsobj) {
+ doc.section("Class Info");
+ doc.descriptions();
+ doc.description(DocString.text("Super Class"), Value.render(clsobj.getSuperClassObj()));
+ doc.description(DocString.text("Class Loader"), Value.render(clsobj.getClassLoader()));
+ doc.end();
+
+ doc.section("Static Fields");
+ doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
+ for (Map.Entry<Field, Object> field : clsobj.getStaticFieldValues().entrySet()) {
+ doc.row(
+ DocString.text(field.getKey().getType().toString()),
+ DocString.text(field.getKey().getName()),
+ Value.render(field.getValue()));
+ }
+ doc.end();
+ }
+
+ private static void printReferences(Doc doc, Instance inst) {
+ doc.section("Objects with References to this Object");
+ if (inst.getHardReferences().isEmpty()) {
+ doc.println(DocString.text("(none)"));
+ } else {
+ doc.table(new Column("Object"));
+ for (Instance ref : inst.getHardReferences()) {
+ doc.row(Value.render(ref));
+ }
+ doc.end();
+ }
+
+ if (inst.getSoftReferences() != null) {
+ doc.section("Objects with Soft References to this Object");
+ doc.table(new Column("Object"));
+ for (Instance ref : inst.getSoftReferences()) {
+ doc.row(Value.render(inst));
+ }
+ doc.end();
+ }
+ }
+
+ private void printAllocationSite(Doc doc, Instance inst) {
+ doc.section("Allocation Site");
+ Site site = mSnapshot.getSiteForInstance(inst);
+ SitePrinter.printSite(doc, mSnapshot, site);
+ }
+
+ // Draw the bitmap corresponding to this instance if there is one.
+ private static void printBitmap(Doc doc, Instance inst) {
+ Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+ if (bitmap != null) {
+ doc.section("Bitmap Image");
+ doc.println(DocString.image(
+ DocString.uri("bitmap?id=%d", bitmap.getId()), "bitmap image"));
+ }
+ }
+
+ private void printDominatorPath(Doc doc, Instance inst) {
+ doc.section("Dominator Path from Root");
+ List<Instance> path = new ArrayList<Instance>();
+ for (Instance parent = inst;
+ parent != null && !(parent instanceof RootObj);
+ parent = parent.getImmediateDominator()) {
+ path.add(parent);
+ }
+
+ // Add 'null' as a marker for the root.
+ path.add(null);
+ Collections.reverse(path);
+
+ HeapTable.TableConfig<Instance> table = new HeapTable.TableConfig<Instance>() {
+ public String getHeapsDescription() {
+ return "Bytes Retained by Heap";
+ }
+
+ public long getSize(Instance element, Heap heap) {
+ if (element == null) {
+ return mSnapshot.getHeapSize(heap);
+ }
+ int index = mSnapshot.getHeapIndex(heap);
+ return element.getRetainedSize(index);
+ }
+
+ public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
+ HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+ public String getDescription() {
+ return "Object";
+ }
+
+ public DocString render(Instance element) {
+ if (element == null) {
+ return DocString.link(DocString.uri("roots"), DocString.text("ROOT"));
+ } else {
+ return DocString.text("→ ").append(Value.render(element));
+ }
+ }
+ };
+ return Collections.singletonList(value);
+ }
+ };
+ HeapTable.render(doc, table, mSnapshot, path);
+ }
+
+ public void printDominatedObjects(Doc doc, Query query, Instance inst) {
+ doc.section("Immediately Dominated Objects");
+ List<Instance> instances = mSnapshot.getDominated(inst);
+ if (instances != null) {
+ DominatedList.render(mSnapshot, doc, instances, query);
+ } else {
+ doc.println(DocString.text("(none)"));
+ }
+ }
+}
+
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
new file mode 100644
index 0000000..066c9d5
--- /dev/null
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Instance;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class ObjectsHandler extends AhatHandler {
+ public ObjectsHandler(AhatSnapshot snapshot) {
+ super(snapshot);
+ }
+
+ @Override
+ public void handle(Doc doc, Query query) throws IOException {
+ int stackId = query.getInt("stack", 0);
+ int depth = query.getInt("depth", 0);
+ String className = query.get("class", null);
+ String heapName = query.get("heap", null);
+ Site site = mSnapshot.getSite(stackId, depth);
+
+ List<Instance> insts = new ArrayList<Instance>();
+ for (Instance inst : site.getObjects()) {
+ if ((heapName == null || inst.getHeap().getName().equals(heapName))
+ && (className == null
+ || AhatSnapshot.getClassName(inst.getClassObj()).equals(className))) {
+ insts.add(inst);
+ }
+ }
+
+ Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
+
+ doc.title("Objects");
+ doc.table(
+ new Column("Size", Column.Align.RIGHT),
+ new Column("Heap"),
+ new Column("Object"));
+ for (Instance inst : insts) {
+ doc.row(
+ DocString.text("%,d", inst.getSize()),
+ DocString.text(inst.getHeap().getName()),
+ Value.render(inst));
+ }
+ doc.end();
+ }
+}
+
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
new file mode 100644
index 0000000..6e6c323
--- /dev/null
+++ b/tools/ahat/src/OverviewHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Heap;
+import java.io.IOException;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+class OverviewHandler extends AhatHandler {
+ private File mHprof;
+
+ public OverviewHandler(AhatSnapshot snapshot, File hprof) {
+ super(snapshot);
+ mHprof = hprof;
+ }
+
+ @Override
+ public void handle(Doc doc, Query query) throws IOException {
+ doc.title("Overview");
+
+ doc.section("General Information");
+ doc.descriptions();
+ doc.description(
+ DocString.text("ahat version"),
+ DocString.text("ahat-%s", OverviewHandler.class.getPackage().getImplementationVersion()));
+ doc.description(DocString.text("hprof file"), DocString.text(mHprof.toString()));
+ doc.end();
+
+ doc.section("Heap Sizes");
+ printHeapSizes(doc);
+
+ DocString menu = new DocString();
+ menu.appendLink(DocString.uri("roots"), DocString.text("Roots"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("site"), DocString.text("Allocations"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("help"), DocString.text("Help"));
+ doc.big(menu);
+ }
+
+ private void printHeapSizes(Doc doc) {
+ List<Object> dummy = Collections.singletonList(null);
+
+ HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
+ public String getHeapsDescription() {
+ return "Bytes Retained by Heap";
+ }
+
+ public long getSize(Object element, Heap heap) {
+ return mSnapshot.getHeapSize(heap);
+ }
+
+ public List<HeapTable.ValueConfig<Object>> getValueConfigs() {
+ return Collections.emptyList();
+ }
+ };
+ HeapTable.render(doc, table, mSnapshot, dummy);
+ }
+}
+
diff --git a/tools/ahat/src/Query.java b/tools/ahat/src/Query.java
new file mode 100644
index 0000000..f910608
--- /dev/null
+++ b/tools/ahat/src/Query.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class for getting and modifying query parameters.
+ */
+class Query {
+ private URI mUri;
+
+ // Map from parameter name to value. If the same parameter appears multiple
+ // times, only the last value is used.
+ private Map<String, String> mParams;
+
+ public Query(URI uri) {
+ mUri = uri;
+ mParams = new HashMap<String, String>();
+
+ String query = uri.getQuery();
+ if (query != null) {
+ for (String param : query.split("&")) {
+ int i = param.indexOf('=');
+ if (i < 0) {
+ mParams.put(param, "");
+ } else {
+ mParams.put(param.substring(0, i), param.substring(i + 1));
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the value of a query parameter with the given name.
+ * If there is no query parameter with that name, returns the default value.
+ * If there are multiple query parameters with that name, the value of the
+ * last query parameter is returned.
+ * If the parameter is defined with an empty value, "" is returned.
+ */
+ public String get(String name, String defaultValue) {
+ String value = mParams.get(name);
+ return (value == null) ? defaultValue : value;
+ }
+
+ /**
+ * Return the long value of a query parameter with the given name.
+ */
+ public long getLong(String name, long defaultValue) {
+ String value = get(name, null);
+ return value == null ? defaultValue : Long.parseLong(value);
+ }
+
+ /**
+ * Return the int value of a query parameter with the given name.
+ */
+ public int getInt(String name, int defaultValue) {
+ String value = get(name, null);
+ return value == null ? defaultValue : Integer.parseInt(value);
+ }
+
+ /**
+ * Return a uri suitable for an href target that links to the current
+ * page, except with the named query parameter set to the new value.
+ *
+ * The generated parameters will be sorted alphabetically so it is easier to
+ * test.
+ */
+ public URI with(String name, String value) {
+ StringBuilder newQuery = new StringBuilder();
+ newQuery.append(mUri.getRawPath());
+ newQuery.append('?');
+
+ Map<String, String> params = new TreeMap<String, String>(mParams);
+ params.put(name, value);
+ String and = "";
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ newQuery.append(and);
+ newQuery.append(entry.getKey());
+ newQuery.append('=');
+ newQuery.append(entry.getValue());
+ and = "&";
+ }
+ return DocString.uri(newQuery.toString());
+ }
+
+ /**
+ * Return a uri suitable for an href target that links to the current
+ * page, except with the named query parameter set to the new long value.
+ */
+ public URI with(String name, long value) {
+ return with(name, String.valueOf(value));
+ }
+}
diff --git a/tools/ahat/src/RootsHandler.java b/tools/ahat/src/RootsHandler.java
new file mode 100644
index 0000000..185b9bf
--- /dev/null
+++ b/tools/ahat/src/RootsHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class RootsHandler extends AhatHandler {
+ public RootsHandler(AhatSnapshot snapshot) {
+ super(snapshot);
+ }
+
+ @Override
+ public void handle(Doc doc, Query query) throws IOException {
+ doc.title("Roots");
+
+ Set<Instance> rootset = new HashSet<Instance>();
+ for (RootObj root : mSnapshot.getGCRoots()) {
+ Instance inst = root.getReferredInstance();
+ if (inst != null) {
+ rootset.add(inst);
+ }
+ }
+
+ List<Instance> roots = new ArrayList<Instance>();
+ for (Instance inst : rootset) {
+ roots.add(inst);
+ }
+ DominatedList.render(mSnapshot, doc, roots, query);
+ }
+}
+
diff --git a/tools/ahat/src/Site.java b/tools/ahat/src/Site.java
new file mode 100644
index 0000000..d504096
--- /dev/null
+++ b/tools/ahat/src/Site.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.StackFrame;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+class Site {
+ // The site that this site was directly called from.
+ // mParent is null for the root site.
+ private Site mParent;
+
+ // A description of the Site. Currently this is used to uniquely identify a
+ // site within its parent.
+ private String mName;
+
+ // To identify this site, we pick one stack trace where we have seen the
+ // site. mStackId is the id for that stack trace, and mStackDepth is the
+ // depth of this site in that stack trace.
+ // For the root site, mStackId is 0 and mStackDepth is 0.
+ private int mStackId;
+ private int mStackDepth;
+
+ // Mapping from heap name to the total size of objects allocated in this
+ // site (including child sites) on the given heap.
+ private Map<String, Long> mSizesByHeap;
+
+ // Mapping from child site name to child site.
+ private Map<String, Site> mChildren;
+
+ // List of all objects allocated in this site (including child sites).
+ private List<Instance> mObjects;
+ private List<ObjectsInfo> mObjectsInfos;
+ private Map<Heap, Map<ClassObj, ObjectsInfo>> mObjectsInfoMap;
+
+ public static class ObjectsInfo {
+ public Heap heap;
+ public ClassObj classObj;
+ public long numInstances;
+ public long numBytes;
+
+ public ObjectsInfo(Heap heap, ClassObj classObj, long numInstances, long numBytes) {
+ this.heap = heap;
+ this.classObj = classObj;
+ this.numInstances = numInstances;
+ this.numBytes = numBytes;
+ }
+ }
+
+ /**
+ * Construct a root site.
+ */
+ public Site(String name) {
+ this(null, name, 0, 0);
+ }
+
+ public Site(Site parent, String name, int stackId, int stackDepth) {
+ mParent = parent;
+ mName = name;
+ mStackId = stackId;
+ mStackDepth = stackDepth;
+ mSizesByHeap = new HashMap<String, Long>();
+ mChildren = new HashMap<String, Site>();
+ mObjects = new ArrayList<Instance>();
+ mObjectsInfos = new ArrayList<ObjectsInfo>();
+ mObjectsInfoMap = new HashMap<Heap, Map<ClassObj, ObjectsInfo>>();
+ }
+
+ /**
+ * Add an instance to this site.
+ * Returns the site at which the instance was allocated.
+ */
+ public Site add(int stackId, int stackDepth, Iterator<StackFrame> path, Instance inst) {
+ mObjects.add(inst);
+
+ String heap = inst.getHeap().getName();
+ mSizesByHeap.put(heap, getSize(heap) + inst.getSize());
+
+ Map<ClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(inst.getHeap());
+ if (classToObjectsInfo == null) {
+ classToObjectsInfo = new HashMap<ClassObj, ObjectsInfo>();
+ mObjectsInfoMap.put(inst.getHeap(), classToObjectsInfo);
+ }
+
+ ObjectsInfo info = classToObjectsInfo.get(inst.getClassObj());
+ if (info == null) {
+ info = new ObjectsInfo(inst.getHeap(), inst.getClassObj(), 0, 0);
+ mObjectsInfos.add(info);
+ classToObjectsInfo.put(inst.getClassObj(), info);
+ }
+
+ info.numInstances++;
+ info.numBytes += inst.getSize();
+
+ if (path.hasNext()) {
+ String next = path.next().toString();
+ Site child = mChildren.get(next);
+ if (child == null) {
+ child = new Site(this, next, stackId, stackDepth + 1);
+ mChildren.put(next, child);
+ }
+ return child.add(stackId, stackDepth + 1, path, inst);
+ } else {
+ return this;
+ }
+ }
+
+ // Get the size of a site for a specific heap.
+ public long getSize(String heap) {
+ Long val = mSizesByHeap.get(heap);
+ if (val == null) {
+ return 0;
+ }
+ return val;
+ }
+
+ /**
+ * Get the list of objects allocated under this site. Includes objects
+ * allocated in children sites.
+ */
+ public Collection<Instance> getObjects() {
+ return mObjects;
+ }
+
+ public List<ObjectsInfo> getObjectsInfos() {
+ return mObjectsInfos;
+ }
+
+ // Get the combined size of the site for all heaps.
+ public long getTotalSize() {
+ long size = 0;
+ for (Long val : mSizesByHeap.values()) {
+ size += val;
+ }
+ return size;
+ }
+
+ /**
+ * Return the site this site was called from.
+ * Returns null for the root site.
+ */
+ public Site getParent() {
+ return mParent;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ // Returns the hprof id of a stack this site appears on.
+ public int getStackId() {
+ return mStackId;
+ }
+
+ // Returns the stack depth of this site in the stack whose id is returned
+ // by getStackId().
+ public int getStackDepth() {
+ return mStackDepth;
+ }
+
+ List<Site> getChildren() {
+ return new ArrayList<Site>(mChildren.values());
+ }
+
+ // Get the child at the given path relative to this site.
+ // Returns null if no such child found.
+ Site getChild(Iterator<StackFrame> path) {
+ if (path.hasNext()) {
+ String next = path.next().toString();
+ Site child = mChildren.get(next);
+ return (child == null) ? null : child.getChild(path);
+ } else {
+ return this;
+ }
+ }
+}
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
new file mode 100644
index 0000000..8fbc176
--- /dev/null
+++ b/tools/ahat/src/SiteHandler.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Heap;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+class SiteHandler extends AhatHandler {
+ public SiteHandler(AhatSnapshot snapshot) {
+ super(snapshot);
+ }
+
+ @Override
+ public void handle(Doc doc, Query query) throws IOException {
+ int stackId = query.getInt("stack", 0);
+ int depth = query.getInt("depth", -1);
+ Site site = mSnapshot.getSite(stackId, depth);
+
+ doc.title("Site %s", site.getName());
+ doc.section("Allocation Site");
+ SitePrinter.printSite(doc, mSnapshot, site);
+
+ doc.section("Sites Called from Here");
+ List<Site> children = site.getChildren();
+ if (children.isEmpty()) {
+ doc.println(DocString.text("(none)"));
+ } else {
+ Collections.sort(children, new Sort.SiteBySize("app"));
+
+ HeapTable.TableConfig<Site> table = new HeapTable.TableConfig<Site>() {
+ public String getHeapsDescription() {
+ return "Reachable Bytes Allocated on Heap";
+ }
+
+ public long getSize(Site element, Heap heap) {
+ return element.getSize(heap.getName());
+ }
+
+ public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
+ HeapTable.ValueConfig<Site> value = new HeapTable.ValueConfig<Site>() {
+ public String getDescription() {
+ return "Child Site";
+ }
+
+ public DocString render(Site element) {
+ return DocString.link(
+ DocString.uri("site?stack=%d&depth=%d",
+ element.getStackId(), element.getStackDepth()),
+ DocString.text(element.getName()));
+ }
+ };
+ return Collections.singletonList(value);
+ }
+ };
+ HeapTable.render(doc, table, mSnapshot, children);
+ }
+
+ doc.section("Objects Allocated");
+ doc.table(
+ new Column("Reachable Bytes Allocated", Column.Align.RIGHT),
+ new Column("Instances", Column.Align.RIGHT),
+ new Column("Heap"),
+ new Column("Class"));
+ List<Site.ObjectsInfo> infos = site.getObjectsInfos();
+ Comparator<Site.ObjectsInfo> compare = new Sort.WithPriority<Site.ObjectsInfo>(
+ new Sort.ObjectsInfoByHeapName(),
+ new Sort.ObjectsInfoBySize(),
+ new Sort.ObjectsInfoByClassName());
+ Collections.sort(infos, compare);
+ for (Site.ObjectsInfo info : infos) {
+ String className = AhatSnapshot.getClassName(info.classObj);
+ doc.row(
+ DocString.text("%,14d", info.numBytes),
+ DocString.link(
+ DocString.uri("objects?stack=%d&depth=%d&heap=%s&class=%s",
+ site.getStackId(), site.getStackDepth(), info.heap.getName(), className),
+ DocString.text("%,14d", info.numInstances)),
+ DocString.text(info.heap.getName()),
+ Value.render(info.classObj));
+ }
+ doc.end();
+ }
+}
+
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
new file mode 100644
index 0000000..9c0c2e0
--- /dev/null
+++ b/tools/ahat/src/SitePrinter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Heap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class SitePrinter {
+ public static void printSite(Doc doc, AhatSnapshot snapshot, Site site) {
+ List<Site> path = new ArrayList<Site>();
+ for (Site parent = site; parent != null; parent = parent.getParent()) {
+ path.add(parent);
+ }
+ Collections.reverse(path);
+
+
+ HeapTable.TableConfig<Site> table = new HeapTable.TableConfig<Site>() {
+ public String getHeapsDescription() {
+ return "Reachable Bytes Allocated on Heap";
+ }
+
+ public long getSize(Site element, Heap heap) {
+ return element.getSize(heap.getName());
+ }
+
+ public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
+ HeapTable.ValueConfig<Site> value = new HeapTable.ValueConfig<Site>() {
+ public String getDescription() {
+ return "Stack Frame";
+ }
+
+ public DocString render(Site element) {
+ DocString str = new DocString();
+ if (element.getParent() != null) {
+ str.append("→ ");
+ }
+ str.appendLink(
+ DocString.uri("site?stack=%d&depth=%d",
+ element.getStackId(), element.getStackDepth()),
+ DocString.text(element.getName()));
+ return str;
+ }
+ };
+ return Collections.singletonList(value);
+ }
+ };
+ HeapTable.render(doc, table, snapshot, path);
+ }
+}
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java
new file mode 100644
index 0000000..3b79166
--- /dev/null
+++ b/tools/ahat/src/Sort.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Heap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Iterator;
+
+/**
+ * Provides Comparators and helper functions for sorting Instances, Sites, and
+ * other things.
+ *
+ * Note: The Comparators defined here impose orderings that are inconsistent
+ * with equals. They should not be used for element lookup or search. They
+ * should only be used for showing elements to the user in different orders.
+ */
+class Sort {
+ /**
+ * Compare instances by their instance id.
+ * This sorts instances from smaller id to larger id.
+ */
+ public static class InstanceById implements Comparator<Instance> {
+ @Override
+ public int compare(Instance a, Instance b) {
+ return Long.compare(a.getId(), b.getId());
+ }
+ }
+
+ /**
+ * Compare instances by their total retained size.
+ * Different instances with the same total retained size are considered
+ * equal for the purposes of comparison.
+ * This sorts instances from larger retained size to smaller retained size.
+ */
+ public static class InstanceByTotalRetainedSize implements Comparator<Instance> {
+ @Override
+ public int compare(Instance a, Instance b) {
+ return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
+ }
+ }
+
+ /**
+ * Compare instances by their retained size for a given heap index.
+ * Different instances with the same total retained size are considered
+ * equal for the purposes of comparison.
+ * This sorts instances from larger retained size to smaller retained size.
+ */
+ public static class InstanceByHeapRetainedSize implements Comparator<Instance> {
+ private int mIndex;
+
+ public InstanceByHeapRetainedSize(AhatSnapshot snapshot, Heap heap) {
+ mIndex = snapshot.getHeapIndex(heap);
+ }
+
+ public InstanceByHeapRetainedSize(int heapIndex) {
+ mIndex = heapIndex;
+ }
+
+ @Override
+ public int compare(Instance a, Instance b) {
+ return Long.compare(b.getRetainedSize(mIndex), a.getRetainedSize(mIndex));
+ }
+ }
+
+ /**
+ * Compare objects based on a list of comparators, giving priority to the
+ * earlier comparators in the list.
+ */
+ public static class WithPriority<T> implements Comparator<T> {
+ private List<Comparator<T>> mComparators;
+
+ public WithPriority(Comparator<T>... comparators) {
+ mComparators = Arrays.asList(comparators);
+ }
+
+ public WithPriority(List<Comparator<T>> comparators) {
+ mComparators = comparators;
+ }
+
+ @Override
+ public int compare(T a, T b) {
+ int res = 0;
+ Iterator<Comparator<T>> iter = mComparators.iterator();
+ while (res == 0 && iter.hasNext()) {
+ res = iter.next().compare(a, b);
+ }
+ return res;
+ }
+ }
+
+ public static Comparator<Instance> defaultInstanceCompare(AhatSnapshot snapshot) {
+ List<Comparator<Instance>> comparators = new ArrayList<Comparator<Instance>>();
+
+ // Priority goes to the app heap, if we can find one.
+ Heap appHeap = snapshot.getHeap("app");
+ if (appHeap != null) {
+ comparators.add(new InstanceByHeapRetainedSize(snapshot, appHeap));
+ }
+
+ // Next is by total retained size.
+ comparators.add(new InstanceByTotalRetainedSize());
+ return new WithPriority<Instance>(comparators);
+ }
+
+ /**
+ * Compare Sites by the size of objects allocated on a given heap.
+ * Different object infos with the same size on the given heap are
+ * considered equal for the purposes of comparison.
+ * This sorts sites from larger size to smaller size.
+ */
+ public static class SiteBySize implements Comparator<Site> {
+ String mHeap;
+
+ public SiteBySize(String heap) {
+ mHeap = heap;
+ }
+
+ @Override
+ public int compare(Site a, Site b) {
+ return Long.compare(b.getSize(mHeap), a.getSize(mHeap));
+ }
+ }
+
+ /**
+ * Compare Site.ObjectsInfo by their size.
+ * Different object infos with the same total retained size are considered
+ * equal for the purposes of comparison.
+ * This sorts object infos from larger retained size to smaller size.
+ */
+ public static class ObjectsInfoBySize implements Comparator<Site.ObjectsInfo> {
+ @Override
+ public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+ return Long.compare(b.numBytes, a.numBytes);
+ }
+ }
+
+ /**
+ * Compare Site.ObjectsInfo by heap name.
+ * Different object infos with the same heap name are considered equal for
+ * the purposes of comparison.
+ */
+ public static class ObjectsInfoByHeapName implements Comparator<Site.ObjectsInfo> {
+ @Override
+ public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+ return a.heap.getName().compareTo(b.heap.getName());
+ }
+ }
+
+ /**
+ * Compare Site.ObjectsInfo by class name.
+ * Different object infos with the same class name are considered equal for
+ * the purposes of comparison.
+ */
+ public static class ObjectsInfoByClassName implements Comparator<Site.ObjectsInfo> {
+ @Override
+ public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+ String aName = AhatSnapshot.getClassName(a.classObj);
+ String bName = AhatSnapshot.getClassName(b.classObj);
+ return aName.compareTo(bName);
+ }
+ }
+}
+
diff --git a/tools/ahat/src/StaticHandler.java b/tools/ahat/src/StaticHandler.java
new file mode 100644
index 0000000..fb7049d
--- /dev/null
+++ b/tools/ahat/src/StaticHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.google.common.io.ByteStreams;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpExchange;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+// Handler that returns a static file included in ahat.jar.
+class StaticHandler implements HttpHandler {
+ private String mResourceName;
+ private String mContentType;
+
+ public StaticHandler(String resourceName, String contentType) {
+ mResourceName = resourceName;
+ mContentType = contentType;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ ClassLoader loader = StaticHandler.class.getClassLoader();
+ InputStream is = loader.getResourceAsStream(mResourceName);
+ if (is == null) {
+ exchange.getResponseHeaders().add("Content-Type", "text/html");
+ exchange.sendResponseHeaders(404, 0);
+ PrintStream ps = new PrintStream(exchange.getResponseBody());
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+ doc.big(DocString.text("Resource not found."));
+ doc.close();
+ } else {
+ exchange.getResponseHeaders().add("Content-Type", mContentType);
+ exchange.sendResponseHeaders(200, 0);
+ OutputStream os = exchange.getResponseBody();
+ ByteStreams.copy(is, os);
+ os.close();
+ }
+ }
+}
diff --git a/tools/ahat/src/Value.java b/tools/ahat/src/Value.java
new file mode 100644
index 0000000..22c3b8f
--- /dev/null
+++ b/tools/ahat/src/Value.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Instance;
+import java.net.URI;
+
+/**
+ * Class to render an hprof value to a DocString.
+ */
+class Value {
+
+ /**
+ * Create a DocString representing a summary of the given instance.
+ */
+ private static DocString renderInstance(Instance inst) {
+ DocString link = new DocString();
+ if (inst == null) {
+ link.append("(null)");
+ return link;
+ }
+
+ // Annotate classes as classes.
+ if (inst instanceof ClassObj) {
+ link.append("class ");
+ }
+
+ link.append(inst.toString());
+
+ // Annotate Strings with their values.
+ String stringValue = InstanceUtils.asString(inst);
+ if (stringValue != null) {
+ link.append("\"%s\"", stringValue);
+ }
+
+ // Annotate DexCache with its location.
+ String dexCacheLocation = InstanceUtils.getDexCacheLocation(inst);
+ if (dexCacheLocation != null) {
+ link.append(" for " + dexCacheLocation);
+ }
+
+ URI objTarget = DocString.uri("object?id=%d", inst.getId());
+ DocString formatted = DocString.link(objTarget, link);
+
+ // Annotate bitmaps with a thumbnail.
+ Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+ String thumbnail = "";
+ if (bitmap != null) {
+ URI uri = DocString.uri("bitmap?id=%d", bitmap.getId());
+ formatted.appendThumbnail(uri, "bitmap image");
+ }
+ return formatted;
+ }
+
+ /**
+ * Create a DocString summarizing the given value.
+ */
+ public static DocString render(Object val) {
+ if (val instanceof Instance) {
+ return renderInstance((Instance)val);
+ } else {
+ return DocString.text("%s", val);
+ }
+ }
+}
diff --git a/tools/ahat/src/help.html b/tools/ahat/src/help.html
new file mode 100644
index 0000000..b48d791
--- /dev/null
+++ b/tools/ahat/src/help.html
@@ -0,0 +1,56 @@
+<!--
+Copyright (C) 2015 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.
+-->
+
+<head>
+<link rel="stylesheet" type="text/css" href="style.css">
+</head>
+
+<div class="menu">
+ <a href="/">overview</a> -
+ <a href="roots">roots</a> -
+ <a href="sites">allocations</a> -
+ <a href="help">help</a>
+</div>
+
+<h1>Help</h1>
+<h2>Information shown by ahat:</h2>
+<ul>
+ <li><a href="/">The total bytes retained by heap.</a></li>
+ <li><a href="/roots">A list of root objects and their retained sizes for each heap.</a></li>
+ <li>Information about each allocated object:
+ <ul>
+ <li>The allocation site (stack trace) of the object (if available).</li>
+ <li>The dominator path from a root to the object.</li>
+ <li>The class, (shallow) size, retained size, and heap of the object.</li>
+ <li>The bitmap image for the object if the object represents a bitmap.</li>
+ <li>The instance fields or array elements of the object.</li>
+ <li>The super class, class loader, and static fields of class objects.</li>
+ <li>Other objects with references to the object.</li>
+ <li>Other objects immediately dominated by the object.</li>
+ </ul>
+ </li>
+ <li>A list of objects, optionally filtered by class, allocation site, and/or
+ heap.</li>
+ <li><a href="site">Information about each allocation site:</a>
+ <ul>
+ <li>The stack trace for the allocation site.</li>
+ <li>The number of bytes allocated at the allocation site.</li>
+ <li>Child sites called from the allocation site.</li>
+ <li>The size and count of objects allocated at the site, organized by
+ heap and object type.</li>
+ </ul>
+ </li>
+</ul>
diff --git a/tools/ahat/src/manifest.txt b/tools/ahat/src/manifest.txt
new file mode 100644
index 0000000..7efb1a7
--- /dev/null
+++ b/tools/ahat/src/manifest.txt
@@ -0,0 +1,4 @@
+Name: ahat/
+Implementation-Title: ahat
+Implementation-Version: 0.2
+Main-Class: com.android.ahat.Main
diff --git a/tools/ahat/src/style.css b/tools/ahat/src/style.css
new file mode 100644
index 0000000..ca074a5
--- /dev/null
+++ b/tools/ahat/src/style.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+div.menu {
+ background-color: #eeffff;
+}
+
+/*
+ * Most of the columns show numbers of bytes. Numbers should be right aligned.
+ */
+table td {
+ background-color: #eeeeee;
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+table th {
+ padding-left: 8px;
+ padding-right: 8px;
+}
diff --git a/tools/ahat/test/QueryTest.java b/tools/ahat/test/QueryTest.java
new file mode 100644
index 0000000..40e3322
--- /dev/null
+++ b/tools/ahat/test/QueryTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class QueryTest {
+ @Test
+ public void simple() throws URISyntaxException {
+ String uri = "http://localhost:7100/object?foo=bar&answer=42";
+ Query query = new Query(new URI(uri));
+ assertEquals("bar", query.get("foo", "not found"));
+ assertEquals("42", query.get("answer", "not found"));
+ assertEquals(42, query.getLong("answer", 0));
+ assertEquals(42, query.getInt("answer", 0));
+ assertEquals("not found", query.get("bar", "not found"));
+ assertEquals("really not found", query.get("bar", "really not found"));
+ assertEquals(0, query.getLong("bar", 0));
+ assertEquals(0, query.getInt("bar", 0));
+ assertEquals(42, query.getLong("bar", 42));
+ assertEquals(42, query.getInt("bar", 42));
+ assertEquals("/object?answer=42&foo=sludge", query.with("foo", "sludge").toString());
+ assertEquals("/object?answer=43&foo=bar", query.with("answer", "43").toString());
+ assertEquals("/object?answer=43&foo=bar", query.with("answer", 43).toString());
+ assertEquals("/object?answer=42&bar=finally&foo=bar", query.with("bar", "finally").toString());
+ }
+
+ @Test
+ public void multiValue() throws URISyntaxException {
+ String uri = "http://localhost:7100/object?foo=bar&answer=42&foo=sludge";
+ Query query = new Query(new URI(uri));
+ assertEquals("sludge", query.get("foo", "not found"));
+ assertEquals(42, query.getLong("answer", 0));
+ assertEquals(42, query.getInt("answer", 0));
+ assertEquals("not found", query.get("bar", "not found"));
+ assertEquals("/object?answer=42&foo=tar", query.with("foo", "tar").toString());
+ assertEquals("/object?answer=43&foo=sludge", query.with("answer", "43").toString());
+ assertEquals("/object?answer=42&bar=finally&foo=sludge",
+ query.with("bar", "finally").toString());
+ }
+
+ @Test
+ public void empty() throws URISyntaxException {
+ String uri = "http://localhost:7100/object";
+ Query query = new Query(new URI(uri));
+ assertEquals("not found", query.get("foo", "not found"));
+ assertEquals(2, query.getLong("foo", 2));
+ assertEquals(2, query.getInt("foo", 2));
+ assertEquals("/object?foo=sludge", query.with("foo", "sludge").toString());
+ assertEquals("/object?answer=43", query.with("answer", "43").toString());
+ }
+}
diff --git a/tools/ahat/test/SortTest.java b/tools/ahat/test/SortTest.java
new file mode 100644
index 0000000..02ff7db
--- /dev/null
+++ b/tools/ahat/test/SortTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class SortTest {
+ @Test
+ public void objectsInfo() {
+ Heap heapA = new Heap(0xA, "A");
+ Heap heapB = new Heap(0xB, "B");
+ ClassObj classA = new ClassObj(0x1A, null, "classA", 0);
+ ClassObj classB = new ClassObj(0x1B, null, "classB", 0);
+ ClassObj classC = new ClassObj(0x1C, null, "classC", 0);
+ Site.ObjectsInfo infoA = new Site.ObjectsInfo(heapA, classA, 4, 14);
+ Site.ObjectsInfo infoB = new Site.ObjectsInfo(heapB, classB, 2, 15);
+ Site.ObjectsInfo infoC = new Site.ObjectsInfo(heapA, classC, 3, 13);
+ Site.ObjectsInfo infoD = new Site.ObjectsInfo(heapB, classA, 5, 12);
+ Site.ObjectsInfo infoE = new Site.ObjectsInfo(heapA, classB, 1, 11);
+ List<Site.ObjectsInfo> list = new ArrayList<Site.ObjectsInfo>();
+ list.add(infoA);
+ list.add(infoB);
+ list.add(infoC);
+ list.add(infoD);
+ list.add(infoE);
+
+ // Sort by size.
+ Collections.sort(list, new Sort.ObjectsInfoBySize());
+ assertEquals(infoB, list.get(0));
+ assertEquals(infoA, list.get(1));
+ assertEquals(infoC, list.get(2));
+ assertEquals(infoD, list.get(3));
+ assertEquals(infoE, list.get(4));
+
+ // Sort by class name.
+ Collections.sort(list, new Sort.ObjectsInfoByClassName());
+ assertEquals(classA, list.get(0).classObj);
+ assertEquals(classA, list.get(1).classObj);
+ assertEquals(classB, list.get(2).classObj);
+ assertEquals(classB, list.get(3).classObj);
+ assertEquals(classC, list.get(4).classObj);
+
+ // Sort by heap name.
+ Collections.sort(list, new Sort.ObjectsInfoByHeapName());
+ assertEquals(heapA, list.get(0).heap);
+ assertEquals(heapA, list.get(1).heap);
+ assertEquals(heapA, list.get(2).heap);
+ assertEquals(heapB, list.get(3).heap);
+ assertEquals(heapB, list.get(4).heap);
+
+ // Sort first by class name, then by size.
+ Collections.sort(list, new Sort.WithPriority<Site.ObjectsInfo>(
+ new Sort.ObjectsInfoByClassName(),
+ new Sort.ObjectsInfoBySize()));
+ assertEquals(infoA, list.get(0));
+ assertEquals(infoD, list.get(1));
+ assertEquals(infoB, list.get(2));
+ assertEquals(infoE, list.get(3));
+ assertEquals(infoC, list.get(4));
+ }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
new file mode 100644
index 0000000..fb53d90
--- /dev/null
+++ b/tools/ahat/test/Tests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 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 com.android.ahat;
+
+import org.junit.runner.JUnitCore;
+
+public class Tests {
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ args = new String[]{
+ "com.android.ahat.QueryTest",
+ "com.android.ahat.SortTest"
+ };
+ }
+ JUnitCore.main(args);
+ }
+}
+
diff --git a/tools/ahat/test/manifest.txt b/tools/ahat/test/manifest.txt
new file mode 100644
index 0000000..af17fad
--- /dev/null
+++ b/tools/ahat/test/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.ahat.Tests
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 7ada189..728991d 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -130,7 +130,28 @@
description: "Crypto failures",
result: EXEC_FAILED,
names: ["libcore.javax.crypto.CipherTest#testCipher_ShortBlock_Failure",
- "libcore.javax.crypto.CipherTest#testCipher_Success"]
+ "libcore.javax.crypto.CipherTest#testCipher_Success",
+ "libcore.javax.crypto.spec.AlgorithmParametersTestDESede#testAlgorithmParameters",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#testDoFinalbyteArrayintintbyteArrayint",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#testUpdatebyteArrayintintbyteArrayint",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_doFinal$BI",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_doFinal$BII$B",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_doFinalLjava_nio_ByteBufferLjava_nio_ByteBuffer",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getAlgorithm",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getBlockSize",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getInstanceLjava_lang_String",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getOutputSizeI",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithAlgorithmParameterSpec",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithKey",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithKeyAlgorithmParameterSpecSecureRandom",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithSecureRandom",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_unwrap$BLjava_lang_StringI",
+ "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_updateLjava_nio_ByteBufferLjava_nio_ByteBuffer",
+ "org.apache.harmony.crypto.tests.javax.crypto.func.CipherAesWrapTest#test_AesWrap",
+ "org.apache.harmony.crypto.tests.javax.crypto.func.CipherDESedeTest#test_DESedeISO",
+ "org.apache.harmony.crypto.tests.javax.crypto.func.CipherDESedeTest#test_DESedeNoISO",
+ "org.apache.harmony.crypto.tests.javax.crypto.func.CipherDESedeWrapTest#test_DESedeWrap",
+ "org.apache.harmony.crypto.tests.javax.crypto.func.CipherPBETest#test_PBEWithMD5AndDES"]
},
{
description: "Flake when running with libartd.so or interpreter",