/*
 * Copyright (C) 2018 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.
 */

#ifndef ART_LIBDEXFILE_DEX_HIDDEN_API_ACCESS_FLAGS_H_
#define ART_LIBDEXFILE_DEX_HIDDEN_API_ACCESS_FLAGS_H_

#include "base/bit_utils.h"
#include "base/macros.h"
#include "dex/modifiers.h"

namespace art {

/* This class is used for encoding and decoding access flags of class members
 * from the boot class path. These access flags might contain additional two bits
 * of information on whether the given class member should be hidden from apps
 * and under what circumstances.
 *
 * The encoding is different inside DexFile, where we are concerned with size,
 * and at runtime where we want to optimize for speed of access. The class
 * provides helper functions to decode/encode both of them.
 *
 * Encoding in DexFile
 * ===================
 *
 * First bit is encoded as inversion of visibility flags (public/private/protected).
 * At most one can be set for any given class member. If two or three are set,
 * this is interpreted as the first bit being set and actual visibility flags
 * being the complement of the encoded flags.
 *
 * Second bit is either encoded as bit 5 for fields and non-native methods, where
 * it carries no other meaning. If a method is native (bit 8 set), bit 9 is used.
 *
 * Bits were selected so that they never increase the length of unsigned LEB-128
 * encoding of the access flags.
 *
 * Encoding at runtime
 * ===================
 *
 * Two bits are set aside in the uint32_t access flags in the intrinsics ordinal
 * space (thus intrinsics need to be special-cased). These are two consecutive
 * bits and they are directly used to store the integer value of the ApiList
 * enum values.
 *
 */
class HiddenApiAccessFlags {
 public:
  enum ApiList {
    kWhitelist = 0,
    kLightGreylist,
    kDarkGreylist,
    kBlacklist,
  };

  static ALWAYS_INLINE ApiList DecodeFromDex(uint32_t dex_access_flags) {
    DexHiddenAccessFlags flags(dex_access_flags);
    uint32_t int_value = (flags.IsFirstBitSet() ? 1 : 0) + (flags.IsSecondBitSet() ? 2 : 0);
    return static_cast<ApiList>(int_value);
  }

  static ALWAYS_INLINE uint32_t RemoveFromDex(uint32_t dex_access_flags) {
    DexHiddenAccessFlags flags(dex_access_flags);
    flags.SetFirstBit(false);
    flags.SetSecondBit(false);
    return flags.GetEncoding();
  }

  static ALWAYS_INLINE uint32_t EncodeForDex(uint32_t dex_access_flags, ApiList value) {
    DexHiddenAccessFlags flags(RemoveFromDex(dex_access_flags));
    uint32_t int_value = static_cast<uint32_t>(value);
    flags.SetFirstBit((int_value & 1) != 0);
    flags.SetSecondBit((int_value & 2) != 0);
    return flags.GetEncoding();
  }

  static ALWAYS_INLINE ApiList DecodeFromRuntime(uint32_t runtime_access_flags) {
    // This is used in the fast path, only DCHECK here.
    DCHECK_EQ(runtime_access_flags & kAccIntrinsic, 0u);
    uint32_t int_value = (runtime_access_flags & kAccHiddenApiBits) >> kAccFlagsShift;
    return static_cast<ApiList>(int_value);
  }

  static ALWAYS_INLINE uint32_t EncodeForRuntime(uint32_t runtime_access_flags, ApiList value) {
    CHECK_EQ(runtime_access_flags & kAccIntrinsic, 0u);

    uint32_t hidden_api_flags = static_cast<uint32_t>(value) << kAccFlagsShift;
    CHECK_EQ(hidden_api_flags & ~kAccHiddenApiBits, 0u);

    runtime_access_flags &= ~kAccHiddenApiBits;
    return runtime_access_flags | hidden_api_flags;
  }

 private:
  static const int kAccFlagsShift = CTZ(kAccHiddenApiBits);
  static_assert(IsPowerOfTwo((kAccHiddenApiBits >> kAccFlagsShift) + 1),
                "kAccHiddenApiBits are not continuous");

  struct DexHiddenAccessFlags {
    explicit DexHiddenAccessFlags(uint32_t access_flags) : access_flags_(access_flags) {}

    ALWAYS_INLINE uint32_t GetSecondFlag() {
      return ((access_flags_ & kAccNative) != 0) ? kAccDexHiddenBitNative : kAccDexHiddenBit;
    }

    ALWAYS_INLINE bool IsFirstBitSet() {
      static_assert(IsPowerOfTwo(0u), "Following statement checks if *at most* one bit is set");
      return !IsPowerOfTwo(access_flags_ & kAccVisibilityFlags);
    }

    ALWAYS_INLINE void SetFirstBit(bool value) {
      if (IsFirstBitSet() != value) {
        access_flags_ ^= kAccVisibilityFlags;
      }
    }

    ALWAYS_INLINE bool IsSecondBitSet() {
      return (access_flags_ & GetSecondFlag()) != 0;
    }

    ALWAYS_INLINE void SetSecondBit(bool value) {
      if (value) {
        access_flags_ |= GetSecondFlag();
      } else {
        access_flags_ &= ~GetSecondFlag();
      }
    }

    ALWAYS_INLINE uint32_t GetEncoding() const {
      return access_flags_;
    }

    uint32_t access_flags_;
  };
};

inline std::ostream& operator<<(std::ostream& os, HiddenApiAccessFlags::ApiList value) {
  switch (value) {
    case HiddenApiAccessFlags::kWhitelist:
      os << "whitelist";
      break;
    case HiddenApiAccessFlags::kLightGreylist:
      os << "light greylist";
      break;
    case HiddenApiAccessFlags::kDarkGreylist:
      os << "dark greylist";
      break;
    case HiddenApiAccessFlags::kBlacklist:
      os << "blacklist";
      break;
  }
  return os;
}

}  // namespace art


#endif  // ART_LIBDEXFILE_DEX_HIDDEN_API_ACCESS_FLAGS_H_
