/*
Copyright 2011-2016 Google Inc. All Rights Reserved.

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.
*/
package com.google.security.zynamics.reil.translators.x86;

import com.google.common.base.Preconditions;
import com.google.security.zynamics.reil.OperandSize;
import com.google.security.zynamics.reil.ReilHelpers;
import com.google.security.zynamics.reil.ReilInstruction;
import com.google.security.zynamics.reil.translators.IInstructionTranslator;
import com.google.security.zynamics.reil.translators.ITranslationEnvironment;
import com.google.security.zynamics.reil.translators.InternalTranslationException;
import com.google.security.zynamics.reil.translators.TranslationHelpers;
import com.google.security.zynamics.reil.translators.TranslationResult;
import com.google.security.zynamics.zylib.disassembly.IInstruction;
import com.google.security.zynamics.zylib.disassembly.IOperandTree;
import com.google.security.zynamics.zylib.general.Pair;

import java.util.List;

/**
 * Translates SAR instructions to REIL code.
 */
public class SarTranslator implements IInstructionTranslator {
  /**
   * Translates a SAR instruction to REIL code by first performing an unsigned division and then
   * (possibly) correcting the result afterwards.
   *
   * @param environment A valid translation environment.
   * @param instruction The SAR instruction to translate.
   * @param instructions The generated REIL code will be added to this list
   * @throws InternalTranslationException if any of the arguments are null the passed instruction is
   *         not an SAR instruction
   */
  @Override
  public void translate(final ITranslationEnvironment environment, final IInstruction instruction,
      final List<ReilInstruction> instructions) throws InternalTranslationException {
    TranslationHelpers.checkTranslationArguments(environment, instruction, instructions, "sar");
    Preconditions.checkArgument(instruction.getOperands().size() == 2,
        "Error: Argument instruction is not a sar instruction (invalid number of operands)");

    final long baseOffset = instruction.getAddress().toLong() * 0x100;
    long offset = baseOffset;

    final List<? extends IOperandTree> operands = instruction.getOperands();

    final IOperandTree targetOperand = operands.get(0);
    final IOperandTree sourceOperand = operands.get(1);

    // Load source operand.
    final TranslationResult sourceResult =
        Helpers.translateOperand(environment, offset, sourceOperand, true);
    instructions.addAll(sourceResult.getInstructions());

    // Adjust the offset of the next REIL instruction.
    offset = baseOffset + instructions.size();

    // Load target operand.
    final TranslationResult targetResult =
        Helpers.translateOperand(environment, offset, targetOperand, true);
    instructions.addAll(targetResult.getInstructions());

    // Adjust the offset of the next REIL instruction.
    offset = baseOffset + instructions.size();

    final OperandSize sourceSize = sourceResult.getSize();
    final OperandSize targetSize = targetResult.getSize();
    final OperandSize resultSize = TranslationHelpers.getNextSize(sourceSize);

    final String sourceRegister = sourceResult.getRegister();
    final String targetRegister = targetResult.getRegister();

    final String msbMask = String.valueOf(TranslationHelpers.getMsbMask(sourceSize));

    final String truncateMask = String.valueOf(TranslationHelpers.getAllBitsMask(sourceSize));
    final String modValue = String.valueOf(targetSize.getBitSize());

    final String shiftMask = environment.getNextVariableString();
    final String shiftMaskZero = environment.getNextVariableString();
    final String shiftMaskLessOne = environment.getNextVariableString();
    final String shiftMaskOne = environment.getNextVariableString();
    final String shiftMaskNeg = environment.getNextVariableString();
    final String result = environment.getNextVariableString();
    final String truncatedResult = environment.getNextVariableString();
    final String msbResult = environment.getNextVariableString();
    final String isPositive = environment.getNextVariableString();
    final String divisor = environment.getNextVariableString();
    final String divisionResult = environment.getNextVariableString();
    final String negateMask = environment.getNextVariableString();
    final String twoComplementResult = environment.getNextVariableString();
    final String shiftBit = environment.getNextVariableString();
    final String shiftedBitsMask = environment.getNextVariableString();
    final String shiftedBits = environment.getNextVariableString();
    final String shiftedBitsZero = environment.getNextVariableString();
    final String shiftedBitsNonZero = environment.getNextVariableString();
    final String shiftAmountMinOne = environment.getNextVariableString();
    final String isNegative = environment.getNextVariableString();
    final String roundTowNegInf = environment.getNextVariableString();
    final String cfBitMask = environment.getNextVariableString();
    final String cfBitResult = environment.getNextVariableString();
    final String tmpCf = environment.getNextVariableString();

    // Generate the unsigned value of the shift value
    // Note: this code must not be moved further down, otherwise all the offset become invalid
    final Pair<String, String> targetRegister1Abs =
        Helpers.generateAbs(environment, offset, targetRegister, targetSize, instructions);
    final String targetRegister1Absolute = targetRegister1Abs.second();
    offset = baseOffset + instructions.size();

    // Since the Reil infrastructure lacks ability to establish branches between instructions
    // objects in (as opposed to raw offsets) we need this hack to correct the jump target offsets
    // The reason is that the are already some instructions in the list so, e.g., createMod(offset +
    // 1) does not actually create an instruction at offset 1 but at offset delta + 1
    final int delta = instructions.size();

    // Make sure to shift less than the size of the target register
    instructions.add(ReilHelpers.createMod(offset, sourceSize, sourceRegister, targetSize,
        modValue, targetSize, shiftMask));

    // Find out if the shift mask is zero or non-zero
    instructions.add(ReilHelpers.createBisz(offset + 1, targetSize, shiftMask, OperandSize.BYTE,
        shiftMaskZero));

    // Bail out if shift count is zero
    final String jmpEnd = String.format("%s.%s", instruction.getAddress().toLong(), delta + 39);
    instructions.add(ReilHelpers.createJcc(offset + 2, OperandSize.BYTE, shiftMaskZero,
        OperandSize.ADDRESS, jmpEnd));

    // AF is undefined if shift count is non-zero
    instructions.add(ReilHelpers.createUndef(offset + 3, OperandSize.BYTE, Helpers.AUXILIARY_FLAG));

    // The carry flag corresponds to the last bit shifted out of the operand
    instructions.add(ReilHelpers.createSub(offset + 4, OperandSize.BYTE, shiftMask,
        OperandSize.BYTE, "1", OperandSize.BYTE, shiftAmountMinOne));
    instructions.add(ReilHelpers.createBsh(offset + 5, OperandSize.BYTE, "1", OperandSize.BYTE,
        shiftAmountMinOne, OperandSize.BYTE, cfBitMask));
    instructions.add(ReilHelpers.createAnd(offset + 6, targetSize, targetRegister,
        OperandSize.BYTE, cfBitMask, OperandSize.BYTE, cfBitResult));
    instructions.add(ReilHelpers.createBisz(offset + 7, OperandSize.BYTE, cfBitResult,
        OperandSize.BYTE, tmpCf));
    instructions.add(ReilHelpers.createXor(offset + 8, OperandSize.BYTE, "1", OperandSize.BYTE,
        tmpCf, OperandSize.BYTE, Helpers.CARRY_FLAG));

    // Find out if the shift mask is 1
    instructions.add(ReilHelpers.createSub(offset + 9, targetSize, "1", targetSize, shiftMask,
        targetSize, shiftMaskLessOne));
    instructions.add(ReilHelpers.createBisz(offset + 10, targetSize, shiftMaskLessOne,
        OperandSize.BYTE, shiftMaskOne));

    // Negate the shift-mask so we can perform a right shift
    instructions.add(ReilHelpers.createSub(offset + 11, targetSize, "0", targetSize, shiftMask,
        targetSize, shiftMaskNeg));

    // Test if the highest bit of the input value was set; if so, we need to perform a sign
    // extension on the shift result
    instructions.add(ReilHelpers.createAnd(offset + 12, sourceSize, msbMask, targetSize,
        targetRegister, sourceSize, msbResult));
    instructions.add(ReilHelpers.createBisz(offset + 13, sourceSize, msbResult, OperandSize.BYTE,
        isPositive));
    instructions.add(ReilHelpers.createXor(offset + 14, OperandSize.BYTE, "1", OperandSize.BYTE,
        isPositive, OperandSize.BYTE, isNegative));

    // Create divisor based on shift amount and calculate unsigned division
    instructions.add(ReilHelpers.createBsh(offset + 15, OperandSize.DWORD, "1", OperandSize.BYTE,
        shiftMask, OperandSize.DWORD, divisor));
    instructions.add(ReilHelpers.createDiv(offset + 16, targetSize, targetRegister1Absolute,
        OperandSize.DWORD, divisor, OperandSize.DWORD, divisionResult));

    // If the MSB of the value to be shifted is set, we create a mask of 0xFFFFFFFF to convert the
    // result to two's complement
    instructions.add(ReilHelpers.createSub(offset + 17, OperandSize.BYTE, "0", OperandSize.BYTE,
        isNegative, OperandSize.DWORD, negateMask));
    instructions.add(ReilHelpers.createXor(offset + 18, OperandSize.DWORD, divisionResult,
        OperandSize.DWORD, negateMask, OperandSize.DWORD, result));

    // If the source value is positive we need to skip adding one to the result
    final String jmpSkipTwosComplement =
        String.format("%s.%s", instruction.getAddress().toLong(), delta + 28);
    instructions.add(ReilHelpers.createJcc(offset + 19, OperandSize.BYTE, isPositive,
        OperandSize.ADDRESS, jmpSkipTwosComplement));

    // Convert to two's complement by adding one to the result
    instructions.add(ReilHelpers.createAdd(offset + 20, OperandSize.DWORD, result,
        OperandSize.BYTE, "1", OperandSize.DWORD, twoComplementResult));

    // We need to subtract one to realize "rounding towards infinity" iff the bits shifted out are
    // not all zeros
    // First we need to mask out all the bits which are shifted out on the right side
    instructions.add(ReilHelpers.createBsh(offset + 21, OperandSize.BYTE, "1", targetSize,
        shiftMask, targetSize, shiftBit));
    instructions.add(ReilHelpers.createSub(offset + 22, targetSize, shiftBit, OperandSize.BYTE,
        "1", targetSize, shiftedBitsMask));
    instructions.add(ReilHelpers.createAnd(offset + 23, targetSize, targetRegister, targetSize,
        shiftedBitsMask, targetSize, shiftedBits));

    // Possibly subtract one from the result, i.e. round towards negative infinity
    instructions.add(ReilHelpers.createBisz(offset + 24, targetSize, shiftedBits, OperandSize.BYTE,
        shiftedBitsZero));
    instructions.add(ReilHelpers.createXor(offset + 25, OperandSize.BYTE, "1", OperandSize.BYTE,
        shiftedBitsZero, OperandSize.BYTE, shiftedBitsNonZero));
    instructions.add(ReilHelpers.createAnd(offset + 26, OperandSize.BYTE, isNegative,
        OperandSize.BYTE, shiftedBitsNonZero, OperandSize.BYTE, roundTowNegInf));
    instructions.add(ReilHelpers.createSub(offset + 27, OperandSize.DWORD, twoComplementResult,
        OperandSize.BYTE, roundTowNegInf, targetSize, result));

    // Truncate the result to the correct size (skip two complement conversion jump target)
    instructions.add(ReilHelpers.createAnd(offset + 28, resultSize, result, sourceSize,
        truncateMask, sourceSize, truncatedResult));

    // Don't change the flags if the shift value was zero
    final String jmpGoal = String.format("%s.%s", instruction.getAddress().toLong(), delta + 39);
    instructions.add(ReilHelpers.createJcc(offset + 29, OperandSize.BYTE, shiftMaskZero,
        OperandSize.ADDRESS, jmpGoal));

    // The SF is always 0
    instructions.add(ReilHelpers.createBisz(offset + 30, OperandSize.BYTE, isPositive,
        OperandSize.BYTE, Helpers.SIGN_FLAG));

    // Set the ZF
    instructions.add(ReilHelpers.createBisz(offset + 31, sourceSize, truncatedResult,
        OperandSize.BYTE, Helpers.ZERO_FLAG));

    // If shift count is one, we need to zero the OF
    final String jmpDontZeroOF =
        String.format("%s.%s", instruction.getAddress().toLong(), delta + 34);
    instructions.add(ReilHelpers.createJcc(offset + 32, OperandSize.BYTE, shiftMaskOne,
        OperandSize.ADDRESS, jmpDontZeroOF));
    instructions.add(ReilHelpers.createStr(offset + 33, OperandSize.BYTE, "0", OperandSize.BYTE,
        Helpers.OVERFLOW_FLAG));

    // Set the OF to undefined if the shift-mask was not zero and not one
    final String shiftCountZeroOrOne = environment.getNextVariableString();
    instructions.add(ReilHelpers.createOr(offset + 34, OperandSize.BYTE, shiftMaskOne,
        OperandSize.BYTE, shiftMaskZero, OperandSize.BYTE, shiftCountZeroOrOne));
    final String jmpSkipUndefOF =
        String.format("%s.%s", instruction.getAddress().toLong(), delta + 38);
    instructions.add(ReilHelpers.createJcc(offset + 35, OperandSize.BYTE, shiftCountZeroOrOne,
        OperandSize.ADDRESS, jmpSkipUndefOF));
    instructions.add(ReilHelpers.createUndef(offset + 36, OperandSize.BYTE, Helpers.OVERFLOW_FLAG));
    // always jump to writeback instruction if OF was undefined
    final String jmpGoal3 = String.format("%s.%s", instruction.getAddress().toLong(), delta + 39);
    instructions.add(ReilHelpers.createJcc(offset + 37, OperandSize.BYTE, "1", OperandSize.ADDRESS,
        jmpGoal3));

    // OF is always zero for the SAR instruction
    instructions.add(ReilHelpers.createStr(offset + 38, OperandSize.BYTE, "0", OperandSize.BYTE,
        Helpers.OVERFLOW_FLAG));

    final int sizeBefore = instructions.size();
    Helpers.writeBack(environment, offset + 39, targetOperand, result, targetSize,
        targetResult.getAddress(), targetResult.getType(), instructions);
    final int sizeAfter = instructions.size();

    instructions.add(ReilHelpers.createNop((sizeAfter - sizeBefore - 1) + offset + 40));
  }
}
