diff options
Diffstat (limited to '')
-rw-r--r-- | compilerplugins/clang/constvars.cxx | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/compilerplugins/clang/constvars.cxx b/compilerplugins/clang/constvars.cxx new file mode 100644 index 000000000..3c78bd458 --- /dev/null +++ b/compilerplugins/clang/constvars.cxx @@ -0,0 +1,561 @@ +/* -*- 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/. + */ + +#if !defined _WIN32 //TODO, #include <sys/file.h> + +#include <cassert> +#include <string> +#include <iostream> +#include <fstream> +#include <unordered_set> +#include <vector> +#include <algorithm> +#include <sys/file.h> +#include <unistd.h> + +#include "config_clang.h" + +#include "plugin.hxx" +#include "compat.hxx" +#include "check.hxx" + +#if CLANG_VERSION >= 110000 +#include "clang/AST/ParentMapContext.h" +#endif + +/** +Look for static vars that are only assigned to once, and never written to, they can be const. +*/ + +namespace +{ +/** + * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything. + */ +class CallerWrapper +{ + const CallExpr* m_callExpr; + const CXXConstructExpr* m_cxxConstructExpr; + +public: + CallerWrapper(const CallExpr* callExpr) + : m_callExpr(callExpr) + , m_cxxConstructExpr(nullptr) + { + } + CallerWrapper(const CXXConstructExpr* cxxConstructExpr) + : m_callExpr(nullptr) + , m_cxxConstructExpr(cxxConstructExpr) + { + } + unsigned getNumArgs() const + { + return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs(); + } + const Expr* getArg(unsigned i) const + { + return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i); + } +}; +class CalleeWrapper +{ + const FunctionDecl* m_calleeFunctionDecl = nullptr; + const CXXConstructorDecl* m_cxxConstructorDecl = nullptr; + const FunctionProtoType* m_functionPrototype = nullptr; + +public: + explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl) + : m_calleeFunctionDecl(calleeFunctionDecl) + { + } + explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr) + : m_cxxConstructorDecl(cxxConstructExpr->getConstructor()) + { + } + explicit CalleeWrapper(const FunctionProtoType* functionPrototype) + : m_functionPrototype(functionPrototype) + { + } + unsigned getNumParams() const + { + if (m_calleeFunctionDecl) + return m_calleeFunctionDecl->getNumParams(); + else if (m_cxxConstructorDecl) + return m_cxxConstructorDecl->getNumParams(); + else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end()) + // FunctionProtoType will assert if we call getParamTypes() and it has no params + return 0; + else + return m_functionPrototype->getParamTypes().size(); + } + const QualType getParamType(unsigned i) const + { + if (m_calleeFunctionDecl) + return m_calleeFunctionDecl->getParamDecl(i)->getType(); + else if (m_cxxConstructorDecl) + return m_cxxConstructorDecl->getParamDecl(i)->getType(); + else + return m_functionPrototype->getParamTypes()[i]; + } + std::string getNameAsString() const + { + if (m_calleeFunctionDecl) + return m_calleeFunctionDecl->getNameAsString(); + else if (m_cxxConstructorDecl) + return m_cxxConstructorDecl->getNameAsString(); + else + return ""; + } + CXXMethodDecl const* getAsCXXMethodDecl() const + { + if (m_calleeFunctionDecl) + return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl); + return nullptr; + } +}; + +class ConstVars : public RecursiveASTVisitor<ConstVars>, public loplugin::Plugin +{ +public: + explicit ConstVars(loplugin::InstantiationData const& data) + : Plugin(data) + { + } + + virtual void run() override; + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } + + bool VisitVarDecl(const VarDecl*); + bool VisitCXXForRangeStmt(const CXXForRangeStmt*); + bool VisitDeclRefExpr(const DeclRefExpr*); + bool TraverseCXXConstructorDecl(CXXConstructorDecl*); + bool TraverseCXXMethodDecl(CXXMethodDecl*); + bool TraverseFunctionDecl(FunctionDecl*); + bool TraverseIfStmt(IfStmt*); + +private: + void check(const VarDecl* varDecl, const Expr* memberExpr); + bool isSomeKindOfZero(const Expr* arg); + bool IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child, CallerWrapper callExpr, + CalleeWrapper calleeFunctionDecl); + llvm::Optional<CalleeWrapper> getCallee(CallExpr const*); + + RecordDecl* insideMoveOrCopyDeclParent = nullptr; + // For reasons I do not understand, parentFunctionDecl() is not reliable, so + // we store the parent function on the way down the AST. + FunctionDecl* insideFunctionDecl = nullptr; + std::vector<VarDecl const*> insideConditionalCheckOfVarSet; + + std::set<VarDecl const*> cannotBeConstSet; + std::set<VarDecl const*> definitionSet; +}; + +void ConstVars::run() +{ + // clang::Expr::isCXX11ConstantExpr only works for C++ + if (!compiler.getLangOpts().CPlusPlus) + return; + + TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); + + SourceManager& SM = compiler.getSourceManager(); + for (VarDecl const* v : definitionSet) + { + if (cannotBeConstSet.find(v) != cannotBeConstSet.end()) + continue; + llvm::StringRef sourceString(SM.getCharacterData(v->getSourceRange().getEnd()), 50); + // Implement a marker that disables this plugins warning at a specific site + if (sourceString.contains("loplugin:constvars:ignore")) + continue; + report(DiagnosticsEngine::Warning, "var can be const", compat::getBeginLoc(v)); + } +} + +bool ConstVars::VisitVarDecl(const VarDecl* varDecl) +{ + varDecl = varDecl->getCanonicalDecl(); + if (varDecl->getLocation().isValid() && ignoreLocation(varDecl)) + return true; + if (!varDecl->hasGlobalStorage()) + return true; + if (isa<ParmVarDecl>(varDecl)) + return true; + if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage) + return true; + if (varDecl->getType().isConstQualified()) + return true; + if (isa<ConstantArrayType>(varDecl->getType())) + return true; + if (loplugin::TypeCheck(varDecl->getType()).Pointer().Const()) + return true; + // ignore stuff that forms part of the stable URE interface + if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation()))) + return true; + + if (!varDecl->getInit()) + return true; + if (varDecl->getInit()->isInstantiationDependent()) + return true; + if (!varDecl->getInit()->isCXX11ConstantExpr(compiler.getASTContext())) + return true; + + definitionSet.insert(varDecl); + return true; +} + +bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt* forStmt) +{ + if (compat::getBeginLoc(forStmt).isValid() && ignoreLocation(forStmt)) + return true; + const VarDecl* varDecl = forStmt->getLoopVariable(); + if (!varDecl) + return true; + // we don't handle structured assignment properly + if (isa<DecompositionDecl>(varDecl)) + return true; + auto tc = loplugin::TypeCheck(varDecl->getType()); + if (!tc.LvalueReference()) + return true; + if (tc.LvalueReference().Const()) + return true; + + definitionSet.insert(varDecl); + return true; +} + +bool ConstVars::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl) +{ + auto copy = insideMoveOrCopyDeclParent; + if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition()) + { + if (cxxConstructorDecl->isCopyOrMoveConstructor()) + insideMoveOrCopyDeclParent = cxxConstructorDecl->getParent(); + } + bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl); + insideMoveOrCopyDeclParent = copy; + return ret; +} + +bool ConstVars::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl) +{ + auto copy1 = insideMoveOrCopyDeclParent; + auto copy2 = insideFunctionDecl; + if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition()) + { + if (cxxMethodDecl->isCopyAssignmentOperator() || cxxMethodDecl->isMoveAssignmentOperator()) + insideMoveOrCopyDeclParent = cxxMethodDecl->getParent(); + } + insideFunctionDecl = cxxMethodDecl; + bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl); + insideMoveOrCopyDeclParent = copy1; + insideFunctionDecl = copy2; + return ret; +} + +bool ConstVars::TraverseFunctionDecl(FunctionDecl* functionDecl) +{ + auto copy2 = insideFunctionDecl; + insideFunctionDecl = functionDecl; + bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl); + insideFunctionDecl = copy2; + return ret; +} + +bool ConstVars::TraverseIfStmt(IfStmt* ifStmt) +{ + VarDecl const* varDecl = nullptr; + Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts(); + if (auto declRefExpr = dyn_cast<DeclRefExpr>(cond)) + { + if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl()))) + insideConditionalCheckOfVarSet.push_back(varDecl); + } + bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt); + if (varDecl) + insideConditionalCheckOfVarSet.pop_back(); + return ret; +} + +bool ConstVars::VisitDeclRefExpr(const DeclRefExpr* declRefExpr) +{ + const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl()); + if (!varDecl) + return true; + varDecl = varDecl->getCanonicalDecl(); + if (ignoreLocation(varDecl)) + return true; + // ignore stuff that forms part of the stable URE interface + if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation()))) + return true; + + if (definitionSet.find(varDecl) != definitionSet.end()) + check(varDecl, declRefExpr); + + return true; +} + +void ConstVars::check(const VarDecl* varDecl, const Expr* memberExpr) +{ + auto parentsRange = getParents(*memberExpr); + const Stmt* child = memberExpr; + const Stmt* parent + = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>(); + + // walk up the tree until we find something interesting + + bool bCannotBeConst = false; + bool bDump = false; + auto walkUp = [&]() { + child = parent; + auto parentsRange = getParents(*parent); + parent = parentsRange.begin() == parentsRange.end() ? nullptr + : parentsRange.begin()->get<Stmt>(); + }; + do + { + if (!parent) + { + // check if we have an expression like + // int& r = var; + auto parentsRange = getParents(*child); + if (parentsRange.begin() != parentsRange.end()) + { + auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>()); + if (varDecl) + { + if (varDecl->isImplicit()) + { + // so we can walk up from inside a for-range stmt + parentsRange = getParents(*varDecl); + if (parentsRange.begin() != parentsRange.end()) + parent = parentsRange.begin()->get<Stmt>(); + } + else if (loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst()) + { + bCannotBeConst = true; + break; + } + } + } + } + if (!parent) + break; + if (isa<CXXReinterpretCastExpr>(parent)) + { + // once we see one of these, there is not much useful we can know + bCannotBeConst = true; + break; + } + else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) + || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent) + || isa<ExprWithCleanups>(parent)) + { + walkUp(); + } + else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent)) + { + UnaryOperator::Opcode op = unaryOperator->getOpcode(); + if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc + || op == UO_PreDec) + { + bCannotBeConst = true; + } + walkUp(); + } + else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent)) + { + auto callee = getCallee(operatorCallExpr); + if (callee) + { + // if calling a non-const operator on the var + auto calleeMethodDecl = callee->getAsCXXMethodDecl(); + if (calleeMethodDecl && operatorCallExpr->getArg(0) == child + && !calleeMethodDecl->isConst()) + { + bCannotBeConst = true; + } + else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee)) + { + bCannotBeConst = true; + } + } + else + bCannotBeConst = true; // conservative, could improve + walkUp(); + } + else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent)) + { + const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl(); + if (calleeMethodDecl) + { + // if calling a non-const method on the var + const Expr* tmp = dyn_cast<Expr>(child); + if (tmp->isBoundMemberFunction(compiler.getASTContext())) + { + tmp = dyn_cast<MemberExpr>(tmp)->getBase(); + } + if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp + && !calleeMethodDecl->isConst()) + { + bCannotBeConst = true; + break; + } + if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr, + CalleeWrapper(calleeMethodDecl))) + bCannotBeConst = true; + } + else + bCannotBeConst = true; // can happen in templates + walkUp(); + } + else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent)) + { + if (IsPassedByNonConst(varDecl, child, cxxConstructExpr, + CalleeWrapper(cxxConstructExpr))) + bCannotBeConst = true; + walkUp(); + } + else if (auto callExpr = dyn_cast<CallExpr>(parent)) + { + auto callee = getCallee(callExpr); + if (callee) + { + if (IsPassedByNonConst(varDecl, child, callExpr, *callee)) + bCannotBeConst = true; + } + else + bCannotBeConst = true; // conservative, could improve + walkUp(); + } + else if (auto binaryOp = dyn_cast<BinaryOperator>(parent)) + { + BinaryOperator::Opcode op = binaryOp->getOpcode(); + const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign + || op == BO_RemAssign || op == BO_AddAssign + || op == BO_SubAssign || op == BO_ShlAssign + || op == BO_ShrAssign || op == BO_AndAssign + || op == BO_XorAssign || op == BO_OrAssign; + if (assignmentOp) + { + if (binaryOp->getLHS() == child) + bCannotBeConst = true; + else if (loplugin::TypeCheck(binaryOp->getLHS()->getType()) + .LvalueReference() + .NonConst()) + // if the LHS is a non-const reference, we could write to the var later on + bCannotBeConst = true; + } + walkUp(); + } + else if (isa<ReturnStmt>(parent)) + { + if (insideFunctionDecl) + { + auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType()); + if (tc.LvalueReference().NonConst()) + bCannotBeConst = true; + } + break; + } + else if (auto rangeStmt = dyn_cast<CXXForRangeStmt>(parent)) + { + if (rangeStmt->getRangeStmt() == child) + { + auto tc = loplugin::TypeCheck(rangeStmt->getLoopVariable()->getType()); + if (tc.LvalueReference().NonConst()) + bCannotBeConst = true; + } + break; + } + else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent) + || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<DefaultStmt>(parent)) + { + break; + } + else + { + walkUp(); + } + } while (true); + + if (bDump) + { + report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0", + compat::getBeginLoc(memberExpr)) + << bCannotBeConst << memberExpr->getSourceRange(); + if (parent) + { + report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent)) + << parent->getSourceRange(); + parent->dump(); + } + memberExpr->dump(); + varDecl->getType()->dump(); + } + + if (bCannotBeConst) + cannotBeConstSet.insert(varDecl); +} + +bool ConstVars::IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child, + CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl) +{ + unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams()); + // if it's an array, passing it by value to a method typically means the + // callee takes a pointer and can modify the array + if (varDecl->getType()->isConstantArrayType()) + { + for (unsigned i = 0; i < len; ++i) + if (callExpr.getArg(i) == child) + if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst()) + return true; + } + else + { + for (unsigned i = 0; i < len; ++i) + if (callExpr.getArg(i) == child) + { + auto tc = loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)); + if (tc.LvalueReference().NonConst() || tc.Pointer().NonConst()) + return true; + } + } + return false; +} + +llvm::Optional<CalleeWrapper> ConstVars::getCallee(CallExpr const* callExpr) +{ + FunctionDecl const* functionDecl = callExpr->getDirectCallee(); + if (functionDecl) + return CalleeWrapper(functionDecl); + + // Extract the functionprototype from a type + clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr(); + if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>()) + { + if (auto prototype = pointerType->getPointeeType() + ->getUnqualifiedDesugaredType() + ->getAs<FunctionProtoType>()) + { + return CalleeWrapper(prototype); + } + } + + return llvm::Optional<CalleeWrapper>(); +} + +/** off by default because it is very expensive, it walks up the AST a lot */ +loplugin::Plugin::Registration<ConstVars> X("constvars", false); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |