summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/Android.gtest.mk9
-rw-r--r--compiler/driver/compiler_driver.h8
-rw-r--r--compiler/verifier_deps_test.cc2
-rw-r--r--dex2oat/dex2oat.cc47
-rw-r--r--dex2oat/dex2oat_options.cc3
-rw-r--r--dex2oat/dex2oat_options.def1
-rw-r--r--dex2oat/dex2oat_test.cc29
-rw-r--r--dex2oat/linker/image_test.h2
-rw-r--r--dex2oat/linker/image_writer.cc16
-rw-r--r--dex2oat/linker/image_writer.h3
-rw-r--r--dex2oat/linker/oat_writer.cc2
-rw-r--r--dexlayout/dexlayout.cc1
-rw-r--r--libartbase/Android.bp1
-rw-r--r--libartbase/base/indenter.h (renamed from runtime/indenter.h)6
-rw-r--r--libartbase/base/indenter_test.cc (renamed from runtime/indenter_test.cc)0
-rw-r--r--libartbase/base/logging_test.cc6
-rw-r--r--libartbase/base/safe_copy_test.cc7
-rw-r--r--libdexfile/Android.bp2
-rw-r--r--libdexfile/dex/type_lookup_table.cc (renamed from runtime/type_lookup_table.cc)1
-rw-r--r--libdexfile/dex/type_lookup_table.h (renamed from runtime/type_lookup_table.h)6
-rw-r--r--libdexfile/dex/type_lookup_table_test.cc (renamed from runtime/type_lookup_table_test.cc)0
-rw-r--r--oatdump/oatdump.cc4
-rw-r--r--openjdkjvmti/ti_redefine.cc34
-rw-r--r--openjdkjvmti/ti_redefine.h4
-rw-r--r--openjdkjvmti/transform.cc5
-rw-r--r--patchoat/patchoat_test.cc7
-rw-r--r--runtime/Android.bp4
-rw-r--r--runtime/class_loader_context.cc22
-rw-r--r--runtime/class_loader_context.h6
-rw-r--r--runtime/debug_print.cc18
-rw-r--r--runtime/gc/heap.cc3
-rw-r--r--runtime/oat_file.cc2
-rw-r--r--runtime/oat_file.h2
-rw-r--r--runtime/runtime.cc72
-rw-r--r--runtime/runtime.h17
-rw-r--r--runtime/stack_map.cc2
-rw-r--r--runtime/thread.cc5
-rw-r--r--runtime/trace.cc79
-rw-r--r--runtime/trace.h39
-rw-r--r--runtime/verifier/method_verifier.cc2
-rw-r--r--runtime/verifier/verifier_deps.cc2
-rwxr-xr-xtest/1950-unprepared-transform/check22
-rw-r--r--test/1950-unprepared-transform/expected.txt7
-rw-r--r--test/1950-unprepared-transform/info.txt1
-rw-r--r--test/1950-unprepared-transform/jvm-expected.patch6
-rwxr-xr-xtest/1950-unprepared-transform/run17
-rw-r--r--test/1950-unprepared-transform/src-ex/Transform.java22
-rw-r--r--test/1950-unprepared-transform/src/Main.java153
-rw-r--r--test/1950-unprepared-transform/src/art/Redefinition.java91
-rw-r--r--test/1950-unprepared-transform/unprepared_transform.cc77
-rw-r--r--test/Android.bp1
-rw-r--r--test/ti-agent/breakpoint_helper.cc9
-rw-r--r--test/ti-agent/exceptions_helper.cc11
-rw-r--r--test/ti-agent/frame_pop_helper.cc9
-rw-r--r--test/ti-agent/monitors_helper.cc15
-rw-r--r--test/ti-agent/redefinition_helper.cc12
-rw-r--r--test/ti-agent/test_env.cc1
-rw-r--r--test/ti-agent/test_env.h5
-rw-r--r--test/ti-agent/trace_helper.cc23
-rw-r--r--tools/dexanalyze/Android.bp53
-rw-r--r--tools/dexanalyze/dexanalyze.cc160
-rw-r--r--tools/dexanalyze/dexanalyze_experiments.cc133
-rw-r--r--tools/dexanalyze/dexanalyze_experiments.h68
-rw-r--r--tools/dexanalyze/dexanalyze_test.cc47
64 files changed, 1271 insertions, 153 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 7ca7409f88..3b2a5bef26 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -172,6 +172,7 @@ ART_GTEST_class_loader_context_test_DEX_DEPS := Main MultiDex MyClass ForClassLo
ART_GTEST_class_table_test_DEX_DEPS := XandY
ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex
ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes
+ART_GTEST_dexanalyze_test_DEX_DEPS := MultiDex
ART_GTEST_dexlayout_test_DEX_DEPS := ManyMethods
ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ManyMethods Statics VerifierDeps MainUncompressed EmptyUncompressed
ART_GTEST_dex2oat_image_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps
@@ -311,6 +312,12 @@ ART_GTEST_imgdiag_test_TARGET_DEPS := \
$(TARGET_CORE_IMAGE_DEFAULT_32) \
imgdiagd-target
+# Dex analyze test requires dexanalyze.
+ART_GTEST_dexanalyze_test_HOST_DEPS := \
+ dexanalyze-host
+ART_GTEST_dexanalyze_test_TARGET_DEPS := \
+ dexanalyze-target
+
# Oatdump test requires an image and oatfile to dump.
ART_GTEST_oatdump_test_HOST_DEPS := \
$(HOST_CORE_IMAGE_DEFAULT_64) \
@@ -353,6 +360,7 @@ ART_TEST_MODULES := \
art_compiler_tests \
art_compiler_host_tests \
art_dex2oat_tests \
+ art_dexanalyze_tests \
art_dexdiag_tests \
art_dexdump_tests \
art_dexlayout_tests \
@@ -798,6 +806,7 @@ ART_GTEST_jni_internal_test_DEX_DEPS :=
ART_GTEST_oat_file_assistant_test_DEX_DEPS :=
ART_GTEST_oat_file_assistant_test_HOST_DEPS :=
ART_GTEST_oat_file_assistant_test_TARGET_DEPS :=
+ART_GTEST_dexanalyze_test_DEX_DEPS :=
ART_GTEST_dexoptanalyzer_test_DEX_DEPS :=
ART_GTEST_dexoptanalyzer_test_HOST_DEPS :=
ART_GTEST_dexoptanalyzer_test_TARGET_DEPS :=
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index 682cf16d45..a5462eefe2 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -378,11 +378,11 @@ class CompilerDriver {
if (!android::base::EndsWith(boot_image_filename, ".art")) {
return false;
}
- size_t dash_pos = boot_image_filename.find_last_of("-/");
- if (dash_pos == std::string::npos || boot_image_filename[dash_pos] != '-') {
- return false;
+ size_t slash_pos = boot_image_filename.rfind('/');
+ if (slash_pos == std::string::npos) {
+ return android::base::StartsWith(boot_image_filename, "core-");
}
- return (dash_pos >= 4u) && (boot_image_filename.compare(dash_pos - 4u, 4u, "core") == 0);
+ return boot_image_filename.compare(slash_pos + 1, 5u, "core-") == 0;
}
optimizer::DexToDexCompiler& GetDexToDexCompiler() {
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 76448d819c..553d131e2f 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -18,6 +18,7 @@
#include "verifier/verifier_deps.h"
#include "art_method-inl.h"
+#include "base/indenter.h"
#include "class_linker.h"
#include "common_compiler_test.h"
#include "compiler_callbacks.h"
@@ -28,7 +29,6 @@
#include "driver/compiler_driver-inl.h"
#include "driver/compiler_options.h"
#include "handle_scope-inl.h"
-#include "indenter.h"
#include "mirror/class_loader.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index ee570ad4de..693ead56bb 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -428,6 +428,10 @@ NO_RETURN static void Usage(const char* fmt, ...) {
UsageError(" --class-loader-context=<string spec>: a string specifying the intended");
UsageError(" runtime loading context for the compiled dex files.");
UsageError("");
+ UsageError(" --stored-class-loader-context=<string spec>: a string specifying the intended");
+ UsageError(" runtime loading context that is stored in the oat file. Overrides");
+ UsageError(" --class-loader-context. Note that this ignores the classpath_dir arg.");
+ UsageError("");
UsageError(" It describes how the class loader chain should be built in order to ensure");
UsageError(" classes are resolved during dex2aot as they would be resolved at runtime.");
UsageError(" This spec will be encoded in the oat file. If at runtime the dex file is");
@@ -1260,11 +1264,32 @@ class Dex2Oat FINAL {
ParseInstructionSetFeatures(*args.Get(M::TargetInstructionSetFeatures), parser_options.get());
}
if (args.Exists(M::ClassLoaderContext)) {
- class_loader_context_ = ClassLoaderContext::Create(*args.Get(M::ClassLoaderContext));
+ std::string class_loader_context_arg = *args.Get(M::ClassLoaderContext);
+ class_loader_context_ = ClassLoaderContext::Create(class_loader_context_arg);
if (class_loader_context_ == nullptr) {
Usage("Option --class-loader-context has an incorrect format: %s",
- args.Get(M::ClassLoaderContext)->c_str());
+ class_loader_context_arg.c_str());
+ }
+ if (args.Exists(M::StoredClassLoaderContext)) {
+ stored_class_loader_context_.reset(new std::string(*args.Get(M::StoredClassLoaderContext)));
+ std::unique_ptr<ClassLoaderContext> temp_context =
+ ClassLoaderContext::Create(*stored_class_loader_context_);
+ if (temp_context == nullptr) {
+ Usage("Option --stored-class-loader-context has an incorrect format: %s",
+ stored_class_loader_context_->c_str());
+ } else if (!class_loader_context_->VerifyClassLoaderContextMatch(
+ *stored_class_loader_context_,
+ /*verify_names*/ false,
+ /*verify_checksums*/ false)) {
+ Usage(
+ "Option --stored-class-loader-context '%s' mismatches --class-loader-context '%s'",
+ stored_class_loader_context_->c_str(),
+ class_loader_context_arg.c_str());
+ }
}
+ } else if (args.Exists(M::StoredClassLoaderContext)) {
+ Usage("Option --stored-class-loader-context should only be used if "
+ "--class-loader-context is also specified");
}
if (!ReadCompilerOptions(args, compiler_options_.get(), &error_msg)) {
@@ -1579,7 +1604,7 @@ class Dex2Oat FINAL {
if (class_loader_context_ == nullptr) {
// If no context was specified use the default one (which is an empty PathClassLoader).
- class_loader_context_ = std::unique_ptr<ClassLoaderContext>(ClassLoaderContext::Default());
+ class_loader_context_ = ClassLoaderContext::Default();
}
DCHECK_EQ(oat_writers_.size(), 1u);
@@ -1605,8 +1630,15 @@ class Dex2Oat FINAL {
}
// Store the class loader context in the oat header.
- key_value_store_->Put(OatHeader::kClassPathKey,
- class_loader_context_->EncodeContextForOatFile(classpath_dir_));
+ // TODO: deprecate this since store_class_loader_context should be enough to cover the users
+ // of classpath_dir as well.
+ std::string class_path_key;
+ if (stored_class_loader_context_ != nullptr) {
+ class_path_key = *stored_class_loader_context_;
+ } else {
+ class_path_key = class_loader_context_->EncodeContextForOatFile(classpath_dir_);
+ }
+ key_value_store_->Put(OatHeader::kClassPathKey, class_path_key);
}
// Now that we have finalized key_value_store_, start writing the oat file.
@@ -2026,7 +2058,7 @@ class Dex2Oat FINAL {
// We need to prepare method offsets in the image address space for direct method patching.
TimingLogger::ScopedTiming t2("dex2oat Prepare image address space", timings_);
- if (!image_writer_->PrepareImageAddressSpace()) {
+ if (!image_writer_->PrepareImageAddressSpace(timings_)) {
LOG(ERROR) << "Failed to prepare image address space.";
return false;
}
@@ -2855,6 +2887,9 @@ class Dex2Oat FINAL {
// The spec describing how the class loader should be setup for compilation.
std::unique_ptr<ClassLoaderContext> class_loader_context_;
+ // The class loader context stored in the oat file. May be equal to class_loader_context_.
+ std::unique_ptr<std::string> stored_class_loader_context_;
+
size_t thread_count_;
uint64_t start_ns_;
uint64_t start_cputime_ns_;
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index 0d68f4fab6..5843691a24 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -245,6 +245,9 @@ static Parser CreateArgumentParser() {
.Define("--class-loader-context=_")
.WithType<std::string>()
.IntoKey(M::ClassLoaderContext)
+ .Define("--stored-class-loader-context=_")
+ .WithType<std::string>()
+ .IntoKey(M::StoredClassLoaderContext)
.Define("--compact-dex-level=_")
.WithType<CompactDexLevel>()
.WithValueMap({{"none", CompactDexLevel::kCompactDexLevelNone},
diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def
index 01f9d9425f..1a913a9bbf 100644
--- a/dex2oat/dex2oat_options.def
+++ b/dex2oat/dex2oat_options.def
@@ -88,6 +88,7 @@ DEX2OAT_OPTIONS_KEY (std::string, NoInlineFrom)
DEX2OAT_OPTIONS_KEY (Unit, ForceDeterminism)
DEX2OAT_OPTIONS_KEY (std::string, ClasspathDir)
DEX2OAT_OPTIONS_KEY (std::string, ClassLoaderContext)
+DEX2OAT_OPTIONS_KEY (std::string, StoredClassLoaderContext)
DEX2OAT_OPTIONS_KEY (std::string, DirtyImageObjects)
DEX2OAT_OPTIONS_KEY (std::vector<std::string>, RuntimeOptions)
DEX2OAT_OPTIONS_KEY (std::string, CompilationReason)
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index c890f8bef0..710a6af792 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -2125,4 +2125,33 @@ TEST_F(Dex2oatTest, AppImageNoProfile) {
EXPECT_EQ(header.GetImageSection(ImageHeader::kSectionArtFields).Size(), 0u);
}
+TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) {
+ const std::string out_dir = GetScratchDir();
+ const std::string odex_location = out_dir + "/base.odex";
+ const std::string valid_context = "PCL[" + GetUsedDexLocation() + "]";
+ const std::string stored_context = "PCL[/system/not_real_lib.jar]";
+ // The class path should not be valid and should fail being stored.
+ GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
+ odex_location,
+ CompilerFilter::Filter::kQuicken,
+ { "--class-loader-context=" + stored_context },
+ true, // expect_success
+ false, // use_fd
+ [&](const OatFile& oat_file) {
+ EXPECT_NE(oat_file.GetClassLoaderContext(), stored_context);
+ EXPECT_NE(oat_file.GetClassLoaderContext(), valid_context);
+ });
+ // The stored context should match what we expect even though it's invalid.
+ GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
+ odex_location,
+ CompilerFilter::Filter::kQuicken,
+ { "--class-loader-context=" + valid_context,
+ "--stored-class-loader-context=" + stored_context },
+ true, // expect_success
+ false, // use_fd
+ [&](const OatFile& oat_file) {
+ EXPECT_EQ(oat_file.GetClassLoaderContext(), stored_context);
+ });
+}
+
} // namespace art
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index 476a843821..a95252d3ed 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -293,7 +293,7 @@ inline void CompilationHelper::Compile(CompilerDriver* driver,
ASSERT_TRUE(cur_opened_dex_files.empty());
}
}
- bool image_space_ok = writer->PrepareImageAddressSpace();
+ bool image_space_ok = writer->PrepareImageAddressSpace(&timings);
ASSERT_TRUE(image_space_ok);
DCHECK_EQ(vdex_files.size(), oat_files.size());
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 6530ead2d1..c7a30a06ed 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -133,23 +133,31 @@ static void ClearDexFileCookies() REQUIRES_SHARED(Locks::mutator_lock_) {
Runtime::Current()->GetHeap()->VisitObjects(visitor);
}
-bool ImageWriter::PrepareImageAddressSpace() {
+bool ImageWriter::PrepareImageAddressSpace(TimingLogger* timings) {
target_ptr_size_ = InstructionSetPointerSize(compiler_driver_.GetInstructionSet());
gc::Heap* const heap = Runtime::Current()->GetHeap();
{
ScopedObjectAccess soa(Thread::Current());
- PruneNonImageClasses(); // Remove junk
+ {
+ TimingLogger::ScopedTiming t("PruneNonImageClasses", timings);
+ PruneNonImageClasses(); // Remove junk
+ }
if (compile_app_image_) {
+ TimingLogger::ScopedTiming t("ClearDexFileCookies", timings);
// Clear dex file cookies for app images to enable app image determinism. This is required
// since the cookie field contains long pointers to DexFiles which are not deterministic.
// b/34090128
ClearDexFileCookies();
} else {
+ TimingLogger::ScopedTiming t("ComputeLazyFieldsForImageClasses", timings);
// Avoid for app image since this may increase RAM and image size.
ComputeLazyFieldsForImageClasses(); // Add useful information
}
}
- heap->CollectGarbage(/* clear_soft_references */ false); // Remove garbage.
+ {
+ TimingLogger::ScopedTiming t("CollectGarbage", timings);
+ heap->CollectGarbage(/* clear_soft_references */ false); // Remove garbage.
+ }
if (kIsDebugBuild) {
ScopedObjectAccess soa(Thread::Current());
@@ -157,12 +165,14 @@ bool ImageWriter::PrepareImageAddressSpace() {
}
{
+ TimingLogger::ScopedTiming t("CalculateNewObjectOffsets", timings);
ScopedObjectAccess soa(Thread::Current());
CalculateNewObjectOffsets();
}
// This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and
// bin size sums being calculated.
+ TimingLogger::ScopedTiming t("AllocMemory", timings);
if (!AllocMemory()) {
return false;
}
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index c67835b455..197253e102 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -64,6 +64,7 @@ class ClassLoader;
class ClassLoaderVisitor;
class ImTable;
class ImtConflictTable;
+class TimingLogger;
static constexpr int kInvalidFd = -1;
@@ -81,7 +82,7 @@ class ImageWriter FINAL {
const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map,
const std::unordered_set<std::string>* dirty_image_objects);
- bool PrepareImageAddressSpace();
+ bool PrepareImageAddressSpace(TimingLogger* timings);
bool IsImageAddressSpaceReady() const {
DCHECK(!image_infos_.empty());
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index bcc59098e5..690b565090 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -40,6 +40,7 @@
#include "dex/dex_file_loader.h"
#include "dex/dex_file_types.h"
#include "dex/standard_dex_file.h"
+#include "dex/type_lookup_table.h"
#include "dex/verification_results.h"
#include "dex_container.h"
#include "dexlayout.h"
@@ -63,7 +64,6 @@
#include "oat_quick_method_header.h"
#include "quicken_info.h"
#include "scoped_thread_state_change-inl.h"
-#include "type_lookup_table.h"
#include "utils/dex_cache_arrays_layout-inl.h"
#include "vdex_file.h"
#include "verifier/verifier_deps.h"
diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc
index ec0cbe6a60..da1573cef1 100644
--- a/dexlayout/dexlayout.cc
+++ b/dexlayout/dexlayout.cc
@@ -49,7 +49,6 @@
#include "dex_visualize.h"
#include "dex_writer.h"
#include "jit/profile_compilation_info.h"
-#include "mem_map.h"
namespace art {
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index 62157e196d..e68c2fba5b 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -104,6 +104,7 @@ art_cc_test {
"base/hash_set_test.cc",
"base/hex_dump_test.cc",
"base/histogram_test.cc",
+ "base/indenter_test.cc",
"base/leb128_test.cc",
"base/logging_test.cc",
"base/memory_region_test.cc",
diff --git a/runtime/indenter.h b/libartbase/base/indenter.h
index 6361dd2092..850b7c4f73 100644
--- a/runtime/indenter.h
+++ b/libartbase/base/indenter.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_INDENTER_H_
-#define ART_RUNTIME_INDENTER_H_
+#ifndef ART_LIBARTBASE_BASE_INDENTER_H_
+#define ART_LIBARTBASE_BASE_INDENTER_H_
#include <ostream>
#include <streambuf>
@@ -160,4 +160,4 @@ class ScopedIndentation {
} // namespace art
-#endif // ART_RUNTIME_INDENTER_H_
+#endif // ART_LIBARTBASE_BASE_INDENTER_H_
diff --git a/runtime/indenter_test.cc b/libartbase/base/indenter_test.cc
index 09c0c54e5a..09c0c54e5a 100644
--- a/runtime/indenter_test.cc
+++ b/libartbase/base/indenter_test.cc
diff --git a/libartbase/base/logging_test.cc b/libartbase/base/logging_test.cc
index 404e080b03..1456eb30fa 100644
--- a/libartbase/base/logging_test.cc
+++ b/libartbase/base/logging_test.cc
@@ -21,7 +21,7 @@
#include "android-base/logging.h"
#include "base/bit_utils.h"
#include "base/macros.h"
-#include "common_runtime_test.h"
+#include "gtest/gtest.h"
#include "runtime_debug.h"
namespace art {
@@ -31,9 +31,9 @@ static void SimpleAborter(const char* msg) {
_exit(1);
}
-class LoggingTest : public CommonRuntimeTest {
+class LoggingTest : public testing::Test {
protected:
- void PostRuntimeCreate() OVERRIDE {
+ LoggingTest() {
// In our abort tests we really don't want the runtime to create a real dump.
android::base::SetAborter(SimpleAborter);
}
diff --git a/libartbase/base/safe_copy_test.cc b/libartbase/base/safe_copy_test.cc
index a9ec9528a1..f1d7c55e77 100644
--- a/libartbase/base/safe_copy_test.cc
+++ b/libartbase/base/safe_copy_test.cc
@@ -16,14 +16,15 @@
#include "safe_copy.h"
-#include "common_runtime_test.h"
-
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/user.h>
-#include "globals.h"
+#include "android-base/logging.h"
+#include "base/globals.h"
+#include "gtest/gtest.h"
+
namespace art {
diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp
index 3fd61ee251..b2c041c81f 100644
--- a/libdexfile/Android.bp
+++ b/libdexfile/Android.bp
@@ -32,6 +32,7 @@ cc_defaults {
"dex/modifiers.cc",
"dex/primitive.cc",
"dex/standard_dex_file.cc",
+ "dex/type_lookup_table.cc",
"dex/utf.cc",
],
@@ -123,6 +124,7 @@ art_cc_test {
"dex/dex_instruction_test.cc",
"dex/primitive_test.cc",
"dex/string_reference_test.cc",
+ "dex/type_lookup_table_test.cc",
"dex/utf_test.cc",
],
shared_libs: [
diff --git a/runtime/type_lookup_table.cc b/libdexfile/dex/type_lookup_table.cc
index 7e204fc03a..ca5ec2f798 100644
--- a/runtime/type_lookup_table.cc
+++ b/libdexfile/dex/type_lookup_table.cc
@@ -20,7 +20,6 @@
#include <memory>
#include "base/bit_utils.h"
-#include "base/utils.h"
#include "dex/dex_file-inl.h"
#include "dex/utf-inl.h"
diff --git a/runtime/type_lookup_table.h b/libdexfile/dex/type_lookup_table.h
index 3352d60ed1..0ba2b75dc6 100644
--- a/runtime/type_lookup_table.h
+++ b/libdexfile/dex/type_lookup_table.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_TYPE_LOOKUP_TABLE_H_
-#define ART_RUNTIME_TYPE_LOOKUP_TABLE_H_
+#ifndef ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_
+#define ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_
#include "base/leb128.h"
#include "dex/dex_file_types.h"
@@ -172,4 +172,4 @@ class TypeLookupTable {
} // namespace art
-#endif // ART_RUNTIME_TYPE_LOOKUP_TABLE_H_
+#endif // ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_
diff --git a/runtime/type_lookup_table_test.cc b/libdexfile/dex/type_lookup_table_test.cc
index b6ab6da78c..b6ab6da78c 100644
--- a/runtime/type_lookup_table_test.cc
+++ b/libdexfile/dex/type_lookup_table_test.cc
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 3bff123386..7530a9c5a8 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -34,6 +34,7 @@
#include "art_field-inl.h"
#include "art_method-inl.h"
#include "base/bit_utils_iterator.h"
+#include "base/indenter.h"
#include "base/os.h"
#include "base/safe_map.h"
#include "base/stl_util.h"
@@ -49,6 +50,7 @@
#include "dex/dex_file-inl.h"
#include "dex/dex_instruction-inl.h"
#include "dex/string_reference.h"
+#include "dex/type_lookup_table.h"
#include "disassembler.h"
#include "gc/accounting/space_bitmap-inl.h"
#include "gc/space/image_space.h"
@@ -56,7 +58,6 @@
#include "gc/space/space-inl.h"
#include "image-inl.h"
#include "imtable-inl.h"
-#include "indenter.h"
#include "subtype_check.h"
#include "index_bss_mapping.h"
#include "interpreter/unstarted_runtime.h"
@@ -75,7 +76,6 @@
#include "stack.h"
#include "stack_map.h"
#include "thread_list.h"
-#include "type_lookup_table.h"
#include "vdex_file.h"
#include "verifier/method_verifier.h"
#include "verifier/verifier_deps.h"
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 5d430d2073..a23baa5095 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -234,12 +234,39 @@ jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
std::string err_unused;
*is_redefinable =
- Redefiner::GetClassRedefinitionError(h_klass, &err_unused) == OK ? JNI_TRUE : JNI_FALSE;
+ Redefiner::GetClassRedefinitionError(h_klass, &err_unused) != ERR(UNMODIFIABLE_CLASS)
+ ? JNI_TRUE : JNI_FALSE;
return OK;
}
+jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg) {
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<1> hs(self);
+ art::ObjPtr<art::mirror::Object> obj(self->DecodeJObject(klass));
+ if (obj.IsNull()) {
+ return ERR(INVALID_CLASS);
+ }
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
+ return Redefiner::GetClassRedefinitionError(h_klass, error_msg);
+}
+
jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
/*out*/std::string* error_msg) {
+ if (!klass->IsResolved()) {
+ // It's only a problem to try to retransform/redefine a unprepared class if it's happening on
+ // the same thread as the class-linking process. If it's on another thread we will be able to
+ // wait for the preparation to finish and continue from there.
+ if (klass->GetLockOwnerThreadId() == art::Thread::Current()->GetThreadId()) {
+ *error_msg = "Modification of class " + klass->PrettyClass() +
+ " from within the classes ClassLoad callback is not supported to prevent deadlocks." +
+ " Please use ClassFileLoadHook directly instead.";
+ return ERR(INTERNAL);
+ } else {
+ LOG(WARNING) << klass->PrettyClass() << " is not yet resolved. Attempting to transform "
+ << "it could cause arbitrary length waits as the class is being resolved.";
+ }
+ }
if (klass->IsPrimitive()) {
*error_msg = "Modification of primitive classes is not supported";
return ERR(UNMODIFIABLE_CLASS);
@@ -332,12 +359,9 @@ jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env,
std::vector<ArtClassDefinition> def_vector;
def_vector.reserve(class_count);
for (jint i = 0; i < class_count; i++) {
- jboolean is_modifiable = JNI_FALSE;
- jvmtiError res = env->IsModifiableClass(definitions[i].klass, &is_modifiable);
+ jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, error_msg);
if (res != OK) {
return res;
- } else if (!is_modifiable) {
- return ERR(UNMODIFIABLE_CLASS);
}
// We make a copy of the class_bytes to pass into the retransformation.
// This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 778f87e68e..fa7d28648d 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -98,6 +98,10 @@ class Redefiner {
art::ArrayRef<const unsigned char> data,
std::string* error_msg);
+ // Helper for checking if redefinition/retransformation is allowed.
+ static jvmtiError GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg)
+ REQUIRES(!art::Locks::mutator_lock_);
+
private:
class ClassRedefinition {
public:
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
index 43b8fe94f4..62094a327d 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -313,12 +313,9 @@ jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env,
std::vector<ArtClassDefinition> definitions;
jvmtiError res = OK;
for (jint i = 0; i < class_count; i++) {
- jboolean is_modifiable = JNI_FALSE;
- res = env->IsModifiableClass(classes[i], &is_modifiable);
+ res = Redefiner::GetClassRedefinitionError(classes[i], error_msg);
if (res != OK) {
return res;
- } else if (!is_modifiable) {
- return ERR(UNMODIFIABLE_CLASS);
}
ArtClassDefinition def;
res = def.Init(self, classes[i]);
diff --git a/patchoat/patchoat_test.cc b/patchoat/patchoat_test.cc
index 974ed3217d..69728ae051 100644
--- a/patchoat/patchoat_test.cc
+++ b/patchoat/patchoat_test.cc
@@ -24,6 +24,7 @@
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
+#include "base/hex_dump.h"
#include "base/leb128.h"
#include "dexopt_test.h"
#include "runtime.h"
@@ -320,6 +321,12 @@ class PatchoatTest : public DexoptTest {
if (image1[i] != image2[i]) {
*error_msg =
StringPrintf("%s and %s differ at offset %zu", filename1.c_str(), filename2.c_str(), i);
+ size_t hexdump_size = std::min<size_t>(16u, size - i);
+ HexDump dump1(&image1[i], hexdump_size, /* show_actual_addresses */ false, /* prefix */ "");
+ HexDump dump2(&image2[i], hexdump_size, /* show_actual_addresses */ false, /* prefix */ "");
+ std::ostringstream oss;
+ oss << "\n" << dump1 << "\n" << dump2;
+ *error_msg += oss.str();
return true;
}
}
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 00d4a6080a..8329b62618 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -199,7 +199,6 @@ cc_defaults {
"ti/agent.cc",
"trace.cc",
"transaction.cc",
- "type_lookup_table.cc",
"vdex_file.cc",
"verifier/instruction_flags.cc",
"verifier/method_verifier.cc",
@@ -449,6 +448,7 @@ gensrcs {
"thread.h",
"thread_state.h",
"ti/agent.h",
+ "trace.h",
"verifier/verifier_enums.h",
],
output_extension: "operator_out.cc",
@@ -571,7 +571,6 @@ art_cc_test {
"handle_scope_test.cc",
"hidden_api_test.cc",
"imtable_test.cc",
- "indenter_test.cc",
"indirect_reference_table_test.cc",
"instrumentation_test.cc",
"intern_table_test.cc",
@@ -598,7 +597,6 @@ art_cc_test {
"subtype_check_test.cc",
"thread_pool_test.cc",
"transaction_test.cc",
- "type_lookup_table_test.cc",
"vdex_file_test.cc",
"verifier/method_verifier_test.cc",
"verifier/reg_type_test.cc",
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index e646520f3d..216ad8f794 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -649,12 +649,16 @@ static bool IsAbsoluteLocation(const std::string& location) {
return !location.empty() && location[0] == '/';
}
-bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const {
- DCHECK(dex_files_open_attempted_);
- DCHECK(dex_files_open_result_);
+bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec,
+ bool verify_names,
+ bool verify_checksums) const {
+ if (verify_names || verify_checksums) {
+ DCHECK(dex_files_open_attempted_);
+ DCHECK(dex_files_open_result_);
+ }
ClassLoaderContext expected_context;
- if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) {
+ if (!expected_context.Parse(context_spec, verify_checksums)) {
LOG(WARNING) << "Invalid class loader context: " << context_spec;
return false;
}
@@ -693,8 +697,14 @@ bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& contex
return false;
}
- DCHECK_EQ(info.classpath.size(), info.checksums.size());
- DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
+ if (verify_checksums) {
+ DCHECK_EQ(info.classpath.size(), info.checksums.size());
+ DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
+ }
+
+ if (!verify_names) {
+ continue;
+ }
for (size_t k = 0; k < info.classpath.size(); k++) {
// Compute the dex location that must be compared.
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 692a6cda5b..231acc46ac 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -104,7 +104,11 @@ class ClassLoaderContext {
// - the class loader from the same position have the same classpath
// (the order and checksum of the dex files matches)
// This should be called after OpenDexFiles().
- bool VerifyClassLoaderContextMatch(const std::string& context_spec) const;
+ // Names are only verified if verify_names is true.
+ // Checksums are only verified if verify_checksums is true.
+ bool VerifyClassLoaderContextMatch(const std::string& context_spec,
+ bool verify_names = true,
+ bool verify_checksums = true) const;
// Creates the class loader context from the given string.
// The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
diff --git a/runtime/debug_print.cc b/runtime/debug_print.cc
index c7530bec6e..60487670ac 100644
--- a/runtime/debug_print.cc
+++ b/runtime/debug_print.cc
@@ -73,11 +73,12 @@ std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* clas
oss << "BootClassLoader"; // This would be unexpected.
}
for (; loader != nullptr; loader = loader->GetParent()) {
- oss << loader_separator << loader->GetClass()->PrettyDescriptor();
+ ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader);
+ oss << loader_separator << loader->GetClass()->PrettyDescriptor()
+ << "/" << static_cast<const void*>(table);
loader_separator = ";";
// If we didn't find the class yet, try to find it in the current class loader.
if (!found_class) {
- ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader);
ObjPtr<mirror::Class> klass =
(table != nullptr) ? table->Lookup(class_descriptor, hash) : nullptr;
if (klass != nullptr) {
@@ -99,7 +100,8 @@ std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* clas
VisitClassLoaderDexFiles(soa,
handle,
[&](const DexFile* dex_file) {
- oss << path_separator << dex_file->GetLocation();
+ oss << path_separator << dex_file->GetLocation()
+ << "/" << static_cast<const void*>(dex_file);
path_separator = ":";
return true; // Continue with the next DexFile.
});
@@ -135,8 +137,9 @@ void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::C
CHECK(iftable != nullptr);
size_t ifcount = iftable->Count();
LOG(ERROR) << "Maybe bug 77342775, looking for " << target_descriptor
- << " with loader " << DescribeLoaders(src_class->GetClassLoader(), target_descriptor)
- << " in interface table for " << source_descriptor << " ifcount=" << ifcount;
+ << " with loader " << DescribeLoaders(target_class->GetClassLoader(), target_descriptor)
+ << " in interface table for " << source_descriptor << " ifcount=" << ifcount
+ << " with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor);
for (size_t i = 0; i != ifcount; ++i) {
ObjPtr<mirror::Class> iface = iftable->GetInterface(i);
CHECK(iface != nullptr);
@@ -145,8 +148,9 @@ void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::C
}
} else {
LOG(ERROR) << "Maybe bug 77342775, looking for " << target_descriptor
- << " with loader " << DescribeLoaders(src_class->GetClassLoader(), target_descriptor)
- << " in superclass chain for " << source_descriptor;
+ << " with loader " << DescribeLoaders(target_class->GetClassLoader(), target_descriptor)
+ << " in superclass chain for " << source_descriptor
+ << " with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor);
for (ObjPtr<mirror::Class> klass = src_class;
klass != nullptr;
klass = klass->GetSuperClass()) {
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 684922099b..e85824de70 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -1203,7 +1203,8 @@ void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType
// If we're in a stack overflow, do not create a new exception. It would require running the
// constructor, which will of course still be in a stack overflow.
if (self->IsHandlingStackOverflow()) {
- self->SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());
+ self->SetException(
+ Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
return;
}
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index cfbcda36b9..bda6604724 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -49,6 +49,7 @@
#include "dex/dex_file_loader.h"
#include "dex/dex_file_types.h"
#include "dex/standard_dex_file.h"
+#include "dex/type_lookup_table.h"
#include "dex/utf-inl.h"
#include "elf_file.h"
#include "elf_utils.h"
@@ -61,7 +62,6 @@
#include "oat_file-inl.h"
#include "oat_file_manager.h"
#include "runtime.h"
-#include "type_lookup_table.h"
#include "vdex_file.h"
namespace art {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 24868dd55d..6494b4c58e 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -32,11 +32,11 @@
#include "compiler_filter.h"
#include "dex/dex_file.h"
#include "dex/dex_file_layout.h"
+#include "dex/type_lookup_table.h"
#include "dex/utf.h"
#include "index_bss_mapping.h"
#include "mirror/object.h"
#include "oat.h"
-#include "type_lookup_table.h"
namespace art {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index f550a151bb..c394fefb38 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1503,19 +1503,34 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
// TODO: move this to just be an Trace::Start argument
Trace::SetDefaultClockSource(runtime_options.GetOrDefault(Opt::ProfileClock));
+ // Pre-allocate an OutOfMemoryError for the case when we fail to
+ // allocate the exception to be thrown.
+ InitPreAllocatedException(self,
+ &Runtime::pre_allocated_OutOfMemoryError_when_throwing_exception_,
+ "Ljava/lang/OutOfMemoryError;",
+ "OutOfMemoryError thrown while trying to throw an exception; "
+ "no stack trace available");
// Pre-allocate an OutOfMemoryError for the double-OOME case.
- self->ThrowNewException("Ljava/lang/OutOfMemoryError;",
- "OutOfMemoryError thrown while trying to throw OutOfMemoryError; "
- "no stack trace available");
- pre_allocated_OutOfMemoryError_ = GcRoot<mirror::Throwable>(self->GetException());
- self->ClearException();
+ InitPreAllocatedException(self,
+ &Runtime::pre_allocated_OutOfMemoryError_when_throwing_oome_,
+ "Ljava/lang/OutOfMemoryError;",
+ "OutOfMemoryError thrown while trying to throw OutOfMemoryError; "
+ "no stack trace available");
+ // Pre-allocate an OutOfMemoryError for the case when we fail to
+ // allocate while handling a stack overflow.
+ InitPreAllocatedException(self,
+ &Runtime::pre_allocated_OutOfMemoryError_when_handling_stack_overflow_,
+ "Ljava/lang/OutOfMemoryError;",
+ "OutOfMemoryError thrown while trying to handle a stack overflow; "
+ "no stack trace available");
// Pre-allocate a NoClassDefFoundError for the common case of failing to find a system class
// ahead of checking the application's class loader.
- self->ThrowNewException("Ljava/lang/NoClassDefFoundError;",
- "Class not found using the boot class loader; no stack trace available");
- pre_allocated_NoClassDefFoundError_ = GcRoot<mirror::Throwable>(self->GetException());
- self->ClearException();
+ InitPreAllocatedException(self,
+ &Runtime::pre_allocated_NoClassDefFoundError_,
+ "Ljava/lang/NoClassDefFoundError;",
+ "Class not found using the boot class loader; "
+ "no stack trace available");
// Runtime initialization is largely done now.
// We load plugins first since that can modify the runtime state slightly.
@@ -1666,6 +1681,16 @@ void Runtime::AttachAgent(JNIEnv* env,
}
}
+void Runtime::InitPreAllocatedException(Thread* self,
+ GcRoot<mirror::Throwable> Runtime::* exception,
+ const char* exception_class_descriptor,
+ const char* msg) {
+ DCHECK_EQ(self, Thread::Current());
+ self->ThrowNewException(exception_class_descriptor, msg);
+ this->*exception = GcRoot<mirror::Throwable>(self->GetException());
+ self->ClearException();
+}
+
void Runtime::InitNativeMethods() {
VLOG(startup) << "Runtime::InitNativeMethods entering";
Thread* self = Thread::Current();
@@ -1917,10 +1942,26 @@ void Runtime::DetachCurrentThread() {
thread_list_->Unregister(self);
}
-mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryError() {
- mirror::Throwable* oome = pre_allocated_OutOfMemoryError_.Read();
+mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenThrowingException() {
+ mirror::Throwable* oome = pre_allocated_OutOfMemoryError_when_throwing_exception_.Read();
+ if (oome == nullptr) {
+ LOG(ERROR) << "Failed to return pre-allocated OOME-when-throwing-exception";
+ }
+ return oome;
+}
+
+mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME() {
+ mirror::Throwable* oome = pre_allocated_OutOfMemoryError_when_throwing_oome_.Read();
+ if (oome == nullptr) {
+ LOG(ERROR) << "Failed to return pre-allocated OOME-when-throwing-OOME";
+ }
+ return oome;
+}
+
+mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow() {
+ mirror::Throwable* oome = pre_allocated_OutOfMemoryError_when_handling_stack_overflow_.Read();
if (oome == nullptr) {
- LOG(ERROR) << "Failed to return pre-allocated OOME";
+ LOG(ERROR) << "Failed to return pre-allocated OOME-when-handling-stack-overflow";
}
return oome;
}
@@ -2005,7 +2046,12 @@ void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
void Runtime::VisitNonThreadRoots(RootVisitor* visitor) {
java_vm_->VisitRoots(visitor);
sentinel_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
- pre_allocated_OutOfMemoryError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
+ pre_allocated_OutOfMemoryError_when_throwing_exception_
+ .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
+ pre_allocated_OutOfMemoryError_when_throwing_oome_
+ .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
+ pre_allocated_OutOfMemoryError_when_handling_stack_overflow_
+ .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
pre_allocated_NoClassDefFoundError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
verifier::MethodVerifier::VisitStaticRoots(visitor);
VisitTransactionRoots(visitor);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 03f17bc04a..3d4b596349 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -291,7 +291,12 @@ class Runtime {
// Get the special object used to mark a cleared JNI weak global.
mirror::Object* GetClearedJniWeakGlobal() REQUIRES_SHARED(Locks::mutator_lock_);
- mirror::Throwable* GetPreAllocatedOutOfMemoryError() REQUIRES_SHARED(Locks::mutator_lock_);
+ mirror::Throwable* GetPreAllocatedOutOfMemoryErrorWhenThrowingException()
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ mirror::Throwable* GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME()
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ mirror::Throwable* GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow()
+ REQUIRES_SHARED(Locks::mutator_lock_);
mirror::Throwable* GetPreAllocatedNoClassDefFoundError()
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -764,6 +769,11 @@ class Runtime {
bool Init(RuntimeArgumentMap&& runtime_options)
SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_);
+ void InitPreAllocatedException(Thread* self,
+ GcRoot<mirror::Throwable> Runtime::* exception,
+ const char* exception_class_descriptor,
+ const char* msg)
+ REQUIRES_SHARED(Locks::mutator_lock_);
void InitNativeMethods() REQUIRES(!Locks::mutator_lock_);
void RegisterRuntimeNativeMethods(JNIEnv* env);
@@ -796,7 +806,10 @@ class Runtime {
// 64 bit so that we can share the same asm offsets for both 32 and 64 bits.
uint64_t callee_save_methods_[kCalleeSaveSize];
- GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_;
+ // Pre-allocated exceptions (see Runtime::Init).
+ GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_when_throwing_exception_;
+ GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_when_throwing_oome_;
+ GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_when_handling_stack_overflow_;
GcRoot<mirror::Throwable> pre_allocated_NoClassDefFoundError_;
ArtMethod* resolution_method_;
ArtMethod* imt_conflict_method_;
diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc
index 250ff2af1a..9c7b6875cf 100644
--- a/runtime/stack_map.cc
+++ b/runtime/stack_map.cc
@@ -19,7 +19,7 @@
#include <stdint.h>
#include "art_method.h"
-#include "indenter.h"
+#include "base/indenter.h"
#include "scoped_thread_state_change-inl.h"
namespace art {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index d17f409a7d..f6ac64f7bd 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -3021,7 +3021,8 @@ void Thread::ThrowNewWrappedException(const char* exception_class_descriptor,
// If we couldn't allocate the exception, throw the pre-allocated out of memory exception.
if (exception == nullptr) {
- SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());
+ Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, so help out and log one.
+ SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenThrowingException());
return;
}
@@ -3101,7 +3102,7 @@ void Thread::ThrowOutOfMemoryError(const char* msg) {
tls32_.throwing_OutOfMemoryError = false;
} else {
Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, so help out and log one.
- SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());
+ SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME());
}
}
diff --git a/runtime/trace.cc b/runtime/trace.cc
index bea510ab61..292cac6d0a 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -470,24 +470,30 @@ void Trace::StopTracing(bool finish_tracing, bool flush_file) {
if (the_trace != nullptr) {
stop_alloc_counting = (the_trace->flags_ & Trace::kTraceCountAllocs) != 0;
+ // Stop the trace sources adding more entries to the trace buffer and synchronise stores.
+ {
+ gc::ScopedGCCriticalSection gcs(self,
+ gc::kGcCauseInstrumentation,
+ gc::kCollectorTypeInstrumentation);
+ ScopedSuspendAll ssa(__FUNCTION__);
+
+ if (the_trace->trace_mode_ == TraceMode::kSampling) {
+ MutexLock mu(self, *Locks::thread_list_lock_);
+ runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr);
+ } else {
+ runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey);
+ runtime->GetInstrumentation()->RemoveListener(
+ the_trace, instrumentation::Instrumentation::kMethodEntered |
+ instrumentation::Instrumentation::kMethodExited |
+ instrumentation::Instrumentation::kMethodUnwind);
+ }
+ }
+ // At this point, code may read buf_ as it's writers are shutdown
+ // and the ScopedSuspendAll above has ensured all stores to buf_
+ // are now visible.
if (finish_tracing) {
the_trace->FinishTracing();
}
- gc::ScopedGCCriticalSection gcs(self,
- gc::kGcCauseInstrumentation,
- gc::kCollectorTypeInstrumentation);
- ScopedSuspendAll ssa(__FUNCTION__);
-
- if (the_trace->trace_mode_ == TraceMode::kSampling) {
- MutexLock mu(self, *Locks::thread_list_lock_);
- runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr);
- } else {
- runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey);
- runtime->GetInstrumentation()->RemoveListener(
- the_trace, instrumentation::Instrumentation::kMethodEntered |
- instrumentation::Instrumentation::kMethodExited |
- instrumentation::Instrumentation::kMethodUnwind);
- }
if (the_trace->trace_file_.get() != nullptr) {
// Do not try to erase, so flush and close explicitly.
if (flush_file) {
@@ -653,7 +659,7 @@ Trace::Trace(File* trace_file,
flags_(flags), trace_output_mode_(output_mode), trace_mode_(trace_mode),
clock_source_(default_clock_source_),
buffer_size_(std::max(kMinBufSize, buffer_size)),
- start_time_(MicroTime()), clock_overhead_ns_(GetClockOverheadNanoSeconds()), cur_offset_(0),
+ start_time_(MicroTime()), clock_overhead_ns_(GetClockOverheadNanoSeconds()),
overflow_(false), interval_us_(0), streaming_lock_(nullptr),
unique_methods_lock_(new Mutex("unique methods lock", kTracingUniqueMethodsLock)) {
CHECK(trace_file != nullptr || output_mode == TraceOutputMode::kDDMS);
@@ -674,7 +680,6 @@ Trace::Trace(File* trace_file,
}
static_assert(18 <= kMinBufSize, "Minimum buffer size not large enough for trace header");
- // Update current offset.
cur_offset_.store(kTraceHeaderLength, std::memory_order_relaxed);
if (output_mode == TraceOutputMode::kStreaming) {
@@ -711,10 +716,10 @@ void Trace::DumpBuf(uint8_t* buf, size_t buf_size, TraceClockSource clock_source
void Trace::FinishTracing() {
size_t final_offset = 0;
-
std::set<ArtMethod*> visited_methods;
if (trace_output_mode_ == TraceOutputMode::kStreaming) {
// Clean up.
+ MutexLock mu(Thread::Current(), *streaming_lock_);
STLDeleteValues(&seen_methods_);
} else {
final_offset = cur_offset_.load(std::memory_order_relaxed);
@@ -759,7 +764,8 @@ void Trace::FinishTracing() {
std::string header(os.str());
if (trace_output_mode_ == TraceOutputMode::kStreaming) {
- MutexLock mu(Thread::Current(), *streaming_lock_); // To serialize writing.
+ // Protect access to buf_ and satisfy sanitizer for calls to WriteBuf / FlushBuf.
+ MutexLock mu(Thread::Current(), *streaming_lock_);
// Write a special token to mark the end of trace records and the start of
// trace summary.
uint8_t buf[7];
@@ -944,6 +950,7 @@ std::string Trace::GetMethodLine(ArtMethod* method) {
}
void Trace::WriteToBuf(const uint8_t* src, size_t src_size) {
+ // Updates to cur_offset_ are done under the streaming_lock_ here as in streaming mode.
int32_t old_offset = cur_offset_.load(std::memory_order_relaxed);
int32_t new_offset = old_offset + static_cast<int32_t>(src_size);
if (dchecked_integral_cast<size_t>(new_offset) > buffer_size_) {
@@ -957,46 +964,59 @@ void Trace::WriteToBuf(const uint8_t* src, size_t src_size) {
if (!trace_file_->WriteFully(src, src_size)) {
PLOG(WARNING) << "Failed streaming a tracing event.";
}
- cur_offset_.store(0, std::memory_order_release); // Buffer is empty now.
+ cur_offset_.store(0, std::memory_order_relaxed); // Buffer is empty now.
return;
}
old_offset = 0;
new_offset = static_cast<int32_t>(src_size);
}
- cur_offset_.store(new_offset, std::memory_order_release);
+ cur_offset_.store(new_offset, std::memory_order_relaxed);
// Fill in data.
memcpy(buf_.get() + old_offset, src, src_size);
}
void Trace::FlushBuf() {
+ // Updates to cur_offset_ are done under the streaming_lock_ here as in streaming mode.
int32_t offset = cur_offset_.load(std::memory_order_relaxed);
if (!trace_file_->WriteFully(buf_.get(), offset)) {
PLOG(WARNING) << "Failed flush the remaining data in streaming.";
}
- cur_offset_.store(0, std::memory_order_release);
+ cur_offset_.store(0, std::memory_order_relaxed);
}
void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method,
instrumentation::Instrumentation::InstrumentationEvent event,
uint32_t thread_clock_diff, uint32_t wall_clock_diff) {
+ // This method is called in both tracing modes (method and
+ // sampling). In sampling mode, this method is only called by the
+ // sampling thread. In method tracing mode, it can be called
+ // concurrently.
+
// Ensure we always use the non-obsolete version of the method so that entry/exit events have the
// same pointer value.
method = method->GetNonObsoleteMethod();
+
// Advance cur_offset_ atomically.
int32_t new_offset;
int32_t old_offset = 0;
- // We do a busy loop here trying to acquire the next offset.
+ // In the non-streaming case, we do a busy loop here trying to get
+ // an offset to write our record and advance cur_offset_ for the
+ // next use.
if (trace_output_mode_ != TraceOutputMode::kStreaming) {
+ // Although multiple threads can call this method concurrently,
+ // the compare_exchange_weak here is still atomic (by definition).
+ // A succeeding update is visible to other cores when they pass
+ // through this point.
+ old_offset = cur_offset_.load(std::memory_order_relaxed); // Speculative read
do {
- old_offset = cur_offset_.load(std::memory_order_relaxed);
new_offset = old_offset + GetRecordSize(clock_source_);
if (static_cast<size_t>(new_offset) > buffer_size_) {
overflow_ = true;
return;
}
- } while (!cur_offset_.CompareAndSetWeakSequentiallyConsistent(old_offset, new_offset));
+ } while (!cur_offset_.compare_exchange_weak(old_offset, new_offset, std::memory_order_relaxed));
}
TraceAction action = kTraceMethodEnter;
@@ -1016,7 +1036,14 @@ void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method,
uint32_t method_value = EncodeTraceMethodAndAction(method, action);
- // Write data
+ // Write data into the tracing buffer (if not streaming) or into a
+ // small buffer on the stack (if streaming) which we'll put into the
+ // tracing buffer below.
+ //
+ // These writes to the tracing buffer are synchronised with the
+ // future reads that (only) occur under FinishTracing(). The callers
+ // of FinishTracing() acquire locks and (implicitly) synchronise
+ // the buffer memory.
uint8_t* ptr;
static constexpr size_t kPacketSize = 14U; // The maximum size of data in a packet.
uint8_t stack_buf[kPacketSize]; // Space to store a packet when in streaming mode.
diff --git a/runtime/trace.h b/runtime/trace.h
index 7171f759c9..b242d1596c 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -52,9 +52,10 @@ using ThreadIDBitSet = std::bitset<kMaxThreadIdNumber>;
enum TracingMode {
kTracingInactive,
- kMethodTracingActive,
- kSampleProfilingActive,
+ kMethodTracingActive, // Trace activity synchronous with method progress.
+ kSampleProfilingActive, // Trace activity captured by sampling thread.
};
+std::ostream& operator<<(std::ostream& os, const TracingMode& rhs);
// File format:
// header
@@ -98,6 +99,9 @@ enum TraceAction {
kTraceMethodActionMask = 0x03, // two bits
};
+// Class for recording event traces. Trace data is either collected
+// synchronously during execution (TracingMode::kMethodTracingActive),
+// or by a separate sampling thread (TracingMode::kSampleProfilingActive).
class Trace FINAL : public instrumentation::InstrumentationListener {
public:
enum TraceFlag {
@@ -316,7 +320,10 @@ class Trace FINAL : public instrumentation::InstrumentationListener {
// File to write trace data out to, null if direct to ddms.
std::unique_ptr<File> trace_file_;
- // Buffer to store trace data.
+ // Buffer to store trace data. In streaming mode, this is protected
+ // by the streaming_lock_. In non-streaming mode, reserved regions
+ // are atomically allocated (using cur_offset_) for log entries to
+ // be written.
std::unique_ptr<uint8_t[]> buf_;
// Flags enabling extra tracing of things such as alloc counts.
@@ -339,7 +346,27 @@ class Trace FINAL : public instrumentation::InstrumentationListener {
// Clock overhead.
const uint32_t clock_overhead_ns_;
- // Offset into buf_.
+ // Offset into buf_. The field is atomic to allow multiple writers
+ // to concurrently reserve space in the buffer. The newly written
+ // buffer contents are not read without some other form of thread
+ // synchronization, such as suspending all potential writers or
+ // acquiring *streaming_lock_. Reading cur_offset_ is thus never
+ // used to ensure visibility of any other objects, and all accesses
+ // are memory_order_relaxed.
+ //
+ // All accesses to buf_ in streaming mode occur whilst holding the
+ // streaming lock. In streaming mode, the buffer may be written out
+ // so cur_offset_ can move forwards and backwards.
+ //
+ // When not in streaming mode, the buf_ writes can come from
+ // multiple threads when the trace mode is kMethodTracing. When
+ // trace mode is kSampling, writes only come from the sampling
+ // thread.
+ //
+ // Reads to the buffer happen after the event sources writing to the
+ // buffer have been shutdown and all stores have completed. The
+ // stores are made visible in StopTracing() when execution leaves
+ // the ScopedSuspendAll block.
AtomicInteger cur_offset_;
// Did we overflow the buffer recording traces?
@@ -353,8 +380,8 @@ class Trace FINAL : public instrumentation::InstrumentationListener {
// Streaming mode data.
Mutex* streaming_lock_;
- std::map<const DexFile*, DexIndexBitSet*> seen_methods_;
- std::unique_ptr<ThreadIDBitSet> seen_threads_;
+ std::map<const DexFile*, DexIndexBitSet*> seen_methods_ GUARDED_BY(streaming_lock_);
+ std::unique_ptr<ThreadIDBitSet> seen_threads_ GUARDED_BY(streaming_lock_);
// Bijective map from ArtMethod* to index.
// Map from ArtMethod* to index in unique_methods_;
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index cee717610d..72292c33f8 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -25,6 +25,7 @@
#include "base/aborting.h"
#include "base/enums.h"
#include "base/leb128.h"
+#include "base/indenter.h"
#include "base/logging.h" // For VLOG.
#include "base/mutex-inl.h"
#include "base/stl_util.h"
@@ -41,7 +42,6 @@
#include "experimental_flags.h"
#include "gc/accounting/card_table-inl.h"
#include "handle_scope-inl.h"
-#include "indenter.h"
#include "intern_table.h"
#include "mirror/class-inl.h"
#include "mirror/class.h"
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index 4772e538aa..fe839f7312 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -20,11 +20,11 @@
#include "art_field-inl.h"
#include "art_method-inl.h"
+#include "base/indenter.h"
#include "base/leb128.h"
#include "base/stl_util.h"
#include "compiler_callbacks.h"
#include "dex/dex_file-inl.h"
-#include "indenter.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
#include "obj_ptr-inl.h"
diff --git a/test/1950-unprepared-transform/check b/test/1950-unprepared-transform/check
new file mode 100755
index 0000000000..8a84388a8f
--- /dev/null
+++ b/test/1950-unprepared-transform/check
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# The RI sends an extra event that art doesn't. Add it to the expected output.
+if [[ "$TEST_RUNTIME" == "jvm" ]]; then
+ patch -p0 expected.txt < jvm-expected.patch >/dev/null
+fi
+
+./default-check "$@"
diff --git a/test/1950-unprepared-transform/expected.txt b/test/1950-unprepared-transform/expected.txt
new file mode 100644
index 0000000000..3be28a5696
--- /dev/null
+++ b/test/1950-unprepared-transform/expected.txt
@@ -0,0 +1,7 @@
+Redefine in ClassLoad on current thread.
+Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL
+Object out is: NON Transformed Object
+Redefine during ClassLoad on another thread.
+retransformClasses on an unprepared class succeeded
+Object out is: Transformed object!
+Redefinition thread finished.
diff --git a/test/1950-unprepared-transform/info.txt b/test/1950-unprepared-transform/info.txt
new file mode 100644
index 0000000000..875a5f6ec1
--- /dev/null
+++ b/test/1950-unprepared-transform/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/1950-unprepared-transform/jvm-expected.patch b/test/1950-unprepared-transform/jvm-expected.patch
new file mode 100644
index 0000000000..fd0131e5e4
--- /dev/null
+++ b/test/1950-unprepared-transform/jvm-expected.patch
@@ -0,0 +1,6 @@
+2,3c2,3
+< Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL
+< Object out is: NON Transformed Object
+---
+> retransformClasses on an unprepared class succeeded
+> Object out is: Transformed object!
diff --git a/test/1950-unprepared-transform/run b/test/1950-unprepared-transform/run
new file mode 100755
index 0000000000..adb1a1c507
--- /dev/null
+++ b/test/1950-unprepared-transform/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-run "$@" --jvmti --no-app-image
diff --git a/test/1950-unprepared-transform/src-ex/Transform.java b/test/1950-unprepared-transform/src-ex/Transform.java
new file mode 100644
index 0000000000..29aa8c68dd
--- /dev/null
+++ b/test/1950-unprepared-transform/src-ex/Transform.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+// This class is caught during loading.
+public class Transform {
+ public String toString() {
+ return "NON Transformed Object";
+ }
+}
diff --git a/test/1950-unprepared-transform/src/Main.java b/test/1950-unprepared-transform/src/Main.java
new file mode 100644
index 0000000000..adbcf38ce3
--- /dev/null
+++ b/test/1950-unprepared-transform/src/Main.java
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+import art.Redefinition;
+
+import java.lang.reflect.*;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+class Main {
+ public static String TEST_NAME = "1950-unprepared-transform";
+
+ // Base 64 encoding of the following class:
+ //
+ // public class Transform {
+ // public String toString() {
+ // return "Transformed object!";
+ // }
+ // }
+ public static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAEQoABAANCAAOBwAPBwAQAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1i" +
+ "ZXJUYWJsZQEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAO" +
+ "VHJhbnNmb3JtLmphdmEMAAUABgEAE1RyYW5zZm9ybWVkIG9iamVjdCEBAAlUcmFuc2Zvcm0BABBq" +
+ "YXZhL2xhbmcvT2JqZWN0ACEAAwAEAAAAAAACAAEABQAGAAEABwAAAB0AAQABAAAABSq3AAGxAAAA" +
+ "AQAIAAAABgABAAAAEgABAAkACgABAAcAAAAbAAEAAQAAAAMSArAAAAABAAgAAAAGAAEAAAAUAAEA" +
+ "CwAAAAIADA==");
+
+ public static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzOACaXU/P8oJOECPrdN1Cu9/ob2cUb2vOKxqYAgAAcAAAAHhWNBIAAAAAAAAAABACAAAK" +
+ "AAAAcAAAAAQAAACYAAAAAgAAAKgAAAAAAAAAAAAAAAMAAADAAAAAAQAAANgAAACgAQAA+AAAADAB" +
+ "AAA4AQAAOwEAAEgBAABcAQAAcAEAAIABAACVAQAAmAEAAKIBAAACAAAAAwAAAAQAAAAHAAAAAQAA" +
+ "AAIAAAAAAAAABwAAAAMAAAAAAAAAAAABAAAAAAAAAAAACAAAAAEAAQAAAAAAAAAAAAEAAAABAAAA" +
+ "AAAAAAUAAAAAAAAAAAIAAAAAAAACAAEAAAAAACwBAAADAAAAGgAGABEAAAABAAEAAQAAACgBAAAE" +
+ "AAAAcBACAAAADgASAA4AFAAOAAY8aW5pdD4AAUwAC0xUcmFuc2Zvcm07ABJMamF2YS9sYW5nL09i" +
+ "amVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAOVHJhbnNmb3JtLmphdmEAE1RyYW5zZm9ybWVkIG9i" +
+ "amVjdCEAAVYACHRvU3RyaW5nAFx+fkQ4eyJtaW4tYXBpIjoyNywic2hhLTEiOiI3YTdjNDlhY2Nj" +
+ "NTkzNTIyNzY4MTY3MThhNGM3YWU1MmY5NjgzZjk5IiwidmVyc2lvbiI6InYxLjIuNC1kZXYifQAA" +
+ "AAEBAIGABJACAQH4AQAACwAAAAAAAAABAAAAAAAAAAEAAAAKAAAAcAAAAAIAAAAEAAAAmAAAAAMA" +
+ "AAACAAAAqAAAAAUAAAADAAAAwAAAAAYAAAABAAAA2AAAAAEgAAACAAAA+AAAAAMgAAACAAAAKAEA" +
+ "AAIgAAAKAAAAMAEAAAAgAAABAAAAAAIAAAAQAAABAAAAEAIAAA==");
+
+ public static native void setupClassLoadHook(Thread target);
+ public static native void clearClassLoadHook(Thread target);
+ private static Consumer<Class<?>> doRedefine = null;
+
+ public static void doClassLoad(Class<?> c) {
+ try {
+ if (c.getName().equals("Transform")) {
+ Redefinition.addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES);
+ doRedefine.accept(c);
+ System.out.println("retransformClasses on an unprepared class succeeded");
+ }
+ } catch (Throwable e) {
+ System.out.println("Trying to redefine: " + c + ". " +
+ "Caught error " + e.getClass() + ": " + e.getMessage());
+ }
+ }
+
+ public static ClassLoader getClassLoaderFor(String location) throws Exception {
+ try {
+ Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
+ Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class);
+ /* on Dalvik, this is a DexFile; otherwise, it's null */
+ return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar",
+ Main.class.getClassLoader());
+ } catch (ClassNotFoundException e) {
+ // Running on RI. Use URLClassLoader.
+ return new java.net.URLClassLoader(
+ new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") });
+ }
+ }
+
+ public static void testCurrentThread() throws Throwable {
+ System.out.println("Redefine in ClassLoad on current thread.");
+ doRedefine = (c) -> { Redefinition.doCommonClassRetransformation(c); };
+ ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION"));
+ Class<?> klass = (Class<?>)new_loader.loadClass("Transform");
+ if (klass == null) {
+ throw new AssertionError("loadClass failed");
+ }
+ Object o = klass.newInstance();
+ System.out.println("Object out is: " + o);
+ }
+
+ public static void testRemoteThread() throws Throwable {
+ System.out.println("Redefine during ClassLoad on another thread.");
+ final Class[] loaded = new Class[] { null, };
+ final CountDownLatch gotClass = new CountDownLatch(1);
+ final CountDownLatch wokeUp = new CountDownLatch(1);
+ Thread redef_thread = new Thread(() -> {
+ try {
+ gotClass.await();
+ wokeUp.countDown();
+ // This will wait until the otehr thread returns so we need to wake up the other thread
+ // first.
+ Redefinition.doCommonClassRetransformation(loaded[0]);
+ } catch (Exception e) {
+ throw new Error("Failed to do redef!", e);
+ }
+ });
+ redef_thread.start();
+ doRedefine = (c) -> {
+ try {
+ loaded[0] = c;
+ gotClass.countDown();
+ wokeUp.await();
+ // Let the other thread do some stuff.
+ Thread.sleep(5000);
+ } catch (Exception e) {
+ throw new Error("Failed to do redef!", e);
+ }
+ };
+ ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION"));
+ Class<?> klass = (Class<?>)new_loader.loadClass("Transform");
+ if (klass == null) {
+ throw new AssertionError("loadClass failed");
+ }
+ Object o = klass.newInstance();
+ System.out.println("Object out is: " + o);
+ redef_thread.join();
+ System.out.println("Redefinition thread finished.");
+ }
+
+ public static void main(String[] args) {
+ // make sure we can do the transform.
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_RETRANSFORM);
+ Redefinition.setPopRetransformations(false);
+ Redefinition.enableCommonRetransformation(true);
+ setupClassLoadHook(Thread.currentThread());
+ try {
+ testCurrentThread();
+ testRemoteThread();
+ } catch (Throwable e) {
+ System.out.println(e.toString());
+ e.printStackTrace(System.out);
+ }
+ clearClassLoadHook(Thread.currentThread());
+ }
+}
diff --git a/test/1950-unprepared-transform/src/art/Redefinition.java b/test/1950-unprepared-transform/src/art/Redefinition.java
new file mode 100644
index 0000000000..56d2938a01
--- /dev/null
+++ b/test/1950-unprepared-transform/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+ public static final class CommonClassDefinition {
+ public final Class<?> target;
+ public final byte[] class_file_bytes;
+ public final byte[] dex_file_bytes;
+
+ public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+ this.target = target;
+ this.class_file_bytes = class_file_bytes;
+ this.dex_file_bytes = dex_file_bytes;
+ }
+ }
+
+ // A set of possible test configurations. Test should set this if they need to.
+ // This must be kept in sync with the defines in ti-agent/common_helper.cc
+ public static enum Config {
+ COMMON_REDEFINE(0),
+ COMMON_RETRANSFORM(1),
+ COMMON_TRANSFORM(2);
+
+ private final int val;
+ private Config(int val) {
+ this.val = val;
+ }
+ }
+
+ public static void setTestConfiguration(Config type) {
+ nativeSetTestConfiguration(type.val);
+ }
+
+ private static native void nativeSetTestConfiguration(int type);
+
+ // Transforms the class
+ public static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+
+ public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+ ArrayList<Class<?>> classes = new ArrayList<>();
+ ArrayList<byte[]> class_files = new ArrayList<>();
+ ArrayList<byte[]> dex_files = new ArrayList<>();
+
+ for (CommonClassDefinition d : defs) {
+ classes.add(d.target);
+ class_files.add(d.class_file_bytes);
+ dex_files.add(d.dex_file_bytes);
+ }
+ doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+ class_files.toArray(new byte[0][]),
+ dex_files.toArray(new byte[0][]));
+ }
+
+ public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+ for (CommonClassDefinition d : defs) {
+ addCommonTransformationResult(d.target.getCanonicalName(),
+ d.class_file_bytes,
+ d.dex_file_bytes);
+ }
+ }
+
+ public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+ byte[][] classfiles,
+ byte[][] dexfiles);
+ public static native void doCommonClassRetransformation(Class<?>... target);
+ public static native void setPopRetransformations(boolean pop);
+ public static native void popTransformationFor(String name);
+ public static native void enableCommonRetransformation(boolean enable);
+ public static native void addCommonTransformationResult(String target_name,
+ byte[] class_bytes,
+ byte[] dex_bytes);
+}
diff --git a/test/1950-unprepared-transform/unprepared_transform.cc b/test/1950-unprepared-transform/unprepared_transform.cc
new file mode 100644
index 0000000000..620ede887f
--- /dev/null
+++ b/test/1950-unprepared-transform/unprepared_transform.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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 <cstdio>
+#include <iostream>
+#include <mutex>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1950UnpreparedTransform {
+
+jclass kMainClass = nullptr;
+jmethodID kPrepareFunc = nullptr;
+
+extern "C" JNIEXPORT void ClassLoadCallback(jvmtiEnv* jvmti ATTRIBUTE_UNUSED,
+ JNIEnv* env,
+ jthread thr ATTRIBUTE_UNUSED,
+ jclass klass) {
+ env->CallStaticVoidMethod(kMainClass, kPrepareFunc, klass);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_clearClassLoadHook(
+ JNIEnv* env, jclass main ATTRIBUTE_UNUSED, jthread thr) {
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+ JVMTI_EVENT_CLASS_LOAD,
+ thr));
+}
+extern "C" JNIEXPORT void JNICALL Java_Main_setupClassLoadHook(
+ JNIEnv* env, jclass main, jthread thr) {
+ kMainClass = reinterpret_cast<jclass>(env->NewGlobalRef(main));
+ kPrepareFunc = env->GetStaticMethodID(main, "doClassLoad", "(Ljava/lang/Class;)V");
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ current_callbacks.ClassLoad = ClassLoadCallback;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(
+ &current_callbacks, sizeof(current_callbacks)))) {
+ return;
+ }
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_LOAD,
+ thr));
+}
+
+} // namespace Test1950UnpreparedTransform
+} // namespace art
diff --git a/test/Android.bp b/test/Android.bp
index 0c1edcaab8..bd13de2fa9 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -277,6 +277,7 @@ art_cc_defaults {
"1942-suspend-raw-monitor-exit/native_suspend_monitor.cc",
"1943-suspend-raw-monitor-wait/native_suspend_monitor.cc",
"1946-list-descriptors/descriptors.cc",
+ "1950-unprepared-transform/unprepared_transform.cc",
],
// Use NDK-compatible headers for ctstiagent.
header_libs: [
diff --git a/test/ti-agent/breakpoint_helper.cc b/test/ti-agent/breakpoint_helper.cc
index 78aab4376f..db4ea61f1c 100644
--- a/test/ti-agent/breakpoint_helper.cc
+++ b/test/ti-agent/breakpoint_helper.cc
@@ -172,10 +172,11 @@ extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch(
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.Breakpoint = breakpointCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.Breakpoint = breakpointCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
if (JvmtiErrorToException(env,
diff --git a/test/ti-agent/exceptions_helper.cc b/test/ti-agent/exceptions_helper.cc
index 74f7ecc881..e56c39b9eb 100644
--- a/test/ti-agent/exceptions_helper.cc
+++ b/test/ti-agent/exceptions_helper.cc
@@ -147,11 +147,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Exceptions_setupExceptionTracing(
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.Exception = exceptionCB;
- cb.ExceptionCatch = exceptionCatchCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.Exception = exceptionCB;
+ current_callbacks.ExceptionCatch = exceptionCatchCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
}
diff --git a/test/ti-agent/frame_pop_helper.cc b/test/ti-agent/frame_pop_helper.cc
index 4571032ce6..f39e1854bc 100644
--- a/test/ti-agent/frame_pop_helper.cc
+++ b/test/ti-agent/frame_pop_helper.cc
@@ -90,10 +90,11 @@ extern "C" JNIEXPORT void JNICALL Java_art_FramePop_enableFramePopEvent(
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.FramePop = framePopCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.FramePop = framePopCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
JvmtiErrorToException(env,
diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc
index 81d4cdc3ae..4434baf01c 100644
--- a/test/ti-agent/monitors_helper.cc
+++ b/test/ti-agent/monitors_helper.cc
@@ -182,13 +182,14 @@ extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents(
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.MonitorContendedEnter = monitorEnterCB;
- cb.MonitorContendedEntered = monitorEnteredCB;
- cb.MonitorWait = monitorWaitCB;
- cb.MonitorWaited = monitorWaitedCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.MonitorContendedEnter = monitorEnterCB;
+ current_callbacks.MonitorContendedEntered = monitorEnteredCB;
+ current_callbacks.MonitorWait = monitorWaitCB;
+ current_callbacks.MonitorWaited = monitorWaitedCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
if (JvmtiErrorToException(env,
diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc
index 76371de20a..0e4b1bdd8c 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -381,10 +381,8 @@ static void SetupCommonRedefine() {
static void SetupCommonRetransform() {
SetStandardCapabilities(jvmti_env);
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
- jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb));
+ current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
+ jvmtiError res = jvmti_env->SetEventCallbacks(&current_callbacks, sizeof(current_callbacks));
CHECK_EQ(res, JVMTI_ERROR_NONE);
common_retransform::gTransformations.clear();
}
@@ -397,10 +395,8 @@ static void SetupCommonTransform() {
jvmti_env->AddCapabilities(&caps);
// Use the same callback as the retransform test.
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
- jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb));
+ current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
+ jvmtiError res = jvmti_env->SetEventCallbacks(&current_callbacks, sizeof(current_callbacks));
CHECK_EQ(res, JVMTI_ERROR_NONE);
common_retransform::gTransformations.clear();
}
diff --git a/test/ti-agent/test_env.cc b/test/ti-agent/test_env.cc
index cf47f22b03..49313876c9 100644
--- a/test/ti-agent/test_env.cc
+++ b/test/ti-agent/test_env.cc
@@ -19,6 +19,7 @@
namespace art {
jvmtiEnv* jvmti_env = nullptr;
+jvmtiEventCallbacks current_callbacks = {};
static bool gRuntimeIsJVM = false;
diff --git a/test/ti-agent/test_env.h b/test/ti-agent/test_env.h
index 2eb631c36c..a8a9f57097 100644
--- a/test/ti-agent/test_env.h
+++ b/test/ti-agent/test_env.h
@@ -23,6 +23,11 @@ namespace art {
extern jvmtiEnv* jvmti_env;
+// This is a jvmtiEventCallbacks struct that is used by all common ti-agent code whenever it calls
+// SetEventCallbacks. This can be used by single tests to add additional event callbacks without
+// being unable to use the rest of the ti-agent support code.
+extern jvmtiEventCallbacks current_callbacks;
+
bool IsJVM();
void SetJVM(bool b);
diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc
index b590175d77..11e1c15757 100644
--- a/test/ti-agent/trace_helper.cc
+++ b/test/ti-agent/trace_helper.cc
@@ -513,17 +513,18 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing2(
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.MethodEntry = methodEntryCB;
- cb.MethodExit = methodExitCB;
- cb.FieldAccess = fieldAccessCB;
- cb.FieldModification = fieldModificationCB;
- cb.ClassPrepare = classPrepareCB;
- cb.SingleStep = singleStepCB;
- cb.ThreadStart = threadStartCB;
- cb.ThreadEnd = threadEndCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.MethodEntry = methodEntryCB;
+ current_callbacks.MethodExit = methodExitCB;
+ current_callbacks.FieldAccess = fieldAccessCB;
+ current_callbacks.FieldModification = fieldModificationCB;
+ current_callbacks.ClassPrepare = classPrepareCB;
+ current_callbacks.SingleStep = singleStepCB;
+ current_callbacks.ThreadStart = threadStartCB;
+ current_callbacks.ThreadEnd = threadEndCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
if (enter != nullptr &&
diff --git a/tools/dexanalyze/Android.bp b/tools/dexanalyze/Android.bp
new file mode 100644
index 0000000000..2754e6445e
--- /dev/null
+++ b/tools/dexanalyze/Android.bp
@@ -0,0 +1,53 @@
+//
+// Copyright (C) 2018 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.
+//
+
+cc_defaults {
+ name: "dexanalyze-defaults",
+ defaults: ["art_defaults"],
+ host_supported: true,
+ srcs: [
+ "dexanalyze.cc",
+ "dexanalyze_experiments.cc",
+ ],
+ target: {
+ android: {
+ shared_libs: ["libcutils"],
+ },
+ },
+ header_libs: [
+ "art_cmdlineparser_headers",
+ ],
+}
+
+art_cc_binary {
+ name: "dexanalyze",
+ defaults: ["dexanalyze-defaults"],
+ shared_libs: [
+ "libdexfile",
+ "libbase",
+ ],
+}
+
+art_cc_test {
+ name: "art_dexanalyze_tests",
+ required: ["dexanalyze"],
+ defaults: [
+ "art_gtest_defaults",
+ ],
+ srcs: [
+ "dexanalyze_test.cc",
+ ],
+}
diff --git a/tools/dexanalyze/dexanalyze.cc b/tools/dexanalyze/dexanalyze.cc
new file mode 100644
index 0000000000..a5f647cc56
--- /dev/null
+++ b/tools/dexanalyze/dexanalyze.cc
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2018 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 <cstdint>
+#include <set>
+#include <sstream>
+
+#include <android-base/file.h>
+
+#include "dexanalyze_experiments.h"
+#include "dex/code_item_accessors-inl.h"
+#include "dex/dex_file.h"
+#include "dex/dex_file_loader.h"
+#include "dex/dex_instruction-inl.h"
+
+namespace art {
+
+class DexAnalyze {
+ static const int kExitCodeUsageError = 1;
+
+ static int Usage(char** argv) {
+ LOG(ERROR)
+ << "Usage " << argv[0] << " [options] <dex files>\n"
+ << " [options] is a combination of the following\n"
+ << " -count_indices (Count dex indices accessed from code items)\n"
+ << " -i (Ignore DEX checksum failure)\n"
+ << " -a (Run all experiments)\n"
+ << " -d (Dump on per DEX basis)\n";
+ return kExitCodeUsageError;
+ }
+
+ struct Options {
+ int Parse(int argc, char** argv) {
+ int i;
+ for (i = 1; i < argc; ++i) {
+ const std::string arg = argv[i];
+ if (arg == "-i") {
+ verify_checksum_ = false;
+ } else if (arg == "-a") {
+ run_all_experiments_ = true;
+ } else if (arg == "-count-indices") {
+ exp_count_indices_ = true;
+ } else if (arg == "-d") {
+ dump_per_input_dex_ = true;
+ } else if (!arg.empty() && arg[0] == '-') {
+ return Usage(argv);
+ } else {
+ break;
+ }
+ }
+ filenames_.insert(filenames_.end(), argv + i, argv + argc);
+ if (filenames_.empty()) {
+ return Usage(argv);
+ }
+ return 0;
+ }
+
+ bool verify_checksum_ = true;
+ bool run_dex_file_verifier_ = true;
+ bool dump_per_input_dex_ = false;
+ bool exp_count_indices_ = false;
+ bool run_all_experiments_ = false;
+ std::vector<std::string> filenames_;
+ };
+
+ class Analysis {
+ public:
+ explicit Analysis(const Options* options) : options_(options) {
+ if (options->run_all_experiments_ || options->exp_count_indices_) {
+ experiments_.emplace_back(new CountDexIndices);
+ }
+ }
+
+ bool ProcessDexFile(const DexFile& dex_file) {
+ for (std::unique_ptr<Experiment>& experiment : experiments_) {
+ experiment->ProcessDexFile(dex_file);
+ }
+ ++dex_count_;
+ return true;
+ }
+
+ void Dump(std::ostream& os) {
+ for (std::unique_ptr<Experiment>& experiment : experiments_) {
+ experiment->Dump(os);
+ }
+ }
+
+ const Options* const options_;
+ std::vector<std::unique_ptr<Experiment>> experiments_;
+ size_t dex_count_ = 0;
+ };
+
+ public:
+ static int Run(int argc, char** argv) {
+ Options options;
+ int result = options.Parse(argc, argv);
+ if (result != 0) {
+ return result;
+ }
+
+ std::string error_msg;
+ Analysis cumulative(&options);
+ for (const std::string& filename : options.filenames_) {
+ std::string content;
+ // TODO: once added, use an api to android::base to read a std::vector<uint8_t>.
+ if (!android::base::ReadFileToString(filename.c_str(), &content)) {
+ LOG(ERROR) << "ReadFileToString failed for " + filename << std::endl;
+ continue;
+ }
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ const DexFileLoader dex_file_loader;
+ if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()),
+ content.size(),
+ filename.c_str(),
+ options.run_dex_file_verifier_,
+ options.verify_checksum_,
+ &error_msg,
+ &dex_files)) {
+ LOG(ERROR) << "OpenAll failed for " + filename << " with " << error_msg << std::endl;
+ continue;
+ }
+ for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
+ if (options.dump_per_input_dex_) {
+ Analysis current(&options);
+ if (!current.ProcessDexFile(*dex_file)) {
+ LOG(ERROR) << "Failed to process " << filename << " with error " << error_msg;
+ continue;
+ }
+ LOG(INFO) << "Analysis for " << dex_file->GetLocation() << std::endl;
+ current.Dump(LOG_STREAM(INFO));
+ }
+ cumulative.ProcessDexFile(*dex_file);
+ }
+ }
+ LOG(INFO) << "Cumulative analysis for " << cumulative.dex_count_ << " DEX files" << std::endl;
+ cumulative.Dump(LOG_STREAM(INFO));
+ return 0;
+ }
+};
+
+} // namespace art
+
+int main(int argc, char** argv) {
+ android::base::SetLogger(android::base::StderrLogger);
+ return art::DexAnalyze::Run(argc, argv);
+}
+
diff --git a/tools/dexanalyze/dexanalyze_experiments.cc b/tools/dexanalyze/dexanalyze_experiments.cc
new file mode 100644
index 0000000000..e1f119df59
--- /dev/null
+++ b/tools/dexanalyze/dexanalyze_experiments.cc
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 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 "dexanalyze_experiments.h"
+#include "dex/code_item_accessors-inl.h"
+#include "dex/dex_instruction-inl.h"
+#include "dex/standard_dex_file.h"
+
+namespace art {
+
+void CountDexIndices::ProcessDexFile(const DexFile& dex_file) {
+ num_string_ids_ += dex_file.NumStringIds();
+ num_method_ids_ += dex_file.NumMethodIds();
+ num_field_ids_ += dex_file.NumFieldIds();
+ num_type_ids_ += dex_file.NumTypeIds();
+ num_class_defs_ += dex_file.NumClassDefs();
+ for (size_t class_def_index = 0; class_def_index < dex_file.NumClassDefs(); ++class_def_index) {
+ const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
+ const uint8_t* class_data = dex_file.GetClassData(class_def);
+ if (class_data == nullptr) {
+ continue;
+ }
+ ClassDataItemIterator it(dex_file, class_data);
+ it.SkipAllFields();
+ std::set<size_t> unique_method_ids;
+ std::set<size_t> unique_string_ids;
+ while (it.HasNextMethod()) {
+ const DexFile::CodeItem* code_item = it.GetMethodCodeItem();
+ if (code_item != nullptr) {
+ CodeItemInstructionAccessor instructions(dex_file, code_item);
+ const uint16_t* code_ptr = instructions.Insns();
+ dex_code_bytes_ += instructions.InsnsSizeInCodeUnits() * sizeof(code_ptr[0]);
+ for (const DexInstructionPcPair& inst : instructions) {
+ switch (inst->Opcode()) {
+ case Instruction::CONST_STRING: {
+ const dex::StringIndex string_index(inst->VRegB_21c());
+ unique_string_ids.insert(string_index.index_);
+ ++num_string_ids_from_code_;
+ break;
+ }
+ case Instruction::CONST_STRING_JUMBO: {
+ const dex::StringIndex string_index(inst->VRegB_31c());
+ unique_string_ids.insert(string_index.index_);
+ ++num_string_ids_from_code_;
+ break;
+ }
+ // Invoke cases.
+ case Instruction::INVOKE_VIRTUAL:
+ case Instruction::INVOKE_VIRTUAL_RANGE: {
+ bool is_range = (inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE);
+ uint32_t method_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
+ if (dex_file.GetMethodId(method_idx).class_idx_ == class_def.class_idx_) {
+ ++same_class_virtual_;
+ } else {
+ ++other_class_virtual_;
+ unique_method_ids.insert(method_idx);
+ }
+ break;
+ }
+ case Instruction::INVOKE_DIRECT:
+ case Instruction::INVOKE_DIRECT_RANGE: {
+ bool is_range = (inst->Opcode() == Instruction::INVOKE_DIRECT_RANGE);
+ uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
+ if (dex_file.GetMethodId(method_idx).class_idx_ == class_def.class_idx_) {
+ ++same_class_direct_;
+ } else {
+ ++other_class_direct_;
+ unique_method_ids.insert(method_idx);
+ }
+ break;
+ }
+ case Instruction::INVOKE_STATIC:
+ case Instruction::INVOKE_STATIC_RANGE: {
+ bool is_range = (inst->Opcode() == Instruction::INVOKE_STATIC_RANGE);
+ uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
+ if (dex_file.GetMethodId(method_idx).class_idx_ == class_def.class_idx_) {
+ ++same_class_static_;
+ } else {
+ ++other_class_static_;
+ unique_method_ids.insert(method_idx);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ it.Next();
+ }
+ DCHECK(!it.HasNext());
+ total_unique_method_idx_ += unique_method_ids.size();
+ total_unique_string_ids_ += unique_string_ids.size();
+ }
+}
+
+void CountDexIndices::Dump(std::ostream& os) const {
+ os << "Num string ids: " << num_string_ids_ << "\n";
+ os << "Num method ids: " << num_method_ids_ << "\n";
+ os << "Num field ids: " << num_field_ids_ << "\n";
+ os << "Num type ids: " << num_type_ids_ << "\n";
+ os << "Num class defs: " << num_class_defs_ << "\n";
+ os << "Same class direct: " << same_class_direct_ << "\n";
+ os << "Other class direct: " << other_class_direct_ << "\n";
+ os << "Same class virtual: " << same_class_virtual_ << "\n";
+ os << "Other class virtual: " << other_class_virtual_ << "\n";
+ os << "Same class static: " << same_class_static_ << "\n";
+ os << "Other class static: " << other_class_static_ << "\n";
+ os << "Num strings accessed from code: " << num_string_ids_from_code_ << "\n";
+ os << "Unique(per class) method ids accessed from code: " << total_unique_method_idx_ << "\n";
+ os << "Unique(per class) string ids accessed from code: " << total_unique_string_ids_ << "\n";
+ size_t same_class_total = same_class_direct_ + same_class_virtual_ + same_class_static_;
+ size_t other_class_total = other_class_direct_ + other_class_virtual_ + other_class_static_;
+ os << "Same class invoke: " << same_class_total << "\n";
+ os << "Other class invoke: " << other_class_total << "\n";
+ os << "Invokes from code: " << (same_class_total + other_class_total) << "\n";
+}
+
+} // namespace art
+
diff --git a/tools/dexanalyze/dexanalyze_experiments.h b/tools/dexanalyze/dexanalyze_experiments.h
new file mode 100644
index 0000000000..5d0f51b821
--- /dev/null
+++ b/tools/dexanalyze/dexanalyze_experiments.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_TOOLS_DEXANALYZE_DEXANALYZE_EXPERIMENTS_H_
+#define ART_TOOLS_DEXANALYZE_DEXANALYZE_EXPERIMENTS_H_
+
+#include <iosfwd>
+#include <set>
+
+namespace art {
+
+class DexFile;
+
+// An experiment a stateful visitor that runs on dex files. Results are cumulative.
+class Experiment {
+ public:
+ virtual ~Experiment() {}
+ virtual void ProcessDexFile(const DexFile& dex_file) = 0;
+ virtual void Dump(std::ostream& os) const = 0;
+};
+
+// Count numbers of dex indices.
+class CountDexIndices : public Experiment {
+ public:
+ void ProcessDexFile(const DexFile& dex_file);
+
+ void Dump(std::ostream& os) const;
+
+ private:
+ // Total string ids loaded from dex code.
+ size_t num_string_ids_from_code_ = 0;
+ size_t total_unique_method_idx_ = 0;
+ size_t total_unique_string_ids_ = 0;
+
+ // Other dex ids.
+ size_t dex_code_bytes_ = 0;
+ size_t num_string_ids_ = 0;
+ size_t num_method_ids_ = 0;
+ size_t num_field_ids_ = 0;
+ size_t num_type_ids_ = 0;
+ size_t num_class_defs_ = 0;
+
+ // Invokes
+ size_t same_class_direct_ = 0;
+ size_t other_class_direct_ = 0;
+ size_t same_class_virtual_ = 0;
+ size_t other_class_virtual_ = 0;
+ size_t same_class_static_ = 0;
+ size_t other_class_static_ = 0;
+};
+
+} // namespace art
+
+#endif // ART_TOOLS_DEXANALYZE_DEXANALYZE_EXPERIMENTS_H_
+
diff --git a/tools/dexanalyze/dexanalyze_test.cc b/tools/dexanalyze/dexanalyze_test.cc
new file mode 100644
index 0000000000..c9b8f53d24
--- /dev/null
+++ b/tools/dexanalyze/dexanalyze_test.cc
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common_runtime_test.h"
+#include "exec_utils.h"
+
+namespace art {
+
+class DexAnalyzeTest : public CommonRuntimeTest {
+ public:
+ std::string GetDexAnalyzePath() {
+ return GetTestAndroidRoot() + "/bin/dexanalyze";
+ }
+
+ void DexAnalyzeExec(const std::vector<std::string>& args, bool expect_success) {
+ std::string binary = GetDexAnalyzePath();
+ CHECK(OS::FileExists(binary.c_str())) << binary << " should be a valid file path";
+ std::vector<std::string> argv;
+ argv.push_back(binary);
+ argv.insert(argv.end(), args.begin(), args.end());
+ std::string error_msg;
+ ASSERT_EQ(::art::Exec(argv, &error_msg), expect_success) << error_msg;
+ }
+};
+
+TEST_F(DexAnalyzeTest, TestAnalyzeMultidex) {
+ DexAnalyzeExec({ "-a", GetTestDexFileName("MultiDex") }, /*expect_success*/ true);
+}
+
+TEST_F(DexAnalyzeTest, TestInvalidArg) {
+ DexAnalyzeExec({ "-invalid-option" }, /*expect_success*/ false);
+}
+
+} // namespace art