/*
 * 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 "boot_image_profile.h"

#include <memory>
#include <set>

#include "android-base/file.h"
#include "base/unix_file/fd_file.h"
#include "dex/class_accessor-inl.h"
#include "dex/descriptors_names.h"
#include "dex/dex_file-inl.h"
#include "dex/method_reference.h"
#include "dex/type_reference.h"
#include "profile/profile_compilation_info.h"

namespace art {

using Hotness = ProfileCompilationInfo::MethodHotness;

static const std::string kMethodSep = "->";  // NOLINT [runtime/string] [4]
static const std::string kPackageUseDelim = "@";  // NOLINT [runtime/string] [4]
static constexpr char kMethodFlagStringHot = 'H';
static constexpr char kMethodFlagStringStartup = 'S';
static constexpr char kMethodFlagStringPostStartup = 'P';

// Returns the type descriptor of the given reference.
static std::string GetTypeDescriptor(const TypeReference& ref) {
  const dex::TypeId& type_id = ref.dex_file->GetTypeId(ref.TypeIndex());
  return ref.dex_file->GetTypeDescriptor(type_id);
}

// Returns the method representation used in the text format of the boot image profile.
static std::string BootImageRepresentation(const MethodReference& ref) {
  const DexFile* dex_file = ref.dex_file;
  const dex::MethodId& id = ref.GetMethodId();
  std::string signature_string(dex_file->GetMethodSignature(id).ToString());
  std::string type_string(dex_file->GetTypeDescriptor(dex_file->GetTypeId(id.class_idx_)));
  std::string method_name(dex_file->GetMethodName(id));
  return type_string +
        kMethodSep +
        method_name +
        signature_string;
}

// Returns the class representation used in the text format of the boot image profile.
static std::string BootImageRepresentation(const TypeReference& ref) {
  return GetTypeDescriptor(ref);
}

// Returns the class representation used in preloaded classes.
static std::string PreloadedClassesRepresentation(const TypeReference& ref) {
  std::string descriptor = GetTypeDescriptor(ref);
  return DescriptorToDot(descriptor.c_str());
}

// Formats the list of packages from the item metadata as a debug string.
static std::string GetPackageUseString(const FlattenProfileData::ItemMetadata& metadata) {
  std::string result;
  for (const auto& it : metadata.GetAnnotations()) {
    result += it.GetOriginPackageName() + ",";
  }

  return metadata.GetAnnotations().empty()
      ? result
      : result.substr(0, result.size() - 1);
}

// Converts a method representation to its final profile format.
static std::string MethodToProfileFormat(
    const std::string& method,
    const FlattenProfileData::ItemMetadata& metadata,
    bool output_package_use) {
  std::string flags_string;
  if (metadata.HasFlagSet(Hotness::kFlagHot)) {
    flags_string += kMethodFlagStringHot;
  }
  if (metadata.HasFlagSet(Hotness::kFlagStartup)) {
    flags_string += kMethodFlagStringStartup;
  }
  if (metadata.HasFlagSet(Hotness::kFlagPostStartup)) {
    flags_string += kMethodFlagStringPostStartup;
  }
  std::string extra;
  if (output_package_use) {
    extra = kPackageUseDelim + GetPackageUseString(metadata);
  }

  return flags_string + method + extra;
}

// Converts a class representation to its final profile or preloaded classes format.
static std::string ClassToProfileFormat(
    const std::string& classString,
    const FlattenProfileData::ItemMetadata& metadata,
    bool output_package_use) {
  std::string extra;
  if (output_package_use) {
    extra = kPackageUseDelim + GetPackageUseString(metadata);
  }

  return classString + extra;
}

// Tries to asses if the given type reference is a clean class.
static bool MaybeIsClassClean(const TypeReference& ref) {
  const dex::ClassDef* class_def = ref.dex_file->FindClassDef(ref.TypeIndex());
  if (class_def == nullptr) {
    return false;
  }

  ClassAccessor accessor(*ref.dex_file, *class_def);
  for (auto& it : accessor.GetStaticFields()) {
    if (!it.IsFinal()) {
      // Not final static field will probably dirty the class.
      return false;
    }
  }
  for (auto& it : accessor.GetMethods()) {
    uint32_t flags = it.GetAccessFlags();
    if ((flags & kAccNative) != 0) {
      // Native method will get dirtied.
      return false;
    }
    if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) {
      // Class initializer, may get dirtied (not sure).
      return false;
    }
  }

  return true;
}

// Returns true iff the item should be included in the profile.
// (i.e. it passes the given aggregation thresholds)
static bool IncludeItemInProfile(uint32_t max_aggregation_count,
                                 uint32_t item_threshold,
                                 const FlattenProfileData::ItemMetadata& metadata,
                                 const BootImageOptions& options) {
  CHECK_NE(max_aggregation_count, 0u);
  float item_percent = metadata.GetAnnotations().size() / static_cast<float>(max_aggregation_count);
  for (const auto& annotIt : metadata.GetAnnotations()) {
    const auto&thresholdIt =
        options.special_packages_thresholds.find(annotIt.GetOriginPackageName());
    if (thresholdIt != options.special_packages_thresholds.end()) {
      if (item_percent >= (thresholdIt->second / 100.f)) {
        return true;
      }
    }
  }
  return item_percent >= (item_threshold / 100.f);
}

// Returns true iff a method with the given metada should be included in the profile.
static bool IncludeMethodInProfile(uint32_t max_aggregation_count,
                                   const FlattenProfileData::ItemMetadata& metadata,
                                   const BootImageOptions& options) {
  return IncludeItemInProfile(max_aggregation_count, options.method_threshold, metadata, options);
}

// Returns true iff a class with the given metada should be included in the profile.
static bool IncludeClassInProfile(const TypeReference& type_ref,
                                  uint32_t max_aggregation_count,
                                  const FlattenProfileData::ItemMetadata& metadata,
                                  const BootImageOptions& options) {
  uint32_t threshold = MaybeIsClassClean(type_ref)
      ? options.image_class_clean_threshold
      : options.image_class_threshold;
  return IncludeItemInProfile(max_aggregation_count, threshold, metadata, options);
}

// Returns true iff a class with the given metada should be included in the list of
// prelaoded classes.
static bool IncludeInPreloadedClasses(const std::string& class_name,
                                      uint32_t max_aggregation_count,
                                      const FlattenProfileData::ItemMetadata& metadata,
                                      const BootImageOptions& options) {
  bool denylisted = options.preloaded_classes_denylist.find(class_name) !=
      options.preloaded_classes_denylist.end();
  return !denylisted && IncludeItemInProfile(
      max_aggregation_count, options.preloaded_class_threshold, metadata, options);
}

bool GenerateBootImageProfile(
    const std::vector<std::unique_ptr<const DexFile>>& dex_files,
    const std::vector<std::string>& profile_files,
    const BootImageOptions& options,
    const std::string& boot_profile_out_path,
    const std::string& preloaded_classes_out_path) {
  if (boot_profile_out_path.empty()) {
    LOG(ERROR) << "No output file specified";
    return false;
  }

  bool generate_preloaded_classes = !preloaded_classes_out_path.empty();

  std::unique_ptr<FlattenProfileData> flattend_data(new FlattenProfileData());
  for (const std::string& profile_file : profile_files) {
    ProfileCompilationInfo profile(/*for_boot_image=*/ true);
    if (!profile.Load(profile_file, /*clear_if_invalid=*/ false)) {
      LOG(ERROR) << "Profile is not a valid: " << profile_file;
      return false;
    }
    std::unique_ptr<FlattenProfileData> currentData = profile.ExtractProfileData(dex_files);
    flattend_data->MergeData(*currentData);
  }

  // We want the output sorted by the method/class name.
  // So we use an intermediate map for that.
  // There's no attempt to optimize this as it's not part of any critical path,
  // and mostly executed on hosts.
  SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_methods;
  SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_classes;
  SafeMap<std::string, FlattenProfileData::ItemMetadata> preloaded_classes;

  for (const auto& it : flattend_data->GetMethodData()) {
    if (IncludeMethodInProfile(flattend_data->GetMaxAggregationForMethods(), it.second, options)) {
      FlattenProfileData::ItemMetadata metadata(it.second);
      if (options.upgrade_startup_to_hot
          && ((metadata.GetFlags() & Hotness::Flag::kFlagStartup) != 0)) {
        metadata.AddFlag(Hotness::Flag::kFlagHot);
      }
      profile_methods.Put(BootImageRepresentation(it.first), metadata);
    }
  }

  for (const auto& it : flattend_data->GetClassData()) {
    const TypeReference& type_ref = it.first;
    const FlattenProfileData::ItemMetadata& metadata = it.second;
    if (IncludeClassInProfile(type_ref,
            flattend_data->GetMaxAggregationForClasses(),
            metadata,
            options)) {
      profile_classes.Put(BootImageRepresentation(it.first), it.second);
    }
    std::string preloaded_class_representation = PreloadedClassesRepresentation(it.first);
    if (generate_preloaded_classes && IncludeInPreloadedClasses(
            preloaded_class_representation,
            flattend_data->GetMaxAggregationForClasses(),
            metadata,
            options)) {
      preloaded_classes.Put(preloaded_class_representation, it.second);
    }
  }

  // Create the output content
  std::string profile_content;
  std::string preloaded_content;
  for (const auto& it : profile_classes) {
    profile_content += ClassToProfileFormat(it.first, it.second, options.append_package_use_list)
        + "\n";
  }
  for (const auto& it : profile_methods) {
    profile_content += MethodToProfileFormat(it.first, it.second, options.append_package_use_list)
        + "\n";
  }

  if (generate_preloaded_classes) {
    for (const auto& it : preloaded_classes) {
      preloaded_content +=
          ClassToProfileFormat(it.first, it.second, options.append_package_use_list) + "\n";
    }
  }

  return android::base::WriteStringToFile(profile_content, boot_profile_out_path)
      && (!generate_preloaded_classes
          || android::base::WriteStringToFile(preloaded_content, preloaded_classes_out_path));
}

}  // namespace art
