/* -*- 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/.
*/
/** This performs two analyses: (1) look for unused fields (2) look for fields that are write-only
We dmp a list of calls to methods, and a list of field definitions. Then we will post-process the 2 lists and find the set of unused methods.
Be warned that it produces around 5G of log file.
The process goes something like this: $ make check $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedfields' check $ ./compilerplugins/clang/unusedfields.py
and then $ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $dir; done to auto-remove the method declarations
Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around to get it to work :-)
RecordDecl * insideMoveOrCopyOrCloneDeclParent = nullptr;
RecordDecl * insideStreamOutputOperator = 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();
switch (fieldDecl->getAccess())
{ case AS_public: aInfo.access = "public"; break; case AS_private: aInfo.access = "private"; break; case AS_protected: aInfo.access = "protected"; break; default: aInfo.access = "unknown"; break;
}
return aInfo;
}
bool UnusedFields::VisitFieldDecl( const FieldDecl* fieldDecl )
{
fieldDecl = fieldDecl->getCanonicalDecl(); if (ignoreLocation( fieldDecl->getBeginLoc() )) { returntrue;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) { returntrue;
}
if (fieldDecl->getInClassInitializer() && !isSomeKindOfZero(fieldDecl->getInClassInitializer())) {
writeToSet.insert(niceName(fieldDecl));
}
/** Does the expression being used to initialise a field value evaluate to the same as a default value?
*/ bool UnusedFields::isSomeKindOfZero(const Expr* arg)
{
assert(arg);
arg = arg->IgnoreParenCasts(); if (isa<CXXDefaultArgExpr>(arg)) {
arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr();
}
arg = arg->IgnoreParenCasts(); // ignore this, it seems to trigger an infinite recursion if (isa<UnaryExprOrTypeTraitExpr>(arg)) { returnfalse;
} if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(arg)) { return cxxConstructExpr->getConstructor()->isDefaultConstructor();
}
APSInt x1; if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
{ return x1 == 0;
} if (isa<CXXNullPtrLiteralExpr>(arg)) { returntrue;
} if (isa<MaterializeTemporaryExpr>(arg))
{ const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts()); if (strippedArg)
{ auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr()); if (temp->getNumArgs() == 0)
{ if (loplugin::TypeCheck(temp->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) { returntrue;
} if (loplugin::TypeCheck(temp->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) { returntrue;
} returnfalse;
}
}
}
// Get the expression contents. // This helps us find params which are always initialised with something like "OUString()".
SourceManager& SM = compiler.getSourceManager();
SourceLocation startLoc = arg->getBeginLoc();
SourceLocation endLoc = arg->getEndLoc(); constchar *p1 = SM.getCharacterData( startLoc ); constchar *p2 = SM.getCharacterData( endLoc ); if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40) { returnfalse;
} unsigned n = Lexer::MeasureTokenLength( endLoc, SM, compiler.getLangOpts());
std::string s( p1, p2 - p1 + n); // strip linefeed and tab characters so they don't interfere with the parsing of the log file
std::replace( s.begin(), s.end(), '\r', ' ');
std::replace( s.begin(), s.end(), '\n', ' ');
std::replace( s.begin(), s.end(), '\t', ' ');
// now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing if (s == "OUString()") returntrue; elseif (s == "OString()") returntrue; returnfalse;
}
bool UnusedFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
{ auto copy = insideMoveOrCopyOrCloneDeclParent; if (!ignoreLocation(cxxConstructorDecl->getBeginLoc()) && cxxConstructorDecl->isThisDeclarationADefinition())
{ if (cxxConstructorDecl->isCopyOrMoveConstructor())
insideMoveOrCopyOrCloneDeclParent = cxxConstructorDecl->getParent();
} bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
insideMoveOrCopyOrCloneDeclParent = copy; return ret;
}
bool UnusedFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
{ auto copy1 = insideMoveOrCopyOrCloneDeclParent; auto copy2 = insideFunctionDecl; if (!ignoreLocation(cxxMethodDecl->getBeginLoc()) && cxxMethodDecl->isThisDeclarationADefinition())
{ if (cxxMethodDecl->isCopyAssignmentOperator()
|| cxxMethodDecl->isMoveAssignmentOperator()
|| (cxxMethodDecl->getIdentifier()
&& (compat::starts_with(cxxMethodDecl->getName(), "Clone")
|| compat::starts_with(cxxMethodDecl->getName(), "clone")
|| compat::starts_with(cxxMethodDecl->getName(), "createClone"))))
insideMoveOrCopyOrCloneDeclParent = cxxMethodDecl->getParent(); // these are similar in that they tend to simply enumerate all the fields of an object without putting // them to some useful purpose auto op = cxxMethodDecl->getOverloadedOperator(); if (op == OO_EqualEqual || op == OO_ExclaimEqual)
insideMoveOrCopyOrCloneDeclParent = cxxMethodDecl->getParent();
}
insideFunctionDecl = cxxMethodDecl; bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
insideMoveOrCopyOrCloneDeclParent = copy1;
insideFunctionDecl = copy2; return ret;
}
bool UnusedFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
{ auto copy1 = insideStreamOutputOperator; auto copy2 = insideFunctionDecl; auto copy3 = insideMoveOrCopyOrCloneDeclParent; if (functionDecl->getLocation().isValid() && !ignoreLocation(functionDecl->getBeginLoc()) && functionDecl->isThisDeclarationADefinition())
{ auto op = functionDecl->getOverloadedOperator(); if (op == OO_LessLess
&& functionDecl->getNumParams() == 2)
{
QualType qt = functionDecl->getParamDecl(1)->getType();
insideStreamOutputOperator = qt.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
} // these are similar in that they tend to simply enumerate all the fields of an object without putting // them to some useful purpose if (op == OO_EqualEqual || op == OO_ExclaimEqual)
{
QualType qt = functionDecl->getParamDecl(1)->getType();
insideMoveOrCopyOrCloneDeclParent = qt.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
}
}
insideFunctionDecl = functionDecl; bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
insideStreamOutputOperator = copy1;
insideFunctionDecl = copy2;
insideMoveOrCopyOrCloneDeclParent = copy3; return ret;
}
if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(cond))
{ if (auto cxxConvert = dyn_cast_or_null<CXXConversionDecl>(memberCallExpr->getMethodDecl()))
{ if (cxxConvert->getConversionType()->isBooleanType()) if (auto memberExpr = dyn_cast<MemberExpr>(memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts())) if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
} elseif (auto cxxMethod = memberCallExpr->getMethodDecl())
{ if (cxxMethod->getIdentifier() && cxxMethod->getName() == "get" && memberCallExpr->getNumArgs()==0) if (auto memberExpr = dyn_cast<MemberExpr>(memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts())) if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
}
} elseif (auto memberExpr = dyn_cast<MemberExpr>(cond))
{ if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
}
bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt); if (memberFieldDecl)
insideConditionalCheckOfMemberSet.pop_back(); return ret;
}
bool UnusedFields::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->getBeginLoc())) { returntrue;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) { returntrue;
}
checkTouchedFromOutside(fieldDecl, memberExpr);
checkIfReadFrom(fieldDecl, memberExpr);
checkIfWrittenTo(fieldDecl, memberExpr);
returntrue;
}
void UnusedFields::checkIfReadFrom(const FieldDecl* fieldDecl, const Expr* memberExpr)
{ if (insideMoveOrCopyOrCloneDeclParent || insideStreamOutputOperator)
{
RecordDecl const * cxxRecordDecl1 = fieldDecl->getParent(); // we don't care about reads from a field when inside the copy/move constructor/operator= for that field if (cxxRecordDecl1 && (cxxRecordDecl1 == insideMoveOrCopyOrCloneDeclParent)) return; // we don't care about reads when the field is being used in an output operator, this is normally // debug stuff if (cxxRecordDecl1 && (cxxRecordDecl1 == insideStreamOutputOperator)) return;
}
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 bPotentiallyReadFrom = 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're inside a CXXCtorInitializer or a VarDecl auto parentsRange = compiler.getASTContext().getParents(*child); if ( parentsRange.begin() != parentsRange.end())
{ const Decl* decl = parentsRange.begin()->get<Decl>(); if (decl && (isa<CXXConstructorDecl>(decl) || isa<VarDecl>(decl)))
bPotentiallyReadFrom = true;
} if (!bPotentiallyReadFrom) return; break;
} if (isa<CXXReinterpretCastExpr>(parent))
{ // once we see one of these, there is not much useful we can know
bPotentiallyReadFrom = 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 (memberExpr->getType()->isArrayType() && op == UO_Deref)
{ // ignore, deref'ing an array does not count as a read
} elseif (op == UO_AddrOf || op == UO_Deref
|| op == UO_Plus || op == UO_Minus
|| op == UO_Not || op == UO_LNot)
{
bPotentiallyReadFrom = true; break;
} /* The following are technically reads, but from a code-sense they're more of a write/modify, so ignore them to find interesting fields that only modified, not usefully read: UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec But we still walk up in case the result of the expression is used in a read sense.
*/
walkUp();
} elseif (auto caseStmt = dyn_cast<CaseStmt>(parent))
{
bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child; break;
} elseif (auto ifStmt = dyn_cast<IfStmt>(parent))
{
bPotentiallyReadFrom = ifStmt->getCond() == child; break;
} elseif (auto doStmt = dyn_cast<DoStmt>(parent))
{
bPotentiallyReadFrom = doStmt->getCond() == child; break;
} elseif (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
{ if (arraySubscriptExpr->getIdx() == child)
{
bPotentiallyReadFrom = true; break;
}
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 (binaryOp->getLHS() == child && assignmentOp) break; else
{
bPotentiallyReadFrom = true; break;
}
} elseif (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
{ auto op = operatorCallExpr->getOperator(); constbool assignmentOp = op == OO_Equal || op == OO_StarEqual ||
op == OO_SlashEqual || op == OO_PercentEqual ||
op == OO_PlusEqual || op == OO_MinusEqual ||
op == OO_LessLessEqual ||
op == OO_AmpEqual || op == OO_CaretEqual ||
op == OO_PipeEqual; if (operatorCallExpr->getArg(0) == child && assignmentOp) break; elseif (op == OO_GreaterGreaterEqual && operatorCallExpr->getArg(1) == child) break; // this is a write-only call else
{
bPotentiallyReadFrom = true; break;
}
} elseif (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
{ bool bWriteOnlyCall = false; const CXXMethodDecl * callee = cxxMemberCallExpr->getMethodDecl(); if (callee)
{ const Expr* tmp = dyn_cast<Expr>(child); if (tmp->isBoundMemberFunction(compiler.getASTContext())) {
tmp = dyn_cast<MemberExpr>(tmp)->getBase();
} if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
{ // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute // which we could scatter around.
std::string name = callee->getNameAsString();
std::transform(name.begin(), name.end(), name.begin(), easytolower); if (startswith(name, "emplace") || name == "insert"
|| name == "erase" || name == "remove" || name == "remove_if" || name == "sort"
|| name == "push_back" || name == "pop_back"
|| name == "push_front" || name == "pop_front"
|| name == "reserve" || name == "resize" || name == "reset"
|| name == "clear" || name == "fill") // write-only modifications to collections
bWriteOnlyCall = true; elseif (name == "dispose" || name == "disposeAndClear" || name == "swap") // we're abusing the write-only analysis here to look for fields which don't have anything useful // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap, // and VclPtr::disposeAndClear
bWriteOnlyCall = true;
}
} if (!bWriteOnlyCall)
bPotentiallyReadFrom = true; break;
} elseif (auto callExpr = dyn_cast<CallExpr>(parent))
{ bool bWriteOnlyCall = false; // check for calls to ReadXXX(foo) type methods, where foo is write-only auto callee = getCallee(callExpr); if (callee)
{ // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute // which we could scatter around.
std::string name = callee->getNameAsString();
std::transform(name.begin(), name.end(), name.begin(), easytolower); if (startswith(name, "read")) // this is a write-only call
bWriteOnlyCall = true;
} if (!bWriteOnlyCall)
bPotentiallyReadFrom = true; break;
} elseif (isa<ReturnStmt>(parent)
|| isa<CXXConstructExpr>(parent)
|| isa<ConditionalOperator>(parent)
|| isa<SwitchStmt>(parent)
|| isa<DeclStmt>(parent)
|| isa<WhileStmt>(parent)
|| isa<CXXNewExpr>(parent)
|| isa<ForStmt>(parent)
|| isa<InitListExpr>(parent)
|| isa<CXXDependentScopeMemberExpr>(parent)
|| isa<UnresolvedMemberExpr>(parent)
|| isa<MaterializeTemporaryExpr>(parent))
{
bPotentiallyReadFrom = true; break;
} elseif (isa<CXXDeleteExpr>(parent)
|| isa<UnaryExprOrTypeTraitExpr>(parent)
|| isa<CXXUnresolvedConstructExpr>(parent)
|| isa<CompoundStmt>(parent)
|| isa<LabelStmt>(parent)
|| isa<CXXForRangeStmt>(parent)
|| isa<CXXTypeidExpr>(parent)
|| isa<DefaultStmt>(parent))
{ break;
} elseif (isa<DeclRefExpr>(parent)) // things like o3tl::convertNarrowing pass members as template params
{
bPotentiallyReadFrom = true; break;
} elseif (isa<DesignatedInitExpr>(parent))
{
bPotentiallyReadFrom = true; break;
} else
{
bPotentiallyReadFrom = true;
bDump = true; break;
}
} while (true);
if (bDump)
{
report(
DiagnosticsEngine::Warning, "oh dear, what can the matter be?",
memberExpr->getBeginLoc())
<< memberExpr->getSourceRange();
report(
DiagnosticsEngine::Note, "parent over here",
parent->getBeginLoc())
<< parent->getSourceRange();
parent->dump();
memberExpr->dump();
}
MyFieldInfo fieldInfo = niceName(fieldDecl); if (bPotentiallyReadFrom)
{
readFromSet.insert(fieldInfo);
}
}
void UnusedFields::checkIfWrittenTo(const FieldDecl* fieldDecl, const Expr* memberExpr)
{ if (insideMoveOrCopyOrCloneDeclParent)
{
RecordDecl const * cxxRecordDecl1 = fieldDecl->getParent(); // we don't care about writes to a field when inside the copy/move constructor/operator= for that field if (cxxRecordDecl1 && (cxxRecordDecl1 == insideMoveOrCopyOrCloneDeclParent))
{ return;
}
}
// if we're inside a block that looks like // if (fieldDecl) // ... // then writes to this field don't matter, because unless we find another write to this field, this field is dead if (std::find(insideConditionalCheckOfMemberSet.begin(), insideConditionalCheckOfMemberSet.end(), fieldDecl) != insideConditionalCheckOfMemberSet.end()) return;
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 bPotentiallyWrittenTo = 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())
{
bPotentiallyWrittenTo = true;
}
} break;
} if (isa<CXXReinterpretCastExpr>(parent))
{ // once we see one of these, there is not much useful we can know
bPotentiallyWrittenTo = 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)
{
bPotentiallyWrittenTo = true;
} break;
} elseif (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
{ if (arraySubscriptExpr->getIdx() == child) break;
walkUp();
} elseif (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
{ bool walk = false; 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)
{ if (!calleeMethodDecl->isConst())
{ // If we are accessing a map entry, we want to keep walking up to determine // if it is written to. if (checkForUsingMap(calleeMethodDecl))
walk = true; else
bPotentiallyWrittenTo = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
}
} elseif (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
bPotentiallyWrittenTo = true;
} else
bPotentiallyWrittenTo = true; // conservative, could improve if (walk)
walkUp(); else break;
} 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)
{ if (!calleeMethodDecl->isConst())
bPotentiallyWrittenTo = checkForWriteWhenUsingCollectionType(calleeMethodDecl); break;
} elseif (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr, CalleeWrapper(calleeMethodDecl)))
bPotentiallyWrittenTo = true;
} else
bPotentiallyWrittenTo = true; // can happen in templates break;
} elseif (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
{ if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr, CalleeWrapper(cxxConstructExpr)))
bPotentiallyWrittenTo = true; break;
} elseif (auto callExpr = dyn_cast<CallExpr>(parent))
{ auto callee = getCallee(callExpr); if (callee) { if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
bPotentiallyWrittenTo = true;
} else
bPotentiallyWrittenTo = true; // conservative, could improve break;
} 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)
bPotentiallyWrittenTo = 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
bPotentiallyWrittenTo = true;
} break;
} elseif (isa<ReturnStmt>(parent))
{ if (insideFunctionDecl)
{ auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType()); if (tc.LvalueReference().NonConst())
bPotentiallyWrittenTo = true;
} break;
} elseif (isa<ConditionalOperator>(parent)
|| isa<SwitchStmt>(parent)
|| isa<DeclStmt>(parent)
|| isa<WhileStmt>(parent)
|| isa<CXXNewExpr>(parent)
|| isa<ForStmt>(parent)
|| isa<InitListExpr>(parent)
|| isa<CXXDependentScopeMemberExpr>(parent)
|| isa<UnresolvedMemberExpr>(parent)
|| isa<MaterializeTemporaryExpr>(parent)
|| isa<IfStmt>(parent)
|| isa<DoStmt>(parent)
|| isa<CXXDeleteExpr>(parent)
|| isa<UnaryExprOrTypeTraitExpr>(parent)
|| isa<CXXUnresolvedConstructExpr>(parent)
|| isa<CompoundStmt>(parent)
|| isa<LabelStmt>(parent)
|| isa<CXXForRangeStmt>(parent)
|| isa<CXXTypeidExpr>(parent)
|| isa<DefaultStmt>(parent))
{ break;
} elseif (isa<DeclRefExpr>(parent)) // things like o3tl::convertNarrowing pass members as template params
{ break;
} elseif (isa<DesignatedInitExpr>(parent))
{
bPotentiallyWrittenTo = true; break;
} else
{
bPotentiallyWrittenTo = true;
bDump = true; break;
}
} while (true);
if (bDump)
{
report(
DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
memberExpr->getBeginLoc())
<< bPotentiallyWrittenTo
<< memberExpr->getSourceRange(); if (parent)
{
report(
DiagnosticsEngine::Note, "parent over here",
parent->getBeginLoc())
<< parent->getSourceRange();
parent->dump();
}
memberExpr->dump();
fieldDecl->getType()->dump();
}
MyFieldInfo fieldInfo = niceName(fieldDecl); if (bPotentiallyWrittenTo)
{
writeToSet.insert(fieldInfo);
}
}
// return true if this not a collection type, or if it is a collection type, and we might be writing to it bool UnusedFields::checkForWriteWhenUsingCollectionType(const CXXMethodDecl * calleeMethodDecl)
{ autoconst tc = loplugin::TypeCheck(calleeMethodDecl->getParent()); bool listLike = false, setLike = false, mapLike = false, cssSequence = false; // Noting that I am deliberately not calling StdNamespace() on these checks, the loplugin::TypeCheck // code seems to be unreliable when dealing with ClassTemplateSpecializationDecl. if (tc.Class("deque")
|| tc.Class("list")
|| tc.Class("queue")
|| tc.Class("vector"))
{
listLike = true;
} elseif (tc.Class("set") || tc.Class("unordered_set"))
{
setLike = true;
} elseif (tc.Class("map") || tc.Class("unordered_map"))
{
mapLike = true;
} elseif (tc.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
{
cssSequence = true;
} else returntrue;
if (calleeMethodDecl->isOverloadedOperator())
{ auto oo = calleeMethodDecl->getOverloadedOperator(); if (oo == OO_Equal) returntrue; // This is operator[]. We only care about things that add elements to the collection. // if nothing modifies the size of the collection, then nothing useful // is stored in it. if (listLike) returnfalse; returntrue;
}
auto name = calleeMethodDecl->getName(); if (listLike || setLike || mapLike)
{ if (name == "reserve" || name == "shrink_to_fit" || name == "clear"
|| name == "erase" || name == "pop_back" || name == "pop_front"
|| name == "front" || name == "back" || name == "data"
|| name == "remove" || name == "remove_if"
|| name == "unique" || name == "sort"
|| name == "begin" || name == "end"
|| name == "rbegin" || name == "rend"
|| name == "at" || name == "find" || name == "equal_range"
|| name == "lower_bound" || name == "upper_bound") returnfalse;
} if (cssSequence)
{ if (name == "getArray" || name == "begin" || name == "end") returnfalse;
}
bool UnusedFields::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;
}
// fields that are assigned via member initialisers do not get visited in VisitDeclRef, so // have to do it here bool UnusedFields::VisitCXXConstructorDecl( const CXXConstructorDecl* cxxConstructorDecl )
{ if (ignoreLocation( cxxConstructorDecl->getBeginLoc() )) { returntrue;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxConstructorDecl->getLocation()))) { returntrue;
}
// templates make EvaluateAsInt crash inside clang if (cxxConstructorDecl->isDependentContext()) returntrue;
// we don't care about writes to a field when inside the copy/move constructor/operator= for that field if (insideMoveOrCopyOrCloneDeclParent && cxxConstructorDecl->getParent() == insideMoveOrCopyOrCloneDeclParent) returntrue;
// Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so // have to do it here. bool UnusedFields::VisitInitListExpr( const InitListExpr* initListExpr)
{ if (ignoreLocation( initListExpr->getBeginLoc() )) returntrue;
QualType varType = initListExpr->getType().getDesugaredType(compiler.getASTContext()); auto recordType = varType->getAs<RecordType>(); if (!recordType) returntrue;
auto recordDecl = recordType->getDecl(); for (auto it = recordDecl->field_begin(); it != recordDecl->field_end(); ++it)
{
MyFieldInfo fieldInfo = niceName(*it);
writeToSet.insert(fieldInfo);
}
returntrue;
}
bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
{ const Decl* decl = declRefExpr->getDecl(); const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl); if (!fieldDecl) { returntrue;
}
fieldDecl = fieldDecl->getCanonicalDecl(); if (ignoreLocation(fieldDecl->getBeginLoc())) { returntrue;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) { returntrue;
}
checkTouchedFromOutside(fieldDecl, declRefExpr); returntrue;
}
// 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);
}
}
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.