/* -*- 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/. */ #include "plugin.hxx" #include "check.hxx" #include #include #include /* Look for member functions that merely return a compile-time constant, or they are empty, and can thus be either removed, or converted into a constant. This mostly tends to happen as a side-effect of other cleanups. */ namespace { class ReturnConstant : public RecursiveASTVisitor, public loplugin::Plugin { public: explicit ReturnConstant(loplugin::InstantiationData const& data) : Plugin(data) { } void run() override { // these files crash clang-3.5 somewhere in the isEvaluatable/EvaluateAsXXX stuff /* FileID mainFileID = compiler.getSourceManager().getMainFileID(); if (strstr(compiler.getSourceManager().getFileEntryForID(mainFileID)->getName(), "bootstrapfixture.cxx") != 0) { return; } if (strstr(compiler.getSourceManager().getFileEntryForID(mainFileID)->getName(), "gtk3gtkinst.cxx") != 0) { return; }*/ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } bool TraverseCXXMethodDecl(CXXMethodDecl*); bool VisitReturnStmt(ReturnStmt const*); private: StringRef getFilename(FunctionDecl const* functionDecl); std::string getExprValue(Expr const* arg); struct Context { bool ignore = false; std::set values; }; std::vector m_functionStack; }; StringRef ReturnConstant::getFilename(FunctionDecl const* functionDecl) { SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc( functionDecl->getCanonicalDecl()->getNameInfo().getLoc()); StringRef name{ compiler.getSourceManager().getFilename(spellingLocation) }; return name; } static bool startsWith(std::string const& rStr, char const* pSubStr) { return rStr.compare(0, strlen(pSubStr), pSubStr) == 0; } bool ReturnConstant::TraverseCXXMethodDecl(CXXMethodDecl* functionDecl) { if (ignoreLocation(functionDecl)) { return true; } if (!functionDecl->hasBody()) { return true; } if (!functionDecl->isThisDeclarationADefinition()) { return true; } // stuff declared extern-C is almost always used as a some kind of callback if (functionDecl->isExternC()) { return true; } if (functionDecl->isConstexpr()) { return true; } if (functionDecl->isMain()) { return true; } StringRef aFileName = getFilename(functionDecl); // various tests in here are empty stubs under Linux if (aFileName.startswith(SRCDIR "/sal/qa/")) { return true; } // lots of empty stuff here where it looks like someone is still going to "fill in the blanks" if (aFileName.startswith(SRCDIR "/basegfx/test/")) { return true; } // some stuff is just stubs under Linux, although this appears to be a SOLARIS-specific hack, so it // should probably not even be compiling under Linux. if (aFileName == SRCDIR "/setup_native/scripts/source/getuid.c") { return true; } // bridges has some weird stuff in it.... if (aFileName.startswith(SRCDIR "/bridges/")) { return true; } // dummy implementation of DDE, since it is only active on Windows if (aFileName == SRCDIR "/svl/unx/source/svdde/ddedummy.cxx" || aFileName == SRCDIR "/include/svl/svdde.hxx") { return true; } // fancy templates at work here if (aFileName == SRCDIR "/vcl/source/gdi/bmpfast.cxx") { return true; } // bunch of stuff used as callbacks here if (aFileName == SRCDIR "/vcl/generic/glyphs/gcach_layout.cxx") { return true; } // salplug runtime-loading mechanism at work if (aFileName == SRCDIR "/vcl/inc/salinst.hxx") { return true; } // lots of callbacks here if (aFileName == SRCDIR "/extensions/source/plugin/unx/npnapi.cxx") { return true; } // template magic if (aFileName == SRCDIR "/filter/source/svg/svgreader.cxx") { return true; } // vcl/unx/gtk3 re-using vcl/unx/gtk: if (aFileName.find("/../../gtk/") != std::string::npos) { return true; } // used by code generated by python if (aFileName == SRCDIR "/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx") { return true; } // this test just test the include of some headers if (aFileName == SRCDIR "/officecfg/qa/cppheader.cxx") { return true; } // just ignore this for now, people furiously hacking in there if (startsWith(aFileName, SRCDIR "/libreofficekit")) { return true; } // templates if (startsWith(aFileName, SRCDIR "/store")) return true; // not worth it if (startsWith(aFileName, SRCDIR "/registry")) return true; if (startsWith(aFileName, SRCDIR "/idlc")) return true; if (startsWith(aFileName, SRCDIR "/include/unotools")) return true; if (startsWith(aFileName, SRCDIR "/xmlreader/")) return true; if (startsWith(aFileName, SRCDIR "/include/xmlreader/")) return true; if (startsWith(aFileName, SRCDIR "/jvmfwk")) return true; const CXXMethodDecl* pCXXMethodDecl = dyn_cast(functionDecl); if (pCXXMethodDecl) { if (pCXXMethodDecl->isVirtual()) { return true; } // static with inline body will be optimised at compile-time to a constant anyway if (pCXXMethodDecl->isStatic() && (pCXXMethodDecl->hasInlineBody() || pCXXMethodDecl->isInlineSpecified())) { return true; } // this catches some stuff in templates if (functionDecl->hasAttr()) { return true; } } // a free function with an inline body will be optimised at compile-time to a constant anyway if (!pCXXMethodDecl && functionDecl->isInlineSpecified()) { return true; } if (isa(functionDecl) || isa(functionDecl) || isa(functionDecl)) { return true; } if (isInUnoIncludeFile(functionDecl)) { return true; } switch (functionDecl->getOverloadedOperator()) { case OO_Delete: case OO_EqualEqual: case OO_Call: return true; default: break; } std::string aFunctionName = functionDecl->getQualifiedNameAsString(); // something to do with dynamic loading in sal/textenc/textenc.cxx if (aFunctionName == "thisModule") { return true; } // an empty stub under certain conditions, sal/osl/unx/thread.cxx if (aFunctionName == "osl_thread_priority_init_Impl") { return true; } // a pointer to this function is taken and passed to an underlying API, shell/source/unix/sysshell/recently_used_file_handler.cxx if (aFunctionName == "(anonymous namespace)::recently_used_item::set_nothing") { return true; } // a pointer to this function is taken and passed to an underlying API, cppu/source/uno/lbenv.cxx if (aFunctionName == "defenv_dispose") { return true; } // a pointer to this function is taken and passed to an underlying API, cppuhelper/source/exc_thrower.cxx if (aFunctionName == "ExceptionThrower_acquire_release_nop") { return true; } // different hook function is called on different platforms, /vcl/source/app/svmainhook.cxx if (aFunctionName == "ImplSVMainHook") { return true; } // used as a callback, /vcl/source/filter/jpeg/JpegReader.cxx if (aFunctionName == "term_source") { return true; } // only valid for windows, extensions/source/update/check/updatecheck.cxx if (aFunctionName == "(anonymous namespace)::UpdateCheckThread::hasInternetConnection") { return true; } // used as callback, extensions/source/plugin/unx/npwrap.cxx if (aFunctionName == "plugin_x_error_handler" || aFunctionName == "noClosure") { return true; } // used as callback, sax/source/expatwrap/sax_expat.cxx if (aFunctionName == "(anonymous namespace)::SaxExpatParser_Impl::callbackUnknownEncoding") { return true; } // used as callback, i18npool/source/textconversion/textconversion.cxx if (aFunctionName == "com::sun::star::i18n::nullFunc") { return true; } // used as callback, xmloff/source/text/txtparae.cxx if (aFunctionName == "(anonymous namespace)::lcl_TextContentsUnfiltered") { return true; } // template magic, include/canvas/verifyinput.hxx if (aFunctionName == "canvas::tools::verifyInput") { return true; } // template magic, cppcanvas/source/mtfrenderer/implrenderer.cxx if (aFunctionName == "cppcanvas::internal::(anonymous namespace)::AreaQuery::result") { return true; } // callback, drawinglayer/source/dumper/XShapeDumper. if (aFunctionName == "(anonymous namespace)::closeCallback") { return true; } // callback, basic/source/runtime/runtime.cxx if (aFunctionName == "SbiRuntime::StepNOP") { return true; } // DLL stuff, only used on windows, basic/source/runtime/dllmgr.hxx if (aFunctionName == "SbiDllMgr::FreeDll") { return true; } // only used on Windows, basic/source/sbx/sbxdec.cxx if (aFunctionName == "SbxDecimal::neg" || aFunctionName == "SbxDecimal::isZero") { return true; } // used as a callback, include/sfx2/shell.hxx if (aFunctionName == "SfxShell::EmptyExecStub" || aFunctionName == "SfxShell::EmptyStateStub" || aFunctionName == "SfxShell::VerbState") { return true; } // SFX_IMPL_POS_CHILDWINDOW_WITHID macro if (aFunctionName.find("GetChildWindowId") != std::string::npos) { return true; } // SFX_IMPL_SUPERCLASS_INTERFACE macro if (aFunctionName.find("InitInterface_Impl") != std::string::npos) { return true; } // callback, vcl/unx/generic/app/sm.cxx if (aFunctionName == "IgnoreIceIOErrors" || aFunctionName == "IgnoreIceErrors") { return true; } // callback, vcl/unx/gtk/a11y/atkcomponent.cxx if (aFunctionName == "component_wrapper_get_mdi_zorder") { return true; } // callback, vcl/unx/gtk/a11y/atkaction.cxx if (aFunctionName == "action_wrapper_set_description") { return true; } // callback, vcl/unx/gtk/a11y/atkutil.cxx if (aFunctionName == "ooo_atk_util_get_toolkit_version" || aFunctionName == "ooo_atk_util_get_toolkit_name") { return true; } // callback, vcl/unx/gtk/a11y/atktextattributes.cxx if (aFunctionName == "InvalidValue") { return true; } // callback, vcl/unx/gtk/a11y/atktable.cxx if (aFunctionName == "table_wrapper_set_summary" || aFunctionName == "table_wrapper_set_row_header" || aFunctionName == "table_wrapper_set_row_description" || aFunctionName == "table_wrapper_set_column_header" || aFunctionName == "table_wrapper_set_column_description" || aFunctionName == "table_wrapper_set_caption") { return true; } // callbacks, vcl/unx/gtk/window/gtksalframe.cxx if (startsWith(aFunctionName, "GtkSalFrame::IMHandler::signal")) { return true; } // callbacks, vcl/unx/gtk/window/glomenu.cxx if (startsWith(aFunctionName, "g_lo_menu_is_mutable")) { return true; } // only contains code for certain versions of GTK, /vcl/unx/gtk/window/gtksalframe.cx if (aFunctionName == "GtkSalFrame::AllocateFrame") { return true; } // only valid for Windows, embeddedobj/source/msole/olemisc.cxx if (aFunctionName == "OleEmbeddedObject::GetRidOfComponent") { return true; } // callback, svx/source/accessibility/ShapeTypeHandler.cxx if (aFunctionName == "accessibility::CreateEmptyShapeReference") { return true; } // chart2/source/view/main/AbstractShapeFactory.cxx if (aFunctionName == "chart::(anonymous namespace)::thisModule") { return true; } // chart2/source/tools/InternalData.cxx if (aFunctionName == "chart::InternalData::dump") { return true; } // hwpfilter/ if (aFunctionName == "debug" || aFunctionName == "token_debug") { return true; } // callback, sdext/source/presenter/PresenterFrameworkObserver.cxx if (aFunctionName == "sdext::presenter::PresenterFrameworkObserver::True") { return true; } // callback, sw/source/core/doc/tblrwcl.cxx if (aFunctionName == "lcl_DelOtherBox") { return true; } // callback, sw/source/filter/ww8/ww8par.cxx if (aFunctionName == "SwWW8ImplReader::Read_Majority") { return true; } // callback, sw/source/filter/ww8/ww8par5.cxx if (aFunctionName == "SwWW8ImplReader::Read_F_Shape") { return true; } // called from SDI file, I don't know what that stuff is about, sd/source/ui/slidesorter/shell/SlideSorterViewShell.cx if (aFunctionName == "sd::slidesorter::SlideSorterViewShell::ExecStatusBar" || aFunctionName == "sd::OutlineViewShell::ExecStatusBar") { return true; } // only used in debug mode, sd/source/filter/ppt/pptinanimations.cxx if (startsWith(aFunctionName, "ppt::AnimationImporter::dump")) { return true; } // only used in ENABLE_SDREMOTE_BLUETOOTH mode, sd/source/ui/dlg/tpoption.cx if (aFunctionName == "SdTpOptionsMisc::SetImpressMode") { return true; } // template magic, sc/source/ui/docshell/datastream.cxx if (startsWith(aFunctionName, "sc::(anonymous namespace)::CSVHandler::")) { return true; } // called from SDI file, I don't know what that stuff is about, sc/source/ui/view/cellsh4.cxx if (aFunctionName == "ScCellShell::GetStateCursor") { return true; } // template magic, sc/source/filter/excel/xepivot.cxx if (aFunctionName == "XclExpPivotCache::SaveXml") { return true; } // template magic, sc/source/filter/html/htmlpars.cxx if (startsWith(aFunctionName, "(anonymous namespace)::CSSHandler::")) { return true; } // callbacks, sc/source/filter/oox/formulaparser.cxx if (startsWith(aFunctionName, "oox::xls::BiffFormulaParserImpl::import")) { return true; } // template magic, sc/qa/unit/helper/csv_handler.hxx if (startsWith(aFunctionName, "csv_handler::") || startsWith(aFunctionName, "conditional_format_handler::")) { return true; } // template magic, slideshow/source/inc/listenercontainer.hxx if (startsWith(aFunctionName, "slideshow::internal::EmptyBase::EmptyClearableGuard::")) { return true; } // callback, scripting/source/vbaevents/eventhelper.cxx if (aFunctionName == "ApproveAll") { return true; } // only on WNT, basic/qa/cppunit/test_vba.cx if (aFunctionName == "(anonymous namespace)::VBATest::testMiscOLEStuff") { return true; } // GtkSalFrame::TriggerPaintEvent() is only compiled under certain versions of GTK if (aFunctionName == "GtkSalFrame::TriggerPaintEvent") { return true; } if (aFunctionName == "SwVectorModifyBase::dumpAsXml") { return true; } // vcl/unx/gtk3 re-using vcl/unx/gtk: if (aFunctionName == "DeInitAtkBridge" || aFunctionName == "GtkData::initNWF" || aFunctionName == "GtkSalFrame::EnsureAppMenuWatch" || aFunctionName == "InitAtkBridge") { return true; } if (aFunctionName == "sc::AlignedAllocator::operator!=") { return true; } if (aFunctionName == "clipboard_owner_init") { return true; } // returns sizeof(struct) vcl/source/gdi/dibtools.cxx if (aFunctionName == "getDIBV5HeaderSize") { return true; } // windows only if (aFunctionName == "InitAccessBridge") { return true; } // callbacks if (aFunctionName == "disabled_initSystray" || aFunctionName == "disabled_deInitSystray") { return true; } // behind a BREAKPAD option if (aFunctionName == "desktop::(anonymous namespace)::crashReportInfoExists") { return true; } // LOK stuff if (aFunctionName == "doc_getTileMode") { return true; } // ignore LINK macro stuff std::string aImmediateMacro = ""; if (compiler.getSourceManager().isMacroBodyExpansion(functionDecl->getLocStart())) { StringRef name{ Lexer::getImmediateMacroName( functionDecl->getLocStart(), compiler.getSourceManager(), compiler.getLangOpts()) }; aImmediateMacro = name; if (name.startswith("IMPL_LINK_")) { return true; } } m_functionStack.emplace_back(); bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(functionDecl); Context& rContext = m_functionStack.back(); if (!rContext.ignore && rContext.values.size() == 1 && rContext.values.find("unknown") == rContext.values.end()) { report(DiagnosticsEngine::Warning, "Method only returns a single constant value %0, does it make sense?", functionDecl->getLocStart()) << *rContext.values.begin() << functionDecl->getSourceRange(); if (functionDecl != functionDecl->getCanonicalDecl()) report(DiagnosticsEngine::Note, "decl here", functionDecl->getCanonicalDecl()->getLocStart()) << functionDecl->getCanonicalDecl()->getSourceRange(); } m_functionStack.pop_back(); return ret; } bool ReturnConstant::VisitReturnStmt(ReturnStmt const* returnStmt) { if (ignoreLocation(returnStmt)) return true; if (m_functionStack.empty()) return true; Context& rContext = m_functionStack.back(); if (!returnStmt->getRetValue()) return true; if (returnStmt->getRetValue()->isTypeDependent()) { rContext.ignore = true; return true; } if (const UnaryOperator* unaryOp = dyn_cast(returnStmt->getRetValue())) { if (unaryOp->getOpcode() == UO_AddrOf) { rContext.ignore = true; return true; } } rContext.values.insert(getExprValue(returnStmt->getRetValue())); return true; } std::string ReturnConstant::getExprValue(Expr const* arg) { arg = arg->IgnoreParenCasts(); if (isa(arg)) { arg = dyn_cast(arg)->getExpr(); } arg = arg->IgnoreParenCasts(); // ignore this, it seems to trigger an infinite recursion if (isa(arg)) { return "unknown"; } APSInt x1; if (arg->EvaluateAsInt(x1, compiler.getASTContext())) { return x1.toString(10); } if (isa(arg)) { return "0"; } if (isa(arg)) { const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null(arg->IgnoreParenCasts()); if (strippedArg && strippedArg->getSubExpr()) { auto temp = dyn_cast(strippedArg->getSubExpr()); if (temp->getNumArgs() == 0) { if (loplugin::TypeCheck(temp->getType()) .Class("OUString") .Namespace("rtl") .GlobalNamespace()) { return "\"\""; } if (loplugin::TypeCheck(temp->getType()) .Class("OString") .Namespace("rtl") .GlobalNamespace()) { return "\"\""; } } } } return "unknown"; } loplugin::Plugin::Registration X("returnconstant", false); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */