/* -*- 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 fields that are (a) only assigned to in the constructor using field-init, and can therefore be const. (b) protected via a mutex guard when accessed
Which normally means we can remove the mutex guard.
The process goes something like this: $ make check $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='locking2' check $ ./compilerplugins/clang/locking2.py
*/
// try to limit the voluminous output a little static std::set<MyFieldInfo> cannotBeConstSet; static std::set<MyFieldInfo> definitionSet; static std::set<MyLockedInfo> lockedAtSet;
/** * 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<FieldDecl const*> insideConditionalCheckOfMemberSet;
aInfo.fieldName = fieldDecl->getNameAsString(); // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
size_t idx = aInfo.fieldName.find(SRCDIR); if (idx != std::string::npos)
{
aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
}
aInfo.fieldType = fieldDecl->getType().getAsString();
aInfo.fieldName = fieldDecl->getNameAsString(); // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
size_t idx = aInfo.fieldName.find(SRCDIR); if (idx != std::string::npos)
{
aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
}
bool Locking2::VisitFieldDecl(const FieldDecl* fieldDecl)
{
fieldDecl = fieldDecl->getCanonicalDecl(); if (ignoreLocation(fieldDecl))
{ returntrue;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
{ returntrue;
}
definitionSet.insert(niceName(fieldDecl)); returntrue;
}
bool Locking2::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 Locking2::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 Locking2::TraverseIfStmt(IfStmt* ifStmt)
{
FieldDecl const* memberFieldDecl = nullptr; if (Expr const* cond = ifStmt->getCond())
{ if (auto memberExpr = dyn_cast<MemberExpr>(cond->IgnoreParenImpCasts()))
{ if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
}
} bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt); if (memberFieldDecl)
insideConditionalCheckOfMemberSet.pop_back(); return ret;
}
bool Locking2::VisitMemberExpr(const MemberExpr* memberExpr)
{ const ValueDecl* decl = memberExpr->getMemberDecl(); const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl); if (!fieldDecl)
{ returntrue;
}
fieldDecl = fieldDecl->getCanonicalDecl(); if (ignoreLocation(fieldDecl))
{ returntrue;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
{ returntrue;
}
check(fieldDecl, memberExpr);
returntrue;
}
void Locking2::check(const FieldDecl* fieldDecl, const Expr* memberExpr)
{ auto parentsRange = compiler.getASTContext().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 = 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 = m_field; auto parentsRange = compiler.getASTContext().getParents(*child); if (parentsRange.begin() != parentsRange.end())
{ auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>()); // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement, // which is of type 'T&&' and also an l-value-ref ? if (varDecl && !varDecl->isImplicit()
&& loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
{
bCannotBeConst = true;
}
} 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 field auto calleeMethodDecl = callee->getAsCXXMethodDecl(); if (calleeMethodDecl && operatorCallExpr->getArg(0) == child
&& !calleeMethodDecl->isConst())
{
bCannotBeConst = true;
} elseif (IsPassedByNonConst(fieldDecl, 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 field 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(fieldDecl, child, cxxMemberCallExpr,
CalleeWrapper(calleeMethodDecl)))
bCannotBeConst = true;
} else
bCannotBeConst = true; // can happen in templates
walkUp();
} elseif (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
{ if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr,
CalleeWrapper(cxxConstructExpr)))
bCannotBeConst = true;
walkUp();
} elseif (auto callExpr = dyn_cast<CallExpr>(parent))
{ auto callee = getCallee(callExpr); if (callee)
{ if (IsPassedByNonConst(fieldDecl, 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 field 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 (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
|| isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<CXXForRangeStmt>(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();
fieldDecl->getType()->dump();
}
if (bCannotBeConst)
{
cannotBeConstSet.insert(niceName(fieldDecl));
}
}
bool Locking2::IsPassedByNonConst(const FieldDecl* fieldDecl, 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 (fieldDecl->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) if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
.LvalueReference()
.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 compat::optional<CalleeWrapper>();
}
bool Locking2::VisitCompoundStmt(const CompoundStmt* compoundStmt)
{ if (ignoreLocation(compoundStmt)) returntrue; if (compoundStmt->size() < 2) returntrue;
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.