text: /*
   * 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 <iostream>
  #include <unordered_set>
  
  #include "android-base/stringprintf.h"
  #include "android-base/strings.h"
  
  #include "base/os.h"
  #include "base/unix_file/fd_file.h"
  #include "dex/art_dex_file_loader.h"
  #include "dex/dex_file-inl.h"
  #include "dex/hidden_api_access_flags.h"
  #include "mem_map.h"
  
  namespace art {
  
  static int original_argc;
  static char** original_argv;
  
  static std::string CommandLine() {
    std::vector<std::string> command;
    for (int i = 0; i < original_argc; ++i) {
      command.push_back(original_argv[i]);
    }
    return android::base::Join(command, ' ');
  }
  
  static void UsageErrorV(const char* fmt, va_list ap) {
    std::string error;
    android::base::StringAppendV(&error, fmt, ap);
    LOG(ERROR) << error;
  }
  
  static void UsageError(const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    UsageErrorV(fmt, ap);
    va_end(ap);
  }
  
  NO_RETURN static void Usage(const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    UsageErrorV(fmt, ap);
    va_end(ap);
  
    UsageError("Command: %s", CommandLine().c_str());
    UsageError("Usage: hiddenapi [options]...");
    UsageError("");
    UsageError("  --dex=<filename>: specify dex file whose members' access flags are to be set.");
    UsageError("      At least one --dex parameter must be specified.");
    UsageError("");
    UsageError("  --light-greylist=<filename>:");
    UsageError("  --dark-greylist=<filename>:");
    UsageError("  --blacklist=<filename>: text files with signatures of methods/fields to be marked");
    UsageError("      greylisted/blacklisted respectively. At least one list must be provided.");
    UsageError("");
    UsageError("  --print-hidden-api: dump a list of marked methods/fields to the standard output.");
    UsageError("      There is no indication which API category they belong to.");
    UsageError("");
  
    exit(EXIT_FAILURE);
  }
  
  class DexClass {
   public:
    DexClass(const DexFile& dex_file, uint32_t idx)
        : dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {}
  
    const DexFile& GetDexFile() const { return dex_file_; }
  
    const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; }
  
    const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); }
  
    const char* GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); }
  
   private:
    const DexFile& dex_file_;
    const DexFile::ClassDef& class_def_;
  };
  
  class DexMember {
   public:
    DexMember(const DexClass& klass, const ClassDataItemIterator& it)
        : klass_(klass), it_(it) {
      DCHECK_EQ(it_.IsAtMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_,
                klass_.GetClassIndex());
    }
  
    // Sets hidden bits in access flags and writes them back into the DEX in memory.
    // Note that this will not update the cached data of ClassDataItemIterator
    // until it iterates over this item again and therefore will fail a CHECK if
    // it is called multiple times on the same DexMember.
    void SetHidden(HiddenApiAccessFlags::ApiList value) {
      const uint32_t old_flags = it_.GetRawMemberAccessFlags();
      const uint32_t new_flags = HiddenApiAccessFlags::EncodeForDex(old_flags, value);
      CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags));
  
      // Locate the LEB128-encoded access flags in class data.
      // `ptr` initially points to the next ClassData item. We iterate backwards
      // until we hit the terminating byte of the previous Leb128 value.
      const uint8_t* ptr = it_.DataPointer();
      if (it_.IsAtMethod()) {
        ptr = ReverseSearchUnsignedLeb128(ptr);
        DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), it_.GetMethodCodeItemOffset());
      }
      ptr = ReverseSearchUnsignedLeb128(ptr);
      DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), old_flags);
  
      // Overwrite the access flags.
      UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags);
    }
  
    // Returns true if this member's API entry is in `list`.
    bool IsOnApiList(const std::unordered_set<std::string>& list) const {
      return list.find(GetApiEntry()) != list.end();
    }
  
    // Constructs a string with a unique signature of this class member.
    std::string GetApiEntry() const {
      std::stringstream ss;
      ss << klass_.GetDescriptor() << "->";
      if (it_.IsAtMethod()) {
        const DexFile::MethodId& mid = GetMethodId();
        ss << klass_.GetDexFile().GetMethodName(mid)
           << klass_.GetDexFile().GetMethodSignature(mid).ToString();
      } else {
        const DexFile::FieldId& fid = GetFieldId();
        ss << klass_.GetDexFile().GetFieldName(fid) << ":"
           << klass_.GetDexFile().GetFieldTypeDescriptor(fid);
      }
      return ss.str();
    }
  
   private:
    inline const DexFile::MethodId& GetMethodId() const {
      DCHECK(it_.IsAtMethod());
      return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex());
    }
  
    inline const DexFile::FieldId& GetFieldId() const {
      DCHECK(!it_.IsAtMethod());
      return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex());
    }
  
    const DexClass& klass_;
    const ClassDataItemIterator& it_;
  };
  
  class HiddenApi FINAL {
   public:
    HiddenApi() : print_hidden_api_(false) {}
  
    void ParseArgs(int argc, char** argv) {
      original_argc = argc;
      original_argv = argv;
  
      android::base::InitLogging(argv);
  
      // Skip over the command name.
      argv++;
      argc--;
  
      if (argc == 0) {
        Usage("No arguments specified");
      }
  
      for (int i = 0; i < argc; ++i) {
        const StringPiece option(argv[i]);
        const bool log_options = false;
        if (log_options) {
          LOG(INFO) << "hiddenapi: option[" << i << "]=" << argv[i];
        }
        if (option == "--print-hidden-api") {
          print_hidden_api_ = true;
        } else if (option.starts_with("--dex=")) {
          dex_paths_.push_back(option.substr(strlen("--dex=")).ToString());
        } else if (option.starts_with("--light-greylist=")) {
          light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString();
        } else if (option.starts_with("--dark-greylist=")) {
          dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString();
        } else if (option.starts_with("--blacklist=")) {
          blacklist_path_ = option.substr(strlen("--blacklist=")).ToString();
        } else {
          Usage("Unknown argument '%s'", option.data());
        }
      }
    }
  
    bool ProcessDexFiles() {
      if (dex_paths_.empty()) {
        Usage("No DEX files specified");
      }
  
      if (light_greylist_path_.empty() && dark_greylist_path_.empty() && blacklist_path_.empty()) {
        Usage("No API file specified");
      }
  
      if (!light_greylist_path_.empty() && !OpenApiFile(light_greylist_path_, &light_greylist_)) {
        return false;
      }
  
      if (!dark_greylist_path_.empty() && !OpenApiFile(dark_greylist_path_, &dark_greylist_)) {
        return false;
      }
  
      if (!blacklist_path_.empty() && !OpenApiFile(blacklist_path_, &blacklist_)) {
        return false;
      }
  
      MemMap::Init();
      if (!OpenDexFiles()) {
        return false;
      }
  
      DCHECK(!dex_files_.empty());
      for (auto& dex_file : dex_files_) {
        CategorizeAllClasses(*dex_file.get());
      }
  
      UpdateDexChecksums();
      return true;
    }
  
   private:
    bool OpenApiFile(const std::string& path, std::unordered_set<std::string>* list) {
      DCHECK(list->empty());
      DCHECK(!path.empty());
  
      std::ifstream api_file(path, std::ifstream::in);
      if (api_file.fail()) {
        LOG(ERROR) << "Unable to open file '" << path << "' " << strerror(errno);
        return false;
      }
  
      for (std::string line; std::getline(api_file, line);) {
        list->insert(line);
      }
  
      api_file.close();
      return true;
    }
  
    bool OpenDexFiles() {
      ArtDexFileLoader dex_loader;
      DCHECK(dex_files_.empty());
  
      for (const std::string& filename : dex_paths_) {
        std::string error_msg;
  
        File fd(filename.c_str(), O_RDWR, /* check_usage */ false);
        if (fd.Fd() == -1) {
          LOG(ERROR) << "Unable to open file '" << filename << "': " << strerror(errno);
          return false;
        }
  
        // Memory-map the dex file with MAP_SHARED flag so that changes in memory
        // propagate to the underlying file. We run dex file verification as if
        // the dex file was not in boot claass path to check basic assumptions,
        // such as that at most one of public/private/protected flag is set.
        // We do those checks here and skip them when loading the processed file
        // into boot class path.
        std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(),
                                                                   /* location */ filename,
                                                                   /* verify */ true,
                                                                   /* verify_checksum */ true,
                                                                   /* mmap_shared */ true,
                                                                   &error_msg));
        if (dex_file.get() == nullptr) {
          LOG(ERROR) << "Open failed for '" << filename << "' " << error_msg;
          return false;
        }
  
        if (!dex_file->IsStandardDexFile()) {
          LOG(ERROR) << "Expected a standard dex file '" << filename << "'";
          return false;
        }
  
        // Change the protection of the memory mapping to read-write.
        if (!dex_file->EnableWrite()) {
          LOG(ERROR) << "Failed to enable write permission for '" << filename << "'";
          return false;
        }
  
        dex_files_.push_back(std::move(dex_file));
      }
      return true;
    }
  
    void CategorizeAllClasses(const DexFile& dex_file) {
      for (uint32_t class_idx = 0; class_idx < dex_file.NumClassDefs(); ++class_idx) {
        DexClass klass(dex_file, class_idx);
        const uint8_t* klass_data = klass.GetData();
        if (klass_data == nullptr) {
          continue;
        }
  
        for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) {
          DexMember member(klass, it);
  
          // Catagorize member and overwrite its access flags.
          // Note that if a member appears on multiple API lists, it will be categorized
          // as the strictest.
          bool is_hidden = true;
          if (member.IsOnApiList(blacklist_)) {
            member.SetHidden(HiddenApiAccessFlags::kBlacklist);
          } else if (member.IsOnApiList(dark_greylist_)) {
            member.SetHidden(HiddenApiAccessFlags::kDarkGreylist);
          } else if (member.IsOnApiList(light_greylist_)) {
            member.SetHidden(HiddenApiAccessFlags::kLightGreylist);
          } else {
            member.SetHidden(HiddenApiAccessFlags::kWhitelist);
            is_hidden = false;
          }
  
          if (print_hidden_api_ && is_hidden) {
            std::cout << member.GetApiEntry() << std::endl;
          }
        }
      }
    }
  
    void UpdateDexChecksums() {
      for (auto& dex_file : dex_files_) {
        // Obtain a writeable pointer to the dex header.
        DexFile::Header* header = const_cast<DexFile::Header*>(&dex_file->GetHeader());
        // Recalculate checksum and overwrite the value in the header.
        header->checksum_ = dex_file->CalculateChecksum();
      }
    }
  
    // Print signatures of APIs which have been grey-/blacklisted.
    bool print_hidden_api_;
  
    // Paths to DEX files which should be processed.
    std::vector<std::string> dex_paths_;
  
    // Paths to text files which contain the lists of API members.
    std::string light_greylist_path_;
    std::string dark_greylist_path_;
    std::string blacklist_path_;
  
    // Opened DEX files. Note that these are opened as `const` but eventually will be written into.
    std::vector<std::unique_ptr<const DexFile>> dex_files_;
  
    // Signatures of DEX members loaded from `light_greylist_path_`, `dark_greylist_path_`,
    // `blacklist_path_`.
    std::unordered_set<std::string> light_greylist_;
    std::unordered_set<std::string> dark_greylist_;
    std::unordered_set<std::string> blacklist_;
  };
  
  }  // namespace art
  
  int main(int argc, char** argv) {
    art::HiddenApi hiddenapi;
  
    // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError.
    hiddenapi.ParseArgs(argc, argv);
    return hiddenapi.ProcessDexFiles() ? EXIT_SUCCESS : EXIT_FAILURE;
  }
  