Merge "ART: Change contention logging lock strategy"
diff --git a/cmdline/cmdline.h b/cmdline/cmdline.h
index 98010d7..18ca944 100644
--- a/cmdline/cmdline.h
+++ b/cmdline/cmdline.h
@@ -295,7 +295,7 @@
template <typename Args = CmdlineArgs>
struct CmdlineMain {
int Main(int argc, char** argv) {
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
std::unique_ptr<Args> args = std::unique_ptr<Args>(CreateArguments());
args_ = args.get();
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index b224ec7..d957869 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -123,7 +123,7 @@
using RuntimeParser = ParsedOptions::RuntimeParser;
static void SetUpTestCase() {
- art::InitLogging(nullptr, art::Runtime::Aborter); // argv = null
+ art::InitLogging(nullptr, art::Runtime::Abort); // argv = null
}
virtual void SetUp() {
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index c0ab21d..92d60b2 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1133,7 +1133,7 @@
original_argc = argc;
original_argv = argv;
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
// Skip over argv[0].
argv++;
diff --git a/dexdump/dexdump_main.cc b/dexdump/dexdump_main.cc
index 74cae3c..606d4f8 100644
--- a/dexdump/dexdump_main.cc
+++ b/dexdump/dexdump_main.cc
@@ -60,7 +60,7 @@
*/
int dexdumpDriver(int argc, char** argv) {
// Art specific set up.
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
MemMap::Init();
// Reset options.
diff --git a/dexlayout/dexdiag.cc b/dexlayout/dexdiag.cc
index 78860e3..c14cd5f 100644
--- a/dexlayout/dexdiag.cc
+++ b/dexlayout/dexdiag.cc
@@ -465,7 +465,7 @@
}
// Art specific set up.
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
MemMap::Init();
#ifdef ART_TARGET_ANDROID
diff --git a/dexlayout/dexlayout_main.cc b/dexlayout/dexlayout_main.cc
index 3c627ea..33d62de 100644
--- a/dexlayout/dexlayout_main.cc
+++ b/dexlayout/dexlayout_main.cc
@@ -67,7 +67,7 @@
*/
int DexlayoutDriver(int argc, char** argv) {
// Art specific set up.
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
MemMap::Init();
Options options;
diff --git a/dexlist/dexlist.cc b/dexlist/dexlist.cc
index 29707af..fa232a7 100644
--- a/dexlist/dexlist.cc
+++ b/dexlist/dexlist.cc
@@ -211,7 +211,7 @@
*/
int dexlistDriver(int argc, char** argv) {
// Art specific set up.
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
MemMap::Init();
// Reset options.
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 9a2eb7f..e2c159a 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -127,7 +127,7 @@
original_argc = argc;
original_argv = argv;
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
// Skip over the command name.
argv++;
argc--;
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index 848eb8d..149960e 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -783,7 +783,7 @@
}
static int patchoat(int argc, char **argv) {
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
MemMap::Init();
const bool debug = kIsDebugBuild;
orig_argc = argc;
diff --git a/profman/profman.cc b/profman/profman.cc
index 14b0262..94e81c7 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -189,7 +189,7 @@
original_argc = argc;
original_argv = argv;
- InitLogging(argv, Runtime::Aborter);
+ InitLogging(argv, Runtime::Abort);
// Skip over the command name.
argv++;
diff --git a/runtime/art_method.h b/runtime/art_method.h
index d537764..396c878 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -451,7 +451,11 @@
}
ProfilingInfo* GetProfilingInfo(PointerSize pointer_size) {
- DCHECK(!IsNative());
+ // Don't do a read barrier in the DCHECK, as GetProfilingInfo is called in places
+ // where the declaring class is treated as a weak reference (accessing it with
+ // a read barrier would either prevent unloading the class, or crash the runtime if
+ // the GC wants to unload it).
+ DCHECK(!IsNative<kWithoutReadBarrier>());
return reinterpret_cast<ProfilingInfo*>(GetDataPtrSize(pointer_size));
}
diff --git a/runtime/base/logging.cc b/runtime/base/logging.cc
index 2be9067..4776357 100644
--- a/runtime/base/logging.cc
+++ b/runtime/base/logging.cc
@@ -85,7 +85,7 @@
LogVerbosity gLogVerbosity;
-unsigned int gAborting = 0;
+std::atomic<unsigned int> gAborting(0);
static std::unique_ptr<std::string> gCmdLine;
static std::unique_ptr<std::string> gProgramInvocationName;
diff --git a/runtime/base/logging.h b/runtime/base/logging.h
index d8954e5..15f9353 100644
--- a/runtime/base/logging.h
+++ b/runtime/base/logging.h
@@ -102,7 +102,7 @@
// 0 if not abort, non-zero if an abort is in progress. Used on fatal exit to prevents recursive
// aborts. Global declaration allows us to disable some error checking to ensure fatal shutdown
// makes forward progress.
-extern unsigned int gAborting;
+extern std::atomic<unsigned int> gAborting;
// Configure logging based on ANDROID_LOG_TAGS environment variable.
// We need to parse a string that looks like
diff --git a/runtime/base/variant_map.h b/runtime/base/variant_map.h
index fdb60c4..d87df87 100644
--- a/runtime/base/variant_map.h
+++ b/runtime/base/variant_map.h
@@ -22,6 +22,7 @@
#include <type_traits>
#include <utility>
+#include "android-base/logging.h"
#include "base/stl_util_identity.h"
namespace art {
@@ -278,7 +279,8 @@
auto* new_value = new TValue(value);
Remove(key);
- storage_map_.insert({{key.Clone(), new_value}});
+ bool inserted = storage_map_.insert({key.Clone(), new_value}).second;
+ DCHECK(inserted); // ensure key.Clone() does not leak memory.
}
// Set a value for a given key, only if there was no previous value before.
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 71558e1..141df1e 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -5440,184 +5440,6 @@
return true;
}
-static void CountMethodsAndFields(ClassDataItemIterator& dex_data,
- size_t* virtual_methods,
- size_t* direct_methods,
- size_t* static_fields,
- size_t* instance_fields) {
- *static_fields = dex_data.NumStaticFields();
- *instance_fields = dex_data.NumInstanceFields();
- *direct_methods = dex_data.NumDirectMethods();
- *virtual_methods = dex_data.NumVirtualMethods();
-}
-
-static void DumpClass(std::ostream& os,
- const DexFile& dex_file, const DexFile::ClassDef& dex_class_def,
- const char* suffix) {
- ClassDataItemIterator dex_data(dex_file, dex_file.GetClassData(dex_class_def));
- os << dex_file.GetClassDescriptor(dex_class_def) << suffix << ":\n";
- os << " Static fields:\n";
- while (dex_data.HasNextStaticField()) {
- const DexFile::FieldId& id = dex_file.GetFieldId(dex_data.GetMemberIndex());
- os << " " << dex_file.GetFieldTypeDescriptor(id) << " " << dex_file.GetFieldName(id) << "\n";
- dex_data.Next();
- }
- os << " Instance fields:\n";
- while (dex_data.HasNextInstanceField()) {
- const DexFile::FieldId& id = dex_file.GetFieldId(dex_data.GetMemberIndex());
- os << " " << dex_file.GetFieldTypeDescriptor(id) << " " << dex_file.GetFieldName(id) << "\n";
- dex_data.Next();
- }
- os << " Direct methods:\n";
- while (dex_data.HasNextDirectMethod()) {
- const DexFile::MethodId& id = dex_file.GetMethodId(dex_data.GetMemberIndex());
- os << " " << dex_file.GetMethodName(id) << dex_file.GetMethodSignature(id).ToString() << "\n";
- dex_data.Next();
- }
- os << " Virtual methods:\n";
- while (dex_data.HasNextVirtualMethod()) {
- const DexFile::MethodId& id = dex_file.GetMethodId(dex_data.GetMemberIndex());
- os << " " << dex_file.GetMethodName(id) << dex_file.GetMethodSignature(id).ToString() << "\n";
- dex_data.Next();
- }
-}
-
-static std::string DumpClasses(const DexFile& dex_file1,
- const DexFile::ClassDef& dex_class_def1,
- const DexFile& dex_file2,
- const DexFile::ClassDef& dex_class_def2) {
- std::ostringstream os;
- DumpClass(os, dex_file1, dex_class_def1, " (Compile time)");
- DumpClass(os, dex_file2, dex_class_def2, " (Runtime)");
- return os.str();
-}
-
-
-// Very simple structural check on whether the classes match. Only compares the number of
-// methods and fields.
-static bool SimpleStructuralCheck(const DexFile& dex_file1,
- const DexFile::ClassDef& dex_class_def1,
- const DexFile& dex_file2,
- const DexFile::ClassDef& dex_class_def2,
- std::string* error_msg) {
- ClassDataItemIterator dex_data1(dex_file1, dex_file1.GetClassData(dex_class_def1));
- ClassDataItemIterator dex_data2(dex_file2, dex_file2.GetClassData(dex_class_def2));
-
- // Counters for current dex file.
- size_t dex_virtual_methods1, dex_direct_methods1, dex_static_fields1, dex_instance_fields1;
- CountMethodsAndFields(dex_data1,
- &dex_virtual_methods1,
- &dex_direct_methods1,
- &dex_static_fields1,
- &dex_instance_fields1);
- // Counters for compile-time dex file.
- size_t dex_virtual_methods2, dex_direct_methods2, dex_static_fields2, dex_instance_fields2;
- CountMethodsAndFields(dex_data2,
- &dex_virtual_methods2,
- &dex_direct_methods2,
- &dex_static_fields2,
- &dex_instance_fields2);
-
- if (dex_virtual_methods1 != dex_virtual_methods2) {
- std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
- *error_msg = StringPrintf("Virtual method count off: %zu vs %zu\n%s",
- dex_virtual_methods1,
- dex_virtual_methods2,
- class_dump.c_str());
- return false;
- }
- if (dex_direct_methods1 != dex_direct_methods2) {
- std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
- *error_msg = StringPrintf("Direct method count off: %zu vs %zu\n%s",
- dex_direct_methods1,
- dex_direct_methods2,
- class_dump.c_str());
- return false;
- }
- if (dex_static_fields1 != dex_static_fields2) {
- std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
- *error_msg = StringPrintf("Static field count off: %zu vs %zu\n%s",
- dex_static_fields1,
- dex_static_fields2,
- class_dump.c_str());
- return false;
- }
- if (dex_instance_fields1 != dex_instance_fields2) {
- std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
- *error_msg = StringPrintf("Instance field count off: %zu vs %zu\n%s",
- dex_instance_fields1,
- dex_instance_fields2,
- class_dump.c_str());
- return false;
- }
-
- return true;
-}
-
-// Checks whether a the super-class changed from what we had at compile-time. This would
-// invalidate quickening.
-static bool CheckSuperClassChange(Handle<mirror::Class> klass,
- const DexFile& dex_file,
- const DexFile::ClassDef& class_def,
- ObjPtr<mirror::Class> super_class)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Check for unexpected changes in the superclass.
- // Quick check 1) is the super_class class-loader the boot class loader? This always has
- // precedence.
- if (super_class->GetClassLoader() != nullptr &&
- // Quick check 2) different dex cache? Breaks can only occur for different dex files,
- // which is implied by different dex cache.
- klass->GetDexCache() != super_class->GetDexCache()) {
- // Now comes the expensive part: things can be broken if (a) the klass' dex file has a
- // definition for the super-class, and (b) the files are in separate oat files. The oat files
- // are referenced from the dex file, so do (b) first. Only relevant if we have oat files.
- const OatDexFile* class_oat_dex_file = dex_file.GetOatDexFile();
- const OatFile* class_oat_file = nullptr;
- if (class_oat_dex_file != nullptr) {
- class_oat_file = class_oat_dex_file->GetOatFile();
- }
-
- if (class_oat_file != nullptr) {
- const OatDexFile* loaded_super_oat_dex_file = super_class->GetDexFile().GetOatDexFile();
- const OatFile* loaded_super_oat_file = nullptr;
- if (loaded_super_oat_dex_file != nullptr) {
- loaded_super_oat_file = loaded_super_oat_dex_file->GetOatFile();
- }
-
- if (loaded_super_oat_file != nullptr && class_oat_file != loaded_super_oat_file) {
- // Now check (a).
- const DexFile::ClassDef* super_class_def = dex_file.FindClassDef(class_def.superclass_idx_);
- if (super_class_def != nullptr) {
- // Uh-oh, we found something. Do our check.
- std::string error_msg;
- if (!SimpleStructuralCheck(dex_file, *super_class_def,
- super_class->GetDexFile(), *super_class->GetClassDef(),
- &error_msg)) {
- // Print a warning to the log. This exception might be caught, e.g., as common in test
- // drivers. When the class is later tried to be used, we re-throw a new instance, as we
- // only save the type of the exception.
- LOG(WARNING) << "Incompatible structural change detected: " <<
- StringPrintf(
- "Structural change of %s is hazardous (%s at compile time, %s at runtime): %s",
- dex_file.PrettyType(super_class_def->class_idx_).c_str(),
- class_oat_file->GetLocation().c_str(),
- loaded_super_oat_file->GetLocation().c_str(),
- error_msg.c_str());
- ThrowIncompatibleClassChangeError(klass.Get(),
- "Structural change of %s is hazardous (%s at compile time, %s at runtime): %s",
- dex_file.PrettyType(super_class_def->class_idx_).c_str(),
- class_oat_file->GetLocation().c_str(),
- loaded_super_oat_file->GetLocation().c_str(),
- error_msg.c_str());
- return false;
- }
- }
- }
- }
- }
- return true;
-}
-
bool ClassLinker::LoadSuperAndInterfaces(Handle<mirror::Class> klass, const DexFile& dex_file) {
CHECK_EQ(mirror::Class::kStatusIdx, klass->GetStatus());
const DexFile::ClassDef& class_def = dex_file.GetClassDef(klass->GetDexClassDefIndex());
@@ -5650,11 +5472,6 @@
}
CHECK(super_class->IsResolved());
klass->SetSuperClass(super_class);
-
- if (!CheckSuperClassChange(klass, dex_file, class_def, super_class)) {
- DCHECK(Thread::Current()->IsExceptionPending());
- return false;
- }
}
const DexFile::TypeList* interfaces = dex_file.GetInterfacesList(class_def);
if (interfaces != nullptr) {
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 5beac1a..6441a44 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -59,7 +59,7 @@
// everything else. In case you want to see all messages, comment out the line.
setenv("ANDROID_LOG_TAGS", "*:e", 1);
- art::InitLogging(argv, art::Runtime::Aborter);
+ art::InitLogging(argv, art::Runtime::Abort);
LOG(INFO) << "Running main() from common_runtime_test.cc...";
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc
index a17226c..edb6ffe 100644
--- a/runtime/openjdkjvmti/ti_stack.cc
+++ b/runtime/openjdkjvmti/ti_stack.cc
@@ -39,6 +39,7 @@
#include "art_field-inl.h"
#include "art_method-inl.h"
#include "art_jvmti.h"
+#include "barrier.h"
#include "base/bit_utils.h"
#include "base/enums.h"
#include "base/mutex.h"
@@ -299,13 +300,20 @@
template <typename Data>
struct GetAllStackTracesVectorClosure : public art::Closure {
- GetAllStackTracesVectorClosure(size_t stop, Data* data_) : stop_input(stop), data(data_) {}
+ GetAllStackTracesVectorClosure(size_t stop, Data* data_)
+ : barrier(0), stop_input(stop), data(data_) {}
void Run(art::Thread* thread) OVERRIDE
REQUIRES_SHARED(art::Locks::mutator_lock_)
REQUIRES(!data->mutex) {
art::Thread* self = art::Thread::Current();
+ Work(thread, self);
+ barrier.Pass(self);
+ }
+ void Work(art::Thread* thread, art::Thread* self)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!data->mutex) {
// Skip threads that are still starting.
if (thread->IsStillStarting()) {
return;
@@ -324,10 +332,23 @@
visitor.WalkStack(/* include_transitions */ false);
}
+ art::Barrier barrier;
const size_t stop_input;
Data* data;
};
+template <typename Data>
+static void RunCheckpointAndWait(Data* data, size_t max_frame_count) {
+ GetAllStackTracesVectorClosure<Data> closure(max_frame_count, data);
+ size_t barrier_count = art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr);
+ if (barrier_count == 0) {
+ return;
+ }
+ art::Thread* self = art::Thread::Current();
+ art::ScopedThreadStateChange tsc(self, art::ThreadState::kWaitingForCheckPointsToRun);
+ closure.barrier.Increment(self, barrier_count);
+}
+
jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env,
jint max_frame_count,
jvmtiStackInfo** stack_info_ptr,
@@ -375,9 +396,7 @@
};
AllStackTracesData data;
- GetAllStackTracesVectorClosure<AllStackTracesData> closure(
- static_cast<size_t>(max_frame_count), &data);
- art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr);
+ RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
art::Thread* current = art::Thread::Current();
@@ -538,9 +557,7 @@
data.handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i])));
}
- GetAllStackTracesVectorClosure<SelectStackTracesData> closure(
- static_cast<size_t>(max_frame_count), &data);
- art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr);
+ RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
// Convert the data into our output format.
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index c11e4bd..46b4392 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -483,7 +483,16 @@
};
void Runtime::Abort(const char* msg) {
- gAborting++; // set before taking any locks
+ auto old_value = gAborting.fetch_add(1); // set before taking any locks
+
+#ifdef ART_TARGET_ANDROID
+ if (old_value == 0) {
+ // Only set the first abort message.
+ android_set_abort_message(msg);
+ }
+#else
+ UNUSED(old_value);
+#endif
// Ensure that we don't have multiple threads trying to abort at once,
// which would result in significantly worse diagnostics.
@@ -570,7 +579,7 @@
bool Runtime::ParseOptions(const RuntimeOptions& raw_options,
bool ignore_unrecognized,
RuntimeArgumentMap* runtime_options) {
- InitLogging(/* argv */ nullptr, Aborter); // Calls Locks::Init() as a side effect.
+ InitLogging(/* argv */ nullptr, Abort); // Calls Locks::Init() as a side effect.
bool parsed = ParsedOptions::Parse(raw_options, ignore_unrecognized, runtime_options);
if (!parsed) {
LOG(ERROR) << "Failed to parse options";
@@ -2358,14 +2367,6 @@
}
}
-NO_RETURN
-void Runtime::Aborter(const char* abort_message) {
-#ifdef ART_TARGET_ANDROID
- android_set_abort_message(abort_message);
-#endif
- Runtime::Abort(abort_message);
-}
-
RuntimeCallbacks* Runtime::GetRuntimeCallbacks() {
return callbacks_.get();
}
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 2505d87..af9d215 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -649,9 +649,6 @@
return cha_;
}
- NO_RETURN
- static void Aborter(const char* abort_message);
-
void AttachAgent(const std::string& agent_arg);
const std::list<ti::Agent>& GetAgents() const {
diff --git a/test/131-structural-change/build b/test/131-structural-change/build
deleted file mode 100755
index ff0da20..0000000
--- a/test/131-structural-change/build
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/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.
-
-# Stop if something fails.
-set -e
-
-if [ ${USE_JACK} = "true" ]; then
- ${JACK} --output-dex . src
- zip $TEST_NAME.jar classes.dex
-
- ${JACK} --output-dex . src-ex
- zip ${TEST_NAME}-ex.jar classes.dex
-else
- mkdir classes
- ${JAVAC} -d classes `find src -name '*.java'`
-
- mkdir classes-ex
- ${JAVAC} -d classes-ex `find src-ex -name '*.java'`
-
- if [ ${NEED_DEX} = "true" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex --dump-width=1000 classes
- zip $TEST_NAME.jar classes.dex
- ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes.dex --dump-width=1000 classes-ex
- zip ${TEST_NAME}-ex.jar classes.dex
- fi
-fi
diff --git a/test/131-structural-change/expected.txt b/test/131-structural-change/expected.txt
deleted file mode 100644
index 1d19278..0000000
--- a/test/131-structural-change/expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-JNI_OnLoad called
-Should really reach here.
-Done.
diff --git a/test/131-structural-change/info.txt b/test/131-structural-change/info.txt
deleted file mode 100644
index 6d5817b..0000000
--- a/test/131-structural-change/info.txt
+++ /dev/null
@@ -1 +0,0 @@
-Check whether a structural change in a (non-native) multi-dex scenario is detected.
diff --git a/test/131-structural-change/run b/test/131-structural-change/run
deleted file mode 100755
index 63fdb8c..0000000
--- a/test/131-structural-change/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/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.
-
-# Use secondary switch to add secondary dex file to class path.
-exec ${RUN} "${@}" --secondary
diff --git a/test/131-structural-change/src-ex/A.java b/test/131-structural-change/src-ex/A.java
deleted file mode 100644
index 800347b..0000000
--- a/test/131-structural-change/src-ex/A.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.
- */
-
-public class A {
- public void bar() {
- }
-}
diff --git a/test/131-structural-change/src-ex/B.java b/test/131-structural-change/src-ex/B.java
deleted file mode 100644
index 61369db..0000000
--- a/test/131-structural-change/src-ex/B.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- */
-
-public class B extends A {
- public void test() {
- System.out.println("Should not reach this!");
- }
-}
diff --git a/test/131-structural-change/src/A.java b/test/131-structural-change/src/A.java
deleted file mode 100644
index b07de58..0000000
--- a/test/131-structural-change/src/A.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.
- */
-
-public class A {
- public void foo() {
- }
-
- public void bar() {
- }
-
- public void baz() {
- }
-}
diff --git a/test/131-structural-change/src/Main.java b/test/131-structural-change/src/Main.java
deleted file mode 100644
index c748899..0000000
--- a/test/131-structural-change/src/Main.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-
-/**
- * Structural hazard test.
- */
-public class Main {
- public static void main(String[] args) {
- System.loadLibrary(args[0]);
- new Main().run();
- }
-
- private void run() {
- try {
- Class<?> bClass = getClass().getClassLoader().loadClass("A");
- System.out.println("Should really reach here.");
- } catch (Exception e) {
- e.printStackTrace(System.out);
- }
-
- boolean haveOatFile = hasOatFile();
- boolean gotError = false;
- try {
- Class<?> bClass = getClass().getClassLoader().loadClass("B");
- } catch (IncompatibleClassChangeError icce) {
- gotError = true;
- } catch (Exception e) {
- e.printStackTrace(System.out);
- }
- if (haveOatFile ^ gotError) {
- System.out.println("Did not get expected error. " + haveOatFile + " " + gotError);
- }
- System.out.println("Done.");
- }
-
- private native static boolean hasOatFile();
-}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 8bdaeaa..7bfa417 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -143,15 +143,6 @@
"complete test) JDWP must be set up."]
},
{
- "tests": "131-structural-change",
- "variant": "debug",
- "description": ["131 is an old test. The functionality has been",
- "implemented at an earlier stage and is checked",
- "in tests 138. Blacklisted for debug builds since",
- "these builds have duplicate classes checks which",
- "punt to interpreter"]
- },
- {
"tests": "138-duplicate-classes-check",
"variant": "ndebug",
"description": ["Turned on for debug builds since debug builds have",
diff --git a/test/run-test b/test/run-test
index 044f63f..e6c2480 100755
--- a/test/run-test
+++ b/test/run-test
@@ -92,7 +92,7 @@
export JACK="$JACK -g -cp $JACK_CLASSPATH"
# Allow changing DESUGAR script to something else, or to disable it with DESUGAR=false.
-if [ -z "$DESUGAR"]; then
+if [ -z "$DESUGAR" ]; then
export DESUGAR="$ANDROID_BUILD_TOP/art/tools/desugar.sh"
fi
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index d6f1faa..cc55b7a 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -21,13 +21,15 @@
import com.android.ahat.heapdump.AhatClassObj;
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
-import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.DiffFields;
+import com.android.ahat.heapdump.DiffedFieldValue;
import com.android.ahat.heapdump.FieldValue;
import com.android.ahat.heapdump.PathElement;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
import java.io.IOException;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -108,13 +110,9 @@
private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
doc.section("Fields");
AhatInstance base = inst.getBaseline();
- List<FieldValue> fields = inst.getInstanceFields();
- if (!base.isPlaceHolder()) {
- Diff.fields(fields, base.asClassInstance().getInstanceFields());
- }
- SubsetSelector<FieldValue> selector = new SubsetSelector(query, INSTANCE_FIELDS_ID, fields);
- printFields(doc, inst != base && !base.isPlaceHolder(), selector.selected());
- selector.render(doc);
+ printFields(doc, query, INSTANCE_FIELDS_ID, !base.isPlaceHolder(),
+ inst.asClassInstance().getInstanceFields(),
+ base.isPlaceHolder() ? null : base.asClassInstance().getInstanceFields());
}
private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
@@ -145,36 +143,61 @@
selector.render(doc);
}
- private static void printFields(Doc doc, boolean diff, List<FieldValue> fields) {
+ /**
+ * Helper function for printing static or instance fields.
+ * @param id - id to use for the field subset selector.
+ * @param diff - whether a diff should be shown for the fields.
+ * @param current - the fields to show.
+ * @param baseline - the baseline fields to diff against if diff is true,
+ * ignored otherwise.
+ */
+ private static void printFields(Doc doc, Query query, String id, boolean diff,
+ List<FieldValue> current, List<FieldValue> baseline) {
+
+ if (!diff) {
+ // To avoid having to special case when diff is disabled, always diff
+ // the fields, but diff against an empty list.
+ baseline = Collections.emptyList();
+ }
+
+ List<DiffedFieldValue> fields = DiffFields.diff(current, baseline);
+ SubsetSelector<DiffedFieldValue> selector = new SubsetSelector(query, id, fields);
+
doc.table(
new Column("Type"),
new Column("Name"),
new Column("Value"),
new Column("Δ", Column.Align.LEFT, diff));
- for (FieldValue field : fields) {
- Value current = field.getValue();
- DocString value;
- if (field.isPlaceHolder()) {
- value = DocString.removed("del");
- } else {
- value = Summarizer.summarize(current);
- }
+ for (DiffedFieldValue field : selector.selected()) {
+ Value previous = Value.getBaseline(field.baseline);
+ DocString was = DocString.text("was ");
+ was.append(Summarizer.summarize(previous));
+ switch (field.status) {
+ case ADDED:
+ doc.row(DocString.text(field.type),
+ DocString.text(field.name),
+ Summarizer.summarize(field.current),
+ DocString.added("new"));
+ break;
- DocString delta = new DocString();
- FieldValue basefield = field.getBaseline();
- if (basefield.isPlaceHolder()) {
- delta.append(DocString.added("new"));
- } else {
- Value previous = Value.getBaseline(basefield.getValue());
- if (!Objects.equals(current, previous)) {
- delta.append("was ");
- delta.append(Summarizer.summarize(previous));
- }
+ case MATCHED:
+ doc.row(DocString.text(field.type),
+ DocString.text(field.name),
+ Summarizer.summarize(field.current),
+ Objects.equals(field.current, previous) ? new DocString() : was);
+ break;
+
+ case DELETED:
+ doc.row(DocString.text(field.type),
+ DocString.text(field.name),
+ DocString.removed("del"),
+ was);
+ break;
}
- doc.row(DocString.text(field.getType()), DocString.text(field.getName()), value, delta);
}
doc.end();
+ selector.render(doc);
}
private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
@@ -188,13 +211,9 @@
doc.section("Static Fields");
AhatInstance base = clsobj.getBaseline();
- List<FieldValue> fields = clsobj.getStaticFieldValues();
- if (!base.isPlaceHolder()) {
- Diff.fields(fields, base.asClassObj().getStaticFieldValues());
- }
- SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
- printFields(doc, clsobj != base && !base.isPlaceHolder(), selector.selected());
- selector.render(doc);
+ printFields(doc, query, STATIC_FIELDS_ID, !base.isPlaceHolder(),
+ clsobj.getStaticFieldValues(),
+ base.isPlaceHolder() ? null : base.asClassObj().getStaticFieldValues());
}
private static void printReferences(Doc doc, Query query, AhatInstance inst) {
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index c10d604..158de52 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -54,8 +54,8 @@
@Override public Value getField(String fieldName) {
for (FieldValue field : mFieldValues) {
- if (fieldName.equals(field.getName())) {
- return field.getValue();
+ if (fieldName.equals(field.name)) {
+ return field.value;
}
}
return null;
diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java
index 943e6e6..489f709 100644
--- a/tools/ahat/src/heapdump/Diff.java
+++ b/tools/ahat/src/heapdump/Diff.java
@@ -336,48 +336,4 @@
placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder);
}
}
-
- /**
- * Diff two lists of field values.
- * PlaceHolder objects are added to the given lists as needed to ensure
- * every FieldValue in A ends up with a corresponding FieldValue in B.
- */
- public static void fields(List<FieldValue> a, List<FieldValue> b) {
- // Fields with the same name and type are considered matching fields.
- // For simplicity, we assume the matching fields are in the same order in
- // both A and B, though some fields may be added or removed in either
- // list. If our assumption is wrong, in the worst case the quality of the
- // field diff is poor.
-
- for (int i = 0; i < a.size(); i++) {
- FieldValue afield = a.get(i);
- afield.setBaseline(null);
-
- // Find the matching field in B, if any.
- for (int j = i; j < b.size(); j++) {
- FieldValue bfield = b.get(j);
- if (afield.getName().equals(bfield.getName())
- && afield.getType().equals(bfield.getType())) {
- // We found the matching field in B.
- // Assume fields i, ..., j-1 in B have no match in A.
- for ( ; i < j; i++) {
- a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
- }
-
- afield.setBaseline(bfield);
- bfield.setBaseline(afield);
- break;
- }
- }
-
- if (afield.getBaseline() == null) {
- b.add(i, FieldValue.newPlaceHolderFieldValue(afield));
- }
- }
-
- // All remaining fields in B are unmatched by any in A.
- for (int i = a.size(); i < b.size(); i++) {
- a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
- }
- }
}
diff --git a/tools/ahat/src/heapdump/DiffFields.java b/tools/ahat/src/heapdump/DiffFields.java
new file mode 100644
index 0000000..dd73456
--- /dev/null
+++ b/tools/ahat/src/heapdump/DiffFields.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This class contains a routine for diffing two collections of static or
+ * instance fields.
+ */
+public class DiffFields {
+ /**
+ * Return the result of diffing two collections of field values.
+ * The input collections 'current' and 'baseline' are not modified by this function.
+ */
+ public static List<DiffedFieldValue> diff(Collection<FieldValue> current,
+ Collection<FieldValue> baseline) {
+ List<FieldValue> currentSorted = new ArrayList<FieldValue>(current);
+ Collections.sort(currentSorted, FOR_DIFF);
+
+ List<FieldValue> baselineSorted = new ArrayList<FieldValue>(baseline);
+ Collections.sort(baselineSorted, FOR_DIFF);
+
+ // Merge the two lists to form the diffed list of fields.
+ List<DiffedFieldValue> diffed = new ArrayList<DiffedFieldValue>();
+ int currentPos = 0;
+ int baselinePos = 0;
+
+ while (currentPos < currentSorted.size() && baselinePos < baselineSorted.size()) {
+ FieldValue currentField = currentSorted.get(currentPos);
+ FieldValue baselineField = baselineSorted.get(baselinePos);
+ int compare = FOR_DIFF.compare(currentField, baselineField);
+ if (compare < 0) {
+ diffed.add(DiffedFieldValue.added(currentField));
+ currentPos++;
+ } else if (compare == 0) {
+ diffed.add(DiffedFieldValue.matched(currentField, baselineField));
+ currentPos++;
+ baselinePos++;
+ } else {
+ diffed.add(DiffedFieldValue.deleted(baselineField));
+ baselinePos++;
+ }
+ }
+
+ while (currentPos < currentSorted.size()) {
+ FieldValue currentField = currentSorted.get(currentPos);
+ diffed.add(DiffedFieldValue.added(currentField));
+ currentPos++;
+ }
+
+ while (baselinePos < baselineSorted.size()) {
+ FieldValue baselineField = baselineSorted.get(baselinePos);
+ diffed.add(DiffedFieldValue.deleted(baselineField));
+ baselinePos++;
+ }
+ return diffed;
+ }
+
+ /**
+ * Comparator used for sorting fields for the purposes of diff.
+ * Fields with the same name and type are considered comparable, so we sort
+ * by field name and type.
+ */
+ private static final Comparator<FieldValue> FOR_DIFF
+ = new Sort.WithPriority(Sort.FIELD_VALUE_BY_NAME, Sort.FIELD_VALUE_BY_TYPE);
+}
diff --git a/tools/ahat/src/heapdump/DiffedFieldValue.java b/tools/ahat/src/heapdump/DiffedFieldValue.java
new file mode 100644
index 0000000..e2dcf3e
--- /dev/null
+++ b/tools/ahat/src/heapdump/DiffedFieldValue.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.Objects;
+
+/** DiffedFieldValue is used by the DiffedField class to return the result of
+ * diffing two collections of fields.
+ */
+public class DiffedFieldValue {
+ public final String name;
+ public final String type;
+ public final Value current;
+ public final Value baseline;
+
+ public final Status status;
+
+ public static enum Status {
+ ADDED, // The current field has no matching baseline value.
+ MATCHED, // The current field has a matching baseline value.
+ DELETED // The baseline field has no matching current value.
+ };
+
+ /**
+ * Return a DiffedFieldValue where there is both a current and baseline.
+ */
+ public static DiffedFieldValue matched(FieldValue current, FieldValue baseline) {
+ return new DiffedFieldValue(current.name,
+ current.type,
+ current.value,
+ baseline.value,
+ Status.MATCHED);
+ }
+
+ /**
+ * Return a DiffedFieldValue where there is no baseline.
+ */
+ public static DiffedFieldValue added(FieldValue current) {
+ return new DiffedFieldValue(current.name, current.type, current.value, null, Status.ADDED);
+ }
+
+ /**
+ * Return a DiffedFieldValue where there is no current.
+ */
+ public static DiffedFieldValue deleted(FieldValue baseline) {
+ return new DiffedFieldValue(baseline.name, baseline.type, null, baseline.value, Status.DELETED);
+ }
+
+ private DiffedFieldValue(String name, String type, Value current, Value baseline, Status status) {
+ this.name = name;
+ this.type = type;
+ this.current = current;
+ this.baseline = baseline;
+ this.status = status;
+ }
+
+ @Override
+ public boolean equals(Object otherObject) {
+ if (otherObject instanceof DiffedFieldValue) {
+ DiffedFieldValue other = (DiffedFieldValue)otherObject;
+ return name.equals(other.name)
+ && type.equals(other.type)
+ && Objects.equals(current, other.current)
+ && Objects.equals(baseline, other.baseline)
+ && Objects.equals(status, other.status);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ switch (status) {
+ case ADDED:
+ return "(" + name + " " + type + " +" + current + ")";
+
+ case MATCHED:
+ return "(" + name + " " + type + " " + current + " " + baseline + ")";
+
+ case DELETED:
+ return "(" + name + " " + type + " -" + baseline + ")";
+
+ default:
+ // There are no other members.
+ throw new AssertionError("unsupported enum member");
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
index 3f65cd3..6d72595 100644
--- a/tools/ahat/src/heapdump/FieldValue.java
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -16,68 +16,14 @@
package com.android.ahat.heapdump;
-public class FieldValue implements Diffable<FieldValue> {
- private final String mName;
- private final String mType;
- private final Value mValue;
- private FieldValue mBaseline;
- private final boolean mIsPlaceHolder;
+public class FieldValue {
+ public final String name;
+ public final String type;
+ public final Value value;
public FieldValue(String name, String type, Value value) {
- mName = name;
- mType = type;
- mValue = value;
- mBaseline = this;
- mIsPlaceHolder = false;
- }
-
- /**
- * Construct a place holder FieldValue
- */
- private FieldValue(FieldValue baseline) {
- mName = baseline.mName;
- mType = baseline.mType;
- mValue = Value.getBaseline(baseline.mValue);
- mBaseline = baseline;
- mIsPlaceHolder = true;
- }
-
- static FieldValue newPlaceHolderFieldValue(FieldValue baseline) {
- FieldValue field = new FieldValue(baseline);
- baseline.setBaseline(field);
- return field;
- }
-
- /**
- * Returns the name of the field.
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns a description of the type of the field.
- */
- public String getType() {
- return mType;
- }
-
- /**
- * Returns the value of this field.
- */
- public Value getValue() {
- return mValue;
- }
-
- public void setBaseline(FieldValue baseline) {
- mBaseline = baseline;
- }
-
- @Override public FieldValue getBaseline() {
- return mBaseline;
- }
-
- @Override public boolean isPlaceHolder() {
- return mIsPlaceHolder;
+ this.name = name;
+ this.type = type;
+ this.value = value;
}
}
diff --git a/tools/ahat/src/heapdump/Sort.java b/tools/ahat/src/heapdump/Sort.java
index 0745803..efe0d6b 100644
--- a/tools/ahat/src/heapdump/Sort.java
+++ b/tools/ahat/src/heapdump/Sort.java
@@ -200,5 +200,27 @@
return aName.compareTo(bName);
}
};
+
+ /**
+ * Compare FieldValue by field name.
+ */
+ public static final Comparator<FieldValue> FIELD_VALUE_BY_NAME
+ = new Comparator<FieldValue>() {
+ @Override
+ public int compare(FieldValue a, FieldValue b) {
+ return a.name.compareTo(b.name);
+ }
+ };
+
+ /**
+ * Compare FieldValue by type name.
+ */
+ public static final Comparator<FieldValue> FIELD_VALUE_BY_TYPE
+ = new Comparator<FieldValue>() {
+ @Override
+ public int compare(FieldValue a, FieldValue b) {
+ return a.type.compareTo(b.type);
+ }
+ };
}
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
index 6b2d38f..c1f3022 100644
--- a/tools/ahat/src/heapdump/Value.java
+++ b/tools/ahat/src/heapdump/Value.java
@@ -28,7 +28,7 @@
* The Object must either be a boxed Java primitive type or a subclass of
* AhatInstance. The object must not be null.
*/
- Value(Object object) {
+ public Value(Object object) {
// TODO: Check that the Object is either an AhatSnapshot or boxed Java
// primitive type?
assert object != null;
diff --git a/tools/ahat/test/DiffFieldsTest.java b/tools/ahat/test/DiffFieldsTest.java
new file mode 100644
index 0000000..6abdd47
--- /dev/null
+++ b/tools/ahat/test/DiffFieldsTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 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.ahat.heapdump.DiffFields;
+import com.android.ahat.heapdump.DiffedFieldValue;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Value;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class DiffFieldsTest {
+ @Test
+ public void normalMatchedDiffedFieldValues() {
+ FieldValue normal1 = new FieldValue("name", "type", new Value(1));
+ FieldValue normal2 = new FieldValue("name", "type", new Value(2));
+
+ DiffedFieldValue x = DiffedFieldValue.matched(normal1, normal2);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.current);
+ assertEquals(new Value(2), x.baseline);
+ assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
+ }
+
+ @Test
+ public void nulledMatchedDiffedFieldValues() {
+ FieldValue normal = new FieldValue("name", "type", new Value(1));
+ FieldValue nulled = new FieldValue("name", "type", null);
+
+ DiffedFieldValue x = DiffedFieldValue.matched(normal, nulled);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.current);
+ assertNull(x.baseline);
+ assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
+
+ DiffedFieldValue y = DiffedFieldValue.matched(nulled, normal);
+ assertEquals("name", y.name);
+ assertEquals("type", y.type);
+ assertNull(y.current);
+ assertEquals(new Value(1), y.baseline);
+ assertEquals(DiffedFieldValue.Status.MATCHED, y.status);
+ }
+
+ @Test
+ public void normalAddedDiffedFieldValues() {
+ FieldValue normal = new FieldValue("name", "type", new Value(1));
+
+ DiffedFieldValue x = DiffedFieldValue.added(normal);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.current);
+ assertEquals(DiffedFieldValue.Status.ADDED, x.status);
+ }
+
+ @Test
+ public void nulledAddedDiffedFieldValues() {
+ FieldValue nulled = new FieldValue("name", "type", null);
+
+ DiffedFieldValue x = DiffedFieldValue.added(nulled);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertNull(x.current);
+ assertEquals(DiffedFieldValue.Status.ADDED, x.status);
+ }
+
+ @Test
+ public void normalDeletedDiffedFieldValues() {
+ FieldValue normal = new FieldValue("name", "type", new Value(1));
+
+ DiffedFieldValue x = DiffedFieldValue.deleted(normal);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.baseline);
+ assertEquals(DiffedFieldValue.Status.DELETED, x.status);
+ }
+
+ @Test
+ public void nulledDeletedDiffedFieldValues() {
+ FieldValue nulled = new FieldValue("name", "type", null);
+
+ DiffedFieldValue x = DiffedFieldValue.deleted(nulled);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertNull(x.baseline);
+ assertEquals(DiffedFieldValue.Status.DELETED, x.status);
+ }
+
+ @Test
+ public void basicDiff() {
+ List<FieldValue> a = new ArrayList<FieldValue>();
+ a.add(new FieldValue("n0", "t0", null));
+ a.add(new FieldValue("n2", "t2", null));
+ a.add(new FieldValue("n3", "t3", null));
+ a.add(new FieldValue("n4", "t4", null));
+ a.add(new FieldValue("n5", "t5", null));
+ a.add(new FieldValue("n6", "t6", null));
+
+ List<FieldValue> b = new ArrayList<FieldValue>();
+ b.add(new FieldValue("n0", "t0", null));
+ b.add(new FieldValue("n1", "t1", null));
+ b.add(new FieldValue("n2", "t2", null));
+ b.add(new FieldValue("n3", "t3", null));
+ b.add(new FieldValue("n5", "t5", null));
+ b.add(new FieldValue("n6", "t6", null));
+ b.add(new FieldValue("n7", "t7", null));
+
+ // Note: The expected result makes assumptions about the implementation of
+ // field diff to match the order of the returned fields. If the
+ // implementation changes, this test may need to be generalized to accept
+ // the new implementation.
+ List<DiffedFieldValue> expected = new ArrayList<DiffedFieldValue>();
+ expected.add(DiffedFieldValue.matched(a.get(0), b.get(0)));
+ expected.add(DiffedFieldValue.deleted(b.get(1)));
+ expected.add(DiffedFieldValue.matched(a.get(1), b.get(2)));
+ expected.add(DiffedFieldValue.matched(a.get(2), b.get(3)));
+ expected.add(DiffedFieldValue.added(a.get(3)));
+ expected.add(DiffedFieldValue.matched(a.get(4), b.get(4)));
+ expected.add(DiffedFieldValue.matched(a.get(5), b.get(5)));
+ expected.add(DiffedFieldValue.deleted(b.get(6)));
+
+ List<DiffedFieldValue> diffed = DiffFields.diff(a, b);
+ assertEquals(expected, diffed);
+ }
+
+ @Test
+ public void reorderedDiff() {
+ List<FieldValue> a = new ArrayList<FieldValue>();
+ a.add(new FieldValue("n0", "t0", null));
+ a.add(new FieldValue("n1", "t1", null));
+ a.add(new FieldValue("n2", "t2", null));
+ a.add(new FieldValue("n3", "t3", null));
+ a.add(new FieldValue("n4", "t4", null));
+ a.add(new FieldValue("n5", "t5", null));
+ a.add(new FieldValue("n6", "t6", null));
+
+ List<FieldValue> b = new ArrayList<FieldValue>();
+ b.add(new FieldValue("n4", "t4", null));
+ b.add(new FieldValue("n1", "t1", null));
+ b.add(new FieldValue("n3", "t3", null));
+ b.add(new FieldValue("n0", "t0", null));
+ b.add(new FieldValue("n5", "t5", null));
+ b.add(new FieldValue("n2", "t2", null));
+ b.add(new FieldValue("n6", "t6", null));
+
+ // Note: The expected result makes assumptions about the implementation of
+ // field diff to match the order of the returned fields. If the
+ // implementation changes, this test may need to be generalized to accept
+ // the new implementation.
+ List<DiffedFieldValue> expected = new ArrayList<DiffedFieldValue>();
+ expected.add(DiffedFieldValue.matched(a.get(0), b.get(3)));
+ expected.add(DiffedFieldValue.matched(a.get(1), b.get(1)));
+ expected.add(DiffedFieldValue.matched(a.get(2), b.get(5)));
+ expected.add(DiffedFieldValue.matched(a.get(3), b.get(2)));
+ expected.add(DiffedFieldValue.matched(a.get(4), b.get(0)));
+ expected.add(DiffedFieldValue.matched(a.get(5), b.get(4)));
+ expected.add(DiffedFieldValue.matched(a.get(6), b.get(6)));
+
+ List<DiffedFieldValue> diffed = DiffFields.diff(a, b);
+ assertEquals(expected, diffed);
+ }
+}
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
index 52b6b7b..d0349fd 100644
--- a/tools/ahat/test/DiffTest.java
+++ b/tools/ahat/test/DiffTest.java
@@ -20,7 +20,6 @@
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Diff;
-import com.android.ahat.heapdump.FieldValue;
import com.android.tools.perflib.heap.hprof.HprofClassDump;
import com.android.tools.perflib.heap.hprof.HprofConstant;
import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
@@ -129,35 +128,4 @@
// Diffing should not crash.
Diff.snapshots(snapshot, snapshot);
}
-
- @Test
- public void diffFields() {
- List<FieldValue> a = new ArrayList<FieldValue>();
- a.add(new FieldValue("n0", "t0", null));
- a.add(new FieldValue("n2", "t2", null));
- a.add(new FieldValue("n3", "t3", null));
- a.add(new FieldValue("n4", "t4", null));
- a.add(new FieldValue("n5", "t5", null));
- a.add(new FieldValue("n6", "t6", null));
-
- List<FieldValue> b = new ArrayList<FieldValue>();
- b.add(new FieldValue("n0", "t0", null));
- b.add(new FieldValue("n1", "t1", null));
- b.add(new FieldValue("n2", "t2", null));
- b.add(new FieldValue("n3", "t3", null));
- b.add(new FieldValue("n5", "t5", null));
- b.add(new FieldValue("n6", "t6", null));
- b.add(new FieldValue("n7", "t7", null));
-
- Diff.fields(a, b);
- assertEquals(8, a.size());
- assertEquals(8, b.size());
- for (int i = 0; i < 8; i++) {
- assertEquals(a.get(i), b.get(i).getBaseline());
- assertEquals(b.get(i), a.get(i).getBaseline());
- }
- assertTrue(a.get(1).isPlaceHolder());
- assertTrue(a.get(7).isPlaceHolder());
- assertTrue(b.get(4).isPlaceHolder());
- }
}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index ceb7346..3dce2dc 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -118,9 +118,9 @@
private Value getDumpedValue(String name, AhatSnapshot snapshot) {
AhatClassObj main = snapshot.findClass("Main");
AhatInstance stuff = null;
- for (FieldValue fields : main.getStaticFieldValues()) {
- if ("stuff".equals(fields.getName())) {
- stuff = fields.getValue().asAhatInstance();
+ for (FieldValue field : main.getStaticFieldValues()) {
+ if ("stuff".equals(field.name)) {
+ stuff = field.value.asAhatInstance();
}
}
return stuff.getField(name);
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index c7e9b18..a95788e 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,6 +22,7 @@
public static void main(String[] args) {
if (args.length == 0) {
args = new String[]{
+ "com.android.ahat.DiffFieldsTest",
"com.android.ahat.DiffTest",
"com.android.ahat.InstanceTest",
"com.android.ahat.NativeAllocationTest",
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index f340fa1..8a4c2df 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -216,5 +216,12 @@
bug: 62528691,
modes: [device],
names: ["libcore.java.util.TimeZoneTest#testSetDefaultRace"]
+},
+{
+ description: "Repeated annotations do not work in javac (OpenJDK8), fixed in OpenJDK9.
+ Blacklisted to support javac/dx build (b/36902714)",
+ result: EXEC_FAILED,
+ bug: 62408076,
+ names: ["libcore.java.lang.reflect.annotations.AnnotatedElementParameterTest#testImplicitConstructorParameters_singleAnnotation"]
}
]