// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "CheckFieldsVisitor.h"

#include <cassert>

#include "BlinkGCPluginOptions.h"
#include "RecordInfo.h"

CheckFieldsVisitor::CheckFieldsVisitor()
    : current_(0),
      stack_allocated_host_(false) {
}

CheckFieldsVisitor::Errors& CheckFieldsVisitor::invalid_fields() {
  return invalid_fields_;
}

bool CheckFieldsVisitor::ContainsInvalidFields(RecordInfo* info) {
  stack_allocated_host_ = info->IsStackAllocated();
  managed_host_ = stack_allocated_host_ ||
                  info->IsGCAllocated() ||
                  info->IsNonNewable() ||
                  info->IsOnlyPlacementNewable();
  for (RecordInfo::Fields::iterator it = info->GetFields().begin();
       it != info->GetFields().end();
       ++it) {
    context().clear();
    current_ = &it->second;
    current_->edge()->Accept(this);
  }
  return !invalid_fields_.empty();
}

void CheckFieldsVisitor::AtMember(Member* edge) {
  if (managed_host_)
    return;
  // A member is allowed to appear in the context of a root.
  for (Context::iterator it = context().begin();
       it != context().end();
       ++it) {
    if ((*it)->Kind() == Edge::kRoot)
      return;
  }
  invalid_fields_.push_back(std::make_pair(current_, kMemberInUnmanaged));
}

void CheckFieldsVisitor::AtIterator(Iterator* edge) {
  if (!managed_host_)
    return;

  if (edge->IsUnsafe())
    invalid_fields_.push_back(std::make_pair(current_, kIteratorToGCManaged));
}

void CheckFieldsVisitor::AtValue(Value* edge) {
  // TODO: what should we do to check unions?
  if (edge->value()->record()->isUnion())
    return;

  if (!stack_allocated_host_ && edge->value()->IsStackAllocated()) {
    invalid_fields_.push_back(std::make_pair(current_, kPtrFromHeapToStack));
    return;
  }

  if (!Parent() &&
      edge->value()->IsGCDerived() &&
      !edge->value()->IsGCMixin()) {
    invalid_fields_.push_back(std::make_pair(current_, kGCDerivedPartObject));
    return;
  }

  // If in a stack allocated context, be fairly insistent that T in Member<T>
  // is GC allocated, as stack allocated objects do not have a trace()
  // that separately verifies the validity of Member<T>.
  //
  // Notice that an error is only reported if T's definition is in scope;
  // we do not require that it must be brought into scope as that would
  // prevent declarations of mutually dependent class types.
  //
  // (Note: Member<>'s constructor will at run-time verify that the
  // pointer it wraps is indeed heap allocated.)
  if (stack_allocated_host_ && Parent() && Parent()->IsMember() &&
      edge->value()->HasDefinition() && !edge->value()->IsGCAllocated()) {
    invalid_fields_.push_back(std::make_pair(current_,
                                             kMemberToGCUnmanaged));
    return;
  }

  if (!Parent() || !edge->value()->IsGCAllocated())
    return;

  // Disallow  OwnPtr<T>, RefPtr<T> and T* to stack-allocated types.
  if (Parent()->IsOwnPtr() ||
      Parent()->IsUniquePtr() ||
      Parent()->IsRefPtr() ||
      (stack_allocated_host_ && Parent()->IsRawPtr())) {
    invalid_fields_.push_back(std::make_pair(
        current_, InvalidSmartPtr(Parent())));
    return;
  }
  if (Parent()->IsRawPtr()) {
    RawPtr* rawPtr = static_cast<RawPtr*>(Parent());
    Error error = rawPtr->HasReferenceType() ?
        kReferencePtrToGCManaged : kRawPtrToGCManaged;
    invalid_fields_.push_back(std::make_pair(current_, error));
  }
}

void CheckFieldsVisitor::AtCollection(Collection* edge) {
  if (edge->on_heap() && Parent() && Parent()->IsOwnPtr())
    invalid_fields_.push_back(std::make_pair(current_, kOwnPtrToGCManaged));
  if (edge->on_heap() && Parent() && Parent()->IsUniquePtr())
    invalid_fields_.push_back(std::make_pair(current_, kUniquePtrToGCManaged));
}

CheckFieldsVisitor::Error CheckFieldsVisitor::InvalidSmartPtr(Edge* ptr) {
  if (ptr->IsRawPtr()) {
    if (static_cast<RawPtr*>(ptr)->HasReferenceType())
      return kReferencePtrToGCManaged;
    else
      return kRawPtrToGCManaged;
  }
  if (ptr->IsRefPtr())
    return kRefPtrToGCManaged;
  if (ptr->IsOwnPtr())
    return kOwnPtrToGCManaged;
  if (ptr->IsUniquePtr())
    return kUniquePtrToGCManaged;
  assert(false && "Unknown smart pointer kind");
}