Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/art/art/dex2oat/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 43 kB image not shown  

Quelle  dex2oat_image_test.cc

  Sprache: C
 

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <fstream>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>

#include "common_runtime_test.h"

#include "art_method-inl.h"
#include "base/array_ref.h"
#include "base/file_utils.h"
#include "base/macros.h"
#include "base/mem_map.h"
#include "base/unix_file/fd_file.h"
#include "base/utils.h"
#include "compiler_callbacks.h"
#include "dex/art_dex_file_loader.h"
#include "dex/dex_file-inl.h"
#include "dex/dex_file_loader.h"
#include "dex/method_reference.h"
#include "dex/type_reference.h"
#include "gc/space/image_space.h"
#include "handle_scope-inl.h"
#include "intern_table-inl.h"
#include "jni/java_vm_ext.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"

namespace art {

// A suitable address for loading the core images.
constexpr uint32_t kBaseAddress = ART_BASE_ADDRESS;

struct ImageSizes {
  size_t art_size = 0;
  size_t oat_size = 0;
  size_t vdex_size = 0;
};

std::ostream& operator<<(std::ostream& os, const ImageSizes& sizes) {
  os << "art=" << sizes.art_size << " oat=" << sizes.oat_size << " vdex=" << sizes.vdex_size;
  return os;
}

class Dex2oatImageTest : public CommonRuntimeTest {
 public:
  void TearDown() override {}

 protected:
  void SetUpRuntimeOptions(RuntimeOptions* options) override {
    // Disable implicit dex2oat invocations when loading image spaces.
    options->emplace_back("-Xnoimage-dex2oat", nullptr);
    // Apply overrides.
    for (const auto& [prefix, replacement] : runtime_option_overrides_) {
      auto it = std::find_if(options->begin(),
                             options->end(),
                             [&](std::pair<std::string, const void*>& entry) {
                               return entry.first.starts_with(prefix);
                             });
      CHECK(it != options->end()) << prefix;
      *it = replacement;
    }
    // Destroy compiler callbacks if requested.
    if (runtime_option_no_compiler_callbacks_) {
      callbacks_.reset();
    }
  }

  static void WriteLine(File* file, std::string line) {
    line += '\n';
    EXPECT_TRUE(file->WriteFully(&line[0], line.length()));
  }

  void AddRuntimeArg(std::vector<std::string>& args, const std::string& arg) {
    args.push_back("--runtime-arg");
    args.push_back(arg);
  }

  ImageSizes CompileImageAndGetSizes(ArrayRef<const std::string> dex_files,
                                     const std::vector<std::string>& extra_args) {
    ImageSizes ret;
    ScratchDir scratch;
    std::string filename_prefix = scratch.GetPath() + "boot";
    std::vector<std::string> local_extra_args = extra_args;
    local_extra_args.push_back(android::base::StringPrintf("--base=0x%08x", kBaseAddress));
    std::string error_msg;
    if (!CompileBootImage(local_extra_args, filename_prefix, dex_files, &error_msg)) {
      LOG(ERROR) << "Failed to compile image " << filename_prefix << error_msg;
    }
    std::string art_file = filename_prefix + ".art";
    std::string oat_file = filename_prefix + ".oat";
    std::string vdex_file = filename_prefix + ".vdex";
    int64_t art_size = OS::GetFileSizeBytes(art_file.c_str());
    int64_t oat_size = OS::GetFileSizeBytes(oat_file.c_str());
    int64_t vdex_size = OS::GetFileSizeBytes(vdex_file.c_str());
    CHECK_GT(art_size, 0u) << art_file;
    CHECK_GT(oat_size, 0u) << oat_file;
    CHECK_GT(vdex_size, 0u) << vdex_file;
    ret.art_size = art_size;
    ret.oat_size = oat_size;
    ret.vdex_size = vdex_size;
    return ret;
  }

  MemMap ReserveCoreImageAddressSpace(/*out*/std::string* error_msg) {
    constexpr size_t kReservationSize = 256 * MB;  // This should be enough for the compiled images.
    // Extend to both directions for maximum relocation difference.
    static_assert(ART_BASE_ADDRESS_MIN_DELTA < 0);
    static_assert(ART_BASE_ADDRESS_MAX_DELTA > 0);
    static_assert(IsAligned<kElfSegmentAlignment>(ART_BASE_ADDRESS_MIN_DELTA));
    static_assert(IsAligned<kElfSegmentAlignment>(ART_BASE_ADDRESS_MAX_DELTA));
    constexpr size_t kExtra = ART_BASE_ADDRESS_MAX_DELTA - ART_BASE_ADDRESS_MIN_DELTA;
    uint32_t min_relocated_address = kBaseAddress + ART_BASE_ADDRESS_MIN_DELTA;
    return MemMap::MapAnonymous("Reservation",
                                reinterpret_cast<uint8_t*>(min_relocated_address),
                                kReservationSize + kExtra,
                                PROT_NONE,
                                /*low_4gb=*/ true,
                                /*reuse=*/ false,
                                /*reservation=*/ nullptr,
                                error_msg);
  }

  void CopyDexFiles(const std::string& dir, /*inout*/std::vector<std::string>* dex_files) {
    CHECK(dir.ends_with("/"));
    for (std::string& dex_file : *dex_files) {
      size_t slash_pos = dex_file.rfind('/');
      CHECK(OS::FileExists(dex_file.c_str())) << dex_file;
      CHECK_NE(std::string::npos, slash_pos);
      std::string new_location = dir + dex_file.substr(slash_pos + 1u);
      std::ifstream src_stream(dex_file, std::ios::binary);
      std::ofstream dst_stream(new_location, std::ios::binary);
      dst_stream << src_stream.rdbuf();
      dex_file = new_location;
    }
  }

  bool CompareFiles(const std::string& filename1, const std::string& filename2) {
    std::unique_ptr<File> file1(OS::OpenFileForReading(filename1.c_str()));
    std::unique_ptr<File> file2(OS::OpenFileForReading(filename2.c_str()));
    // Did we open the files?
    if (file1 == nullptr || file2 == nullptr) {
      return false;
    }
    // Are they non-empty and the same length?
    if (file1->GetLength() <= 0 || file2->GetLength() != file1->GetLength()) {
      return false;
    }
    return file1->Compare(file2.get()) == 0;
  }

  void AddAndroidRootToImageCompilerOptions() {
    const char* android_root = getenv("ANDROID_ROOT");
    CHECK(android_root != nullptr);
    Runtime::Current()->image_compiler_options_.push_back(
        "--android-root=" + std::string(android_root));
  }

  void EnableImageDex2Oat() {
    Runtime::Current()->image_dex2oat_enabled_ = true;
  }

  void DisableImageDex2Oat() {
    Runtime::Current()->image_dex2oat_enabled_ = false;
  }

  void FakeRuntimeStart() {
    Runtime::Current()->started_ = true;
  }

  // Maps prefix to a replacement runtime option.
  std::vector<std::pair<std::string, std::pair<std::string, const void*>>>
      runtime_option_overrides_;
  // Allow creating `Runtime` without compiler callbacks.
  bool runtime_option_no_compiler_callbacks_ = false;
};

TEST_F(Dex2oatImageTest, TestModesAndFilters) {
  // This test crashes on the gtest-heap-poisoning configuration
  // (AddressSanitizer + CMS/RosAlloc + heap-poisoning); see b/111061592.
  // Temporarily disable this test on this configuration to keep
  // our automated build/testing green while we work on a fix.
  TEST_DISABLED_FOR_MEMORY_TOOL_WITH_HEAP_POISONING_WITHOUT_READ_BARRIERS();
  if (kIsTargetBuild) {
    // This test is too slow for target builds.
    return;
  }
  // Compile only a subset of the libcore dex files to make this test shorter.
  std::vector<std::string> libcore_dex_files = GetLibCoreDexFileNames();
  // The primary image must contain at least core-oj and core-libart to initialize the runtime.
  ASSERT_NE(std::string::npos, libcore_dex_files[0].find("core-oj"));
  ASSERT_NE(std::string::npos, libcore_dex_files[1].find("core-libart"));
  ArrayRef<const std::string> dex_files =
      ArrayRef<const std::string>(libcore_dex_files).SubArray(/*pos=*/ 0u, /*length=*/ 2u);

  ImageSizes base_sizes = CompileImageAndGetSizes(dex_files, {});
  ImageSizes everything_sizes;
  ImageSizes filter_sizes;
  std::cout << "Base compile sizes " << base_sizes << std::endl;
  // Compile all methods and classes
  std::vector<std::string> libcore_dexes = GetLibCoreDexFileNames();
  ArrayRef<const std::string> libcore_dexes_array(libcore_dexes);
  {
    ScratchFile profile_file;
    GenerateBootProfile(libcore_dexes_array,
                        profile_file.GetFile(),
                        /*method_frequency=*/ 1u,
                        /*type_frequency=*/ 1u);
    everything_sizes = CompileImageAndGetSizes(
        dex_files,
        {"--profile-file=" + profile_file.GetFilename(),
         "--compiler-filter=speed-profile"});
    profile_file.Close();
    std::cout << "All methods and classes sizes " << everything_sizes << std::endl;
    // Putting all classes as image classes should increase art size
    EXPECT_GE(everything_sizes.art_size, base_sizes.art_size);
    // Check that dex is the same size.
    EXPECT_EQ(everything_sizes.vdex_size, base_sizes.vdex_size);
  }
  static size_t kMethodFrequency = 3;
  static size_t kTypeFrequency = 4;
  // Test compiling fewer methods and classes.
  {
    ScratchFile profile_file;
    GenerateBootProfile(libcore_dexes_array,
                        profile_file.GetFile(),
                        kMethodFrequency,
                        kTypeFrequency);
    filter_sizes = CompileImageAndGetSizes(
        dex_files,
        {"--profile-file=" + profile_file.GetFilename(),
         "--compiler-filter=speed-profile"});
    profile_file.Close();
    std::cout << "Fewer methods and classes sizes " << filter_sizes << std::endl;
    EXPECT_LE(filter_sizes.art_size, everything_sizes.art_size);
    EXPECT_LE(filter_sizes.oat_size, everything_sizes.oat_size);
    EXPECT_LE(filter_sizes.vdex_size, everything_sizes.vdex_size);
  }
  // Test dirty image objects.
  {
    ScratchFile classes;
    VisitDexes(libcore_dexes_array,
               VoidFunctor(),
               [&](TypeReference ref) {
      WriteLine(classes.GetFile(), ref.dex_file->PrettyType(ref.TypeIndex()));
    }, /*method_frequency=*/ 1u, /*class_frequency=*/ 1u);
    ImageSizes image_classes_sizes = CompileImageAndGetSizes(
        dex_files,
        {"--dirty-image-objects=" + classes.GetFilename()});
    classes.Close();
    std::cout << "Dirty image object sizes " << image_classes_sizes << std::endl;
  }
  // Test multiple dirty image objects.
  {
    std::array<ScratchFile, 2> files;
    int idx = 0;
    VisitDexes(
        libcore_dexes_array,
        VoidFunctor(),
        [&](TypeReference ref) {
          WriteLine(files[idx].GetFile(), ref.dex_file->PrettyType(ref.TypeIndex()));
          idx = (idx + 1) % files.size();
        },
        /*method_frequency=*/ 1u,
        /*class_frequency=*/ 1u);
    ImageSizes image_classes_sizes =
        CompileImageAndGetSizes(dex_files,
                                {"--dirty-image-objects=" + files[0].GetFilename(),
                                 "--dirty-image-objects=" + files[1].GetFilename()});
    for (ScratchFile& file : files) {
      file.Close();
    }
    std::cout << "Dirty image object sizes " << image_classes_sizes << std::endl;
  }
}

TEST_F(Dex2oatImageTest, TestExtension) {
  // TODO(b/376621099): investigate LUCI failures (timeouts?) and re-enable this test.
  // This is not related to riscv64 arch, but a combination of riscv64 and running on VM.
  TEST_DISABLED_ON_RISCV64_VM();

  std::string error_msg;
  MemMap reservation = ReserveCoreImageAddressSpace(&error_msg);
  ASSERT_TRUE(reservation.IsValid()) << error_msg;

  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);
  std::string filename_prefix = image_dir + "/boot";

  // Copy the libcore dex files to a custom dir inside `scratch_dir` so that we do not
  // accidentally load pre-compiled core images from their original directory based on BCP paths.
  std::string jar_dir = scratch_dir + "jars";
  mkdir_result = mkdir(jar_dir.c_str(), 0700);
  ASSERT_EQ(0, mkdir_result);
  jar_dir += '/';
  std::vector<std::string> libcore_dex_files = GetLibCoreDexFileNames();
  CopyDexFiles(jar_dir, &libcore_dex_files);

  ArrayRef<const std::string> full_bcp(libcore_dex_files);
  size_t total_dex_files = full_bcp.size();
  ASSERT_GE(total_dex_files, 4u);  // 2 for "head", 1 for "tail", at least one for "mid", see below.

  // The primary image must contain at least core-oj and core-libart to initialize the runtime.
  ASSERT_NE(std::string::npos, full_bcp[0].find("core-oj"));
  ASSERT_NE(std::string::npos, full_bcp[1].find("core-libart"));
  ArrayRef<const std::string> head_dex_files = full_bcp.SubArray(/*pos=*/ 0u, /*length=*/ 2u);
  // Middle part is everything else except for conscrypt.
  ASSERT_NE(std::string::npos, full_bcp[full_bcp.size() - 1u].find("conscrypt"));
  ArrayRef<const std::string> mid_bcp =
      full_bcp.SubArray(/*pos=*/ 0u, /*length=*/ total_dex_files - 1u);
  ArrayRef<const std::string> mid_dex_files = mid_bcp.SubArray(/*pos=*/ 2u);
  // Tail is just the conscrypt.
  ArrayRef<const std::string> tail_dex_files =
      full_bcp.SubArray(/*pos=*/ total_dex_files - 1u, /*length=*/ 1u);

  // Prepare the "head", "mid" and "tail" names and locations.
  std::string base_name = "boot.art";
  std::string base_location = scratch_dir + base_name;
  std::vector<std::string> expanded_mid = gc::space::ImageSpace::ExpandMultiImageLocations(
      mid_dex_files.SubArray(/*pos=*/ 0u, /*length=*/ 1u),
      base_location,
      /*boot_image_extension=*/ true);
  CHECK_EQ(1u, expanded_mid.size());
  std::string mid_location = expanded_mid[0];
  size_t mid_slash_pos = mid_location.rfind('/');
  ASSERT_NE(std::string::npos, mid_slash_pos);
  std::string mid_name = mid_location.substr(mid_slash_pos + 1u);
  CHECK_EQ(1u, tail_dex_files.size());
  std::vector<std::string> expanded_tail = gc::space::ImageSpace::ExpandMultiImageLocations(
      tail_dex_files, base_location, /*boot_image_extension=*/ true);
  CHECK_EQ(1u, expanded_tail.size());
  std::string tail_location = expanded_tail[0];
  size_t tail_slash_pos = tail_location.rfind('/');
  ASSERT_NE(std::string::npos, tail_slash_pos);
  std::string tail_name = tail_location.substr(tail_slash_pos + 1u);

  // Create profiles.
  ScratchFile head_profile_file;
  GenerateBootProfile(head_dex_files,
                      head_profile_file.GetFile(),
                      /*method_frequency=*/ 1u,
                      /*type_frequency=*/ 1u);
  const std::string& head_profile_filename = head_profile_file.GetFilename();
  ScratchFile mid_profile_file;
  GenerateBootProfile(mid_dex_files,
                      mid_profile_file.GetFile(),
                      /*method_frequency=*/ 5u,
                      /*type_frequency=*/ 4u);
  const std::string& mid_profile_filename = mid_profile_file.GetFilename();
  ScratchFile tail_profile_file;
  GenerateBootProfile(tail_dex_files,
                      tail_profile_file.GetFile(),
                      /*method_frequency=*/ 5u,
                      /*type_frequency=*/ 4u);
  const std::string& tail_profile_filename = tail_profile_file.GetFilename();

  // Compile the "head", i.e. the primary boot image.
  std::vector<std::string> extra_args;
  extra_args.push_back("--profile-file=" + head_profile_filename);
  extra_args.push_back(android::base::StringPrintf("--base=0x%08x", kBaseAddress));
  bool head_ok = CompileBootImage(extra_args, filename_prefix, head_dex_files, &error_msg);
  ASSERT_TRUE(head_ok) << error_msg;

  // Compile the "mid", i.e. the first extension.
  std::string mid_bcp_string = android::base::Join(mid_bcp, ':');
  extra_args.clear();
  extra_args.push_back("--profile-file=" + mid_profile_filename);
  AddRuntimeArg(extra_args, "-Xbootclasspath:" + mid_bcp_string);
  AddRuntimeArg(extra_args, "-Xbootclasspath-locations:" + mid_bcp_string);
  extra_args.push_back("--boot-image=" + base_location);
  bool mid_ok = CompileBootImage(extra_args, filename_prefix, mid_dex_files, &error_msg);
  ASSERT_TRUE(mid_ok) << error_msg;

  // Try to compile the "tail" without specifying the "mid" extension. This shall fail.
  extra_args.clear();
  extra_args.push_back("--profile-file=" + tail_profile_filename);
  std::string full_bcp_string = android::base::Join(full_bcp, ':');
  AddRuntimeArg(extra_args, "-Xbootclasspath:" + full_bcp_string);
  AddRuntimeArg(extra_args, "-Xbootclasspath-locations:" + full_bcp_string);
  extra_args.push_back("--boot-image=" + base_location);
  bool tail_ok = CompileBootImage(extra_args, filename_prefix, tail_dex_files, &error_msg);
  ASSERT_FALSE(tail_ok) << error_msg;

  // Now compile the tail against both "head" and "mid".
  CHECK(extra_args.back().starts_with("--boot-image="));
  extra_args.back() = "--boot-image=" + base_location + ':' + mid_location;
  tail_ok = CompileBootImage(extra_args, filename_prefix, tail_dex_files, &error_msg);
  ASSERT_TRUE(tail_ok) << error_msg;

  // Prepare directory for the single-image test that squashes the "mid" and "tail".
  std::string single_dir = scratch_dir + "single";
  mkdir_result = mkdir(single_dir.c_str(), 0700);
  ASSERT_EQ(0, mkdir_result);
  single_dir += '/';
  std::string single_image_dir = single_dir + GetInstructionSetString(kRuntimeISA);
  mkdir_result = mkdir(single_image_dir.c_str(), 0700);
  ASSERT_EQ(0, mkdir_result);
  std::string single_filename_prefix = single_image_dir + "/boot";

  // The dex files for the single-image are everything not in the "head".
  ArrayRef<const std::string> single_dex_files = full_bcp.SubArray(/*pos=*/ head_dex_files.size());

  // Create a smaller profile for the single-image test that squashes the "mid" and "tail".
  ScratchFile single_profile_file;
  GenerateBootProfile(single_dex_files,
                      single_profile_file.GetFile(),
                      /*method_frequency=*/ 5u,
                      /*type_frequency=*/ 4u);
  const std::string& single_profile_filename = single_profile_file.GetFilename();

  // Prepare the single image name and location.
  CHECK_GE(single_dex_files.size(), 2u);
  std::string single_base_location = single_dir + base_name;
  std::vector<std::string> expanded_single = gc::space::ImageSpace::ExpandMultiImageLocations(
      single_dex_files.SubArray(/*pos=*/ 0u, /*length=*/ 1u),
      single_base_location,
      /*boot_image_extension=*/ true);
  CHECK_EQ(1u, expanded_single.size());
  std::string single_location = expanded_single[0];
  size_t single_slash_pos = single_location.rfind('/');
  ASSERT_NE(std::string::npos, single_slash_pos);
  std::string single_name = single_location.substr(single_slash_pos + 1u);
  CHECK_EQ(single_name, mid_name);

  // Compile the single-image against the primary boot image.
  extra_args.clear();
  extra_args.push_back("--profile-file=" + single_profile_filename);
  AddRuntimeArg(extra_args, "-Xbootclasspath:" + full_bcp_string);
  AddRuntimeArg(extra_args, "-Xbootclasspath-locations:" + full_bcp_string);
  extra_args.push_back("--boot-image=" + base_location);
  extra_args.push_back("--single-image");
  extra_args.push_back("--avoid-storing-invocation");  // For comparison below.
  error_msg.clear();
  bool single_ok =
      CompileBootImage(extra_args, single_filename_prefix, single_dex_files, &error_msg);
  ASSERT_TRUE(single_ok) << error_msg;

  reservation = MemMap::Invalid();  // Free the reserved memory for loading images.

  // Try to load the boot image with different image locations.
  std::vector<std::string> boot_class_path = libcore_dex_files;
  std::vector<std::unique_ptr<gc::space::ImageSpace>> boot_image_spaces;
  bool relocate = false;
  MemMap extra_reservation;
  auto load = [&](const std::string& image_location) {
    boot_image_spaces.clear();
    extra_reservation = MemMap::Invalid();
    ScopedObjectAccess soa(Thread::Current());
    return gc::space::ImageSpace::LoadBootImage(
        /*boot_class_path=*/ boot_class_path,
        /*boot_class_path_locations=*/ libcore_dex_files,
        /*boot_class_path_files=*/ {},
        /*boot_class_path_image_files=*/ {},
        /*boot_class_path_vdex_files=*/ {},
        /*boot_class_path_oat_files=*/ {},
        android::base::Split(image_location, ":"),
        kRuntimeISA,
        relocate,
        /*executable=*/ true,
        /*extra_reservation_size=*/ 0u,
        /*allow_in_memory_compilation=*/ true,
        Runtime::GetApexVersions(ArrayRef<const std::string>(libcore_dex_files)),
        &boot_image_spaces,
        &extra_reservation);
  };
  auto silent_load = [&](const std::string& image_location) {
    ScopedLogSeverity quiet(LogSeverity::FATAL);
    return load(image_location);
  };

  for (bool r : { falsetrue }) {
    relocate = r;

    // Load primary image with full path.
    bool load_ok = load(base_location);
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_FALSE(extra_reservation.IsValid());
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());

    // Fail to load primary image with just the name.
    load_ok = silent_load(base_name);
    ASSERT_FALSE(load_ok);

    // Fail to load primary image with a search path.
    load_ok = silent_load("*");
    ASSERT_FALSE(load_ok);
    load_ok = silent_load(scratch_dir + "*");
    ASSERT_FALSE(load_ok);

    // Load the primary and first extension with full path.
    load_ok = load(ART_FORMAT("{}:{}", base_location, mid_location));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());

    // Load the primary with full path and fail to load first extension without full path.
    load_ok = load(ART_FORMAT("{}:{}", base_location, mid_name));
    ASSERT_TRUE(load_ok) << error_msg;  // Primary image loaded successfully.
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());  // But only the primary image.

    // Load all the libcore images with full paths.
    load_ok = load(ART_FORMAT("{}:{}:{}", base_location, mid_location, tail_location));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());

    // Load the primary and first extension with full paths, fail to load second extension by name.
    load_ok = load(ART_FORMAT("{}:{}:{}", base_location, mid_location, tail_name));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());

    // Load the primary with full path and fail to load first extension without full path,
    // fail to load second extension because it depends on the first.
    load_ok = load(ART_FORMAT("{}:{}:{}", base_location, mid_name, tail_location));
    ASSERT_TRUE(load_ok) << error_msg;  // Primary image loaded successfully.
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());  // But only the primary image.

    // Load the primary with full path and extensions with a specified search path.
    load_ok = load(ART_FORMAT("{}:{}*", base_location, scratch_dir));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());

    // Load the primary with full path and fail to find extensions in BCP path.
    load_ok = load(base_location + ":*");
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());
  }

  // Now copy the libcore dex files to the `scratch_dir` and retry loading the boot image
  // with BCP in the scratch_dir so that the images can be found based on BCP paths.
  CopyDexFiles(scratch_dir, &boot_class_path);

  for (bool r : { falsetrue }) {
    relocate = r;

    // Loading the primary image with just the name now succeeds.
    bool load_ok = load(base_name);
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());

    // Loading the primary image with a search path still fails.
    load_ok = silent_load("*");
    ASSERT_FALSE(load_ok);
    load_ok = silent_load(scratch_dir + "*");
    ASSERT_FALSE(load_ok);

    // Load the primary and first extension without paths.
    load_ok = load(ART_FORMAT("{}:{}", base_name, mid_name));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());

    // Load the primary without path and first extension with path.
    load_ok = load(ART_FORMAT("{}:{}", base_name, mid_location));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());

    // Load the primary with full path and the first extension without full path.
    load_ok = load(ART_FORMAT("{}:{}", base_location, mid_name));
    ASSERT_TRUE(load_ok) << error_msg;  // Loaded successfully.
    ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size());  // Including the extension.

    // Load all the libcore images without paths.
    load_ok = load(ART_FORMAT("{}:{}:{}", base_name, mid_name, tail_name));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());

    // Load the primary and first extension with full paths and second extension by name.
    load_ok = load(ART_FORMAT("{}:{}:{}", base_location, mid_location, tail_name));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());

    // Load the primary with full path, first extension without path,
    // and second extension with full path.
    load_ok = load(ART_FORMAT("{}:{}:{}", base_location, mid_name, tail_location));
    ASSERT_TRUE(load_ok) << error_msg;  // Loaded successfully.
    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());  // Including both extensions.

    // Load the primary with full path and find both extensions in BCP path.
    load_ok = load(base_location + ":*");
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(full_bcp.size(), boot_image_spaces.size());

    // Fail to load any images with invalid image locations (named component after search paths).
    load_ok = silent_load(ART_FORMAT("{}:*:{}", base_location, tail_location));
    ASSERT_FALSE(load_ok);
    load_ok = silent_load(ART_FORMAT("{}:{}*:{}", base_location, scratch_dir, tail_location));
    ASSERT_FALSE(load_ok);

    // Load the primary and single-image extension with full path.
    load_ok = load(ART_FORMAT("{}:{}", base_location, single_location));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size() + 1u, boot_image_spaces.size());

    // Load the primary with full path and single-image extension with a specified search path.
    load_ok = load(ART_FORMAT("{}:{}*", base_location, single_dir));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size() + 1u, boot_image_spaces.size());
  }

  // Recompile the single-image extension using file descriptors and compare contents.
  std::vector<std::string> expanded_single_filename_prefix =
      gc::space::ImageSpace::ExpandMultiImageLocations(
          single_dex_files.SubArray(/*pos=*/ 0u, /*length=*/ 1u),
          single_filename_prefix,
          /*boot_image_extension=*/ true);
  CHECK_EQ(1u, expanded_single_filename_prefix.size());
  std::string single_ext_prefix = expanded_single_filename_prefix[0];
  std::string single_ext_prefix2 = single_ext_prefix + "2";
  error_msg.clear();
  single_ok = CompileBootImage(extra_args,
                               single_filename_prefix,
                               single_dex_files,
                               &error_msg,
                               /*use_fd_prefix=*/ single_ext_prefix2);
  ASSERT_TRUE(single_ok) << error_msg;
  EXPECT_TRUE(CompareFiles(single_ext_prefix + ".art", single_ext_prefix2 + ".art"));
  EXPECT_TRUE(CompareFiles(single_ext_prefix + ".vdex", single_ext_prefix2 + ".vdex"));
  EXPECT_TRUE(CompareFiles(single_ext_prefix + ".oat", single_ext_prefix2 + ".oat"));

  // Test parsing profile specification and creating the boot image extension on-the-fly.
  // We must set --android-root in the image compiler options.
  AddAndroidRootToImageCompilerOptions();
  for (bool r : { falsetrue }) {
    relocate = r;

    // Load primary boot image with a profile name.
    bool load_ok = silent_load(ART_FORMAT("{}!{}", base_location, single_profile_filename));
    ASSERT_TRUE(load_ok);

    // Try and fail to load with invalid spec, two profile name separators.
    load_ok =
        silent_load(ART_FORMAT("{}:{}!!arbitrary-profile-name", base_location, single_location));
    ASSERT_FALSE(load_ok);

    // Try and fail to load with invalid spec, missing profile name.
    load_ok = silent_load(ART_FORMAT("{}:{}!", base_location, single_location));
    ASSERT_FALSE(load_ok);

    // Try and fail to load with invalid spec, missing component name.
    load_ok = silent_load(ART_FORMAT("{}:!{}", base_location, single_profile_filename));
    ASSERT_FALSE(load_ok);

    // Load primary boot image, specifying invalid extension component and profile name.
    load_ok = load(
        ART_FORMAT("{}:/non-existent/{}!non-existent-profile-name", base_location, single_name));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());

    // Load primary boot image and the single extension, specifying invalid profile name.
    // (Load extension from file.)
    load_ok = load(ART_FORMAT("{}:{}!non-existent-profile-name", base_location, single_location));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size() + 1u, boot_image_spaces.size());
    ASSERT_EQ(single_dex_files.size(),
              boot_image_spaces.back()->GetImageHeader().GetComponentCount());

    // Load primary boot image and fail to load the single extension, specifying
    // invalid extension component name but a valid profile file.
    // (Running dex2oat to compile extension is disabled.)
    ASSERT_FALSE(Runtime::Current()->IsImageDex2OatEnabled());
    load_ok = load(
        ART_FORMAT("{}:/non-existent/{}!{}", base_location, single_name, single_profile_filename));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());

    EnableImageDex2Oat();

    // Load primary boot image and the single extension, specifying invalid extension
    // component name but a valid profile file. (Compile extension by running dex2oat.)
    load_ok = load(
        ART_FORMAT("{}:/non-existent/{}!{}", base_location, single_name, single_profile_filename));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size() + 1u, boot_image_spaces.size());
    ASSERT_EQ(single_dex_files.size(),
              boot_image_spaces.back()->GetImageHeader().GetComponentCount());

    // Load primary boot image and two extensions, specifying invalid extension component
    // names but valid profile files. (Compile extensions by running dex2oat.)
    load_ok = load(ART_FORMAT("{}:/non-existent/{}!{}:/non-existent/{}!{}",
                              base_location,
                              mid_name,
                              mid_profile_filename,
                              tail_name,
                              tail_profile_filename));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size() + 2u, boot_image_spaces.size());
    ASSERT_EQ(mid_dex_files.size(),
              boot_image_spaces[head_dex_files.size()]->GetImageHeader().GetComponentCount());
    ASSERT_EQ(tail_dex_files.size(),
              boot_image_spaces[head_dex_files.size() + 1u]->GetImageHeader().GetComponentCount());

    // Load primary boot image and fail to load extensions, specifying invalid component
    // names but valid profile file only for the second one. As we fail to load the first
    // extension, the second extension has a missing dependency and cannot be compiled.
    load_ok = load(ART_FORMAT("{}:/non-existent/{}:/non-existent/{}!{}",
                              base_location,
                              mid_name,
                              tail_name,
                              tail_profile_filename));
    ASSERT_TRUE(load_ok) << error_msg;
    ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size());

    DisableImageDex2Oat();
  }
}

TEST_F(Dex2oatImageTest, InlinedString) {
  // TODO(b/480856545): investigate and fix SIGSEGV that happens on LUCI
  TEST_DISABLED_ON_VM();

  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);
  std::string filename_prefix = image_dir + "/boot";

  // The primary image must contain at least core-oj and core-libart to initialize the runtime.
  std::vector<std::string> bcp = GetLibCoreDexFileNames();
  ASSERT_GE(bcp.size(), 2u);
  ASSERT_NE(std::string::npos, bcp[0].find("core-oj"));
  ASSERT_NE(std::string::npos, bcp[1].find("core-libart"));
  bcp.resize(2u);

  // Add `StringLiterals` and `InlinedString` to the boot class path.
  // When compiling `InlinedString.getUniqueStringFromStringLiterals()`, we shall inline
  // `HLoadString` from `StringLiterals.getUniqueString()`. That string does not have
  // a `StringId` in the `InlinedString` dex file but we want to include it in the boot
  // image extension anyway and that requires special handling in `ImageWriter`.
  // Otherwise, `dex2oat` would crash when trying to determine the string's image
  // address when processing the method's `LinkerPatch` for that string.
  bcp.push_back(GetTestDexFileName("StringLiterals"));
  bcp.push_back(GetTestDexFileName("InlinedString"));

  // Copy the dex files to a custom dir inside `scratch_dir` so that we do not accidentally
  // try to load pre-compiled core images from their original directory based on BCP paths.
  std::string jar_dir = scratch_dir + "jars";
  mkdir_result = mkdir(jar_dir.c_str(), 0700);
  ASSERT_EQ(0, mkdir_result);
  jar_dir += '/';
  CopyDexFiles(jar_dir, &bcp);

  // Split the BCP to primary and extension (just the last dex file).
  ArrayRef<const std::string> primary_dex_files =
      ArrayRef<const std::string>(bcp).SubArray(/*pos=*/ 0u, /*length=*/ bcp.size() - 1u);
  ArrayRef<const std::string> extension_dex_files =
      ArrayRef<const std::string>(bcp).SubArray(/*pos=*/ bcp.size() - 1u, /*length=*/ 1u);

  // Prepare the primary and extension locations.
  std::string base_name = "boot.art";
  std::string base_location = scratch_dir + base_name;
  CHECK_EQ(1u, extension_dex_files.size());
  std::vector<std::string> expanded_extension = gc::space::ImageSpace::ExpandMultiImageLocations(
      extension_dex_files, base_location, /*boot_image_extension=*/ true);
  CHECK_EQ(1u, expanded_extension.size());
  std::string extension_location = expanded_extension[0];

  // Create profiles for primary boot image and extension. We want all classes from
  // `StringLiterals`, so we split the primary boot image profile into two files.
  ScratchFile primary_profile_file1, primary_profile_file2;
  GenerateBootProfile(
      primary_dex_files.SubArray(/*pos=*/ 0u, /*length=*/ primary_dex_files.size() - 1u),
      primary_profile_file1.GetFile(),
      /*method_frequency=*/ 100u,
      /*type_frequency=*/ 100u);
  GenerateBootProfile(
      primary_dex_files.SubArray(/*pos=*/ primary_dex_files.size() - 1u),
      primary_profile_file2.GetFile(),
      /*method_frequency=*/ std::numeric_limits<size_t>::max(),
      /*type_frequency=*/ 1u);
  ScratchFile extension_profile_file;
  GenerateBootProfile(extension_dex_files,
                      extension_profile_file.GetFile(),
                      /*method_frequency=*/ 1u,
                      /*type_frequency=*/ 1u);

  // Compile the primary boot image.
  std::string error_msg;
  std::vector<std::string> extra_args;
  extra_args.push_back("--profile-file=" + primary_profile_file1.GetFilename());
  extra_args.push_back("--profile-file=" + primary_profile_file2.GetFilename());
  extra_args.push_back(android::base::StringPrintf("--base=0x%08x", kBaseAddress));
  bool primary_ok = CompileBootImage(extra_args, filename_prefix, primary_dex_files, &error_msg);
  ASSERT_TRUE(primary_ok) << error_msg;

  // Compile the extension.
  std::string extension_bcp_string = android::base::Join(bcp, ':');
  extra_args.clear();
  extra_args.push_back("--profile-file=" + extension_profile_file.GetFilename());
  AddRuntimeArg(extra_args, "-Xbootclasspath:" + extension_bcp_string);
  AddRuntimeArg(extra_args, "-Xbootclasspath-locations:" + extension_bcp_string);
  extra_args.push_back("--boot-image=" + base_location);
  bool ext_ok = CompileBootImage(extra_args, filename_prefix, extension_dex_files, &error_msg);
  ASSERT_TRUE(ext_ok) << error_msg;

  {
    // Load the three-component primary boot image and single-component extension.
    std::vector<std::unique_ptr<gc::space::ImageSpace>> boot_image_spaces;
    MemMap extra_reservation = MemMap::Invalid();
    ScopedObjectAccess soa(Thread::Current());
    bool load_ok = gc::space::ImageSpace::LoadBootImage(
        /*boot_class_path=*/ bcp,
        /*boot_class_path_locations=*/ bcp,
        /*boot_class_path_files=*/ {},
        /*boot_class_path_image_files=*/ {},
        /*boot_class_path_vdex_files=*/ {},
        /*boot_class_path_oat_files=*/ {},
        {base_location, extension_location},
        kRuntimeISA,
        /*relocate=*/ false,
        /*executable=*/ true,
        /*extra_reservation_size=*/ 0u,
        /*allow_in_memory_compilation=*/ true,
        Runtime::GetApexVersions(ArrayRef<const std::string>(bcp)),
        &boot_image_spaces,
        &extra_reservation);
    ASSERT_TRUE(load_ok);
    ASSERT_EQ(4u, boot_image_spaces.size());

    // Check that there is no String .bss entry in any boot image oat file.
    for (const std::unique_ptr<gc::space::ImageSpace>& space : boot_image_spaces) {
      const OatFile* oat_file = space->GetOatFile();
      ASSERT_TRUE(oat_file != nullptr);
      for (const auto& info : oat_file->GetBcpBssInfo()) {
        ASSERT_TRUE(info.string_bss_mapping == nullptr);
      }
    }

    // Check the presence of the inlined string in the boot image extension.
    if (com::android::art::flags::weak_const_string()) {
      const char kStringToFind[] = "Unique string for gtests from StringLiterals";
      const size_t kStringToFindUtf16Length = /* Same as ASCII length. */ strlen(kStringToFind);
      gc::space::ImageSpace* extension_space = boot_image_spaces.back().get();
      const ImageSection& extension_interns =
          extension_space->GetImageHeader().GetInternedStringsSection();
      ASSERT_NE(extension_interns.Size(), 0u);
      const uint8_t* intern_data = extension_space->Begin() + extension_interns.Offset();
      size_t read_count;
      InternTable::UnorderedSet intern_set(intern_data, /*make_copy_of_data=*/ false, &read_count);
      auto it = intern_set.find(InternTable::Utf8String(kStringToFindUtf16Length, kStringToFind));
      ASSERT_TRUE(it != intern_set.end());
    }
  }

  // Also compile the `InlinedString` as an app with app image. We need an app image profile.
  ScratchFile app_profile_file;
  GenerateProfile(extension_dex_files,
                  app_profile_file.GetFile(),
                  /*method_frequency=*/ 1u,
                  /*type_frequency=*/ 1u);
  std::string primary_bcp_string = android::base::Join(primary_dex_files, ':');
  std::string app_jar_name = extension_dex_files[0];
  std::string app_odex_name = ReplaceFileExtension(app_jar_name, kOdexExtension);
  std::string app_image_name = ReplaceFileExtension(app_jar_name, kArtExtension);
  std::vector<std::string> argv;
  bool success = StartDex2OatCommandLine(&argv, &error_msg, /*use_runtime_bcp_and_image=*/ false);
  ASSERT_TRUE(success) << error_msg;
  argv.insert(argv.end(),
              {
                  "--profile-file=" + app_profile_file.GetFilename(),
                  "--runtime-arg",
                  "-Xbootclasspath:" + primary_bcp_string,
                  "--runtime-arg",
                  "-Xbootclasspath-locations:" + primary_bcp_string,
                  "--boot-image=" + base_location,
                  "--dex-file=" + app_jar_name,
                  "--dex-location=" + app_jar_name,
                  "--oat-file=" + app_odex_name,
                  "--app-image-file=" + app_image_name,
              });
  bool app_ok = RunDex2Oat(argv, &error_msg);
  ASSERT_TRUE(app_ok) << error_msg;

  // Destroy the current `Runtime` and create a new one with the compiled primary boot image.
  runtime_.reset();
  ASSERT_TRUE(Runtime::Current() == nullptr);
  runtime_option_overrides_.push_back(
      std::make_pair("-Xbootclasspath:",
                     std::make_pair("-Xbootclasspath:" + primary_bcp_string, nullptr)));
  runtime_option_overrides_.push_back(
      std::make_pair("-Xbootclasspath-locations:",
                     std::make_pair("-Xbootclasspath-locations:" + primary_bcp_string, nullptr)));
  runtime_option_overrides_.push_back(
      std::make_pair("-Ximage:", std::make_pair("-Ximage:" + base_location, nullptr)));
  use_boot_image_ = true;
  runtime_option_no_compiler_callbacks_ = true;
  CreateRuntime();
  ASSERT_TRUE(Runtime::Current() != nullptr);
  FakeRuntimeStart();  // Allow execution of compiled code from the oat file.

  // Load the compiled `InlinedString` dex file with its app image.
  std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1,
                                                  app_odex_name,
                                                  app_odex_name,
                                                  /*executable=*/ true,
                                                  /*low_4gb=*/ false,
                                                  app_jar_name,
                                                  &error_msg));
  ASSERT_TRUE(oat_file != nullptr) << error_msg;
  ASSERT_TRUE(oat_file->IsExecutable());
  ASSERT_EQ(1u, oat_file->GetOatDexFiles().size());
  std::unique_ptr<const DexFile> dex_file = oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg);
  ASSERT_TRUE(dex_file != nullptr) << error_msg;

  Thread* self = Thread::Current();
  jweak weak_str;
  {
    // Create a class loader for the `InlinedString` dex file.
    ScopedObjectAccess soa(self);
    ClassLinker* cl = Runtime::Current()->GetClassLinker();
    jobject class_loader = cl->CreatePathClassLoader(self, {dex_file.get()});
    ASSERT_TRUE(class_loader != nullptr);

    // Load the `InlinedString` class, find and call the `getUniqueStringFromStringLiterals()`
    // method and convert the result to a weak global.
    static const char kDescriptor[] = "LInlinedString;";
    StackHandleScope<2u> hs(self);
    Handle<mirror::ClassLoader> h_class_loader =
        hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
    Handle<mirror::Class> klass =
        hs.NewHandle(cl->FindClass(self, kDescriptor, strlen(kDescriptor), h_class_loader));
    ASSERT_TRUE(klass != nullptr);
    ASSERT_TRUE(
        cl->EnsureInitialized(self, klass, /*can_init_fields=*/ true, /*can_init_parents=*/ true));
    ArtMethod* method = klass->FindClassMethod("getUniqueStringFromStringLiterals",
                                               "()Ljava/lang/String;",
                                               kRuntimePointerSize);
    ASSERT_TRUE(method != nullptr);
    // The call puts the string to the `DexCache` and the .bss strings section.
    ObjPtr<mirror::Object> str = method->InvokeStatic<'L'>(self);
    ASSERT_TRUE(str != nullptr);
    ASSERT_TRUE(str->IsString());
    ASSERT_TRUE(soa.Vm() != nullptr);
    weak_str = soa.Vm()->AddWeakGlobalRef(self, str);
  }
  // Collect garbage and check that the `weak_str` is cleared if the weak `const-string`
  // interns feature is enabled.
  runtime_->GetHeap()->CollectGarbage(/*clear_soft_references=*/ true);
  {
    ScopedObjectAccess soa(self);
    bool is_cleared = soa.Decode<mirror::Object>(weak_str) == nullptr;
    ASSERT_EQ(com::android::art::flags::weak_const_string(), is_cleared);
  }
}

}  // namespace art

Messung V0.5 in Prozent
C=83 H=93 G=87

¤ Dauer der Verarbeitung: 0.18 Sekunden  (vorverarbeitet am  2026-06-29) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.