// Copyright (c) 2016 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 "CheckIPCVisitor.h"

using namespace clang;

namespace chrome_checker {

namespace {

const char kWriteParamBadType[] =
    "[chromium-ipc] IPC::WriteParam() is called on blacklisted type '%0'%1.";

const char kTupleBadType[] =
    "[chromium-ipc] IPC tuple references banned type '%0'%1.";

const char kWriteParamBadSignature[] =
    "[chromium-ipc] IPC::WriteParam() is expected to have two arguments.";

const char kNoteSeeHere[] =
    "see here";

}  // namespace

CheckIPCVisitor::CheckIPCVisitor(CompilerInstance& compiler)
  : compiler_(compiler), context_(nullptr) {
  auto& diagnostics = compiler_.getDiagnostics();
  error_write_param_bad_type_ = diagnostics.getCustomDiagID(
      DiagnosticsEngine::Error, kWriteParamBadType);
  error_tuple_bad_type_ = diagnostics.getCustomDiagID(
      DiagnosticsEngine::Error, kTupleBadType);
  error_write_param_bad_signature_ = diagnostics.getCustomDiagID(
      DiagnosticsEngine::Error, kWriteParamBadSignature);
  note_see_here_ = diagnostics.getCustomDiagID(
      DiagnosticsEngine::Note, kNoteSeeHere);

  blacklisted_typedefs_ = llvm::StringSet<>({
      "intmax_t",
      "uintmax_t",
      "intptr_t",
      "uintptr_t",
      "wint_t",
      "size_t",
      "rsize_t",
      "ssize_t",
      "ptrdiff_t",
      "dev_t",
      "off_t",
      "clock_t",
      "time_t",
      "suseconds_t"
  });
}

void CheckIPCVisitor::BeginDecl(Decl* decl) {
  decl_stack_.push_back(decl);
}

void CheckIPCVisitor::EndDecl() {
  decl_stack_.pop_back();
}

void CheckIPCVisitor::VisitTemplateSpecializationType(
    TemplateSpecializationType* spec) {
  ValidateCheckedTuple(spec);
}

void CheckIPCVisitor::VisitCallExpr(CallExpr* call_expr) {
  ValidateWriteParam(call_expr);
}

bool CheckIPCVisitor::ValidateWriteParam(const CallExpr* call_expr) {
  const FunctionDecl* callee_decl = call_expr->getDirectCallee();
  if (!callee_decl ||
      callee_decl->getQualifiedNameAsString() != "IPC::WriteParam") {
    return true;
  }

  return ValidateWriteParamSignature(call_expr) &&
      ValidateWriteParamArgument(call_expr->getArg(1));
}

// Checks that IPC::WriteParam() has expected signature.
bool CheckIPCVisitor::ValidateWriteParamSignature(
    const CallExpr* call_expr) {
  if (call_expr->getNumArgs() != 2) {
    compiler_.getDiagnostics().Report(
        call_expr->getExprLoc(), error_write_param_bad_signature_);
    return false;
  }
  return true;
}

// Checks that IPC::WriteParam() argument type is allowed.
// See CheckType() for specifics.
bool CheckIPCVisitor::ValidateWriteParamArgument(const Expr* arg_expr) {
  if (auto* parent_fn_decl = GetParentDecl<FunctionDecl>()) {
    auto template_kind = parent_fn_decl->getTemplatedKind();
    if (template_kind != FunctionDecl::TK_NonTemplate &&
        template_kind != FunctionDecl::TK_FunctionTemplate) {
      // Skip all specializations - we don't check WriteParam() on dependent
      // types (typedef info gets lost), and we checked all non-dependent uses
      // earlier (when we checked the template itself).
      return true;
    }
  }

  QualType arg_type;

  arg_expr = arg_expr->IgnoreImplicit();
  if (auto* cast_expr = dyn_cast<ExplicitCastExpr>(arg_expr)) {
    arg_type = cast_expr->getTypeAsWritten();
  } else {
    arg_type = arg_expr->getType();
  }

  CheckDetails details;
  if (CheckType(arg_type, &details)) {
    return true;
  }

  ReportCheckError(details,
                   arg_expr->getExprLoc(),
                   error_write_param_bad_type_);

  return false;
}

// Checks that IPC::CheckedTuple<> is specialized with allowed types.
// See CheckType() above for specifics.
bool CheckIPCVisitor::ValidateCheckedTuple(
    const TemplateSpecializationType* spec) {
  TemplateDecl* decl = spec->getTemplateName().getAsTemplateDecl();
  if (!decl || decl->getQualifiedNameAsString() != "IPC::CheckedTuple") {
    return true;
  }

  bool valid = true;
  for (unsigned i = 0; i != spec->getNumArgs(); ++i) {
    const TemplateArgument& arg = spec->getArg(i);
    CheckDetails details;
    if (CheckTemplateArgument(arg, &details)) {
      continue;
    }

    valid = false;

    auto* parent_decl = GetParentDecl<Decl>();
    ReportCheckError(
        details,
        parent_decl ? parent_decl->getLocStart() : SourceLocation(),
        error_tuple_bad_type_);
  }

  return valid;
}

template <typename T>
const T* CheckIPCVisitor::GetParentDecl() const {
  for (auto i = decl_stack_.rbegin(); i != decl_stack_.rend(); ++i) {
    if (auto* parent = dyn_cast_or_null<T>(*i)) {
      return parent;
    }
  }
  return nullptr;
}


bool CheckIPCVisitor::IsBlacklistedType(QualType type) const {
  return context_->hasSameUnqualifiedType(type, context_->LongTy) ||
      context_->hasSameUnqualifiedType(type, context_->UnsignedLongTy);
}

bool CheckIPCVisitor::IsBlacklistedTypedef(const TypedefNameDecl* tdef) const {
  return blacklisted_typedefs_.find(tdef->getName()) !=
      blacklisted_typedefs_.end();
}

// Checks that integer type is allowed (not blacklisted).
bool CheckIPCVisitor::CheckIntegerType(QualType type,
                                       CheckDetails* details) const {
  bool seen_typedef = false;
  while (true) {
    details->exit_type = type;

    if (auto* tdef = dyn_cast<TypedefType>(type)) {
      if (IsBlacklistedTypedef(tdef->getDecl())) {
        return false;
      }
      details->typedefs.push_back(tdef);
      seen_typedef = true;
    }

    QualType desugared_type =
        type->getLocallyUnqualifiedSingleStepDesugaredType();
    if (desugared_type == type) {
      break;
    }

    type = desugared_type;
  }

  return seen_typedef || !IsBlacklistedType(type);
}

// Checks that |type| is allowed (not blacklisted), recursively visiting
// template specializations.
bool CheckIPCVisitor::CheckType(QualType type, CheckDetails* details) const {
  if (type->isReferenceType()) {
    type = type->getPointeeType();
  }
  type = type.getLocalUnqualifiedType();

  if (details->entry_type.isNull()) {
    details->entry_type = type;
  }

  if (type->isIntegerType()) {
    return CheckIntegerType(type, details);
  }

  while (true) {
    if (auto* spec = dyn_cast<TemplateSpecializationType>(type)) {
      for (const TemplateArgument& arg: *spec) {
        if (!CheckTemplateArgument(arg, details)) {
          return false;
        }
      }
      return true;
    }

    if (auto* record = dyn_cast<RecordType>(type)) {
      if (auto* spec = dyn_cast<ClassTemplateSpecializationDecl>(
              record->getDecl())) {
        const TemplateArgumentList& args = spec->getTemplateArgs();
        for (unsigned i = 0; i != args.size(); ++i) {
          if (!CheckTemplateArgument(args[i], details)) {
            return false;
          }
        }
      }
      return true;
    }

    if (auto* tdef = dyn_cast<TypedefType>(type)) {
      details->typedefs.push_back(tdef);
    }

    QualType desugared_type =
        type->getLocallyUnqualifiedSingleStepDesugaredType();
    if (desugared_type == type) {
      break;
    }

    type = desugared_type;
  }

  return true;
}

bool CheckIPCVisitor::CheckTemplateArgument(const TemplateArgument& arg,
                                            CheckDetails* details) const {
  return arg.getKind() != TemplateArgument::Type ||
      CheckType(arg.getAsType(), details);
}

void CheckIPCVisitor::ReportCheckError(const CheckDetails& details,
                                       SourceLocation loc,
                                       unsigned error) {
  DiagnosticsEngine& diagnostics = compiler_.getDiagnostics();

  std::string entry_type = details.entry_type.getAsString();
  std::string exit_type = details.exit_type.getAsString();

  std::string via;
  if (entry_type != exit_type) {
    via = " via '" + entry_type + "'";
  }
  diagnostics.Report(loc, error) << exit_type << via;

  for (const TypedefType* tdef: details.typedefs) {
    diagnostics.Report(tdef->getDecl()->getLocation(), note_see_here_);
  }
}

}  // namespace chrome_checker