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

#include <android-base/logging.h>

#include "base/mutex-inl.h"
#include "base/stl_util.h"
#include "common_throws.h"
#include "dex/descriptors_names.h"
#include "gc/accounting/card_table-inl.h"
#include "gc/heap.h"
#include "gc_root-inl.h"
#include "intern_table.h"
#include "mirror/class-inl.h"
#include "mirror/dex_cache-inl.h"
#include "mirror/object-inl.h"
#include "mirror/object_array-inl.h"
#include "oat/aot_class_linker.h"
#include "obj_ptr-inl.h"
#include "runtime.h"

#include <list>

namespace art HIDDEN {

// TODO: remove (only used for debugging purpose).
static constexpr bool kEnableTransactionStats = false;

Transaction::Transaction(bool strict,
                         mirror::Class* root,
                         ArenaStack* arena_stack,
                         ArenaPool* arena_pool)
    : arena_stack_(std::nullopt),
      allocator_(arena_stack != nullptr ? arena_stack : &arena_stack_.emplace(arena_pool)),
      object_logs_(std::less<mirror::Object*>(), allocator_.Adapter(kArenaAllocTransaction)),
      array_logs_(std::less<mirror::Array*>(), allocator_.Adapter(kArenaAllocTransaction)),
      intern_string_logs_(allocator_.Adapter(kArenaAllocTransaction)),
      resolve_string_logs_(allocator_.Adapter(kArenaAllocTransaction)),
      resolve_method_type_logs_(allocator_.Adapter(kArenaAllocTransaction)),
      aborted_(false),
      rolling_back_(false),
      heap_(Runtime::Current()->GetHeap()),
      strict_(strict),
      root_(root),
      last_allocated_object_(nullptr),
      assert_no_new_records_reason_(nullptr) {
  DCHECK(Runtime::Current()->IsAotCompiler());
  DCHECK_NE(arena_stack != nullptr, arena_pool != nullptr);
}

Transaction::~Transaction() {
  if (kEnableTransactionStats) {
    size_t objects_count = object_logs_.size();
    size_t field_values_count = 0;
    for (const auto& it : object_logs_) {
      field_values_count += it.second.Size();
    }
    size_t array_count = array_logs_.size();
    size_t array_values_count = 0;
    for (const auto& it : array_logs_) {
      array_values_count += it.second.Size();
    }
    size_t intern_string_count =
        std::distance(intern_string_logs_.begin(), intern_string_logs_.end());
    size_t resolve_string_count =
        std::distance(resolve_string_logs_.begin(), resolve_string_logs_.end());
    size_t resolve_method_type_count =
        std::distance(resolve_method_type_logs_.begin(), resolve_method_type_logs_.end());
    LOG(INFO) << "Transaction::~Transaction"
              << ": objects_count=" << objects_count
              << ", field_values_count=" << field_values_count
              << ", array_count=" << array_count
              << ", array_values_count=" << array_values_count
              << ", intern_string_count=" << intern_string_count
              << ", resolve_string_count=" << resolve_string_count
              << ", resolve_method_type_count=" << resolve_method_type_count;
  }
}

void Transaction::Abort(const std::string& abort_message) {
  // We may abort more than once if the exception thrown at the time of the
  // previous abort has been caught during execution of a class initializer.
  // We just keep the message of the first abort because it will cause the
  // transaction to be rolled back anyway.
  if (!aborted_) {
    aborted_ = true;
    abort_message_ = abort_message;
  }
}

void Transaction::ThrowAbortError(Thread* self, const std::string* abort_message) {
  const bool rethrow = (abort_message == nullptr);
  if (kIsDebugBuild && rethrow) {
    CHECK(IsAborted()) << "Rethrow " << DescriptorToDot(kTransactionAbortErrorDescriptor)
                       << " while transaction is not aborted";
  }
  if (rethrow) {
    // Rethrow an exception with the earlier abort message stored in the transaction.
    self->ThrowNewWrappedException(kTransactionAbortErrorDescriptor, GetAbortMessage().c_str());
  } else {
    // Throw an exception with the given abort message.
    self->ThrowNewWrappedException(kTransactionAbortErrorDescriptor, abort_message->c_str());
  }
}

const std::string& Transaction::GetAbortMessage() const {
  return abort_message_;
}

bool Transaction::WriteConstraint(ObjPtr<mirror::Object> obj) const {
  DCHECK(obj != nullptr);

  // Prevent changes in boot image spaces for app or boot image extension.
  // For boot image there are no boot image spaces and this condition evaluates to false.
  if (heap_->ObjectIsInBootImageSpace(obj)) {
    return true;
  }

  // For apps, also prevent writing to other classes.
  return IsStrict() &&
         obj->IsClass() &&  // no constraint updating instances or arrays
         obj != root_;  // modifying other classes' static field, fail
}

bool Transaction::WriteValueConstraint(ObjPtr<mirror::Object> value) const {
  if (value == nullptr) {
    return false;  // We can always store null values.
  }
  if (IsStrict()) {
    // TODO: Should we restrict writes the same way as for boot image extension?
    return false;
  } else if (heap_->GetBootImageSpaces().empty()) {
    return false;  // No constraints for boot image.
  } else {
    // Boot image extension.
    ObjPtr<mirror::Class> klass = value->IsClass() ? value->AsClass() : value->GetClass();
    return !AotClassLinker::CanReferenceInBootImageExtensionOrAppImage(klass, heap_);
  }
}

bool Transaction::ReadConstraint(ObjPtr<mirror::Object> obj) const {
  // Read constraints are checked only for static field reads as there are
  // no constraints on reading instance fields and array elements.
  DCHECK(obj->IsClass());
  if (IsStrict()) {
    return obj != root_;  // fail if not self-updating
  } else {
    // For boot image and boot image extension, allow reading any field.
    return false;
  }
}

void Transaction::RecordNewObject(ObjPtr<mirror::Object> obj) {
  last_allocated_object_ = obj.Ptr();
  ObjectLog log(&allocator_);
  log.MarkAsNewObject();
  object_logs_.Put(obj.Ptr(), std::move(log));
}

void Transaction::RecordNewArray(ObjPtr<mirror::Array> array) {
  if (array->IsObjectArray()) {
    // `ObjectArray<T>::SetWithoutChecks()` uses `SetFieldObject()` which records value
    // changes in `object_log_`, so we need to record new object arrays as normal objects.
    RecordNewObject(array);
    return;
  }
  last_allocated_object_ = array.Ptr();
  ArrayLog log(&allocator_);
  log.MarkAsNewArray();
  array_logs_.Put(array.Ptr(), std::move(log));
}

bool Transaction::ObjectNeedsTransactionRecords(ObjPtr<mirror::Object> obj) {
  if (obj == last_allocated_object_) {
    return false;
  }
  auto it = object_logs_.find(obj.Ptr());
  return it == object_logs_.end() || !it->second.IsNewObject();
}

bool Transaction::ArrayNeedsTransactionRecords(ObjPtr<mirror::Array> array) {
  if (array == last_allocated_object_) {
    return false;
  }
  auto it = array_logs_.find(array.Ptr());
  return it == array_logs_.end() || !it->second.IsNewArray();
}

inline Transaction::ObjectLog& Transaction::GetOrCreateObjectLog(mirror::Object* obj) {
  return object_logs_.GetOrCreate(obj, [&]() { return ObjectLog(&allocator_); });
}

void Transaction::RecordWriteFieldBoolean(mirror::Object* obj,
                                          MemberOffset field_offset,
                                          uint8_t value,
                                          bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.LogBooleanValue(field_offset, value, is_volatile);
  }
}

void Transaction::RecordWriteFieldByte(mirror::Object* obj,
                                       MemberOffset field_offset,
                                       int8_t value,
                                       bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.LogByteValue(field_offset, value, is_volatile);
  }
}

void Transaction::RecordWriteFieldChar(mirror::Object* obj,
                                       MemberOffset field_offset,
                                       uint16_t value,
                                       bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.LogCharValue(field_offset, value, is_volatile);
  }
}


void Transaction::RecordWriteFieldShort(mirror::Object* obj,
                                        MemberOffset field_offset,
                                        int16_t value,
                                        bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.LogShortValue(field_offset, value, is_volatile);
  }
}


void Transaction::RecordWriteField32(mirror::Object* obj,
                                     MemberOffset field_offset,
                                     uint32_t value,
                                     bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.Log32BitsValue(field_offset, value, is_volatile);
  }
}

void Transaction::RecordWriteField64(mirror::Object* obj,
                                     MemberOffset field_offset,
                                     uint64_t value,
                                     bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.Log64BitsValue(field_offset, value, is_volatile);
  }
}

void Transaction::RecordWriteFieldReference(mirror::Object* obj,
                                            MemberOffset field_offset,
                                            mirror::Object* value,
                                            bool is_volatile) {
  DCHECK(obj != nullptr);
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (obj != last_allocated_object_) {
    ObjectLog& object_log = GetOrCreateObjectLog(obj);
    object_log.LogReferenceValue(field_offset, value, is_volatile);
  }
}

void Transaction::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) {
  DCHECK(array != nullptr);
  DCHECK(array->IsArrayInstance());
  DCHECK(!array->IsObjectArray());
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  if (array != last_allocated_object_) {
    ArrayLog& array_log = array_logs_.GetOrCreate(array, [&]() { return ArrayLog(&allocator_); });
    array_log.LogValue(index, value);
  }
}

void Transaction::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
                                      dex::StringIndex string_idx) {
  DCHECK(dex_cache != nullptr);
  DCHECK_LT(string_idx.index_, dex_cache->GetDexFile()->NumStringIds());
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  resolve_string_logs_.emplace_front(dex_cache, string_idx);
}

void Transaction::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
                                          dex::ProtoIndex proto_idx) {
  DCHECK(dex_cache != nullptr);
  DCHECK_LT(proto_idx.index_, dex_cache->GetDexFile()->NumProtoIds());
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  resolve_method_type_logs_.emplace_front(dex_cache, proto_idx);
}

void Transaction::RecordStrongStringInsertion(ObjPtr<mirror::String> s) {
  InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kInsert);
  LogInternedString(std::move(log));
}

void Transaction::RecordWeakStringInsertion(ObjPtr<mirror::String> s) {
  InternStringLog log(s, InternStringLog::kWeakString, InternStringLog::kInsert);
  LogInternedString(std::move(log));
}

void Transaction::RecordStrongStringRemoval(ObjPtr<mirror::String> s) {
  InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kRemove);
  LogInternedString(std::move(log));
}

void Transaction::RecordWeakStringRemoval(ObjPtr<mirror::String> s) {
  InternStringLog log(s, InternStringLog::kWeakString, InternStringLog::kRemove);
  LogInternedString(std::move(log));
}

void Transaction::LogInternedString(InternStringLog&& log) {
  Locks::intern_table_lock_->AssertExclusiveHeld(Thread::Current());
  DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
  intern_string_logs_.push_front(std::move(log));
}

void Transaction::Rollback() {
  Thread* self = Thread::Current();
  self->AssertNoPendingException();
  MutexLock mu(self, *Locks::intern_table_lock_);
  rolling_back_ = true;
  CHECK(!Runtime::Current()->IsActiveTransaction());
  UndoObjectModifications();
  UndoArrayModifications();
  UndoInternStringTableModifications();
  UndoResolveStringModifications();
  UndoResolveMethodTypeModifications();
  rolling_back_ = false;
}

void Transaction::UndoObjectModifications() {
  // TODO we may not need to restore objects allocated during this transaction. Or we could directly
  // remove them from the heap.
  for (const auto& it : object_logs_) {
    it.second.Undo(it.first);
  }
  object_logs_.clear();
}

void Transaction::UndoArrayModifications() {
  // TODO we may not need to restore array allocated during this transaction. Or we could directly
  // remove them from the heap.
  for (const auto& it : array_logs_) {
    it.second.Undo(it.first);
  }
  array_logs_.clear();
}

void Transaction::UndoInternStringTableModifications() {
  InternTable* const intern_table = Runtime::Current()->GetInternTable();
  // We want to undo each operation from the most recent to the oldest. List has been filled so the
  // most recent operation is at list begin so just have to iterate over it.
  for (const InternStringLog& string_log : intern_string_logs_) {
    string_log.Undo(intern_table);
  }
  intern_string_logs_.clear();
}

void Transaction::UndoResolveStringModifications() {
  for (ResolveStringLog& string_log : resolve_string_logs_) {
    string_log.Undo();
  }
  resolve_string_logs_.clear();
}

void Transaction::UndoResolveMethodTypeModifications() {
  for (ResolveMethodTypeLog& method_type_log : resolve_method_type_logs_) {
    method_type_log.Undo();
  }
  resolve_method_type_logs_.clear();
}

void Transaction::VisitRoots(RootVisitor* visitor) {
  // Transactions are used for single-threaded initialization.
  // This is the only function that should be called from a different thread,
  // namely the GC thread, and it is called with the mutator lock held exclusively,
  // so the data structures in the `Transaction` are protected from concurrent use.
  DCHECK(Locks::mutator_lock_->IsExclusiveHeld(Thread::Current()));

  visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&root_), RootInfo(kRootUnknown));
  visitor->VisitRoot(&last_allocated_object_, RootInfo(kRootUnknown));
  {
    // Create a separate `ArenaStack` for this thread.
    ArenaStack arena_stack(Runtime::Current()->GetArenaPool());
    VisitObjectLogs(visitor, &arena_stack);
    VisitArrayLogs(visitor, &arena_stack);
  }
  VisitInternStringLogs(visitor);
  VisitResolveStringLogs(visitor);
  VisitResolveMethodTypeLogs(visitor);
}

template <typename MovingRoots, typename Container>
void UpdateKeys(const MovingRoots& moving_roots, Container& container) {
  for (const auto& pair : moving_roots) {
    auto* old_root = pair.first;
    auto* new_root = pair.second;
    auto node = container.extract(old_root);
    CHECK(!node.empty());
    node.key() = new_root;
    bool inserted = container.insert(std::move(node)).inserted;
    CHECK(inserted);
  }
}

void Transaction::VisitObjectLogs(RootVisitor* visitor, ArenaStack* arena_stack) {
  // List of moving roots.
  ScopedArenaAllocator allocator(arena_stack);
  using ObjectPair = std::pair<mirror::Object*, mirror::Object*>;
  ScopedArenaForwardList<ObjectPair> moving_roots(allocator.Adapter(kArenaAllocTransaction));

  // Visit roots.
  for (auto& it : object_logs_) {
    it.second.VisitRoots(visitor);
    mirror::Object* old_root = it.first;
    mirror::Object* new_root = old_root;
    visitor->VisitRoot(&new_root, RootInfo(kRootUnknown));
    if (new_root != old_root) {
      moving_roots.push_front(std::make_pair(old_root, new_root));
    }
  }

  // Update object logs with moving roots.
  UpdateKeys(moving_roots, object_logs_);
}

void Transaction::VisitArrayLogs(RootVisitor* visitor, ArenaStack* arena_stack) {
  // List of moving roots.
  ScopedArenaAllocator allocator(arena_stack);
  using ArrayPair = std::pair<mirror::Array*, mirror::Array*>;
  ScopedArenaForwardList<ArrayPair> moving_roots(allocator.Adapter(kArenaAllocTransaction));

  for (auto& it : array_logs_) {
    mirror::Array* old_root = it.first;
    mirror::Array* new_root = old_root;
    visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&new_root), RootInfo(kRootUnknown));
    if (new_root != old_root) {
      moving_roots.push_front(std::make_pair(old_root, new_root));
    }
  }

  // Update array logs with moving roots.
  UpdateKeys(moving_roots, array_logs_);
}

void Transaction::VisitInternStringLogs(RootVisitor* visitor) {
  for (InternStringLog& log : intern_string_logs_) {
    log.VisitRoots(visitor);
  }
}

void Transaction::VisitResolveStringLogs(RootVisitor* visitor) {
  for (ResolveStringLog& log : resolve_string_logs_) {
    log.VisitRoots(visitor);
  }
}

void Transaction::VisitResolveMethodTypeLogs(RootVisitor* visitor) {
  for (ResolveMethodTypeLog& log : resolve_method_type_logs_) {
    log.VisitRoots(visitor);
  }
}

void Transaction::ObjectLog::LogBooleanValue(MemberOffset offset, uint8_t value, bool is_volatile) {
  LogValue(ObjectLog::kBoolean, offset, value, is_volatile);
}

void Transaction::ObjectLog::LogByteValue(MemberOffset offset, int8_t value, bool is_volatile) {
  LogValue(ObjectLog::kByte, offset, value, is_volatile);
}

void Transaction::ObjectLog::LogCharValue(MemberOffset offset, uint16_t value, bool is_volatile) {
  LogValue(ObjectLog::kChar, offset, value, is_volatile);
}

void Transaction::ObjectLog::LogShortValue(MemberOffset offset, int16_t value, bool is_volatile) {
  LogValue(ObjectLog::kShort, offset, value, is_volatile);
}

void Transaction::ObjectLog::Log32BitsValue(MemberOffset offset, uint32_t value, bool is_volatile) {
  LogValue(ObjectLog::k32Bits, offset, value, is_volatile);
}

void Transaction::ObjectLog::Log64BitsValue(MemberOffset offset, uint64_t value, bool is_volatile) {
  LogValue(ObjectLog::k64Bits, offset, value, is_volatile);
}

void Transaction::ObjectLog::LogReferenceValue(MemberOffset offset,
                                               mirror::Object* obj,
                                               bool is_volatile) {
  LogValue(ObjectLog::kReference, offset, reinterpret_cast<uintptr_t>(obj), is_volatile);
}

void Transaction::ObjectLog::LogValue(ObjectLog::FieldValueKind kind,
                                      MemberOffset offset,
                                      uint64_t value,
                                      bool is_volatile) {
  if (is_new_object_) {
    return;
  }
  auto it = field_values_.find(offset.Uint32Value());
  if (it == field_values_.end()) {
    ObjectLog::FieldValue field_value;
    field_value.value = value;
    field_value.is_volatile = is_volatile;
    field_value.kind = kind;
    field_values_.emplace(offset.Uint32Value(), std::move(field_value));
  }
}

void Transaction::ObjectLog::Undo(mirror::Object* obj) const {
  for (auto& it : field_values_) {
    // Garbage collector needs to access object's class and array's length. So we don't rollback
    // these values.
    MemberOffset field_offset(it.first);
    if (field_offset.Uint32Value() == mirror::Class::ClassOffset().Uint32Value()) {
      // Skip Object::class field.
      continue;
    }
    if (obj->IsArrayInstance() &&
        field_offset.Uint32Value() == mirror::Array::LengthOffset().Uint32Value()) {
      // Skip Array::length field.
      continue;
    }
    const FieldValue& field_value = it.second;
    UndoFieldWrite(obj, field_offset, field_value);
  }
}

void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj,
                                            MemberOffset field_offset,
                                            const FieldValue& field_value) const {
  // TODO We may want to abort a transaction while still being in transaction mode. In this case,
  // we'd need to disable the check.
  constexpr bool kCheckTransaction = false;
  switch (field_value.kind) {
    case kBoolean:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetFieldBooleanVolatile<false, kCheckTransaction>(
            field_offset,
            field_value.value);
      } else {
        obj->SetFieldBoolean<false, kCheckTransaction>(
            field_offset,
            field_value.value);
      }
      break;
    case kByte:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetFieldByteVolatile<false, kCheckTransaction>(
            field_offset,
            static_cast<int8_t>(field_value.value));
      } else {
        obj->SetFieldByte<false, kCheckTransaction>(
            field_offset,
            static_cast<int8_t>(field_value.value));
      }
      break;
    case kChar:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetFieldCharVolatile<false, kCheckTransaction>(
            field_offset,
            static_cast<uint16_t>(field_value.value));
      } else {
        obj->SetFieldChar<false, kCheckTransaction>(
            field_offset,
            static_cast<uint16_t>(field_value.value));
      }
      break;
    case kShort:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetFieldShortVolatile<false, kCheckTransaction>(
            field_offset,
            static_cast<int16_t>(field_value.value));
      } else {
        obj->SetFieldShort<false, kCheckTransaction>(
            field_offset,
            static_cast<int16_t>(field_value.value));
      }
      break;
    case k32Bits:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetField32Volatile<false, kCheckTransaction>(
            field_offset,
            static_cast<uint32_t>(field_value.value));
      } else {
        obj->SetField32<false, kCheckTransaction>(
            field_offset,
            static_cast<uint32_t>(field_value.value));
      }
      break;
    case k64Bits:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetField64Volatile<false, kCheckTransaction>(field_offset, field_value.value);
      } else {
        obj->SetField64<false, kCheckTransaction>(field_offset, field_value.value);
      }
      break;
    case kReference:
      if (UNLIKELY(field_value.is_volatile)) {
        obj->SetFieldObjectVolatile<false, kCheckTransaction>(
            field_offset,
            reinterpret_cast<mirror::Object*>(field_value.value));
      } else {
        obj->SetFieldObject<false, kCheckTransaction>(
            field_offset,
            reinterpret_cast<mirror::Object*>(field_value.value));
      }
      break;
  }
}

void Transaction::ObjectLog::VisitRoots(RootVisitor* visitor) {
  for (auto& it : field_values_) {
    FieldValue& field_value = it.second;
    if (field_value.kind == ObjectLog::kReference) {
      visitor->VisitRootIfNonNull(reinterpret_cast<mirror::Object**>(&field_value.value),
                                  RootInfo(kRootUnknown));
    }
  }
}

void Transaction::InternStringLog::Undo(InternTable* intern_table) const {
  DCHECK(!Runtime::Current()->IsActiveTransaction());
  DCHECK(intern_table != nullptr);
  ObjPtr<mirror::String> s = str_.Read();
  uint32_t hash = static_cast<uint32_t>(s->GetStoredHashCode());
  switch (string_op_) {
    case InternStringLog::kInsert: {
      switch (string_kind_) {
        case InternStringLog::kStrongString:
          intern_table->RemoveStrong(s, hash);
          break;
        case InternStringLog::kWeakString:
          intern_table->RemoveWeak(s, hash);
          break;
        default:
          LOG(FATAL) << "Unknown interned string kind";
          UNREACHABLE();
      }
      break;
    }
    case InternStringLog::kRemove: {
      switch (string_kind_) {
        case InternStringLog::kStrongString:
          intern_table->InsertStrong(s, hash);
          break;
        case InternStringLog::kWeakString:
          intern_table->InsertWeak(s, hash);
          break;
        default:
          LOG(FATAL) << "Unknown interned string kind";
          UNREACHABLE();
      }
      break;
    }
    default:
      LOG(FATAL) << "Unknown interned string op";
      UNREACHABLE();
  }
}

void Transaction::InternStringLog::VisitRoots(RootVisitor* visitor) {
  str_.VisitRoot(visitor, RootInfo(kRootInternedString));
}

void Transaction::ResolveStringLog::Undo() const {
  dex_cache_.Read()->ClearString(string_idx_);
}

Transaction::ResolveStringLog::ResolveStringLog(ObjPtr<mirror::DexCache> dex_cache,
                                                dex::StringIndex string_idx)
    : dex_cache_(dex_cache),
      string_idx_(string_idx) {
  DCHECK(dex_cache != nullptr);
  DCHECK_LT(string_idx_.index_, dex_cache->GetDexFile()->NumStringIds());
}

void Transaction::ResolveStringLog::VisitRoots(RootVisitor* visitor) {
  dex_cache_.VisitRoot(visitor, RootInfo(kRootVMInternal));
}

void Transaction::ResolveMethodTypeLog::Undo() const {
  dex_cache_.Read()->ClearMethodType(proto_idx_);
}

Transaction::ResolveMethodTypeLog::ResolveMethodTypeLog(ObjPtr<mirror::DexCache> dex_cache,
                                                        dex::ProtoIndex proto_idx)
    : dex_cache_(dex_cache),
      proto_idx_(proto_idx) {
  DCHECK(dex_cache != nullptr);
  DCHECK_LT(proto_idx_.index_, dex_cache->GetDexFile()->NumProtoIds());
}

void Transaction::ResolveMethodTypeLog::VisitRoots(RootVisitor* visitor) {
  dex_cache_.VisitRoot(visitor, RootInfo(kRootVMInternal));
}

Transaction::InternStringLog::InternStringLog(ObjPtr<mirror::String> s,
                                              StringKind kind,
                                              StringOp op)
    : str_(s),
      string_kind_(kind),
      string_op_(op) {
  DCHECK(s != nullptr);
}

void Transaction::ArrayLog::LogValue(size_t index, uint64_t value) {
  if (is_new_array_) {
    return;
  }
  // Add a mapping if there is none yet.
  array_values_.FindOrAdd(index, value);
}

void Transaction::ArrayLog::Undo(mirror::Array* array) const {
  DCHECK(array != nullptr);
  DCHECK(array->IsArrayInstance());
  Primitive::Type type = array->GetClass()->GetComponentType()->GetPrimitiveType();
  for (auto it : array_values_) {
    UndoArrayWrite(array, type, it.first, it.second);
  }
}

void Transaction::ArrayLog::UndoArrayWrite(mirror::Array* array,
                                           Primitive::Type array_type,
                                           size_t index,
                                           uint64_t value) const {
  // TODO We may want to abort a transaction while still being in transaction mode. In this case,
  // we'd need to disable the check.
  constexpr bool kCheckTransaction = false;
  switch (array_type) {
    case Primitive::kPrimBoolean:
      array->AsBooleanArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<uint8_t>(value));
      break;
    case Primitive::kPrimByte:
      array->AsByteArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<int8_t>(value));
      break;
    case Primitive::kPrimChar:
      array->AsCharArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<uint16_t>(value));
      break;
    case Primitive::kPrimShort:
      array->AsShortArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<int16_t>(value));
      break;
    case Primitive::kPrimInt:
      array->AsIntArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<int32_t>(value));
      break;
    case Primitive::kPrimFloat:
      array->AsFloatArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<float>(value));
      break;
    case Primitive::kPrimLong:
      array->AsLongArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<int64_t>(value));
      break;
    case Primitive::kPrimDouble:
      array->AsDoubleArray()->SetWithoutChecks<false, kCheckTransaction>(
          index, static_cast<double>(value));
      break;
    case Primitive::kPrimNot:
      LOG(FATAL) << "ObjectArray should be treated as Object";
      UNREACHABLE();
    default:
      LOG(FATAL) << "Unsupported type " << array_type;
      UNREACHABLE();
  }
}

Transaction* ScopedAssertNoNewTransactionRecords::InstallAssertion(const char* reason) {
  Transaction* transaction = nullptr;
  if (kIsDebugBuild && Runtime::Current()->IsActiveTransaction()) {
    AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
    transaction = class_linker->GetTransaction();
    if (transaction != nullptr) {
      CHECK(transaction->assert_no_new_records_reason_ == nullptr)
          << "old: " << transaction->assert_no_new_records_reason_ << " new: " << reason;
      transaction->assert_no_new_records_reason_ = reason;
    }
  }
  return transaction;
}

void ScopedAssertNoNewTransactionRecords::RemoveAssertion(Transaction* transaction) {
  if (kIsDebugBuild) {
    AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
    CHECK(class_linker->GetTransaction() == transaction);
    CHECK(transaction->assert_no_new_records_reason_ != nullptr);
    transaction->assert_no_new_records_reason_ = nullptr;
  }
}

}  // namespace art