/* -*- 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/.
*/
/** 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;
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;
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", v->getBeginLoc());
}
}
bool ConstVars::VisitVarDecl(const VarDecl* varDecl)
{
varDecl = varDecl->getCanonicalDecl(); if (varDecl->getLocation().isValid() && ignoreLocation(varDecl)) returntrue; if (!varDecl->hasGlobalStorage()) returntrue; if (isa<ParmVarDecl>(varDecl)) returntrue; if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage) returntrue; if (varDecl->getType().isConstQualified()) returntrue; if (isa<ConstantArrayType>(varDecl->getType())) returntrue; if (loplugin::TypeCheck(varDecl->getType()).Pointer().Const()) returntrue; // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation()))) returntrue;
if (!varDecl->getInit()) returntrue; if (varDecl->getInit()->isInstantiationDependent()) returntrue; if (!varDecl->getInit()->isCXX11ConstantExpr(compiler.getASTContext())) returntrue;
definitionSet.insert(varDecl); returntrue;
}
bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt* forStmt)
{ if (forStmt->getBeginLoc().isValid() && ignoreLocation(forStmt)) returntrue; const VarDecl* varDecl = forStmt->getLoopVariable(); if (!varDecl) returntrue; // we don't handle structured assignment properly if (isa<DecompositionDecl>(varDecl)) returntrue; auto tc = loplugin::TypeCheck(varDecl->getType()); if (!tc.LvalueReference()) returntrue; if (tc.LvalueReference().Const()) returntrue;
definitionSet.insert(varDecl); returntrue;
}
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::TraverseIfStmt(IfStmt* ifStmt)
{
VarDecl const* varDecl = nullptr; if (Expr const* cond = ifStmt->getCond())
{ if (auto declRefExpr = dyn_cast<DeclRefExpr>(cond->IgnoreParenImpCasts()))
{ 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) returntrue;
varDecl = varDecl->getCanonicalDecl(); if (ignoreLocation(varDecl)) returntrue; // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation()))) returntrue;
if (definitionSet.find(varDecl) != definitionSet.end())
check(varDecl, declRefExpr);
// walk up the tree until we find something interesting
bool bCannotBeConst = false; bool bDump = false; auto walkUp = [&]() {
child = parent; auto parentsRange = compiler.getASTContext().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 = compiler.getASTContext().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 = compiler.getASTContext().getParents(*varDecl); if (parentsRange.begin() != parentsRange.end())
parent = parentsRange.begin()->get<Stmt>();
} elseif (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;
} elseif (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
|| isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
|| isa<ExprWithCleanups>(parent))
{
walkUp();
} elseif (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();
} elseif (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;
} elseif (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
{
bCannotBeConst = true;
}
} else
bCannotBeConst = true; // conservative, could improve
walkUp();
} elseif (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();
} elseif (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
{ if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
CalleeWrapper(cxxConstructExpr)))
bCannotBeConst = true;
walkUp();
} elseif (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();
} elseif (auto binaryOp = dyn_cast<BinaryOperator>(parent))
{
BinaryOperator::Opcode op = binaryOp->getOpcode(); constbool 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; elseif (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();
} elseif (isa<ReturnStmt>(parent))
{ if (insideFunctionDecl)
{ auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType()); if (tc.LvalueReference().NonConst())
bCannotBeConst = true;
} break;
} elseif (auto rangeStmt = dyn_cast<CXXForRangeStmt>(parent))
{ if (rangeStmt->getRangeStmt() == child)
{ auto tc = loplugin::TypeCheck(rangeStmt->getLoopVariable()->getType()); if (tc.LvalueReference().NonConst())
bCannotBeConst = true;
} break;
} elseif (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",
memberExpr->getBeginLoc())
<< bCannotBeConst << memberExpr->getSourceRange(); if (parent)
{
report(DiagnosticsEngine::Note, "parent over here", parent->getBeginLoc())
<< 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()) returntrue;
} 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()) returntrue;
}
} returnfalse;
}
// 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);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.