/* -*- 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/.
*/
/** Find pointer and reference params that can be declared const.
This is not a sophisticated analysis. It deliberately skips all of the hard cases for now. It is an exercise in getting the most benefit for the least effort.
*/ namespace
{
class ConstParams: public loplugin::FunctionAddress<loplugin::FilteringPlugin<ConstParams>>
{ public: explicit ConstParams(loplugin::InstantiationData const & data): FunctionAddress(data) {}
virtualvoid run() override {
std::string fn(handler.getMainFileName());
loplugin::normalizeDotDotInFilePath(fn); if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/")
|| fn == SRCDIR "/jurt/source/pipe/staticsalhack.cxx"
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/bridges/")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/binaryurp/")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/stoc/")
|| loplugin::hasPathnamePrefix(fn, WORKDIR "/YaccTarget/unoidl/source/sourceprovider-parser.cxx") // some weird calling through a function pointer
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/svtools/source/table/defaultinputhandler.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/sdext/source/pdfimport/test/pdfunzip.cxx") // windows only
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/basic/source/sbx/sbxdec.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/sfx2/source/doc/syspath.cxx") // ignore this for now
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/libreofficekit") // FunctionAddress not working well enough here
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/pyuno/source/module/pyuno_struct.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/pyuno/source/module/pyuno.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/ascii/ascatr.cxx") // TODO this plugin doesn't handle it well when we take the address of a pointer
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/svl/source/misc/sharedstringpool.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/registry/source/regkey.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/cppu/source/uno/lbenv.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/cppuhelper/source/implbase_ex.cxx") // legacy code, don't care
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/registry/") // false+
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/cppuhelper/source/compbase.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/comphelper/source/misc/compbase.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/unotools/source/misc/fontcvt.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/source/gdi/pdfwriter_impl2.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/source/treelist/treelist.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/unx/gtk3/gloactiongroup.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/unx/gtk3/customcellrenderer.cxx") // the constructor should not take a const& because it indicates that we are going to modify this Bitmap
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/source/bitmap/BitmapWriteAccess.cxx") // false+ because of #if
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/source/image/ImplImage.cxx") // false+
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/ucb/source/ucp/gio/gio_mount.cxx")
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/ucb/source/ucp/webdav-curl/CurlUri.cxx") // false+ macro
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/unx/gtk3/glomenu.cxx") // false+, called via function pointer
|| loplugin::hasPathnamePrefix(fn, SRCDIR "/sd/source/ui/slidesorter/shell/SlideSorterViewShell.cxx")
) return;
bool ConstParams::TraverseFunctionDecl(FunctionDecl * functionDecl)
{ // We cannot short-circuit the traverse here entirely without breaking the // loplugin::FunctionAddress stuff. auto prev = currentFunctionDecl; if (CheckTraverseFunctionDecl(functionDecl))
currentFunctionDecl = functionDecl; auto rv = FunctionAddress::TraverseFunctionDecl(functionDecl);
currentFunctionDecl = prev; return rv;
} bool ConstParams::TraverseCXXMethodDecl(CXXMethodDecl * f)
{ auto prev = currentFunctionDecl; if (CheckTraverseFunctionDecl(f))
currentFunctionDecl = f; auto rv = FunctionAddress::TraverseCXXMethodDecl(f);
currentFunctionDecl = prev; return rv;
} bool ConstParams::TraverseCXXConstructorDecl(CXXConstructorDecl * f)
{ auto prev = currentFunctionDecl; if (CheckTraverseFunctionDecl(f))
currentFunctionDecl = f; auto rv = FunctionAddress::TraverseCXXConstructorDecl(f);
currentFunctionDecl = prev; return rv;
}
bool ConstParams::CheckTraverseFunctionDecl(FunctionDecl * functionDecl)
{ if (ignoreLocation(functionDecl) || !functionDecl->isThisDeclarationADefinition()) { returnfalse;
} // ignore stuff that forms part of the stable URE interface if (isInUnoIncludeFile(functionDecl)) { returnfalse;
} if (functionDecl->isDeleted()) returnfalse; // ignore virtual methods if (isa<CXXMethodDecl>(functionDecl)
&& dyn_cast<CXXMethodDecl>(functionDecl)->isVirtual() ) { returnfalse;
} // ignore C main if (functionDecl->isMain()) { returnfalse;
} if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) returnfalse;
// ignore the macros from include/tools/link.hxx auto canonicalDecl = functionDecl->getCanonicalDecl(); if (compiler.getSourceManager().isMacroBodyExpansion(canonicalDecl->getBeginLoc())
|| compiler.getSourceManager().isMacroArgExpansion(canonicalDecl->getBeginLoc())) {
StringRef name { Lexer::getImmediateMacroName(
canonicalDecl->getBeginLoc(), compiler.getSourceManager(), compiler.getLangOpts()) }; if (compat::starts_with(name, "DECL_LINK") || compat::starts_with(name, "DECL_STATIC_LINK")
|| compat::starts_with(name, "DECL_DLLPRIVATE_STATIC_LINK") ) returnfalse; auto loc2 = compat::getImmediateExpansionRange(compiler.getSourceManager(), canonicalDecl->getBeginLoc()).first; if (compiler.getSourceManager().isMacroBodyExpansion(loc2))
{
StringRef name2 { Lexer::getImmediateMacroName(
loc2, compiler.getSourceManager(), compiler.getLangOpts()) }; if (compat::starts_with(name2, "DECL_DLLPRIVATE_LINK")
|| compat::starts_with(name2, "DECL_DLLPRIVATE_STATIC_LINK") ) returnfalse;
}
}
if (functionDecl->getIdentifier())
{
StringRef name = functionDecl->getName(); if ( name == "file_write"
|| name == "SalMainPipeExchangeSignal_impl"
|| compat::starts_with(name, "SbRtl_")
|| name == "GoNext"
|| name == "GoPrevious"
|| compat::starts_with(name, "Read_F_") // UNO component entry points
|| compat::ends_with(name, "component_getFactory")
|| compat::ends_with(name, "_get_implementation") // callback for some external code?
|| name == "ScAddInAsyncCallBack" // used as function pointers
|| name == "Read_Footnote"
|| name == "Read_Field"
|| name == "Read_And" // passed as a LINK<> to another method
|| name == "GlobalBasicErrorHdl_Impl" // template
|| name == "extract_throw" || name == "readProp" // callbacks
|| name == "signalDragDropReceived" || name == "signal_column_clicked" || name == "signal_key_press"
) returnfalse;
// calculate the ones we want to check bool foundInterestingParam = false; for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) { // ignore unused params if (pParmVarDecl->getName().empty()
|| pParmVarDecl->hasAttr<UnusedAttr>()) continue; autoconst type = loplugin::TypeCheck(pParmVarDecl->getType()); if (!isPointerOrReferenceToNonConst(pParmVarDecl->getType())) continue; // since we normally can't change typedefs, just ignore them if (isa<TypedefType>(pParmVarDecl->getType())) continue; // some typedefs turn into these if (isa<DecayedType>(pParmVarDecl->getType())) continue; // TODO ignore these for now, has some effects I don't understand if (type.Pointer().Pointer()) continue; // const is meaningless when applied to function pointer types if (pParmVarDecl->getType()->isFunctionPointerType()) continue;
interestingParamSet.insert(pParmVarDecl);
parmToFunction[pParmVarDecl] = functionDecl;
foundInterestingParam = true;
} return foundInterestingParam;
}
bool ConstParams::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
{ if (!currentFunctionDecl) returntrue; const ParmVarDecl* parmVarDecl = dyn_cast_or_null<ParmVarDecl>(declRefExpr->getDecl()); if (!parmVarDecl) returntrue; if (interestingParamSet.find(parmVarDecl) == interestingParamSet.end()) returntrue; if (!checkIfCanBeConst(declRefExpr, parmVarDecl))
interestingParamSet.erase(parmVarDecl); returntrue;
}
bool ConstParams::VisitLambdaExpr(const LambdaExpr* lambdaExpr)
{ if (ignoreLocation(lambdaExpr)) returntrue; for (auto captureIt = lambdaExpr->capture_begin(); captureIt != lambdaExpr->capture_end();
++captureIt)
{ const LambdaCapture& capture = *captureIt; if (capture.capturesVariable())
{ if (auto varDecl = dyn_cast<ParmVarDecl>(capture.getCapturedVar()))
interestingParamSet.erase(varDecl);
}
} returntrue;
}
// Walk up from a statement that contains a DeclRefExpr, checking if the usage means that the // related ParamVarDecl can be const. bool ConstParams::checkIfCanBeConst(const Stmt* stmt, const ParmVarDecl* parmVarDecl)
{ const Stmt* parent = getParentStmt( stmt ); if (!parent)
{ // check if we're inside a CXXCtorInitializer auto parentsRange = compiler.getASTContext().getParents(*stmt); auto it = parentsRange.begin(); if ( parentsRange.begin() != parentsRange.end())
{ const Decl *decl = it->get<Decl>(); if (auto cxxConstructorDecl = dyn_cast_or_null<CXXConstructorDecl>(decl))
{ for ( auto cxxCtorInitializer : cxxConstructorDecl->inits())
{ if ( cxxCtorInitializer->getInit() == stmt)
{ if (cxxCtorInitializer->isAnyMemberInitializer())
{ // if the member is not pointer-to-const or ref-to-const or value, we cannot make the param const auto fieldDecl = cxxCtorInitializer->getAnyMember(); auto tc = loplugin::TypeCheck(fieldDecl->getType()); if (tc.Pointer() || tc.LvalueReference()) return tc.Pointer().Const() || tc.LvalueReference().Const(); else returntrue;
} else
{ // probably base initialiser, but no simple way to look up the relevant constructor decl returnfalse;
}
}
}
} if (auto varDecl = dyn_cast_or_null<VarDecl>(decl))
{ return isOkForParameter(varDecl->getType());
}
} // parmVarDecl->dump(); // stmt->dump(); // report( // DiagnosticsEngine::Warning, // "no parent?", // stmt->getBeginLoc()) // << stmt->getSourceRange(); returnfalse;
}
if (auto unaryOperator = dyn_cast<UnaryOperator>(parent)) {
UnaryOperator::Opcode op = unaryOperator->getOpcode(); if (op == UO_PreInc || op == UO_PostInc
|| op == UO_PreDec || op == UO_PostDec) { returnfalse;
} if (op == UO_Deref || op == UO_AddrOf) { return checkIfCanBeConst(parent, parmVarDecl);
} returntrue;
} elseif (auto binaryOp = dyn_cast<BinaryOperator>(parent)) {
BinaryOperator::Opcode op = binaryOp->getOpcode(); if (binaryOp->getRHS() == stmt && op == BO_Assign) { return isOkForParameter(binaryOp->getLHS()->getType());
} if (binaryOp->getRHS() == stmt) { returntrue;
} if (op == BO_Assign || op == BO_PtrMemD || op == BO_PtrMemI || 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) { returnfalse;
} // for pointer arithmetic need to check parent if (binaryOp->getType()->isPointerType()) { return checkIfCanBeConst(parent, parmVarDecl);
} returntrue;
} elseif (auto constructExpr = dyn_cast<CXXConstructExpr>(parent)) { const CXXConstructorDecl * constructorDecl = constructExpr->getConstructor(); for (unsigned i = 0; i < constructExpr->getNumArgs(); ++i) { if (constructExpr->getArg(i) == stmt) { return isOkForParameter(constructorDecl->getParamDecl(i)->getType());
}
}
} elseif (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent)) { const CXXMethodDecl* calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(operatorCallExpr->getDirectCallee()); if (calleeMethodDecl) { // unary operator if (calleeMethodDecl->getNumParams() == 0) return calleeMethodDecl->isConst(); // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang // doesn't have yet. auto Opc = operatorCallExpr->getOperator(); if (Opc == OO_Equal || Opc == OO_StarEqual ||
Opc == OO_SlashEqual || Opc == OO_PercentEqual ||
Opc == OO_PlusEqual || Opc == OO_MinusEqual ||
Opc == OO_LessLessEqual || Opc == OO_GreaterGreaterEqual ||
Opc == OO_AmpEqual || Opc == OO_CaretEqual ||
Opc == OO_PipeEqual)
{ if (operatorCallExpr->getArg(0) == stmt) // assigning to the param returnfalse; // not all operator= take a const& return isOkForParameter(calleeMethodDecl->getParamDecl(0)->getType());
} if (operatorCallExpr->getOperator() == OO_Subscript && operatorCallExpr->getArg(1) == stmt) returntrue; if (operatorCallExpr->getOperator() == OO_EqualEqual || operatorCallExpr->getOperator() == OO_ExclaimEqual) returntrue; // binary operator if (operatorCallExpr->getArg(0) == stmt) return calleeMethodDecl->isConst(); unsignedconst n = std::min(
operatorCallExpr->getNumArgs(),
calleeMethodDecl->getNumParams() + 1); for (unsigned i = 1; i < n; ++i) if (operatorCallExpr->getArg(i) == stmt) { auto qt = calleeMethodDecl->getParamDecl(i - 1)->getType(); return isOkForParameter(qt);
}
} else { const Expr* callee = operatorCallExpr->getCallee()->IgnoreParenImpCasts(); const DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee); const FunctionDecl* calleeFunctionDecl = nullptr; if (dr) {
calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
} if (calleeFunctionDecl) { for (unsigned i = 0; i < operatorCallExpr->getNumArgs(); ++i) { if (operatorCallExpr->getArg(i) == stmt) { return isOkForParameter(calleeFunctionDecl->getParamDecl(i)->getType());
}
}
}
} returnfalse;
} elseif (auto callExpr = dyn_cast<CallExpr>(parent)) {
QualType functionType = callExpr->getCallee()->getType(); if (functionType->isFunctionPointerType()) {
functionType = functionType->getPointeeType();
} if (const FunctionProtoType* prototype = functionType->getAs<FunctionProtoType>()) { // TODO could do better if (prototype->isVariadic()) { returnfalse;
} if (callExpr->getCallee() == stmt) { returntrue;
} for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) { if (callExpr->getArg(i) == stmt) { return isOkForParameter(prototype->getParamType(i));
}
}
} const FunctionDecl* calleeFunctionDecl = callExpr->getDirectCallee(); if (calleeFunctionDecl)
{ if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(parent)) { const MemberExpr* memberExpr = dyn_cast<MemberExpr>(stmt); if (memberExpr && memberCallExpr->getImplicitObjectArgument() == memberExpr->getBase())
{ const CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl); return calleeMethodDecl->isConst();
}
} // TODO could do better if (calleeFunctionDecl->isVariadic()) { returnfalse;
} if (callExpr->getCallee() == stmt) { returntrue;
} for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) { if (i >= calleeFunctionDecl->getNumParams()) // can happen in template code returnfalse; if (callExpr->getArg(i) == stmt) { return isOkForParameter(calleeFunctionDecl->getParamDecl(i)->getType());
}
}
} returnfalse;
} elseif (auto callExpr = dyn_cast<ObjCMessageExpr>(parent)) { if (callExpr->getInstanceReceiver() == stmt) { returntrue;
} if (autoconst method = callExpr->getMethodDecl()) { // TODO could do better if (method->isVariadic()) { returnfalse;
}
assert(method->param_size() == callExpr->getNumArgs()); for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) { if (callExpr->getArg(i) == stmt) { return isOkForParameter(
method->param_begin()[i]->getType());
}
}
}
} elseif (isa<CXXReinterpretCastExpr>(parent)) { returnfalse;
} elseif (isa<CXXConstCastExpr>(parent)) { returnfalse;
} elseif (isa<CastExpr>(parent)) { // all other cast expression subtypes if (auto e = dyn_cast<ExplicitCastExpr>(parent)) { if (loplugin::TypeCheck(e->getTypeAsWritten()).Void()) { if (autoconst sub = dyn_cast<DeclRefExpr>(
e->getSubExpr()->IgnoreParenImpCasts()))
{ if (sub->getDecl() == parmVarDecl) returnfalse;
}
}
} return checkIfCanBeConst(parent, parmVarDecl);
} elseif (isa<MemberExpr>(parent)) { return checkIfCanBeConst(parent, parmVarDecl);
} elseif (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent)) { if (arraySubscriptExpr->getIdx() == stmt) returntrue; return checkIfCanBeConst(parent, parmVarDecl);
} elseif (isa<ParenExpr>(parent)) { return checkIfCanBeConst(parent, parmVarDecl);
} elseif (isa<DeclStmt>(parent)) { // TODO could do better here, but would require tracking the target(s) //return false;
} elseif (isa<ReturnStmt>(parent)) { return !isPointerOrReferenceToNonConst(currentFunctionDecl->getReturnType());
} elseif (isa<InitListExpr>(parent)) { returnfalse;
} elseif (isa<IfStmt>(parent)) { returntrue;
} elseif (isa<WhileStmt>(parent)) { returntrue;
} elseif (isa<ForStmt>(parent)) { returntrue;
} elseif (isa<CompoundStmt>(parent)) { returntrue;
} elseif (isa<SwitchStmt>(parent)) { returntrue;
} elseif (isa<DoStmt>(parent)) { returntrue;
} elseif (isa<CXXDeleteExpr>(parent)) { returnfalse;
} elseif (isa<VAArgExpr>(parent)) { returnfalse;
} elseif (isa<CXXDependentScopeMemberExpr>(parent)) { returnfalse;
} elseif (isa<MaterializeTemporaryExpr>(parent)) { return checkIfCanBeConst(parent, parmVarDecl);
} elseif (auto conditionalExpr = dyn_cast<ConditionalOperator>(parent)) { if (conditionalExpr->getCond() == stmt) returntrue; return checkIfCanBeConst(parent, parmVarDecl);
} elseif (isa<UnaryExprOrTypeTraitExpr>(parent)) { returnfalse; // ???
} elseif (auto cxxNewExpr = dyn_cast<CXXNewExpr>(parent)) { for (unsigned i = 0; i < cxxNewExpr->getNumPlacementArgs(); ++i) if (cxxNewExpr->getPlacementArg(i) == stmt) returnfalse; returntrue; // ???
} elseif (auto lambdaExpr = dyn_cast<LambdaExpr>(parent)) { for (auto it = lambdaExpr->capture_begin(); it != lambdaExpr->capture_end(); ++it)
{ if (it->capturesVariable() && it->getCapturedVar() == parmVarDecl) return it->getCaptureKind() != LCK_ByRef;
} returnfalse;
} elseif (isa<CXXTypeidExpr>(parent)) { returntrue;
} elseif (isa<ParenListExpr>(parent)) { returnfalse; // could be improved, seen in constructors when calling base class constructor
} elseif (isa<CXXUnresolvedConstructExpr>(parent)) { returnfalse;
} elseif (isa<UnresolvedMemberExpr>(parent)) { returnfalse;
} elseif (isa<PackExpansionExpr>(parent)) { returnfalse;
} elseif (isa<ExprWithCleanups>(parent)) { return checkIfCanBeConst(parent, parmVarDecl);
} elseif (isa<CaseStmt>(parent)) { returntrue;
} elseif (isa<CXXPseudoDestructorExpr>(parent)) { returnfalse;
} elseif (isa<CXXDependentScopeMemberExpr>(parent)) { returnfalse;
} elseif (isa<ObjCIvarRefExpr>(parent)) { return checkIfCanBeConst(parent, parmVarDecl);
}
parent->dump();
parmVarDecl->dump();
report(
DiagnosticsEngine::Warning, "oh dear, what can the matter be?",
parent->getBeginLoc())
<< parent->getSourceRange(); returntrue;
}
bool ConstParams::isOkForParameter(const QualType& qt) { if (qt->isIntegralOrEnumerationType()) returntrue; autoconst type = loplugin::TypeCheck(qt); if (type.Pointer()) { returnbool(type.Pointer().Const());
} elseif (type.LvalueReference().Const().Pointer()) { // If we have a method that takes (T* t) and it calls std::vector<T*>::push_back // then the type of push_back is T * const & // There is probably a more elegant way to check this, but it will probably require // recalculating types while walking up the AST. returnfalse;
} elseif (type.LvalueReference()) { returnbool(type.LvalueReference().Const());
} returnfalse;
}
bool ConstParams::isPointerOrReferenceToNonConst(const QualType& qt) { // cannot do anything useful with typedefs if (qt->isTypedefNameType()) returnfalse; autoconst type = loplugin::TypeCheck(qt); if (type.Pointer()) { return !bool(type.Pointer().Const());
} elseif (type.LvalueReference()) { return !bool(type.LvalueReference().Const());
} returnfalse;
}
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.