Deduplicate interned image strings.
Also fix a bug in relocation; even for -Xnorelocate we need
to relocate second and later extension if it's not compiled
against all previous boot image components.
Also clean up InternTable includes.
Test: New tests in image_space_test.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Test: aosp_taimen-userdebug boots.
Bug: 152037801
Change-Id: Ie6ae70721f4ffb48950bd248ffa123dee460bcd7
diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc
index 580b490..b08c680 100644
--- a/runtime/gc/space/image_space_test.cc
+++ b/runtime/gc/space/image_space_test.cc
@@ -16,18 +16,211 @@
#include <gtest/gtest.h>
+#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "base/stl_util.h"
#include "class_linker.h"
#include "dexopt_test.h"
+#include "dex/utf.h"
+#include "intern_table.h"
#include "noop_compiler_callbacks.h"
+#include "oat_file.h"
namespace art {
namespace gc {
namespace space {
+class ImageSpaceTest : public CommonRuntimeTest {
+ protected:
+ void SetUpRuntimeOptions(RuntimeOptions* options) override {
+ // Disable implicit dex2oat invocations when loading image spaces.
+ options->emplace_back("-Xnoimage-dex2oat", nullptr);
+ // Disable relocation.
+ options->emplace_back("-Xnorelocate", nullptr);
+ }
+
+ std::string GetFilenameBase(const std::string& full_path) {
+ size_t slash_pos = full_path.rfind('/');
+ CHECK_NE(std::string::npos, slash_pos);
+ size_t dot_pos = full_path.rfind('.');
+ CHECK_NE(std::string::npos, dot_pos);
+ CHECK_GT(dot_pos, slash_pos + 1u);
+ return full_path.substr(slash_pos + 1u, dot_pos - (slash_pos + 1u));
+ }
+};
+
+TEST_F(ImageSpaceTest, StringDeduplication) {
+ const char* const kBaseNames[] = { "Extension1", "Extension2" };
+
+ ScratchDir scratch;
+ const std::string& scratch_dir = scratch.GetPath();
+ std::string image_dir = scratch_dir + GetInstructionSetString(kRuntimeISA);
+ int mkdir_result = mkdir(image_dir.c_str(), 0700);
+ ASSERT_EQ(0, mkdir_result);
+
+ // Prepare boot class path variables, exclude conscrypt which is not in the primary boot image.
+ std::vector<std::string> bcp = GetLibCoreDexFileNames();
+ std::vector<std::string> bcp_locations = GetLibCoreDexLocations();
+ CHECK_EQ(bcp.size(), bcp_locations.size());
+ ASSERT_NE(std::string::npos, bcp.back().find("conscrypt"));
+ bcp.pop_back();
+ bcp_locations.pop_back();
+ std::string base_bcp_string = android::base::Join(bcp, ':');
+ std::string base_bcp_locations_string = android::base::Join(bcp_locations, ':');
+ std::string base_image_location = GetImageLocation();
+
+ // Compile the two extensions independently.
+ std::vector<std::string> extension_image_locations;
+ for (const char* base_name : kBaseNames) {
+ std::string jar_name = GetTestDexFileName(base_name);
+ ArrayRef<const std::string> dex_files(&jar_name, /*size=*/ 1u);
+ ScratchFile profile_file;
+ GenerateProfile(dex_files, profile_file.GetFile());
+ std::vector<std::string> extra_args = {
+ "--profile-file=" + profile_file.GetFilename(),
+ "--runtime-arg",
+ "-Xbootclasspath:" + base_bcp_string + ':' + jar_name,
+ "--runtime-arg",
+ "-Xbootclasspath-locations:" + base_bcp_locations_string + ':' + jar_name,
+ "--boot-image=" + base_image_location,
+ };
+ std::string prefix = GetFilenameBase(base_image_location);
+ std::string error_msg;
+ bool success = CompileBootImage(extra_args, image_dir + '/' + prefix, dex_files, &error_msg);
+ ASSERT_TRUE(success) << error_msg;
+ bcp.push_back(jar_name);
+ bcp_locations.push_back(jar_name);
+ extension_image_locations.push_back(
+ scratch_dir + prefix + '-' + GetFilenameBase(jar_name) + ".art");
+ }
+
+ // Also compile the second extension as an app with app image.
+ const char* app_base_name = kBaseNames[std::size(kBaseNames) - 1u];
+ std::string app_jar_name = GetTestDexFileName(app_base_name);
+ std::string app_odex_name = scratch_dir + app_base_name + ".odex";
+ std::string app_image_name = scratch_dir + app_base_name + ".art";
+ {
+ ArrayRef<const std::string> dex_files(&app_jar_name, /*size=*/ 1u);
+ ScratchFile profile_file;
+ GenerateProfile(dex_files, profile_file.GetFile());
+ std::vector<std::string> argv;
+ std::string error_msg;
+ bool success = StartDex2OatCommandLine(&argv, &error_msg, /*use_runtime_bcp_and_image=*/ false);
+ ASSERT_TRUE(success) << error_msg;
+ argv.insert(argv.end(), {
+ "--profile-file=" + profile_file.GetFilename(),
+ "--runtime-arg",
+ "-Xbootclasspath:" + base_bcp_string,
+ "--runtime-arg",
+ "-Xbootclasspath-locations:" + base_bcp_locations_string,
+ "--boot-image=" + base_image_location,
+ "--dex-file=" + app_jar_name,
+ "--dex-location=" + app_jar_name,
+ "--oat-file=" + app_odex_name,
+ "--app-image-file=" + app_image_name,
+ "--initialize-app-image-classes=true",
+ });
+ success = RunDex2Oat(argv, &error_msg);
+ ASSERT_TRUE(success) << error_msg;
+ }
+
+ std::string full_image_locations;
+ std::vector<std::unique_ptr<gc::space::ImageSpace>> boot_image_spaces;
+ MemMap extra_reservation;
+ auto load_boot_image = [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+ boot_image_spaces.clear();
+ extra_reservation = MemMap::Invalid();
+ return ImageSpace::LoadBootImage(bcp,
+ bcp_locations,
+ full_image_locations,
+ kRuntimeISA,
+ ImageSpaceLoadingOrder::kSystemFirst,
+ /*relocate=*/ false,
+ /*executable=*/ true,
+ /*is_zygote=*/ false,
+ /*extra_reservation_size=*/ 0u,
+ &boot_image_spaces,
+ &extra_reservation);
+ };
+
+ const char test_string[] = "SharedBootImageExtensionTestString";
+ size_t test_string_length = std::size(test_string) - 1u; // Equals UTF-16 length.
+ uint32_t hash = ComputeUtf16HashFromModifiedUtf8(test_string, test_string_length);
+ InternTable::Utf8String utf8_test_string(test_string_length, test_string, hash);
+ auto contains_test_string = [utf8_test_string](ImageSpace* space)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const ImageHeader& image_header = space->GetImageHeader();
+ if (image_header.GetInternedStringsSection().Size() != 0u) {
+ const uint8_t* data = space->Begin() + image_header.GetInternedStringsSection().Offset();
+ size_t read_count;
+ InternTable::UnorderedSet temp_set(data, /*make_copy_of_data=*/ false, &read_count);
+ return temp_set.find(utf8_test_string) != temp_set.end();
+ } else {
+ return false;
+ }
+ };
+
+ // Load extensions and test for the presence of the test string.
+ ScopedObjectAccess soa(Thread::Current());
+ ASSERT_EQ(2u, extension_image_locations.size());
+ full_image_locations = base_image_location +
+ ImageSpace::kComponentSeparator + extension_image_locations[0] +
+ ImageSpace::kComponentSeparator + extension_image_locations[1];
+ bool success = load_boot_image();
+ ASSERT_TRUE(success);
+ ASSERT_EQ(bcp.size(), boot_image_spaces.size());
+ EXPECT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 2u].get()));
+ // The string in the second extension should be replaced and removed from interned string section.
+ EXPECT_FALSE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get()));
+
+ // Reload extensions in reverse order and test for the presence of the test string.
+ std::swap(bcp[bcp.size() - 2u], bcp[bcp.size() - 1u]);
+ std::swap(bcp_locations[bcp_locations.size() - 2u], bcp_locations[bcp_locations.size() - 1u]);
+ full_image_locations = base_image_location +
+ ImageSpace::kComponentSeparator + extension_image_locations[1] +
+ ImageSpace::kComponentSeparator + extension_image_locations[0];
+ success = load_boot_image();
+ ASSERT_TRUE(success);
+ ASSERT_EQ(bcp.size(), boot_image_spaces.size());
+ EXPECT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 2u].get()));
+ // The string in the second extension should be replaced and removed from interned string section.
+ EXPECT_FALSE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get()));
+
+ // Reload the image without the second extension.
+ bcp.erase(bcp.end() - 2u);
+ bcp_locations.erase(bcp_locations.end() - 2u);
+ full_image_locations =
+ base_image_location + ImageSpace::kComponentSeparator + extension_image_locations[0];
+ success = load_boot_image();
+ ASSERT_TRUE(success);
+ ASSERT_EQ(bcp.size(), boot_image_spaces.size());
+ ASSERT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get()));
+
+ // Load the app odex file and app image.
+ std::string error_msg;
+ std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+ app_odex_name.c_str(),
+ app_odex_name.c_str(),
+ /*executable=*/ false,
+ /*low_4gb=*/ false,
+ app_jar_name,
+ &error_msg));
+ ASSERT_TRUE(odex_file != nullptr) << error_msg;
+ std::vector<ImageSpace*> non_owning_boot_image_spaces =
+ MakeNonOwningPointerVector(boot_image_spaces);
+ std::unique_ptr<ImageSpace> app_image_space = ImageSpace::CreateFromAppImage(
+ app_image_name.c_str(),
+ odex_file.get(),
+ ArrayRef<ImageSpace* const>(non_owning_boot_image_spaces),
+ &error_msg);
+ ASSERT_TRUE(app_image_space != nullptr) << error_msg;
+
+ // The string in the app image should be replaced and removed from interned string section.
+ EXPECT_FALSE(contains_test_string(app_image_space.get()));
+}
+
TEST_F(DexoptTest, ValidateOatFile) {
std::string dex1 = GetScratchDir() + "/Dex1.jar";
std::string multidex1 = GetScratchDir() + "/MultiDex1.jar";
@@ -234,6 +427,8 @@
class NoAccessAndroidDataTest : public ImageSpaceLoadingTest<false, true, true> {
protected:
+ NoAccessAndroidDataTest() : quiet_(LogSeverity::FATAL) {}
+
void SetUpRuntimeOptions(RuntimeOptions* options) override {
const char* android_data = getenv("ANDROID_DATA");
CHECK(android_data != nullptr);
@@ -265,6 +460,7 @@
}
private:
+ ScopedLogSeverity quiet_;
std::string old_android_data_;
std::string bad_android_data_;
std::string bad_dalvik_cache_;