diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-23 09:56:13 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-23 09:56:13 +0000 |
commit | ce4af10bdb762077988f60dc79e4a251d4ea97a0 (patch) | |
tree | 7dbe9760b6a3ba46caa424f67bf724725dffdfec | |
parent | 532d4af422fc93d99b55d3887e1c3efe9e4f3325 (diff) | |
parent | 7d360b3bc2e2ed19e5d7f64fe2e2b20e2dbcdee9 (diff) | |
download | art-android13-mainline-scheduling-release.tar.gz |
Snap for 9098257 from 7d360b3bc2e2ed19e5d7f64fe2e2b20e2dbcdee9 to mainline-scheduling-releaseaml_sch_331113000aml_sch_331111000android13-mainline-scheduling-release
Change-Id: Ia1cc4457c297fae722221ade419cf17045cc299a
94 files changed, 2356 insertions, 608 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index 300526faa9..ec814ef614 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1283,6 +1283,9 @@ "name": "art_standalone_sigchain_tests[com.google.android.art.apex]" }, { + "name": "libnativeloader_e2e_tests[com.google.android.art.apex]" + }, + { "name": "libnativeloader_test[com.google.android.art.apex]" } ], @@ -2584,6 +2587,9 @@ "name": "art_standalone_sigchain_tests" }, { + "name": "libnativeloader_e2e_tests" + }, + { "name": "libnativeloader_test" } ] diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py index 428baf4ab9..fa9ca5a137 100755 --- a/build/apex/art_apex_test.py +++ b/build/apex/art_apex_test.py @@ -698,6 +698,7 @@ class TestingTargetChecker: # Check ART jar files which are needed for gtests. self._checker.check_art_test_data('art-gtest-jars-AbstractMethod.jar') + self._checker.check_art_test_data('art-gtest-jars-ArrayClassWithUnresolvedComponent.dex') self._checker.check_art_test_data('art-gtest-jars-MyClassNatives.jar') self._checker.check_art_test_data('art-gtest-jars-Main.jar') self._checker.check_art_test_data('art-gtest-jars-ProtoCompare.jar') @@ -749,6 +750,7 @@ class TestingTargetChecker: self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressed.jar') self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexTestDex.jar') self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexPublicSdkDex.dex') + self._checker.check_art_test_data('art-gtest-jars-SuperWithAccessChecks.dex') class NoSuperfluousBinariesChecker: diff --git a/build/apex/manifest-art.json b/build/apex/manifest-art.json index bf45076b0d..4f20be67d2 100644 --- a/build/apex/manifest-art.json +++ b/build/apex/manifest-art.json @@ -1,6 +1,10 @@ { "name": "com.android.art", - "version": 339990000, + + // Placeholder module version to be replaced during build. + // Do not change! + "version": 0, + "provideNativeLibs": [ "libjdwp.so" ], diff --git a/build/sdk/Android.bp b/build/sdk/Android.bp index a9ee6754a1..b47bdc2fee 100644 --- a/build/sdk/Android.bp +++ b/build/sdk/Android.bp @@ -93,17 +93,10 @@ art_module_sdk { }, android: { - bootclasspath_fragments: [ - // Adds the fragment and its contents to the sdk. - "art-bootclasspath-fragment", - ], - - systemserverclasspath_fragments: [ - "art-systemserverclasspath-fragment", - ], - - compat_configs: [ - "libcore-platform-compat-config", + apexes: [ + // Adds exportable dependencies of the API to the sdk, + // e.g. *classpath_fragments. + "com.android.art", ], java_header_libs: [ diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 7002636d4e..e578d3bf7f 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -85,7 +85,7 @@ void JitCompiler::ParseCompilerOptions() { if (StartsWith(option, "--instruction-set-variant=")) { const char* str = option.c_str() + strlen("--instruction-set-variant="); VLOG(compiler) << "JIT instruction set variant " << str; - instruction_set_features = InstructionSetFeatures::FromVariant( + instruction_set_features = InstructionSetFeatures::FromVariantAndHwcap( instruction_set, str, &error_msg); if (instruction_set_features == nullptr) { LOG(WARNING) << "Error parsing " << option << " message=" << error_msg; diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp index 26cbd51459..941b9e0e95 100644 --- a/dex2oat/Android.bp +++ b/dex2oat/Android.bp @@ -459,6 +459,7 @@ art_cc_defaults { name: "art_dex2oat_tests_defaults", data: [ ":art-gtest-jars-AbstractMethod", + ":art-gtest-jars-ArrayClassWithUnresolvedComponent", ":art-gtest-jars-DefaultMethods", ":art-gtest-jars-Dex2oatVdexPublicSdkDex", ":art-gtest-jars-Dex2oatVdexTestDex", @@ -479,6 +480,7 @@ art_cc_defaults { ":art-gtest-jars-StaticLeafMethods", ":art-gtest-jars-Statics", ":art-gtest-jars-StringLiterals", + ":art-gtest-jars-SuperWithAccessChecks", ":art-gtest-jars-VerifierDeps", ":art-gtest-jars-VerifierDepsMulti", ":art-gtest-jars-VerifySoftFailDuringClinit", diff --git a/dex2oat/art_standalone_dex2oat_tests.xml b/dex2oat/art_standalone_dex2oat_tests.xml index 32346f2f23..8bd0fb1fac 100644 --- a/dex2oat/art_standalone_dex2oat_tests.xml +++ b/dex2oat/art_standalone_dex2oat_tests.xml @@ -23,6 +23,8 @@ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> <option name="cleanup" value="true" /> <option name="push" value="art-gtest-jars-AbstractMethod.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-AbstractMethod.jar" /> + <option name="push" value="art-gtest-jars-ArrayClassWithUnresolvedComponent.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-ArrayClassWithUnresolvedComponent.dex" /> + <option name="push" value="art-gtest-jars-SuperWithAccessChecks.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-SuperWithAccessChecks.dex" /> <option name="push" value="art-gtest-jars-DefaultMethods.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-DefaultMethods.jar" /> <option name="push" value="art-gtest-jars-Dex2oatVdexPublicSdkDex.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Dex2oatVdexPublicSdkDex.dex" /> <option name="push" value="art-gtest-jars-Dex2oatVdexTestDex.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Dex2oatVdexTestDex.jar" /> diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 9e6103b424..27bae654c3 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -607,8 +607,13 @@ class Dex2Oat final { } void ParseInstructionSetVariant(const std::string& option, ParserOptions* parser_options) { - compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant( - compiler_options_->instruction_set_, option, &parser_options->error_msg); + if (kIsTargetBuild) { + compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariantAndHwcap( + compiler_options_->instruction_set_, option, &parser_options->error_msg); + } else { + compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant( + compiler_options_->instruction_set_, option, &parser_options->error_msg); + } if (compiler_options_->instruction_set_features_ == nullptr) { Usage("%s", parser_options->error_msg.c_str()); } diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc index d9509b0c53..5a7217897d 100644 --- a/dex2oat/driver/compiler_driver.cc +++ b/dex2oat/driver/compiler_driver.cc @@ -1109,6 +1109,7 @@ static void MaybeAddToImageClasses(Thread* self, HashSet<std::string>* image_classes) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK_EQ(self, Thread::Current()); + DCHECK(klass->IsResolved()); Runtime* runtime = Runtime::Current(); gc::Heap* heap = runtime->GetHeap(); if (heap->ObjectIsInBootImageSpace(klass)) { diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc index 33d122bdbe..82c87f504d 100644 --- a/dex2oat/linker/image_test.cc +++ b/dex2oat/linker/image_test.cc @@ -176,5 +176,37 @@ TEST_F(ImageTest, TestSoftVerificationFailureDuringClassInitialization) { /*image_classes_failing_aot_clinit=*/ {"LClassToInitialize;"}); } +TEST_F(ImageTest, TestImageClassWithArrayClassWithUnresolvedComponent) { + CompilationHelper helper; + Compile(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(), + helper, + "ArrayClassWithUnresolvedComponent", + /*image_classes=*/ {"LClassWithStatic;", + "LClassWithStaticConst;", + "[LClassWithMissingInterface;", + "[[LClassWithMissingInterface;", + "[LClassWithMissingSuper", + "[[LClassWithMissingSuper"}, + /*image_classes_failing_aot_clinit=*/ { + "LClassWithStatic;", + "LClassWithStaticConst;"}, + /*image_classes_failing_resolution=*/ { + "[LClassWithMissingInterface;", + "[[LClassWithMissingInterface;", + "[LClassWithMissingSuper", + "[[LClassWithMissingSuper"}); +} + +TEST_F(ImageTest, TestSuperWithAccessChecks) { + CompilationHelper helper; + Compile(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(), + helper, + "SuperWithAccessChecks", + /*image_classes=*/ {"LSubClass;", "LImplementsClass;"}, + /*image_classes_failing_aot_clinit=*/ {"LSubClass;", "LImplementsClass;"}); +} + } // namespace linker } // namespace art diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index 5c2d84cc5e..b570d99e64 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -86,7 +86,8 @@ class ImageTest : public CommonCompilerDriverTest { /*out*/ CompilationHelper& out_helper, const std::string& extra_dex = "", const std::initializer_list<std::string>& image_classes = {}, - const std::initializer_list<std::string>& image_classes_failing_aot_clinit = {}); + const std::initializer_list<std::string>& image_classes_failing_aot_clinit = {}, + const std::initializer_list<std::string>& image_classes_failing_resolution = {}); void SetUpRuntimeOptions(RuntimeOptions* options) override { CommonCompilerTest::SetUpRuntimeOptions(options); @@ -352,7 +353,8 @@ inline void ImageTest::Compile( CompilationHelper& helper, const std::string& extra_dex, const std::initializer_list<std::string>& image_classes, - const std::initializer_list<std::string>& image_classes_failing_aot_clinit) { + const std::initializer_list<std::string>& image_classes_failing_aot_clinit, + const std::initializer_list<std::string>& image_classes_failing_resolution) { for (const std::string& image_class : image_classes_failing_aot_clinit) { ASSERT_TRUE(ContainsElement(image_classes, image_class)); } @@ -375,12 +377,14 @@ inline void ImageTest::Compile( ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); for (const std::string& image_class : image_classes) { ObjPtr<mirror::Class> klass = - class_linker->FindSystemClass(Thread::Current(), image_class.c_str()); - EXPECT_TRUE(klass != nullptr); - EXPECT_TRUE(klass->IsResolved()); - if (ContainsElement(image_classes_failing_aot_clinit, image_class)) { + class_linker->LookupClass(Thread::Current(), image_class.c_str(), nullptr); + if (ContainsElement(image_classes_failing_resolution, image_class)) { + EXPECT_TRUE(klass == nullptr || klass->IsErroneousUnresolved()); + } else if (ContainsElement(image_classes_failing_aot_clinit, image_class)) { + ASSERT_TRUE(klass != nullptr) << image_class; EXPECT_FALSE(klass->IsInitialized()); } else { + ASSERT_TRUE(klass != nullptr) << image_class; EXPECT_TRUE(klass->IsInitialized()); } } diff --git a/dex2oat/verifier_deps_test.cc b/dex2oat/verifier_deps_test.cc index 708af04390..27eadec3fc 100644 --- a/dex2oat/verifier_deps_test.cc +++ b/dex2oat/verifier_deps_test.cc @@ -630,12 +630,5 @@ TEST_F(VerifierDepsTest, MultiDexVerification) { ASSERT_FALSE(buffer.empty()); } -TEST_F(VerifierDepsTest, Assignable_Arrays) { - ASSERT_TRUE(TestAssignabilityRecording(/* dst= */ "[LIface;", - /* src= */ "[LMyClassExtendingInterface;")); - ASSERT_FALSE(HasAssignable( - "LIface;", "LMyClassExtendingInterface;")); -} - } // namespace verifier } // namespace art diff --git a/libartbase/Android.bp b/libartbase/Android.bp index f9375239e4..80c63c6cfb 100644 --- a/libartbase/Android.bp +++ b/libartbase/Android.bp @@ -70,6 +70,7 @@ cc_defaults { // ZipArchive support, the order matters here to get all symbols. "libziparchive", ], + whole_static_libs: ["libtinyxml2"], shared_libs: [ "libz", "liblog", @@ -88,6 +89,7 @@ cc_defaults { static: { cflags: ["-DART_STATIC_LIBARTBASE"], }, + whole_static_libs: ["libtinyxml2"], shared_libs: [ "libziparchive", "libz", @@ -112,6 +114,7 @@ cc_defaults { // For common macros. "libbase", ], + whole_static_libs: ["libtinyxml2"], export_static_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h. cflags: ["-Wno-thread-safety"], @@ -391,6 +394,8 @@ cc_library_headers { shared_libs: ["libbase"], export_shared_lib_headers: ["libbase"], + whole_static_libs: ["libtinyxml2"], + apex_available: [ "com.android.art", "com.android.art.debug", diff --git a/libartbase/base/flags.h b/libartbase/base/flags.h index d1e1ca6243..fc568b2be2 100644 --- a/libartbase/base/flags.h +++ b/libartbase/base/flags.h @@ -304,6 +304,12 @@ struct Flags { // Note that the actual write is still controlled by // MetricsReportingMods and MetricsReportingNumMods. Flag<std::string> MetricsWriteToFile{"metrics.write-to-file", "", FlagType::kCmdlineOnly}; + + // The output format for metrics. This is only used + // when writing metrics to a file; metrics written + // to logcat will be in human-readable text format. + // Supported values are "text" and "xml". + Flag<std::string> MetricsFormat{"metrics.format", "text", FlagType::kCmdlineOnly}; }; // This is the actual instance of all the flags. diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h index d6f2463bb6..ebc44a9394 100644 --- a/libartbase/base/metrics/metrics.h +++ b/libartbase/base/metrics/metrics.h @@ -30,6 +30,7 @@ #include "android-base/logging.h" #include "base/bit_utils.h" #include "base/time_utils.h" +#include "tinyxml2.h" #pragma clang diagnostic push #pragma clang diagnostic error "-Wconversion" @@ -55,7 +56,15 @@ METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000) \ METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000) \ METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \ - METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) + METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \ + METRIC(GcWorldStopTime, MetricsCounter) \ + METRIC(GcWorldStopCount, MetricsCounter) \ + METRIC(YoungGcScannedBytes, MetricsCounter) \ + METRIC(YoungGcFreedBytes, MetricsCounter) \ + METRIC(YoungGcDuration, MetricsCounter) \ + METRIC(FullGcScannedBytes, MetricsCounter) \ + METRIC(FullGcFreedBytes, MetricsCounter) \ + METRIC(FullGcDuration, MetricsCounter) // A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS // and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be @@ -433,12 +442,80 @@ class MetricsAccumulator final : MetricsBase<T> { friend class ArtMetrics; }; -// A backend that writes metrics in a human-readable format to a string. +// Base class for formatting metrics into different formats +// (human-readable text, JSON, etc.) +class MetricsFormatter { + public: + virtual ~MetricsFormatter() = default; + + virtual void FormatBeginReport(uint64_t timestamp_since_start_ms, + const std::optional<SessionData>& session_data) = 0; + virtual void FormatEndReport() = 0; + virtual void FormatReportCounter(DatumId counter_type, uint64_t value) = 0; + virtual void FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) = 0; + virtual std::string GetAndResetBuffer() = 0; + + protected: + const std::string version = "1.0"; +}; + +// Formatter outputting metrics in human-readable text format +class TextFormatter : public MetricsFormatter { + public: + TextFormatter() = default; + + void FormatBeginReport(uint64_t timestamp_millis, + const std::optional<SessionData>& session_data) override; + + void FormatReportCounter(DatumId counter_type, uint64_t value) override; + + void FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) override; + + void FormatEndReport() override; + + std::string GetAndResetBuffer() override; + + private: + std::ostringstream os_; +}; + +// Formatter outputting metrics in XML format +class XmlFormatter : public MetricsFormatter { + public: + XmlFormatter() = default; + + void FormatBeginReport(uint64_t timestamp_millis, + const std::optional<SessionData>& session_data) override; + + void FormatReportCounter(DatumId counter_type, uint64_t value) override; + + void FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) override; + + void FormatEndReport() override; + + std::string GetAndResetBuffer() override; + + private: + tinyxml2::XMLDocument document_; +}; + +// A backend that writes metrics to a string. +// The format of the metrics' output is delegated +// to the MetricsFormatter class. // // This is used as a base for LogBackend and FileBackend. class StringBackend : public MetricsBackend { public: - StringBackend(); + explicit StringBackend(std::unique_ptr<MetricsFormatter> formatter); void BeginOrUpdateSession(const SessionData& session_data) override; @@ -456,14 +533,15 @@ class StringBackend : public MetricsBackend { std::string GetAndResetBuffer(); private: - std::ostringstream os_; + std::unique_ptr<MetricsFormatter> formatter_; std::optional<SessionData> session_data_; }; // A backend that writes metrics in human-readable format to the log (i.e. logcat). class LogBackend : public StringBackend { public: - explicit LogBackend(android::base::LogSeverity level); + explicit LogBackend(std::unique_ptr<MetricsFormatter> formatter, + android::base::LogSeverity level); void BeginReport(uint64_t timestamp_millis) override; void EndReport() override; @@ -473,12 +551,10 @@ class LogBackend : public StringBackend { }; // A backend that writes metrics to a file. -// -// These are currently written in the same human-readable format used by StringBackend and -// LogBackend, but we will probably want a more machine-readable format in the future. class FileBackend : public StringBackend { public: - explicit FileBackend(const std::string& filename); + explicit FileBackend(std::unique_ptr<MetricsFormatter> formatter, + const std::string& filename); void BeginReport(uint64_t timestamp_millis) override; void EndReport() override; diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc index f09987ba8e..025f5eb79e 100644 --- a/libartbase/base/metrics/metrics_common.cc +++ b/libartbase/base/metrics/metrics_common.cc @@ -76,7 +76,7 @@ void ArtMetrics::ReportAllMetrics(MetricsBackend* backend) const { } void ArtMetrics::DumpForSigQuit(std::ostream& os) const { - StringBackend backend; + StringBackend backend(std::make_unique<TextFormatter>()); ReportAllMetrics(&backend); os << backend.GetAndResetBuffer(); } @@ -88,13 +88,12 @@ void ArtMetrics::Reset() { #undef ART_METRIC } -StringBackend::StringBackend() {} +StringBackend::StringBackend(std::unique_ptr<MetricsFormatter> formatter) + : formatter_(std::move(formatter)) +{} std::string StringBackend::GetAndResetBuffer() { - std::string result = os_.str(); - os_.clear(); - os_.str(""); - return result; + return formatter_->GetAndResetBuffer(); } void StringBackend::BeginOrUpdateSession(const SessionData& session_data) { @@ -102,32 +101,51 @@ void StringBackend::BeginOrUpdateSession(const SessionData& session_data) { } void StringBackend::BeginReport(uint64_t timestamp_since_start_ms) { + formatter_->FormatBeginReport(timestamp_since_start_ms, session_data_); +} + +void StringBackend::EndReport() { + formatter_->FormatEndReport(); +} + +void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) { + formatter_->FormatReportCounter(counter_type, value); +} + +void StringBackend::ReportHistogram(DatumId histogram_type, + int64_t minimum_value_, + int64_t maximum_value_, + const std::vector<uint32_t>& buckets) { + formatter_->FormatReportHistogram(histogram_type, minimum_value_, maximum_value_, buckets); +} + +void TextFormatter::FormatBeginReport(uint64_t timestamp_since_start_ms, + const std::optional<SessionData>& session_data) { os_ << "\n*** ART internal metrics ***\n"; os_ << " Metadata:\n"; os_ << " timestamp_since_start_ms: " << timestamp_since_start_ms << "\n"; - if (session_data_.has_value()) { - os_ << " session_id: " << session_data_->session_id << "\n"; - os_ << " uid: " << session_data_->uid << "\n"; - os_ << " compilation_reason: " << CompilationReasonName(session_data_->compilation_reason) + if (session_data.has_value()) { + os_ << " session_id: " << session_data->session_id << "\n"; + os_ << " uid: " << session_data->uid << "\n"; + os_ << " compilation_reason: " << CompilationReasonName(session_data->compilation_reason) << "\n"; - os_ << " compiler_filter: " << CompilerFilterReportingName(session_data_->compiler_filter) + os_ << " compiler_filter: " << CompilerFilterReportingName(session_data->compiler_filter) << "\n"; } os_ << " Metrics:\n"; } -void StringBackend::EndReport() { os_ << "*** Done dumping ART internal metrics ***\n"; } - -void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) { +void TextFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) { os_ << " " << DatumName(counter_type) << ": count = " << value << "\n"; } -void StringBackend::ReportHistogram(DatumId histogram_type, - int64_t minimum_value_, - int64_t maximum_value_, - const std::vector<uint32_t>& buckets) { - os_ << " " << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_; - if (buckets.size() > 0) { +void TextFormatter::FormatReportHistogram(DatumId histogram_type, + int64_t minimum_value_, + int64_t maximum_value_, + const std::vector<uint32_t>& buckets) { + os_ << " " << DatumName(histogram_type) << ": range = " + << minimum_value_ << "..." << maximum_value_; + if (!buckets.empty()) { os_ << ", buckets: "; bool first = true; for (const auto& count : buckets) { @@ -143,22 +161,100 @@ void StringBackend::ReportHistogram(DatumId histogram_type, } } -LogBackend::LogBackend(android::base::LogSeverity level) : level_{level} {} +void TextFormatter::FormatEndReport() { + os_ << "*** Done dumping ART internal metrics ***\n"; +} + +std::string TextFormatter::GetAndResetBuffer() { + std::string result = os_.str(); + os_.clear(); + os_.str(""); + return result; +} + +void XmlFormatter::FormatBeginReport(uint64_t timestamp_millis, + const std::optional<SessionData>& session_data) { + tinyxml2::XMLElement* art_runtime_metrics = document_.NewElement("art_runtime_metrics"); + document_.InsertEndChild(art_runtime_metrics); + + art_runtime_metrics->InsertNewChildElement("version")->SetText(version.data()); + + tinyxml2::XMLElement* metadata = art_runtime_metrics->InsertNewChildElement("metadata"); + metadata->InsertNewChildElement("timestamp_since_start_ms")->SetText(timestamp_millis); + + if (session_data.has_value()) { + metadata->InsertNewChildElement("session_id")->SetText(session_data->session_id); + metadata->InsertNewChildElement("uid")->SetText(session_data->uid); + metadata + ->InsertNewChildElement("compilation_reason") + ->SetText(CompilationReasonName(session_data->compilation_reason)); + metadata + ->InsertNewChildElement("compiler_filter") + ->SetText(CompilerFilterReportingName(session_data->compiler_filter)); + } + + art_runtime_metrics->InsertNewChildElement("metrics"); +} + +void XmlFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) { + tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics"); + + tinyxml2::XMLElement* counter = metrics->InsertNewChildElement(DatumName(counter_type).data()); + counter->InsertNewChildElement("counter_type")->SetText("count"); + counter->InsertNewChildElement("value")->SetText(value); +} + +void XmlFormatter::FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) { + tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics"); + + tinyxml2::XMLElement* histogram = + metrics->InsertNewChildElement(DatumName(histogram_type).data()); + histogram->InsertNewChildElement("counter_type")->SetText("histogram"); + histogram->InsertNewChildElement("minimum_value")->SetText(low_value); + histogram->InsertNewChildElement("maximum_value")->SetText(high_value); + + tinyxml2::XMLElement* buckets_element = histogram->InsertNewChildElement("buckets"); + for (const auto& count : buckets) { + buckets_element->InsertNewChildElement("bucket")->SetText(count); + } +} + +void XmlFormatter::FormatEndReport() {} + +std::string XmlFormatter::GetAndResetBuffer() { + tinyxml2::XMLPrinter printer(/*file=*/nullptr, /*compact=*/true); + document_.Print(&printer); + std::string result = printer.CStr(); + document_.Clear(); + + return result; +} + +LogBackend::LogBackend(std::unique_ptr<MetricsFormatter> formatter, + android::base::LogSeverity level) + : StringBackend{std::move(formatter)}, level_{level} +{} void LogBackend::BeginReport(uint64_t timestamp_since_start_ms) { - GetAndResetBuffer(); + StringBackend::GetAndResetBuffer(); StringBackend::BeginReport(timestamp_since_start_ms); } void LogBackend::EndReport() { StringBackend::EndReport(); - LOG_STREAM(level_) << GetAndResetBuffer(); + LOG_STREAM(level_) << StringBackend::GetAndResetBuffer(); } -FileBackend::FileBackend(const std::string& filename) : filename_{filename} {} +FileBackend::FileBackend(std::unique_ptr<MetricsFormatter> formatter, + const std::string& filename) + : StringBackend{std::move(formatter)}, filename_{filename} +{} void FileBackend::BeginReport(uint64_t timestamp_since_start_ms) { - GetAndResetBuffer(); + StringBackend::GetAndResetBuffer(); StringBackend::BeginReport(timestamp_since_start_ms); } @@ -170,7 +266,7 @@ void FileBackend::EndReport() { if (file.get() == nullptr) { LOG(WARNING) << "Could open metrics file '" << filename_ << "': " << error_message; } else { - if (!android::base::WriteStringToFd(GetAndResetBuffer(), file.get()->Fd())) { + if (!android::base::WriteStringToFd(StringBackend::GetAndResetBuffer(), file.get()->Fd())) { PLOG(WARNING) << "Error writing metrics to file"; } } diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc index ed6f88d709..282029012b 100644 --- a/libartbase/base/metrics/metrics_test.cc +++ b/libartbase/base/metrics/metrics_test.cc @@ -16,6 +16,7 @@ #include "metrics.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "metrics_test.h" @@ -248,7 +249,7 @@ TEST_F(MetricsTest, HistogramTimer) { // Makes sure all defined metrics are included when dumping through StreamBackend. TEST_F(MetricsTest, StreamBackendDumpAllMetrics) { ArtMetrics metrics; - StringBackend backend; + StringBackend backend(std::make_unique<TextFormatter>()); metrics.ReportAllMetrics(&backend); @@ -305,6 +306,266 @@ TEST_F(MetricsTest, ResetMetrics) { metrics.ReportAllMetrics(&zero_backend); } +TEST(TextFormatterTest, ReportMetrics_WithBuckets) { + TextFormatter text_formatter; + SessionData session_data { + .session_id = 1000, + .uid = 50, + .compilation_reason = CompilationReason::kInstall, + .compiler_filter = CompilerFilterReporting::kSpeed, + }; + + text_formatter.FormatBeginReport(200, session_data); + text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u); + text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime, + 50, + 200, + {2, 4, 7, 1}); + text_formatter.FormatEndReport(); + + const std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 200\n" + " session_id: 1000\n" + " uid: 50\n" + " compilation_reason: install\n" + " compiler_filter: speed\n" + " Metrics:\n" + " FullGcCount: count = 1\n" + " FullGcCollectionTime: range = 50...200, buckets: 2,4,7,1\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(TextFormatterTest, ReportMetrics_NoBuckets) { + TextFormatter text_formatter; + SessionData session_data { + .session_id = 500, + .uid = 15, + .compilation_reason = CompilationReason::kCmdLine, + .compiler_filter = CompilerFilterReporting::kExtract, + }; + + text_formatter.FormatBeginReport(400, session_data); + text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime, 10, 20, {}); + text_formatter.FormatEndReport(); + + std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 400\n" + " session_id: 500\n" + " uid: 15\n" + " compilation_reason: cmdline\n" + " compiler_filter: extract\n" + " Metrics:\n" + " FullGcCollectionTime: range = 10...20, no buckets\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(TextFormatterTest, BeginReport_NoSessionData) { + TextFormatter text_formatter; + std::optional<SessionData> empty_session_data; + + text_formatter.FormatBeginReport(100, empty_session_data); + text_formatter.FormatEndReport(); + + std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 100\n" + " Metrics:\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(TextFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) { + TextFormatter text_formatter; + std::optional<SessionData> empty_session_data; + + text_formatter.FormatBeginReport(200, empty_session_data); + text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u); + text_formatter.FormatEndReport(); + + std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 200\n" + " Metrics:\n" + " FullGcCount: count = 1\n" + "*** Done dumping ART internal metrics ***\n"); + + text_formatter.FormatBeginReport(300, empty_session_data); + text_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u); + text_formatter.FormatEndReport(); + + result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 300\n" + " Metrics:\n" + " FullGcCount: count = 5\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(XmlFormatterTest, ReportMetrics_WithBuckets) { + XmlFormatter xml_formatter; + SessionData session_data { + .session_id = 123, + .uid = 456, + .compilation_reason = CompilationReason::kFirstBoot, + .compiler_filter = CompilerFilterReporting::kSpace, + }; + + xml_formatter.FormatBeginReport(250, session_data); + xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u); + xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime, + 300, + 600, + {1, 5, 3}); + xml_formatter.FormatEndReport(); + + const std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>250</timestamp_since_start_ms>" + "<session_id>123</session_id>" + "<uid>456</uid>" + "<compilation_reason>first-boot</compilation_reason>" + "<compiler_filter>space</compiler_filter>" + "</metadata>" + "<metrics>" + "<YoungGcCount>" + "<counter_type>count</counter_type>" + "<value>3</value>" + "</YoungGcCount>" + "<YoungGcCollectionTime>" + "<counter_type>histogram</counter_type>" + "<minimum_value>300</minimum_value>" + "<maximum_value>600</maximum_value>" + "<buckets>" + "<bucket>1</bucket>" + "<bucket>5</bucket>" + "<bucket>3</bucket>" + "</buckets>" + "</YoungGcCollectionTime>" + "</metrics>" + "</art_runtime_metrics>"); +} + +TEST(XmlFormatterTest, ReportMetrics_NoBuckets) { + XmlFormatter xml_formatter; + SessionData session_data { + .session_id = 234, + .uid = 345, + .compilation_reason = CompilationReason::kFirstBoot, + .compiler_filter = CompilerFilterReporting::kSpace, + }; + + xml_formatter.FormatBeginReport(160, session_data); + xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 4u); + xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime, 20, 40, {}); + xml_formatter.FormatEndReport(); + + const std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>160</timestamp_since_start_ms>" + "<session_id>234</session_id>" + "<uid>345</uid>" + "<compilation_reason>first-boot</compilation_reason>" + "<compiler_filter>space</compiler_filter>" + "</metadata>" + "<metrics>" + "<YoungGcCount>" + "<counter_type>count</counter_type>" + "<value>4</value>" + "</YoungGcCount>" + "<YoungGcCollectionTime>" + "<counter_type>histogram</counter_type>" + "<minimum_value>20</minimum_value>" + "<maximum_value>40</maximum_value>" + "<buckets/>" + "</YoungGcCollectionTime>" + "</metrics>" + "</art_runtime_metrics>"); +} + +TEST(XmlFormatterTest, BeginReport_NoSessionData) { + XmlFormatter xml_formatter; + std::optional<SessionData> empty_session_data; + + xml_formatter.FormatBeginReport(100, empty_session_data); + xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u); + xml_formatter.FormatEndReport(); + + std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>100</timestamp_since_start_ms>" + "</metadata>" + "<metrics>" + "<YoungGcCount>" + "<counter_type>count</counter_type>" + "<value>3</value>" + "</YoungGcCount>" + "</metrics>" + "</art_runtime_metrics>"); +} + +TEST(XmlFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) { + XmlFormatter xml_formatter; + std::optional<SessionData> empty_session_data; + + xml_formatter.FormatBeginReport(200, empty_session_data); + xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u); + xml_formatter.FormatEndReport(); + + std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>200</timestamp_since_start_ms>" + "</metadata>" + "<metrics>" + "<FullGcCount>" + "<counter_type>count</counter_type>" + "<value>1</value>" + "</FullGcCount>" + "</metrics>" + "</art_runtime_metrics>"); + + xml_formatter.FormatBeginReport(300, empty_session_data); + xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u); + xml_formatter.FormatEndReport(); + + result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>300</timestamp_since_start_ms>" + "</metadata>" + "<metrics>" + "<FullGcCount>" + "<counter_type>count</counter_type>" + "<value>5</value>" + "</FullGcCount>" + "</metrics>" + "</art_runtime_metrics>"); +} + TEST(CompilerFilterReportingTest, FromName) { ASSERT_EQ(CompilerFilterReportingFromName("error"), CompilerFilterReporting::kError); diff --git a/libartservice/Android.bp b/libartservice/Android.bp index 985a2eb956..9fac53b67c 100644 --- a/libartservice/Android.bp +++ b/libartservice/Android.bp @@ -49,66 +49,27 @@ java_sdk_library { // for JAR files in the System Server. name: "service-art", defaults: ["framework-system-server-module-defaults"], - permitted_packages: ["com.android.server.art"], - visibility: [ "//art:__subpackages__", "//frameworks/base/services/core", ], - impl_library_visibility: [ "//art/libartservice/tests", ], - - stubs_library_visibility: ["//visibility:public"], - stubs_source_visibility: ["//visibility:private"], - apex_available: [ "com.android.art", "com.android.art.debug", ], - sdk_version: "core_platform", + sdk_version: "system_server_current", min_sdk_version: "31", - - public: { - // Override the setting of "module_current" from the defaults as that is - // not available in all manifests where this needs to be built. - sdk_version: "core_current", - }, - - system_server: { - // Override the setting of "module_current" from the defaults as that is - // not available in all manifests where this needs to be built. - sdk_version: "core_current", - }, - - // The API elements are the ones annotated with - // libcore.api.CorePlatformApi(status=libcore.api.CorePlatformApi.Status.STABLE) - droiddoc_options: [ - "--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)", - ], - - // Temporarily disable compatibility with previous released APIs. - // TODO - remove once prototype has stabilized - // running "m update-api" will give instructions on what to do next - unsafe_ignore_missing_latest_api: true, - - // This cannot be accessed by apps using <uses-library> in their manifest. - shared_library: false, - // TODO(b/188773212): force dex compilation for inclusion in bootclasspath_fragment. - compile_dex: true, - srcs: [ - "service/java/com/android/server/art/ArtManagerLocal.java", + "service/java/**/*.java", ], - - libs: [ - "art.module.api.annotations.for.system.modules", + static_libs: [ ], - plugins: ["java_api_finder"], - dist_group: "android", + jarjar_rules: "jarjar-rules.txt", } art_cc_defaults { diff --git a/libartservice/api/current.txt b/libartservice/api/current.txt index c7844e0780..d802177e24 100644 --- a/libartservice/api/current.txt +++ b/libartservice/api/current.txt @@ -1,9 +1 @@ // Signature format: 2.0 -package com.android.server.art { - - public final class ArtManagerLocal { - ctor public ArtManagerLocal(); - } - -} - diff --git a/libartservice/jarjar-rules.txt b/libartservice/jarjar-rules.txt new file mode 100644 index 0000000000..c7d39e68c9 --- /dev/null +++ b/libartservice/jarjar-rules.txt @@ -0,0 +1,2 @@ +# Repackages static libraries to make them private to ART Services. +rule com.android.modules.utils.** com.android.server.art.jarjar.@0 diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index aac4b2508d..64aec7bf6b 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -16,15 +16,16 @@ package com.android.server.art; -import libcore.api.CorePlatformApi; +import android.annotation.SystemApi; /** - * This class provides a system API for functionality provided by the ART - * module. + * This class provides a system API for functionality provided by the ART module. + * + * @hide */ -@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public final class ArtManagerLocal { - static final String LOG_TAG = "ArtService"; + private static final String TAG = "ArtService"; public ArtManagerLocal() {} } diff --git a/libnativebridge/tests/Android.bp b/libnativebridge/tests/Android.bp index b787fbabb6..57d26c0316 100644 --- a/libnativebridge/tests/Android.bp +++ b/libnativebridge/tests/Android.bp @@ -110,6 +110,14 @@ cc_test { // unloading, so each test needs to be its own process. test_per_src: true, + // Disable pre-submit host unit-testing for this test module, as + // it is not compatible with TradeFed (because of the use of the + // `test_per_src` feature above) and meant to be executed with the + // `runtests.sh` script instead. + test_options: { + unit_test: false, + }, + srcs: [ "NativeBridgeApi.c", "CodeCacheCreate_test.cpp", diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp index f31c4302a4..f3c93a0ecc 100644 --- a/libnativeloader/library_namespaces.cpp +++ b/libnativeloader/library_namespaces.cpp @@ -81,16 +81,22 @@ constexpr const char* kSharedNamespaceSuffix = "-shared"; constexpr const char* kAlwaysPermittedDirectories = "/data:/mnt/expand"; constexpr const char* kVendorLibPath = "/vendor/" LIB; +// TODO(mast): It's unlikely that both paths are necessary for kProductLibPath +// below, because they can't be two separate directories - either one has to be +// a symlink to the other. constexpr const char* kProductLibPath = "/product/" LIB ":/system/product/" LIB; +constexpr const char* kSystemLibPath = "/system/" LIB ":/system_ext/" LIB; -const std::regex kVendorDexPathRegex("(^|:)/vendor/"); +const std::regex kVendorDexPathRegex("(^|:)(/system)?/vendor/"); const std::regex kProductDexPathRegex("(^|:)(/system)?/product/"); +const std::regex kSystemDexPathRegex("(^|:)/system(_ext)?/"); // MUST be tested last. -// Define origin of APK if it is from vendor partition or product partition +// Define origin partition of APK using ApkOrigin = enum { APK_ORIGIN_DEFAULT = 0, - APK_ORIGIN_VENDOR = 1, - APK_ORIGIN_PRODUCT = 2, + APK_ORIGIN_VENDOR = 1, // Includes both /vendor and /system/vendor + APK_ORIGIN_PRODUCT = 2, // Includes both /product and /system/product + APK_ORIGIN_SYSTEM = 3, // Includes both /system and /system_ext but not /system/{vendor,product} }; jobject GetParentClassLoader(JNIEnv* env, jobject class_loader) { @@ -113,6 +119,9 @@ ApkOrigin GetApkOriginFromDexPath(const std::string& dex_path) { apk_origin = APK_ORIGIN_PRODUCT; } + if (apk_origin == APK_ORIGIN_DEFAULT && std::regex_search(dex_path, kSystemDexPathRegex)) { + apk_origin = APK_ORIGIN_SYSTEM; + } return apk_origin; } @@ -231,51 +240,49 @@ Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t t std::string system_exposed_libraries = default_public_libraries(); std::string namespace_name = kClassloaderNamespaceName; ApkOrigin unbundled_app_origin = APK_ORIGIN_DEFAULT; - if ((apk_origin == APK_ORIGIN_VENDOR || - (apk_origin == APK_ORIGIN_PRODUCT && - is_product_vndk_version_defined())) && - !is_shared) { - unbundled_app_origin = apk_origin; - // For vendor / product apks, give access to the vendor / product lib even though - // they are treated as unbundled; the libs and apks are still bundled - // together in the vendor / product partition. - const char* origin_partition; - const char* origin_lib_path; - const char* llndk_libraries; - - switch (apk_origin) { - case APK_ORIGIN_VENDOR: - origin_partition = "vendor"; - origin_lib_path = kVendorLibPath; - llndk_libraries = llndk_libraries_vendor().c_str(); - break; - case APK_ORIGIN_PRODUCT: - origin_partition = "product"; - origin_lib_path = kProductLibPath; - llndk_libraries = llndk_libraries_product().c_str(); - break; - default: - origin_partition = "unknown"; - origin_lib_path = ""; - llndk_libraries = ""; - } - library_path = library_path + ":" + origin_lib_path; - permitted_path = permitted_path + ":" + origin_lib_path; - - // Also give access to LLNDK libraries since they are available to vendor or product - system_exposed_libraries = system_exposed_libraries + ":" + llndk_libraries; - - // Different name is useful for debugging - namespace_name = kVendorClassloaderNamespaceName; - ALOGD("classloader namespace configured for unbundled %s apk. library_path=%s", - origin_partition, library_path.c_str()); - } else { - auto libs = filter_public_libraries(target_sdk_version, uses_libraries, - extended_public_libraries()); - // extended public libraries are NOT available to vendor apks, otherwise it - // would be system->vendor violation. - if (!libs.empty()) { - system_exposed_libraries = system_exposed_libraries + ':' + libs; + const char* apk_origin_msg = "other apk"; // Only for debug logging. + + if (!is_shared) { + if (apk_origin == APK_ORIGIN_SYSTEM) { + // System apps commonly get shared namespaces and hence don't need this. + // In practice it's necessary for shared system libraries (i.e. JARs + // rather than actual APKs) that are loaded by ordinary apps which don't + // get shared namespaces. + apk_origin_msg = "system apk"; + + // Give access to all libraries in the system and system_ext partitions + // (they can freely access each other's private APIs). + library_path = library_path + ":" + kSystemLibPath; + permitted_path = permitted_path + ":" + kSystemLibPath; + } else if (apk_origin == APK_ORIGIN_VENDOR) { + unbundled_app_origin = APK_ORIGIN_VENDOR; + apk_origin_msg = "unbundled vendor apk"; + + // For vendor apks, give access to the vendor libs even though they are + // treated as unbundled; the libs and apks are still bundled together in the + // vendor partition. + library_path = library_path + ':' + kVendorLibPath; + permitted_path = permitted_path + ':' + kVendorLibPath; + + // Also give access to LLNDK libraries since they are available to vendor. + system_exposed_libraries = system_exposed_libraries + ':' + llndk_libraries_vendor(); + + // Different name is useful for debugging + namespace_name = kVendorClassloaderNamespaceName; + } else if (apk_origin == APK_ORIGIN_PRODUCT && is_product_vndk_version_defined()) { + unbundled_app_origin = APK_ORIGIN_PRODUCT; + apk_origin_msg = "unbundled product apk"; + + // Like for vendor apks, give access to the product libs since they are + // bundled together in the same partition. + library_path = library_path + ':' + kProductLibPath; + permitted_path = permitted_path + ':' + kProductLibPath; + + // Also give access to LLNDK libraries since they are available to product. + system_exposed_libraries = system_exposed_libraries + ':' + llndk_libraries_product(); + + // Different name is useful for debugging + namespace_name = kVendorClassloaderNamespaceName; } } @@ -285,6 +292,31 @@ Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t t namespace_name = namespace_name + kSharedNamespaceSuffix; } + ALOGD( + "Configuring %s for %s %s. target_sdk_version=%u, uses_libraries=%s, library_path=%s, " + "permitted_path=%s", + namespace_name.c_str(), + apk_origin_msg, + dex_path.c_str(), + static_cast<unsigned>(target_sdk_version), + android::base::Join(uses_libraries, ':').c_str(), + library_path.c_str(), + permitted_path.c_str()); + + if (unbundled_app_origin != APK_ORIGIN_VENDOR) { + // Extended public libraries are NOT available to unbundled vendor apks, but + // they are to other apps, including those in system, system_ext, and + // product partitions. The reason is that when GSI is used, the system + // partition may get replaced, and then vendor apps may fail. It's fine for + // product apps, because that partition isn't mounted in GSI tests. + auto libs = + filter_public_libraries(target_sdk_version, uses_libraries, extended_public_libraries()); + if (!libs.empty()) { + ALOGD("Extending system_exposed_libraries: %s", libs.c_str()); + system_exposed_libraries = system_exposed_libraries + ':' + libs; + } + } + // Create the app namespace NativeLoaderNamespace* parent_ns = FindParentNamespaceByClassLoader(env, class_loader); // Heuristic: the first classloader with non-empty library_path is assumed to diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp index 9648713e70..080b4d6e46 100644 --- a/libnativeloader/native_loader_test.cpp +++ b/libnativeloader/native_loader_test.cpp @@ -167,13 +167,16 @@ INSTANTIATE_TEST_SUITE_P(NativeLoaderTests, NativeLoaderTest, testing::Bool()); ///////////////////////////////////////////////////////////////// -std::string default_public_and_extended_libraries() { - std::string public_libs = default_public_libraries(); - std::string ext_libs = extended_public_libraries(); +std::string append_extended_libraries(const std::string& libs) { + const std::string& ext_libs = extended_public_libraries(); if (!ext_libs.empty()) { - public_libs = public_libs + ":" + ext_libs; + return libs + ":" + ext_libs; } - return public_libs; + return libs; +} + +std::string default_public_and_extended_libraries() { + return append_extended_libraries(default_public_libraries()); } class NativeLoaderTest_Create : public NativeLoaderTest { @@ -380,7 +383,7 @@ TEST_P(NativeLoaderTest_Create, UnbundledProductApp) { expected_permitted_path = expected_permitted_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR; expected_shared_libs_to_platform_ns = - default_public_libraries() + ":" + llndk_libraries_product(); + append_extended_libraries(default_public_libraries() + ":" + llndk_libraries_product()); expected_link_with_vndk_product_ns = true; } SetExpectations(); diff --git a/libnativeloader/test/Android.bp b/libnativeloader/test/Android.bp index fb9ae0d9d3..1d3a07a5f3 100644 --- a/libnativeloader/test/Android.bp +++ b/libnativeloader/test/Android.bp @@ -24,57 +24,125 @@ package { } cc_library { - name: "libfoo.oem1", - srcs: ["test.cpp"], - cflags: ["-DLIBNAME=\"libfoo.oem1.so\""], - shared_libs: [ - "libbase", - ], + name: "libnativeloader_testlib", + srcs: [], + stl: "none", } -cc_library { - name: "libbar.oem1", - srcs: ["test.cpp"], - cflags: ["-DLIBNAME=\"libbar.oem1.so\""], - shared_libs: [ - "libbase", - ], +// This app is just an intermediate container to be able to include the .so +// library in the host test. It's not actually installed or started. +android_test_helper_app { + name: "library_container_app", + defaults: ["art_module_source_build_java_defaults"], + manifest: "library_container_app_manifest.xml", + compile_multilib: "both", + jni_libs: ["libnativeloader_testlib"], } -cc_library { - name: "libfoo.oem2", - srcs: ["test.cpp"], - cflags: ["-DLIBNAME=\"libfoo.oem2.so\""], - shared_libs: [ - "libbase", +java_library { + name: "loadlibrarytest_test_utils", + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.ext.truth", ], + srcs: ["src/android/test/lib/TestUtils.java"], } -cc_library { - name: "libbar.oem2", - srcs: ["test.cpp"], - cflags: ["-DLIBNAME=\"libbar.oem2.so\""], - shared_libs: [ - "libbase", - ], +// Test fixture that represents a shared library in /system/framework. +java_library { + name: "libnativeloader_system_shared_lib", + installable: true, + srcs: ["src/android/test/systemsharedlib/SystemSharedLib.java"], } -cc_library { - name: "libfoo.product1", - srcs: ["test.cpp"], - cflags: ["-DLIBNAME=\"libfoo.product1.so\""], - product_specific: true, - shared_libs: [ - "libbase", +// Test fixture that represents a shared library in /system_ext/framework. +java_library { + name: "libnativeloader_system_ext_shared_lib", + installable: true, + srcs: ["src/android/test/systemextsharedlib/SystemExtSharedLib.java"], +} + +java_defaults { + name: "loadlibrarytest_app_defaults", + defaults: ["art_module_source_build_java_defaults"], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "loadlibrarytest_test_utils", + ], + libs: [ + "libnativeloader_system_shared_lib", + "libnativeloader_system_ext_shared_lib", ], } -cc_library { - name: "libbar.product1", - srcs: ["test.cpp"], - cflags: ["-DLIBNAME=\"libbar.product1.so\""], +android_test_helper_app { + name: "loadlibrarytest_system_priv_app", + defaults: ["loadlibrarytest_app_defaults"], + manifest: "loadlibrarytest_system_priv_app_manifest.xml", + // /system/priv-app currently reuses the same test as /system/app. + srcs: ["src/android/test/app/SystemAppTest.java"], +} + +android_test_helper_app { + name: "loadlibrarytest_system_app", + defaults: ["loadlibrarytest_app_defaults"], + manifest: "loadlibrarytest_system_app_manifest.xml", + srcs: ["src/android/test/app/SystemAppTest.java"], +} + +android_test_helper_app { + name: "loadlibrarytest_system_ext_app", + defaults: ["loadlibrarytest_app_defaults"], + system_ext_specific: true, + manifest: "loadlibrarytest_system_ext_app_manifest.xml", + // /system_ext should behave the same as /system, so use the same test class there. + srcs: ["src/android/test/app/SystemAppTest.java"], +} + +android_test_helper_app { + name: "loadlibrarytest_product_app", + defaults: ["loadlibrarytest_app_defaults"], product_specific: true, - shared_libs: [ - "libbase", + manifest: "loadlibrarytest_product_app_manifest.xml", + srcs: ["src/android/test/app/ProductAppTest.java"], +} + +android_test_helper_app { + name: "loadlibrarytest_vendor_app", + defaults: ["loadlibrarytest_app_defaults"], + vendor: true, + manifest: "loadlibrarytest_vendor_app_manifest.xml", + srcs: ["src/android/test/app/VendorAppTest.java"], +} + +// A normal app installed in /data. +android_test_helper_app { + name: "loadlibrarytest_data_app", + defaults: ["loadlibrarytest_app_defaults"], + manifest: "loadlibrarytest_data_app_manifest.xml", + srcs: ["src/android/test/app/DataAppTest.java"], +} + +java_test_host { + name: "libnativeloader_e2e_tests", + defaults: ["art_module_source_build_java_defaults"], + srcs: ["src/android/test/hostside/*.java"], + libs: [ + "compatibility-tradefed", + "tradefed", + ], + data: [ + ":library_container_app", + ":libnativeloader_system_shared_lib", + ":libnativeloader_system_ext_shared_lib", + ":loadlibrarytest_system_priv_app", + ":loadlibrarytest_system_app", + ":loadlibrarytest_system_ext_app", + ":loadlibrarytest_product_app", + ":loadlibrarytest_vendor_app", + ":loadlibrarytest_data_app", ], + test_config: "libnativeloader_e2e_tests.xml", + test_suites: ["general-tests"], } diff --git a/libnativeloader/test/Android.mk b/libnativeloader/test/Android.mk deleted file mode 100644 index 95fa68af68..0000000000 --- a/libnativeloader/test/Android.mk +++ /dev/null @@ -1,72 +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) -LOCAL_MODULE := public.libraries-oem1.txt -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SRC_FILES:= $(LOCAL_MODULE) -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC) -include $(BUILD_PREBUILT) - -include $(CLEAR_VARS) -LOCAL_MODULE := public.libraries-oem2.txt -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SRC_FILES:= $(LOCAL_MODULE) -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC) -include $(BUILD_PREBUILT) - -include $(CLEAR_VARS) -LOCAL_MODULE := public.libraries-product1.txt -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_SRC_FILES:= $(LOCAL_MODULE) -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_ETC) -include $(BUILD_PREBUILT) - -include $(CLEAR_VARS) -LOCAL_PACKAGE_NAME := oemlibrarytest-system -LOCAL_MODULE_TAGS := tests -LOCAL_MANIFEST_FILE := system/AndroidManifest.xml -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_SDK_VERSION := current -LOCAL_PROGUARD_ENABLED := disabled -LOCAL_MODULE_PATH := $(TARGET_OUT_APPS) -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -include $(BUILD_PACKAGE) - -include $(CLEAR_VARS) -LOCAL_PACKAGE_NAME := oemlibrarytest-vendor -LOCAL_MODULE_TAGS := tests -LOCAL_MANIFEST_FILE := vendor/AndroidManifest.xml -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_SDK_VERSION := current -LOCAL_PROGUARD_ENABLED := disabled -LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_APPS) -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -include $(BUILD_PACKAGE) diff --git a/libnativeloader/test/libnativeloader_e2e_tests.xml b/libnativeloader/test/libnativeloader_e2e_tests.xml new file mode 100644 index 0000000000..b1333b0765 --- /dev/null +++ b/libnativeloader/test/libnativeloader_e2e_tests.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<configuration description="Config for libnativeloader e2e test cases"> + <option name="test-suite-tag" value="libnativeloader_e2e_tests" /> + <option name="test-suite-tag" value="apct" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + + <test class="com.android.tradefed.testtype.HostTest" > + <option name="jar" value="libnativeloader_e2e_tests.jar" /> + </test> + + <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. --> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" /> +</configuration> diff --git a/libnativeloader/test/system/AndroidManifest.xml b/libnativeloader/test/library_container_app_manifest.xml index c3048891a8..20030de7eb 100644 --- a/libnativeloader/test/system/AndroidManifest.xml +++ b/libnativeloader/test/library_container_app_manifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2022 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. @@ -16,16 +16,5 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.test.app.system"> - - <application> - <activity android:name="android.test.app.TestActivity" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> - + package="android.test.app.container"> </manifest> - diff --git a/libnativeloader/test/loadlibrarytest_data_app_manifest.xml b/libnativeloader/test/loadlibrarytest_data_app_manifest.xml new file mode 100644 index 0000000000..2af0af417e --- /dev/null +++ b/libnativeloader/test/loadlibrarytest_data_app_manifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2022 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.app.data"> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.app.data" /> + <application> + <uses-library android:name="android.test.systemsharedlib" /> + <uses-library android:name="android.test.systemextsharedlib" /> + <uses-native-library android:required="false" android:name="libfoo.oem1.so" /> + <uses-native-library android:required="false" android:name="libbar.oem1.so" /> + <uses-native-library android:required="false" android:name="libfoo.oem2.so" /> + <!--libbar.oem2.so is left out --> + <uses-native-library android:required="false" android:name="libfoo.product1.so" /> + <uses-native-library android:required="false" android:name="libbar.product1.so" /> + </application> +</manifest> + diff --git a/libnativeloader/test/loadlibrarytest_product_app_manifest.xml b/libnativeloader/test/loadlibrarytest_product_app_manifest.xml new file mode 100644 index 0000000000..614f33ffc9 --- /dev/null +++ b/libnativeloader/test/loadlibrarytest_product_app_manifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.app.product"> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.app.product" /> + <application> + <uses-library android:name="android.test.systemsharedlib" /> + <uses-library android:name="android.test.systemextsharedlib" /> + <uses-native-library android:required="false" android:name="libfoo.oem1.so" /> + <uses-native-library android:required="false" android:name="libbar.oem1.so" /> + <uses-native-library android:required="false" android:name="libfoo.oem2.so" /> + <!--libbar.oem2.so is left out --> + <uses-native-library android:required="false" android:name="libfoo.product1.so" /> + <uses-native-library android:required="false" android:name="libbar.product1.so" /> + </application> +</manifest> + diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/loadlibrarytest_system_app_manifest.xml index c4c1a9c210..5711f651cf 100644 --- a/libnativeloader/test/vendor/AndroidManifest.xml +++ b/libnativeloader/test/loadlibrarytest_system_app_manifest.xml @@ -16,16 +16,16 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.test.app.vendor"> - + package="android.test.app.system"> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.app.system" /> <application> - <activity android:name="android.test.app.TestActivity" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> + <uses-library android:name="android.test.systemsharedlib" /> + <uses-library android:name="android.test.systemextsharedlib" /> + <!-- System apps get a shared classloader namespace, so they don't need + uses-native-library entries for anything in /system. --> + <uses-native-library android:required="false" android:name="libfoo.product1.so" /> + <uses-native-library android:required="false" android:name="libbar.product1.so" /> </application> - </manifest> diff --git a/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml b/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml new file mode 100644 index 0000000000..8aa3fa9f1f --- /dev/null +++ b/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.app.system_ext"> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.app.system_ext" /> + <application> + <uses-library android:name="android.test.systemsharedlib" /> + <uses-library android:name="android.test.systemextsharedlib" /> + <!-- System apps get a shared classloader namespace, so they don't need + uses-native-library entries for anything in /system. --> + <uses-native-library android:required="false" android:name="libfoo.product1.so" /> + <uses-native-library android:required="false" android:name="libbar.product1.so" /> + </application> +</manifest> + diff --git a/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml b/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml new file mode 100644 index 0000000000..126453c86f --- /dev/null +++ b/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.app.system_priv"> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.app.system_priv" /> + <application> + <uses-library android:name="android.test.systemsharedlib" /> + <uses-library android:name="android.test.systemextsharedlib" /> + <!-- System apps get a shared classloader namespace, so they don't need + uses-native-library entries for anything in /system. --> + <uses-native-library android:required="false" android:name="libfoo.product1.so" /> + <uses-native-library android:required="false" android:name="libbar.product1.so" /> + </application> +</manifest> + diff --git a/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml b/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml new file mode 100644 index 0000000000..a2a9f648f4 --- /dev/null +++ b/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.app.vendor"> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.app.vendor" /> + <application> + <uses-library android:name="android.test.systemsharedlib" /> + <uses-library android:name="android.test.systemextsharedlib" /> + <uses-native-library android:required="false" android:name="libfoo.oem1.so" /> + <uses-native-library android:required="false" android:name="libbar.oem1.so" /> + <uses-native-library android:required="false" android:name="libfoo.oem2.so" /> + <!--libbar.oem2.so is left out --> + <uses-native-library android:required="false" android:name="libfoo.product1.so" /> + <uses-native-library android:required="false" android:name="libbar.product1.so" /> + </application> +</manifest> + diff --git a/libnativeloader/test/public.libraries-oem1.txt b/libnativeloader/test/public.libraries-oem1.txt deleted file mode 100644 index f9433e2a04..0000000000 --- a/libnativeloader/test/public.libraries-oem1.txt +++ /dev/null @@ -1,2 +0,0 @@ -libfoo.oem1.so -libbar.oem1.so diff --git a/libnativeloader/test/public.libraries-oem2.txt b/libnativeloader/test/public.libraries-oem2.txt deleted file mode 100644 index de6bdb08e0..0000000000 --- a/libnativeloader/test/public.libraries-oem2.txt +++ /dev/null @@ -1,2 +0,0 @@ -libfoo.oem2.so -libbar.oem2.so diff --git a/libnativeloader/test/public.libraries-product1.txt b/libnativeloader/test/public.libraries-product1.txt deleted file mode 100644 index 358154c626..0000000000 --- a/libnativeloader/test/public.libraries-product1.txt +++ /dev/null @@ -1,2 +0,0 @@ -libfoo.product1.so -libbar.product1.so diff --git a/libnativeloader/test/runtest.sh b/libnativeloader/test/runtest.sh deleted file mode 100755 index 40beb5b4d5..0000000000 --- a/libnativeloader/test/runtest.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -adb root -adb remount -adb sync -adb shell stop -adb shell start -sleep 5 # wait until device reboots -adb logcat -c; -adb shell am start -n android.test.app.system/android.test.app.TestActivity -adb shell am start -n android.test.app.vendor/android.test.app.TestActivity -adb logcat | grep android.test.app diff --git a/libnativeloader/test/src/android/test/app/DataAppTest.java b/libnativeloader/test/src/android/test/app/DataAppTest.java new file mode 100644 index 0000000000..767a7b19ff --- /dev/null +++ b/libnativeloader/test/src/android/test/app/DataAppTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 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 android.test.app; + +import android.test.lib.TestUtils; +import android.test.systemextsharedlib.SystemExtSharedLib; +import android.test.systemsharedlib.SystemSharedLib; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DataAppTest { + @Test + public void testLoadExtendedPublicLibraries() { + System.loadLibrary("foo.oem1"); + System.loadLibrary("bar.oem1"); + System.loadLibrary("foo.oem2"); + TestUtils.assertLinkerNamespaceError( + () -> System.loadLibrary("bar.oem2")); // Missing <uses-native-library>. + System.loadLibrary("foo.product1"); + System.loadLibrary("bar.product1"); + } + + @Test + public void testLoadPrivateLibraries() { + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_private1")); + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("systemext_private1")); + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("product_private1")); + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemSharedLib() { + SystemSharedLib.loadLibrary("system_private2"); + SystemSharedLib.loadLibrary("systemext_private2"); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2")); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemExtSharedLib() { + SystemExtSharedLib.loadLibrary("system_private3"); + SystemExtSharedLib.loadLibrary("systemext_private3"); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3")); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3")); + } +} diff --git a/libnativeloader/test/src/android/test/app/ProductAppTest.java b/libnativeloader/test/src/android/test/app/ProductAppTest.java new file mode 100644 index 0000000000..1f36798515 --- /dev/null +++ b/libnativeloader/test/src/android/test/app/ProductAppTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 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 android.test.app; + +import android.test.lib.TestUtils; +import android.test.systemextsharedlib.SystemExtSharedLib; +import android.test.systemsharedlib.SystemSharedLib; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ProductAppTest { + @Test + public void testLoadExtendedPublicLibraries() { + System.loadLibrary("foo.oem1"); + System.loadLibrary("bar.oem1"); + System.loadLibrary("foo.oem2"); + TestUtils.assertLinkerNamespaceError( + () -> System.loadLibrary("bar.oem2")); // Missing <uses-native-library>. + System.loadLibrary("foo.product1"); + System.loadLibrary("bar.product1"); + } + + @Test + public void testLoadPrivateLibraries() { + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_private1")); + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("systemext_private1")); + System.loadLibrary("product_private1"); + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemSharedLib() { + SystemSharedLib.loadLibrary("system_private2"); + SystemSharedLib.loadLibrary("systemext_private2"); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2")); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemExtSharedLib() { + SystemExtSharedLib.loadLibrary("system_private3"); + SystemExtSharedLib.loadLibrary("systemext_private3"); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3")); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3")); + } +} diff --git a/libnativeloader/test/src/android/test/app/SystemAppTest.java b/libnativeloader/test/src/android/test/app/SystemAppTest.java new file mode 100644 index 0000000000..197a40cf0e --- /dev/null +++ b/libnativeloader/test/src/android/test/app/SystemAppTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 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 android.test.app; + +import android.test.lib.TestUtils; +import android.test.systemextsharedlib.SystemExtSharedLib; +import android.test.systemsharedlib.SystemSharedLib; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +// These tests are run from /system/app, /system/priv-app, and /system_ext/app. +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SystemAppTest { + @Test + public void testLoadExtendedPublicLibraries() { + System.loadLibrary("foo.oem1"); + System.loadLibrary("bar.oem1"); + System.loadLibrary("foo.oem2"); + System.loadLibrary("bar.oem2"); + System.loadLibrary("foo.product1"); + System.loadLibrary("bar.product1"); + } + + @Test + public void testLoadPrivateLibraries() { + System.loadLibrary("system_private1"); + System.loadLibrary("systemext_private1"); + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("product_private1")); + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemSharedLib() { + SystemSharedLib.loadLibrary("system_private2"); + SystemSharedLib.loadLibrary("systemext_private2"); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2")); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemExtSharedLib() { + SystemExtSharedLib.loadLibrary("system_private3"); + SystemExtSharedLib.loadLibrary("systemext_private3"); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3")); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3")); + } +} diff --git a/libnativeloader/test/src/android/test/app/TestActivity.java b/libnativeloader/test/src/android/test/app/TestActivity.java deleted file mode 100644 index a7a455d33d..0000000000 --- a/libnativeloader/test/src/android/test/app/TestActivity.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.test.app; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; - -public class TestActivity extends Activity { - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - tryLoadingLib("foo.oem1"); - tryLoadingLib("bar.oem1"); - tryLoadingLib("foo.oem2"); - tryLoadingLib("bar.oem2"); - tryLoadingLib("foo.product1"); - tryLoadingLib("bar.product1"); - } - - private void tryLoadingLib(String name) { - try { - System.loadLibrary(name); - Log.d(getPackageName(), "library " + name + " is successfully loaded"); - } catch (UnsatisfiedLinkError e) { - Log.d(getPackageName(), "failed to load libarary " + name, e); - } - } -} diff --git a/libnativeloader/test/src/android/test/app/VendorAppTest.java b/libnativeloader/test/src/android/test/app/VendorAppTest.java new file mode 100644 index 0000000000..c9ce8db57a --- /dev/null +++ b/libnativeloader/test/src/android/test/app/VendorAppTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 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 android.test.app; + +import android.test.lib.TestUtils; +import android.test.systemextsharedlib.SystemExtSharedLib; +import android.test.systemsharedlib.SystemSharedLib; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VendorAppTest { + @Test + public void testLoadExtendedPublicLibraries() { + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("foo.oem1")); + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("bar.oem1")); + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("foo.oem2")); + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("bar.oem2")); + System.loadLibrary("foo.product1"); + System.loadLibrary("bar.product1"); + } + + @Test + public void testLoadPrivateLibraries() { + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_private1")); + TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("systemext_private1")); + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("product_private1")); + // TODO(mast): The vendor app fails to load a private vendor library because it gets + // classified as untrusted_app in SELinux, which doesn't have access to vendor_file. Even an + // app in /vendor/priv-app, which gets classified as priv_app, still doesn't have access to + // vendor_file. Check that the test setup is correct and if this is WAI. + TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemSharedLib() { + SystemSharedLib.loadLibrary("system_private2"); + SystemSharedLib.loadLibrary("systemext_private2"); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2")); + TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2")); + } + + @Test + public void testLoadPrivateLibrariesViaSystemExtSharedLib() { + SystemExtSharedLib.loadLibrary("system_private3"); + SystemExtSharedLib.loadLibrary("systemext_private3"); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3")); + TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3")); + } +} diff --git a/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java new file mode 100644 index 0000000000..c9290377ee --- /dev/null +++ b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2022 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 android.test.hostside; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.IInvocationContext; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.IAbi; +import com.android.tradefed.testtype.junit4.AfterClassWithInfo; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.testtype.junit4.BeforeClassWithInfo; +import com.android.tradefed.util.CommandResult; +import com.google.common.io.ByteStreams; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test libnativeloader behavior for apps and libs in various partitions by overlaying them over + * the system partitions. Requires root. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class LibnativeloaderTest extends BaseHostJUnit4Test { + private static final String TAG = "LibnativeloaderTest"; + private static final String CLEANUP_PATHS_KEY = TAG + ":CLEANUP_PATHS"; + private static final String LOG_FILE_NAME = "TestActivity.log"; + + @BeforeClassWithInfo + public static void beforeClassWithDevice(TestInformation testInfo) throws Exception { + DeviceContext ctx = new DeviceContext( + testInfo.getContext(), testInfo.getDevice(), testInfo.getBuildInfo()); + + // A soft reboot is slow, so do setup for all tests and reboot once. + + ctx.mDevice.remountSystemWritable(); + + File libContainerApk = ctx.mBuildHelper.getTestFile("library_container_app.apk"); + try (ZipFile libApk = new ZipFile(libContainerApk)) { + ctx.pushExtendedPublicSystemOemLibs(libApk); + ctx.pushExtendedPublicProductLibs(libApk); + ctx.pushPrivateLibs(libApk); + } + ctx.pushSystemSharedLib("/system/framework", "android.test.systemsharedlib", + "libnativeloader_system_shared_lib.jar"); + ctx.pushSystemSharedLib("/system_ext/framework", "android.test.systemextsharedlib", + "libnativeloader_system_ext_shared_lib.jar"); + + // "Install" apps in various partitions through plain adb push followed by a soft reboot. We + // need them in these locations to test library loading restrictions, so for all except + // loadlibrarytest_data_app we cannot use ITestDevice.installPackage for it since it only + // installs in /data. + + // For testSystemPrivApp + ctx.pushApk("loadlibrarytest_system_priv_app", "/system/priv-app"); + + // For testSystemApp + ctx.pushApk("loadlibrarytest_system_app", "/system/app"); + + // For testSystemExtApp + ctx.pushApk("loadlibrarytest_system_ext_app", "/system_ext/app"); + + // For testProductApp + ctx.pushApk("loadlibrarytest_product_app", "/product/app"); + + // For testVendorApp + ctx.pushApk("loadlibrarytest_vendor_app", "/vendor/app"); + + ctx.softReboot(); + + // For testDataApp. Install this the normal way after the system server restart. + ctx.installPackage("loadlibrarytest_data_app"); + + testInfo.properties().put(CLEANUP_PATHS_KEY, ctx.mCleanup.getPathList()); + } + + @AfterClassWithInfo + public static void afterClassWithDevice(TestInformation testInfo) throws Exception { + ITestDevice device = testInfo.getDevice(); + + // Uninstall loadlibrarytest_data_app. + device.uninstallPackage("android.test.app.data"); + + String cleanupPathList = testInfo.properties().get(CLEANUP_PATHS_KEY); + CleanupPaths cleanup = new CleanupPaths(device, cleanupPathList); + cleanup.cleanup(); + } + + @Test + public void testSystemPrivApp() throws Exception { + // There's currently no difference in the tests between /system/priv-app and /system/app, so + // let's reuse the same one. + runDeviceTests("android.test.app.system_priv", "android.test.app.SystemAppTest"); + } + + @Test + public void testSystemApp() throws Exception { + runDeviceTests("android.test.app.system", "android.test.app.SystemAppTest"); + } + + @Test + public void testSystemExtApp() throws Exception { + // /system_ext should behave the same as /system, so run the same test class there. + runDeviceTests("android.test.app.system_ext", "android.test.app.SystemAppTest"); + } + + @Test + public void testProductApp() throws Exception { + runDeviceTests("android.test.app.product", "android.test.app.ProductAppTest"); + } + + @Test + public void testVendorApp() throws Exception { + runDeviceTests("android.test.app.vendor", "android.test.app.VendorAppTest"); + } + + @Test + public void testDataApp() throws Exception { + runDeviceTests("android.test.app.data", "android.test.app.DataAppTest"); + } + + // Utility class that keeps track of a set of paths the need to be deleted after testing. + private static class CleanupPaths { + private ITestDevice mDevice; + private List<String> mCleanupPaths; + + CleanupPaths(ITestDevice device) { + mDevice = device; + mCleanupPaths = new ArrayList<String>(); + } + + CleanupPaths(ITestDevice device, String pathList) { + mDevice = device; + mCleanupPaths = Arrays.asList(pathList.split(":")); + } + + String getPathList() { return String.join(":", mCleanupPaths); } + + // Adds the given path, or its topmost nonexisting parent directory, to the list of paths to + // clean up. + void addPath(String devicePath) throws DeviceNotAvailableException { + File path = new File(devicePath); + while (true) { + File parentPath = path.getParentFile(); + if (parentPath == null || mDevice.doesFileExist(parentPath.toString())) { + break; + } + path = parentPath; + } + String nonExistingPath = path.toString(); + if (!mCleanupPaths.contains(nonExistingPath)) { + mCleanupPaths.add(nonExistingPath); + } + } + + void cleanup() throws DeviceNotAvailableException { + // Clean up in reverse order in case several pushed files were in the same nonexisting + // directory. + for (int i = mCleanupPaths.size() - 1; i >= 0; --i) { + mDevice.deleteFile(mCleanupPaths.get(i)); + } + } + } + + // Class for code that needs an ITestDevice. It is instantiated both in tests and in + // (Before|After)ClassWithInfo. + private static class DeviceContext implements AutoCloseable { + IInvocationContext mContext; + ITestDevice mDevice; + CompatibilityBuildHelper mBuildHelper; + CleanupPaths mCleanup; + private String mTestArch; + + DeviceContext(IInvocationContext context, ITestDevice device, IBuildInfo buildInfo) { + mContext = context; + mDevice = device; + mBuildHelper = new CompatibilityBuildHelper(buildInfo); + mCleanup = new CleanupPaths(mDevice); + } + + public void close() throws DeviceNotAvailableException { mCleanup.cleanup(); } + + void pushExtendedPublicSystemOemLibs(ZipFile libApk) throws Exception { + pushNativeTestLib(libApk, "/system/${LIB}/libfoo.oem1.so"); + pushNativeTestLib(libApk, "/system/${LIB}/libbar.oem1.so"); + pushString("libfoo.oem1.so\n" + + "libbar.oem1.so\n", + "/system/etc/public.libraries-oem1.txt"); + + pushNativeTestLib(libApk, "/system/${LIB}/libfoo.oem2.so"); + pushNativeTestLib(libApk, "/system/${LIB}/libbar.oem2.so"); + pushString("libfoo.oem2.so\n" + + "libbar.oem2.so\n", + "/system/etc/public.libraries-oem2.txt"); + } + + void pushExtendedPublicProductLibs(ZipFile libApk) throws Exception { + pushNativeTestLib(libApk, "/product/${LIB}/libfoo.product1.so"); + pushNativeTestLib(libApk, "/product/${LIB}/libbar.product1.so"); + pushString("libfoo.product1.so\n" + + "libbar.product1.so\n", + "/product/etc/public.libraries-product1.txt"); + } + + void pushPrivateLibs(ZipFile libApk) throws Exception { + // Push the libraries once for each test. Since we cannot unload them, we need a fresh + // never-before-loaded library in each loadLibrary call. + for (int i = 1; i <= 3; ++i) { + pushNativeTestLib(libApk, "/system/${LIB}/libsystem_private" + i + ".so"); + pushNativeTestLib(libApk, "/system_ext/${LIB}/libsystemext_private" + i + ".so"); + pushNativeTestLib(libApk, "/product/${LIB}/libproduct_private" + i + ".so"); + pushNativeTestLib(libApk, "/vendor/${LIB}/libvendor_private" + i + ".so"); + } + } + + void pushSystemSharedLib(String packageDir, String packageName, String buildJarName) + throws Exception { + String path = packageDir + "/" + packageName + ".jar"; + pushFile(buildJarName, path); + pushString("<permissions>\n" + + "<library name=\"" + packageName + "\" file=\"" + path + "\" />\n" + + "</permissions>\n", + "system/etc/permissions/" + packageName + ".xml"); + } + + void softReboot() throws DeviceNotAvailableException { + assertCommandSucceeds("setprop dev.bootcomplete 0"); + assertCommandSucceeds("stop"); + assertCommandSucceeds("start"); + mDevice.waitForDeviceAvailable(); + } + + String getTestArch() throws DeviceNotAvailableException { + if (mTestArch == null) { + IAbi abi = mContext.getConfigurationDescriptor().getAbi(); + mTestArch = abi != null ? abi.getName() + : assertCommandSucceeds("getprop ro.bionic.arch"); + } + return mTestArch; + } + + // Pushes the given file contents to the device at the given destination path. destPath is + // assumed to have no risk of overlapping with existing files, and is deleted in tearDown(), + // along with any directory levels that had to be created. + void pushString(String fileContents, String destPath) throws DeviceNotAvailableException { + mCleanup.addPath(destPath); + assertThat(mDevice.pushString(fileContents, destPath)).isTrue(); + } + + // Like pushString, but pushes a data file included in the host test. + void pushFile(String fileName, String destPath) throws Exception { + mCleanup.addPath(destPath); + assertThat(mDevice.pushFile(mBuildHelper.getTestFile(fileName), destPath)).isTrue(); + } + + void pushApk(String apkBaseName, String destPath) throws Exception { + pushFile(apkBaseName + ".apk", + destPath + "/" + apkBaseName + "/" + apkBaseName + ".apk"); + } + + // Like pushString, but extracts libnativeloader_testlib.so from the library_container_app + // APK and pushes it to destPath. "${LIB}" is replaced with "lib" or "lib64" as appropriate. + void pushNativeTestLib(ZipFile libApk, String destPath) throws Exception { + String libApkPath = "lib/" + getTestArch() + "/libnativeloader_testlib.so"; + ZipEntry entry = libApk.getEntry(libApkPath); + assertWithMessage("Failed to find " + libApkPath + " in library_container_app.apk") + .that(entry) + .isNotNull(); + + File libraryTempFile; + try (InputStream inStream = libApk.getInputStream(entry)) { + libraryTempFile = writeStreamToTempFile("libnativeloader_testlib.so", inStream); + } + + String libDir = getTestArch().contains("64") ? "lib64" : "lib"; + destPath = destPath.replace("${LIB}", libDir); + + mCleanup.addPath(destPath); + assertThat(mDevice.pushFile(libraryTempFile, destPath)).isTrue(); + } + + void installPackage(String apkBaseName) throws Exception { + assertThat(mDevice.installPackage(mBuildHelper.getTestFile(apkBaseName + ".apk"), + false /* reinstall */)) + .isNull(); + } + + String assertCommandSucceeds(String command) throws DeviceNotAvailableException { + CommandResult result = mDevice.executeShellV2Command(command); + assertWithMessage(result.toString()).that(result.getExitCode()).isEqualTo(0); + // Remove trailing \n's. + return result.getStdout().trim(); + } + } + + static private File writeStreamToTempFile(String tempFileBaseName, InputStream inStream) + throws Exception { + File hostTempFile = File.createTempFile(tempFileBaseName, null); + try (FileOutputStream outStream = new FileOutputStream(hostTempFile)) { + ByteStreams.copy(inStream, outStream); + } + return hostTempFile; + } +} diff --git a/libnativeloader/test/src/android/test/lib/TestUtils.java b/libnativeloader/test/src/android/test/lib/TestUtils.java new file mode 100644 index 0000000000..eda9993a7f --- /dev/null +++ b/libnativeloader/test/src/android/test/lib/TestUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 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 android.test.lib; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import org.junit.function.ThrowingRunnable; + +public final class TestUtils { + public static void assertLibraryNotFound(ThrowingRunnable loadLibrary) { + Throwable t = assertThrows(UnsatisfiedLinkError.class, loadLibrary); + assertThat(t.getMessage()).containsMatch("dlopen failed: library .* not found"); + } + + public static void assertLinkerNamespaceError(ThrowingRunnable loadLibrary) { + Throwable t = assertThrows(UnsatisfiedLinkError.class, loadLibrary); + assertThat(t.getMessage()) + .containsMatch("dlopen failed: .* is not accessible for the namespace"); + } +} diff --git a/libnativeloader/test/src/android/test/systemextsharedlib/SystemExtSharedLib.java b/libnativeloader/test/src/android/test/systemextsharedlib/SystemExtSharedLib.java new file mode 100644 index 0000000000..1240e12e55 --- /dev/null +++ b/libnativeloader/test/src/android/test/systemextsharedlib/SystemExtSharedLib.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 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 android.test.systemextsharedlib; + +public final class SystemExtSharedLib { + public static void loadLibrary(String name) { System.loadLibrary(name); } +} diff --git a/libnativeloader/test/test.cpp b/libnativeloader/test/src/android/test/systemsharedlib/SystemSharedLib.java index b166928f0e..8e2af9f79c 100644 --- a/libnativeloader/test/test.cpp +++ b/libnativeloader/test/src/android/test/systemsharedlib/SystemSharedLib.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2022 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. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define LOG_TAG "oemlib" -#include <android-base/logging.h> -static __attribute__((constructor)) void test_lib_init() { - LOG(DEBUG) << LIBNAME << " loaded"; +package android.test.systemsharedlib; + +public final class SystemSharedLib { + public static void loadLibrary(String name) { System.loadLibrary(name); } } diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp index 8fee83a38a..55a1fdea2c 100644 --- a/odrefresh/Android.bp +++ b/odrefresh/Android.bp @@ -47,7 +47,6 @@ cc_defaults { ], static_libs: [ "libc++fs", - "libtinyxml2", ], tidy: true, tidy_disabled_srcs: [":art-apex-cache-info"], diff --git a/odrefresh/odr_metrics.cc b/odrefresh/odr_metrics.cc index 4bddb17bd8..a2ce51f65b 100644 --- a/odrefresh/odr_metrics.cc +++ b/odrefresh/odr_metrics.cc @@ -64,16 +64,16 @@ OdrMetrics::~OdrMetrics() { } } -void OdrMetrics::SetCompilationTime(int32_t seconds) { +void OdrMetrics::SetCompilationTime(int32_t millis) { switch (stage_) { case Stage::kPrimaryBootClasspath: - primary_bcp_compilation_seconds_ = seconds; + primary_bcp_compilation_millis_ = millis; break; case Stage::kSecondaryBootClasspath: - secondary_bcp_compilation_seconds_ = seconds; + secondary_bcp_compilation_millis_ = millis; break; case Stage::kSystemServerClasspath: - system_server_compilation_seconds_ = seconds; + system_server_compilation_millis_ = millis; break; case Stage::kCheck: case Stage::kComplete: @@ -119,28 +119,31 @@ bool OdrMetrics::ToRecord(/*out*/OdrMetricsRecord* record) const { if (!trigger_.has_value()) { return false; } + record->odrefresh_metrics_version = kOdrefreshMetricsVersion; record->art_apex_version = art_apex_version_; record->trigger = static_cast<uint32_t>(trigger_.value()); record->stage_reached = static_cast<uint32_t>(stage_); record->status = static_cast<uint32_t>(status_); - record->primary_bcp_compilation_seconds = primary_bcp_compilation_seconds_; - record->secondary_bcp_compilation_seconds = secondary_bcp_compilation_seconds_; - record->system_server_compilation_seconds = system_server_compilation_seconds_; record->cache_space_free_start_mib = cache_space_free_start_mib_; record->cache_space_free_end_mib = cache_space_free_end_mib_; + record->primary_bcp_compilation_millis = primary_bcp_compilation_millis_; + record->secondary_bcp_compilation_millis = secondary_bcp_compilation_millis_; + record->system_server_compilation_millis = system_server_compilation_millis_; return true; } void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) { - OdrMetricsRecord record; + OdrMetricsRecord record{}; if (!metrics->ToRecord(&record)) { LOG(ERROR) << "Attempting to report metrics without a compilation trigger."; return; } - // Preserve order from frameworks/proto_logging/stats/atoms.proto in metrics file written. - std::ofstream ofs(path); - ofs << record; + const android::base::Result<void>& result = record.WriteToFile(path); + if (!result.ok()) { + LOG(ERROR) << "Failed to report metrics to file: " << path + << ", error: " << result.error().message(); + } } } // namespace odrefresh diff --git a/odrefresh/odr_metrics.h b/odrefresh/odr_metrics.h index cd80beffc5..7ebc148684 100644 --- a/odrefresh/odr_metrics.h +++ b/odrefresh/odr_metrics.h @@ -126,7 +126,7 @@ class OdrMetrics final { static int32_t GetFreeSpaceMiB(const std::string& path); static void WriteToFile(const std::string& path, const OdrMetrics* metrics); - void SetCompilationTime(int32_t seconds); + void SetCompilationTime(int32_t millis); const std::string cache_directory_; const std::string metrics_file_; @@ -137,11 +137,11 @@ class OdrMetrics final { Stage stage_ = Stage::kUnknown; Status status_ = Status::kUnknown; - int32_t primary_bcp_compilation_seconds_ = 0; - int32_t secondary_bcp_compilation_seconds_ = 0; - int32_t system_server_compilation_seconds_ = 0; int32_t cache_space_free_start_mib_ = 0; int32_t cache_space_free_end_mib_ = 0; + int32_t primary_bcp_compilation_millis_ = 0; + int32_t secondary_bcp_compilation_millis_ = 0; + int32_t system_server_compilation_millis_ = 0; friend class ScopedOdrCompilationTimer; }; @@ -155,8 +155,8 @@ class ScopedOdrCompilationTimer final { ~ScopedOdrCompilationTimer() { auto elapsed_time = std::chrono::steady_clock::now() - start_; - auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_time); - metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_seconds.count())); + auto elapsed_millis = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time); + metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_millis.count())); } private: diff --git a/odrefresh/odr_metrics_record.cc b/odrefresh/odr_metrics_record.cc index fc135d3399..b8c2f80fea 100644 --- a/odrefresh/odr_metrics_record.cc +++ b/odrefresh/odr_metrics_record.cc @@ -14,59 +14,110 @@ * limitations under the License. */ +#include "android-base/logging.h" #include "odr_metrics_record.h" +#include "tinyxml2.h" #include <iosfwd> -#include <istream> -#include <ostream> -#include <streambuf> #include <string> namespace art { namespace odrefresh { -std::istream& operator>>(std::istream& is, OdrMetricsRecord& record) { - // Block I/O related exceptions - auto saved_exceptions = is.exceptions(); - is.exceptions(std::ios_base::iostate {}); +namespace { +android::base::Result<int64_t> ReadInt64(tinyxml2::XMLElement* parent, const char* name) { + tinyxml2::XMLElement* element = parent->FirstChildElement(name); + if (element == nullptr) { + return Errorf("Expected Odrefresh metric {} not found", name); + } - // The order here matches the field order of MetricsRecord. - is >> record.art_apex_version >> std::ws; - is >> record.trigger >> std::ws; - is >> record.stage_reached >> std::ws; - is >> record.status >> std::ws; - is >> record.primary_bcp_compilation_seconds >> std::ws; - is >> record.secondary_bcp_compilation_seconds >> std::ws; - is >> record.system_server_compilation_seconds >> std::ws; - is >> record.cache_space_free_start_mib >> std::ws; - is >> record.cache_space_free_end_mib >> std::ws; - - // Restore I/O related exceptions - is.exceptions(saved_exceptions); - return is; + int64_t metric; + tinyxml2::XMLError result = element->QueryInt64Text(&metric); + if (result == tinyxml2::XML_SUCCESS) { + return metric; + } else { + return Errorf("Odrefresh metric {} is not an int64", name); + } +} + +android::base::Result<int32_t> ReadInt32(tinyxml2::XMLElement* parent, const char* name) { + tinyxml2::XMLElement* element = parent->FirstChildElement(name); + if (element == nullptr) { + return Errorf("Expected Odrefresh metric {} not found", name); + } + + int32_t metric; + tinyxml2::XMLError result = element->QueryIntText(&metric); + if (result == tinyxml2::XML_SUCCESS) { + return metric; + } else { + return Errorf("Odrefresh metric {} is not an int32", name); + } +} + +template <typename T> +void AddMetric(tinyxml2::XMLElement* parent, const char* name, const T& value) { + parent->InsertNewChildElement(name)->SetText(value); } +} // namespace -std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record) { - static const char kSpace = ' '; +android::base::Result<void> OdrMetricsRecord::ReadFromFile(const std::string& filename) { + tinyxml2::XMLDocument xml_document; + tinyxml2::XMLError result = xml_document.LoadFile(filename.data()); + if (result != tinyxml2::XML_SUCCESS) { + return android::base::Error() << xml_document.ErrorStr(); + } - // Block I/O related exceptions - auto saved_exceptions = os.exceptions(); - os.exceptions(std::ios_base::iostate {}); + tinyxml2::XMLElement* metrics = xml_document.FirstChildElement("odrefresh_metrics"); + if (metrics == nullptr) { + return Errorf("odrefresh_metrics element not found in {}", filename); + } + + odrefresh_metrics_version = OR_RETURN(ReadInt32(metrics, "odrefresh_metrics_version")); + if (odrefresh_metrics_version != kOdrefreshMetricsVersion) { + return Errorf("odrefresh_metrics_version {} is different than expected ({})", + odrefresh_metrics_version, + kOdrefreshMetricsVersion); + } + + art_apex_version = OR_RETURN(ReadInt64(metrics, "art_apex_version")); + trigger = OR_RETURN(ReadInt32(metrics, "trigger")); + stage_reached = OR_RETURN(ReadInt32(metrics, "stage_reached")); + status = OR_RETURN(ReadInt32(metrics, "status")); + cache_space_free_start_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_start_mib")); + cache_space_free_end_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_end_mib")); + primary_bcp_compilation_millis = OR_RETURN( + ReadInt32(metrics, "primary_bcp_compilation_millis")); + secondary_bcp_compilation_millis = OR_RETURN( + ReadInt32(metrics, "secondary_bcp_compilation_millis")); + system_server_compilation_millis = OR_RETURN( + ReadInt32(metrics, "system_server_compilation_millis")); + return {}; +} + +android::base::Result<void> OdrMetricsRecord::WriteToFile(const std::string& filename) const { + tinyxml2::XMLDocument xml_document; + tinyxml2::XMLElement* metrics = xml_document.NewElement("odrefresh_metrics"); + xml_document.InsertEndChild(metrics); // The order here matches the field order of MetricsRecord. - os << record.art_apex_version << kSpace; - os << record.trigger << kSpace; - os << record.stage_reached << kSpace; - os << record.status << kSpace; - os << record.primary_bcp_compilation_seconds << kSpace; - os << record.secondary_bcp_compilation_seconds << kSpace; - os << record.system_server_compilation_seconds << kSpace; - os << record.cache_space_free_start_mib << kSpace; - os << record.cache_space_free_end_mib << std::endl; - - // Restore I/O related exceptions - os.exceptions(saved_exceptions); - return os; + AddMetric(metrics, "odrefresh_metrics_version", odrefresh_metrics_version); + AddMetric(metrics, "art_apex_version", art_apex_version); + AddMetric(metrics, "trigger", trigger); + AddMetric(metrics, "stage_reached", stage_reached); + AddMetric(metrics, "status", status); + AddMetric(metrics, "cache_space_free_start_mib", cache_space_free_start_mib); + AddMetric(metrics, "cache_space_free_end_mib", cache_space_free_end_mib); + AddMetric(metrics, "primary_bcp_compilation_millis", primary_bcp_compilation_millis); + AddMetric(metrics, "secondary_bcp_compilation_millis", secondary_bcp_compilation_millis); + AddMetric(metrics, "system_server_compilation_millis", system_server_compilation_millis); + + tinyxml2::XMLError result = xml_document.SaveFile(filename.data(), /*compact=*/true); + if (result == tinyxml2::XML_SUCCESS) { + return {}; + } else { + return android::base::Error() << xml_document.ErrorStr(); + } } } // namespace odrefresh diff --git a/odrefresh/odr_metrics_record.h b/odrefresh/odr_metrics_record.h index 9dd51a63cc..fa953374e4 100644 --- a/odrefresh/odr_metrics_record.h +++ b/odrefresh/odr_metrics_record.h @@ -20,42 +20,41 @@ #include <cstdint> #include <iosfwd> // For forward-declaration of std::string. +#include "android-base/result.h" +#include "tinyxml2.h" + namespace art { namespace odrefresh { // Default location for storing metrics from odrefresh. -constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.txt"; +constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.xml"; + +// Initial OdrefreshMetrics version +static constexpr int32_t kOdrefreshMetricsVersion = 2; // MetricsRecord is a simpler container for Odrefresh metric values reported to statsd. The order // and types of fields here mirror definition of `OdrefreshReported` in // frameworks/proto_logging/stats/atoms.proto. struct OdrMetricsRecord { + int32_t odrefresh_metrics_version; int64_t art_apex_version; int32_t trigger; int32_t stage_reached; int32_t status; - int32_t primary_bcp_compilation_seconds; - int32_t secondary_bcp_compilation_seconds; - int32_t system_server_compilation_seconds; int32_t cache_space_free_start_mib; int32_t cache_space_free_end_mib; -}; + int32_t primary_bcp_compilation_millis; + int32_t secondary_bcp_compilation_millis; + int32_t system_server_compilation_millis; -// Read a `MetricsRecord` from an `istream`. -// -// This method blocks istream related exceptions, the caller should check `is.fail()` is false after -// calling. -// -// Returns `is`. -std::istream& operator>>(std::istream& is, OdrMetricsRecord& record); - -// Write a `MetricsRecord` to an `ostream`. -// -// This method blocks ostream related exceptions, the caller should check `os.fail()` is false after -// calling. -// -// Returns `os` -std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record); + // Reads a `MetricsRecord` from an XML file. + // Returns an error if the XML document was not found or parsed correctly. + android::base::Result<void> ReadFromFile(const std::string& filename); + + // Writes a `MetricsRecord` to an XML file. + // Returns an error if the XML document was not saved correctly. + android::base::Result<void> WriteToFile(const std::string& filename) const; +}; } // namespace odrefresh } // namespace art diff --git a/odrefresh/odr_metrics_record_test.cc b/odrefresh/odr_metrics_record_test.cc index dd739d68f2..fbb5b99063 100644 --- a/odrefresh/odr_metrics_record_test.cc +++ b/odrefresh/odr_metrics_record_test.cc @@ -20,6 +20,8 @@ #include <fstream> +#include "android-base/result-gmock.h" +#include "android-base/stringprintf.h" #include "base/common_art_test.h" namespace art { @@ -27,86 +29,124 @@ namespace odrefresh { class OdrMetricsRecordTest : public CommonArtTest {}; +using android::base::testing::Ok; +using android::base::testing::HasError; +using android::base::testing::WithMessage; + TEST_F(OdrMetricsRecordTest, HappyPath) { - const OdrMetricsRecord expected { + const OdrMetricsRecord expected{ + .odrefresh_metrics_version = art::odrefresh::kOdrefreshMetricsVersion, .art_apex_version = 0x01233456'789abcde, .trigger = 0x01020304, .stage_reached = 0x11121314, .status = 0x21222324, - .primary_bcp_compilation_seconds = 0x31323334, - .secondary_bcp_compilation_seconds = 0x41424344, - .system_server_compilation_seconds = 0x51525354, .cache_space_free_start_mib = 0x61626364, - .cache_space_free_end_mib = 0x71727374 + .cache_space_free_end_mib = 0x71727374, + .primary_bcp_compilation_millis = 0x31323334, + .secondary_bcp_compilation_millis = 0x41424344, + .system_server_compilation_millis = 0x51525354 }; ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; - - { - std::ofstream ofs(file_path); - ofs << expected; - ASSERT_FALSE(ofs.fail()); - ofs.close(); - } + std::string file_path = dir.GetPath() + "/metrics-record.xml"; + ASSERT_THAT(expected.WriteToFile(file_path), Ok()); OdrMetricsRecord actual {}; - { - std::ifstream ifs(file_path); - ifs >> actual; - ASSERT_TRUE(ifs.eof()); - } + ASSERT_THAT(actual.ReadFromFile(file_path), Ok()); + ASSERT_EQ(expected.odrefresh_metrics_version, actual.odrefresh_metrics_version); ASSERT_EQ(expected.art_apex_version, actual.art_apex_version); ASSERT_EQ(expected.trigger, actual.trigger); ASSERT_EQ(expected.stage_reached, actual.stage_reached); ASSERT_EQ(expected.status, actual.status); - ASSERT_EQ(expected.primary_bcp_compilation_seconds, actual.primary_bcp_compilation_seconds); - ASSERT_EQ(expected.secondary_bcp_compilation_seconds, actual.secondary_bcp_compilation_seconds); - ASSERT_EQ(expected.system_server_compilation_seconds, actual.system_server_compilation_seconds); ASSERT_EQ(expected.cache_space_free_start_mib, actual.cache_space_free_start_mib); ASSERT_EQ(expected.cache_space_free_end_mib, actual.cache_space_free_end_mib); + ASSERT_EQ(expected.primary_bcp_compilation_millis, actual.primary_bcp_compilation_millis); + ASSERT_EQ(expected.secondary_bcp_compilation_millis, actual.secondary_bcp_compilation_millis); + ASSERT_EQ(expected.system_server_compilation_millis, actual.system_server_compilation_millis); ASSERT_EQ(0, memcmp(&expected, &actual, sizeof(expected))); } TEST_F(OdrMetricsRecordTest, EmptyInput) { ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; - - std::ifstream ifs(file_path); - OdrMetricsRecord record; - ifs >> record; + std::string file_path = dir.GetPath() + "/metrics-record.xml"; - ASSERT_TRUE(ifs.fail()); - ASSERT_TRUE(!ifs); + OdrMetricsRecord record{}; + ASSERT_THAT(record.ReadFromFile(file_path), testing::Not(Ok())); } -TEST_F(OdrMetricsRecordTest, ClosedInput) { +TEST_F(OdrMetricsRecordTest, UnexpectedInput) { ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; + std::string file_path = dir.GetPath() + "/metrics-record.xml"; - std::ifstream ifs(file_path); - ifs.close(); + std::ofstream ofs(file_path); + ofs << "<not_odrefresh_metrics></not_odrefresh_metrics>"; + ofs.close(); - OdrMetricsRecord record; - ifs >> record; + OdrMetricsRecord record{}; + ASSERT_THAT( + record.ReadFromFile(file_path), + HasError(WithMessage("odrefresh_metrics element not found in " + file_path))); +} + +TEST_F(OdrMetricsRecordTest, ExpectedElementNotFound) { + ScratchDir dir(/*keep_files=*/false); + std::string file_path = dir.GetPath() + "/metrics-record.xml"; - ASSERT_TRUE(ifs.fail()); - ASSERT_TRUE(!ifs); + std::ofstream ofs(file_path); + ofs << "<odrefresh_metrics>"; + ofs << "<not_valid_metric>25</not_valid_metric>"; + ofs << "</odrefresh_metrics>"; + ofs.close(); + + OdrMetricsRecord record{}; + ASSERT_THAT( + record.ReadFromFile(file_path), + HasError(WithMessage("Expected Odrefresh metric odrefresh_metrics_version not found"))); } -TEST_F(OdrMetricsRecordTest, ClosedOutput) { +TEST_F(OdrMetricsRecordTest, UnexpectedOdrefreshMetricsVersion) { ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; + std::string file_path = dir.GetPath() + "/metrics-record.xml"; std::ofstream ofs(file_path); + ofs << "<odrefresh_metrics>"; + ofs << "<odrefresh_metrics_version>0</odrefresh_metrics_version>"; + ofs << "</odrefresh_metrics>"; ofs.close(); - OdrMetricsRecord record {}; - ofs << record; + OdrMetricsRecord record{}; + std::string expected_error = android::base::StringPrintf( + "odrefresh_metrics_version 0 is different than expected (%d)", + kOdrefreshMetricsVersion); + ASSERT_THAT(record.ReadFromFile(file_path), + HasError(WithMessage(expected_error))); +} + +TEST_F(OdrMetricsRecordTest, UnexpectedType) { + ScratchDir dir(/*keep_files=*/false); + std::string file_path = dir.GetPath() + "/metrics-record.xml"; + + std::ofstream ofs(file_path); + ofs << "<odrefresh_metrics>"; + ofs << "<odrefresh_metrics_version>" << kOdrefreshMetricsVersion + << "</odrefresh_metrics_version>"; + ofs << "<art_apex_version>81966764218039518</art_apex_version>"; + ofs << "<trigger>16909060</trigger>"; + ofs << "<stage_reached>286397204</stage_reached>"; + ofs << "<status>abcd</status>"; // It should be an int32. + ofs << "<cache_space_free_start_mib>1633837924</cache_space_free_start_mib>"; + ofs << "<cache_space_free_end_mib>1903326068</cache_space_free_end_mib>"; + ofs << "<primary_bcp_compilation_millis>825373492</primary_bcp_compilation_millis>"; + ofs << "<secondary_bcp_compilation_millis>1094861636</secondary_bcp_compilation_millis>"; + ofs << "<system_server_compilation_millis>1364349780</system_server_compilation_millis>"; + ofs << "</odrefresh_metrics>"; + ofs.close(); - ASSERT_TRUE(ofs.fail()); - ASSERT_TRUE(!ofs.good()); + OdrMetricsRecord record{}; + ASSERT_THAT( + record.ReadFromFile(file_path), + HasError(WithMessage("Odrefresh metric status is not an int32"))); } } // namespace odrefresh diff --git a/odrefresh/odr_metrics_test.cc b/odrefresh/odr_metrics_test.cc index 4519f00363..f222caaf2e 100644 --- a/odrefresh/odr_metrics_test.cc +++ b/odrefresh/odr_metrics_test.cc @@ -24,6 +24,7 @@ #include <memory> #include <string> +#include "android-base/result-gmock.h" #include "base/common_art_test.h" namespace art { @@ -35,7 +36,7 @@ class OdrMetricsTest : public CommonArtTest { CommonArtTest::SetUp(); scratch_dir_ = std::make_unique<ScratchDir>(); - metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.txt"; + metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.xml"; cache_directory_ = scratch_dir_->GetPath() + "/dir"; mkdir(cache_directory_.c_str(), S_IRWXU); } @@ -158,10 +159,10 @@ TEST_F(OdrMetricsTest, TimeValuesAreRecorded) { EXPECT_TRUE(metrics.ToRecord(&record)); EXPECT_EQ(OdrMetrics::Stage::kPrimaryBootClasspath, enum_cast<OdrMetrics::Stage>(record.stage_reached)); - EXPECT_NE(0, record.primary_bcp_compilation_seconds); - EXPECT_GT(10, record.primary_bcp_compilation_seconds); - EXPECT_EQ(0, record.secondary_bcp_compilation_seconds); - EXPECT_EQ(0, record.system_server_compilation_seconds); + EXPECT_NE(0, record.primary_bcp_compilation_millis); + EXPECT_GT(10'000, record.primary_bcp_compilation_millis); + EXPECT_EQ(0, record.secondary_bcp_compilation_millis); + EXPECT_EQ(0, record.system_server_compilation_millis); // Secondary boot classpath compilation time. { @@ -172,10 +173,10 @@ TEST_F(OdrMetricsTest, TimeValuesAreRecorded) { EXPECT_TRUE(metrics.ToRecord(&record)); EXPECT_EQ(OdrMetrics::Stage::kSecondaryBootClasspath, enum_cast<OdrMetrics::Stage>(record.stage_reached)); - EXPECT_NE(0, record.primary_bcp_compilation_seconds); - EXPECT_NE(0, record.secondary_bcp_compilation_seconds); - EXPECT_GT(10, record.secondary_bcp_compilation_seconds); - EXPECT_EQ(0, record.system_server_compilation_seconds); + EXPECT_NE(0, record.primary_bcp_compilation_millis); + EXPECT_NE(0, record.secondary_bcp_compilation_millis); + EXPECT_GT(10'000, record.secondary_bcp_compilation_millis); + EXPECT_EQ(0, record.system_server_compilation_millis); // system_server classpath compilation time. { @@ -186,10 +187,10 @@ TEST_F(OdrMetricsTest, TimeValuesAreRecorded) { EXPECT_TRUE(metrics.ToRecord(&record)); EXPECT_EQ(OdrMetrics::Stage::kSystemServerClasspath, enum_cast<OdrMetrics::Stage>(record.stage_reached)); - EXPECT_NE(0, record.primary_bcp_compilation_seconds); - EXPECT_NE(0, record.secondary_bcp_compilation_seconds); - EXPECT_NE(0, record.system_server_compilation_seconds); - EXPECT_GT(10, record.system_server_compilation_seconds); + EXPECT_NE(0, record.primary_bcp_compilation_millis); + EXPECT_NE(0, record.secondary_bcp_compilation_millis); + EXPECT_NE(0, record.system_server_compilation_millis); + EXPECT_GT(10'000, record.system_server_compilation_millis); } TEST_F(OdrMetricsTest, CacheSpaceValuesAreUpdated) { @@ -207,11 +208,8 @@ TEST_F(OdrMetricsTest, CacheSpaceValuesAreUpdated) { EXPECT_EQ(0, snap.cache_space_free_end_mib); } - OdrMetricsRecord on_disk; - std::ifstream ifs(GetMetricsFilePath()); - EXPECT_TRUE(ifs); - ifs >> on_disk; - EXPECT_TRUE(ifs); + OdrMetricsRecord on_disk{}; + EXPECT_THAT(on_disk.ReadFromFile(GetMetricsFilePath()), android::base::testing::Ok()); EXPECT_EQ(snap.cache_space_free_start_mib, on_disk.cache_space_free_start_mib); EXPECT_NE(0, on_disk.cache_space_free_end_mib); } diff --git a/odrefresh/odr_statslog_android.cc b/odrefresh/odr_statslog_android.cc index 7db348e633..ee173d4b3d 100644 --- a/odrefresh/odr_statslog_android.cc +++ b/odrefresh/odr_statslog_android.cc @@ -103,16 +103,11 @@ int32_t TranslateTrigger(int32_t art_metrics_trigger) { bool ReadValues(const char* metrics_file, /*out*/ OdrMetricsRecord* record, /*out*/ std::string* error_msg) { - std::ifstream ifs(metrics_file); - if (!ifs) { - *error_msg = android::base::StringPrintf( - "metrics file '%s' could not be opened: %s", metrics_file, strerror(errno)); - return false; - } - - ifs >> *record; - if (!ifs) { - *error_msg = "file parsing error."; + const android::base::Result<void>& result = record->ReadFromFile(metrics_file); + if (!result.ok()) { + *error_msg = android::base::StringPrintf("Unable to open or parse metrics file %s (error: %s)", + metrics_file, + result.error().message().data()); return false; } @@ -151,16 +146,20 @@ bool UploadStatsIfAvailable(/*out*/std::string* error_msg) { // Write values to statsd. The order of values passed is the same as the order of the // fields in OdrMetricsRecord. - int bytes_written = art::metrics::statsd::stats_write(metrics::statsd::ODREFRESH_REPORTED, - record.art_apex_version, - record.trigger, - record.stage_reached, - record.status, - record.primary_bcp_compilation_seconds, - record.secondary_bcp_compilation_seconds, - record.system_server_compilation_seconds, - record.cache_space_free_start_mib, - record.cache_space_free_end_mib); + int bytes_written = art::metrics::statsd::stats_write( + metrics::statsd::ODREFRESH_REPORTED, + record.art_apex_version, + record.trigger, + record.stage_reached, + record.status, + record.primary_bcp_compilation_millis / 1000, + record.secondary_bcp_compilation_millis / 1000, + record.system_server_compilation_millis / 1000, + record.cache_space_free_start_mib, + record.cache_space_free_end_mib, + record.primary_bcp_compilation_millis, + record.secondary_bcp_compilation_millis, + record.system_server_compilation_millis); if (bytes_written <= 0) { *error_msg = android::base::StringPrintf("stats_write returned %d", bytes_written); return false; diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc index a15e6678f1..312a797b9d 100644 --- a/openjdkjvmti/deopt_manager.cc +++ b/openjdkjvmti/deopt_manager.cc @@ -460,15 +460,7 @@ void DeoptManager::AddDeoptimizationRequester() { art::ScopedThreadStateChange stsc(self, art::ThreadState::kSuspended); deoptimization_status_lock_.ExclusiveLock(self); deopter_count_++; - if (deopter_count_ == 1) { - ScopedDeoptimizationContext sdc(self, this); - art::instrumentation::Instrumentation* instrumentation = - art::Runtime::Current()->GetInstrumentation(); - // Tell instrumentation we will be deopting single threads. - instrumentation->EnableSingleThreadDeopt(kInstrumentationKey); - } else { - deoptimization_status_lock_.ExclusiveUnlock(self); - } + deoptimization_status_lock_.ExclusiveUnlock(self); } void DeoptManager::DeoptimizeThread(art::Thread* target) { diff --git a/runtime/Android.bp b/runtime/Android.bp index 9e0e78ebdb..98ddbb7210 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -415,7 +415,6 @@ libart_cc_defaults { ], static_libs: [ "libstatslog_art", - "libtinyxml2", ], generated_sources: [ "apex-info-list-tinyxml", diff --git a/runtime/app_info.cc b/runtime/app_info.cc index c72951eebc..2dbbbf6a90 100644 --- a/runtime/app_info.cc +++ b/runtime/app_info.cc @@ -93,6 +93,12 @@ void AppInfo::RegisterOdexStatus(const std::string& code_path, << "\nodex_status=" << odex_status; } +bool AppInfo::HasRegisteredAppInfo() { + MutexLock mu(Thread::Current(), update_mutex_); + + return package_name_.has_value(); +} + void AppInfo::GetPrimaryApkOptimizationStatus( std::string* out_compiler_filter, std::string* out_compilation_reason) { @@ -110,6 +116,13 @@ void AppInfo::GetPrimaryApkOptimizationStatus( *out_compilation_reason = kUnknownValue; } +AppInfo::CodeType AppInfo::GetRegisteredCodeType(const std::string& code_path) { + MutexLock mu(Thread::Current(), update_mutex_); + + const auto it = registered_code_locations_.find(code_path); + return it != registered_code_locations_.end() ? it->second.code_type : CodeType::kUnknown; +} + std::ostream& operator<<(std::ostream& os, AppInfo& rhs) { MutexLock mu(Thread::Current(), rhs.update_mutex_); diff --git a/runtime/app_info.h b/runtime/app_info.h index 68f2c586da..43e2ef320b 100644 --- a/runtime/app_info.h +++ b/runtime/app_info.h @@ -77,6 +77,13 @@ class AppInfo { void GetPrimaryApkOptimizationStatus(std::string* out_compiler_filter, std::string* out_compilation_reason); + // Whether we've received a call to RegisterAppInfo. + bool HasRegisteredAppInfo(); + + // The registered code type for a given code path. Note that this will + // be kUnknown until an explicit registration for that path has been made. + CodeType GetRegisteredCodeType(const std::string& code_path); + private: // Encapsulates optimization information about a particular code location. struct CodeLocationInfo { diff --git a/runtime/app_info_test.cc b/runtime/app_info_test.cc index 4a365dec96..51dd42f6fb 100644 --- a/runtime/app_info_test.cc +++ b/runtime/app_info_test.cc @@ -24,12 +24,17 @@ namespace art { TEST(AppInfoTest, RegisterAppInfo) { AppInfo app_info; + EXPECT_FALSE(app_info.HasRegisteredAppInfo()); + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kUnknown); + app_info.RegisterAppInfo( "package_name", std::vector<std::string>({"code_location"}), "", "", AppInfo::CodeType::kPrimaryApk); + EXPECT_TRUE(app_info.HasRegisteredAppInfo()); + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk); std::string filter; std::string reason; @@ -48,11 +53,13 @@ TEST(AppInfoTest, RegisterAppInfoWithOdexStatus) { "", "", AppInfo::CodeType::kPrimaryApk); + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk); app_info.RegisterOdexStatus( "code_location", "filter", "reason", "odex_status"); + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk); std::string filter; std::string reason; @@ -69,17 +76,22 @@ TEST(AppInfoTest, RegisterAppInfoWithOdexStatusMultiplePrimary) { "filter", "reason", "odex_status"); + EXPECT_FALSE(app_info.HasRegisteredAppInfo()); app_info.RegisterOdexStatus( "code_location2", "filter2", "reason2", "odex_status"); + EXPECT_FALSE(app_info.HasRegisteredAppInfo()); app_info.RegisterAppInfo( "package_name", std::vector<std::string>({"code_location"}), "", "", AppInfo::CodeType::kPrimaryApk); + EXPECT_TRUE(app_info.HasRegisteredAppInfo()); + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk); + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location2"), AppInfo::CodeType::kUnknown); std::string filter; std::string reason; @@ -110,7 +122,7 @@ TEST(AppInfoTest, RegisterAppInfoWithOdexStatusNoPrimary) { "filter", "reason", "odex_status"); - + EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kSplitApk); // The optimization status is unknown since we don't have primary apks. app_info.GetPrimaryApkOptimizationStatus(&filter, &reason); diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc index ad082aed1f..93400d9c7c 100644 --- a/runtime/arch/arm64/instruction_set_features_arm64.cc +++ b/runtime/arch/arm64/instruction_set_features_arm64.cc @@ -171,6 +171,18 @@ Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::FromVariant( has_sve)); } +Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::IntersectWithHwcap() const { + Arm64FeaturesUniquePtr hwcaps = Arm64InstructionSetFeatures::FromHwcap(); + return Arm64FeaturesUniquePtr(new Arm64InstructionSetFeatures( + fix_cortex_a53_835769_, + fix_cortex_a53_843419_, + has_crc_ && hwcaps->has_crc_, + has_lse_ && hwcaps->has_lse_, + has_fp16_ && hwcaps->has_fp16_, + has_dotprod_ && hwcaps->has_dotprod_, + has_sve_ && hwcaps->has_sve_)); +} + Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::FromBitmap(uint32_t bitmap) { bool is_a53 = (bitmap & kA53Bitfield) != 0; bool has_crc = (bitmap & kCRCBitField) != 0; diff --git a/runtime/arch/arm64/instruction_set_features_arm64.h b/runtime/arch/arm64/instruction_set_features_arm64.h index eb98c01633..8f0013ac86 100644 --- a/runtime/arch/arm64/instruction_set_features_arm64.h +++ b/runtime/arch/arm64/instruction_set_features_arm64.h @@ -53,6 +53,10 @@ class Arm64InstructionSetFeatures final : public InstructionSetFeatures { // Use external cpu_features library. static Arm64FeaturesUniquePtr FromCpuFeatures(); + // Return a new set of instruction set features, intersecting `this` features + // with hardware capabilities. + Arm64FeaturesUniquePtr IntersectWithHwcap() const; + bool Equals(const InstructionSetFeatures* other) const override; // Note that newer CPUs do not have a53 erratum 835769 and 843419, diff --git a/runtime/arch/instruction_set_features.cc b/runtime/arch/instruction_set_features.cc index ec1e340245..7a1e6b05ad 100644 --- a/runtime/arch/instruction_set_features.cc +++ b/runtime/arch/instruction_set_features.cc @@ -53,6 +53,33 @@ std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromVarian UNREACHABLE(); } +std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromVariantAndHwcap( + InstructionSet isa, const std::string& variant, std::string* error_msg) { + auto variant_features = FromVariant(isa, variant, error_msg); + if (variant_features == nullptr) { + return nullptr; + } + // Pixel3a is wrongly reporting itself as cortex-a75, so validate the features + // with hwcaps. + // Note that when cross-compiling on device (using dex2oat32 for compiling + // arm64), the hwcaps will report that no feature is supported. This is + // currently our best approach to be safe/correct. Maybe using the + // cpu_features library could fix this issue. + if (isa == InstructionSet::kArm64) { + auto new_features = down_cast<const Arm64InstructionSetFeatures*>(variant_features.get()) + ->IntersectWithHwcap(); + if (!variant_features->Equals(new_features.get())) { + LOG(WARNING) << "Mismatch between instruction set variant of device (" + << *variant_features + << ") and features returned by the hardware (" << *new_features << ")"; + } + return new_features; + } else { + // TODO: Implement this validation on all architectures. + return variant_features; + } +} + std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromBitmap(InstructionSet isa, uint32_t bitmap) { std::unique_ptr<const InstructionSetFeatures> result; diff --git a/runtime/arch/instruction_set_features.h b/runtime/arch/instruction_set_features.h index b80d36f153..cee8c5d42f 100644 --- a/runtime/arch/instruction_set_features.h +++ b/runtime/arch/instruction_set_features.h @@ -39,6 +39,12 @@ class InstructionSetFeatures { const std::string& variant, std::string* error_msg); + // Process a CPU variant string for the given ISA and make sure the features advertised + // are supported by the hardware. This is needed for Pixel3a which wrongly + // reports itself as cortex-a75. + static std::unique_ptr<const InstructionSetFeatures> FromVariantAndHwcap( + InstructionSet isa, const std::string& variant, std::string* error_msg); + // Parse a bitmap for the given isa and create an InstructionSetFeatures. static std::unique_ptr<const InstructionSetFeatures> FromBitmap(InstructionSet isa, uint32_t bitmap); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index c8dbc75e61..c233aecb64 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -4144,10 +4144,11 @@ ObjPtr<mirror::Class> ClassLinker::CreateArrayClass(Thread* self, class_loader))); if (component_type == nullptr) { DCHECK(self->IsExceptionPending()); - // We need to accept erroneous classes as component types. + // We need to accept erroneous classes as component types. Under AOT, we + // don't accept them as we cannot encode the erroneous class in an image. const size_t component_hash = ComputeModifiedUtf8Hash(descriptor + 1); component_type.Assign(LookupClass(self, descriptor + 1, component_hash, class_loader.Get())); - if (component_type == nullptr) { + if (component_type == nullptr || Runtime::Current()->IsAotCompiler()) { DCHECK(self->IsExceptionPending()); return nullptr; } else { @@ -5088,11 +5089,19 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons CHECK_EQ(prototype, method->GetInterfaceMethodIfProxy(image_pointer_size_)); } -bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass, bool can_init_statics, +bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass, + bool can_init_statics, bool can_init_parents) { if (can_init_statics && can_init_parents) { return true; } + DCHECK(Runtime::Current()->IsAotCompiler()); + + // We currently don't support initializing at AOT time classes that need access + // checks. + if (klass->IsVerifiedNeedsAccessChecks()) { + return false; + } if (!can_init_statics) { // Check if there's a class initializer. ArtMethod* clinit = klass->FindClassInitializer(image_pointer_size_); diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 0de62fef47..f3c61e3ef4 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -164,6 +164,9 @@ ConcurrentCopying::ConcurrentCopying(Heap* heap, gc_tracing_throughput_hist_ = metrics->YoungGcTracingThroughput(); gc_throughput_avg_ = metrics->YoungGcThroughputAvg(); gc_tracing_throughput_avg_ = metrics->YoungGcTracingThroughputAvg(); + gc_scanned_bytes_ = metrics->YoungGcScannedBytes(); + gc_freed_bytes_ = metrics->YoungGcFreedBytes(); + gc_duration_ = metrics->YoungGcDuration(); } else { gc_time_histogram_ = metrics->FullGcCollectionTime(); metrics_gc_count_ = metrics->FullGcCount(); @@ -171,6 +174,9 @@ ConcurrentCopying::ConcurrentCopying(Heap* heap, gc_tracing_throughput_hist_ = metrics->FullGcTracingThroughput(); gc_throughput_avg_ = metrics->FullGcThroughputAvg(); gc_tracing_throughput_avg_ = metrics->FullGcTracingThroughputAvg(); + gc_scanned_bytes_ = metrics->FullGcScannedBytes(); + gc_freed_bytes_ = metrics->FullGcFreedBytes(); + gc_duration_ = metrics->FullGcDuration(); } } diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc index 80b39824ec..4efe48c318 100644 --- a/runtime/gc/collector/garbage_collector.cc +++ b/runtime/gc/collector/garbage_collector.cc @@ -76,6 +76,9 @@ GarbageCollector::GarbageCollector(Heap* heap, const std::string& name) gc_tracing_throughput_hist_(nullptr), gc_throughput_avg_(nullptr), gc_tracing_throughput_avg_(nullptr), + gc_scanned_bytes_(nullptr), + gc_freed_bytes_(nullptr), + gc_duration_(nullptr), cumulative_timings_(name), pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true), is_transaction_active_(false), @@ -189,15 +192,18 @@ void GarbageCollector::Run(GcCause gc_cause, bool clear_soft_references) { RegisterPause(duration_ns); } total_time_ns_ += duration_ns; - uint64_t total_pause_time = 0; + uint64_t total_pause_time_ns = 0; for (uint64_t pause_time : current_iteration->GetPauseTimes()) { MutexLock mu(self, pause_histogram_lock_); pause_histogram_.AdjustAndAddValue(pause_time); - total_pause_time += pause_time; + total_pause_time_ns += pause_time; } metrics::ArtMetrics* metrics = runtime->GetMetrics(); // Report STW pause time in microseconds. - metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time / 1'000); + const uint64_t total_pause_time_us = total_pause_time_ns / 1'000; + metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time_us); + metrics->GcWorldStopTime()->Add(total_pause_time_us); + metrics->GcWorldStopCount()->AddOne(); // Report total collection time of all GCs put together. metrics->TotalGcCollectionTime()->Add(NsToMs(duration_ns)); if (are_metrics_initialized_) { @@ -216,6 +222,10 @@ void GarbageCollector::Run(GcCause gc_cause, bool clear_soft_references) { throughput = current_iteration->GetEstimatedThroughput() / MB; gc_throughput_histogram_->Add(throughput); gc_throughput_avg_->Add(throughput); + + gc_scanned_bytes_->Add(current_iteration->GetScannedBytes()); + gc_freed_bytes_->Add(current_iteration->GetFreedBytes()); + gc_duration_->Add(NsToMs(current_iteration->GetDurationNs())); } is_transaction_active_ = false; } diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h index d439914621..d11aea36c9 100644 --- a/runtime/gc/collector/garbage_collector.h +++ b/runtime/gc/collector/garbage_collector.h @@ -166,6 +166,9 @@ class GarbageCollector : public RootVisitor, public IsMarkedVisitor, public Mark metrics::MetricsBase<int64_t>* gc_tracing_throughput_hist_; metrics::MetricsBase<uint64_t>* gc_throughput_avg_; metrics::MetricsBase<uint64_t>* gc_tracing_throughput_avg_; + metrics::MetricsBase<uint64_t>* gc_scanned_bytes_; + metrics::MetricsBase<uint64_t>* gc_freed_bytes_; + metrics::MetricsBase<uint64_t>* gc_duration_; uint64_t total_thread_cpu_time_ns_; uint64_t total_time_ns_; uint64_t total_freed_objects_; diff --git a/runtime/gc/space/dlmalloc_space.cc b/runtime/gc/space/dlmalloc_space.cc index 25cac7efde..1edcdbdf91 100644 --- a/runtime/gc/space/dlmalloc_space.cc +++ b/runtime/gc/space/dlmalloc_space.cc @@ -350,11 +350,18 @@ void DlMallocSpace::CheckMoreCoreForPrecondition() { } #endif +struct MspaceCbArgs { + size_t max_contiguous; + size_t used; +}; + static void MSpaceChunkCallback(void* start, void* end, size_t used_bytes, void* arg) { size_t chunk_size = reinterpret_cast<uint8_t*>(end) - reinterpret_cast<uint8_t*>(start); + MspaceCbArgs* mspace_cb_args = reinterpret_cast<MspaceCbArgs*>(arg); + mspace_cb_args->used += used_bytes; if (used_bytes < chunk_size) { size_t chunk_free_bytes = chunk_size - used_bytes; - size_t& max_contiguous_allocation = *reinterpret_cast<size_t*>(arg); + size_t& max_contiguous_allocation = mspace_cb_args->max_contiguous; max_contiguous_allocation = std::max(max_contiguous_allocation, chunk_free_bytes); } } @@ -362,16 +369,17 @@ static void MSpaceChunkCallback(void* start, void* end, size_t used_bytes, void* bool DlMallocSpace::LogFragmentationAllocFailure(std::ostream& os, size_t failed_alloc_bytes) { Thread* const self = Thread::Current(); - size_t max_contiguous_allocation = 0; + MspaceCbArgs mspace_cb_args = {0, 0}; // To allow the Walk/InspectAll() to exclusively-lock the mutator // lock, temporarily release the shared access to the mutator // lock here by transitioning to the suspended state. Locks::mutator_lock_->AssertSharedHeld(self); ScopedThreadSuspension sts(self, ThreadState::kSuspended); - Walk(MSpaceChunkCallback, &max_contiguous_allocation); - if (failed_alloc_bytes > max_contiguous_allocation) { - os << "; failed due to fragmentation (largest possible contiguous allocation " - << max_contiguous_allocation << " bytes)"; + Walk(MSpaceChunkCallback, &mspace_cb_args); + if (failed_alloc_bytes > mspace_cb_args.max_contiguous) { + os << "; failed due to malloc_space fragmentation (largest possible contiguous allocation " + << mspace_cb_args.max_contiguous << " bytes, space in use " << mspace_cb_args.used + << " bytes, capacity = " << Capacity() << ")"; return true; } return false; diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 6afd63e4a5..4deb089349 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -1922,12 +1922,29 @@ bool ImageSpace::BootImageLayout::ReadHeader(const std::string& base_location, return false; } - // Validate oat files. We do it here so that the boot image will be re-compiled in memory if it's - // outdated. - size_t component_count = (header.GetImageSpaceCount() == 1u) ? header.GetComponentCount() : 1u; - for (size_t i = 0; i < header.GetImageSpaceCount(); i++) { - if (!ValidateOatFile(base_location, base_filename, bcp_index + i, component_count, error_msg)) { - return false; + bool validate_oat_files = true; + Runtime* runtime = Runtime::Current(); + if (runtime != nullptr && runtime->GetHeap() != nullptr) { + for (const ImageSpace* image_space : runtime->GetHeap()->GetBootImageSpaces()) { + if (image_space->GetComponentCount() == header.GetImageSpaceCount() && + image_space->GetImageHeader().GetImageChecksum() == header.GetImageChecksum()) { + // This image has been loaded by the runtime, meaning that the oat files were validated when + // the runtime loaded them, so we don't have to validate them again. + validate_oat_files = false; + break; + } + } + } + + if (validate_oat_files) { + // Validate oat files. We do it here so that the boot image will be re-compiled in memory if + // it's outdated. + size_t component_count = (header.GetImageSpaceCount() == 1u) ? header.GetComponentCount() : 1u; + for (size_t i = 0; i < header.GetImageSpaceCount(); i++) { + if (!ValidateOatFile( + base_location, base_filename, bcp_index + i, component_count, error_msg)) { + return false; + } } } diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 1e328a31d2..6ec98ff4d8 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -372,7 +372,7 @@ void Instrumentation::InitializeMethodsCode(ArtMethod* method, const void* aot_c REQUIRES_SHARED(Locks::mutator_lock_) { // Use instrumentation entrypoints if instrumentation is installed. if (UNLIKELY(EntryExitStubsInstalled()) && !IsProxyInit(method)) { - if (!method->IsNative() && InterpretOnly()) { + if (!method->IsNative() && InterpretOnly(method)) { UpdateEntryPoints(method, GetQuickToInterpreterBridge()); } else { UpdateEntryPoints(method, GetQuickInstrumentationEntryPoint()); @@ -380,7 +380,7 @@ void Instrumentation::InitializeMethodsCode(ArtMethod* method, const void* aot_c return; } - if (UNLIKELY(IsForcedInterpretOnly())) { + if (UNLIKELY(IsForcedInterpretOnly() || IsDeoptimized(method))) { UpdateEntryPoints( method, method->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge()); return; @@ -896,11 +896,6 @@ void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desir UpdateStubs(); } -void Instrumentation::EnableSingleThreadDeopt(const char* key) { - // Prepare for single thread deopt by installing instrumentation stubs. - ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInstrumentationStubs); -} - void Instrumentation::UpdateInstrumentationLevel(InstrumentationLevel requested_level) { instrumentation_level_ = requested_level; } @@ -922,7 +917,9 @@ void Instrumentation::MaybeRestoreInstrumentationStack() { Locks::mutator_lock_->AssertExclusiveHeld(self); Runtime::Current()->GetThreadList()->ForEach([&](Thread* t) NO_THREAD_SAFETY_ANALYSIS { no_remaining_deopts = - no_remaining_deopts && !t->IsForceInterpreter() && + no_remaining_deopts && + !t->IsForceInterpreter() && + !t->HasDebuggerShadowFrames() && std::all_of(t->GetInstrumentationStack()->cbegin(), t->GetInstrumentationStack()->cend(), [&](const auto& frame) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -1058,7 +1055,7 @@ std::string Instrumentation::EntryPointString(const void* code) { } void Instrumentation::UpdateMethodsCodeImpl(ArtMethod* method, const void* new_code) { - if (!EntryExitStubsInstalled()) { + if (!AreExitStubsInstalled()) { // Fast path: no instrumentation. DCHECK(!IsDeoptimized(method)); UpdateEntryPoints(method, new_code); @@ -1079,7 +1076,7 @@ void Instrumentation::UpdateMethodsCodeImpl(ArtMethod* method, const void* new_c return; } - if (CodeNeedsEntryExitStub(new_code, method)) { + if (EntryExitStubsInstalled() && CodeNeedsEntryExitStub(new_code, method)) { DCHECK(method->GetEntryPointFromQuickCompiledCode() == GetQuickInstrumentationEntryPoint() || class_linker->IsQuickToInterpreterBridge(method->GetEntryPointFromQuickCompiledCode())) << EntryPointString(method->GetEntryPointFromQuickCompiledCode()) diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index b1631091ae..c811935e9d 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -524,13 +524,6 @@ class Instrumentation { void InstallStubsForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock()); - // Sets up instrumentation to allow single thread deoptimization using ForceInterpreterCount. - void EnableSingleThreadDeopt(const char* key) - REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) - REQUIRES(!Locks::thread_list_lock_, - !Locks::classlinker_classes_lock_, - !GetDeoptimizedMethodsLock()); - // Install instrumentation exit stub on every method of the stack of the given thread. // This is used by: // - the debugger to cause a deoptimization of the all frames in thread's stack (for diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 5e01aaa7fa..6d634ae120 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -568,7 +568,12 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, // Before allowing the jump, make sure no code is actively inspecting the method to avoid // jumping from interpreter to OSR while e.g. single stepping. Note that we could selectively // disable OSR when single stepping, but that's currently hard to know at this point. - if (Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method)) { + if (Runtime::Current()->GetInstrumentation()->InterpreterStubsInstalled() || + Runtime::Current()->GetInstrumentation()->IsDeoptimized(method) || + thread->IsForceInterpreter() || + method->GetDeclaringClass()->IsObsoleteObject() || + Dbg::IsForcedInterpreterNeededForUpcall(thread, method) || + Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method)) { return false; } diff --git a/runtime/metrics/reporter.cc b/runtime/metrics/reporter.cc index a44066e487..28ca997cec 100644 --- a/runtime/metrics/reporter.cc +++ b/runtime/metrics/reporter.cc @@ -126,10 +126,17 @@ void MetricsReporter::BackgroundThreadRun() { // Configure the backends if (config_.dump_to_logcat) { - backends_.emplace_back(new LogBackend(LogSeverity::INFO)); + backends_.emplace_back(new LogBackend(std::make_unique<TextFormatter>(), LogSeverity::INFO)); } if (config_.dump_to_file.has_value()) { - backends_.emplace_back(new FileBackend(config_.dump_to_file.value())); + std::unique_ptr<MetricsFormatter> formatter; + if (config_.metrics_format == "xml") { + formatter = std::make_unique<XmlFormatter>(); + } else { + formatter = std::make_unique<TextFormatter>(); + } + + backends_.emplace_back(new FileBackend(std::move(formatter), config_.dump_to_file.value())); } if (config_.dump_to_statsd) { auto backend = CreateStatsdBackend(); @@ -291,6 +298,7 @@ ReportingConfig ReportingConfig::FromFlags(bool is_system_server) { .dump_to_logcat = gFlags.MetricsWriteToLogcat(), .dump_to_file = gFlags.MetricsWriteToFile.GetValueOptional(), .dump_to_statsd = gFlags.MetricsWriteToStatsd(), + .metrics_format = gFlags.MetricsFormat(), .period_spec = period_spec, .reporting_num_mods = reporting_num_mods, .reporting_mods = reporting_mods, diff --git a/runtime/metrics/reporter.h b/runtime/metrics/reporter.h index daeaf1fa18..af9e0ca151 100644 --- a/runtime/metrics/reporter.h +++ b/runtime/metrics/reporter.h @@ -78,6 +78,9 @@ struct ReportingConfig { // If set, provides a file name to enable metrics logging to a file. std::optional<std::string> dump_to_file; + // Provides the desired output format for metrics written to a file. + std::string metrics_format; + // The reporting period configuration. std::optional<ReportingPeriodSpec> period_spec; diff --git a/runtime/metrics/statsd.cc b/runtime/metrics/statsd.cc index f68d50730f..560e7dab32 100644 --- a/runtime/metrics/statsd.cc +++ b/runtime/metrics/statsd.cc @@ -106,6 +106,30 @@ constexpr std::optional<int32_t> EncodeDatumId(DatumId datum_id) { case DatumId::kFullGcTracingThroughputAvg: return std::make_optional( statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_TRACING_THROUGHPUT_AVG_MB_PER_SEC); + case DatumId::kGcWorldStopTime: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_TIME_US); + case DatumId::kGcWorldStopCount: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_COUNT); + case DatumId::kYoungGcScannedBytes: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_SCANNED_BYTES); + case DatumId::kYoungGcFreedBytes: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_FREED_BYTES); + case DatumId::kYoungGcDuration: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_DURATION_MS); + case DatumId::kFullGcScannedBytes: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_SCANNED_BYTES); + case DatumId::kFullGcFreedBytes: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_FREED_BYTES); + case DatumId::kFullGcDuration: + return std::make_optional( + statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_DURATION_MS); } } diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 63778c7b0b..221cf67e8b 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1907,17 +1907,6 @@ OatFile* OatFile::Open(int zip_fd, reservation, error_msg); if (with_dlopen != nullptr) { - Runtime* runtime = Runtime::Current(); - // The runtime might not be available at this point if we're running - // dex2oat or oatdump. - if (runtime != nullptr) { - size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex(); - Runtime::MadviseFileForRange(madvise_size_limit, - with_dlopen->Size(), - with_dlopen->Begin(), - with_dlopen->End(), - oat_location); - } return with_dlopen; } if (kPrintDlOpenErrorMessage) { diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index c3a268d8b7..ecf3a04cc3 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -229,6 +229,16 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( compilation_filter.c_str(), compilation_reason.c_str())); + const bool has_registered_app_info = Runtime::Current()->GetAppInfo()->HasRegisteredAppInfo(); + const AppInfo::CodeType code_type = + Runtime::Current()->GetAppInfo()->GetRegisteredCodeType(dex_location); + // We only want to madvise primary/split dex artifacts as a startup optimization. However, + // as the code_type for those artifacts may not be set until the initial app info registration, + // we conservatively madvise everything until the app info registration is complete. + const bool should_madvise_vdex_and_odex = !has_registered_app_info || + code_type == AppInfo::CodeType::kPrimaryApk || + code_type == AppInfo::CodeType::kSplitApk; + // Proceed with oat file loading. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()=" @@ -244,6 +254,16 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // Load the dex files from the oat file. bool added_image_space = false; if (oat_file->IsExecutable()) { + if (should_madvise_vdex_and_odex) { + VLOG(oat) << "Madvising oat file: " << oat_file->GetLocation(); + size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex(); + Runtime::MadviseFileForRange(madvise_size_limit, + oat_file->Size(), + oat_file->Begin(), + oat_file->End(), + oat_file->GetLocation()); + } + ScopedTrace app_image_timing("AppImage:Loading"); // We need to throw away the image space if we are debuggable but the oat-file source of the @@ -345,7 +365,8 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( if (oat_file != nullptr) { VdexFile* vdex_file = oat_file->GetVdexFile(); - if (vdex_file != nullptr) { + if (should_madvise_vdex_and_odex && vdex_file != nullptr) { + VLOG(oat) << "Madvising vdex file: " << vdex_file->GetName(); // Opened vdex file from an oat file, madvise it to its loaded state. // TODO(b/196052575): Unify dex and vdex madvise knobs and behavior. const size_t madvise_size_limit = Runtime::Current()->GetMadviseWillNeedSizeVdex(); diff --git a/runtime/runtime.cc b/runtime/runtime.cc index e20f883446..2de3eb5c74 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -178,6 +178,7 @@ #include "well_known_classes.h" #ifdef ART_TARGET_ANDROID +#include <android/api-level.h> #include <android/set_abort_message.h> #include "com_android_apex.h" namespace apex = com::android::apex; @@ -3358,6 +3359,22 @@ void Runtime::MadviseFileForRange(size_t madvise_size_limit_bytes, const uint8_t* map_begin, const uint8_t* map_end, const std::string& file_name) { +#ifdef ART_TARGET_ANDROID + // Short-circuit the madvise optimization for background processes. This + // avoids IO and memory contention with foreground processes, particularly + // those involving app startup. + // Note: We can only safely short-circuit the madvise on T+, as it requires + // the framework to always immediately notify ART of process states. + static const int kApiLevel = android_get_device_api_level(); + const bool accurate_process_state_at_startup = kApiLevel >= __ANDROID_API_T__; + if (accurate_process_state_at_startup) { + const Runtime* runtime = Runtime::Current(); + if (runtime != nullptr && !runtime->InJankPerceptibleProcessState()) { + return; + } + } +#endif // ART_TARGET_ANDROID + // Ideal blockTransferSize for madvising files (128KiB) static constexpr size_t kIdealIoTransferSizeBytes = 128*1024; diff --git a/runtime/thread.cc b/runtime/thread.cc index 78ba26dec0..26b795b967 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -4282,6 +4282,9 @@ void Thread::VisitRoots(RootVisitor* visitor) { static void SweepCacheEntry(IsMarkedVisitor* visitor, const Instruction* inst, size_t* value) REQUIRES_SHARED(Locks::mutator_lock_) { + // WARNING: The interpreter will not modify the cache while this method is running in GC. + // However, ClearAllInterpreterCaches can still run if any dex file is closed. + // Therefore the cache entry can be nulled at any point through this method. if (inst == nullptr) { return; } @@ -4307,6 +4310,9 @@ static void SweepCacheEntry(IsMarkedVisitor* visitor, const Instruction* inst, s case Opcode::CONST_STRING: case Opcode::CONST_STRING_JUMBO: { mirror::Object* object = reinterpret_cast<mirror::Object*>(*value); + if (object == nullptr) { + return; + } mirror::Object* new_object = visitor->IsMarked(object); // We know the string is marked because it's a strongly-interned string that // is always alive (see b/117621117 for trying to make those strings weak). diff --git a/test/1000-non-moving-space-stress/src-art/Main.java b/test/1000-non-moving-space-stress/src-art/Main.java index 18bfdd3c04..3cd7224080 100644 --- a/test/1000-non-moving-space-stress/src-art/Main.java +++ b/test/1000-non-moving-space-stress/src-art/Main.java @@ -15,8 +15,11 @@ */ import dalvik.system.VMRuntime; +import java.lang.ref.Reference; // For reachabilityFence. +import java.util.ArrayList; public class Main { + private static final boolean SHOULD_PRINT = false; // True causes failure. public static void main(String[] args) throws Exception { VMRuntime runtime = VMRuntime.getRuntime(); @@ -35,15 +38,40 @@ public class Main { Object[] moving_array = new Object[S]; } } catch (OutOfMemoryError e) { - // Stop here. + System.out.println("Unexpected OOME"); } - System.out.println("passed"); + Runtime.getRuntime().gc(); + int numAllocs = 0; + ArrayList<Object> chunks = new ArrayList<>(); + try { + final int MAX_PLAUSIBLE_ALLOCS = 1024 * 1024; + for (numAllocs = 0; numAllocs < MAX_PLAUSIBLE_ALLOCS; ++numAllocs) { + chunks.add(runtime.newNonMovableArray(Object.class, 252)); // About 1KB + } + // If we get here, we've allocated about 1GB of nonmovable memory, which + // should be impossible. + } catch (OutOfMemoryError e) { + chunks.remove(0); // Give us a little space back. + if (((Object[]) (chunks.get(42)))[17] != null) { + System.out.println("Bad entry in chunks array"); + } else { + chunks.clear(); // Recover remaining space. + if (SHOULD_PRINT) { + System.out.println("Successfully allocated " + numAllocs + " non-movable KBs"); + } + System.out.println("passed"); + } + Reference.reachabilityFence(chunks); + return; + } + Reference.reachabilityFence(chunks); + System.out.println("Failed to exhaust non-movable space"); } // When using the Concurrent Copying (CC) collector (default collector), // this method allocates an object in the non-moving space and an object // in the region space, make the former reference the later, and returns - // nothing (so that none of these objects are reachable when upon return). + // nothing (so that none of these objects are reachable upon return). static void $noinline$Alloc(VMRuntime runtime) { Object[] non_moving_array = (Object[]) runtime.newNonMovableArray(Object.class, 1); // Small object, unlikely to trigger garbage collection. diff --git a/test/719-varhandle-concurrency/src/Main.java b/test/719-varhandle-concurrency/src/Main.java index 1c2135bf7d..784e7cc103 100644 --- a/test/719-varhandle-concurrency/src/Main.java +++ b/test/719-varhandle-concurrency/src/Main.java @@ -46,7 +46,8 @@ public class Main { * for example with gcstress, set a cap duration in MAX_RETRIES_DURATION. With this at least one * iteration would run, but there could be fewer retries if each of them takes too long. */ private static final int RETRIES = 50; - private static final Duration MAX_RETRIES_DURATION = Duration.ofMinutes(1); + // b/235431387: timeout reduced from 1 minute + private static final Duration MAX_RETRIES_DURATION = Duration.ofSeconds(15); public static void main(String[] args) throws Throwable { testConcurrentProcessing(new CompareAndExchangeRunnerFactory(), "compareAndExchange"); diff --git a/test/Android.bp b/test/Android.bp index 8a1ca84af1..4289c18f41 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -1121,6 +1121,8 @@ java_library { "2007-virtual-structural-finalizable/src-art/art/Test2007.java", ], sdk_version: "core_platform", + // Make sure that this will be added to the sdk snapshot for S. + min_sdk_version: "S", // Some ART run-tests contain constructs which break ErrorProne checks; // disable `errorprone` builds. errorprone: { @@ -1284,6 +1286,8 @@ java_library { "expected_cts_outputs_gen", ], sdk_version: "core_current", + // Make sure that this will be added to the sdk snapshot for S. + min_sdk_version: "S", } art_cc_test { @@ -1381,6 +1385,7 @@ filegroup { srcs: [ ":art-gtest-jars-AbstractMethod", ":art-gtest-jars-AllFields", + ":art-gtest-jars-ArrayClassWithUnresolvedComponent", ":art-gtest-jars-DefaultMethods", ":art-gtest-jars-ErroneousA", ":art-gtest-jars-ErroneousB", @@ -1427,6 +1432,7 @@ filegroup { ":art-gtest-jars-MainStripped", ":art-gtest-jars-MainUncompressedAligned", ":art-gtest-jars-MultiDexUncompressedAligned", + ":art-gtest-jars-SuperWithAccessChecks", ":art-gtest-jars-VerifierDeps", ":art-gtest-jars-VerifierDepsMulti", ":art-gtest-jars-VerifySoftFailDuringClinit", @@ -1832,6 +1838,20 @@ genrule { } genrule { + name: "art-gtest-jars-ArrayClassWithUnresolvedComponent", + defaults: ["art-gtest-jars-smali-defaults"], + srcs: ["ArrayClassWithUnresolvedComponent/*.smali"], + out: ["art-gtest-jars-ArrayClassWithUnresolvedComponent.dex"], +} + +genrule { + name: "art-gtest-jars-SuperWithAccessChecks", + defaults: ["art-gtest-jars-smali-defaults"], + srcs: ["SuperWithAccessChecks/*.smali"], + out: ["art-gtest-jars-SuperWithAccessChecks.dex"], +} + +genrule { name: "art-gtest-jars-LinkageTest", defaults: ["art-gtest-jars-smali-defaults"], srcs: ["LinkageTest/*.smali"], diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali new file mode 100644 index 0000000000..f68d70c7dd --- /dev/null +++ b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LClassWithMissingInterface; + +.super Ljava/lang/Object; +.implements LMissingInterface; diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali new file mode 100644 index 0000000000..134302cd9d --- /dev/null +++ b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LClassWithMissingSuper; + +.super LMissingClass; diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali new file mode 100644 index 0000000000..f35e5f0a3f --- /dev/null +++ b/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali @@ -0,0 +1,28 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LClassWithStatic; + +.super Ljava/lang/Object; + +.method static constructor <clinit>()V + .registers 1 + const-string v0, "[LClassWithMissingInterface;" + invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class; + move-result-object v0 + sput-object v0, LClassWithStatic;->field:Ljava/lang/Class; + return-void +.end method + +.field public static field:Ljava/lang/Class; diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali new file mode 100644 index 0000000000..68aecfbdad --- /dev/null +++ b/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali @@ -0,0 +1,26 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LClassWithStaticConst; + +.super Ljava/lang/Object; + +.method static constructor <clinit>()V + .registers 1 + const-class v0, [LClassWithMissingInterface; + sput-object v0, LClassWithStaticConst;->field:Ljava/lang/Class; + return-void +.end method + +.field public static field:Ljava/lang/Class; diff --git a/test/SuperWithAccessChecks/ImplementsClass.smali b/test/SuperWithAccessChecks/ImplementsClass.smali new file mode 100644 index 0000000000..e39877f45f --- /dev/null +++ b/test/SuperWithAccessChecks/ImplementsClass.smali @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LImplementsClass; + +.super Ljava/lang/Object; +.implements LItf; diff --git a/test/SuperWithAccessChecks/Itf.smali b/test/SuperWithAccessChecks/Itf.smali new file mode 100644 index 0000000000..c91686ed03 --- /dev/null +++ b/test/SuperWithAccessChecks/Itf.smali @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public abstract interface LItf; + +.super Ljava/lang/Object; + +.method public foo()V + .registers 1 + invoke-static {}, LMissingClass;->forName()V + return-void +.end method diff --git a/test/SuperWithAccessChecks/SubClass.smali b/test/SuperWithAccessChecks/SubClass.smali new file mode 100644 index 0000000000..21c8f1cbd8 --- /dev/null +++ b/test/SuperWithAccessChecks/SubClass.smali @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSubClass; + +.super LSuperClass; diff --git a/test/SuperWithAccessChecks/SuperClass.smali b/test/SuperWithAccessChecks/SuperClass.smali new file mode 100644 index 0000000000..8e12521acf --- /dev/null +++ b/test/SuperWithAccessChecks/SuperClass.smali @@ -0,0 +1,24 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSuperClass; + +.super Ljava/lang/Object; + +.method static foo()V + .registers 0 + invoke-static {}, LMissingClass;->forName()V + return-void +.end method + diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 75dc8fe728..ef168ca871 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -650,11 +650,11 @@ if [ "$USE_JVMTI" = "y" ]; then # needed anymore since the plugin can do it for us now. FLAGS="${FLAGS} -Xplugin:${plugin}" - # For jvmti tests, set the threshold of compilation to 0, so we jit on the first - # use to provide better test coverage for jvmti + jit. This means we won't run + # For jvmti tests, set the threshold of compilation to 1, so we jit early to + # provide better test coverage for jvmti + jit. This means we won't run # the default --jit configuration but it is not too important test scenario for # jvmti tests. This is art specific flag, so don't use it with jvm. - FLAGS="${FLAGS} -Xjitthreshold:0" + FLAGS="${FLAGS} -Xjitthreshold:1" fi fi diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java index 7905660dc9..caf94a783b 100644 --- a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java +++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java @@ -86,7 +86,8 @@ public class OdsignTestUtils { String packagesOutput = mTestInfo.getDevice().executeShellCommand("pm list packages -f --apex-only"); Pattern p = Pattern.compile( - "^package:(.*)=(com(?:\\.google)?\\.android\\.art)$", Pattern.MULTILINE); + "^package:(.*)=(com(?:\\.google)?\\.android(?:\\.go)?\\.art)$", + Pattern.MULTILINE); Matcher m = p.matcher(packagesOutput); assertTrue("ART module not found. Packages are:\n" + packagesOutput, m.find()); String artApexPath = m.group(1); diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files index 237ec960d4..ebe25205d6 100755 --- a/test/utils/regen-test-files +++ b/test/utils/regen-test-files @@ -230,6 +230,7 @@ art_gtest_user_module_names = [ # ART gtests that need root access to the device. art_gtest_eng_only_module_names = [ + "libnativeloader_e2e_tests", "art_standalone_dexoptanalyzer_tests", "art_standalone_profman_tests", ] @@ -736,18 +737,11 @@ class Generator: mts_test_shards = [] - # ART test (gtest & run-test) shard(s). - # TODO: Also handle the case of gtests requiring root access to the device - # (`art_gtest_eng_only_module_names`). + # ART run-tests shard(s). art_run_test_module_names = [ART_RUN_TEST_MODULE_NAME_PREFIX + t for t in art_run_tests] art_run_test_shards = split_list(art_run_test_module_names, NUM_MTS_ART_RUN_TEST_SHARDS) for i in range(len(art_run_test_shards)): art_tests_shard_i_tests = art_run_test_shards[i] - # Append ART gtests to the last ART run-test shard for now. - # If needed, consider moving them to their own shard to increase - # the parallelization of code coverage runs. - if i + 1 == len(art_run_test_shards): - art_tests_shard_i_tests.extend(art_gtest_mts_user_module_names) art_tests_shard_i = self.create_mts_test_shard( "ART run-tests", art_tests_shard_i_tests, i, 2020, ["TODO(rpl): Find a way to express this list in a more concise fashion."]) @@ -775,6 +769,15 @@ class Generator: other_cts_libcore_tests_shard_num, 2021) mts_test_shards.append(other_cts_libcore_tests_shard) + # ART gtests shard. + # TODO: Also handle the case of gtests requiring root access to the device + # (`art_gtest_eng_only_module_names`). + art_gtests_shard_num = len(mts_test_shards) + art_gtests_shard_tests = art_gtest_mts_user_module_names + art_gtests_shard = self.create_mts_test_shard( + "ART gtests", art_gtests_shard_tests, art_gtests_shard_num, 2022) + mts_test_shards.append(art_gtests_shard) + for s in mts_test_shards: s.regen_test_plan_file() s.regen_test_list_file() |