/* -*- 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 #include #include #include #include #include "plugin.hxx" #include "clang/AST/CXXInheritance.h" // Check for local variables that we are calling delete on namespace { class MemoryVar: public RecursiveASTVisitor, 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 maVarUsesSet; std::set maVarNewSet; std::set maVarIgnoreSet; std::map maVarDeclSourceRangeMap; std::map 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(argumentExpr)) { argumentExpr = dyn_cast(argumentExpr)->getSubExpr(); } const DeclRefExpr* declRefExpr = dyn_cast(argumentExpr); if (!declRefExpr) return true; const Decl* decl = declRefExpr->getDecl(); if (!isa(decl) || isa(decl)) { return true; } const VarDecl * varDecl = dyn_cast(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(stmt); if (declStmt) { const VarDecl* varDecl = dyn_cast(declStmt->getSingleDecl()); if (varDecl) { varDecl = varDecl->getCanonicalDecl(); SourceLocation loc = varDecl->getLocation(); maVarNewSet.insert(loc); } return true; } const BinaryOperator* binaryOp = dyn_cast(stmt); if (binaryOp && binaryOp->getOpcode() == BO_Assign) { const DeclRefExpr* declRefExpr = dyn_cast(binaryOp->getLHS()); if (declRefExpr) { const VarDecl* varDecl = dyn_cast(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(expr)) { expr = dyn_cast(expr)->getSubExpr(); } const DeclRefExpr* declRefExpr = dyn_cast(expr); if (!declRefExpr) { return true; } const VarDecl* varDecl = dyn_cast(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(expr)) { expr = dyn_cast(expr)->getSubExpr(); } const DeclRefExpr* declRefExpr = dyn_cast(expr); if (!declRefExpr) { return true; } const VarDecl* varDecl = dyn_cast(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: */