diff options
54 files changed, 2633 insertions, 382 deletions
diff --git a/Android.mk b/Android.mk index 0402513cb5..7eb0bf926e 100644 --- a/Android.mk +++ b/Android.mk @@ -81,7 +81,6 @@ include $(art_path)/tools/Android.mk include $(art_path)/tools/ahat/Android.mk include $(art_path)/tools/dexfuzz/Android.mk include $(art_path)/libart_fake/Android.mk -include $(art_path)/test/Android.run-test-jvmti-java-library.mk ART_HOST_DEPENDENCIES := \ $(ART_HOST_EXECUTABLES) \ @@ -380,11 +379,12 @@ LOCAL_REQUIRED_MODULES += libart_fake # * We will never add them if PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD = false. # * We will always add them if PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD = true. # * Otherwise, we will add them by default to userdebug and eng builds. -ifneq (false,$(PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD)) -ifneq (,$(filter userdebug eng,$(PRODUCT_TARGET_BUILD_VARIANT))) - PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD := true +art_target_include_debug_build := $(PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD) +ifneq (false,$(art_target_include_debug_build)) +ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) + art_target_include_debug_build := true endif -ifeq (true,$(PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD)) +ifeq (true,$(art_target_include_debug_build)) LOCAL_REQUIRED_MODULES += \ libartd \ libartd-compiler \ diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..7297a14da2 --- /dev/null +++ b/OWNERS @@ -0,0 +1,3 @@ +ngeoffray@google.com +sehr@google.com +* diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index c87abe5664..b6ffcc54f7 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -30,6 +30,10 @@ GTEST_DEX_DIRECTORIES := \ ErroneousA \ ErroneousB \ ErroneousInit \ + ForClassLoaderA \ + ForClassLoaderB \ + ForClassLoaderC \ + ForClassLoaderD \ ExceptionHandle \ GetMethodSignature \ ImageLayoutA \ @@ -99,7 +103,7 @@ $(ART_TEST_TARGET_GTEST_VerifierDepsMulti_DEX): $(ART_TEST_GTEST_VerifierDepsMul ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces -ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode +ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit ForClassLoaderA ForClassLoaderB ForClassLoaderC ForClassLoaderD Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode 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 diff --git a/compiler/Android.bp b/compiler/Android.bp index 05c4553251..761f53fa5a 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -356,6 +356,7 @@ art_cc_test { "optimizing/live_interval_test.cc", "optimizing/loop_optimization_test.cc", "optimizing/nodes_test.cc", + "optimizing/nodes_vector_test.cc", "optimizing/parallel_move_test.cc", "optimizing/pretty_printer_test.cc", "optimizing/reference_type_propagation_test.cc", @@ -429,13 +430,20 @@ art_cc_test { shared_libs: [ "libartd-compiler", - "libartd-simulator", "libvixld-arm", "libvixld-arm64", "libbacktrace", "libnativeloader", ], + + target: { + host: { + shared_libs: [ + "libartd-simulator", + ], + }, + }, } art_cc_test { diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index f834f30b84..bb64755c9e 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -87,6 +87,10 @@ static constexpr bool kTimeCompileMethod = !kIsDebugBuild; // Print additional info during profile guided compilation. static constexpr bool kDebugProfileGuidedCompilation = false; +// Max encoded fields allowed for initializing app image. Hardcode the number for now +// because 5000 should be large enough. +static constexpr uint32_t kMaxEncodedFields = 5000; + static double Percentage(size_t x, size_t y) { return 100.0 * (static_cast<double>(x)) / (static_cast<double>(x + y)); } @@ -2273,11 +2277,17 @@ class InitializeClassVisitor : public CompilationVisitor { } // Otherwise it's in app image but superclasses can't be initialized, no need to proceed. old_status = klass->GetStatus(); + + bool too_many_encoded_fields = false; + if (!is_boot_image && klass->NumStaticFields() > kMaxEncodedFields) { + too_many_encoded_fields = true; + } // If the class was not initialized, we can proceed to see if we can initialize static - // fields. + // fields. Limit the max number of encoded fields. if (!klass->IsInitialized() && (is_app_image || is_boot_image) && is_superclass_initialized && + !too_many_encoded_fields && manager_->GetCompiler()->IsImageClass(descriptor)) { bool can_init_static_fields = false; if (is_boot_image) { @@ -2415,30 +2425,6 @@ class InitializeClassVisitor : public CompilationVisitor { } } - bool NoPotentialInternStrings(Handle<mirror::Class> klass, - Handle<mirror::ClassLoader>* class_loader) - REQUIRES_SHARED(Locks::mutator_lock_) { - StackHandleScope<1> hs(Thread::Current()); - Handle<mirror::DexCache> h_dex_cache = hs.NewHandle(klass->GetDexCache()); - const DexFile* dex_file = h_dex_cache->GetDexFile(); - const DexFile::ClassDef* class_def = klass->GetClassDef(); - annotations::RuntimeEncodedStaticFieldValueIterator value_it(*dex_file, - &h_dex_cache, - class_loader, - manager_->GetClassLinker(), - *class_def); - - const auto jString = annotations::RuntimeEncodedStaticFieldValueIterator::kString; - for ( ; value_it.HasNext(); value_it.Next()) { - if (value_it.GetValueType() == jString) { - // We don't want cache the static encoded strings which is a potential intern. - return false; - } - } - - return true; - } - bool ResolveTypesOfMethods(Thread* self, ArtMethod* m) REQUIRES_SHARED(Locks::mutator_lock_) { auto rtn_type = m->GetReturnType(true); // return value is discarded because resolve will be done internally. @@ -2568,7 +2554,7 @@ class InitializeClassVisitor : public CompilationVisitor { } } - return NoPotentialInternStrings(klass, class_loader); + return true; } const ParallelCompilationManager* const manager_; diff --git a/compiler/optimizing/nodes_vector.h b/compiler/optimizing/nodes_vector.h index 5dbe29b4fa..dc522a463e 100644 --- a/compiler/optimizing/nodes_vector.h +++ b/compiler/optimizing/nodes_vector.h @@ -46,6 +46,10 @@ class Alignment { return "ALIGN(" + std::to_string(base_) + "," + std::to_string(offset_) + ")"; } + bool operator==(const Alignment& other) const { + return base_ == other.base_ && offset_ == other.offset_; + } + private: size_t base_; size_t offset_; @@ -96,6 +100,13 @@ class HVecOperation : public HVariableInputSizeInstruction { return GetPackedField<TypeField>(); } + bool CanBeMoved() const OVERRIDE { return true; } + + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecOperation* o = other->AsVecOperation(); + return GetVectorLength() == o->GetVectorLength() && GetPackedType() == o->GetPackedType(); + } + DECLARE_ABSTRACT_INSTRUCTION(VecOperation); protected: @@ -189,6 +200,11 @@ class HVecMemoryOperation : public HVecOperation { HInstruction* GetArray() const { return InputAt(0); } HInstruction* GetIndex() const { return InputAt(1); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecMemoryOperation* o = other->AsVecMemoryOperation(); + return HVecOperation::InstructionDataEquals(o) && GetAlignment() == o->GetAlignment(); + } + DECLARE_ABSTRACT_INSTRUCTION(VecMemoryOperation); private: @@ -378,6 +394,13 @@ class HVecHalvingAdd FINAL : public HVecBinaryOperation { bool IsUnsigned() const { return GetPackedFlag<kFieldHAddIsUnsigned>(); } bool IsRounded() const { return GetPackedFlag<kFieldHAddIsRounded>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecHalvingAdd* o = other->AsVecHalvingAdd(); + return HVecOperation::InstructionDataEquals(o) && + IsUnsigned() == o->IsUnsigned() && + IsRounded() == o->IsRounded(); + } + DECLARE_INSTRUCTION(VecHalvingAdd); private: @@ -466,6 +489,11 @@ class HVecMin FINAL : public HVecBinaryOperation { bool IsUnsigned() const { return GetPackedFlag<kFieldMinOpIsUnsigned>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecMin* o = other->AsVecMin(); + return HVecOperation::InstructionDataEquals(o) && IsUnsigned() == o->IsUnsigned(); + } + DECLARE_INSTRUCTION(VecMin); private: @@ -496,6 +524,11 @@ class HVecMax FINAL : public HVecBinaryOperation { bool IsUnsigned() const { return GetPackedFlag<kFieldMaxOpIsUnsigned>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecMax* o = other->AsVecMax(); + return HVecOperation::InstructionDataEquals(o) && IsUnsigned() == o->IsUnsigned(); + } + DECLARE_INSTRUCTION(VecMax); private: @@ -694,10 +727,9 @@ class HVecMultiplyAccumulate FINAL : public HVecOperation { static constexpr int kInputMulLeftIndex = 1; static constexpr int kInputMulRightIndex = 2; - bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { - return op_kind_ == other->AsVecMultiplyAccumulate()->op_kind_; + const HVecMultiplyAccumulate* o = other->AsVecMultiplyAccumulate(); + return HVecOperation::InstructionDataEquals(o) && GetOpKind() == o->GetOpKind(); } InstructionKind GetOpKind() const { return op_kind_; } @@ -732,10 +764,16 @@ class HVecLoad FINAL : public HVecMemoryOperation { SetRawInputAt(1, index); SetPackedFlag<kFieldIsStringCharAt>(is_string_char_at); } - DECLARE_INSTRUCTION(VecLoad); bool IsStringCharAt() const { return GetPackedFlag<kFieldIsStringCharAt>(); } + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HVecLoad* o = other->AsVecLoad(); + return HVecMemoryOperation::InstructionDataEquals(o) && IsStringCharAt() == o->IsStringCharAt(); + } + + DECLARE_INSTRUCTION(VecLoad); + private: // Additional packed bits. static constexpr size_t kFieldIsStringCharAt = HVecOperation::kNumberOfVectorOpPackedBits; @@ -767,7 +805,11 @@ class HVecStore FINAL : public HVecMemoryOperation { SetRawInputAt(1, index); SetRawInputAt(2, value); } + + bool CanBeMoved() const OVERRIDE { return false; } + DECLARE_INSTRUCTION(VecStore); + private: DISALLOW_COPY_AND_ASSIGN(HVecStore); }; diff --git a/compiler/optimizing/nodes_vector_test.cc b/compiler/optimizing/nodes_vector_test.cc new file mode 100644 index 0000000000..d33f8e5d2e --- /dev/null +++ b/compiler/optimizing/nodes_vector_test.cc @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/arena_allocator.h" +#include "nodes.h" +#include "optimizing_unit_test.h" + +namespace art { + +/** + * Fixture class for testing vector nodes. + */ +class NodesVectorTest : public CommonCompilerTest { + public: + NodesVectorTest() + : pool_(), + allocator_(&pool_), + graph_(CreateGraph(&allocator_)) { + BuildGraph(); + } + + ~NodesVectorTest() { } + + void BuildGraph() { + graph_->SetNumberOfVRegs(1); + entry_block_ = new (&allocator_) HBasicBlock(graph_); + exit_block_ = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(entry_block_); + graph_->AddBlock(exit_block_); + graph_->SetEntryBlock(entry_block_); + graph_->SetExitBlock(exit_block_); + parameter_ = new (&allocator_) HParameterValue(graph_->GetDexFile(), + dex::TypeIndex(0), + 0, + Primitive::kPrimInt); + entry_block_->AddInstruction(parameter_); + } + + // General building fields. + ArenaPool pool_; + ArenaAllocator allocator_; + HGraph* graph_; + + HBasicBlock* entry_block_; + HBasicBlock* exit_block_; + + HInstruction* parameter_; +}; + +// +// The actual vector nodes tests. +// + +TEST(NodesVector, Alignment) { + EXPECT_TRUE(Alignment(1, 0).IsAlignedAt(1)); + EXPECT_FALSE(Alignment(1, 0).IsAlignedAt(2)); + + EXPECT_TRUE(Alignment(2, 0).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(2, 1).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(2, 0).IsAlignedAt(2)); + EXPECT_FALSE(Alignment(2, 1).IsAlignedAt(2)); + EXPECT_FALSE(Alignment(2, 0).IsAlignedAt(4)); + EXPECT_FALSE(Alignment(2, 1).IsAlignedAt(4)); + + EXPECT_TRUE(Alignment(4, 0).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(4, 2).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(4, 0).IsAlignedAt(2)); + EXPECT_TRUE(Alignment(4, 2).IsAlignedAt(2)); + EXPECT_TRUE(Alignment(4, 0).IsAlignedAt(4)); + EXPECT_FALSE(Alignment(4, 2).IsAlignedAt(4)); + EXPECT_FALSE(Alignment(4, 0).IsAlignedAt(8)); + EXPECT_FALSE(Alignment(4, 2).IsAlignedAt(8)); + + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(1)); + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(2)); + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(4)); + EXPECT_TRUE(Alignment(16, 8).IsAlignedAt(8)); + EXPECT_TRUE(Alignment(16, 0).IsAlignedAt(16)); + EXPECT_FALSE(Alignment(16, 1).IsAlignedAt(16)); + EXPECT_FALSE(Alignment(16, 7).IsAlignedAt(16)); + EXPECT_FALSE(Alignment(16, 0).IsAlignedAt(32)); +} + +TEST(NodesVector, AlignmentEQ) { + EXPECT_TRUE(Alignment(2, 0) == Alignment(2, 0)); + EXPECT_TRUE(Alignment(2, 1) == Alignment(2, 1)); + EXPECT_TRUE(Alignment(4, 0) == Alignment(4, 0)); + EXPECT_TRUE(Alignment(4, 2) == Alignment(4, 2)); + + EXPECT_FALSE(Alignment(4, 0) == Alignment(2, 0)); + EXPECT_FALSE(Alignment(4, 0) == Alignment(4, 1)); + EXPECT_FALSE(Alignment(4, 0) == Alignment(8, 0)); +} + +TEST(NodesVector, AlignmentString) { + EXPECT_STREQ("ALIGN(1,0)", Alignment(1, 0).ToString().c_str()); + + EXPECT_STREQ("ALIGN(2,0)", Alignment(2, 0).ToString().c_str()); + EXPECT_STREQ("ALIGN(2,1)", Alignment(2, 1).ToString().c_str()); + + EXPECT_STREQ("ALIGN(16,0)", Alignment(16, 0).ToString().c_str()); + EXPECT_STREQ("ALIGN(16,1)", Alignment(16, 1).ToString().c_str()); + EXPECT_STREQ("ALIGN(16,8)", Alignment(16, 8).ToString().c_str()); + EXPECT_STREQ("ALIGN(16,9)", Alignment(16, 9).ToString().c_str()); +} + +TEST_F(NodesVectorTest, VectorOperationProperties) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + HVecOperation* v1 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + HVecOperation* v2 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 2); + HVecOperation* v3 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimShort, 4); + HVecOperation* v4 = new (&allocator_) + HVecStore(&allocator_, parameter_, parameter_, v0, Primitive::kPrimInt, 4); + + EXPECT_TRUE(v0->Equals(v0)); + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + EXPECT_TRUE(v4->Equals(v4)); + + EXPECT_TRUE(v0->Equals(v1)); + EXPECT_FALSE(v0->Equals(v2)); // different vector lengths + EXPECT_FALSE(v0->Equals(v3)); // different packed types + EXPECT_FALSE(v0->Equals(v4)); // different kinds + + EXPECT_TRUE(v1->Equals(v0)); // switch operands + EXPECT_FALSE(v4->Equals(v0)); + + EXPECT_EQ(4u, v0->GetVectorLength()); + EXPECT_EQ(4u, v1->GetVectorLength()); + EXPECT_EQ(2u, v2->GetVectorLength()); + EXPECT_EQ(4u, v3->GetVectorLength()); + EXPECT_EQ(4u, v4->GetVectorLength()); + + EXPECT_EQ(Primitive::kPrimDouble, v0->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v1->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v2->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v3->GetType()); + EXPECT_EQ(Primitive::kPrimDouble, v4->GetType()); + + EXPECT_EQ(Primitive::kPrimInt, v0->GetPackedType()); + EXPECT_EQ(Primitive::kPrimInt, v1->GetPackedType()); + EXPECT_EQ(Primitive::kPrimInt, v2->GetPackedType()); + EXPECT_EQ(Primitive::kPrimShort, v3->GetPackedType()); + EXPECT_EQ(Primitive::kPrimInt, v4->GetPackedType()); + + EXPECT_EQ(16u, v0->GetVectorNumberOfBytes()); + EXPECT_EQ(16u, v1->GetVectorNumberOfBytes()); + EXPECT_EQ(8u, v2->GetVectorNumberOfBytes()); + EXPECT_EQ(8u, v3->GetVectorNumberOfBytes()); + EXPECT_EQ(16u, v4->GetVectorNumberOfBytes()); + + EXPECT_TRUE(v0->CanBeMoved()); + EXPECT_TRUE(v1->CanBeMoved()); + EXPECT_TRUE(v2->CanBeMoved()); + EXPECT_TRUE(v3->CanBeMoved()); + EXPECT_FALSE(v4->CanBeMoved()); +} + +TEST_F(NodesVectorTest, VectorAlignmentAndStringCharAtMatterOnLoad) { + HVecLoad* v0 = new (&allocator_) + HVecLoad(&allocator_, parameter_, parameter_, Primitive::kPrimInt, 4, /*is_string_char_at*/ false); + HVecLoad* v1 = new (&allocator_) + HVecLoad(&allocator_, parameter_, parameter_, Primitive::kPrimInt, 4, /*is_string_char_at*/ false); + HVecLoad* v2 = new (&allocator_) + HVecLoad(&allocator_, parameter_, parameter_, Primitive::kPrimInt, 4, /*is_string_char_at*/ true); + + EXPECT_FALSE(v0->IsStringCharAt()); + EXPECT_FALSE(v1->IsStringCharAt()); + EXPECT_TRUE(v2->IsStringCharAt()); + + EXPECT_TRUE(v0->Equals(v0)); + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + + EXPECT_TRUE(v0->Equals(v1)); + EXPECT_FALSE(v0->Equals(v2)); + + EXPECT_TRUE(v0->GetAlignment() == Alignment(4, 0)); + EXPECT_TRUE(v1->GetAlignment() == Alignment(4, 0)); + EXPECT_TRUE(v2->GetAlignment() == Alignment(4, 0)); + + v1->SetAlignment(Alignment(8, 0)); + + EXPECT_TRUE(v1->GetAlignment() == Alignment(8, 0)); + + EXPECT_FALSE(v0->Equals(v1)); // no longer equal +} + +TEST_F(NodesVectorTest, VectorSignMattersOnMin) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecMin* v1 = new (&allocator_) + HVecMin(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true); + HVecMin* v2 = new (&allocator_) + HVecMin(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false); + HVecMin* v3 = new (&allocator_) + HVecMin(&allocator_, v0, v0, Primitive::kPrimInt, 2, /*is_unsigned*/ true); + + EXPECT_TRUE(v1->IsUnsigned()); + EXPECT_FALSE(v2->IsUnsigned()); + EXPECT_TRUE(v3->IsUnsigned()); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + + EXPECT_FALSE(v1->Equals(v2)); // different signs + EXPECT_FALSE(v1->Equals(v3)); // different vector lengths +} + +TEST_F(NodesVectorTest, VectorSignMattersOnMax) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecMax* v1 = new (&allocator_) + HVecMax(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true); + HVecMax* v2 = new (&allocator_) + HVecMax(&allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false); + HVecMax* v3 = new (&allocator_) + HVecMax(&allocator_, v0, v0, Primitive::kPrimInt, 2, /*is_unsigned*/ true); + + EXPECT_TRUE(v1->IsUnsigned()); + EXPECT_FALSE(v2->IsUnsigned()); + EXPECT_TRUE(v3->IsUnsigned()); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + + EXPECT_FALSE(v1->Equals(v2)); // different signs + EXPECT_FALSE(v1->Equals(v3)); // different vector lengths +} + +TEST_F(NodesVectorTest, VectorAttributesMatterOnHalvingAdd) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecHalvingAdd* v1 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true, /*is_rounded*/ true); + HVecHalvingAdd* v2 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ true, /*is_rounded*/ false); + HVecHalvingAdd* v3 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false, /*is_rounded*/ true); + HVecHalvingAdd* v4 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 4, /*is_unsigned*/ false, /*is_rounded*/ false); + HVecHalvingAdd* v5 = new (&allocator_) HVecHalvingAdd( + &allocator_, v0, v0, Primitive::kPrimInt, 2, /*is_unsigned*/ true, /*is_rounded*/ true); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + EXPECT_TRUE(v4->Equals(v4)); + EXPECT_TRUE(v5->Equals(v5)); + + EXPECT_TRUE(v1->IsUnsigned() && v1->IsRounded()); + EXPECT_TRUE(v2->IsUnsigned() && !v2->IsRounded()); + EXPECT_TRUE(!v3->IsUnsigned() && v3->IsRounded()); + EXPECT_TRUE(!v4->IsUnsigned() && !v4->IsRounded()); + EXPECT_TRUE(v5->IsUnsigned() && v5->IsRounded()); + + EXPECT_FALSE(v1->Equals(v2)); // different attributes + EXPECT_FALSE(v1->Equals(v3)); // different attributes + EXPECT_FALSE(v1->Equals(v4)); // different attributes + EXPECT_FALSE(v1->Equals(v5)); // different vector lengths +} + +TEST_F(NodesVectorTest, VectorOperationMattersOnMultiplyAccumulate) { + HVecOperation* v0 = new (&allocator_) + HVecReplicateScalar(&allocator_, parameter_, Primitive::kPrimInt, 4); + + HVecMultiplyAccumulate* v1 = new (&allocator_) + HVecMultiplyAccumulate(&allocator_, HInstruction::kAdd, v0, v0, v0, Primitive::kPrimInt, 4); + HVecMultiplyAccumulate* v2 = new (&allocator_) + HVecMultiplyAccumulate(&allocator_, HInstruction::kSub, v0, v0, v0, Primitive::kPrimInt, 4); + HVecMultiplyAccumulate* v3 = new (&allocator_) + HVecMultiplyAccumulate(&allocator_, HInstruction::kAdd, v0, v0, v0, Primitive::kPrimInt, 2); + + EXPECT_EQ(HInstruction::kAdd, v1->GetOpKind()); + EXPECT_EQ(HInstruction::kSub, v2->GetOpKind()); + EXPECT_EQ(HInstruction::kAdd, v3->GetOpKind()); + + EXPECT_TRUE(v1->Equals(v1)); + EXPECT_TRUE(v2->Equals(v2)); + EXPECT_TRUE(v3->Equals(v3)); + + EXPECT_FALSE(v1->Equals(v2)); // different operators + EXPECT_FALSE(v1->Equals(v3)); // different vector lengths +} + +} // namespace art diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h index 531bc98a0c..ed011d6771 100644 --- a/dexlayout/dexlayout.h +++ b/dexlayout/dexlayout.h @@ -58,7 +58,8 @@ class Options { bool show_section_headers_ = false; bool show_section_statistics_ = false; bool verbose_ = false; - bool verify_output_ = false; + // TODO: Set verify_output_ back to false by default. Was set to true for debugging b/62840842. + bool verify_output_ = true; bool visualize_pattern_ = false; OutputFormat output_format_ = kOutputPlain; const char* output_dex_directory_ = nullptr; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 7aab9de1ed..a19085f5b5 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -55,6 +55,7 @@ #include "gc_root-inl.h" #include "gc/accounting/card_table-inl.h" #include "gc/accounting/heap_bitmap-inl.h" +#include "gc/accounting/space_bitmap-inl.h" #include "gc/heap.h" #include "gc/scoped_gc_critical_section.h" #include "gc/space/image_space.h" @@ -88,6 +89,7 @@ #include "mirror/method_handles_lookup.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" +#include "mirror/object-refvisitor-inl.h" #include "mirror/proxy.h" #include "mirror/reference-inl.h" #include "mirror/stack_trace_element.h" @@ -1193,6 +1195,63 @@ class VerifyDeclaringClassVisitor : public ArtMethodVisitor { gc::accounting::HeapBitmap* const live_bitmap_; }; +class FixupInternVisitor { + public: + ALWAYS_INLINE ObjPtr<mirror::Object> TryInsertIntern(mirror::Object* obj) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (obj != nullptr && obj->IsString()) { + const auto intern = Runtime::Current()->GetInternTable()->InternStrong(obj->AsString()); + return intern; + } + return obj; + } + + ALWAYS_INLINE void VisitRootIfNonNull( + mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!root->IsNull()) { + VisitRoot(root); + } + } + + ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + root->Assign(TryInsertIntern(root->AsMirrorPtr())); + } + + // Visit Class Fields + ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> obj, + MemberOffset offset, + bool is_static ATTRIBUTE_UNUSED) const + REQUIRES_SHARED(Locks::mutator_lock_) { + // There could be overlap between ranges, we must avoid visiting the same reference twice. + // Avoid the class field since we already fixed it up in FixupClassVisitor. + if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) { + // Updating images, don't do a read barrier. + // Only string fields are fixed, don't do a verify. + mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>( + offset); + obj->SetFieldObject<false, false>(offset, TryInsertIntern(ref)); + } + } + + void operator()(ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED, + ObjPtr<mirror::Reference> ref) const + REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { + this->operator()(ref, mirror::Reference::ReferentOffset(), false); + } + + void operator()(mirror::Object* obj) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (obj->IsDexCache()) { + obj->VisitReferences<true, kVerifyNone, kWithoutReadBarrier>(*this, *this); + } else { + // Don't visit native roots for non-dex-cache + obj->VisitReferences<false, kVerifyNone, kWithoutReadBarrier>(*this, *this); + } + } +}; + // Copies data from one array to another array at the same position // if pred returns false. If there is a page of continuous data in // the src array for which pred consistently returns true then @@ -1285,6 +1344,7 @@ bool AppImageClassLoadersAndDexCachesHelper::Update( return false; } } + // Only add the classes to the class loader after the points where we can return false. for (size_t i = 0; i < num_dex_caches; i++) { ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get(i); @@ -1448,6 +1508,21 @@ bool AppImageClassLoadersAndDexCachesHelper::Update( } } } + { + // Fixup all the literal strings happens at app images which are supposed to be interned. + ScopedTrace timing("Fixup String Intern in image and dex_cache"); + const auto& image_header = space->GetImageHeader(); + const auto bitmap = space->GetMarkBitmap(); // bitmap of objects + const uint8_t* target_base = space->GetMemMap()->Begin(); + const ImageSection& objects_section = + image_header.GetImageSection(ImageHeader::kSectionObjects); + + uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); + uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); + + FixupInternVisitor fixup_intern_visitor; + bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_intern_visitor); + } if (*out_forward_dex_cache_array) { ScopedTrace timing("Fixup ArtMethod dex cache arrays"); FixupArtMethodArrayVisitor visitor(header); @@ -2410,74 +2485,121 @@ ClassPathEntry FindInClassPath(const char* descriptor, return ClassPathEntry(nullptr, nullptr); } +// Returns true if the given class loader is either a PathClassLoader or a DexClassLoader. +// (they both have the same behaviour with respect to class lockup order) +static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) { + mirror::Class* class_loader_class = class_loader->GetClass(); + return + (class_loader_class == + soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) || + (class_loader_class == + soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader)); +} + +static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) { + mirror::Class* class_loader_class = class_loader->GetClass(); + return class_loader_class == + soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader); +} + bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, Thread* self, const char* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader, ObjPtr<mirror::Class>* result) { - // Termination case: boot class-loader. + // Termination case: boot class loader. if (IsBootClassLoader(soa, class_loader.Get())) { - // The boot class loader, search the boot class path. - ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); - if (pair.second != nullptr) { - ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, nullptr); - if (klass != nullptr) { - *result = EnsureResolved(self, descriptor, klass); - } else { - *result = DefineClass(self, - descriptor, - hash, - ScopedNullHandle<mirror::ClassLoader>(), - *pair.first, - *pair.second); - } - if (*result == nullptr) { - CHECK(self->IsExceptionPending()) << descriptor; - self->ClearException(); - } - } else { - *result = nullptr; - } + *result = FindClassInBootClassLoaderClassPath(self, descriptor, hash); return true; } - // Unsupported class-loader? - if (soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader) != - class_loader->GetClass()) { - // PathClassLoader is the most common case, so it's the one we check first. For secondary dex - // files, we also check DexClassLoader here. - if (soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader) != - class_loader->GetClass()) { - *result = nullptr; - return false; + if (IsPathOrDexClassLoader(soa, class_loader)) { + // For regular path or dex class loader the search order is: + // - parent + // - class loader dex files + + // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension). + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent())); + if (!FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result)) { + return false; // One of the parents is not supported. + } + if (*result != nullptr) { + return true; // Found the class up the chain. } + + // Search the current class loader classpath. + *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader); + return true; } - // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension). - StackHandleScope<4> hs(self); - Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent())); - bool recursive_result = FindClassInBaseDexClassLoader(soa, - self, - descriptor, - hash, - h_parent, - result); + if (IsDelegateLastClassLoader(soa, class_loader)) { + // For delegate last, the search order is: + // - boot class path + // - class loader dex files + // - parent + *result = FindClassInBootClassLoaderClassPath(self, descriptor, hash); + if (*result != nullptr) { + return true; // The class is part of the boot class path. + } - if (!recursive_result) { - // Something wrong up the chain. - return false; + *result = FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader); + if (*result != nullptr) { + return true; // Found the class in the current class loader + } + + // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension). + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent())); + return FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result); } - if (*result != nullptr) { - // Found the class up the chain. - return true; + // Unsupported class loader. + *result = nullptr; + return false; +} + +// Finds the class in the boot class loader. +// If the class is found the method returns the resolved class. Otherwise it returns null. +ObjPtr<mirror::Class> ClassLinker::FindClassInBootClassLoaderClassPath(Thread* self, + const char* descriptor, + size_t hash) { + ObjPtr<mirror::Class> result = nullptr; + ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); + if (pair.second != nullptr) { + ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, nullptr); + if (klass != nullptr) { + result = EnsureResolved(self, descriptor, klass); + } else { + result = DefineClass(self, + descriptor, + hash, + ScopedNullHandle<mirror::ClassLoader>(), + *pair.first, + *pair.second); + } + if (result == nullptr) { + CHECK(self->IsExceptionPending()) << descriptor; + self->ClearException(); + } } + return result; +} + +ObjPtr<mirror::Class> ClassLinker::FindClassInBaseDexClassLoaderClassPath( + ScopedObjectAccessAlreadyRunnable& soa, + const char* descriptor, + size_t hash, + Handle<mirror::ClassLoader> class_loader) { + CHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader)) + << "Unexpected class loader for descriptor " << descriptor; - // Handle this step. - // Handle as if this is the child PathClassLoader. - // The class loader is a PathClassLoader which inherits from BaseDexClassLoader. - // We need to get the DexPathList and loop through it. + Thread* self = soa.Self(); ArtField* const cookie_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie); ArtField* const dex_file_field = @@ -2489,10 +2611,11 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl // DexPathList has an array dexElements of Elements[] which each contain a dex file. ObjPtr<mirror::Object> dex_elements_obj = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> - GetObject(dex_path_list); + GetObject(dex_path_list); // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look // at the mCookie which is a DexFile vector. if (dex_elements_obj != nullptr) { + StackHandleScope<1> hs(self); Handle<mirror::ObjectArray<mirror::Object>> dex_elements = hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()); for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { @@ -2518,19 +2641,18 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl OatDexFile::FindClassDef(*cp_dex_file, descriptor, hash); if (dex_class_def != nullptr) { ObjPtr<mirror::Class> klass = DefineClass(self, - descriptor, - hash, - class_loader, - *cp_dex_file, - *dex_class_def); + descriptor, + hash, + class_loader, + *cp_dex_file, + *dex_class_def); if (klass == nullptr) { CHECK(self->IsExceptionPending()) << descriptor; self->ClearException(); // TODO: Is it really right to break here, and not check the other dex files? - return true; + return nullptr; } - *result = klass; - return true; + return klass; } } } @@ -2538,9 +2660,7 @@ bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnabl } self->AssertNoPendingException(); } - - // Result is still null from the parent call, no need to set it again... - return true; + return nullptr; } mirror::Class* ClassLinker::FindClass(Thread* self, @@ -8640,8 +8760,15 @@ const char* ClassLinker::GetClassRootDescriptor(ClassRoot class_root) { return descriptor; } -jobject ClassLinker::CreatePathClassLoader(Thread* self, - const std::vector<const DexFile*>& dex_files) { +jobject ClassLinker::CreateWellKnownClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files, + jclass loader_class, + jobject parent_loader) { + CHECK(self->GetJniEnv()->IsSameObject(loader_class, + WellKnownClasses::dalvik_system_PathClassLoader) || + self->GetJniEnv()->IsSameObject(loader_class, + WellKnownClasses::dalvik_system_DelegateLastClassLoader)); + // SOAAlreadyRunnable is protected, and we need something to add a global reference. // We could move the jobject to the callers, but all call-sites do this... ScopedObjectAccessUnchecked soa(self); @@ -8677,8 +8804,8 @@ jobject ClassLinker::CreatePathClassLoader(Thread* self, for (const DexFile* dex_file : dex_files) { StackHandleScope<4> hs2(self); - // CreatePathClassLoader is only used by gtests. Index 0 of h_long_array is supposed to be the - // oat file but we can leave it null. + // CreateWellKnownClassLoader is only used by gtests and compiler. + // Index 0 of h_long_array is supposed to be the oat file but we can leave it null. Handle<mirror::LongArray> h_long_array = hs2.NewHandle(mirror::LongArray::Alloc( self, kDexFileIndexStart + 1)); @@ -8713,36 +8840,44 @@ jobject ClassLinker::CreatePathClassLoader(Thread* self, // Set elements. dex_elements_field->SetObject<false>(h_dex_path_list.Get(), h_dex_elements.Get()); - // Create PathClassLoader. - Handle<mirror::Class> h_path_class_class = hs.NewHandle( - soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)); - Handle<mirror::Object> h_path_class_loader = hs.NewHandle( - h_path_class_class->AllocObject(self)); - DCHECK(h_path_class_loader != nullptr); + // Create the class loader.. + Handle<mirror::Class> h_loader_class = hs.NewHandle(soa.Decode<mirror::Class>(loader_class)); + Handle<mirror::Object> h_class_loader = hs.NewHandle(h_loader_class->AllocObject(self)); + DCHECK(h_class_loader != nullptr); // Set DexPathList. ArtField* path_list_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList); DCHECK(path_list_field != nullptr); - path_list_field->SetObject<false>(h_path_class_loader.Get(), h_dex_path_list.Get()); + path_list_field->SetObject<false>(h_class_loader.Get(), h_dex_path_list.Get()); // Make a pretend boot-classpath. // TODO: Should we scan the image? ArtField* const parent_field = mirror::Class::FindField(self, - h_path_class_loader->GetClass(), + h_class_loader->GetClass(), "parent", "Ljava/lang/ClassLoader;"); DCHECK(parent_field != nullptr); - ObjPtr<mirror::Object> boot_cl = - soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader)->AllocObject(self); - parent_field->SetObject<false>(h_path_class_loader.Get(), boot_cl); + + ObjPtr<mirror::Object> parent = (parent_loader != nullptr) + ? soa.Decode<mirror::ClassLoader>(parent_loader) + : soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader)->AllocObject(self); + parent_field->SetObject<false>(h_class_loader.Get(), parent); // Make it a global ref and return. ScopedLocalRef<jobject> local_ref( - soa.Env(), soa.Env()->AddLocalReference<jobject>(h_path_class_loader.Get())); + soa.Env(), soa.Env()->AddLocalReference<jobject>(h_class_loader.Get())); return soa.Env()->NewGlobalRef(local_ref.get()); } +jobject ClassLinker::CreatePathClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files) { + return CreateWellKnownClassLoader(self, + dex_files, + WellKnownClasses::dalvik_system_PathClassLoader, + nullptr); +} + void ClassLinker::DropFindArrayClassCache() { std::fill_n(find_array_class_cache_, kFindArrayCacheSize, GcRoot<mirror::Class>(nullptr)); find_array_class_cache_next_victim_ = 0; diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 1e8125eb05..de1fefd20e 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -553,8 +553,24 @@ class ClassLinker { REQUIRES(!Locks::classlinker_classes_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - // Creates a GlobalRef PathClassLoader that can be used to load classes from the given dex files. + // Creates a GlobalRef PathClassLoader or DelegateLastClassLoader (specified by loader_class) + // that can be used to load classes from the given dex files. The parent of the class loader + // will be set to `parent_loader`. If `parent_loader` is null the parent will be + // the boot class loader. + // If class_loader points to a different class than PathClassLoader or DelegateLastClassLoader + // this method will abort. // Note: the objects are not completely set up. Do not use this outside of tests and the compiler. + jobject CreateWellKnownClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files, + jclass loader_class, + jobject parent_loader) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + + // Calls CreateWellKnownClassLoader(self, + // dex_files, + // WellKnownClasses::dalvik_system_PathClassLoader, + // nullptr) jobject CreatePathClassLoader(Thread* self, const std::vector<const DexFile*>& dex_files) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_); @@ -819,6 +835,27 @@ class ClassLinker { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_); + // Finds the class in the classpath of the given class loader. It only searches the class loader + // dex files and does not recurse into its parent. + // The method checks that the provided class loader is either a PathClassLoader or a + // DexClassLoader. + // If the class is found the method returns the resolved class. Otherwise it returns null. + ObjPtr<mirror::Class> FindClassInBaseDexClassLoaderClassPath( + ScopedObjectAccessAlreadyRunnable& soa, + const char* descriptor, + size_t hash, + Handle<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + + // Finds the class in the boot class loader. + // If the class is found the method returns the resolved class. Otherwise it returns null. + ObjPtr<mirror::Class> FindClassInBootClassLoaderClassPath(Thread* self, + const char* descriptor, + size_t hash) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_); + // Finds a class by its descriptor, returning NULL if it isn't wasn't loaded // by the given 'class_loader'. Uses the provided hash for the descriptor. mirror::Class* LookupClass(Thread* self, diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 684a261cca..03cc6c59c4 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -1533,4 +1533,110 @@ TEST_F(ClassLinkerMethodHandlesTest, TestResolveMethodTypes) { ASSERT_TRUE(method1_type.Get() != method2_type.Get()); } +// Verify that ClassLinker's CreateWellknownClassLoader works as expected +// by creating a chain of class loaders with various dex files. +TEST_F(ClassLinkerTest, CreateWellKnownClassLoader) { + // LoadDexIn*ClassLoader methods already assert that the parent loader is the expected one. + // No need to check again. + jobject class_loader_a = LoadDexInPathClassLoader("MyClass", nullptr); + jobject class_loader_b = LoadDexInDelegateLastClassLoader("Nested", class_loader_a); + jobject class_loader_c = LoadDexInPathClassLoader("MultiDex", class_loader_b); + LoadDexInDelegateLastClassLoader("Interfaces", class_loader_c); +} + +class ClassLinkerClassLoaderTest : public ClassLinkerTest { + protected: + // Verifies that the class identified by the given descriptor is loaded with + // the expected_class_loader_obj when search from class_loader_to_search_obj. + // When expected_class_loader_obj is null the check will be done against BootClassLoader. + void VerifyClassResolution(const std::string& descriptor, + jobject class_loader_to_search_obj, + jobject expected_class_loader_obj, + bool should_find = true) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<3> hs(self); + Handle<mirror::ClassLoader> class_loader_to_search( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_to_search_obj))); + + Handle<mirror::Class> klass = hs.NewHandle( + class_linker_->FindClass(soa.Self(), descriptor.c_str(), class_loader_to_search)); + + if (!should_find) { + if (self->IsExceptionPending()) { + self->ClearException(); + } + ASSERT_TRUE(klass == nullptr); + } else if (expected_class_loader_obj == nullptr) { + ASSERT_TRUE(ClassLinker::IsBootClassLoader(soa, klass->GetClassLoader())); + } else { + ASSERT_TRUE(klass != nullptr) << descriptor; + Handle<mirror::ClassLoader> expected_class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(expected_class_loader_obj))); + ASSERT_EQ(klass->GetClassLoader(), expected_class_loader.Get()); + } + } +}; + +TEST_F(ClassLinkerClassLoaderTest, CreatePathClassLoader) { + jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr); + VerifyClassResolution("LDefinedInA;", class_loader_a, class_loader_a); + VerifyClassResolution("Ljava/lang/String;", class_loader_a, nullptr); + VerifyClassResolution("LDefinedInB;", class_loader_a, nullptr, /*should_find*/ false); +} + +TEST_F(ClassLinkerClassLoaderTest, CreateDelegateLastClassLoader) { + jobject class_loader_a = LoadDexInDelegateLastClassLoader("ForClassLoaderA", nullptr); + VerifyClassResolution("LDefinedInA;", class_loader_a, class_loader_a); + VerifyClassResolution("Ljava/lang/String;", class_loader_a, nullptr); + VerifyClassResolution("LDefinedInB;", class_loader_a, nullptr, /*should_find*/ false); +} + +TEST_F(ClassLinkerClassLoaderTest, CreateClassLoaderChain) { + // The chain is + // ClassLoaderA (PathClassLoader, defines: A, AB, AC, AD) + // ^ + // | + // ClassLoaderB (DelegateLastClassLoader, defines: B, AB, BC, BD) + // ^ + // | + // ClassLoaderC (PathClassLoader, defines: C, AC, BC, CD) + // ^ + // | + // ClassLoaderD (DelegateLastClassLoader, defines: D, AD, BD, CD) + + jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr); + jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a); + jobject class_loader_c = LoadDexInPathClassLoader("ForClassLoaderC", class_loader_b); + jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c); + + // Verify exclusive classes (present in only one class loader). + VerifyClassResolution("LDefinedInD;", class_loader_d, class_loader_d); + VerifyClassResolution("LDefinedInC;", class_loader_d, class_loader_c); + VerifyClassResolution("LDefinedInB;", class_loader_d, class_loader_b); + VerifyClassResolution("LDefinedInA;", class_loader_d, class_loader_a); + + // Verify classes that are defined in multiple classloader. + + // Classes defined in B should be found in B even if they are defined in A or C because + // B is a DelegateLastClassLoader. + VerifyClassResolution("LDefinedInAB;", class_loader_d, class_loader_b); + VerifyClassResolution("LDefinedInABC;", class_loader_d, class_loader_b); + VerifyClassResolution("LDefinedInBC;", class_loader_d, class_loader_b); + + // Classes defined in D should be found in D even if they are defined in parent class loaders + // as well because D is a DelegateLastClassLoader. + VerifyClassResolution("LDefinedInAD;", class_loader_d, class_loader_d); + VerifyClassResolution("LDefinedInBD;", class_loader_d, class_loader_d); + VerifyClassResolution("LDefinedInCD;", class_loader_d, class_loader_d); + + + // Classes not defined in the DelegateLastClassLoaders (i.e. D or B) should be found + // in the top parent. + VerifyClassResolution("LDefinedInAC;", class_loader_d, class_loader_a); + + // Sanity check that we don't find an undefined class. + VerifyClassResolution("LNotDefined;", class_loader_d, nullptr, /*should_find*/ false); +} + } // namespace art diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 6441a44e6e..659c7e4950 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -680,19 +680,66 @@ jobject CommonRuntimeTestImpl::LoadMultiDex(const char* first_dex_name, } jobject CommonRuntimeTestImpl::LoadDex(const char* dex_name) { - std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(dex_name); + jobject class_loader = LoadDexInPathClassLoader(dex_name, nullptr); + Thread::Current()->SetClassLoaderOverride(class_loader); + return class_loader; +} + +jobject CommonRuntimeTestImpl::LoadDexInWellKnownClassLoader(const std::string& dex_name, + jclass loader_class, + jobject parent_loader) { + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(dex_name.c_str()); std::vector<const DexFile*> class_path; CHECK_NE(0U, dex_files.size()); for (auto& dex_file : dex_files) { class_path.push_back(dex_file.get()); loaded_dex_files_.push_back(std::move(dex_file)); } - Thread* self = Thread::Current(); - jobject class_loader = Runtime::Current()->GetClassLinker()->CreatePathClassLoader(self, - class_path); - self->SetClassLoaderOverride(class_loader); - return class_loader; + ScopedObjectAccess soa(self); + + jobject result = Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader( + self, + class_path, + loader_class, + parent_loader); + + { + // Verify we build the correct chain. + + ObjPtr<mirror::ClassLoader> actual_class_loader = soa.Decode<mirror::ClassLoader>(result); + // Verify that the result has the correct class. + CHECK_EQ(soa.Decode<mirror::Class>(loader_class), actual_class_loader->GetClass()); + // Verify that the parent is not null. The boot class loader will be set up as a + // proper object. + ObjPtr<mirror::ClassLoader> actual_parent(actual_class_loader->GetParent()); + CHECK(actual_parent != nullptr); + + if (parent_loader != nullptr) { + // We were given a parent. Verify that it's what we expect. + ObjPtr<mirror::ClassLoader> expected_parent = soa.Decode<mirror::ClassLoader>(parent_loader); + CHECK_EQ(expected_parent, actual_parent); + } else { + // No parent given. The parent must be the BootClassLoader. + CHECK(Runtime::Current()->GetClassLinker()->IsBootClassLoader(soa, actual_parent)); + } + } + + return result; +} + +jobject CommonRuntimeTestImpl::LoadDexInPathClassLoader(const std::string& dex_name, + jobject parent_loader) { + return LoadDexInWellKnownClassLoader(dex_name, + WellKnownClasses::dalvik_system_PathClassLoader, + parent_loader); +} + +jobject CommonRuntimeTestImpl::LoadDexInDelegateLastClassLoader(const std::string& dex_name, + jobject parent_loader) { + return LoadDexInWellKnownClassLoader(dex_name, + WellKnownClasses::dalvik_system_DelegateLastClassLoader, + parent_loader); } std::string CommonRuntimeTestImpl::GetCoreFileLocation(const char* suffix) { diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 3b3e6c5321..5893573bdd 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -134,10 +134,20 @@ class CommonRuntimeTestImpl { std::unique_ptr<const DexFile> OpenTestDexFile(const char* name); + // Loads the test dex file identified by the given dex_name into a PathClassLoader. + // Returns the created class loader. jobject LoadDex(const char* dex_name) REQUIRES_SHARED(Locks::mutator_lock_); + // Loads the test dex file identified by the given first_dex_name and second_dex_name + // into a PathClassLoader. Returns the created class loader. jobject LoadMultiDex(const char* first_dex_name, const char* second_dex_name) REQUIRES_SHARED(Locks::mutator_lock_); + jobject LoadDexInPathClassLoader(const std::string& dex_name, jobject parent_loader); + jobject LoadDexInDelegateLastClassLoader(const std::string& dex_name, jobject parent_loader); + jobject LoadDexInWellKnownClassLoader(const std::string& dex_name, + jclass loader_class, + jobject parent_loader); + std::string android_data_; std::string dalvik_cache_; diff --git a/runtime/debugger.cc b/runtime/debugger.cc index c94a8e0f3e..b59511214d 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -18,7 +18,9 @@ #include <sys/uio.h> +#include <memory> #include <set> +#include <vector> #include "android-base/stringprintf.h" @@ -26,6 +28,7 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "base/enums.h" +#include "base/strlcpy.h" #include "base/time_utils.h" #include "class_linker.h" #include "class_linker-inl.h" @@ -4926,11 +4929,20 @@ class StringTable { StringTable() { } - void Add(const std::string& str) { - table_.insert(str); - } + void Add(const char* str, bool copy_string) { + if (UNLIKELY(copy_string)) { + // Check whether it's already there. + if (table_.find(str) != table_.end()) { + return; + } - void Add(const char* str) { + // Make a copy. + size_t str_len = strlen(str); + char* copy = new char[str_len + 1]; + strlcpy(copy, str, str_len + 1); + string_backup_.emplace_back(copy); + str = copy; + } table_.insert(str); } @@ -4947,17 +4959,23 @@ class StringTable { } void WriteTo(std::vector<uint8_t>& bytes) const { - for (const std::string& str : table_) { - const char* s = str.c_str(); - size_t s_len = CountModifiedUtf8Chars(s); + for (const char* str : table_) { + size_t s_len = CountModifiedUtf8Chars(str); std::unique_ptr<uint16_t[]> s_utf16(new uint16_t[s_len]); - ConvertModifiedUtf8ToUtf16(s_utf16.get(), s); + ConvertModifiedUtf8ToUtf16(s_utf16.get(), str); JDWP::AppendUtf16BE(bytes, s_utf16.get(), s_len); } } private: - std::set<std::string> table_; + struct ConstCharStarComparator { + bool operator()(const char *s1, const char *s2) const { + return strcmp(s1, s2) < 0; + } + }; + + std::set<const char*, ConstCharStarComparator> table_; + std::vector<std::unique_ptr<char[]>> string_backup_; DISALLOW_COPY_AND_ASSIGN(StringTable); }; @@ -5044,12 +5062,13 @@ jbyteArray Dbg::GetRecentAllocations() { count > 0 && it != end; count--, it++) { const gc::AllocRecord* record = &it->second; std::string temp; - class_names.Add(record->GetClassDescriptor(&temp)); + const char* class_descr = record->GetClassDescriptor(&temp); + class_names.Add(class_descr, !temp.empty()); for (size_t i = 0, depth = record->GetDepth(); i < depth; i++) { ArtMethod* m = record->StackElement(i).GetMethod(); - class_names.Add(m->GetDeclaringClassDescriptor()); - method_names.Add(m->GetName()); - filenames.Add(GetMethodSourceFile(m)); + class_names.Add(m->GetDeclaringClassDescriptor(), false); + method_names.Add(m->GetName(), false); + filenames.Add(GetMethodSourceFile(m), false); } } diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index b57e2b2b40..0687b753d8 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -951,13 +951,20 @@ static inline bool DoCallCommon(ArtMethod* called_method, // Test whether to use the interpreter or compiler entrypoint, and save that result to pass to // PerformCall. A deoptimization could occur at any time, and we shouldn't change which // entrypoint to use once we start building the shadow frame. - bool use_interpreter_entrypoint = ClassLinker::ShouldUseInterpreterEntrypoint( - called_method, called_method->GetEntryPointFromQuickCompiledCode()); + + // For unstarted runtimes, always use the interpreter entrypoint. This fixes the case where we are + // doing cross compilation. Note that GetEntryPointFromQuickCompiledCode doesn't use the image + // pointer size here and this may case an overflow if it is called from the compiler. b/62402160 + const bool use_interpreter_entrypoint = !Runtime::Current()->IsStarted() || + ClassLinker::ShouldUseInterpreterEntrypoint( + called_method, + called_method->GetEntryPointFromQuickCompiledCode()); if (LIKELY(code_item != nullptr)) { // When transitioning to compiled code, space only needs to be reserved for the input registers. // The rest of the frame gets discarded. This also prevents accessing the called method's code // item, saving memory by keeping code items of compiled code untouched. - if (Runtime::Current()->IsStarted() && !use_interpreter_entrypoint) { + if (!use_interpreter_entrypoint) { + DCHECK(!Runtime::Current()->IsAotCompiler()) << "Compiler should use interpreter entrypoint"; num_regs = number_of_inputs; } else { num_regs = code_item->registers_size_; diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 21239fe3de..d3e8798bd6 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -913,12 +913,12 @@ class JvmtiFunctions { } static jvmtiError GetBytecodes(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jint* bytecode_count_ptr ATTRIBUTE_UNUSED, - unsigned char** bytecodes_ptr ATTRIBUTE_UNUSED) { + jmethodID method, + jint* bytecode_count_ptr, + unsigned char** bytecodes_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_get_bytecodes); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetBytecodes(env, method, bytecode_count_ptr, bytecodes_ptr); } static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr) { @@ -1208,6 +1208,23 @@ class JvmtiFunctions { return error; } + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(AllocUtil::GetGlobalJvmtiAllocationState), + "com.android.art.alloc.get_global_jvmti_allocation_state", + "Returns the total amount of memory currently allocated by all jvmtiEnvs through the" + " 'Allocate' jvmti function. This does not include any memory that has been deallocated" + " through the 'Deallocate' function. This number is approximate and might not correspond" + " exactly to the sum of the sizes of all not freed allocations.", + 1, + { // NOLINT [whitespace/braces] [4] + { "currently_allocated", JVMTI_KIND_OUT, JVMTI_TYPE_JLONG, false}, + }, + 1, + { ERR(NULL_POINTER) }); + if (error != ERR(NONE)) { + return error; + } + // Copy into output buffer. *extension_count_ptr = ext_vector.size(); diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 2d5d527e68..c63e50252b 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -216,7 +216,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_tag_objects = 1, .can_generate_field_modification_events = 1, .can_generate_field_access_events = 1, - .can_get_bytecodes = 0, + .can_get_bytecodes = 1, .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, diff --git a/runtime/openjdkjvmti/jvmti_allocator.h b/runtime/openjdkjvmti/jvmti_allocator.h index 1225c143f9..44b1cb1c20 100644 --- a/runtime/openjdkjvmti/jvmti_allocator.h +++ b/runtime/openjdkjvmti/jvmti_allocator.h @@ -36,6 +36,8 @@ #include "base/macros.h" #include "jvmti.h" +#include "ti_allocator.h" + namespace openjdkjvmti { template <typename T> class JvmtiAllocator; @@ -53,6 +55,7 @@ class JvmtiAllocator<void> { }; explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {} + explicit JvmtiAllocator() : env_(nullptr) {} template <typename U> JvmtiAllocator(const JvmtiAllocator<U>& other) // NOLINT, implicit @@ -89,6 +92,7 @@ class JvmtiAllocator { }; explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {} + explicit JvmtiAllocator() : env_(nullptr) {} template <typename U> JvmtiAllocator(const JvmtiAllocator<U>& other) // NOLINT, implicit @@ -108,8 +112,8 @@ class JvmtiAllocator { pointer allocate(size_type n, JvmtiAllocator<void>::pointer hint ATTRIBUTE_UNUSED = nullptr) { DCHECK_LE(n, max_size()); if (env_ == nullptr) { - T* result = reinterpret_cast<T*>(malloc(n * sizeof(T))); - CHECK(result != nullptr || n == 0u); // Abort if malloc() fails. + T* result = reinterpret_cast<T*>(AllocUtil::AllocateImpl(n * sizeof(T))); + CHECK(result != nullptr || n == 0u); // Abort if AllocateImpl() fails. return result; } else { unsigned char* result; @@ -120,7 +124,7 @@ class JvmtiAllocator { } void deallocate(pointer p, size_type n ATTRIBUTE_UNUSED) { if (env_ == nullptr) { - free(p); + AllocUtil::DeallocateImpl(reinterpret_cast<unsigned char*>(p)); } else { jvmtiError dealloc_error = env_->Deallocate(reinterpret_cast<unsigned char*>(p)); CHECK(dealloc_error == JVMTI_ERROR_NONE); diff --git a/runtime/openjdkjvmti/jvmti_weak_table.h b/runtime/openjdkjvmti/jvmti_weak_table.h index 01c24b1917..a5175a42ba 100644 --- a/runtime/openjdkjvmti/jvmti_weak_table.h +++ b/runtime/openjdkjvmti/jvmti_weak_table.h @@ -40,6 +40,7 @@ #include "gc_root-inl.h" #include "globals.h" #include "jvmti.h" +#include "jvmti_allocator.h" #include "mirror/object.h" #include "thread-current-inl.h" @@ -191,7 +192,7 @@ class JvmtiWeakTable : public art::gc::SystemWeakHolder { REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(allow_disallow_lock_); - template <typename Storage, class Allocator = std::allocator<T>> + template <typename Storage, class Allocator = JvmtiAllocator<T>> struct ReleasableContainer; struct HashGcRoot { @@ -209,10 +210,12 @@ class JvmtiWeakTable : public art::gc::SystemWeakHolder { } }; + using TagAllocator = JvmtiAllocator<std::pair<const art::GcRoot<art::mirror::Object>, T>>; std::unordered_map<art::GcRoot<art::mirror::Object>, T, HashGcRoot, - EqGcRoot> tagged_objects_ + EqGcRoot, + TagAllocator> tagged_objects_ GUARDED_BY(allow_disallow_lock_) GUARDED_BY(art::Locks::mutator_lock_); // To avoid repeatedly scanning the whole table, remember if we did that since the last sweep. diff --git a/runtime/openjdkjvmti/ti_allocator.cc b/runtime/openjdkjvmti/ti_allocator.cc index 603a43ffd2..8a0237d6c3 100644 --- a/runtime/openjdkjvmti/ti_allocator.cc +++ b/runtime/openjdkjvmti/ti_allocator.cc @@ -31,23 +31,25 @@ #include "ti_allocator.h" +#include <malloc.h> +#include <atomic> + #include "art_jvmti.h" -#include "art_method-inl.h" #include "base/enums.h" -#include "dex_file_annotations.h" -#include "events-inl.h" -#include "jni_internal.h" -#include "mirror/object_array-inl.h" -#include "modifiers.h" -#include "runtime_callbacks.h" -#include "scoped_thread_state_change-inl.h" -#include "ScopedLocalRef.h" -#include "thread-current-inl.h" -#include "thread_list.h" -#include "ti_phase.h" namespace openjdkjvmti { +std::atomic<jlong> AllocUtil::allocated; + +jvmtiError AllocUtil::GetGlobalJvmtiAllocationState(jvmtiEnv* env ATTRIBUTE_UNUSED, + jlong* allocated_ptr) { + if (allocated_ptr == nullptr) { + return ERR(NULL_POINTER); + } + *allocated_ptr = allocated.load(); + return OK; +} + jvmtiError AllocUtil::Allocate(jvmtiEnv* env ATTRIBUTE_UNUSED, jlong size, unsigned char** mem_ptr) { @@ -57,15 +59,31 @@ jvmtiError AllocUtil::Allocate(jvmtiEnv* env ATTRIBUTE_UNUSED, *mem_ptr = nullptr; return OK; } - *mem_ptr = static_cast<unsigned char*>(malloc(size)); - return (*mem_ptr != nullptr) ? OK : ERR(OUT_OF_MEMORY); + *mem_ptr = AllocateImpl(size); + if (UNLIKELY(*mem_ptr == nullptr)) { + return ERR(OUT_OF_MEMORY); + } + return OK; +} + +unsigned char* AllocUtil::AllocateImpl(jlong size) { + unsigned char* ret = size != 0 ? reinterpret_cast<unsigned char*>(malloc(size)) : nullptr; + if (LIKELY(ret != nullptr)) { + allocated += malloc_usable_size(ret); + } + return ret; } jvmtiError AllocUtil::Deallocate(jvmtiEnv* env ATTRIBUTE_UNUSED, unsigned char* mem) { + DeallocateImpl(mem); + return OK; +} + +void AllocUtil::DeallocateImpl(unsigned char* mem) { if (mem != nullptr) { + allocated -= malloc_usable_size(mem); free(mem); } - return OK; } } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_allocator.h b/runtime/openjdkjvmti/ti_allocator.h index 7f1aa6d9c0..35575c3884 100644 --- a/runtime/openjdkjvmti/ti_allocator.h +++ b/runtime/openjdkjvmti/ti_allocator.h @@ -35,12 +35,28 @@ #include "jni.h" #include "jvmti.h" +#include <atomic> +#include <memory> + namespace openjdkjvmti { +template<typename T> +class JvmtiAllocator; + class AllocUtil { public: static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr); static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem); + static jvmtiError GetGlobalJvmtiAllocationState(jvmtiEnv* env, jlong* total_allocated); + + private: + static void DeallocateImpl(unsigned char* mem); + static unsigned char* AllocateImpl(jlong size); + + static std::atomic<jlong> allocated; + + template <typename T> + friend class JvmtiAllocator; }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc index beb639e208..9b5b964a4d 100644 --- a/runtime/openjdkjvmti/ti_method.cc +++ b/runtime/openjdkjvmti/ti_method.cc @@ -91,6 +91,40 @@ void MethodUtil::Unregister() { runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback); } +jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env, + jmethodID method, + jint* size_ptr, + unsigned char** bytecode_ptr) { + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + art::ArtMethod* art_method = art::jni::DecodeArtMethod(method); + + if (art_method->IsNative()) { + return ERR(NATIVE_METHOD); + } + + if (size_ptr == nullptr || bytecode_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::ScopedObjectAccess soa(art::Thread::Current()); + const art::DexFile::CodeItem* code_item = art_method->GetCodeItem(); + if (code_item == nullptr) { + *size_ptr = 0; + *bytecode_ptr = nullptr; + return OK; + } + // 2 bytes per instruction for dex code. + *size_ptr = code_item->insns_size_in_code_units_ * 2; + jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr); + if (err != OK) { + return err; + } + memcpy(*bytecode_ptr, code_item->insns_, *size_ptr); + return OK; +} + jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED, jmethodID method, jint* size_ptr) { diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h index cc161c8fed..d95a81b63b 100644 --- a/runtime/openjdkjvmti/ti_method.h +++ b/runtime/openjdkjvmti/ti_method.h @@ -44,6 +44,11 @@ class MethodUtil { static void Register(EventHandler* event_handler); static void Unregister(); + static jvmtiError GetBytecodes(jvmtiEnv* env, + jmethodID method, + jint* count_ptr, + unsigned char** bytecodes); + static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr); static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr); diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index 24f194b5ee..8d505e2582 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -37,6 +37,7 @@ namespace art { jclass WellKnownClasses::dalvik_annotation_optimization_CriticalNative; jclass WellKnownClasses::dalvik_annotation_optimization_FastNative; jclass WellKnownClasses::dalvik_system_BaseDexClassLoader; +jclass WellKnownClasses::dalvik_system_DelegateLastClassLoader; jclass WellKnownClasses::dalvik_system_DexClassLoader; jclass WellKnownClasses::dalvik_system_DexFile; jclass WellKnownClasses::dalvik_system_DexPathList; @@ -270,6 +271,7 @@ void WellKnownClasses::Init(JNIEnv* env) { CacheClass(env, "dalvik/annotation/optimization/CriticalNative"); dalvik_annotation_optimization_FastNative = CacheClass(env, "dalvik/annotation/optimization/FastNative"); dalvik_system_BaseDexClassLoader = CacheClass(env, "dalvik/system/BaseDexClassLoader"); + dalvik_system_DelegateLastClassLoader = CacheClass(env, "dalvik/system/DelegateLastClassLoader"); dalvik_system_DexClassLoader = CacheClass(env, "dalvik/system/DexClassLoader"); dalvik_system_DexFile = CacheClass(env, "dalvik/system/DexFile"); dalvik_system_DexPathList = CacheClass(env, "dalvik/system/DexPathList"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index c18473197b..c5a16c1c76 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -47,6 +47,7 @@ struct WellKnownClasses { static jclass dalvik_annotation_optimization_CriticalNative; static jclass dalvik_annotation_optimization_FastNative; static jclass dalvik_system_BaseDexClassLoader; + static jclass dalvik_system_DelegateLastClassLoader; static jclass dalvik_system_DexClassLoader; static jclass dalvik_system_DexFile; static jclass dalvik_system_DexPathList; diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java index 72c5a28203..e9a11d76c2 100644 --- a/test/098-ddmc/src/Main.java +++ b/test/098-ddmc/src/Main.java @@ -14,8 +14,11 @@ * limitations under the License. */ +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.nio.ByteBuffer; +import java.util.ArrayList; public class Main { public static void main(String[] args) throws Exception { @@ -27,6 +30,8 @@ public class Main { testRecentAllocationTracking(); } + private static ArrayList<Object> staticHolder = new ArrayList<>(100000); + private static void testRecentAllocationTracking() throws Exception { System.out.println("Confirm empty"); Allocations empty = new Allocations(DdmVmInternal.getRecentAllocations()); @@ -44,18 +49,15 @@ public class Main { System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248"); final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value. for (int i = 0; i < overflowAllocations; i++) { - new Object() { - // Add a finalizer so that the allocation won't be eliminated. - public void finalize() { - System.out.print(""); - } - }; + allocate(i, 0); } Allocations after = new Allocations(DdmVmInternal.getRecentAllocations()); System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations)); System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries)); System.out.println("after.numberOfEntries=" + after.numberOfEntries); + staticHolder.clear(); // Free the allocated objects. + System.out.println("Disable and confirm back to empty"); DdmVmInternal.enableRecentAllocations(false); System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus()); @@ -72,7 +74,7 @@ public class Main { DdmVmInternal.enableRecentAllocations(true); System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus()); for (int i = 0; i < 16 * 1024; i++) { - new String("fnord"); + staticHolder.add(new String("fnord")); } Allocations first = new Allocations(DdmVmInternal.getRecentAllocations()); DdmVmInternal.enableRecentAllocations(true); @@ -86,6 +88,50 @@ public class Main { System.out.println("goodbye=" + goodbye); } + // Allocate a simple object. Use depth for a reasonably deep stack. + private static final int ALLOCATE1_DEPTH = 50; + + private static Object createProxy() { + try { + InvocationHandler handler = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) { + // Don't expect to be invoked. + return null; + } + }; + return Proxy.newProxyInstance(Main.class.getClassLoader(), + new Class[] { Runnable.class }, handler); + } catch (Exception e) { + // We don't really expect exceptions here. + throw new RuntimeException(e); + } + } + + private static void allocate(int i, int depth) { + if (depth >= ALLOCATE1_DEPTH) { + // Mix proxies, int arrays and Objects to test the different descriptor paths. + switch (i) { + case 0: + staticHolder.add(createProxy()); + break; + + case 1: + staticHolder.add(new int[0]); + break; + + case 2: + staticHolder.add(new Object[0]); + break; + + default: + staticHolder.add(new Object()); + break; + } + } else { + allocate(i, depth + 1); + } + } + private static class Allocations { final int messageHeaderLen; final int entryHeaderLen; diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java index 9072c8b538..3cfe0064fc 100644 --- a/test/141-class-unload/src/Main.java +++ b/test/141-class-unload/src/Main.java @@ -65,7 +65,8 @@ public class Main { String line; int count = 0; while ((line = reader.readLine()) != null) { - if (line.contains("@141-class-unload-ex.jar")) { + if (line.contains("141-class-unload-ex.odex") || + line.contains("141-class-unload-ex.vdex")) { System.out.println(line); ++count; } diff --git a/test/1900-track-alloc/alloc.cc b/test/1900-track-alloc/alloc.cc new file mode 100644 index 0000000000..db5617c54c --- /dev/null +++ b/test/1900-track-alloc/alloc.cc @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2013 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 "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace Test1900TrackAlloc { + +typedef jvmtiError (*GetGlobalState)(jvmtiEnv* env, jlong* allocated); + +struct AllocTrackingData { + GetGlobalState get_global_state; +}; + +template <typename T> +static void Dealloc(T* t) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t)); +} + +template <typename T, typename ...Rest> +static void Dealloc(T* t, Rest... rs) { + Dealloc(t); + Dealloc(rs...); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1900_doDeallocate(JNIEnv* env, + jclass, + jlong jvmti_env_ptr, + jlong ptr) { + JvmtiErrorToException(env, + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr), + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->Deallocate( + reinterpret_cast<unsigned char*>(static_cast<intptr_t>(ptr)))); +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_doAllocate(JNIEnv* env, + jclass, + jlong jvmti_env_ptr, + jlong size) { + unsigned char* res = nullptr; + JvmtiErrorToException(env, + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr), + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->Allocate(size, &res)); + return static_cast<jlong>(reinterpret_cast<intptr_t>(res)); +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_getAmountAllocated(JNIEnv* env, jclass) { + AllocTrackingData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return -1; + } + if (data == nullptr || data->get_global_state == nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Alloc tracking data not initialized."); + return -1; + } + jlong allocated = -1; + JvmtiErrorToException(env, jvmti_env, data->get_global_state(jvmti_env, &allocated)); + return allocated; +} + +static void DeallocParams(jvmtiParamInfo* params, jint n_params) { + for (jint i = 0; i < n_params; i++) { + Dealloc(params[i].name); + } +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Test1900_getDefaultJvmtiEnv(JNIEnv*, jclass) { + return static_cast<jlong>(reinterpret_cast<intptr_t>(jvmti_env)); +} + +extern "C" JNIEXPORT void Java_art_Test1900_destroyJvmtiEnv(JNIEnv* env, + jclass, + jlong jvmti_env_ptr) { + JvmtiErrorToException(env, + jvmti_env, + reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->DisposeEnvironment()); +} + +extern "C" JNIEXPORT jlong Java_art_Test1900_newJvmtiEnv(JNIEnv* env, jclass) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != 0) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to get JavaVM"); + return -1; + } + jvmtiEnv* new_env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&new_env), JVMTI_VERSION_1_0) != 0) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to create new jvmtiEnv"); + return -1; + } + return static_cast<jlong>(reinterpret_cast<intptr_t>(new_env)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1900_initializeTest(JNIEnv* env, jclass) { + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + AllocTrackingData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(AllocTrackingData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(AllocTrackingData)); + // Get the extensions. + jint n_ext = 0; + jvmtiExtensionFunctionInfo* infos = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) { + return; + } + for (jint i = 0; i < n_ext; i++) { + jvmtiExtensionFunctionInfo* cur_info = &infos[i]; + if (strcmp("com.android.art.alloc.get_global_jvmti_allocation_state", cur_info->id) == 0) { + data->get_global_state = reinterpret_cast<GetGlobalState>(cur_info->func); + } + // Cleanup the cur_info + DeallocParams(cur_info->params, cur_info->param_count); + Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors); + } + // Cleanup the array. + Dealloc(infos); + if (data->get_global_state == nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to find memory tracking extensions."); + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data)); + return; +} + +} // namespace Test1900TrackAlloc +} // namespace art diff --git a/test/1900-track-alloc/expected.txt b/test/1900-track-alloc/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/1900-track-alloc/expected.txt diff --git a/test/1900-track-alloc/info.txt b/test/1900-track-alloc/info.txt new file mode 100644 index 0000000000..e1d35ae026 --- /dev/null +++ b/test/1900-track-alloc/info.txt @@ -0,0 +1 @@ +Tests the jvmti-extension to get allocated memory snapshot. diff --git a/test/1900-track-alloc/run b/test/1900-track-alloc/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1900-track-alloc/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 diff --git a/test/1900-track-alloc/src/Main.java b/test/1900-track-alloc/src/Main.java new file mode 100644 index 0000000000..0dab4ef726 --- /dev/null +++ b/test/1900-track-alloc/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1900.run(); + } +} diff --git a/test/1900-track-alloc/src/art/Main.java b/test/1900-track-alloc/src/art/Main.java new file mode 100644 index 0000000000..aa5498bd62 --- /dev/null +++ b/test/1900-track-alloc/src/art/Main.java @@ -0,0 +1,32 @@ +/* + * 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; + +// Binder class so the agent's C code has something that can be bound and exposed to tests. +// In a package to separate cleanly and work around CTS reference issues (though this class +// should be replaced in the CTS version). +public class Main { + // Load the given class with the given classloader, and bind all native methods to corresponding + // C methods in the agent. Will abort if any of the steps fail. + public static native void bindAgentJNI(String className, ClassLoader classLoader); + // Same as above, giving the class directly. + public static native void bindAgentJNIForClass(Class<?> klass); + + // Common infrastructure. + public static native void setTag(Object o, long tag); + public static native long getTag(Object o); +} diff --git a/test/1900-track-alloc/src/art/Test1900.java b/test/1900-track-alloc/src/art/Test1900.java new file mode 100644 index 0000000000..becee1b15c --- /dev/null +++ b/test/1900-track-alloc/src/art/Test1900.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. + */ + +package art; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; + +public class Test1900 { + public static void checkLE(long exp, long o) { + if (exp > o) { + throw new Error("Expected: " + exp + " Got: " + o); + } + } + public static void checkEq(long exp, long o) { + if (exp != o) { + throw new Error("Expected: " + exp + " Got: " + o); + } + } + + public static void runConcurrent(Runnable... rs) throws Exception { + final CountDownLatch latch = new CountDownLatch(rs.length); + Thread[] thrs = new Thread[rs.length]; + for (int i = 0; i < rs.length; i++) { + final Runnable r = rs[i]; + thrs[i] = new Thread(() -> { + latch.countDown(); + r.run(); + }); + thrs[i].start(); + } + for (Thread thr : thrs) { + thr.join(); + } + } + static class Holder { + public long val; + } + + public static void run() throws Exception { + initializeTest(); + // Get the overhead for the native part of this test. + final long base_state = getAmountAllocated(); + + // Basic alloc-dealloc + checkEq(base_state + 0, getAmountAllocated()); + long abc = doAllocate(10); + checkLE(base_state + 10, getAmountAllocated()); + long def = doAllocate(10); + checkLE(base_state + 20, getAmountAllocated()); + doDeallocate(abc); + checkLE(base_state + 10, getAmountAllocated()); + + doDeallocate(def); + + checkEq(base_state + 0, getAmountAllocated()); + + // Try doing it concurrently. + Runnable add10 = () -> { long x = doAllocate(10); doDeallocate(x); }; + Runnable[] rs = new Runnable[100]; + Arrays.fill(rs, add10); + runConcurrent(rs); + checkEq(base_state + 0, getAmountAllocated()); + + // Try doing it concurrently with different threads to allocate and deallocate. + final Semaphore sem = new Semaphore(0); + final Holder h = new Holder(); + runConcurrent( + () -> { + try { + h.val = doAllocate(100); + checkLE(base_state + 100, getAmountAllocated()); + sem.release(); + } catch (Exception e) { throw new Error("exception!", e); } + }, + () -> { + try { + sem.acquire(); + long after_acq = getAmountAllocated(); + doDeallocate(h.val); + checkLE(base_state + 100, after_acq); + } catch (Exception e) { throw new Error("exception!", e); } + } + ); + checkEq(base_state + 0, getAmountAllocated()); + + // Try doing it with multiple jvmtienvs. + long env1 = newJvmtiEnv(); + long env2 = newJvmtiEnv(); + + final long new_base_state = getAmountAllocated(); + // new jvmtienvs shouldn't save us memory. + checkLE(base_state, new_base_state); + // Make sure we track both. + abc = doAllocate(env1, 10); + checkLE(new_base_state + 10, getAmountAllocated()); + def = doAllocate(env2, 10); + checkLE(new_base_state + 20, getAmountAllocated()); + doDeallocate(env1, abc); + checkLE(new_base_state + 10, getAmountAllocated()); + + doDeallocate(env2, def); + + checkEq(new_base_state + 0, getAmountAllocated()); + + destroyJvmtiEnv(env1); + destroyJvmtiEnv(env2); + + // Back to normal after getting rid of the envs. + checkEq(base_state + 0, getAmountAllocated()); + + // Try adding some tags + Object a = new Object(); + Object b = new Object(); + Main.setTag(a, 100); + Main.setTag(b, 200); + + // tags should be counted and should have some data associated with them. + checkLE(base_state + 1, getAmountAllocated()); + } + + private static native long doAllocate(long jvmtienv, long size); + private static long doAllocate(long size) { + return doAllocate(getDefaultJvmtiEnv(), size); + } + + private static native void doDeallocate(long jvmtienv, long ptr); + private static void doDeallocate(long size) { + doDeallocate(getDefaultJvmtiEnv(), size); + } + + private static native long getDefaultJvmtiEnv(); + private static native long newJvmtiEnv(); + private static native void destroyJvmtiEnv(long jvmtienv); + private static native long getAmountAllocated(); + private static native void initializeTest(); +} diff --git a/test/1901-get-bytecodes/bytecodes.cc b/test/1901-get-bytecodes/bytecodes.cc new file mode 100644 index 0000000000..edcb734788 --- /dev/null +++ b/test/1901-get-bytecodes/bytecodes.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1901Bytecodes { + +extern "C" JNIEXPORT jbyteArray JNICALL Java_art_Test1901_getBytecodes(JNIEnv* env, + jclass, + jobject jmethod) { + jmethodID method = env->FromReflectedMethod(jmethod); + if (env->ExceptionCheck()) { + return nullptr; + } + unsigned char* bytecodes = nullptr; + jint bytecodes_size = 0; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetBytecodes(method, &bytecodes_size, &bytecodes))) { + return nullptr; + } + jbyteArray out = env->NewByteArray(bytecodes_size); + if (env->ExceptionCheck()) { + return nullptr; + } else if (bytecodes_size == 0) { + return out; + } + jbyte* bytes = env->GetByteArrayElements(out, /* is_copy */ nullptr); + memcpy(bytes, bytecodes, bytecodes_size); + env->ReleaseByteArrayElements(out, bytes, 0); + return out; +} + +} // namespace Test1901Bytecodes +} // namespace art diff --git a/test/1901-get-bytecodes/expected.txt b/test/1901-get-bytecodes/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/1901-get-bytecodes/expected.txt diff --git a/test/1901-get-bytecodes/info.txt b/test/1901-get-bytecodes/info.txt new file mode 100644 index 0000000000..c8c91893e5 --- /dev/null +++ b/test/1901-get-bytecodes/info.txt @@ -0,0 +1,3 @@ +Tests basic functions in the jvmti plugin. + +Tests that the GetBytecodes function works as expected. diff --git a/test/1901-get-bytecodes/run b/test/1901-get-bytecodes/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1901-get-bytecodes/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 diff --git a/test/1901-get-bytecodes/src/Main.java b/test/1901-get-bytecodes/src/Main.java new file mode 100644 index 0000000000..d37fcdb0cb --- /dev/null +++ b/test/1901-get-bytecodes/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1901.run(); + } +} diff --git a/test/1901-get-bytecodes/src/art/Test1901.java b/test/1901-get-bytecodes/src/art/Test1901.java new file mode 100644 index 0000000000..6940491e3c --- /dev/null +++ b/test/1901-get-bytecodes/src/art/Test1901.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Base64; + +public class Test1901 { + // Class & Dex file containing the following class. + // Using this representation to prevent any changes to the compiler or the file formats from + // changing the output of this test. + // + // package art; + // public class Target { + // public void doNothing() { + // return; + // } + // + // public static void staticNothing() { + // return; + // } + // + // public void sayHi() { + // System.out.println("hello"); + // } + // } + public static byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHgoABgAQCQARABIIABMKABQAFQcAFgcAFwEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAlkb05vdGhpbmcBAA1zdGF0aWNOb3RoaW5nAQAFc2F5SGkBAApT" + + "b3VyY2VGaWxlAQALVGFyZ2V0LmphdmEMAAcACAcAGAwAGQAaAQAFaGVsbG8HABsMABwAHQEACmFy" + + "dC9UYXJnZXQBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxq" + + "YXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExq" + + "YXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAAEAAEABwAIAAEACQAAAB0AAQABAAAABSq3AAGx" + + "AAAAAQAKAAAABgABAAAAAgABAAsACAABAAkAAAAZAAAAAQAAAAGxAAAAAQAKAAAABgABAAAABAAJ" + + "AAwACAABAAkAAAAZAAAAAAAAAAGxAAAAAQAKAAAABgABAAAACAABAA0ACAABAAkAAAAlAAIAAQAA" + + "AAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAMAAgADQABAA4AAAACAA8="); + public static byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAbYkxNjiZ8a+fNWF4smR2+uXbrq88/FNoYAwAAcAAAAHhWNBIAAAAAAAAAAHgCAAAP" + + "AAAAcAAAAAYAAACsAAAAAgAAAMQAAAABAAAA3AAAAAYAAADkAAAAAQAAABQBAADkAQAANAEAAJoB" + + "AACiAQAAsAEAAMcBAADbAQAA7wEAAAMCAAAQAgAAEwIAABcCAAAiAgAAKQIAAC4CAAA3AgAAPgIA" + + "AAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAcAAAAFAAAAAAAAAAgAAAAFAAAAlAEAAAQAAQALAAAA" + + "AAAAAAAAAAAAAAAACQAAAAAAAAANAAAAAAAAAA4AAAABAAEADAAAAAIAAAAAAAAAAAAAAAEAAAAC" + + "AAAAAAAAAAYAAAAAAAAAYgIAAAAAAAABAAEAAQAAAE0CAAAEAAAAcBAFAAAADgAAAAAAAAAAAFIC" + + "AAABAAAADgAAAAEAAQAAAAAAVwIAAAEAAAAOAAAAAwABAAIAAABcAgAACAAAAGIAAAAaAQoAbiAE" + + "ABAADgABAAAAAwAGPGluaXQ+AAxMYXJ0L1RhcmdldDsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwAS" + + "TGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVt" + + "OwALVGFyZ2V0LmphdmEAAVYAAlZMAAlkb05vdGhpbmcABWhlbGxvAANvdXQAB3ByaW50bG4ABXNh" + + "eUhpAA1zdGF0aWNOb3RoaW5nAAIABw4ACAAHDgAEAAcOAAwABw54AAAAAgIAgYAEtAIDCcwCAQHg" + + "AgEB9AINAAAAAAAAAAEAAAAAAAAAAQAAAA8AAABwAAAAAgAAAAYAAACsAAAAAwAAAAIAAADEAAAA" + + "BAAAAAEAAADcAAAABQAAAAYAAADkAAAABgAAAAEAAAAUAQAAASAAAAQAAAA0AQAAARAAAAEAAACU" + + "AQAAAiAAAA8AAACaAQAAAyAAAAQAAABNAgAAACAAAAEAAABiAgAAABAAAAEAAAB4AgAA"); + + public static byte[][] DO_NOTHING_BYTECODES = new byte[][] { + // Dex Bytecodes for doNothing + // 0e00 |0000: return-void + new byte[] { 14, 0 }, + // Java bytecodes + // 0: return + new byte[] { -79 }, + }; + + public static byte[][] STATIC_NOTHING_BYTECODES = new byte[][] { + // Dex Bytecodes for staticNothing + // 0e00 |0000: return-void + new byte[] { 14, 0 }, + // Java bytecodes + // 0: return + new byte[] { -79 }, + }; + + public static byte[][] SAY_HI_NOTHING_BYTECODES = new byte[][] { + // Dex Bytecodes for sayHi + // 6200 0000 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000 + // 1a01 0a00 |0002: const-string v1, "hello" // string@000a + // 6e20 0400 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0004 + // 0e00 |0007: return-void + new byte[] { 98, 0, 0, 0, 26, 1, 10, 0, 110, 32, 4, 0, 16, 0, 14, 0 }, + // Java bytecodes + // 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; + // 3: ldc #3 // String hello + // 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + // 8: return + new byte[] { -78, 0, 2, 18, 3, -74, 0, 4, -79 }, + }; + + public static ClassLoader getClassLoader() throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class); + // We are on art since we got the InMemoryDexClassLoader. + return (ClassLoader)ctor.newInstance( + ByteBuffer.wrap(DEX_BYTES), Test1901.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. + return new ClassLoader(Test1901.class.getClassLoader()) { + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals("art.Target")) { + return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length); + } else { + return super.findClass(name); + } + } + }; + } + } + + public static void CheckMethodBytes(Method m, byte[][] possible_bytecodes) { + byte[] real_codes = getBytecodes(m); + for (byte[] pos : possible_bytecodes) { + if (Arrays.equals(pos, real_codes)) { + return; + } + } + System.out.println("Unexpected bytecodes for " + m); + System.out.println("Received: " + Arrays.toString(real_codes)); + System.out.println("Expected one of:"); + for (byte[] pos : possible_bytecodes) { + System.out.println("\t" + Arrays.toString(pos)); + } + } + + public static void run() throws Exception { + Class<?> target = getClassLoader().loadClass("art.Target"); + CheckMethodBytes(target.getDeclaredMethod("doNothing"), DO_NOTHING_BYTECODES); + CheckMethodBytes(target.getDeclaredMethod("staticNothing"), STATIC_NOTHING_BYTECODES); + CheckMethodBytes(target.getDeclaredMethod("sayHi"), SAY_HI_NOTHING_BYTECODES); + } + + public static native byte[] getBytecodes(Method m); +} diff --git a/test/596-app-images/src/Main.java b/test/596-app-images/src/Main.java index 8ee3c888b0..88d95f4162 100644 --- a/test/596-app-images/src/Main.java +++ b/test/596-app-images/src/Main.java @@ -14,6 +14,10 @@ * limitations under the License. */ +import java.lang.reflect.Field; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + class Main { static class Inner { final public static int abc = 10; @@ -46,13 +50,76 @@ class Main { if (!checkInitialized(StaticFieldsInit.class)) System.out.println("StaticFieldsInit class is not initialized!"); - if (checkInitialized(StaticInternString.class)) - System.out.println("StaticInternString class is initialized!"); + if (!checkInitialized(StaticInternString.class)) + System.out.println("StaticInternString class is not initialized!"); + + StringBuffer sb = new StringBuffer(); + sb.append("java."); + sb.append("abc."); + sb.append("Action"); + + String tmp = sb.toString(); + String intern = tmp.intern(); + + assertNotEqual(tmp, intern, "Dynamically constructed String, not interned."); + assertEqual(intern, StaticInternString.intent, "Static encoded literal String not interned."); + assertEqual(BootInternedString.boot, BootInternedString.boot.intern(), + "Static encoded literal String not moved back to runtime intern table."); + + try { + Field f = StaticInternString.class.getDeclaredField("intent"); + assertEqual(intern, f.get(null), "String Literals are not interned properly."); + + } catch (Exception e) { + System.out.println("Exception"); + } + + assertEqual(StaticInternString.getIntent(), StaticInternString2.getIntent(), + "String Literals are not intenred properly, App image static strings duplicated."); + + // reload the class StaticInternString, check whether static strings interned properly + final String DEX_FILE = System.getenv("DEX_LOCATION") + "/596-app-images.jar"; + final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path"); + + try { + Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); + if (pathClassLoader == null) { + throw new AssertionError("Counldn't find path class loader class"); + } + Constructor<?> ctor = + pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class); + ClassLoader loader = (ClassLoader) ctor.newInstance( + DEX_FILE, LIBRARY_SEARCH_PATH, null); + + Class<?> staticInternString = loader.loadClass("StaticInternString"); + + if (!checkAppImageContains(staticInternString)) { + System.out.println("Not loaded again."); + } + Method getIntent = staticInternString.getDeclaredMethod("getIntent"); + + assertEqual(StaticInternString.getIntent(), getIntent.invoke(staticInternString), + "Dynamically loaded app image's literal strings not interned properly."); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } public static native boolean checkAppImageLoaded(); public static native boolean checkAppImageContains(Class<?> klass); public static native boolean checkInitialized(Class<?> klass); + + public static void assertEqual(Object a, Object b, String msg) { + if (a != b) + System.out.println(msg); + } + + public static void assertNotEqual(Object a, Object b, String msg) { + if (a == b) + System.out.println(msg); + } + } class StaticFields{ @@ -68,6 +135,21 @@ class StaticFieldsInit{ } class StaticInternString { - final public static String intern = "java.abc.Action"; + final public static String intent = "java.abc.Action"; + static public String getIntent() { + return intent; + } +} + +class BootInternedString { + final public static String boot = "double"; +} + +class StaticInternString2 { + final public static String intent = "java.abc.Action"; + + static String getIntent() { + return intent; + } } diff --git a/test/988-method-trace/check b/test/988-method-trace/check new file mode 100644 index 0000000000..4c583eb051 --- /dev/null +++ b/test/988-method-trace/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. + +# Jack uses @hide API which gives it wrong method trace in the expected.txt +if [[ "$USE_JACK" == true ]]; then + patch -p0 expected.txt < expected_jack.diff >/dev/null +fi + +./default-check "$@" diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt index 8e42a48865..574d5b0772 100644 --- a/test/988-method-trace/expected.txt +++ b/test/988-method-trace/expected.txt @@ -1,4 +1,4 @@ -.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +.<= public static void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> <= public static void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> => art.Test988$IterOp() .=> public java.lang.Object() @@ -130,8 +130,8 @@ fibonacci(5)=5 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...=> public java.lang.String java.lang.StringBuilder.toString() -....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> +....=> static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) +....<= static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> ...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0> ...=> public java.lang.Error(java.lang.String) ....=> public java.lang.Throwable(java.lang.String) @@ -140,13 +140,13 @@ fibonacci(5)=5 .....=> public static final java.util.List java.util.Collections.emptyList() .....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []> .....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> +......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() +......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - art.Test988.iter_fibonacci(Test988.java:228) - art.Test988$IterOp.applyAsInt(Test988.java:223) - art.Test988.doFibTest(Test988.java:316) - art.Test988.run(Test988.java:286) + art.Test988.iter_fibonacci(Test988.java:235) + art.Test988$IterOp.applyAsInt(Test988.java:230) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:304) <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> @@ -163,10 +163,10 @@ fibonacci(5)=5 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - art.Test988.iter_fibonacci(Test988.java:228) - art.Test988$IterOp.applyAsInt(Test988.java:223) - art.Test988.doFibTest(Test988.java:316) - art.Test988.run(Test988.java:286) + art.Test988.iter_fibonacci(Test988.java:235) + art.Test988$IterOp.applyAsInt(Test988.java:230) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:304) <additional hidden frames> .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> @@ -231,8 +231,8 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> ...=> public java.lang.String java.lang.StringBuilder.toString() -....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> +....=> static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) +....<= static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> ...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0> ...=> public java.lang.Error(java.lang.String) ....=> public java.lang.Throwable(java.lang.String) @@ -241,13 +241,13 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 .....=> public static final java.util.List java.util.Collections.emptyList() .....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []> .....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> +......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() +......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - art.Test988.fibonacci(Test988.java:250) - art.Test988$RecurOp.applyAsInt(Test988.java:245) - art.Test988.doFibTest(Test988.java:316) - art.Test988.run(Test988.java:287) + art.Test988.fibonacci(Test988.java:257) + art.Test988$RecurOp.applyAsInt(Test988.java:252) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:305) <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> @@ -264,14 +264,194 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - art.Test988.fibonacci(Test988.java:250) - art.Test988$RecurOp.applyAsInt(Test988.java:245) - art.Test988.doFibTest(Test988.java:316) - art.Test988.run(Test988.java:287) + art.Test988.fibonacci(Test988.java:257) + art.Test988$RecurOp.applyAsInt(Test988.java:252) + art.Test988.doFibTest(Test988.java:339) + art.Test988.run(Test988.java:305) <additional hidden frames> .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> -=> public static native java.lang.Thread java.lang.Thread.currentThread() -<= public static native java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> -=> public static native void art.Trace.disableTracing(java.lang.Thread) +=> static void art.Test988$IntrinsicsTest.doTest() +.=> static void art.Test988Intrinsics.test() +..=> public static long java.lang.Double.doubleToRawLongBits(double) +..<= public static long java.lang.Double.doubleToRawLongBits(double) -> <class java.lang.Long: 0> +..=> public static long java.lang.Double.doubleToLongBits(double) +..<= public static long java.lang.Double.doubleToLongBits(double) -> <class java.lang.Long: 0> +..=> public static boolean java.lang.Double.isInfinite(double) +..<= public static boolean java.lang.Double.isInfinite(double) -> <class java.lang.Boolean: false> +..=> public static boolean java.lang.Double.isNaN(double) +..<= public static boolean java.lang.Double.isNaN(double) -> <class java.lang.Boolean: false> +..=> public static double java.lang.Double.longBitsToDouble(long) +..<= public static double java.lang.Double.longBitsToDouble(long) -> <class java.lang.Double: 0.0> +..=> public static int java.lang.Float.floatToRawIntBits(float) +..<= public static int java.lang.Float.floatToRawIntBits(float) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Float.floatToIntBits(float) +..<= public static int java.lang.Float.floatToIntBits(float) -> <class java.lang.Integer: 0> +..=> public static boolean java.lang.Float.isInfinite(float) +..<= public static boolean java.lang.Float.isInfinite(float) -> <class java.lang.Boolean: false> +..=> public static boolean java.lang.Float.isNaN(float) +..<= public static boolean java.lang.Float.isNaN(float) -> <class java.lang.Boolean: false> +..=> public static float java.lang.Float.intBitsToFloat(int) +..<= public static float java.lang.Float.intBitsToFloat(int) -> <class java.lang.Float: 0.0> +..=> public static int java.lang.Integer.reverse(int) +..<= public static int java.lang.Integer.reverse(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.reverseBytes(int) +..<= public static int java.lang.Integer.reverseBytes(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.bitCount(int) +..<= public static int java.lang.Integer.bitCount(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.compare(int,int) +..<= public static int java.lang.Integer.compare(int,int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.highestOneBit(int) +..<= public static int java.lang.Integer.highestOneBit(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.lowestOneBit(int) +..<= public static int java.lang.Integer.lowestOneBit(int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.numberOfLeadingZeros(int) +..<= public static int java.lang.Integer.numberOfLeadingZeros(int) -> <class java.lang.Integer: 32> +..=> public static int java.lang.Integer.numberOfTrailingZeros(int) +..<= public static int java.lang.Integer.numberOfTrailingZeros(int) -> <class java.lang.Integer: 32> +..=> public static int java.lang.Integer.rotateRight(int,int) +..<= public static int java.lang.Integer.rotateRight(int,int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.rotateLeft(int,int) +..<= public static int java.lang.Integer.rotateLeft(int,int) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Integer.signum(int) +..<= public static int java.lang.Integer.signum(int) -> <class java.lang.Integer: 0> +..=> public static long java.lang.Long.reverse(long) +..<= public static long java.lang.Long.reverse(long) -> <class java.lang.Long: 0> +..=> public static long java.lang.Long.reverseBytes(long) +..<= public static long java.lang.Long.reverseBytes(long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Long.bitCount(long) +..<= public static int java.lang.Long.bitCount(long) -> <class java.lang.Integer: 0> +..=> public static int java.lang.Long.compare(long,long) +..<= public static int java.lang.Long.compare(long,long) -> <class java.lang.Integer: 0> +..=> public static long java.lang.Long.highestOneBit(long) +..<= public static long java.lang.Long.highestOneBit(long) -> <class java.lang.Long: 0> +..=> public static long java.lang.Long.lowestOneBit(long) +..<= public static long java.lang.Long.lowestOneBit(long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Long.numberOfLeadingZeros(long) +..<= public static int java.lang.Long.numberOfLeadingZeros(long) -> <class java.lang.Integer: 64> +..=> public static int java.lang.Long.numberOfTrailingZeros(long) +..<= public static int java.lang.Long.numberOfTrailingZeros(long) -> <class java.lang.Integer: 64> +..=> public static long java.lang.Long.rotateRight(long,int) +..<= public static long java.lang.Long.rotateRight(long,int) -> <class java.lang.Long: 0> +..=> public static long java.lang.Long.rotateLeft(long,int) +..<= public static long java.lang.Long.rotateLeft(long,int) -> <class java.lang.Long: 0> +..=> public static int java.lang.Long.signum(long) +..<= public static int java.lang.Long.signum(long) -> <class java.lang.Integer: 0> +..=> public static short java.lang.Short.reverseBytes(short) +..<= public static short java.lang.Short.reverseBytes(short) -> <class java.lang.Short: 0> +..=> public static double java.lang.Math.abs(double) +..<= public static double java.lang.Math.abs(double) -> <class java.lang.Double: 0.0> +..=> public static float java.lang.Math.abs(float) +..<= public static float java.lang.Math.abs(float) -> <class java.lang.Float: 0.0> +..=> public static long java.lang.Math.abs(long) +..<= public static long java.lang.Math.abs(long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.abs(int) +..<= public static int java.lang.Math.abs(int) -> <class java.lang.Integer: 0> +..=> public static double java.lang.Math.min(double,double) +..<= public static double java.lang.Math.min(double,double) -> <class java.lang.Double: 0.0> +..=> public static float java.lang.Math.min(float,float) +..<= public static float java.lang.Math.min(float,float) -> <class java.lang.Float: 0.0> +..=> public static long java.lang.Math.min(long,long) +..<= public static long java.lang.Math.min(long,long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.min(int,int) +..<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 0> +..=> public static double java.lang.Math.max(double,double) +..<= public static double java.lang.Math.max(double,double) -> <class java.lang.Double: 0.0> +..=> public static float java.lang.Math.max(float,float) +..<= public static float java.lang.Math.max(float,float) -> <class java.lang.Float: 0.0> +..=> public static long java.lang.Math.max(long,long) +..<= public static long java.lang.Math.max(long,long) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.max(int,int) +..<= public static int java.lang.Math.max(int,int) -> <class java.lang.Integer: 0> +..=> public static double java.lang.Math.cos(double) +..<= public static double java.lang.Math.cos(double) -> <class java.lang.Double: 1.0> +..=> public static double java.lang.Math.sin(double) +..<= public static double java.lang.Math.sin(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.acos(double) +..<= public static double java.lang.Math.acos(double) -> <class java.lang.Double: 1.5707963267948966> +..=> public static double java.lang.Math.asin(double) +..<= public static double java.lang.Math.asin(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.atan(double) +..<= public static double java.lang.Math.atan(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.atan2(double,double) +..<= public static double java.lang.Math.atan2(double,double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.cbrt(double) +..<= public static double java.lang.Math.cbrt(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.cosh(double) +..<= public static double java.lang.Math.cosh(double) -> <class java.lang.Double: 1.0> +..=> public static double java.lang.Math.exp(double) +..<= public static double java.lang.Math.exp(double) -> <class java.lang.Double: 1.0> +..=> public static double java.lang.Math.expm1(double) +..<= public static double java.lang.Math.expm1(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.hypot(double,double) +..<= public static double java.lang.Math.hypot(double,double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.log(double) +..<= public static double java.lang.Math.log(double) -> <class java.lang.Double: -Infinity> +..=> public static double java.lang.Math.log10(double) +..<= public static double java.lang.Math.log10(double) -> <class java.lang.Double: -Infinity> +..=> public static double java.lang.Math.nextAfter(double,double) +..<= public static double java.lang.Math.nextAfter(double,double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.sinh(double) +..<= public static double java.lang.Math.sinh(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.tan(double) +..<= public static double java.lang.Math.tan(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.tanh(double) +..<= public static double java.lang.Math.tanh(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.sqrt(double) +..<= public static double java.lang.Math.sqrt(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.ceil(double) +..<= public static double java.lang.Math.ceil(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.floor(double) +..<= public static double java.lang.Math.floor(double) -> <class java.lang.Double: 0.0> +..=> public static double java.lang.Math.rint(double) +..<= public static double java.lang.Math.rint(double) -> <class java.lang.Double: 0.0> +..=> public static long java.lang.Math.round(double) +..<= public static long java.lang.Math.round(double) -> <class java.lang.Long: 0> +..=> public static int java.lang.Math.round(float) +..<= public static int java.lang.Math.round(float) -> <class java.lang.Integer: 0> +..=> public static java.lang.Thread java.lang.Thread.currentThread() +..<= public static java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> +..=> public char java.lang.String.charAt(int) +..<= public char java.lang.String.charAt(int) -> <class java.lang.Character: s> +..=> public int java.lang.String.compareTo(java.lang.String) +..<= public int java.lang.String.compareTo(java.lang.String) -> <class java.lang.Integer: 11> +..=> public boolean java.lang.String.equals(java.lang.Object) +..<= public boolean java.lang.String.equals(java.lang.Object) -> <class java.lang.Boolean: false> +..=> public int java.lang.String.indexOf(int) +..<= public int java.lang.String.indexOf(int) -> <class java.lang.Integer: -1> +..=> public int java.lang.String.indexOf(int,int) +..<= public int java.lang.String.indexOf(int,int) -> <class java.lang.Integer: -1> +..=> public int java.lang.String.indexOf(java.lang.String) +..<= public int java.lang.String.indexOf(java.lang.String) -> <class java.lang.Integer: -1> +..=> public int java.lang.String.indexOf(java.lang.String,int) +..<= public int java.lang.String.indexOf(java.lang.String,int) -> <class java.lang.Integer: -1> +..=> public boolean java.lang.String.isEmpty() +..<= public boolean java.lang.String.isEmpty() -> <class java.lang.Boolean: false> +..=> public int java.lang.String.length() +..<= public int java.lang.String.length() -> <class java.lang.Integer: 17> +..=> public synchronized java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String) +..<= public synchronized java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String) -> <class java.lang.StringBuffer: some large string bufferhello> +..=> public synchronized int java.lang.StringBuffer.length() +..<= public synchronized int java.lang.StringBuffer.length() -> <class java.lang.Integer: 29> +..=> public synchronized java.lang.String java.lang.StringBuffer.toString() +..<= public synchronized java.lang.String java.lang.StringBuffer.toString() -> <class java.lang.String: some large string bufferhello> +..=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) +..<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: some large string builderhello> +..=> public int java.lang.StringBuilder.length() +..<= public int java.lang.StringBuilder.length() -> <class java.lang.Integer: 30> +..=> public java.lang.String java.lang.StringBuilder.toString() +..<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: some large string builderhello> +..=> public static java.lang.Integer java.lang.Integer.valueOf(int) +..<= public static java.lang.Integer java.lang.Integer.valueOf(int) -> <class java.lang.Integer: 0> +..=> public static boolean java.lang.Thread.interrupted() +..<= public static boolean java.lang.Thread.interrupted() -> <class java.lang.Boolean: false> +.<= static void art.Test988Intrinsics.test() -> <null: null> +.=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +.<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +.=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +.<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +<= static void art.Test988$IntrinsicsTest.doTest() -> <null: null> +=> public static java.lang.Thread java.lang.Thread.currentThread() +<= public static java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> +=> public static void art.Trace.disableTracing(java.lang.Thread) diff --git a/test/988-method-trace/expected_jack.diff b/test/988-method-trace/expected_jack.diff new file mode 100644 index 0000000000..11364a0539 --- /dev/null +++ b/test/988-method-trace/expected_jack.diff @@ -0,0 +1,10 @@ +450,453c450,453 +< .=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +< .<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +< .=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) +< .<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null> +--- +> .=> public static void java.lang.System.arraycopy(int[],int,int[],int,int) +> .<= public static void java.lang.System.arraycopy(int[],int,int[],int,int) -> <null: null> +> .=> public static void java.lang.System.arraycopy(char[],int,char[],int,int) +> .<= public static void java.lang.System.arraycopy(char[],int,char[],int,int) -> <null: null> diff --git a/test/988-method-trace/gen_srcs.py b/test/988-method-trace/gen_srcs.py new file mode 100755 index 0000000000..c1ce35c278 --- /dev/null +++ b/test/988-method-trace/gen_srcs.py @@ -0,0 +1,321 @@ +#!/usr/bin/python3 +# 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. + +# +# Generates the src/art/Test988Intrinsics.java file. +# Re-run this every time art/compiler/intrinics_list.h is modified. +# +# $> python3.4 gen_srcs.py > src/art/Test988Intrinsics.java +# + +import argparse +import os +import re +import collections +import sys + +from string import Template + +# Relative path to art/compiler/intrinsics_list.h +INTRINSICS_LIST_H = os.path.dirname(os.path.realpath(__file__)) + "/../../compiler/intrinsics_list.h" + +# Macro parameter index to V(). Negative means from the end. +IDX_STATIC_OR_VIRTUAL = 1 +IDX_SIGNATURE = -1 +IDX_METHOD_NAME = -2 +IDX_CLASS_NAME = -3 + +# Exclude all hidden API. +KLASS_BLACK_LIST = ['sun.misc.Unsafe', 'libcore.io.Memory', 'java.lang.StringFactory'] +METHOD_BLACK_LIST = [('java.lang.ref.Reference', 'getReferent'), + ('java.lang.String', 'getCharsNoCheck'), + ('java.lang.System', 'arraycopy')] # arraycopy has a manual test. + +# When testing a virtual function, it needs to operate on an instance. +# These instances will be created with the following values, +# otherwise a default 'new T()' is used. +KLASS_INSTANCE_INITIALIZERS = { + 'java.lang.String' : '"some large string"', + 'java.lang.StringBuffer' : 'new java.lang.StringBuffer("some large string buffer")', + 'java.lang.StringBuilder' : 'new java.lang.StringBuilder("some large string builder")', + 'java.lang.ref.Reference' : 'new java.lang.ref.WeakReference(new Object())' +}; + +OUTPUT_TPL = Template(""" +/* + * 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. + */ + +// AUTO-GENENERATED by gen_srcs.py: DO NOT EDIT HERE DIRECTLY. +// +// $$> python3.4 gen_srcs.py > src/art/Test988Intrinsics.java +// +// RUN ABOVE COMMAND TO REGENERATE THIS FILE. + +package art; + +class Test988Intrinsics { + // Pre-initialize *all* instance variables used so that their constructors are not in the trace. +$static_fields + + static void initialize() { + // Ensure all static variables are initialized. + // In addition, pre-load classes here so that we don't see diverging class loading traces. +$initialize_classes + } + + static void test() { + // Call each intrinsic from art/compiler/intrinsics_list.h to make sure they are traced. +$test_body + } +} +""") + +JNI_TYPES = { + 'Z' : 'boolean', + 'B' : 'byte', + 'C' : 'char', + 'S' : 'short', + 'I' : 'int', + 'J' : 'long', + 'F' : 'float', + 'D' : 'double', + 'L' : 'object' +}; + +debug_printing_enabled = False + +def debug_print(x): + if debug_printing_enabled: + print(x, file=sys.stderr) + +# Parse JNI sig into a list, e.g. "II" -> ['I', 'I'], '[[IJ' -> ['[[I', 'J'], etc. +def sig_to_parameter_type_list(sig): + sig = re.sub(r'[(](.*)[)].*', r'\1', sig) + + lst = [] + obj = "" + is_obj = False + is_array = False + for x in sig: + if is_obj: + obj = obj + x + if x == ";": + is_obj = False + lst.append(obj) + obj = "" + elif is_array: + obj = obj + x + if x != "[": + is_array = False + lst.append(obj) + obj = "" + else: + if x == "[": + obj = "[" + is_array = True + elif x == "L": + obj = "L" + is_obj = True + else: + lst.append(x) + + return lst + +# Convert a single JNI descriptor into a pretty java name, e.g. "[I" -> "int[]", etc. +def javafy_name(kls_name): + if kls_name.startswith("L"): + kls_name = kls_name.lstrip("L").rstrip(";") + return kls_name.replace("/", ".") + elif kls_name.startswith("["): + array_count = kls_name.count("[") + non_array = javafy_name(kls_name.lstrip("[")) + return non_array + ("[]" * array_count) + + return JNI_TYPES.get(kls_name, kls_name) + +def extract_staticness(static_or_virtual): + if static_or_virtual == "kStatic": + return 'static' + return 'virtual' # kVirtual, kDirect + +class MethodInfo: + def __init__(self, staticness, pretty_params, method, kls): + # 'virtual' or 'static' + self.staticness = staticness + # list of e.g. ['int', 'double', 'java.lang.String'] etc + self.parameters = pretty_params + # e.g. 'toString' + self.method_name = method + # e.g. 'java.lang.String' + self.klass = kls + + def __str__(self): + return "MethodInfo " + str(self.__dict__) + + def dummy_parameters(self): + dummy_values = { + 'boolean' : 'false', + 'byte' : '(byte)0', + 'char' : "'x'", + 'short' : '(short)0', + 'int' : '0', + 'long' : '0L', + 'float' : '0.0f', + 'double' : '0.0' + } + + def object_dummy(name): + if name == "java.lang.String": + return '"hello"' + else: + return "(%s)null" %(name) + return [ dummy_values.get(param, object_dummy(param)) for param in self.parameters ] + + def dummy_instance_value(self): + return KLASS_INSTANCE_INITIALIZERS.get(self.klass, 'new %s()' %(self.klass)) + + def is_blacklisted(self): + for blk in KLASS_BLACK_LIST: + if self.klass.startswith(blk): + return True + + return (self.klass, self.method_name) in METHOD_BLACK_LIST + +# parse the V(...) \ list of items into a MethodInfo +def parse_method_info(items): + def get_item(idx): + return items[idx].strip().strip("\"") + + staticness = get_item(IDX_STATIC_OR_VIRTUAL) + sig = get_item(IDX_SIGNATURE) + method = get_item(IDX_METHOD_NAME) + kls = get_item(IDX_CLASS_NAME) + + debug_print ((sig, method, kls)) + + staticness = extract_staticness(staticness) + kls = javafy_name(kls) + param_types = sig_to_parameter_type_list(sig) + pretty_params = param_types + pretty_params = [javafy_name(i) for i in param_types] + + return MethodInfo(staticness, pretty_params, method, kls) + +# parse a line containing ' V(...)' into a MethodInfo +def parse_line(line): + line = line.strip() + if not line.startswith("V("): + return None + + line = re.sub(r'V[(](.*)[)]', r'\1', line) + debug_print(line) + + items = line.split(",") + + method_info = parse_method_info(items) + return method_info + +# Generate all the MethodInfo that we parse from intrinsics_list.h +def parse_all_method_infos(): + with open(INTRINSICS_LIST_H) as f: + for line in f: + s = parse_line(line) + if s is not None: + yield s + +# Format a receiver name. For statics, it's the class name, for receivers, it's an instance variable +def format_receiver_name(method_info): + receiver = method_info.klass + if method_info.staticness == 'virtual': + receiver = "instance_" + method_info.klass.replace(".", "_") + return receiver + +# Format a dummy call with dummy method parameters to the requested method. +def format_call_to(method_info): + dummy_args = ", ".join(method_info.dummy_parameters()) + receiver = format_receiver_name(method_info) + + return ("%s.%s(%s);" %(receiver, method_info.method_name, dummy_args)) + +# Format a static variable with an instance that could be used as the receiver +# (or None for non-static methods). +def format_instance_variable(method_info): + if method_info.staticness == 'static': + return None + return "static %s %s = %s;" %(method_info.klass, format_receiver_name(method_info), method_info.dummy_instance_value()) + +def format_initialize_klass(method_info): + return "%s.class.toString();" %(method_info.klass) + +def indent_list(lst, indent): + return [' ' * indent + i for i in lst] + +def main(): + global debug_printing_enabled + parser = argparse.ArgumentParser(description='Generate art/test/988-method-trace/src/art/Test988Intrinsics.java') + parser.add_argument('-d', '--debug', action='store_true', dest='debug', help='Print extra debugging information to stderr.') + parser.add_argument('output_file', nargs='?', metavar='<output-file>', default=sys.stdout, type=argparse.FileType('w'), help='Destination file to write to (default: stdout).') + args = parser.parse_args() + + debug_printing_enabled = args.debug + + ##### + + call_str_list = [] + instance_variable_dict = collections.OrderedDict() + initialize_klass_dict = collections.OrderedDict() + for i in parse_all_method_infos(): + debug_print(i) + if i.is_blacklisted(): + debug_print("Blacklisted: " + str(i)) + continue + + call_str = format_call_to(i) + debug_print(call_str) + + call_str_list.append(call_str) + + instance_variable = format_instance_variable(i) + if instance_variable is not None: + debug_print(instance_variable) + instance_variable_dict[i.klass] = instance_variable + + initialize_klass_dict[i.klass] = format_initialize_klass(i) + + static_fields = indent_list([ value for (key, value) in instance_variable_dict.items() ], 2) + test_body = indent_list(call_str_list, 4) + initialize_classes = indent_list([ value for (key, value) in initialize_klass_dict.items() ], 4) + + print(OUTPUT_TPL.substitute(static_fields="\n".join(static_fields), + test_body="\n".join(test_body), + initialize_classes="\n".join(initialize_classes)). + strip("\n"), \ + file=args.output_file) + +if __name__ == '__main__': + main() diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java index e40c612851..d7eda524c4 100644 --- a/test/988-method-trace/src/art/Test988.java +++ b/test/988-method-trace/src/art/Test988.java @@ -57,7 +57,7 @@ public class Test988 { } @Override public void Print() { - System.out.println(whitespace(cnt) + "=> " + m); + System.out.println(whitespace(cnt) + "=> " + methodToString(m)); } } @@ -124,6 +124,13 @@ public class Test988 { } } + static String methodToString(Object m) { + // Make the output more similar between ART and RI, + // by removing the 'native' specifier from methods. + String methodStr = m.toString(); + return methodStr.replaceFirst(" native", ""); + } + static final class MethodReturn implements Printable { private Object m; private Object val; @@ -154,7 +161,7 @@ public class Test988 { klass_print = klass.toString(); } System.out.println( - whitespace(cnt) + "<= " + m + " -> <" + klass_print + ": " + print + ">"); + whitespace(cnt) + "<= " + methodToString(m) + " -> <" + klass_print + ": " + print + ">"); } } @@ -167,7 +174,7 @@ public class Test988 { } @Override public void Print() { - System.out.println(whitespace(cnt) + "<= " + m + " EXCEPTION"); + System.out.println(whitespace(cnt) + "<= " + methodToString(m) + " EXCEPTION"); } } @@ -255,15 +262,26 @@ public class Test988 { } } + static final int METHOD_TRACING_IGNORE_DEPTH = 2; + static boolean sMethodTracingIgnore = false; + public static void notifyMethodEntry(Object m) { // Called by native code when a method is entered. This method is ignored by the native // entry and exit hooks. - results.add(new MethodEntry(m, cnt)); cnt++; + if ((cnt - 1) > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) { + return; + } + results.add(new MethodEntry(m, cnt - 1)); } public static void notifyMethodExit(Object m, boolean exception, Object result) { cnt--; + + if (cnt > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) { + return; + } + if (exception) { results.add(new MethodThrownThrough(m, cnt)); } else { @@ -285,6 +303,10 @@ public class Test988 { doFibTest(5, new RecurOp()); doFibTest(-19, new IterOp()); doFibTest(-19, new RecurOp()); + + sMethodTracingIgnore = true; + IntrinsicsTest.doTest(); + sMethodTracingIgnore = false; // Turn off method tracing so we don't have to deal with print internals. Trace.disableTracing(Thread.currentThread()); printResults(); @@ -303,6 +325,7 @@ public class Test988 { RecurOp.class.toString(); IterOp.class.toString(); StringBuilder.class.toString(); + IntrinsicsTest.initialize(); // ensure <clinit> is executed prior to tracing. } public static void printResults() { @@ -319,4 +342,30 @@ public class Test988 { results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t)); } } + + static class IntrinsicsTest { + static int[] sSourceArray = { 0, 1, 2, 3, 4, 5 }; + static int[] sDestArray = { 5, 6, 7, 8, 9, 10 }; + + static char[] sSourceArrayChar = { '0', '1', '2', '3', '4', '5' }; + static char[] sDestArrayChar = { '5', '6', '7', '8', '9', 'a' }; + + static void initialize() { + Test988Intrinsics.initialize(); + + // Pre-load all classes used in #doTest manual intrinsics. + java.lang.System.class.toString(); + } + static void doTest() { + // Ensure that the ART intrinsics in intrinsics_list.h are also being traced, + // since in non-tracing operation they are effectively inlined by the optimizing compiler. + + // Auto-generated test file that uses null/0s as default parameters. + Test988Intrinsics.test(); + + // Manual list here for functions that require special non-null/non-zero parameters: + System.arraycopy(sSourceArray, 0, sDestArray, 0, 1); + System.arraycopy(sSourceArrayChar, 0, sDestArrayChar, 0, 1); + } + } } diff --git a/test/988-method-trace/src/art/Test988Intrinsics.java b/test/988-method-trace/src/art/Test988Intrinsics.java new file mode 100644 index 0000000000..099fbf2ce8 --- /dev/null +++ b/test/988-method-trace/src/art/Test988Intrinsics.java @@ -0,0 +1,135 @@ +/* + * 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. + */ + +// AUTO-GENENERATED by gen_srcs.py: DO NOT EDIT HERE DIRECTLY. +// +// $> python3.4 gen_srcs.py > src/art/Test988Intrinsics.java +// +// RUN ABOVE COMMAND TO REGENERATE THIS FILE. + +package art; + +class Test988Intrinsics { + // Pre-initialize *all* instance variables used so that their constructors are not in the trace. + static java.lang.String instance_java_lang_String = "some large string"; + static java.lang.StringBuffer instance_java_lang_StringBuffer = new java.lang.StringBuffer("some large string buffer"); + static java.lang.StringBuilder instance_java_lang_StringBuilder = new java.lang.StringBuilder("some large string builder"); + + static void initialize() { + // Ensure all static variables are initialized. + // In addition, pre-load classes here so that we don't see diverging class loading traces. + java.lang.Double.class.toString(); + java.lang.Float.class.toString(); + java.lang.Integer.class.toString(); + java.lang.Long.class.toString(); + java.lang.Short.class.toString(); + java.lang.Math.class.toString(); + java.lang.Thread.class.toString(); + java.lang.String.class.toString(); + java.lang.StringBuffer.class.toString(); + java.lang.StringBuilder.class.toString(); + } + + static void test() { + // Call each intrinsic from art/compiler/intrinsics_list.h to make sure they are traced. + java.lang.Double.doubleToRawLongBits(0.0); + java.lang.Double.doubleToLongBits(0.0); + java.lang.Double.isInfinite(0.0); + java.lang.Double.isNaN(0.0); + java.lang.Double.longBitsToDouble(0L); + java.lang.Float.floatToRawIntBits(0.0f); + java.lang.Float.floatToIntBits(0.0f); + java.lang.Float.isInfinite(0.0f); + java.lang.Float.isNaN(0.0f); + java.lang.Float.intBitsToFloat(0); + java.lang.Integer.reverse(0); + java.lang.Integer.reverseBytes(0); + java.lang.Integer.bitCount(0); + java.lang.Integer.compare(0, 0); + java.lang.Integer.highestOneBit(0); + java.lang.Integer.lowestOneBit(0); + java.lang.Integer.numberOfLeadingZeros(0); + java.lang.Integer.numberOfTrailingZeros(0); + java.lang.Integer.rotateRight(0, 0); + java.lang.Integer.rotateLeft(0, 0); + java.lang.Integer.signum(0); + java.lang.Long.reverse(0L); + java.lang.Long.reverseBytes(0L); + java.lang.Long.bitCount(0L); + java.lang.Long.compare(0L, 0L); + java.lang.Long.highestOneBit(0L); + java.lang.Long.lowestOneBit(0L); + java.lang.Long.numberOfLeadingZeros(0L); + java.lang.Long.numberOfTrailingZeros(0L); + java.lang.Long.rotateRight(0L, 0); + java.lang.Long.rotateLeft(0L, 0); + java.lang.Long.signum(0L); + java.lang.Short.reverseBytes((short)0); + java.lang.Math.abs(0.0); + java.lang.Math.abs(0.0f); + java.lang.Math.abs(0L); + java.lang.Math.abs(0); + java.lang.Math.min(0.0, 0.0); + java.lang.Math.min(0.0f, 0.0f); + java.lang.Math.min(0L, 0L); + java.lang.Math.min(0, 0); + java.lang.Math.max(0.0, 0.0); + java.lang.Math.max(0.0f, 0.0f); + java.lang.Math.max(0L, 0L); + java.lang.Math.max(0, 0); + java.lang.Math.cos(0.0); + java.lang.Math.sin(0.0); + java.lang.Math.acos(0.0); + java.lang.Math.asin(0.0); + java.lang.Math.atan(0.0); + java.lang.Math.atan2(0.0, 0.0); + java.lang.Math.cbrt(0.0); + java.lang.Math.cosh(0.0); + java.lang.Math.exp(0.0); + java.lang.Math.expm1(0.0); + java.lang.Math.hypot(0.0, 0.0); + java.lang.Math.log(0.0); + java.lang.Math.log10(0.0); + java.lang.Math.nextAfter(0.0, 0.0); + java.lang.Math.sinh(0.0); + java.lang.Math.tan(0.0); + java.lang.Math.tanh(0.0); + java.lang.Math.sqrt(0.0); + java.lang.Math.ceil(0.0); + java.lang.Math.floor(0.0); + java.lang.Math.rint(0.0); + java.lang.Math.round(0.0); + java.lang.Math.round(0.0f); + java.lang.Thread.currentThread(); + instance_java_lang_String.charAt(0); + instance_java_lang_String.compareTo("hello"); + instance_java_lang_String.equals((java.lang.Object)null); + instance_java_lang_String.indexOf(0); + instance_java_lang_String.indexOf(0, 0); + instance_java_lang_String.indexOf("hello"); + instance_java_lang_String.indexOf("hello", 0); + instance_java_lang_String.isEmpty(); + instance_java_lang_String.length(); + instance_java_lang_StringBuffer.append("hello"); + instance_java_lang_StringBuffer.length(); + instance_java_lang_StringBuffer.toString(); + instance_java_lang_StringBuilder.append("hello"); + instance_java_lang_StringBuilder.length(); + instance_java_lang_StringBuilder.toString(); + java.lang.Integer.valueOf(0); + java.lang.Thread.interrupted(); + } +} diff --git a/test/Android.bp b/test/Android.bp index f893531c41..591684b887 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -286,6 +286,8 @@ art_cc_defaults { "992-source-data/source_file.cc", "993-breakpoints/breakpoints.cc", "996-breakpoint-obsolete/obsolete_breakpoints.cc", + "1900-track-alloc/alloc.cc", + "1901-get-bytecodes/bytecodes.cc", ], shared_libs: [ "libbase", @@ -445,6 +447,7 @@ art_cc_test_library { "art_debug_defaults", "art_defaults", ], + header_libs: ["libnativebridge-dummy-headers"], srcs: ["115-native-bridge/nativebridge.cc"], target: { android: { diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk deleted file mode 100644 index 753fe9a330..0000000000 --- a/test/Android.run-test-jvmti-java-library.mk +++ /dev/null @@ -1,171 +0,0 @@ -# -# 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. -# - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -# shim classes. We use one that exposes the common functionality. -LOCAL_SHIM_CLASSES := \ - 902-hello-transformation/src/art/Redefinition.java \ - 903-hello-tagging/src/art/Main.java \ - 989-method-trace-throw/src/art/Trace.java \ - -LOCAL_SRC_FILES := $(LOCAL_SHIM_CLASSES) - -# Actual test classes. -LOCAL_SRC_FILES += \ - 901-hello-ti-agent/src/art/Test901.java \ - 902-hello-transformation/src/art/Test902.java \ - 903-hello-tagging/src/art/Test903.java \ - 904-object-allocation/src/art/Test904.java \ - 905-object-free/src/art/Test905.java \ - 906-iterate-heap/src/art/Test906.java \ - 907-get-loaded-classes/src/art/Test907.java \ - 907-get-loaded-classes/src/art/Cerr.java \ - 908-gc-start-finish/src/art/Test908.java \ - 910-methods/src/art/Test910.java \ - 911-get-stack-trace/src/art/Test911.java \ - 911-get-stack-trace/src/art/AllTraces.java \ - 911-get-stack-trace/src/art/ControlData.java \ - 911-get-stack-trace/src/art/Frames.java \ - 911-get-stack-trace/src/art/OtherThread.java \ - 911-get-stack-trace/src/art/PrintThread.java \ - 911-get-stack-trace/src/art/Recurse.java \ - 911-get-stack-trace/src/art/SameThread.java \ - 911-get-stack-trace/src/art/ThreadListTraces.java \ - 912-classes/src-art/art/Test912.java \ - 912-classes/src-art/art/DexData.java \ - 913-heaps/src/art/Test913.java \ - 914-hello-obsolescence/src/art/Test914.java \ - 915-obsolete-2/src/art/Test915.java \ - 917-fields-transformation/src/art/Test917.java \ - 918-fields/src/art/Test918.java \ - 919-obsolete-fields/src/art/Test919.java \ - 920-objects/src/art/Test920.java \ - 922-properties/src/art/Test922.java \ - 923-monitors/src/art/Test923.java \ - 924-threads/src/art/Test924.java \ - 925-threadgroups/src/art/Test925.java \ - 926-multi-obsolescence/src/art/Test926.java \ - 927-timers/src/art/Test927.java \ - 928-jni-table/src/art/Test928.java \ - 930-hello-retransform/src/art/Test930.java \ - 931-agent-thread/src/art/Test931.java \ - 932-transform-saves/src/art/Test932.java \ - 933-misc-events/src/art/Test933.java \ - 940-recursive-obsolete/src/art/Test940.java \ - 942-private-recursive/src/art/Test942.java \ - 944-transform-classloaders/src/art/Test944.java \ - 945-obsolete-native/src/art/Test945.java \ - 947-reflect-method/src/art/Test947.java \ - 951-threaded-obsolete/src/art/Test951.java \ - 981-dedup-original-dex/src-art/art/Test981.java \ - 982-ok-no-retransform/src/art/Test982.java \ - 984-obsolete-invoke/src/art/Test984.java \ - 985-re-obsolete/src/art/Test985.java \ - 986-native-method-bind/src/art/Test986.java \ - 988-method-trace/src/art/Test988.java \ - 989-method-trace-throw/src/art/Test989.java \ - 990-field-trace/src/art/Test990.java \ - 991-field-trace-2/src/art/Test991.java \ - 992-source-data/src/art/Test992.java \ - 992-source-data/src/art/Target2.java \ - -JVMTI_RUN_TEST_GENERATED_NUMBERS := \ - 901 \ - 902 \ - 903 \ - 904 \ - 905 \ - 906 \ - 907 \ - 908 \ - 910 \ - 911 \ - 912 \ - 913 \ - 914 \ - 915 \ - 917 \ - 918 \ - 919 \ - 920 \ - 922 \ - 923 \ - 924 \ - 925 \ - 926 \ - 927 \ - 928 \ - 930 \ - 931 \ - 932 \ - 933 \ - 940 \ - 942 \ - 944 \ - 945 \ - 947 \ - 951 \ - 981 \ - 982 \ - 984 \ - 985 \ - 986 \ - 988 \ - 989 \ - 990 \ - 991 \ - 992 \ - -# Try to enforce that the directories correspond to the Java files we pull in. -JVMTI_RUN_TEST_DIR_CHECK := $(sort $(foreach DIR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS), \ - $(filter $(DIR)%,$(LOCAL_SRC_FILES)))) -ifneq ($(sort $(LOCAL_SRC_FILES)),$(JVMTI_RUN_TEST_DIR_CHECK)) - $(error Missing file, compare $(sort $(LOCAL_SRC_FILES)) with $(JVMTI_RUN_TEST_DIR_CHECK)) -endif - -LOCAL_MODULE_CLASS := JAVA_LIBRARIES -LOCAL_MODULE_TAGS := optional -LOCAL_JAVA_LANGUAGE_VERSION := 1.8 -LOCAL_MODULE := run-test-jvmti-java - -GENERATED_SRC_DIR := $(call local-generated-sources-dir) -JVMTI_RUN_TEST_GENERATED_FILES := \ - $(foreach NR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS),$(GENERATED_SRC_DIR)/results.$(NR).expected.txt) - -define GEN_JVMTI_RUN_TEST_GENERATED_FILE - -GEN_INPUT := $(wildcard $(LOCAL_PATH)/$(1)*/expected.txt) -GEN_OUTPUT := $(GENERATED_SRC_DIR)/results.$(1).expected.txt -$$(GEN_OUTPUT): $$(GEN_INPUT) - cp $$< $$@ - -GEN_INPUT := -GEN_OUTPUT := - -endef - -$(foreach NR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS),\ - $(eval $(call GEN_JVMTI_RUN_TEST_GENERATED_FILE,$(NR)))) -LOCAL_JAVA_RESOURCE_FILES := $(JVMTI_RUN_TEST_GENERATED_FILES) - -# We only want to depend on libcore. -LOCAL_NO_STANDARD_LIBRARIES := true -LOCAL_JAVA_LIBRARIES := core-all - -include $(BUILD_JAVA_LIBRARY) diff --git a/test/ForClassLoaderA/Classes.java b/test/ForClassLoaderA/Classes.java new file mode 100644 index 0000000000..a65ef64161 --- /dev/null +++ b/test/ForClassLoaderA/Classes.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInA { +} + +class DefinedInAB { +} + +class DefinedInABC { +} + +class DefinedInAC { +} + +class DefinedInAD { +} + diff --git a/test/ForClassLoaderB/Classes.java b/test/ForClassLoaderB/Classes.java new file mode 100644 index 0000000000..8c85ed5fc0 --- /dev/null +++ b/test/ForClassLoaderB/Classes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInB { +} + +class DefinedInAB { +} + +class DefinedInABC { +} + +class DefinedInBC { +} + +class DefinedInBD { +} diff --git a/test/ForClassLoaderC/Classes.java b/test/ForClassLoaderC/Classes.java new file mode 100644 index 0000000000..7b9e83ffff --- /dev/null +++ b/test/ForClassLoaderC/Classes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInC { +} + +class DefinedInAC { +} + +class DefinedInABC { +} + +class DefinedInBC { +} + +class DefinedInCD { +} diff --git a/test/ForClassLoaderD/Classes.java b/test/ForClassLoaderD/Classes.java new file mode 100644 index 0000000000..b34177f05f --- /dev/null +++ b/test/ForClassLoaderD/Classes.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class DefinedInD { +} + +class DefinedInAD { +} + +class DefinedInBD { +} + +class DefinedInCD { +} diff --git a/test/knownfailures.json b/test/knownfailures.json index d3d92c651e..3939bd162f 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -538,7 +538,8 @@ "595-profile-saving", "900-hello-plugin", "909-attach-agent", - "981-dedup-original-dex" + "981-dedup-original-dex", + "1900-track-alloc" ], "description": ["Tests that require exact knowledge of the number of plugins and agents."], "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress" @@ -594,11 +595,12 @@ }, { "tests": [ - "567-checker-compare" + "567-checker-compare", + "988-method-trace" ], - "description": "Checker tests failing when run with --build-with-javac-dx.", + "description": "Checker tests fail because desugar lowers Long.compare to lcmp", "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, - "bug": "b/62950048" + "bug": "b/63078894" }, { "tests": [ @@ -645,6 +647,11 @@ "env_vars": {"SANITIZE_HOST": "address"} }, { + "tests": "141-class-unload", + "description": "Idk why this fails", + "env_vars": {"SANITIZE_HOST": "address"} + }, + { "tests": ["988-method-trace"], "variant": "redefine-stress | jvmti-stress", "description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly." |