Files
libreoffice/compilerplugins/clang/constvars.cxx
Stephan Bergmann 09aa5a9be8 Adapt to changed clang::ASTContext::getParents behavior on Clang 11 trunk
...since <https://github.com/llvm/llvm-project/commit/
d0da5d2bbe8305d06dc01a98706fd73e11e24a9f> "Change default traversal in AST
Matchers to ignore invisible nodes".  This caused failures

> [CPT] compilerplugins/clang/test/constparams.cxx
> ParmVarDecl 0x11d76c730 <compilerplugins/clang/test/constparams.cxx:15:12, col:18> col:18 used f1 'int *'
> DeclRefExpr 0x11d76c948 'int *' lvalue ParmVar 0x11d76c730 'f1' 'int *'
> ParmVarDecl 0x11d76cc80 <compilerplugins/clang/test/constparams.cxx:21:12, col:18> col:18 used f2 'int *'
> DeclRefExpr 0x11d76ce60 'int *' lvalue ParmVar 0x11d76cc80 'f2' 'int *'
> error: 'error' diagnostics expected but not seen:
>   File compilerplugins/clang/test/constparams.cxx Line 15: this parameter can be const Class1::Class1 [loplugin:constparams]
> error: 'error' diagnostics seen but not expected:
>   File compilerplugins/clang/test/constparams.cxx Line 15: no parent? [loplugin:constparams]
>   File compilerplugins/clang/test/constparams.cxx Line 21: no parent? [loplugin:constparams]
> 3 errors generated.
[...]
> [CPT] compilerplugins/clang/test/unusedenumconstants.cxx
> error: 'error' diagnostics expected but not seen:
>   File compilerplugins/clang/test/unusedenumconstants.cxx Line 30: read Bottom [loplugin:unusedenumconstants]
> error: 'error' diagnostics seen but not expected:
>   File compilerplugins/clang/test/unusedenumconstants.cxx Line 30: write Bottom [loplugin:unusedenumconstants]
> 2 errors generated.
[...]
> [CPT] compilerplugins/clang/test/unusedfields.cxx
> error: 'error' diagnostics expected but not seen:
>   File compilerplugins/clang/test/unusedfields.cxx Line 156 (directive at compilerplugins/clang/test/unusedfields.cxx:164): write m_f5 [loplugin:unusedfields]
>   File compilerplugins/clang/test/unusedfields.cxx Line 210 (directive at compilerplugins/clang/test/unusedfields.cxx:211): read m_f1 [loplugin:unusedfields]
> 2 errors generated.

For compilerplugins/clang/test/constparams.cxx at least it would have worked to
fix that locally with

> diff --git a/compilerplugins/clang/constparams.cxx b/compilerplugins/clang/constparams.cxx
> index 95c8184009d7..70f056fa5a69 100644
> --- a/compilerplugins/clang/constparams.cxx
> +++ b/compilerplugins/clang/constparams.cxx
> @@ -274,7 +274,7 @@ bool ConstParams::checkIfCanBeConst(const Stmt* stmt, const ParmVarDecl* parmVar
>              {
>                  for ( auto cxxCtorInitializer : cxxConstructorDecl->inits())
>                  {
> -                    if ( cxxCtorInitializer->getInit() == stmt)
> +                    if ( cxxCtorInitializer->getInit()->IgnoreImpCasts() == stmt)
>                      {
>                          if (cxxCtorInitializer->isAnyMemberInitializer())
>                          {

(somewhat unintuitively, given the Clang change is apparently about ignoring
more implicit nodes), but overall it appears better---at least for now---to use
a getParents variant that keeps the old traversal behavior.

For that, instead of using the clang::ASTContext::ParentMapCtx, we create our
own loplugin::Plugin::parentMapContext_.  There appear to be no uses of
ASTContext::getParent across the Clang codebase itself, outside of ASTMatcher
code, so it looks unlikely that creating our own ParentMapContext instance would
degrade performance by no longer sharing cached data between Clang's internals
and our plugin.  (And given that ASTContext::getParents is deprecated with the
note "New callers should use ParentMapContext::getParents() directly", this may
well be the correct way in the long run, anyway.)

Change-Id: I46c7912f2737e7c224fd45ab41441f69e2f10bd4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94795
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2020-05-25 20:40:25 +02:00

562 lines
19 KiB
C++

/* -*- 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: */