/*
    SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com
    SPDX-FileContributor: Sérgio Martins <sergio.martins@kdab.com>
    SPDX-FileCopyrightText: 2015-2016 Sergio Martins <smartins@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "Utils.h"
#include "HierarchyUtils.h"
#include "StmtBodyRange.h"
#include "StringUtils.h"
#include "clazy_stl.h"

#include <clang/AST/Decl.h>
#include <clang/AST/DeclBase.h>
#include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclGroup.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/AST/Expr.h>
#include <clang/AST/ExprCXX.h>
#include <clang/AST/OperationKinds.h>
#include <clang/AST/Stmt.h>
#include <clang/AST/StmtIterator.h>
#include <clang/AST/Type.h>
#include <clang/Basic/CharInfo.h>
#include <clang/Basic/IdentifierTable.h>
#include <clang/Basic/LLVM.h>
#include <clang/Basic/SourceLocation.h>
#include <clang/Basic/SourceManager.h>
#include <clang/Lex/Lexer.h>
#include <clang/Lex/Token.h>
#include <llvm/Support/Casting.h>

#include <cctype>
#include <iterator>
#include <utility>

namespace clang
{
class LangOptions;
} // namespace clang

using namespace clang;

bool Utils::hasConstexprCtor(CXXRecordDecl *decl)
{
    return std::ranges::any_of(decl->ctors(), [](CXXConstructorDecl *ctor) {
        return ctor->isConstexpr();
    });
}

CXXRecordDecl *Utils::namedCastInnerDecl(CXXNamedCastExpr *staticOrDynamicCast)
{
    Expr *e = staticOrDynamicCast->getSubExpr();
    if (!e) {
        return nullptr;
    }
    if (auto *implicitCast = dyn_cast<ImplicitCastExpr>(e)) {
        // Sometimes it's automatically cast to base
        if (implicitCast->getCastKind() == CK_DerivedToBase) {
            e = implicitCast->getSubExpr();
        }
    }

    QualType qt = e->getType();
    const Type *t = qt.getTypePtrOrNull();
    if (!t) {
        return nullptr;
    }
    QualType qt2 = t->getPointeeType();
    const Type *t2 = qt2.getTypePtrOrNull();
    if (!t2) {
        return nullptr;
    }
    return t2->getAsCXXRecordDecl();
}

CXXRecordDecl *Utils::namedCastOuterDecl(CXXNamedCastExpr *staticOrDynamicCast)
{
    QualType qt = staticOrDynamicCast->getTypeAsWritten();
    const Type *t = qt.getTypePtrOrNull();
    QualType qt2 = t->getPointeeType();
    const Type *t2 = qt2.getTypePtrOrNull();
    if (!t2) {
        return nullptr;
    }
    return t2->getAsCXXRecordDecl();
}

bool Utils::allChildrenMemberCallsConst(Stmt *stm)
{
    if (!stm) {
        return false;
    }

    auto *expr = dyn_cast<MemberExpr>(stm);

    if (expr) {
        auto *methodDecl = dyn_cast<CXXMethodDecl>(expr->getMemberDecl());
        if (methodDecl && !methodDecl->isConst()) {
            return false;
        }
    }

    const auto children = stm->children();
    return std::all_of(children.begin(), children.end(), [](Stmt *child) {
        return allChildrenMemberCallsConst(child);
    });
}

bool Utils::childsHaveSideEffects(Stmt *stm)
{
    if (!stm) {
        return false;
    }

    auto *unary = dyn_cast<UnaryOperator>(stm);
    if (unary && (unary->isIncrementOp() || unary->isDecrementOp())) {
        return true;
    }

    auto *binary = dyn_cast<BinaryOperator>(stm);
    if (binary && (binary->isAssignmentOp() || binary->isShiftAssignOp() || binary->isCompoundAssignmentOp())) {
        return true;
    }

    static const std::vector<StringRef> method_blacklist = {
        "isDestroyed",
        "isRecursive", // TODO: Use qualified name instead ?
        "q_func",
        "d_func",
        "begin",
        "end",
        "data",
        "fragment",
        "glIsRenderbuffer",
    };

    auto *memberCall = dyn_cast<MemberExpr>(stm);
    if (memberCall) {
        auto *methodDecl = dyn_cast<CXXMethodDecl>(memberCall->getMemberDecl());
        if (methodDecl && !methodDecl->isConst() && !methodDecl->isStatic() && !clazy::contains(method_blacklist, clazy::name(methodDecl))) {
            return true;
        }
    }

    /* // too many false positives, qIsFinite() etc for example
    auto callExpr = dyn_cast<CallExpr>(stm);
    if (callExpr) {
        FunctionDecl *callee = callExpr->getDirectCallee();
        if (callee && callee->isGlobal())
            return true;
    }*/

    return clazy::any_of(stm->children(), childsHaveSideEffects);
}

CXXRecordDecl *Utils::recordFromVarDecl(Decl *decl)
{
    auto *varDecl = dyn_cast<VarDecl>(decl);
    if (!varDecl) {
        return nullptr;
    }

    QualType qt = varDecl->getType();
    const Type *t = qt.getTypePtrOrNull();
    if (!t) {
        return nullptr;
    }

    return t->getAsCXXRecordDecl();
}

ClassTemplateSpecializationDecl *Utils::templateSpecializationFromVarDecl(Decl *decl)
{
    auto *record = recordFromVarDecl(decl);
    if (record) {
        return dyn_cast<ClassTemplateSpecializationDecl>(record);
    }

    return nullptr;
}

ValueDecl *Utils::valueDeclForMemberCall(CXXMemberCallExpr *memberCall)
{
    if (!memberCall) {
        return nullptr;
    }

    Expr *implicitObject = memberCall->getImplicitObjectArgument();
    if (!implicitObject) {
        return nullptr;
    }

    auto *declRefExpr = dyn_cast<DeclRefExpr>(implicitObject);
    auto *memberExpr = dyn_cast<MemberExpr>(implicitObject);
    if (declRefExpr) {
        return declRefExpr->getDecl();
    }
    if (memberExpr) {
        return memberExpr->getMemberDecl();
    }

    // Maybe there's an implicit cast in between..
    auto memberExprs = clazy::getStatements<MemberExpr>(implicitObject, nullptr, {}, /**depth=*/1, /*includeParent=*/true);
    auto declRefs = clazy::getStatements<DeclRefExpr>(implicitObject, nullptr, {}, /**depth=*/1, /*includeParent=*/true);

    if (!memberExprs.empty()) {
        return memberExprs.at(0)->getMemberDecl();
    }

    if (!declRefs.empty()) {
        return declRefs.at(0)->getDecl();
    }

    return nullptr;
}

ValueDecl *Utils::valueDeclForOperatorCall(CXXOperatorCallExpr *operatorCall)
{
    if (!operatorCall) {
        return nullptr;
    }

    // CXXOperatorCallExpr doesn't have API to access the value decl.
    // By inspecting several ASTs I noticed it's always in the 2nd child

    Stmt *child2 = clazy::childAt(operatorCall, 1);
    if (!child2) {
        return nullptr;
    }

    if (auto *memberExpr = dyn_cast<MemberExpr>(child2)) {
        return memberExpr->getMemberDecl();
    } else {
        std::vector<DeclRefExpr *> refs;
        clazy::getChilds<DeclRefExpr>(child2, refs);
        if (refs.size() == 1) {
            return refs[0]->getDecl();
        }
    }

    return nullptr;
}

clang::ValueDecl *Utils::valueDeclForCallExpr(clang::CallExpr *expr)
{
    if (auto *memberExpr = dyn_cast<CXXMemberCallExpr>(expr)) {
        return valueDeclForMemberCall(memberExpr);
    }
    if (auto *operatorExpr = dyn_cast<CXXOperatorCallExpr>(expr)) {
        return valueDeclForOperatorCall(operatorExpr);
    }

    return nullptr;
}

static bool referencesVar(Stmt *s, const VarDecl *varDecl)
{
    // look for a DeclRefExpr that references varDecl
    while (s) {
        auto it = s->child_begin();
        Stmt *child = it == s->child_end() ? nullptr : *it;
        if (auto *declRef = dyn_cast_or_null<DeclRefExpr>(child)) {
            if (declRef->getDecl() == varDecl) {
                return true;
            }
        }
        s = child;
    }

    return false;
}

bool Utils::containsNonConstMemberCall(clang::ParentMap * /*map*/, Stmt *body, const VarDecl *varDecl)
{
    if (!varDecl) {
        return false;
    }

    std::vector<CXXMemberCallExpr *> memberCallExprs;
    clazy::getChilds<CXXMemberCallExpr>(body, memberCallExprs);
    for (auto *memberCall : memberCallExprs) {
        CXXMethodDecl *methodDecl = memberCall->getMethodDecl();
        if (methodDecl && !methodDecl->isConst()) {
            const ValueDecl *valueDecl = Utils::valueDeclForMemberCall(memberCall);
            if (valueDecl == varDecl) {
                return true;
            }
        }
    }

    std::vector<CXXOperatorCallExpr *> operatorCalls;
    clazy::getChilds<CXXOperatorCallExpr>(body, operatorCalls);
    for (auto *operatorCall : operatorCalls) {
        FunctionDecl *fDecl = operatorCall->getDirectCallee();
        if (fDecl) {
            auto *methodDecl = dyn_cast<CXXMethodDecl>(fDecl);
            if (methodDecl && !methodDecl->isConst()) {
                const ValueDecl *valueDecl = Utils::valueDeclForOperatorCall(operatorCall);
                if (valueDecl == varDecl) {
                    return true;
                }
            }
        }
    }

    std::vector<BinaryOperator *> assignmentOperators;
    clazy::getChilds<BinaryOperator>(body, assignmentOperators);
    for (auto *op : assignmentOperators) {
        if (!op->isAssignmentOp()) {
            continue;
        }

        if (referencesVar(op, varDecl)) {
            return true;
        }
    }

    return false;
}

template<class T>
static bool isArgOfFunc(T expr, FunctionDecl *fDecl, const VarDecl *varDecl, bool byRefOrPtrOnly)
{
    unsigned int param = -1;
    for (auto arg : expr->arguments()) {
        ++param;
        auto refExpr = dyn_cast<DeclRefExpr>(arg);
        if (!refExpr) {
            if (clazy::hasChildren(arg)) {
                Stmt *firstChild = *(arg->child_begin()); // Can be null (bug #362236)
                refExpr = firstChild ? dyn_cast<DeclRefExpr>(firstChild) : nullptr;
                if (!refExpr) {
                    continue;
                }
            } else {
                continue;
            }
        }

        if (refExpr->getDecl() != varDecl) { // It's our variable ?
            continue;
        }

        if (!byRefOrPtrOnly) {
            // We found it
            return true;
        }

        // It is, lets see if the callee takes our variable by const-ref
        if (param >= fDecl->param_size()) {
            continue;
        }

        ParmVarDecl *paramDecl = fDecl->getParamDecl(param);
        if (!paramDecl) {
            continue;
        }

        QualType qt = paramDecl->getType();
        const clang::Type *t = qt.getTypePtrOrNull();
        if (!t) {
            continue;
        }

        if ((t->isReferenceType() || t->isPointerType()) && !t->getPointeeType().isConstQualified()) {
            return true; // function receives non-const ref, so our foreach variable can't be const-ref
        }
    }

    return false;
}

bool Utils::isPassedToFunction(const StmtBodyRange &bodyRange, const VarDecl *varDecl, bool byRefOrPtrOnly)
{
    if (!bodyRange.isValid()) {
        return false;
    }

    Stmt *body = bodyRange.body;
    std::vector<CallExpr *> callExprs;
    clazy::getChilds<CallExpr>(body, callExprs);
    for (CallExpr *callexpr : callExprs) {
        if (bodyRange.isOutsideRange(callexpr)) {
            continue;
        }

        FunctionDecl *fDecl = callexpr->getDirectCallee();
        if (!fDecl) {
            continue;
        }

        if (isArgOfFunc(callexpr, fDecl, varDecl, byRefOrPtrOnly)) {
            return true;
        }
    }

    std::vector<CXXConstructExpr *> constructExprs;
    clazy::getChilds<CXXConstructExpr>(body, constructExprs);
    for (CXXConstructExpr *constructExpr : constructExprs) {
        if (bodyRange.isOutsideRange(constructExpr)) {
            continue;
        }
        FunctionDecl *fDecl = constructExpr->getConstructor();
        if (isArgOfFunc(constructExpr, fDecl, varDecl, byRefOrPtrOnly)) {
            return true;
        }
    }

    return false;
}

bool Utils::addressIsTaken(Stmt *body, const clang::ValueDecl *valDecl)
{
    if (!body || !valDecl) {
        return false;
    }

    auto unaries = clazy::getStatements<UnaryOperator>(body);
    return std::ranges::any_of(unaries, [valDecl](UnaryOperator *op) {
        if (op->getOpcode() != clang::UO_AddrOf) {
            return false;
        }

        auto *declRef = clazy::getFirstChildOfType<DeclRefExpr>(op);
        return declRef && declRef->getDecl() == valDecl;
    });
}

bool Utils::isReturned(Stmt *body, const VarDecl *varDecl)
{
    if (!body) {
        return false;
    }

    std::vector<ReturnStmt *> returns;
    clazy::getChilds<ReturnStmt>(body, returns);
    for (ReturnStmt *returnStmt : returns) {
        Expr *retValue = returnStmt->getRetValue();
        if (!retValue) {
            continue;
        }
        auto *declRef = clazy::unpeal<DeclRefExpr>(retValue, clazy::IgnoreImplicitCasts);
        if (!declRef) {
            continue;
        }
        if (declRef->getDecl() == varDecl) {
            return true;
        }
    }

    return false;
}

bool Utils::isAssignedTo(Stmt *body, const VarDecl *varDecl)
{
    if (!body) {
        return false;
    }

    std::vector<BinaryOperator *> operatorCalls;
    clazy::getChilds<BinaryOperator>(body, operatorCalls);
    for (BinaryOperator *binaryOperator : operatorCalls) {
        if (binaryOperator->getOpcode() != clang::BO_Assign) {
            continue;
        }

        Expr *rhs = binaryOperator->getRHS();
        auto *declRef = clazy::unpeal<DeclRefExpr>(rhs, clazy::IgnoreImplicitCasts);
        if (!declRef) {
            continue;
        }

        if (declRef->getDecl() == varDecl) {
            return true;
        }
    }

    return false;
}

bool Utils::isAssignedFrom(Stmt *body, const VarDecl *varDecl)
{
    if (!body) {
        return false;
    }

    std::vector<CXXOperatorCallExpr *> operatorCalls;
    clazy::getChilds<CXXOperatorCallExpr>(body, operatorCalls);
    for (CXXOperatorCallExpr *operatorExpr : operatorCalls) {
        FunctionDecl *fDecl = operatorExpr->getDirectCallee();
        if (!fDecl) {
            continue;
        }

        auto *methodDecl = dyn_cast<CXXMethodDecl>(fDecl);
        if (methodDecl && methodDecl->isCopyAssignmentOperator()) {
            const ValueDecl *valueDecl = Utils::valueDeclForOperatorCall(operatorExpr);
            if (valueDecl == varDecl) {
                return true;
            }
        }
    }

    return false;
}

bool Utils::callHasDefaultArguments(clang::CallExpr *expr)
{
    std::vector<clang::CXXDefaultArgExpr *> exprs;
    clazy::getChilds<clang::CXXDefaultArgExpr>(expr, exprs, 1);
    return !exprs.empty();
}

bool Utils::containsStringLiteral(Stmt *stm, bool allowEmpty, int depth)
{
    if (!stm) {
        return false;
    }

    std::vector<StringLiteral *> stringLiterals;
    clazy::getChilds<StringLiteral>(stm, stringLiterals, depth);

    if (allowEmpty) {
        return !stringLiterals.empty();
    }

    for (StringLiteral *sl : stringLiterals) {
        if (sl->getLength() > 0) {
            return true;
        }
    }

    return false;
}

bool Utils::ternaryOperatorIsOfStringLiteral(ConditionalOperator *ternary)
{
    bool skipFirst = true;
    for (auto *child : ternary->children()) {
        if (skipFirst) {
            skipFirst = false;
            continue;
        }

        if (isa<StringLiteral>(child)) {
            continue;
        }

        auto *arrayToPointerDecay = dyn_cast<ImplicitCastExpr>(child);
        if (!arrayToPointerDecay || !isa<StringLiteral>(*(arrayToPointerDecay->child_begin()))) {
            return false;
        }
    }

    return true;
}

bool Utils::isAssignOperator(CXXOperatorCallExpr *op, StringRef className, StringRef argumentType, const clang::LangOptions &lo)
{
    if (!op) {
        return false;
    }

    FunctionDecl *functionDecl = op->getDirectCallee();
    if (!functionDecl || functionDecl->param_size() != 1) {
        return false;
    }

    if (!className.empty()) {
        auto *methodDecl = dyn_cast<clang::CXXMethodDecl>(functionDecl);
        if (!clazy::isOfClass(methodDecl, className)) {
            return false;
        }
    }

    if (functionDecl->getOverloadedOperator() != clang::OO_Equal) {
        return false;
    }

    if (!argumentType.empty() && !clazy::hasArgumentOfType(functionDecl, argumentType, lo)) {
        return false;
    }

    return true;
}

bool Utils::isImplicitCastTo(Stmt *s, const std::string &className)
{
    auto *expr = dyn_cast<ImplicitCastExpr>(s);
    if (!expr) {
        return false;
    }

    const auto *record = expr->getBestDynamicClassType();
    return record && clazy::name(record) == className;
}

bool Utils::isInsideOperatorCall(ParentMap *map, Stmt *s, const std::vector<StringRef> &anyOf)
{
    if (!s) {
        return false;
    }

    auto *oper = dyn_cast<CXXOperatorCallExpr>(s);
    if (oper) {
        auto *func = oper->getDirectCallee();
        if (func) {
            if (anyOf.empty()) {
                return true;
            }

            auto *method = dyn_cast<CXXMethodDecl>(func);
            if (method) {
                auto *record = method->getParent();
                if (record && clazy::contains(anyOf, clazy::name(record))) {
                    return true;
                }
            }
        }
    }

    return isInsideOperatorCall(map, clazy::parent(map, s), anyOf);
}

bool Utils::insideCTORCall(ParentMap *map, Stmt *s, const std::vector<llvm::StringRef> &anyOf)
{
    if (!s) {
        return false;
    }

    auto *expr = dyn_cast<CXXConstructExpr>(s);
    if (expr && expr->getConstructor() && clazy::contains(anyOf, clazy::name(expr->getConstructor()))) {
        return true;
    }

    return insideCTORCall(map, clazy::parent(map, s), anyOf);
}

bool Utils::presumedLocationsEqual(const clang::PresumedLoc &l1, const clang::PresumedLoc &l2)
{
    return l1.isValid() && l2.isValid() && l1.getColumn() == l2.getColumn() && l1.getLine() == l2.getLine()
        && StringRef(l1.getFilename()) == StringRef(l2.getFilename());
}

CXXRecordDecl *Utils::isMemberVariable(ValueDecl *decl)
{
    return decl ? dyn_cast<CXXRecordDecl>(decl->getDeclContext()) : nullptr;
}

std::vector<CXXMethodDecl *> Utils::methodsFromString(const CXXRecordDecl *record, const std::string &methodName)
{
    if (!record) {
        return {};
    }

    std::vector<CXXMethodDecl *> methods;
    std::ranges::copy_if(record->methods(), std::back_inserter(methods), [&methodName](CXXMethodDecl *method) {
        return clazy::name(method) == methodName;
    });

    // Also include the base classes
    for (auto base : record->bases()) {
        if (const Type *t = base.getType().getTypePtrOrNull()) {
            auto baseMethods = methodsFromString(t->getAsCXXRecordDecl(), methodName);
            std::ranges::copy(baseMethods, std::back_inserter(methods));
        }
    }

    return methods;
}

const CXXRecordDecl *Utils::recordForMemberCall(CXXMemberCallExpr *call, std::string &implicitCallee)
{
    implicitCallee.clear();
    Expr *implicitArgument = call->getImplicitObjectArgument();
    if (!implicitArgument) {
        return nullptr;
    }

    Stmt *s = implicitArgument;
    while (s) {
        if (auto *declRef = dyn_cast<DeclRefExpr>(s)) {
            if (declRef->getDecl()) {
                implicitCallee = declRef->getDecl()->getNameAsString();
                QualType qt = declRef->getDecl()->getType();
                return qt->getPointeeCXXRecordDecl();
            }
            return nullptr;

        } else if (auto *thisExpr = dyn_cast<CXXThisExpr>(s)) {
            implicitCallee = "this";
            return thisExpr->getType()->getPointeeCXXRecordDecl();
        } else if (auto *memberExpr = dyn_cast<MemberExpr>(s)) {
            auto *decl = memberExpr->getMemberDecl();
            if (decl) {
                implicitCallee = decl->getNameAsString();
                QualType qt = decl->getType();
                return qt->getPointeeCXXRecordDecl();
            }
            return nullptr;
        }

        s = s->child_begin() == s->child_end() ? nullptr : *(s->child_begin());
    }

    return nullptr;
}

bool Utils::isAscii(StringLiteral *lt)
{
    // 'é' for some reason has isAscii() == true, so also call containsNonAsciiOrNull
    return lt && lt->isOrdinary() && !lt->containsNonAsciiOrNull();
}

bool Utils::isInDerefExpression(Stmt *s, ParentMap *map)
{
    if (!s) {
        return false;
    }

    Stmt *p = s;
    do {
        p = clazy::parent(map, p);
        auto *op = p ? dyn_cast<CXXOperatorCallExpr>(p) : nullptr;
        if (op && op->getOperator() == OO_Star) {
            return op;
        }
    } while (p);

    return false;
}

std::vector<CallExpr *> Utils::callListForChain(CallExpr *lastCallExpr)
{
    if (!lastCallExpr) {
        return {};
    }

    const bool isOperator = isa<CXXOperatorCallExpr>(lastCallExpr);
    std::vector<CallExpr *> callexprs = {lastCallExpr};
    Stmt *s = lastCallExpr;
    do {
        const int childCount = std::distance(s->child_begin(), s->child_end());
        if (isOperator && childCount > 1 && s == lastCallExpr) {
            // for operator case, the chained call childs are in the second child
            s = *(++s->child_begin());
        } else {
            s = childCount > 0 ? *s->child_begin() : nullptr;
        }

        if (s) {
            auto *callExpr = dyn_cast<CallExpr>(s);
            if (callExpr && callExpr->getCalleeDecl()) {
                callexprs.push_back(callExpr);
            } else if (auto *memberExpr = dyn_cast<MemberExpr>(s)) {
                if (isa<FieldDecl>(memberExpr->getMemberDecl())) {
                    break; // accessing a public member via . or -> breaks the chain
                }
            } else if (isa<ConditionalOperator>(s)) {
                // Gets very greasy with conditional operators
                // This would match: (should() ? container1 : container2).append()
                // and it would return { append(), should()}
                break;
            }
        }
    } while (s);

    return callexprs;
}

CXXRecordDecl *Utils::rootBaseClass(CXXRecordDecl *derived)
{
    if (!derived || derived->getNumBases() == 0) {
        return derived;
    }

    CXXBaseSpecifier *base = derived->bases_begin();
    CXXRecordDecl *record = base->getType()->getAsCXXRecordDecl();

    return record ? rootBaseClass(record) : derived;
}

CXXConstructorDecl *Utils::copyCtor(const CXXRecordDecl *record)
{
    for (auto *ctor : record->ctors()) {
        if (ctor->isCopyConstructor()) {
            return ctor;
        }
    }

    return nullptr;
}

CXXMethodDecl *Utils::copyAssign(const CXXRecordDecl *record)
{
    for (auto *copyAssign : record->methods()) {
        if (copyAssign->isCopyAssignmentOperator()) {
            return copyAssign;
        }
    }

    return nullptr;
}

bool Utils::hasMember(CXXRecordDecl *record, const std::string &memberTypeName)
{
    if (!record) {
        return false;
    }

    for (auto *field : record->fields()) {
        field->getParent()->getNameAsString();
        QualType qt = field->getType();
        const Type *t = qt.getTypePtrOrNull();
        if (t && t->getAsCXXRecordDecl()) {
            const CXXRecordDecl *rec = t->getAsCXXRecordDecl();
            if (clazy::name(rec) == memberTypeName) {
                return true;
            }
        }
    }

    return false;
}

bool Utils::isInitializedExternally(clang::VarDecl *varDecl)
{
    if (!varDecl) {
        return false;
    }

    DeclContext *context = varDecl->getDeclContext();
    auto *fDecl = context ? dyn_cast<FunctionDecl>(context) : nullptr;
    Stmt *body = fDecl ? fDecl->getBody() : nullptr;
    if (!body) {
        return false;
    }

    std::vector<DeclStmt *> declStmts;
    clazy::getChilds<DeclStmt>(body, declStmts);
    for (DeclStmt *declStmt : declStmts) {
        if (referencesVarDecl(declStmt, varDecl)) {
            std::vector<DeclRefExpr *> declRefs;

            clazy::getChilds<DeclRefExpr>(declStmt, declRefs);
            if (!declRefs.empty()) {
                return true;
            }

            std::vector<CallExpr *> callExprs;
            clazy::getChilds<CallExpr>(declStmt, callExprs);
            if (!callExprs.empty()) {
                return true;
            }
        }
    }

    return false;
}

bool Utils::functionHasEmptyBody(clang::FunctionDecl *func)
{
    Stmt *body = func ? func->getBody() : nullptr;
    return !clazy::hasChildren(body);
}

clang::Expr *Utils::isWriteOperator(Stmt *stm)
{
    if (!stm) {
        return nullptr;
    }

    if (auto *up = dyn_cast<UnaryOperator>(stm)) {
        auto opcode = up->getOpcode();
        if (opcode == clang::UO_AddrOf || opcode == clang::UO_Deref) {
            return nullptr;
        }

        return up->getSubExpr();
    }

    if (auto *bp = dyn_cast<BinaryOperator>(stm)) {
        return bp->getLHS();
    }

    return nullptr;
}

bool Utils::referencesVarDecl(clang::DeclStmt *declStmt, clang::VarDecl *varDecl)
{
    if (!declStmt || !varDecl) {
        return false;
    }

    if (declStmt->isSingleDecl() && declStmt->getSingleDecl() == varDecl) {
        return true;
    }

    return std::ranges::any_of(declStmt->getDeclGroup(), [varDecl](Decl *decl) {
        return varDecl == decl;
    });
}

UserDefinedLiteral *Utils::userDefinedLiteral(Stmt *stm, const std::string &type, const clang::LangOptions &lo)
{
    auto *udl = dyn_cast<UserDefinedLiteral>(stm);
    if (!udl) {
        udl = clazy::getFirstChildOfType<UserDefinedLiteral>(stm);
    }

    if (udl && clazy::returnTypeName(udl, lo) == type) {
        return udl;
    }

    return nullptr;
}

clang::ArrayRef<clang::ParmVarDecl *> Utils::functionParameters(clang::FunctionDecl *func)
{
    return func->parameters();
}

std::vector<CXXCtorInitializer *> Utils::ctorInitializer(CXXConstructorDecl *ctor, clang::ParmVarDecl *param)
{
    if (!ctor) {
        return {};
    }

    std::vector<CXXCtorInitializer *> result;

    for (auto it = ctor->init_begin(), end = ctor->init_end(); it != end; ++it) {
        auto *ctorInit = *it;
        std::vector<DeclRefExpr *> declRefs;
        clazy::getChilds(ctorInit->getInit(), declRefs);
        for (auto *declRef : declRefs) {
            if (declRef->getDecl() == param) {
                result.push_back(ctorInit);
                break;
            }
        }
    }

    return result;
}

bool Utils::ctorInitializerContainsMove(CXXCtorInitializer *init)
{
    if (!init) {
        return false;
    }

    std::vector<CallExpr *> calls;
    clazy::getChilds(init->getInit(), calls);

    for (auto *call : calls) {
        if (FunctionDecl *funcDecl = call->getDirectCallee()) {
            auto name = funcDecl->getQualifiedNameAsString();
            if (name == "std::move" || name == "std::__1::move") {
                return true;
            }
        }
    }

    return false;
}

bool Utils::ctorInitializerContainsMove(const std::vector<CXXCtorInitializer *> &ctorInits)
{
    return std::ranges::any_of(ctorInits, [](CXXCtorInitializer *ctorInit) {
        return Utils::ctorInitializerContainsMove(ctorInit);
    });
}

std::string Utils::filenameForLoc(SourceLocation loc, const clang::SourceManager &sm)
{
    if (loc.isMacroID()) {
        loc = sm.getExpansionLoc(loc);
    }

    const std::string filename = sm.getFilename(loc).str();
    auto splitted = clazy::splitString(filename, '/');
    if (splitted.empty()) {
        return {};
    }

    return splitted[splitted.size() - 1];
}

SourceLocation Utils::locForNextToken(SourceLocation loc, const clang::SourceManager &sm, const clang::LangOptions &lo)
{
    std::pair<FileID, unsigned> locInfo = sm.getDecomposedLoc(loc);
    bool InvalidTemp = false;
    StringRef File = sm.getBufferData(locInfo.first, &InvalidTemp);
    if (InvalidTemp) {
        return {};
    }

    const char *TokenBegin = File.data() + locInfo.second;
    Lexer lexer(sm.getLocForStartOfFile(locInfo.first), lo, File.begin(), TokenBegin, File.end());

    Token Tok;
    lexer.LexFromRawLexer(Tok);

    SourceLocation TokenLoc = Tok.getLocation();

    // Calculate how much whitespace needs to be skipped if any.
    unsigned NumWhitespaceChars = 0;
    const char *TokenEnd = sm.getCharacterData(TokenLoc) + Tok.getLength();
    unsigned char C = *TokenEnd;
    while (isHorizontalWhitespace(C)) {
        C = *(++TokenEnd);
        NumWhitespaceChars++;
    }

    // Skip \r, \n, \r\n, or \n\r
    if (C == '\n' || C == '\r') {
        char PrevC = C;
        C = *(++TokenEnd);
        NumWhitespaceChars++;
        if ((C == '\n' || C == '\r') && C != PrevC) {
            NumWhitespaceChars++;
        }
    }

    return loc.getLocWithOffset(Tok.getLength() + NumWhitespaceChars);
}

bool Utils::literalContainsEscapedBytes(StringLiteral *lt, const SourceManager &sm, const LangOptions &lo)
{
    if (!lt) {
        return false;
    }

    // The AST doesn't have the info, we need to ask the Lexer
    SourceRange sr = lt->getSourceRange();
    CharSourceRange cr = Lexer::getAsCharRange(sr, sm, lo);
    const StringRef str = Lexer::getSourceText(cr, sm, lo);

    for (int i = 0, size = str.size(); i < size - 1; ++i) {
        if (str[i] == '\\') {
            auto next = str[i + 1];
            if (next == 'U' || next == 'u' || next == 'x' || std::isdigit(next)) {
                return true;
            }
        }
    }

    return false;
}
