/* -*- 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 <memory>
#include <string>
#include <iostream>
#include <map>
#include <set>

#include "plugin.hxx"
#include "clang/AST/CXXInheritance.h"

// Check for local variables that we are calling delete on

namespace
{

class MemoryVar:
    public RecursiveASTVisitor<MemoryVar>, public loplugin::Plugin
{
public:
    explicit MemoryVar(loplugin::InstantiationData const & data): Plugin(data), mbChecking(false) {}

    virtual void run() override {
        TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
    }

    bool TraverseFunctionDecl(FunctionDecl*);
    bool VisitCXXDeleteExpr(const CXXDeleteExpr*);
    bool VisitCXXNewExpr(const CXXNewExpr* );
    bool VisitBinaryOperator(const BinaryOperator*);
    bool VisitReturnStmt(const ReturnStmt*);

private:
    bool mbChecking;
    std::set<SourceLocation> maVarUsesSet;
    std::set<SourceLocation> maVarNewSet;
    std::set<SourceLocation> maVarIgnoreSet;
    std::map<SourceLocation,SourceRange> maVarDeclSourceRangeMap;
    std::map<SourceLocation,SourceRange> maVarDeleteSourceRangeMap;
    StringRef getFilename(SourceLocation loc);
};

StringRef MemoryVar::getFilename(SourceLocation loc)
{
    SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(loc);
    StringRef name { compiler.getSourceManager().getFilename(spellingLocation) };
    return name;
}

bool MemoryVar::TraverseFunctionDecl(FunctionDecl * decl)
{
    if (ignoreLocation(decl)) {
        return true;
    }
    if (!decl->hasBody() || !decl->isThisDeclarationADefinition()) {
        return true;
    }

    maVarUsesSet.clear();
    maVarNewSet.clear();
    maVarIgnoreSet.clear();
    maVarDeclSourceRangeMap.clear();
    maVarDeleteSourceRangeMap.clear();

    assert(!mbChecking);
    mbChecking = true;
    TraverseStmt(decl->getBody());
    mbChecking = false;

    for (const auto& varLoc : maVarUsesSet)
    {
        // checking the location of the var instead of the function because for some reason
        // I'm not getting accurate results from clang right now
        StringRef aFileName = getFilename(varLoc);
        // TODO these files are doing some weird stuff I don't know how to ignore yet
        if (loplugin::hasPathnamePrefix(aFileName, SRCDIR "/vcl/source/filter/")) {
           return true;
        }
        if (loplugin::isSamePathname(aFileName, SRCDIR "/sw/source/core/layout/frmtool.cxx")) {
           return true;
        }


        if (maVarNewSet.find(varLoc) == maVarNewSet.end())
            continue;
        if (maVarIgnoreSet.find(varLoc) != maVarIgnoreSet.end())
            continue;

        report(DiagnosticsEngine::Warning,
                "calling new and delete on a local var, rather use std::unique_ptr",
               varLoc)
            << maVarDeclSourceRangeMap[varLoc];
        report(DiagnosticsEngine::Note,
               "delete called here",
               maVarDeleteSourceRangeMap[varLoc].getBegin())
            << maVarDeleteSourceRangeMap[varLoc];
    }
    return true;
}

bool MemoryVar::VisitCXXDeleteExpr(const CXXDeleteExpr *deleteExpr)
{
    if (!mbChecking)
        return true;
    if (ignoreLocation(deleteExpr)) {
        return true;
    }
    const Expr* argumentExpr = deleteExpr->getArgument();
    if (isa<CastExpr>(argumentExpr)) {
        argumentExpr = dyn_cast<CastExpr>(argumentExpr)->getSubExpr();
    }
    const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(argumentExpr);
    if (!declRefExpr)
        return true;
    const Decl* decl = declRefExpr->getDecl();
    if (!isa<VarDecl>(decl) || isa<ParmVarDecl>(decl)) {
        return true;
    }
    const VarDecl * varDecl = dyn_cast<VarDecl>(decl)->getCanonicalDecl();
    if (varDecl->hasGlobalStorage()) {
        return true;
    }

    SourceLocation loc = varDecl->getLocation();

    if (maVarUsesSet.find(loc) == maVarUsesSet.end()) {
        maVarUsesSet.insert(loc);
        maVarDeclSourceRangeMap[loc] = varDecl->getSourceRange();
        maVarDeleteSourceRangeMap[loc] = declRefExpr->getSourceRange();
    }
    return true;
}

bool MemoryVar::VisitCXXNewExpr(const CXXNewExpr *newExpr)
{
    if (!mbChecking)
        return true;
    if (ignoreLocation(newExpr)) {
        return true;
    }
    const Stmt* stmt = getParentStmt(newExpr);

    const DeclStmt* declStmt = dyn_cast<DeclStmt>(stmt);
    if (declStmt) {
        const VarDecl* varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
        if (varDecl) {
            varDecl = varDecl->getCanonicalDecl();
            SourceLocation loc = varDecl->getLocation();
            maVarNewSet.insert(loc);
        }
        return true;
    }

    const BinaryOperator* binaryOp = dyn_cast<BinaryOperator>(stmt);
    if (binaryOp && binaryOp->getOpcode() == BO_Assign) {
        const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(binaryOp->getLHS());
        if (declRefExpr) {
            const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
            if (varDecl) {
                varDecl = varDecl->getCanonicalDecl();
                SourceLocation loc = varDecl->getLocation();
                maVarNewSet.insert(loc);
            }
        }
    }
    return true;
}

// Ignore cases where the variable in question is assigned to another variable
bool MemoryVar::VisitBinaryOperator(const BinaryOperator *binaryOp)
{
    if (!mbChecking)
        return true;
    if (ignoreLocation(binaryOp)) {
        return true;
    }
    if (binaryOp->getOpcode() != BO_Assign) {
        return true;
    }
    const Expr* expr = binaryOp->getRHS();
    // unwrap casts
    while (isa<CastExpr>(expr)) {
        expr = dyn_cast<CastExpr>(expr)->getSubExpr();
    }
    const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(expr);
    if (!declRefExpr) {
        return true;
    }
    const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
    if (!varDecl) {
        return true;
    }
    varDecl = varDecl->getCanonicalDecl();
    maVarIgnoreSet.insert(varDecl->getLocation());
    return true;
}

// Ignore cases where the variable in question is returned from a function
bool MemoryVar::VisitReturnStmt(const ReturnStmt *returnStmt)
{
    if (!mbChecking)
        return true;
    if (ignoreLocation(returnStmt)) {
        return true;
    }
    const Expr* expr = returnStmt->getRetValue();
    if (!expr) {
        return true;
    }
    // unwrap casts
    while (isa<CastExpr>(expr)) {
        expr = dyn_cast<CastExpr>(expr)->getSubExpr();
    }
    const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(expr);
    if (!declRefExpr) {
        return true;
    }
    const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
    if (!varDecl) {
        return true;
    }
    varDecl = varDecl->getCanonicalDecl();
    maVarIgnoreSet.insert(varDecl->getLocation());
    return true;
}

loplugin::Plugin::Registration< MemoryVar > X("memoryvar", false);

}

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