/*
 * Copyright (C) 2011 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 <string.h>
#include <vector>

#include "image_test.h"

#include "base/pointer_size.h"
#include "oat/image.h"
#include "scoped_thread_state_change-inl.h"
#include "thread.h"

namespace art {
namespace linker {

TEST_F(ImageTest, TestImageLayout) {
  std::vector<size_t> image_sizes;
  std::vector<size_t> image_sizes_extra;
  // Compile multi-image with ImageLayoutA being the last image.
  {
    CompilationHelper helper;
    Compile(ImageHeader::kStorageModeUncompressed,
            /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
            helper,
            "ImageLayoutA",
            {"LMyClass;"});
    image_sizes = helper.GetImageObjectSectionSizes();
  }
  TearDown();
  runtime_.reset();
  SetUp();
  // Compile multi-image with ImageLayoutB being the last image.
  {
    CompilationHelper helper;
    Compile(ImageHeader::kStorageModeUncompressed,
            /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
            helper,
            "ImageLayoutB",
            {"LMyClass;"});
    image_sizes_extra = helper.GetImageObjectSectionSizes();
  }
  // Make sure that the new stuff in the clinit in ImageLayoutB is in the last image and not in the
  // first two images.
  ASSERT_EQ(image_sizes.size(), image_sizes.size());
  // Sizes of the object sections should be the same for all but the last image.
  for (size_t i = 0; i < image_sizes.size() - 1; ++i) {
    EXPECT_EQ(image_sizes[i], image_sizes_extra[i]);
  }
  // Last image should be larger since it has a hash map and a string.
  EXPECT_LT(image_sizes.back(), image_sizes_extra.back());
}

TEST_F(ImageTest, ImageHeaderIsValid) {
  uint32_t image_begin = ART_BASE_ADDRESS;
  uint32_t image_size_ = kElfSegmentAlignment;
  uint32_t image_roots = ART_BASE_ADDRESS + (1 * KB);
  uint32_t oat_checksum = 0;
  uint32_t oat_file_begin = ART_BASE_ADDRESS + (kElfSegmentAlignment);
  uint32_t oat_data_begin = ART_BASE_ADDRESS + (2 * kElfSegmentAlignment);
  uint32_t oat_data_end = ART_BASE_ADDRESS + (2 * kElfSegmentAlignment + 1 * KB);
  uint32_t oat_file_end = ART_BASE_ADDRESS + (2 * kElfSegmentAlignment + 2 * KB);
  ImageSection sections[ImageHeader::kSectionCount];
  uint32_t image_reservation_size = RoundUp(oat_file_end - image_begin, kElfSegmentAlignment);
  ImageHeader image_header(image_reservation_size,
                           /*component_count=*/ 1u,
                           image_begin,
                           image_size_,
                           sections,
                           image_roots,
                           oat_checksum,
                           oat_file_begin,
                           oat_data_begin,
                           oat_data_end,
                           oat_file_end,
                           /*boot_image_begin=*/ 0u,
                           /*boot_image_size=*/ 0u,
                           /*boot_image_component_count=*/ 0u,
                           /*boot_image_checksum=*/ 0u,
                           kRuntimePointerSize);
  ASSERT_TRUE(image_header.IsValid());

  // Please note that for the following condition to be true, the above values should be chosen in
  // a way that image_reservation_size != RoundUp(image_size_, kElfSegmentAlignment).
  ASSERT_TRUE(!image_header.IsAppImage());

  char* magic = const_cast<char*>(image_header.GetMagic());
  strcpy(magic, "");  // bad magic
  ASSERT_FALSE(image_header.IsValid());
  strcpy(magic, "art\n000");  // bad version
  ASSERT_FALSE(image_header.IsValid());
}

// Test that pointer to quick code is the same in
// a default method of an interface and in a copied method
// of a class which implements the interface. This should be true
// only if the copied method and the origin method are located in the
// same oat file.
TEST_F(ImageTest, TestDefaultMethods) {
  // TODO(b/376621099): investigate LUCI failures (timeouts?) and re-enable this test.
  // This is probably not related to riscv64 arch, but a combination of riscv64 and running
  // on VM, but we don't use TEST_DISABLED_ON_VM to keep running it on other VM builders.
  TEST_DISABLED_FOR_RISCV64();

  // Use this test to compile managed code to catch crashes when compiling the boot class path.
  // This test already needs to compile some managed methods and by compiling with "speed" we
  // avoid the need to create a specialized profile for the "speed-profile" compilation.
  // (Using "speed" shall compile most methods. We could compile more with "everything".)
  SetCompilerFilter(CompilerFilter::kSpeed);
  CompilationHelper helper;
  Compile(ImageHeader::kStorageModeUncompressed,
          /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
          helper,
          "DefaultMethods",
          {"LIface;", "LImpl;", "LIterableBase;"});

  PointerSize pointer_size = class_linker_->GetImagePointerSize();
  Thread* self = Thread::Current();
  ScopedObjectAccess soa(self);

  // Test the pointer to quick code is the same in origin method
  // and in the copied method from the same oat file.
  ObjPtr<mirror::Class> iface_klass =
      class_linker_->LookupClass(self, "LIface;", /*class_loader=*/ nullptr);
  ASSERT_NE(nullptr, iface_klass);
  ArtMethod* origin = iface_klass->FindInterfaceMethod("defaultMethod", "()V", pointer_size);
  ASSERT_NE(nullptr, origin);
  ASSERT_OBJ_PTR_EQ(origin->GetDeclaringClass(), iface_klass);
  const void* code = origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size);
  // The origin method should have a pointer to quick code
  ASSERT_NE(nullptr, code);
  ASSERT_FALSE(class_linker_->IsQuickToInterpreterBridge(code));
  ObjPtr<mirror::Class> impl_klass =
      class_linker_->LookupClass(self, "LImpl;", /*class_loader=*/ nullptr);
  ASSERT_NE(nullptr, impl_klass);
  ArtMethod* copied = FindCopiedMethod(origin, impl_klass);
  ASSERT_NE(nullptr, copied);
  // the copied method should have pointer to the same quick code as the origin method
  ASSERT_EQ(code, copied->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size));

  // Test the origin method has pointer to quick code
  // but the copied method has pointer to interpreter
  // because these methods are in different oat files.
  ObjPtr<mirror::Class> iterable_klass =
      class_linker_->LookupClass(self, "Ljava/lang/Iterable;", /*class_loader=*/ nullptr);
  ASSERT_NE(nullptr, iterable_klass);
  origin = iterable_klass->FindClassMethod(
      "forEach", "(Ljava/util/function/Consumer;)V", pointer_size);
  ASSERT_NE(nullptr, origin);
  ASSERT_FALSE(origin->IsDirect());
  ASSERT_OBJ_PTR_EQ(origin->GetDeclaringClass(), iterable_klass);
  code = origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size);
  // the origin method should have a pointer to quick code
  ASSERT_NE(nullptr, code);
  ASSERT_FALSE(class_linker_->IsQuickToInterpreterBridge(code));
  ObjPtr<mirror::Class> iterablebase_klass =
      class_linker_->LookupClass(self, "LIterableBase;", /*class_loader=*/ nullptr);
  ASSERT_NE(nullptr, iterablebase_klass);
  copied = FindCopiedMethod(origin, iterablebase_klass);
  ASSERT_NE(nullptr, copied);
  code = copied->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size);
  // the copied method should have a pointer to interpreter
  ASSERT_TRUE(class_linker_->IsQuickToInterpreterBridge(code));
}

// Regression test for dex2oat crash for soft verification failure during
// class initialization check from the transactional interpreter while
// running the class initializer for another class.
TEST_F(ImageTest, TestSoftVerificationFailureDuringClassInitialization) {
  CompilationHelper helper;
  Compile(ImageHeader::kStorageModeUncompressed,
          /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
          helper,
          "VerifySoftFailDuringClinit",
          /*image_classes=*/ {"LClassToInitialize;"},
          /*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;"});
}

// Regression test for b/297453985, where we used to generate a bogus image
// checksum.
TEST_F(ImageTest, ImageChecksum) {
  uint32_t image_begin = ART_BASE_ADDRESS;
  uint32_t image_roots = ART_BASE_ADDRESS + (1 * KB);
  ImageSection sections[ImageHeader::kSectionCount];
  // We require bitmap section to be at least kElfSegmentAlignment.
  sections[ImageHeader::kSectionImageBitmap] = ImageSection(0, kElfSegmentAlignment);
  ImageHeader image_header(/*image_reservation_size=*/ kElfSegmentAlignment,
                           /*component_count=*/ 1u,
                           image_begin,
                           /*image_size=*/ sizeof(ImageHeader),
                           sections,
                           image_roots,
                           /*oat_checksum=*/ 0u,
                           /*oat_file_begin=*/ 0u,
                           /*oat_data_begin=*/ 0u,
                           /*oat_data_end=*/ 0u,
                           /*oat_file_end=*/ 0u,
                           /*boot_image_begin=*/ 0u,
                           /*boot_image_size=*/ 0u,
                           /*boot_image_component_count=*/ 0u,
                           /*boot_image_checksum=*/ 0u,
                           kRuntimePointerSize);
    ASSERT_TRUE(image_header.IsValid());

    std::string error_msg;
    ImageFileGuard image_file;
    ScratchFile location;
    image_file.reset(OS::CreateEmptyFile(location.GetFilename().c_str()));
    const uint8_t* data = reinterpret_cast<const uint8_t*>(&image_header);
    std::unique_ptr<uint8_t> bitmap(new uint8_t[kElfSegmentAlignment]);
    memset(bitmap.get(), 0, kElfSegmentAlignment);
    ASSERT_EQ(image_header.GetImageChecksum(), 0u);
    ASSERT_TRUE(image_header.WriteData(
        image_file,
        data,
        bitmap.get(),
        ImageHeader::kStorageModeUncompressed,
        /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
        /*update_checksum=*/ true,
        &error_msg)) << error_msg;

    uint32_t first_checksum = image_header.GetImageChecksum();
    // Reset the image checksum, `WriteData` updated it.
    image_header.SetImageChecksum(0u);

    // Change the header to ensure the checksum will be different.
    image_header.SetOatChecksum(0xFFFF);

    ASSERT_TRUE(image_header.WriteData(
        image_file,
        data,
        bitmap.get(),
        ImageHeader::kStorageModeUncompressed,
        /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
        /*update_checksum=*/ true,
        &error_msg)) << error_msg;

    ASSERT_NE(first_checksum, image_header.GetImageChecksum());
}

}  // namespace linker
}  // namespace art
