//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "DefinitionsInHeadersCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace misc {

namespace {

AST_MATCHER(NamedDecl, isHeaderFileExtension) {
  SourceManager& SM = Finder->getASTContext().getSourceManager();
  SourceLocation ExpansionLoc = SM.getExpansionLoc(Node.getLocStart());
  StringRef Filename = SM.getFilename(ExpansionLoc);
  return Filename.endswith(".h") || Filename.endswith(".hh") ||
         Filename.endswith(".hpp") || Filename.endswith(".hxx") ||
         llvm::sys::path::extension(Filename).empty();
}

} // namespace

DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(
    StringRef Name, ClangTidyContext *Context)
      : ClangTidyCheck(Name, Context),
        UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)) {}

void DefinitionsInHeadersCheck::storeOptions(
    ClangTidyOptions::OptionMap &Opts) {
  Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension);
}

void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) {
  if (UseHeaderFileExtension) {
    Finder->addMatcher(
        namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
                  isHeaderFileExtension()).bind("name-decl"),
        this);
  } else {
    Finder->addMatcher(
        namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
                  anyOf(isHeaderFileExtension(),
                        unless(isExpansionInMainFile()))).bind("name-decl"),
        this);
  }
}

void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
  // C++ [basic.def.odr] p6:
  // There can be more than one definition of a class type, enumeration type,
  // inline function with external linkage, class template, non-static function
  // template, static data member of a class template, member function of a
  // class template, or template specialization for which some template
  // parameters are not specifiedin a program provided that each definition
  // appears in a different translation unit, and provided the definitions
  // satisfy the following requirements.
  const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("name-decl");
  assert(ND);

  // Internal linkage variable definitions are ignored for now:
  //   const int a = 1;
  //   static int b = 1;
  //
  // Although these might also cause ODR violations, we can be less certain and
  // should try to keep the false-positive rate down.
  if (ND->getLinkageInternal() == InternalLinkage)
    return;

  if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
    // Inline functions are allowed.
    if (FD->isInlined())
      return;
    // Function templates are allowed.
    if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
      return;
    // Function template full specialization is prohibited in header file.
    if (FD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
      return;
    // Member function of a class template and member function of a nested class
    // in a class template are allowed.
    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
      const auto *DC = MD->getDeclContext();
      while (DC->isRecord()) {
        if (const auto *RD = dyn_cast<CXXRecordDecl>(DC))
          if (RD->getDescribedClassTemplate())
            return;
        DC = DC->getParent();
      }
    }

    diag(FD->getLocation(),
         "function '%0' defined in a header file; "
         "function definitions in header files can lead to ODR violations")
        << FD->getNameInfo().getName().getAsString()
        << FixItHint::CreateInsertion(FD->getSourceRange().getBegin(),
                                      "inline ");
  } else if (const auto *VD = dyn_cast<VarDecl>(ND)) {
    // Static data members of a class template are allowed.
    if (VD->getDeclContext()->isDependentContext() && VD->isStaticDataMember())
      return;
    if (VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
      return;
    // Ignore variable definition within function scope.
    if (VD->hasLocalStorage() || VD->isStaticLocal())
      return;

    diag(VD->getLocation(),
         "variable '%0' defined in a header file; "
         "variable definitions in header files can lead to ODR violations")
        << VD->getName();
  }
}

} // namespace misc
} // namespace tidy
} // namespace clang
