/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <algorithm>
#include <cassert>
#include <limits>
#include <map>
#include <string>

#include "clang/AST/Attr.h"

#include "check.hxx"
#include "compat.hxx"
#include "functionaddress.hxx"
#include "plugin.hxx"

namespace {

// BEGIN code copied from LLVM's clang/lib/Sema/Sema.cpp

typedef llvm::DenseMap<const CXXRecordDecl*, bool> RecordCompleteMap;

/// Returns true, if all methods and nested classes of the given
/// CXXRecordDecl are defined in this translation unit.
///
/// Should only be called from ActOnEndOfTranslationUnit so that all
/// definitions are actually read.
static bool MethodsAndNestedClassesComplete(const CXXRecordDecl *RD,
                                            RecordCompleteMap &MNCComplete) {
  RecordCompleteMap::iterator Cache = MNCComplete.find(RD);
  if (Cache != MNCComplete.end())
    return Cache->second;
  if (!RD->isCompleteDefinition())
    return false;
  bool Complete = true;
  for (DeclContext::decl_iterator I = RD->decls_begin(),
                                  E = RD->decls_end();
       I != E && Complete; ++I) {
    if (const CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(*I))
      Complete = M->isDefined() || M->isDefaulted() ||
                 (M->isPure() && !isa<CXXDestructorDecl>(M));
    else if (const FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(*I))
      // If the template function is marked as late template parsed at this
      // point, it has not been instantiated and therefore we have not
      // performed semantic analysis on it yet, so we cannot know if the type
      // can be considered complete.
      Complete = !F->getTemplatedDecl()->isLateTemplateParsed() &&
                  F->getTemplatedDecl()->isDefined();
    else if (const CXXRecordDecl *R = dyn_cast<CXXRecordDecl>(*I)) {
      if (R->isInjectedClassName())
        continue;
      if (R->hasDefinition())
        Complete = MethodsAndNestedClassesComplete(R->getDefinition(),
                                                   MNCComplete);
      else
        Complete = false;
    }
  }
  MNCComplete[RD] = Complete;
  return Complete;
}

/// Returns true, if the given CXXRecordDecl is fully defined in this
/// translation unit, i.e. all methods are defined or pure virtual and all
/// friends, friend functions and nested classes are fully defined in this
/// translation unit.
///
/// Should only be called from ActOnEndOfTranslationUnit so that all
/// definitions are actually read.
static bool IsRecordFullyDefined(const CXXRecordDecl *RD,
                                 RecordCompleteMap &RecordsComplete,
                                 RecordCompleteMap &MNCComplete) {
  RecordCompleteMap::iterator Cache = RecordsComplete.find(RD);
  if (Cache != RecordsComplete.end())
    return Cache->second;
  bool Complete = MethodsAndNestedClassesComplete(RD, MNCComplete);
  for (CXXRecordDecl::friend_iterator I = RD->friend_begin(),
                                      E = RD->friend_end();
       I != E && Complete; ++I) {
    // Check if friend classes and methods are complete.
    if (TypeSourceInfo *TSI = (*I)->getFriendType()) {
      // Friend classes are available as the TypeSourceInfo of the FriendDecl.
      if (CXXRecordDecl *FriendD = TSI->getType()->getAsCXXRecordDecl())
        Complete = MethodsAndNestedClassesComplete(FriendD, MNCComplete);
      else
        Complete = false;
    } else {
      // Friend functions are available through the NamedDecl of FriendDecl.
      if (const FunctionDecl *FD =
          dyn_cast<FunctionDecl>((*I)->getFriendDecl()))
        Complete = FD->isDefined();
      else
        // This is a template friend, give up.
        Complete = false;
    }
  }
  RecordsComplete[RD] = Complete;
  return Complete;
}

RecordCompleteMap RecordsComplete;
RecordCompleteMap MNCComplete;

// END code copied from LLVM's clang/lib/Sema/Sema.cpp

// Is all code that could see `decl` defined in this TU?
bool isAllRelevantCodeDefined(NamedDecl const * decl) {
    switch (decl->getAccess()) {
    case AS_protected:
        if (!cast<CXXRecordDecl>(decl->getDeclContext())->hasAttr<FinalAttr>()) {
            break;
        }
        LLVM_FALLTHROUGH;
    case AS_private:
        if (IsRecordFullyDefined(
                cast<CXXRecordDecl>(decl->getDeclContext()), RecordsComplete, MNCComplete))
        {
            return true;
        }
        break;
    default:
        break;
    }
    return !decl->isExternallyVisible();
}

enum FakeBoolKind {
    FBK_No,
    FBK_BOOL, FBK_First = FBK_BOOL,
    FBK_Boolean, FBK_FT_Bool, FBK_FcBool, FBK_GLboolean, FBK_NPBool, FBK_TW_BOOL, FBK_UBool,
    FBK_boolean, FBK_dbus_bool_t, FBK_gboolean, FBK_hb_boot_t, FBK_jboolean, FBK_my_bool,
    FBK_sal_Bool,
    FBK_End };
    // matches loplugin::TypeCheck::AnyBoolean (compilerplugins/clang/check.hxx)

StringRef getName(FakeBoolKind k) {
    static constexpr llvm::StringLiteral names[] = {
        "BOOL", "Boolean", "FT_Bool", "FcBool", "GLboolean", "NPBool", "TW_BOOL", "UBool",
        "boolean", "dbus_bool_t", "gboolean", "hb_boot_t", "jboolean", "my_bool", "sal_Bool"};
    assert(k >= FBK_First && k < FBK_End);
    return names[k - FBK_First];
}

FakeBoolKind isFakeBool(QualType type) {
    TypedefType const * t = type->getAs<TypedefType>();
    if (t != nullptr) {
        auto const name = t->getDecl()->getName();
        for (int i = FBK_First; i != FBK_End; ++i) {
            auto const k = FakeBoolKind(i);
            if (name == getName(k)) {
                return k;
            }
        }
    }
    return FBK_No;
}

FakeBoolKind isFakeBoolArray(QualType type) {
    auto t = type->getAsArrayTypeUnsafe();
    if (t == nullptr) {
        return FBK_No;
    }
    auto const k = isFakeBool(t->getElementType());
    if (k != FBK_No) {
        return k;
    }
    return isFakeBoolArray(t->getElementType());
}

// It appears that, given a function declaration, there is no way to determine
// the language linkage of the function's type, only of the function's name
// (via FunctionDecl::isExternC); however, in a case like
//
//   extern "C" { static void f(); }
//
// the function's name does not have C language linkage while the function's
// type does (as clarified in C++11 [decl.link]); cf. <http://clang-developers.
// 42468.n3.nabble.com/Language-linkage-of-function-type-tt4037248.html>
// "Language linkage of function type":
bool hasCLanguageLinkageType(FunctionDecl const * decl) {
    assert(decl != nullptr);
    if (decl->isExternC()) {
        return true;
    }
    if (decl->isInExternCContext()) {
        return true;
    }
    return false;
}

enum class OverrideKind { NO, YES, MAYBE };

OverrideKind getOverrideKind(FunctionDecl const * decl) {
    CXXMethodDecl const * m = dyn_cast<CXXMethodDecl>(decl);
    if (m == nullptr) {
        return OverrideKind::NO;
    }
    if (m->size_overridden_methods() != 0 || m->hasAttr<OverrideAttr>()) {
        return OverrideKind::YES;
    }
    if (!dyn_cast<CXXRecordDecl>(m->getDeclContext())->hasAnyDependentBases()) {
        return OverrideKind::NO;
    }
    return OverrideKind::MAYBE;
}

enum class BoolOverloadKind { No, Yes, CheckNext };

BoolOverloadKind isBoolOverloadOf(
    FunctionDecl const * f, FunctionDecl const * decl, bool mustBeDeleted)
{
    if (!mustBeDeleted || f->isDeleted()) {
        unsigned n = decl->getNumParams();
        if (f->getNumParams() == n) {
            bool hasFB = false;
            for (unsigned i = 0; i != n; ++i) {
                QualType t1 { decl->getParamDecl(i)->getType() };
                bool isFB = isFakeBool(t1) != FBK_No;
                bool isFBRef = !isFB && t1->isReferenceType()
                    && isFakeBool(t1.getNonReferenceType()) != FBK_No;
                QualType t2 { f->getParamDecl(i)->getType() };
                if (!(isFB
                      ? t2->isBooleanType()
                      : isFBRef
                      ? (t2->isReferenceType()
                         && t2.getNonReferenceType()->isBooleanType())
                      : t2.getCanonicalType() == t1.getCanonicalType()))
                {
                    return BoolOverloadKind::CheckNext;
                }
                hasFB |= isFB || isFBRef;
            }
            return hasFB ? BoolOverloadKind::Yes : BoolOverloadKind::No;
                // cheaply protect against the case where decl would have no
                // fake bool parameters at all and would match itself
        }
    }
    return BoolOverloadKind::CheckNext;
}

//TODO: current implementation is not at all general, just tests what we
// encounter in practice:
bool hasBoolOverload(FunctionDecl const * decl, bool mustBeDeleted) {
    auto ctx = decl->getDeclContext();
    if (!ctx->isLookupContext()) {
        return false;
    }
    auto res = ctx->lookup(decl->getDeclName());
    for (auto d = res.begin(); d != res.end(); ++d) {
        if (auto f = dyn_cast<FunctionDecl>(*d)) {
            switch (isBoolOverloadOf(f, decl, mustBeDeleted)) {
            case BoolOverloadKind::No:
                return false;
            case BoolOverloadKind::Yes:
                return true;
            case BoolOverloadKind::CheckNext:
                break;
            }
        } else if (auto ftd = dyn_cast<FunctionTemplateDecl>(*d)) {
            for (auto f: ftd->specializations()) {
                if (f->getTemplateSpecializationKind()
                    == TSK_ExplicitSpecialization)
                {
                    switch (isBoolOverloadOf(f, decl, mustBeDeleted)) {
                    case BoolOverloadKind::No:
                        return false;
                    case BoolOverloadKind::Yes:
                        return true;
                    case BoolOverloadKind::CheckNext:
                        break;
                    }
                }
            }
        }
    }
    return false;
}

class FakeBool:
    public loplugin::FunctionAddress<loplugin::FilteringRewritePlugin<FakeBool>>
{
public:
    explicit FakeBool(loplugin::InstantiationData const & data):
        FunctionAddress(data) {}

    virtual void run() override;

    bool VisitUnaryAddrOf(UnaryOperator const * op);

    bool VisitCallExpr(CallExpr * expr);

    bool VisitCStyleCastExpr(CStyleCastExpr * expr);

    bool VisitCXXStaticCastExpr(CXXStaticCastExpr * expr);

    bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr);

    bool VisitImplicitCastExpr(ImplicitCastExpr * expr);

    bool VisitReturnStmt(ReturnStmt const * stmt);

    bool WalkUpFromParmVarDecl(ParmVarDecl const * decl);
    bool VisitParmVarDecl(ParmVarDecl const * decl);

    bool WalkUpFromVarDecl(VarDecl const * decl);
    bool VisitVarDecl(VarDecl const * decl);

    bool WalkUpFromFieldDecl(FieldDecl const * decl);
    bool VisitFieldDecl(FieldDecl const * decl);

    bool WalkUpFromFunctionDecl(FunctionDecl const * decl);
    bool VisitFunctionDecl(FunctionDecl const * decl);

    bool VisitValueDecl(ValueDecl const * decl);

    bool TraverseStaticAssertDecl(StaticAssertDecl * decl);

    bool TraverseLinkageSpecDecl(LinkageSpecDecl * decl);

private:
    bool isFromCIncludeFile(SourceLocation spellingLocation) const;

    bool isSharedCAndCppCode(SourceLocation location) const;

    bool isInSpecialMainFile(SourceLocation spellingLocation) const;

    bool rewrite(SourceLocation location, FakeBoolKind kind);

    std::map<VarDecl const *, FakeBoolKind> varDecls_;
    std::map<FieldDecl const *, FakeBoolKind> fieldDecls_;
    std::map<ParmVarDecl const *, FakeBoolKind> parmVarDecls_;
    std::map<FunctionDecl const *, FakeBoolKind> functionDecls_;
    unsigned int externCContexts_ = 0;
};

void FakeBool::run() {
    if (compiler.getLangOpts().CPlusPlus) {
        TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
        for (auto const dcl: varDecls_) {
            auto const decl = dcl.first; auto const fbk = dcl.second;
            SourceLocation loc { compat::getBeginLoc(decl) };
            TypeSourceInfo * tsi = decl->getTypeSourceInfo();
            if (tsi != nullptr) {
                SourceLocation l {
                    compiler.getSourceManager().getExpansionLoc(
                        tsi->getTypeLoc().getBeginLoc()) };
                SourceLocation end {
                    compiler.getSourceManager().getExpansionLoc(
                        tsi->getTypeLoc().getEndLoc()) };
                assert(l.isFileID() && end.isFileID());
                if (l == end
                    || compiler.getSourceManager().isBeforeInTranslationUnit(
                        l, end))
                {
                    for (;;) {
                        unsigned n = Lexer::MeasureTokenLength(
                            l, compiler.getSourceManager(),
                            compiler.getLangOpts());
                        std::string s {
                            compiler.getSourceManager().getCharacterData(l),
                            n };
                        if (s == getName(fbk)) {
                            loc = l;
                            break;
                        }
                        if (l == end) {
                            break;
                        }
                        l = l.getLocWithOffset(std::max<unsigned>(n, 1));
                    }
                }
            }
            if (!rewrite(loc, fbk)) {
                report(
                    DiagnosticsEngine::Warning,
                    "VarDecl, use \"bool\" instead of %0", loc)
                    << decl->getType().getLocalUnqualifiedType()
                    << decl->getSourceRange();
            }
        }
        for (auto const dcl: fieldDecls_) {
            auto const decl = dcl.first; auto const fbk = dcl.second;
            SourceLocation loc { compat::getBeginLoc(decl) };
            TypeSourceInfo * tsi = decl->getTypeSourceInfo();
            if (tsi != nullptr) {
                SourceLocation l {
                    compiler.getSourceManager().getExpansionLoc(
                        tsi->getTypeLoc().getBeginLoc()) };
                SourceLocation end {
                    compiler.getSourceManager().getExpansionLoc(
                        tsi->getTypeLoc().getEndLoc()) };
                assert(l.isFileID() && end.isFileID());
                if (l == end
                    || compiler.getSourceManager().isBeforeInTranslationUnit(
                        l, end))
                {
                    for (;;) {
                        unsigned n = Lexer::MeasureTokenLength(
                            l, compiler.getSourceManager(),
                            compiler.getLangOpts());
                        std::string s {
                            compiler.getSourceManager().getCharacterData(l),
                            n };
                        if (s == getName(fbk)) {
                            loc = l;
                            break;
                        }
                        if (l == end) {
                            break;
                        }
                        l = l.getLocWithOffset(std::max<unsigned>(n, 1));
                    }
                }
            }
            if (!rewrite(loc, fbk)) {
                report(
                    DiagnosticsEngine::Warning,
                    "FieldDecl, use \"bool\" instead of %0", loc)
                    << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange();
            }
        }
        auto const ignoredFns = getFunctionsWithAddressTaken();
        for (auto const dcl: parmVarDecls_) {
            auto const decl = dcl.first; auto const fbk = dcl.second;
            FunctionDecl const * f = cast<FunctionDecl>(decl->getDeclContext())->getCanonicalDecl();
            if (ignoredFns.find(f) != ignoredFns.end()) {
                continue;
            }
            SourceLocation loc { compat::getBeginLoc(decl) };
            TypeSourceInfo * tsi = decl->getTypeSourceInfo();
            if (tsi != nullptr) {
                SourceLocation l {
                    compiler.getSourceManager().getExpansionLoc(
                        tsi->getTypeLoc().getBeginLoc()) };
                SourceLocation end {
                    compiler.getSourceManager().getExpansionLoc(
                        tsi->getTypeLoc().getEndLoc()) };
                assert(l.isFileID() && end.isFileID());
                if (l == end
                    || (compiler.getSourceManager()
                        .isBeforeInTranslationUnit(l, end)))
                {
                    for (;;) {
                        unsigned n = Lexer::MeasureTokenLength(
                            l, compiler.getSourceManager(),
                            compiler.getLangOpts());
                        std::string s {
                            compiler.getSourceManager().getCharacterData(l),
                            n };
                        if (s == getName(fbk)) {
                            loc = l;
                            break;
                        }
                        if (l == end) {
                            break;
                        }
                        l = l.getLocWithOffset(std::max<unsigned>(n, 1));
                    }
                }
            }
            // Only rewrite declarations in include files if a
            // definition is also seen, to avoid compilation of a
            // definition (in a main file only processed later) to fail
            // with a "mismatch" error before the rewriter had a chance
            // to act upon the definition (but use the heuristic of
            // assuming pure virtual functions do not have definitions);
            // also, do not automatically rewrite functions that could
            // implicitly override depend base functions (and thus stop
            // doing so after the rewrite; note that this is less
            // dangerous for return types than for parameter types,
            // where the function would still implicitly override and
            // cause a compilation error due to the incompatible return
            // type):
            OverrideKind k = getOverrideKind(f);
            if (!((compiler.getSourceManager().isInMainFile(
                       compiler.getSourceManager().getSpellingLoc(
                           dyn_cast<FunctionDecl>(
                               decl->getDeclContext())
                           ->getNameInfo().getLoc()))
                   || f->isDefined() || f->isPure())
                  && k != OverrideKind::MAYBE && rewrite(loc, fbk)))
            {
                report(
                    DiagnosticsEngine::Warning,
                    ("ParmVarDecl, use \"bool\" instead of"
                     " %0%1"),
                    loc)
                    << decl->getType().getNonReferenceType().getLocalUnqualifiedType()
                    << (k == OverrideKind::MAYBE
                        ? (" (unless this member function overrides a"
                           " dependent base member function, even"
                           " though it is not marked 'override')")
                        : "")
                    << decl->getSourceRange();
            }
        }
        for (auto const dcl: functionDecls_) {
            auto const decl = dcl.first; auto const fbk = dcl.second;
            FunctionDecl const * f = decl->getCanonicalDecl();
            if (ignoredFns.find(f) != ignoredFns.end()) {
                continue;
            }
            SourceLocation loc { compat::getBeginLoc(decl) };
            SourceLocation l { compiler.getSourceManager().getExpansionLoc(
                loc) };
            SourceLocation end { compiler.getSourceManager().getExpansionLoc(
                decl->getNameInfo().getLoc()) };
            assert(l.isFileID() && end.isFileID());
            if (compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) {
                while (l != end) {
                    unsigned n = Lexer::MeasureTokenLength(
                        l, compiler.getSourceManager(), compiler.getLangOpts());
                    std::string s {
                        compiler.getSourceManager().getCharacterData(l), n };
                    if (s == getName(fbk)) {
                        loc = l;
                        break;
                    }
                    l = l.getLocWithOffset(std::max<unsigned>(n, 1));
                }
            }
            // Only rewrite declarations in include files if a definition is
            // also seen, to avoid compilation of a definition (in a main file
            // only processed later) to fail with a "mismatch" error before the
            // rewriter had a chance to act upon the definition (but use the
            // heuristic of assuming pure virtual functions do not have
            // definitions):
            if (!((compiler.getSourceManager().isInMainFile(
                       compiler.getSourceManager().getSpellingLoc(
                           decl->getNameInfo().getLoc()))
                   || f->isDefined() || f->isPure())
                  && rewrite(loc, fbk)))
            {
                report(
                    DiagnosticsEngine::Warning,
                    "use \"bool\" instead of %0 as return type%1",
                    loc)
                    << decl->getReturnType().getNonReferenceType().getLocalUnqualifiedType()
                    << (getOverrideKind(f) == OverrideKind::MAYBE
                        ? (" (unless this member function overrides a dependent"
                           " base member function, even though it is not marked"
                           " 'override')")
                        : "")
                    << decl->getSourceRange();
            }
        }
    }
}

bool FakeBool::VisitUnaryAddrOf(UnaryOperator const * op) {
    FunctionAddress::VisitUnaryAddrOf(op);
    Expr const * e1 = op->getSubExpr()->IgnoreParenCasts();
    if (isFakeBool(e1->getType()) != FBK_No) {
        if (DeclRefExpr const * e2 = dyn_cast<DeclRefExpr>(e1)) {
            VarDecl const * d = dyn_cast<VarDecl>(e2->getDecl());
            if (d != nullptr) {
                varDecls_.erase(d);
            }
        } else if (auto const e3 = dyn_cast<MemberExpr>(e1)) {
            if (auto const d = dyn_cast<FieldDecl>(e3->getMemberDecl())) {
                fieldDecls_.erase(d);
            }
        }
    }
    return true;
}

bool FakeBool::VisitCallExpr(CallExpr * expr) {
    Decl const * d = expr->getCalleeDecl();
    FunctionProtoType const * ft = nullptr;
    if (d != nullptr) {
        FunctionDecl const * fd = dyn_cast<FunctionDecl>(d);
        if (fd != nullptr) {
            if (!hasBoolOverload(fd, false)) {
                clang::PointerType const * pt = fd->getType()
                    ->getAs<clang::PointerType>();
                QualType t2(
                    pt == nullptr ? fd->getType() : pt->getPointeeType());
                ft = t2->getAs<FunctionProtoType>();
                assert(
                    ft != nullptr || !compiler.getLangOpts().CPlusPlus
                    || (fd->getBuiltinID() != Builtin::NotBuiltin
                        && isa<FunctionNoProtoType>(t2)));
                    // __builtin_*s have no proto type?
            }
        } else {
            VarDecl const * vd = dyn_cast<VarDecl>(d);
            if (vd != nullptr) {
                clang::PointerType const * pt = vd->getType()
                    ->getAs<clang::PointerType>();
                ft = (pt == nullptr ? vd->getType() : pt->getPointeeType())
                    ->getAs<FunctionProtoType>();
            }
        }
    }
    if (ft != nullptr) {
        for (unsigned i = 0; i != ft->getNumParams(); ++i) {
            QualType t(ft->getParamType(i));
            bool b = false;
            if (t->isLValueReferenceType()) {
                t = t.getNonReferenceType();
                b = !t.isConstQualified() && isFakeBool(t) != FBK_No;
            } else if (t->isPointerType()) {
                for (;;) {
                    auto t2 = t->getAs<clang::PointerType>();
                    if (t2 == nullptr) {
                        break;
                    }
                    t = t2->getPointeeType();
                }
                b = isFakeBool(t) != FBK_No;
            }
            if (b && i < expr->getNumArgs()) {
                auto const e1 = expr->getArg(i)->IgnoreParenImpCasts();
                if (DeclRefExpr * ref = dyn_cast<DeclRefExpr>(e1)) {
                    VarDecl const * d = dyn_cast<VarDecl>(ref->getDecl());
                    if (d != nullptr) {
                        varDecls_.erase(d);
                    }
                } else if (auto const e2 = dyn_cast<MemberExpr>(e1)) {
                    if (auto const d = dyn_cast<FieldDecl>(e2->getMemberDecl())) {
                        fieldDecls_.erase(d);
                    }
                }
            }
        }
    }
    return true;
}

bool FakeBool::VisitCStyleCastExpr(CStyleCastExpr * expr) {
    if (ignoreLocation(expr)) {
        return true;
    }
    auto const k = isFakeBool(expr->getType());
    if (k != FBK_No) {
        SourceLocation loc { compat::getBeginLoc(expr) };
        while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
            loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
        }
        if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
            StringRef name { Lexer::getImmediateMacroName(
                loc, compiler.getSourceManager(), compiler.getLangOpts()) };
            if (k == FBK_sal_Bool && (name == "sal_False" || name == "sal_True")) {
                auto callLoc = compiler.getSourceManager()
                    .getImmediateMacroCallerLoc(loc);
                if (!isSharedCAndCppCode(callLoc)) {
                    SourceLocation argLoc;
                    if (compiler.getSourceManager().isMacroArgExpansion(
                            compat::getBeginLoc(expr), &argLoc)
                        //TODO: check it's the complete (first) arg to the macro
                        && (Lexer::getImmediateMacroName(
                                argLoc, compiler.getSourceManager(),
                                compiler.getLangOpts())
                            == "CPPUNIT_ASSERT_EQUAL"))
                    {
                        // Ignore sal_False/True that are directly used as
                        // arguments to CPPUNIT_ASSERT_EQUAL:
                        return true;
                    }
                    bool b = k == FBK_sal_Bool && name == "sal_True";
                    if (rewriter != nullptr) {
                        auto callSpellLoc = compiler.getSourceManager()
                            .getSpellingLoc(callLoc);
                        unsigned n = Lexer::MeasureTokenLength(
                            callSpellLoc, compiler.getSourceManager(),
                            compiler.getLangOpts());
                        if (StringRef(
                                compiler.getSourceManager().getCharacterData(
                                    callSpellLoc),
                                n)
                            == name)
                        {
                            return replaceText(
                                callSpellLoc, n, b ? "true" : "false");
                        }
                    }
                    report(
                        DiagnosticsEngine::Warning,
                        "use '%select{false|true}0' instead of '%1'", callLoc)
                        << b << name << expr->getSourceRange();
                }
                return true;
            }
        }
        report(
            DiagnosticsEngine::Warning,
            "CStyleCastExpr, suspicious cast from %0 to %1",
            compat::getBeginLoc(expr))
            << expr->getSubExpr()->IgnoreParenImpCasts()->getType()
            << expr->getType() << expr->getSourceRange();
    }
    return true;
}

bool FakeBool::VisitCXXStaticCastExpr(CXXStaticCastExpr * expr) {
    if (ignoreLocation(expr)) {
        return true;
    }
    auto const k = isFakeBool(expr->getType());
    if (k == FBK_No) {
        return true;
    }
    if (k == FBK_sal_Bool
        && isInSpecialMainFile(
            compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr))))
    {
        return true;
    }
    report(
        DiagnosticsEngine::Warning,
        "CXXStaticCastExpr, suspicious cast from %0 to %1",
        compat::getBeginLoc(expr))
        << expr->getSubExpr()->IgnoreParenImpCasts()->getType()
        << expr->getType() << expr->getSourceRange();
    return true;
}

bool FakeBool::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr) {
    if (ignoreLocation(expr)) {
        return true;
    }
    if (isFakeBool(expr->getType()) != FBK_No) {
        report(
            DiagnosticsEngine::Warning,
            "CXXFunctionalCastExpr, suspicious cast from %0 to %1",
            compat::getBeginLoc(expr))
            << expr->getSubExpr()->IgnoreParenImpCasts()->getType()
            << expr->getType() << expr->getSourceRange();
    }
    return true;
}

bool FakeBool::VisitImplicitCastExpr(ImplicitCastExpr * expr) {
    FunctionAddress::VisitImplicitCastExpr(expr);
    if (ignoreLocation(expr)) {
        return true;
    }
    auto const k = isFakeBool(expr->getType());
    if (k == FBK_No) {
        return true;
    }
    auto l = compat::getBeginLoc(expr);
    while (compiler.getSourceManager().isMacroArgExpansion(l)) {
        l = compiler.getSourceManager().getImmediateMacroCallerLoc(l);
    }
    if (compiler.getSourceManager().isMacroBodyExpansion(l)) {
        auto n = Lexer::getImmediateMacroName(
            l, compiler.getSourceManager(), compiler.getLangOpts());
        if ((k == FBK_GLboolean && (n == "GL_FALSE" || n == "GL_TRUE"))
            || (k == FBK_UBool && (n == "FALSE" || n == "TRUE"))
            || (k == FBK_jboolean && (n == "JNI_FALSE" || n == "JNI_TRUE"))
            || (k == FBK_sal_Bool && (n == "sal_False" || n == "sal_True")))
        {
            return true;
        }
    }
    auto e1 = expr->getSubExprAsWritten();
    auto t = e1->getType();
    if (!t->isFundamentalType() || loplugin::TypeCheck(t).AnyBoolean()) {
        return true;
    }
    auto e2 = dyn_cast<ConditionalOperator>(e1);
    if (e2 != nullptr) {
        auto ic1 = dyn_cast<ImplicitCastExpr>(
            e2->getTrueExpr()->IgnoreParens());
        auto ic2 = dyn_cast<ImplicitCastExpr>(
            e2->getFalseExpr()->IgnoreParens());
        if (ic1 != nullptr && ic2 != nullptr
            && ic1->getType()->isSpecificBuiltinType(BuiltinType::Int)
            && (loplugin::TypeCheck(ic1->getSubExprAsWritten()->getType())
                .AnyBoolean())
            && ic2->getType()->isSpecificBuiltinType(BuiltinType::Int)
            && (loplugin::TypeCheck(ic2->getSubExprAsWritten()->getType())
                .AnyBoolean()))
        {
            return true;
        }
    }
    report(
        DiagnosticsEngine::Warning, "conversion from %0 to %1",
        compat::getBeginLoc(expr))
        << t << expr->getType() << expr->getSourceRange();
    return true;
}

bool FakeBool::VisitReturnStmt(ReturnStmt const * stmt) {
    // Just enough to avoid warnings in rtl_getUriCharClass (sal/rtl/uri.cxx),
    // which has
    //
    //  static sal_Bool const aCharClass[][nCharClassSize] = ...;
    //
    // and
    //
    //  return aCharClass[eCharClass];
    //
    if (ignoreLocation(stmt)) {
        return true;
    }
    auto e = stmt->getRetValue();
    if (e == nullptr) {
        return true;
    }
    auto t = e->getType();
    if (!t->isPointerType()) {
        return true;
    }
    for (;;) {
        auto t2 = t->getAs<clang::PointerType>();
        if (t2 == nullptr) {
            break;
        }
        t = t2->getPointeeType();
    }
    if (isFakeBool(t) != FBK_sal_Bool) {
        return true;
    }
    auto e2 = dyn_cast<ArraySubscriptExpr>(e->IgnoreParenImpCasts());
    if (e2 == nullptr) {
        return true;
    }
    auto e3 = dyn_cast<DeclRefExpr>(e2->getBase()->IgnoreParenImpCasts());
    if (e3 == nullptr) {
        return true;
    }
    auto d = dyn_cast<VarDecl>(e3->getDecl());
    if (d == nullptr) {
        return true;
    }
    varDecls_.erase(d);
    return true;
}

bool FakeBool::WalkUpFromParmVarDecl(ParmVarDecl const * decl) {
    return VisitParmVarDecl(decl);
}

bool FakeBool::VisitParmVarDecl(ParmVarDecl const * decl) {
    if (ignoreLocation(decl)) {
        return true;
    }
    auto const fbk = isFakeBool(decl->getType().getNonReferenceType());
    if (fbk != FBK_No) {
        FunctionDecl const * f = dyn_cast<FunctionDecl>(decl->getDeclContext());
        if (f != nullptr) { // e.g.: typedef sal_Bool (* FuncPtr )( sal_Bool );
            f = f->getCanonicalDecl();
            if (isAllRelevantCodeDefined(f)
                && !(hasCLanguageLinkageType(f)
                     || (fbk == FBK_sal_Bool && isInUnoIncludeFile(f)
                         && (!f->isInlined() || f->hasAttr<DeprecatedAttr>()
                             || decl->getType()->isReferenceType()
                             || hasBoolOverload(f, false)))
                     || f->isDeleted() || hasBoolOverload(f, true)))
            {
                OverrideKind k = getOverrideKind(f);
                if (k != OverrideKind::YES) {
                    parmVarDecls_.insert({decl, fbk});
                }
            }
        }
    }
    return true;
}

bool FakeBool::WalkUpFromVarDecl(VarDecl const * decl) {
    return VisitVarDecl(decl);
}

bool FakeBool::VisitVarDecl(VarDecl const * decl) {
    if (ignoreLocation(decl)) {
        return true;
    }
    if (decl->isExternC()) {
        return true;
    }
    auto k = isFakeBool(decl->getType());
    if (k == FBK_No) {
        k = isFakeBoolArray(decl->getType());
    }
    if (k == FBK_No) {
        return true;
    }
    auto const loc = compat::getBeginLoc(decl);
    if (k == FBK_sal_Bool
        && isInSpecialMainFile(
            compiler.getSourceManager().getSpellingLoc(loc)))
    {
        return true;
    }
    auto l = loc;
    while (compiler.getSourceManager().isMacroArgExpansion(l)) {
        l = compiler.getSourceManager().getImmediateMacroCallerLoc(l);
    }
    if (compiler.getSourceManager().isMacroBodyExpansion(l)
        && isSharedCAndCppCode(compiler.getSourceManager().getImmediateMacroCallerLoc(l)))
    {
        return true;
    }
    varDecls_.insert({decl, k});
    return true;
}

bool FakeBool::WalkUpFromFieldDecl(FieldDecl const * decl) {
    return VisitFieldDecl(decl);
}

bool FakeBool::VisitFieldDecl(FieldDecl const * decl) {
    if (ignoreLocation(decl)) {
        return true;
    }
    auto k = isFakeBool(decl->getType());
    if (k == FBK_No) {
        k = isFakeBoolArray(decl->getType());
    }
    if (k == FBK_No) {
        return true;
    }
    if (!isAllRelevantCodeDefined(decl)) {
        return true;
    }
    if (k == FBK_sal_Bool
        && isInSpecialMainFile(
            compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(decl))))
    {
        return true;
    }
    TagDecl const * td = dyn_cast<TagDecl>(decl->getDeclContext());
    assert(td != nullptr);
    if (!(((td->isStruct() || td->isUnion()) && td->isExternCContext())
          || isInUnoIncludeFile(
              compiler.getSourceManager().getSpellingLoc(
                  decl->getLocation()))))
    {
        fieldDecls_.insert({decl, k});
    }
    return true;
}

bool FakeBool::WalkUpFromFunctionDecl(FunctionDecl const * decl) {
    return VisitFunctionDecl(decl);
}

bool FakeBool::VisitFunctionDecl(FunctionDecl const * decl) {
    if (ignoreLocation(decl)) {
        return true;
    }
    auto const fbk = isFakeBool(decl->getReturnType().getNonReferenceType());
    if (fbk != FBK_No
        && !(decl->isDeletedAsWritten() && isa<CXXConversionDecl>(decl))
        && isAllRelevantCodeDefined(decl))
    {
        FunctionDecl const * f = decl->getCanonicalDecl();
        OverrideKind k = getOverrideKind(f);
        if (k != OverrideKind::YES
            && !(hasCLanguageLinkageType(f)
                 || (isInUnoIncludeFile(f)
                     && (!f->isInlined() || f->hasAttr<DeprecatedAttr>()))))
        {
            functionDecls_.insert({decl, fbk});
        }
    }
    return true;
}

bool FakeBool::VisitValueDecl(ValueDecl const * decl) {
    if (ignoreLocation(decl)) {
        return true;
    }
    auto const k = isFakeBool(decl->getType());
    if (k != FBK_No && !rewrite(compat::getBeginLoc(decl), k)) {
        report(
            DiagnosticsEngine::Warning,
            "ValueDecl, use \"bool\" instead of %0",
            compat::getBeginLoc(decl))
            << decl->getType() << decl->getSourceRange();
    }
    return true;
}

bool FakeBool::TraverseStaticAssertDecl(StaticAssertDecl * decl) {
    // Ignore special code like
    //
    //   static_cast<sal_Bool>(true) == sal_True
    //
    // inside static_assert in cppu/source/uno/check.cxx:
    return
        loplugin::isSamePathname(
            getFilenameOfLocation(decl->getLocation()),
            SRCDIR "/cppu/source/uno/check.cxx")
        || RecursiveASTVisitor::TraverseStaticAssertDecl(decl);
}

bool FakeBool::TraverseLinkageSpecDecl(LinkageSpecDecl * decl) {
    assert(externCContexts_ != std::numeric_limits<unsigned int>::max()); //TODO
    ++externCContexts_;
    bool ret = RecursiveASTVisitor::TraverseLinkageSpecDecl(decl);
    assert(externCContexts_ != 0);
    --externCContexts_;
    return ret;
}

bool FakeBool::isFromCIncludeFile(SourceLocation spellingLocation) const {
    return !compiler.getSourceManager().isInMainFile(spellingLocation)
        && (StringRef(
                compiler.getSourceManager().getPresumedLoc(spellingLocation)
                .getFilename())
            .endswith(".h"));
}

bool FakeBool::isSharedCAndCppCode(SourceLocation location) const {
    // Assume that code is intended to be shared between C and C++ if it comes
    // from an include file ending in .h, and is either in an extern "C" context
    // or the body of a macro definition:
    return
        isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(location))
        && (externCContexts_ != 0
            || compiler.getSourceManager().isMacroBodyExpansion(location));
}

bool FakeBool::isInSpecialMainFile(SourceLocation spellingLocation) const {
    if (!compiler.getSourceManager().isInMainFile(spellingLocation)) {
        return false;
    }
    auto f = getFilenameOfLocation(spellingLocation);
    return loplugin::isSamePathname(f, SRCDIR "/cppu/qa/test_any.cxx")
        || loplugin::isSamePathname(f, SRCDIR "/cppu/source/uno/check.cxx");
        // TODO: the offset checks
}

bool FakeBool::rewrite(SourceLocation location, FakeBoolKind kind) {
    if (rewriter != nullptr) {
        //TODO: "::sal_Bool" -> "bool", not "::bool"
        SourceLocation loc { compiler.getSourceManager().getExpansionLoc(
                location) };
        unsigned n = Lexer::MeasureTokenLength(
            loc, compiler.getSourceManager(), compiler.getLangOpts());
        if (std::string(compiler.getSourceManager().getCharacterData(loc), n)
            == getName(kind))
        {
            return replaceText(loc, n, "bool");
        }
    }
    return false;
}

loplugin::Plugin::Registration<FakeBool> X("fakebool", true);

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
