/*
 * Copyright (C) 2016 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 "dex_to_dex_decompiler.h"

#include <android-base/logging.h>

#include "base/macros.h"
#include "base/mutex.h"
#include "dex/bytecode_utils.h"
#include "dex/code_item_accessors-inl.h"
#include "dex/dex_file-inl.h"
#include "dex/dex_instruction-inl.h"
#include "quicken_info.h"

namespace art {
namespace optimizer {

class DexDecompiler {
 public:
  DexDecompiler(const DexFile& dex_file,
                const dex::CodeItem& code_item,
                const ArrayRef<const uint8_t>& quickened_info,
                bool decompile_return_instruction)
    : code_item_accessor_(dex_file, &code_item),
      quicken_info_(quickened_info),
      decompile_return_instruction_(decompile_return_instruction) {}

  bool Decompile();

 private:
  void DecompileInstanceFieldAccess(Instruction* inst, Instruction::Code new_opcode) {
    uint16_t index = NextIndex();
    inst->SetOpcode(new_opcode);
    inst->SetVRegC_22c(index);
  }

  void DecompileInvokeVirtual(Instruction* inst, Instruction::Code new_opcode, bool is_range) {
    const uint16_t index = NextIndex();
    inst->SetOpcode(new_opcode);
    if (is_range) {
      inst->SetVRegB_3rc(index);
    } else {
      inst->SetVRegB_35c(index);
    }
  }

  void DecompileNop(Instruction* inst) {
    const uint16_t reference_index = NextIndex();
    if (reference_index == DexFile::kDexNoIndex16) {
      // This means it was a normal nop and not a check-cast.
      return;
    }
    const uint16_t type_index = NextIndex();
    inst->SetOpcode(Instruction::CHECK_CAST);
    inst->SetVRegA_21c(reference_index);
    inst->SetVRegB_21c(type_index);
  }

  uint16_t NextIndex() {
    DCHECK_LT(quicken_index_, quicken_info_.NumIndices());
    const uint16_t ret = quicken_info_.GetData(quicken_index_);
    quicken_index_++;
    return ret;
  }

  const CodeItemInstructionAccessor code_item_accessor_;
  const QuickenInfoTable quicken_info_;
  const bool decompile_return_instruction_;

  size_t quicken_index_ = 0u;

  DISALLOW_COPY_AND_ASSIGN(DexDecompiler);
};

bool DexDecompiler::Decompile() {
  // We need to iterate over the code item, and not over the quickening data,
  // because the RETURN_VOID quickening is not encoded in the quickening data. Because
  // unquickening is a rare need and not performance sensitive, it is not worth the
  // added storage to also add the RETURN_VOID quickening in the quickened data.
  for (const DexInstructionPcPair& pair : code_item_accessor_) {
    Instruction* inst = const_cast<Instruction*>(&pair.Inst());

    switch (inst->Opcode()) {
      case Instruction::RETURN_VOID_NO_BARRIER:
        if (decompile_return_instruction_) {
          inst->SetOpcode(Instruction::RETURN_VOID);
        }
        break;

      case Instruction::NOP:
        if (quicken_info_.NumIndices() > 0) {
          // Only try to decompile NOP if there are more than 0 indices. Not having
          // any index happens when we unquicken a code item that only has
          // RETURN_VOID_NO_BARRIER as quickened instruction.
          DecompileNop(inst);
        }
        break;

      case Instruction::IGET_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET);
        break;

      case Instruction::IGET_WIDE_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET_WIDE);
        break;

      case Instruction::IGET_OBJECT_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET_OBJECT);
        break;

      case Instruction::IGET_BOOLEAN_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET_BOOLEAN);
        break;

      case Instruction::IGET_BYTE_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET_BYTE);
        break;

      case Instruction::IGET_CHAR_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET_CHAR);
        break;

      case Instruction::IGET_SHORT_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IGET_SHORT);
        break;

      case Instruction::IPUT_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT);
        break;

      case Instruction::IPUT_BOOLEAN_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT_BOOLEAN);
        break;

      case Instruction::IPUT_BYTE_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT_BYTE);
        break;

      case Instruction::IPUT_CHAR_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT_CHAR);
        break;

      case Instruction::IPUT_SHORT_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT_SHORT);
        break;

      case Instruction::IPUT_WIDE_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT_WIDE);
        break;

      case Instruction::IPUT_OBJECT_QUICK:
        DecompileInstanceFieldAccess(inst, Instruction::IPUT_OBJECT);
        break;

      case Instruction::INVOKE_VIRTUAL_QUICK:
        DecompileInvokeVirtual(inst, Instruction::INVOKE_VIRTUAL, false);
        break;

      case Instruction::INVOKE_VIRTUAL_RANGE_QUICK:
        DecompileInvokeVirtual(inst, Instruction::INVOKE_VIRTUAL_RANGE, true);
        break;

      default:
        break;
    }
  }

  if (quicken_index_ != quicken_info_.NumIndices()) {
    if (quicken_index_ == 0) {
      LOG(WARNING) << "Failed to use any value in quickening info,"
                   << " potentially due to duplicate methods.";
    } else {
      LOG(FATAL) << "Failed to use all values in quickening info."
                 << " Actual: " << std::hex << quicken_index_
                 << " Expected: " << quicken_info_.NumIndices();
    }
  }

  return true;
}

bool ArtDecompileDEX(const DexFile& dex_file,
                     const dex::CodeItem& code_item,
                     const ArrayRef<const uint8_t>& quickened_info,
                     bool decompile_return_instruction) {
  if (quickened_info.size() == 0 && !decompile_return_instruction) {
    return true;
  }
  DexDecompiler decompiler(dex_file, code_item, quickened_info, decompile_return_instruction);
  return decompiler.Decompile();
}

}  // namespace optimizer
}  // namespace art
