summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/store
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /compilerplugins/clang/store
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compilerplugins/clang/store')
-rw-r--r--compilerplugins/clang/store/README3
-rw-r--r--compilerplugins/clang/store/badvectorinit.cxx214
-rw-r--r--compilerplugins/clang/store/bodynotinblock.cxx147
-rw-r--r--compilerplugins/clang/store/bodynotinblock.hxx35
-rw-r--r--compilerplugins/clang/store/cascadingassignop.cxx94
-rw-r--r--compilerplugins/clang/store/cascadingassignop.hxx35
-rw-r--r--compilerplugins/clang/store/cascadingcondop.cxx85
-rw-r--r--compilerplugins/clang/store/cascadingcondop.hxx35
-rw-r--r--compilerplugins/clang/store/changefunctioncalls.cxx91
-rw-r--r--compilerplugins/clang/store/checkunusedparams.cxx502
-rw-r--r--compilerplugins/clang/store/comparisonwithconstant.cxx168
-rw-r--r--compilerplugins/clang/store/constantfunction.cxx506
-rw-r--r--compilerplugins/clang/store/constfields.cxx596
-rwxr-xr-xcompilerplugins/clang/store/constfields.py86
-rw-r--r--compilerplugins/clang/store/constfieldsrewrite.cxx169
-rw-r--r--compilerplugins/clang/store/constparams.cxx618
-rw-r--r--compilerplugins/clang/store/constvars.cxx558
-rw-r--r--compilerplugins/clang/store/convertlong.cxx139
-rw-r--r--compilerplugins/clang/store/countusersofdefaultparams.cxx250
-rwxr-xr-xcompilerplugins/clang/store/countusersofdefaultparams.py80
-rw-r--r--compilerplugins/clang/store/deadclass.cxx69
-rw-r--r--compilerplugins/clang/store/defaultparams.cxx128
-rw-r--r--compilerplugins/clang/store/deletedspecial.cxx153
-rw-r--r--compilerplugins/clang/store/derivedclass.cxx70
-rw-r--r--compilerplugins/clang/store/dodgyswitch.cxx78
-rw-r--r--compilerplugins/clang/store/doubleconvert.cxx83
-rw-r--r--compilerplugins/clang/store/finalprotected.cxx84
-rw-r--r--compilerplugins/clang/store/findoncontainer.cxx77
-rw-r--r--compilerplugins/clang/store/fpcomparison.cxx382
-rw-r--r--compilerplugins/clang/store/inlinefields.cxx251
-rwxr-xr-xcompilerplugins/clang/store/inlinefields.py72
-rw-r--r--compilerplugins/clang/store/inlinesimplememberfunctions.cxx294
-rw-r--r--compilerplugins/clang/store/lclstaticfix.cxx54
-rw-r--r--compilerplugins/clang/store/lclstaticfix.hxx30
-rw-r--r--compilerplugins/clang/store/manualrefcount.cxx323
-rw-r--r--compilerplugins/clang/store/memoryvar.cxx238
-rw-r--r--compilerplugins/clang/store/namespaceindentation.cxx220
-rw-r--r--compilerplugins/clang/store/oncevar.cxx414
-rw-r--r--compilerplugins/clang/store/optmove.cxx161
-rw-r--r--compilerplugins/clang/store/optvalue.cxx66
-rw-r--r--compilerplugins/clang/store/paintmethodconversion.cxx94
-rw-r--r--compilerplugins/clang/store/postfixincrementfix.cxx132
-rw-r--r--compilerplugins/clang/store/postfixincrementfix.hxx35
-rw-r--r--compilerplugins/clang/store/putpoolitem.cxx103
-rw-r--r--compilerplugins/clang/store/refassign.cxx151
-rw-r--r--compilerplugins/clang/store/removeforwardstringdecl.cxx78
-rw-r--r--compilerplugins/clang/store/removeforwardstringdecl.hxx32
-rw-r--r--compilerplugins/clang/store/removevirtuals.cxx150
-rw-r--r--compilerplugins/clang/store/returnbyref.cxx138
-rw-r--r--compilerplugins/clang/store/returnunique.cxx83
-rw-r--r--compilerplugins/clang/store/revisibility.cxx89
-rw-r--r--compilerplugins/clang/store/rtlconstasciimacro.cxx144
-rw-r--r--compilerplugins/clang/store/sequentialassign.cxx327
-rw-r--r--compilerplugins/clang/store/sfxitemsetrewrite.cxx419
-rw-r--r--compilerplugins/clang/store/shouldreturnbool.cxx248
-rw-r--r--compilerplugins/clang/store/simplifybool.cxx1333
-rw-r--r--compilerplugins/clang/store/staticvar.cxx213
-rw-r--r--compilerplugins/clang/store/stdexception.cxx188
-rw-r--r--compilerplugins/clang/store/stringbuffer.cxx75
-rw-r--r--compilerplugins/clang/store/stringliteraldefine.cxx171
-rw-r--r--compilerplugins/clang/store/stringloop.cxx292
-rw-r--r--compilerplugins/clang/store/stylepolice.cxx196
-rw-r--r--compilerplugins/clang/store/svstreamoutputoperators.cxx223
-rw-r--r--compilerplugins/clang/store/test/deadclass.cxx15
-rw-r--r--compilerplugins/clang/store/toolslong.cxx653
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial1.cxx68
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial1.hxx35
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial1_example.cxx21
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial2.cxx95
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial2.hxx35
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial2_example.cxx18
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial3.cxx77
-rw-r--r--compilerplugins/clang/store/tutorial/tutorial3.hxx37
-rw-r--r--compilerplugins/clang/store/unique2optional.cxx264
-rw-r--r--compilerplugins/clang/store/unusedcode.cxx77
-rw-r--r--compilerplugins/clang/store/unusedfieldsremove.cxx137
-rw-r--r--compilerplugins/clang/store/unusedindex.cxx87
-rw-r--r--compilerplugins/clang/store/unusedmethodsremove.cxx153
-rw-r--r--compilerplugins/clang/store/valueof.cxx148
79 files changed, 14492 insertions, 0 deletions
diff --git a/compilerplugins/clang/store/README b/compilerplugins/clang/store/README
new file mode 100644
index 0000000000..b562544128
--- /dev/null
+++ b/compilerplugins/clang/store/README
@@ -0,0 +1,3 @@
+This plugin actions are not used. They are still kept in case they would be useful again
+(they can be activated again by simply moving them back in the clang/ source directory)
+or simply as a reference when writing new plugins.
diff --git a/compilerplugins/clang/store/badvectorinit.cxx b/compilerplugins/clang/store/badvectorinit.cxx
new file mode 100644
index 0000000000..4ab086d9ec
--- /dev/null
+++ b/compilerplugins/clang/store/badvectorinit.cxx
@@ -0,0 +1,214 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include "plugin.hxx"
+
+/**
+
+Comments from Bjoern Michaelsen:
+
+Killing the 1-argument vector fill constructor:
+
+ std::vector< basebmp::Color > aDevPal(2);
+
+in general is probably a Good Thing(tm). It can just be too misleading.
+Requiring at least the explicit two-value fill constructor for the rare cases where
+someone wants a filled vector isn't too much to ask and less prone to
+misunderstandings:
+
+ std::vector< basebmp::Color > aDevPal(2, basebmp::Color(0,0,0));
+
+Although that _still_ might be misleading[1], so turning all those into the
+somewhat longer, but more explicit:
+
+ std::vector< basebmp::Color > aDevPal;
+ aDevPal.reserve(2);
+ aDevPal.push_back(...);
+ ...
+
+> So I suppose the check would be for a size reservation on a vector
+> followed by push_back - rather than some array indexing - does that make
+> sense ? or did I go crazy ;-)
+
+Yes, in general you want neither of the above forms. Preferably instead of
+e.g.:
+
+ std::vector< basebmp::Color > aDevPal(2);
+ aDevPal[0] = basebmp::Color( 0, 0, 0 );
+ aDevPal[1] = basebmp::Color( 0xff, 0xff, 0xff );
+
+you would -- if possible -- simply:
+
+ std::vector< basebmp::Color > aDevPal{
+ basebmp::Color( 0, 0, 0 ),
+ basebmp::Color( 0xff, 0xff, 0xff ) };
+
+and only for complex cases, where you do not have the elements statically
+available, something like:
+
+ std::vector< foo > vFoos;
+ vFoos.reserve(vInput.size());
+ std::transform(std::back_inserter(vFoos),
+ vInput.begin(),
+ vInput.end(),
+ [] (decltype(vInput)::value_type aInputValue) { return do_something(aInputValue); });
+
+see also:
+https://skyfromme.wordpress.com/2015/03/02/50-ways-to-fill-your-vector/
+https://skyfromme.wordpress.com/2015/03/12/following-the-white-rabbit/
+(tl;dr: Use initializer lists to fill vectors when possible).
+
+Best,
+
+Bjoern
+
+[1] Well, except that:
+ std::vector<int>(3, 0)
+ is doing something different from:
+ std::vector<int>{3, 0}
+ just to make things more interesting. But hey, that's C++ for you.
+ But that wart exists for the 1-arg ctor too -- yet another reason to kill that.
+*/
+
+namespace {
+
+
+class BadVectorInit:
+ public loplugin::FilteringPlugin<BadVectorInit>
+{
+public:
+ explicit BadVectorInit(InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitCXXConstructExpr(const CXXConstructExpr* );
+ bool TraverseFunctionDecl(FunctionDecl* );
+ bool VisitCXXMemberCallExpr(const CXXMemberCallExpr* );
+private:
+ StringRef getFilename(SourceLocation loc);
+ std::set<const VarDecl*> suspectSet;
+};
+
+bool BadVectorInit::TraverseFunctionDecl(FunctionDecl* decl)
+{
+ bool ret = RecursiveASTVisitor::TraverseFunctionDecl(decl);
+ suspectSet.clear();
+ return ret;
+}
+
+StringRef BadVectorInit::getFilename(SourceLocation loc)
+{
+ SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(loc);
+ StringRef name { getFilenameOfLocation(spellingLocation) };
+ return name;
+}
+
+bool BadVectorInit::VisitCXXMemberCallExpr(const CXXMemberCallExpr* expr)
+{
+ if (suspectSet.empty() || ignoreLocation( expr ))
+ return true;
+
+ // need to exclude some false positives
+ StringRef aFileName = getFilename(expr->getLocStart());
+ if (aFileName == SRCDIR "/framework/source/services/autorecovery.cxx"
+ || aFileName == SRCDIR "/vcl/source/opengl/OpenGLHelper.cxx"
+ || aFileName == SRCDIR "/vcl/source/gdi/gdimtf.cxx"
+ )
+ {
+ return true;
+ }
+
+ const FunctionDecl* functionDecl = expr->getDirectCallee();
+ if (!functionDecl)
+ return true;
+ if (functionDecl->getNameAsString().find("push_back") == string::npos)
+ return true;
+ const DeclRefExpr* declExpr = dyn_cast<DeclRefExpr>(expr->getImplicitObjectArgument());
+ if (!declExpr)
+ return true;
+ const VarDecl* varDecl = dyn_cast<VarDecl>(declExpr->getDecl());
+ if (!varDecl)
+ return true;
+ varDecl = varDecl->getCanonicalDecl();
+ if (suspectSet.find(varDecl) == suspectSet.end())
+ return true;
+ report(
+ DiagnosticsEngine::Warning,
+ "calling push_back after using sized constructor",
+ expr->getLocStart())
+ << expr->getSourceRange();
+ report(
+ DiagnosticsEngine::Note,
+ "on this var",
+ varDecl->getLocStart())
+ << varDecl->getSourceRange();
+
+ return true;
+}
+
+bool BadVectorInit::VisitCXXConstructExpr(const CXXConstructExpr* expr)
+{
+ if (ignoreLocation( expr ))
+ return true;
+
+ const CXXConstructorDecl *consDecl = expr->getConstructor();
+ consDecl = consDecl->getCanonicalDecl();
+
+ // The default constructor can potentially have a parameter, e.g.
+ // in glibcxx-debug the default constructor is:
+ // explicit vector(const _Allocator& __a = _Allocator())
+ if (consDecl->param_size() == 0 || consDecl->isDefaultConstructor())
+ return true;
+
+ std::string aParentName = consDecl->getParent()->getQualifiedNameAsString();
+ if (aParentName.find("vector") == string::npos && aParentName.find("deque") == string::npos)
+ return true;
+
+ // ignore the copy/move constructors, and those taking an initializer_list
+ // etc.:
+ if (consDecl->isCopyConstructor() || consDecl->isMoveConstructor())
+ return true;
+ const ParmVarDecl* pParam = consDecl->getParamDecl(0);
+ std::string aParam1 = pParam->getOriginalType().getAsString();
+ if (aParam1.find("initializer_list") != string::npos
+ || aParam1.find("iterator") != string::npos)
+ return true;
+
+ // found a call to the 1-arg vector constructor, now look for the VarDecl it belongs to
+
+ const Stmt* parent = expr;
+ do {
+ parent = parentStmt(parent);
+ if (!parent) break;
+ if (isa<DeclStmt>(parent))
+ {
+ const DeclStmt* declStmt = dyn_cast<DeclStmt>(parent);
+ const Decl* decl = declStmt->getSingleDecl();
+ if (decl && isa<VarDecl>(decl))
+ suspectSet.insert(dyn_cast<VarDecl>(decl)->getCanonicalDecl());
+ break;
+ }
+ } while (true);
+
+ return true;
+}
+
+loplugin::Plugin::Registration< BadVectorInit > X("badvectorinit", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/bodynotinblock.cxx b/compilerplugins/clang/store/bodynotinblock.cxx
new file mode 100644
index 0000000000..ca4f904992
--- /dev/null
+++ b/compilerplugins/clang/store/bodynotinblock.cxx
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "bodynotinblock.hxx"
+
+namespace loplugin
+{
+
+/*
+This is a compile check.
+
+Check for two statements that are both indented to look like a body of if/while/for
+but are not inside a compound statement and thus the second one is unrelated.
+
+For example:
+
+ if( a != 0 )
+ b = 2;
+ c = 3;
+
+Here either both statements should be inside {} or the second statement in indented wrong.
+*/
+
+BodyNotInBlock::BodyNotInBlock( const InstantiationData& data )
+ : Plugin( data )
+ {
+ }
+
+void BodyNotInBlock::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool BodyNotInBlock::VisitIfStmt( const IfStmt* stmt )
+ {
+ if( ignoreLocation( stmt ))
+ return true;
+ checkBody( stmt->getThen(), stmt->getIfLoc(), 0, stmt->getElse() != NULL );
+ checkBody( stmt->getElse(), stmt->getElseLoc(), 0 );
+ return true;
+ }
+
+bool BodyNotInBlock::VisitWhileStmt( const WhileStmt* stmt )
+ {
+ if( ignoreLocation( stmt ))
+ return true;
+ checkBody( stmt->getBody(), stmt->getWhileLoc(), 1 );
+ return true;
+ }
+
+bool BodyNotInBlock::VisitForStmt( const ForStmt* stmt )
+ {
+ if( ignoreLocation( stmt ))
+ return true;
+ checkBody( stmt->getBody(), stmt->getForLoc(), 2 );
+ return true;
+ }
+
+bool BodyNotInBlock::VisitCXXForRangeStmt( const CXXForRangeStmt* stmt )
+ {
+ if( ignoreLocation( stmt ))
+ return true;
+ checkBody( stmt->getBody(), stmt->getForLoc(), 2 );
+ return true;
+ }
+
+void BodyNotInBlock::checkBody( const Stmt* body, SourceLocation stmtLocation, int stmtType, bool dontGoUp )
+ {
+ if( body == NULL )
+ return;
+ // TODO: If the if/else/while/for comes from a macro expansion, ignore it completely for
+ // now. The code below could assume everything is in the same place (and thus also column)
+ // and give a false warning. Moreover some macros are rather loosely written and would
+ // result in poor formatting. To be evaluated later, maybe this could be handled
+ // including macro expansion.
+ if( stmtLocation.isMacroID())
+ return;
+ if( dyn_cast< CompoundStmt >( body ))
+ return; // if body is a compound statement, then it is in {}
+ const Stmt* previousParent = parentStmt( body ); // Here the statement itself.
+ // Find the next statement (in source position) after 'body'.
+ for(;;)
+ {
+ const Stmt* parent = parentStmt( previousParent );
+ if( parent == NULL )
+ break;
+ for( ConstStmtIterator it = parent->child_begin();
+ it != parent->child_end();
+ )
+ {
+ if( *it == previousParent ) // found grand(grand...)parent
+ {
+ // get next statement after our (grand...)parent
+ ++it;
+ while( it != parent->child_end() && *it == NULL )
+ ++it; // skip empty ones (missing 'else' bodies for example)
+ if( it != parent->child_end())
+ {
+ bool invalid1, invalid2;
+ unsigned bodyColumn = compiler.getSourceManager()
+ .getPresumedColumnNumber( body->getLocStart(), &invalid1 );
+ unsigned nextStatementColumn = compiler.getSourceManager()
+ .getPresumedColumnNumber( (*it)->getLocStart(), &invalid2 );
+ if( invalid1 || invalid2 )
+ return;
+ if( bodyColumn == nextStatementColumn )
+ {
+ report( DiagnosticsEngine::Warning,
+ "statement aligned as second statement in %select{if|while|for}0 body but not in a statement block",
+ (*it)->getLocStart()) << stmtType;
+ report( DiagnosticsEngine::Note,
+ "%select{if|while|for}0 body statement is here",
+ body->getLocStart()) << stmtType;
+ }
+ return;
+ }
+ // else we need to go higher to find the next statement
+ }
+ else
+ ++it;
+ }
+ // If going up would mean leaving a {} block, stop, because the } should
+ // make it visible the two statements are not in the same body.
+ if( dyn_cast< CompoundStmt >( parent ))
+ return;
+ // If the body to be checked is a body of an if statement that has also
+ // an else part, don't go up, the else is after the body and should make
+ // it clear the body does not continue there.
+ if( dontGoUp )
+ return;
+ previousParent = parent;
+ }
+ }
+
+static Plugin::Registration< BodyNotInBlock > X( "bodynotinblock" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/bodynotinblock.hxx b/compilerplugins/clang/store/bodynotinblock.hxx
new file mode 100644
index 0000000000..d74a4fbc16
--- /dev/null
+++ b/compilerplugins/clang/store/bodynotinblock.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+class BodyNotInBlock
+ : public loplugin::FilteringPlugin<BodyNotInBlock>
+ {
+ public:
+ explicit BodyNotInBlock( const InstantiationData& data );
+ virtual void run() override;
+ bool VisitIfStmt( const IfStmt* stmt );
+ bool VisitWhileStmt( const WhileStmt* stmt );
+ bool VisitForStmt( const ForStmt* stmt );
+ bool VisitCXXForRangeStmt( const CXXForRangeStmt* stmt );
+ private:
+ void checkBody( const Stmt* body, SourceLocation stmtLocation, int stmtType, bool dontGoUp = false );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/cascadingassignop.cxx b/compilerplugins/clang/store/cascadingassignop.cxx
new file mode 100644
index 0000000000..a1098ee064
--- /dev/null
+++ b/compilerplugins/clang/store/cascadingassignop.cxx
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "cascadingassignop.hxx"
+
+/*
+This is a compile check.
+
+It checks for complex statements with conditional operators in conditional
+operators, which are error prone, e.g.
+ Thing foo = IsBar() ? ( IsBaz() ? b1 : b2 ) : b3;
+
+However, it finds 556 cases in sw/source alone, thus likely needs some more
+restricting, e.g. by checking for multiline conditional operator statements or
+a certain length in characters (but that needs the Context/SourceManager, which
+I haven't played with yet).
+*/
+
+// the value is rather arbitrary, but code above this number of stmts begins to
+// be smelly
+static const int stmtlimit = 20;
+
+namespace loplugin
+{
+
+struct WalkCounter
+{
+ int stmtcount;
+ bool cascading;
+ bool conditionals;
+};
+
+// Ctor, nothing special, pass the argument(s).
+CascadingAssignOp::CascadingAssignOp( const InstantiationData& data )
+ : FilteringPlugin( data )
+{
+}
+
+// Perform the actual action.
+void CascadingAssignOp::run()
+{
+ // Traverse the whole AST of the translation unit (i.e. examine the whole source file).
+ // The Clang AST helper class will call VisitReturnStmt for every return statement.
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+}
+
+void CascadingAssignOp::Walk( const Stmt* stmt, WalkCounter& c )
+{
+ for(Stmt::const_child_iterator it = stmt->child_begin(); it != stmt->child_end(); ++it)
+ {
+ ++c.stmtcount;
+ const BinaryOperator* binop = dyn_cast< BinaryOperator >( *it );
+ if ( binop )
+ {
+ if ( (binop->isAssignmentOp() || binop->isCompoundAssignmentOp()))
+ c.cascading = true;
+ if ( dyn_cast< AbstractConditionalOperator >( binop ) || binop->isLogicalOp())
+ c.conditionals = true;
+ }
+ Walk(*it, c);
+ }
+}
+
+bool CascadingAssignOp::VisitStmt( const Stmt* stmt )
+{
+ const BinaryOperator* binop = dyn_cast< BinaryOperator >( stmt );
+ if ( binop && (binop->isAssignmentOp() || binop->isCompoundAssignmentOp()))
+ {
+ WalkCounter c = { 0, false, false };
+ Walk(binop, c);
+ if(c.cascading && c.conditionals && c.stmtcount >= stmtlimit)
+ {
+ std::string msg("cascading assign operator mixing in conditionals, complexity: ");
+ msg.append(std::to_string(c.stmtcount));
+ report( DiagnosticsEngine::Warning, msg, binop->getLocStart());
+ }
+ }
+ return true;
+}
+
+// Register the plugin action with the LO plugin handling.
+static Plugin::Registration< CascadingAssignOp > X( "cascadingassignop" );
+
+} // namespace loplugin
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/cascadingassignop.hxx b/compilerplugins/clang/store/cascadingassignop.hxx
new file mode 100644
index 0000000000..147ecaad0d
--- /dev/null
+++ b/compilerplugins/clang/store/cascadingassignop.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+struct WalkCounter;
+
+// The class implementing the plugin action.
+class CascadingAssignOp
+ // Inherits from the Clang class that will allow examining the Clang AST tree (i.e. syntax tree).
+ : public FilteringPlugin< CascadingAssignOp >
+ {
+ public:
+ CascadingAssignOp( const InstantiationData& data );
+ virtual void run() override;
+ void Walk( const Stmt* stmt, WalkCounter& c );
+ bool VisitStmt( const Stmt* stmt );
+ };
+
+} // namespace loplugin
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/cascadingcondop.cxx b/compilerplugins/clang/store/cascadingcondop.cxx
new file mode 100644
index 0000000000..4671f41b43
--- /dev/null
+++ b/compilerplugins/clang/store/cascadingcondop.cxx
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "cascadingcondop.hxx"
+
+/*
+This is a compile check.
+
+It checks for complex statements with conditional operators in conditional
+operators, which are error prone, e.g.
+ Thing foo = IsBar() ? ( IsBaz() ? b1 : b2 ) : b3;
+
+However, it finds 556 cases in sw/source alone, thus likely needs some more
+restricting, e.g. by checking for multiline conditional operator statements or
+a certain length in characters (but that needs the Context/SourceManager, which
+I haven't played with yet).
+*/
+
+// the value is rather arbitrary, but code above this number of stmts begins to
+// be smelly
+static const int stmtlimit = 50;
+
+namespace loplugin
+{
+struct WalkCounter
+{
+ int stmtcount;
+ bool cascading;
+};
+
+// Ctor, nothing special, pass the argument(s).
+CascadingCondOp::CascadingCondOp(const InstantiationData& data)
+ : FilteringPlugin(data)
+{
+}
+
+// Perform the actual action.
+void CascadingCondOp::run()
+{
+ // Traverse the whole AST of the translation unit (i.e. examine the whole source file).
+ // The Clang AST helper class will call VisitReturnStmt for every return statement.
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+}
+
+void CascadingCondOp::Walk(const Stmt* stmt, WalkCounter& c)
+{
+ for (Stmt::const_child_iterator it = stmt->child_begin(); it != stmt->child_end(); ++it)
+ {
+ ++c.stmtcount;
+ if (dyn_cast<ConditionalOperator>(*it))
+ c.cascading = true;
+ Walk(*it, c);
+ }
+}
+
+bool CascadingCondOp::VisitStmt(const Stmt* stmt)
+{
+ if (const ConditionalOperator* condop = dyn_cast<ConditionalOperator>(stmt))
+ {
+ WalkCounter c = { 0, false };
+ Walk(condop, c);
+ if (c.cascading && c.stmtcount >= stmtlimit)
+ {
+ std::string msg("cascading conditional operator, complexity: ");
+ msg.append(std::to_string(c.stmtcount));
+ report(DiagnosticsEngine::Warning, msg, condop->getLocStart());
+ }
+ }
+ return true;
+}
+
+// Register the plugin action with the LO plugin handling.
+static Plugin::Registration<CascadingCondOp> X("cascadingcondop");
+
+} // namespace loplugin
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/cascadingcondop.hxx b/compilerplugins/clang/store/cascadingcondop.hxx
new file mode 100644
index 0000000000..599fafd826
--- /dev/null
+++ b/compilerplugins/clang/store/cascadingcondop.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+struct WalkCounter;
+
+// The class implementing the plugin action.
+class CascadingCondOp
+ // Inherits from the Clang class that will allow examining the Clang AST tree (i.e. syntax tree).
+ : public FilteringPlugin< CascadingCondOp >
+ {
+ public:
+ CascadingCondOp( const InstantiationData& data );
+ virtual void run() override;
+ void Walk( const Stmt* stmt, WalkCounter& c );
+ bool VisitStmt( const Stmt* stmt );
+ };
+
+} // namespace loplugin
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/changefunctioncalls.cxx b/compilerplugins/clang/store/changefunctioncalls.cxx
new file mode 100644
index 0000000000..9f5390a215
--- /dev/null
+++ b/compilerplugins/clang/store/changefunctioncalls.cxx
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+/*
+This is a rewriter.
+
+Changes all calls to a specific function (after it's been renamed or its
+arguments have changed).
+
+This specific example checks for calls to function 'void bar(unsigned int)'
+and adds '+ 10' to the argument (as plain text, so if the argument is a more
+complex expression, operator precedence may mean the result is actually different).
+
+This can be easily adjusted for different modifications to a function:
+- replace CallExpr with CXXOperatorCallExpr or CXXMemberCallExpr
+- check different names or arguments
+- change getDirectCallee() to getCallee()
+- etc.
+*/
+
+#include "plugin.hxx"
+#include "check.hxx"
+
+namespace loplugin
+{
+
+class ChangeFunctionCalls
+ : public loplugin::FilteringRewritePlugin< ChangeFunctionCalls >
+ {
+ public:
+ explicit ChangeFunctionCalls( CompilerInstance& compiler, Rewriter& rewriter );
+ virtual void run() override;
+ bool VisitCallExpr( const CallExpr* call );
+ };
+
+ChangeFunctionCalls::ChangeFunctionCalls( CompilerInstance& compiler, Rewriter& rewriter )
+ : FilteringRewritePlugin( compiler, rewriter )
+ {
+ }
+
+void ChangeFunctionCalls::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool ChangeFunctionCalls::VisitCallExpr( const CallExpr* call )
+ {
+ if( ignoreLocation( call ))
+ return true;
+ // Using getDirectCallee() here means that we find only calls
+ // that call the function directly (i.e. not using a pointer, for example).
+ // Use getCallee() to include also those :
+ // if( const FunctionDecl* func = dyn_cast_or_null< FunctionDecl >( call->getCalleeDecl()))
+ if( const FunctionDecl* func = call->getDirectCallee())
+ {
+ // so first check fast details like number of arguments or the (unqualified)
+ // name before checking the fully qualified name.
+ // See FunctionDecl for all the API about the function.
+ if( func->getNumParams() == 1 && func->getIdentifier() != NULL
+ && ( func->getName() == "bar" ))
+ {
+ auto qt = loplugin::DeclCheck(func);
+ if( qt.Function("bar").GlobalNamespace() )
+ {
+ // Further checks about arguments. Check mainly ParmVarDecl, VarDecl,
+ // ValueDecl and QualType for Clang API details.
+ string arg0 = func->getParamDecl( 0 )->getType().getAsString();
+ if( arg0 == "unsigned int" )
+ {
+ insertTextAfterToken( call->getArg( 0 )->getLocEnd(), " + 10" );
+ report( DiagnosticsEngine::Warning, "found", call->getLocStart());
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+static Plugin::Registration< ChangeFunctionCalls > X( "changefunctioncalls" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/checkunusedparams.cxx b/compilerplugins/clang/store/checkunusedparams.cxx
new file mode 100644
index 0000000000..2f45049632
--- /dev/null
+++ b/compilerplugins/clang/store/checkunusedparams.cxx
@@ -0,0 +1,502 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <set>
+#include <iostream>
+
+#include "config_clang.h"
+
+#include "plugin.hxx"
+
+/**
+Find parameters that have no name, i.e. they are unused and we're worked around the "unused parameter" warning.
+
+Most of these can be removed.
+
+TODO look for places where we are working around the warning by doing
+ (void) param1;
+ */
+namespace {
+
+class CheckUnusedParams: public loplugin::FilteringPlugin<CheckUnusedParams> {
+public:
+ explicit CheckUnusedParams(loplugin::InstantiationData const & data):
+ FilteringPlugin(data) {}
+ void run() override;
+ bool VisitFunctionDecl(FunctionDecl const *);
+ bool VisitUnaryOperator(UnaryOperator const *);
+ bool VisitInitListExpr(InitListExpr const *);
+ bool VisitCallExpr(CallExpr const *);
+ bool VisitBinaryOperator(BinaryOperator const *);
+ bool VisitCXXConstructExpr(CXXConstructExpr const *);
+private:
+ void checkForFunctionDecl(Expr const *, bool bCheckOnly = false);
+ std::set<FunctionDecl const *> m_addressOfSet;
+ enum class PluginPhase { FindAddressOf, Warning };
+ PluginPhase m_phase;
+};
+
+void CheckUnusedParams::run()
+{
+ StringRef fn(handler.getMainFileName());
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/"))
+ return;
+ // Taking pointer to function
+ if (loplugin::isSamePathname(fn, SRCDIR "/l10ntools/source/xmlparse.cxx"))
+ return;
+ // macro magic which declares something needed by an external library
+ if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/misc/gridprinter.cxx"))
+ return;
+
+ // valid test/qa code
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/compilerplugins/clang/test/"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/cppu/qa/test_reference.cxx"))
+ return;
+
+ // leave this alone for now
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/libreofficekit/"))
+ return;
+ // this has a certain pattern to its code which appears to include lots of unused params
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/xmloff/"))
+ return;
+ // I believe someone is busy working on this chunk of code
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/docshell/dataprovider.cxx"))
+ return;
+ // I think erack is working on stuff here
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/excel/xiformula.cxx"))
+ return;
+ // lots of callbacks here
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/lotus/op.cxx"))
+ return;
+ // template magic
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/html/htmlpars.cxx"))
+ return;
+
+ m_phase = PluginPhase::FindAddressOf;
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ m_phase = PluginPhase::Warning;
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+}
+
+bool CheckUnusedParams::VisitUnaryOperator(UnaryOperator const * op) {
+ if (op->getOpcode() != UO_AddrOf) {
+ return true;
+ }
+ if (m_phase != PluginPhase::FindAddressOf)
+ return true;
+ checkForFunctionDecl(op->getSubExpr());
+ return true;
+}
+
+bool CheckUnusedParams::VisitBinaryOperator(BinaryOperator const * binaryOperator) {
+ if (binaryOperator->getOpcode() != BO_Assign) {
+ return true;
+ }
+ if (m_phase != PluginPhase::FindAddressOf)
+ return true;
+ checkForFunctionDecl(binaryOperator->getRHS());
+ return true;
+}
+
+bool CheckUnusedParams::VisitCallExpr(CallExpr const * callExpr) {
+ if (m_phase != PluginPhase::FindAddressOf)
+ return true;
+ for (auto arg : callExpr->arguments())
+ checkForFunctionDecl(arg);
+ return true;
+}
+
+bool CheckUnusedParams::VisitCXXConstructExpr(CXXConstructExpr const * constructExpr) {
+ if (m_phase != PluginPhase::FindAddressOf)
+ return true;
+ for (auto arg : constructExpr->arguments())
+ checkForFunctionDecl(arg);
+ return true;
+}
+
+bool CheckUnusedParams::VisitInitListExpr(InitListExpr const * initListExpr) {
+ if (m_phase != PluginPhase::FindAddressOf)
+ return true;
+ for (auto subStmt : *initListExpr)
+ checkForFunctionDecl(dyn_cast<Expr>(subStmt));
+ return true;
+}
+
+void CheckUnusedParams::checkForFunctionDecl(Expr const * expr, bool bCheckOnly) {
+ auto e1 = expr->IgnoreParenCasts();
+ auto declRef = dyn_cast<DeclRefExpr>(e1);
+ if (!declRef)
+ return;
+ auto functionDecl = dyn_cast<FunctionDecl>(declRef->getDecl());
+ if (!functionDecl)
+ return;
+ if (bCheckOnly)
+ getParentStmt(expr)->dump();
+ else
+ m_addressOfSet.insert(functionDecl->getCanonicalDecl());
+}
+
+static int noFieldsInRecord(RecordType const * recordType) {
+ auto recordDecl = recordType->getDecl();
+ // if it's complicated, lets just assume it has fields
+ if (isa<ClassTemplateSpecializationDecl>(recordDecl))
+ return 1;
+ return std::distance(recordDecl->field_begin(), recordDecl->field_end());
+}
+static bool startswith(const std::string& rStr, const char* pSubStr) {
+ return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
+}
+static bool endswith(const std::string& rStr, const char* pSubStr) {
+ auto len = strlen(pSubStr);
+ if (len > rStr.size())
+ return false;
+ return rStr.compare(rStr.size() - len, rStr.size(), pSubStr) == 0;
+}
+
+bool CheckUnusedParams::VisitFunctionDecl(FunctionDecl const * decl) {
+ if (m_phase != PluginPhase::Warning)
+ return true;
+ if (m_addressOfSet.find(decl->getCanonicalDecl()) != m_addressOfSet.end())
+ return true;
+ if (ignoreLocation(decl))
+ return true;
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
+ return true;
+
+ auto cxxMethodDecl = dyn_cast<CXXMethodDecl>(decl);
+ if (cxxMethodDecl) {
+ if (cxxMethodDecl->isVirtual())
+ return true;
+ auto cxxConstructorDecl = dyn_cast<CXXConstructorDecl>(cxxMethodDecl);
+ if (cxxConstructorDecl && cxxConstructorDecl->isCopyOrMoveConstructor())
+ return true;
+ }
+ if (!decl->isThisDeclarationADefinition())
+ return true;
+ if (decl->isFunctionTemplateSpecialization())
+ return true;
+ if (decl->isDeleted())
+ return true;
+ if (decl->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate)
+ return true;
+ if (decl->isOverloadedOperator())
+ return true;
+ if (decl->isExternC())
+ return true;
+
+ //TODO, filtering out any functions relating to class templates for now:
+ CXXRecordDecl const * r = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
+ if (r != nullptr
+ && (r->getTemplateSpecializationKind() != TSK_Undeclared
+ || r->isDependentContext()))
+ {
+ return true;
+ }
+ FunctionDecl const * canon = decl->getCanonicalDecl();
+ std::string fqn = canon->getQualifiedNameAsString(); // because sometimes clang returns nonsense for the filename of canon
+ if (ignoreLocation(canon))
+ return true;
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(canon->getLocation())))
+ return true;
+ StringRef fn = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(canon->getBeginLoc()));
+ // Some backwards compat magic.
+ // TODO Can probably be removed, but need to do some checking
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/sax/fshelper.hxx"))
+ return true;
+ // Platform-specific code
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/svl/svdde.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/vcl/svmain.hxx"))
+ return true;
+ // passing pointer to function
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/vcl/BitmapReadAccess.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/unx/gtk/gtkobject.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/unx/gtk/gtksalframe.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/unx/gtk/gtkframe.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/gtk/fpicker/SalGtkFilePicker.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/extensions/source/propctrlr/propertyeditor.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/solar/inc/navtoolbar.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/grammar.cxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/lexer.cxx"))
+ return true;
+ // marked with a TODO/FIXME
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/sallayout.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/accessibility/inc/standard/vclxaccessiblelist.hxx"))
+ return true;
+ // these are "extern C" but clang doesn't seem to report that accurately
+ if (loplugin::isSamePathname(fn, SRCDIR "/sax/source/fastparser/fastparser.cxx"))
+ return true;
+ // these all follow the same pattern, seems a pity to break that
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/vcl/graphicfilter.hxx"))
+ return true;
+ // looks like work in progress
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/ipdf/pdfdocument.cxx"))
+ return true;
+ // macro magic
+ if (loplugin::isSamePathname(fn, SRCDIR "/basctl/source/inc/basidesh.hxx"))
+ return true;
+ // template magic
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/include/canvas/"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/comphelper/unwrapargs.hxx"))
+ return true;
+ // this looks like vaguely useful code (ParseError) that I'm loathe to remove
+ if (loplugin::isSamePathname(fn, SRCDIR "/connectivity/source/inc/RowFunctionParser.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/svx/EnhancedCustomShapeFunctionParser.hxx"))
+ return true;
+ // TODO marker parameter in constructor, should probably be using an enum
+ if (loplugin::isSamePathname(fn, SRCDIR "/framework/inc/uielement/uicommanddescription.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/SlideTransitionPane.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/animations/CustomAnimationPane.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/table/TableDesignPane.hxx"))
+ return true;
+ // debug stuff
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/core/data/column2.cxx"))
+ return true;
+ // weird stuff
+ if (loplugin::isSamePathname(fn, SRCDIR "/scaddins/source/analysis/analysishelper.hxx"))
+ return true;
+ // SFX_DECL_CHILDWINDOWCONTEXT macro stuff
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/NavigatorChildWindow.hxx"))
+ return true;
+ // TODO, need to remove this from the .sdi file too
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/SlideSorterViewShell.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/OutlineViewShell.hxx"))
+ return true;
+ // SFX_DECL_INTERFACE macro stuff
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/ViewShellBase.hxx"))
+ return true;
+ // debug stuff
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/filter/ppt/pptinanimations.hxx"))
+ return true;
+ // takes pointer to fn
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/sfx2/shell.hxx"))
+ return true;
+ // TODO, need to remove this from the .sdi file too
+ if (fqn == "SfxObjectShell::StateView_Impl")
+ return true;
+ // SFX_DECL_CHILDWINDOW_WITHID macro
+ if (loplugin::isSamePathname(fn, SRCDIR "/include/sfx2/infobar.hxx"))
+ return true;
+ // this looks like vaguely useful code (ParseError) that I'm loathe to remove
+ if (loplugin::isSamePathname(fn, SRCDIR "/slideshow/source/inc/slideshowexceptions.hxx"))
+ return true;
+ // SFX_DECL_VIEWFACTORY macro
+ if (loplugin::isSamePathname(fn, SRCDIR "/starmath/inc/view.hxx"))
+ return true;
+ // debugging
+ if (fqn == "BrowseBox::DoShowCursor" || fqn == "BrowseBox::DoHideCursor")
+ return true;
+ // if I change this one, it then overrides a superclass virtual method
+ if (fqn == "GalleryBrowser2::KeyInput")
+ return true;
+ // takes pointer to function
+ if (fqn == "cmis::AuthProvider::onedriveAuthCodeFallback" || fqn == "cmis::AuthProvider::gdriveAuthCodeFallback")
+ return true;
+ if (fqn == "ooo_mount_operation_ask_password")
+ return true;
+ // TODO tricky to remove because of default params
+ if (fqn == "xmloff::OAttribute2Property::addBooleanProperty")
+ return true;
+ // taking pointer to function
+ if (fqn == "sw::DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl"
+ || fqn == "sw::DocumentContentOperationsManager::DeleteRangeImpl"
+ || fqn == "SwTableFormula::GetFormulaBoxes"
+ || fqn == "SwFEShell::Drag"
+ || fqn == "GetASCWriter" || fqn == "GetHTMLWriter" || fqn == "GetXMLWriter"
+ || fqn == "SwWrtShell::UpdateLayoutFrame" || fqn == "SwWrtShell::DefaultDrag"
+ || fqn == "SwWrtShell::DefaultEndDrag"
+ || startswith(fqn, "SwWW8ImplReader::Read_"))
+ return true;
+ // WIN32 only
+ if (fqn == "SwFntObj::GuessLeading")
+ return true;
+ // SFX_DECL_CHILDWINDOW_WITHID macro
+ if (fqn == "SwSpellDialogChildWindow::SwSpellDialogChildWindow"
+ || fqn == "SwFieldDlgWrapper::SwFieldDlgWrapper"
+ || fqn == "SwInputChild::SwInputChild")
+ return true;
+ // SFX_DECL_VIEWFACTORY macro
+ if (fqn == "SwSrcView::SwSrcView")
+ return true;
+ // Serves to disambiguate two very similar methods
+ if (fqn == "MSWordStyles::BuildGetSlot")
+ return true;
+ // TODO there are just too many default params to make this worth fixing right now
+ if (fqn == "ScDocument::CopyMultiRangeFromClip")
+ return true;
+ // TODO looks like this needs fixing?
+ if (fqn == "ScTable::ExtendPrintArea")
+ return true;
+ // there is a FIXME in the code
+ if (fqn == "ScRangeUtil::IsAbsTabArea")
+ return true;
+ // SFX_DECL_CHILDWINDOW_WITHID
+ if (fqn == "ScInputWindowWrapper::ScInputWindowWrapper"
+ || fqn == "sc::SearchResultsDlgWrapper::SearchResultsDlgWrapper")
+ return true;
+ // ExecMethod in .sdi file
+ if (fqn == "ScChartShell::ExecuteExportAsGraphic")
+ return true;
+ // bool marker parameter
+ if (fqn == "SvxIconReplacementDialog::SvxIconReplacementDialog")
+ return true;
+ // used as pointer to fn
+ if (endswith(fqn, "_createInstance"))
+ return true;
+ // callback
+ if (startswith(fqn, "SbRtl_"))
+ return true;
+ // takes pointer to fn
+ if (fqn == "migration::BasicMigration_create" || fqn == "migration::WordbookMigration_create"
+ || fqn == "comp_CBlankNode::_create" || fqn == "comp_CURI::_create"
+ || fqn == "comp_CLiteral::_create" || fqn == "CDocumentBuilder::_getInstance"
+ || fqn == "DOM::CDocumentBuilder::_getInstance"
+ || fqn == "xml_security::serial_number_adapter::create"
+ || fqn == "desktop::splash::create" || fqn == "ScannerManager_CreateInstance"
+ || fqn == "formula::FormulaOpCodeMapperObj::create"
+ || fqn == "(anonymous namespace)::createInstance"
+ || fqn == "x_error_handler"
+ || fqn == "warning_func"
+ || fqn == "error_func"
+ || fqn == "ScaDateAddIn_CreateInstance"
+ || fqn == "ScaPricingAddIn_CreateInstance"
+ || fqn == "(anonymous namespace)::PDFSigningPKCS7PasswordCallback"
+ || fqn == "ContextMenuEventLink"
+ || fqn == "DelayedCloseEventLink"
+ || fqn == "GDIMetaFile::ImplColMonoFnc"
+ || fqn == "vcl::getGlyph0"
+ || fqn == "vcl::getGlyph6"
+ || fqn == "vcl::getGlyph12"
+ || fqn == "setPasswordCallback"
+ || fqn == "VCLExceptionSignal_impl"
+ || fqn == "getFontTable"
+ || fqn == "textconversiondlgs::ChineseTranslation_UnoDialog::create"
+ || fqn == "pcr::DefaultHelpProvider::Create"
+ || fqn == "pcr::DefaultFormComponentInspectorModel::Create"
+ || fqn == "pcr::ObjectInspectorModel::Create"
+ || fqn == "GraphicExportFilter::GraphicExportFilter"
+ || fqn == "CertificateContainer::CertificateContainer"
+ || startswith(fqn, "ParseCSS1_")
+ )
+ return true;
+ // TODO
+ if (fqn == "FontSubsetInfo::CreateFontSubsetFromType1")
+ return true;
+ // used in template magic
+ if (fqn == "MtfRenderer::MtfRenderer" || fqn == "shell::sessioninstall::SyncDbusSessionHelper::SyncDbusSessionHelper"
+ || fqn == "dp_gui::LicenseDialog::LicenseDialog"
+ || fqn == "(anonymous namespace)::OGLTransitionFactoryImpl::OGLTransitionFactoryImpl")
+ return true;
+ // FIXME
+ if (fqn == "GtkSalDisplay::filterGdkEvent" || fqn == "SvXMLEmbeddedObjectHelper::ImplReadObject"
+ || fqn == "chart::CachedDataSequence::CachedDataSequence")
+ return true;
+ // used via macro
+ if (fqn == "framework::MediaTypeDetectionHelper::MediaTypeDetectionHelper"
+ || fqn == "framework::UriAbbreviation::UriAbbreviation"
+ || fqn == "framework::DispatchDisabler::DispatchDisabler"
+ || fqn == "framework::DispatchRecorderSupplier::DispatchRecorderSupplier")
+ return true;
+ // TODO Armin Le Grand is still working on this
+ if (fqn == "svx::frame::CreateDiagFrameBorderPrimitives"
+ || fqn == "svx::frame::CreateBorderPrimitives")
+ return true;
+ // marked with a TODO
+ if (fqn == "pcr::FormLinkDialog::getExistingRelation"
+ || fqn == "ooo::vba::DebugHelper::basicexception"
+ || fqn == "ScPrintFunc::DrawToDev")
+ return true;
+ // macros at work
+ if (fqn == "msfilter::lcl_PrintDigest")
+ return true;
+ // TODO something wrong here, the method that calls this (Normal::GenSlidingWindowFunction) cannot be correct
+ if (fqn == "sc::opencl::OpBase::Gen")
+ return true;
+ // Can't change this without conflicting with another constructor with the same signature
+ if (fqn == "XclExpSupbook::XclExpSupbook")
+ return true;
+ // ignore the LINK macros from include/tools/link.hxx
+ if (decl->getLocation().isMacroID())
+ return true;
+ // debug code in sw/
+ if (fqn == "lcl_dbg_out")
+ return true;
+
+ for( auto it = decl->param_begin(); it != decl->param_end(); ++it) {
+ auto param = *it;
+ if (param->hasAttr<UnusedAttr>())
+ continue;
+ if (!param->getName().empty())
+ continue;
+ // ignore params which are enum types with only a single enumerator, these are marker/tag types
+ auto paramType = param->getType();
+ if (paramType->isEnumeralType()) {
+ auto enumType = paramType->getAs<EnumType>();
+ int cnt = std::distance(enumType->getDecl()->enumerator_begin(), enumType->getDecl()->enumerator_end());
+ if (cnt == 1)
+ continue;
+ }
+ // ignore params which are a reference to a struct which has no fields.
+ // These are either
+ // (a) marker/tag types
+ // (b) selective "friend" access
+ if (paramType->isReferenceType()) {
+ auto referenceType = paramType->getAs<ReferenceType>();
+ if (referenceType->getPointeeType()->isRecordType()) {
+ auto recordType = referenceType->getPointeeType()->getAs<RecordType>();
+ if (noFieldsInRecord(recordType) == 0)
+ continue;
+ }
+ }
+ else if (paramType->isRecordType()) {
+ if (noFieldsInRecord(paramType->getAs<RecordType>()) == 0)
+ continue;
+ }
+ report( DiagnosticsEngine::Warning,
+ "unused param %0 in %1", param->getBeginLoc())
+ << param->getSourceRange()
+ << param->getName()
+ << fqn;
+ if (canon != decl)
+ {
+ unsigned idx = param->getFunctionScopeIndex();
+ const ParmVarDecl* pOther = canon->getParamDecl(idx);
+ report( DiagnosticsEngine::Note, "declaration is here",
+ pOther->getBeginLoc())
+ << pOther->getSourceRange();
+ }
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<CheckUnusedParams> X("checkunusedparams", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/comparisonwithconstant.cxx b/compilerplugins/clang/store/comparisonwithconstant.cxx
new file mode 100644
index 0000000000..d796b7c3a3
--- /dev/null
+++ b/compilerplugins/clang/store/comparisonwithconstant.cxx
@@ -0,0 +1,168 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+
+#include "compat.hxx"
+#include "plugin.hxx"
+
+/**
+ Look for comparisons where the constant is on the left, it should be on the right.
+ */
+
+namespace {
+
+class ComparisonWithConstant :
+ public loplugin::FilteringRewritePlugin<ComparisonWithConstant>
+{
+public:
+ explicit ComparisonWithConstant(loplugin::InstantiationData const & data): FilteringRewritePlugin(data) {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ // Deliberately drop RecursiveASTVisitor::TraverseBinaryOperator's DataRecursionQueue
+ // parameter; TraverseBinaryOperator must use stack instead of data recursion for any
+ // children's VisitBinaryOperator to see changes to occurrence_ by a parent
+ // VisitBinaryOperator:
+ bool TraverseBinaryOperator(BinaryOperator * S)
+ {
+ auto const op = S->getOpcode();
+ if (op != BO_EQ && op != BO_NE) {
+ return RecursiveASTVisitor::TraverseBinaryOperator(S);
+ }
+ auto const saved = occurrence_;
+ auto const ret = RecursiveASTVisitor::TraverseBinaryOperator(S);
+ occurrence_ = saved;
+ return ret;
+ }
+
+ bool VisitBinaryOperator(const BinaryOperator *);
+private:
+ bool rewrite(const BinaryOperator *);
+ std::string getExprAsString(SourceRange range);
+ SourceRange ignoreMacroExpansions(SourceRange range);
+
+ bool occurrence_ = false;
+};
+
+bool ComparisonWithConstant::VisitBinaryOperator(const BinaryOperator* binaryOp)
+{
+ if (ignoreLocation(binaryOp)) {
+ return true;
+ }
+ if (!(binaryOp->getOpcode() == BO_EQ || binaryOp->getOpcode() == BO_NE)) {
+ return true;
+ }
+ // protect against clang assert
+ if (binaryOp->getLHS()->isValueDependent() || binaryOp->getRHS()->isValueDependent()) {
+ return true;
+ }
+ if (!binaryOp->getLHS()->isEvaluatable(compiler.getASTContext())) {
+ return true;
+ }
+ if (binaryOp->getRHS()->isEvaluatable(compiler.getASTContext())) {
+ return true;
+ }
+ if (occurrence_ || !rewrite(binaryOp))
+ {
+ report(
+ DiagnosticsEngine::Warning, "Rather put constant on right when comparing",
+ binaryOp->getSourceRange().getBegin())
+ << binaryOp->getSourceRange();
+ }
+ occurrence_ = true;
+ return true;
+}
+
+
+bool ComparisonWithConstant::rewrite(const BinaryOperator * binaryOp) {
+ if (rewriter == nullptr) {
+ return false;
+ }
+
+ auto lhsRange = ignoreMacroExpansions(binaryOp->getLHS()->getSourceRange());
+ if (!lhsRange.isValid()) {
+ return false;
+ }
+ auto rhsRange = ignoreMacroExpansions(binaryOp->getRHS()->getSourceRange());
+ if (!rhsRange.isValid()) {
+ return false;
+ }
+
+ const std::string lhsString = getExprAsString(lhsRange);
+ const std::string rhsString = getExprAsString(rhsRange);
+
+ // switch LHS and RHS
+ if (!replaceText(lhsRange, rhsString)) {
+ return false;
+ }
+ if (!replaceText(rhsRange, lhsString)) {
+ return false;
+ }
+
+ return true;
+}
+
+// get the expression contents
+std::string ComparisonWithConstant::getExprAsString(SourceRange range)
+{
+ SourceManager& SM = compiler.getSourceManager();
+ SourceLocation startLoc = range.getBegin();
+ SourceLocation endLoc = range.getEnd();
+ const char *p1 = SM.getCharacterData( startLoc );
+ const char *p2 = SM.getCharacterData( endLoc );
+ unsigned n = Lexer::MeasureTokenLength( endLoc, SM, compiler.getLangOpts());
+ return std::string( p1, p2 - p1 + n);
+}
+
+SourceRange ComparisonWithConstant::ignoreMacroExpansions(SourceRange range) {
+ while (compiler.getSourceManager().isMacroArgExpansion(range.getBegin())) {
+ range.setBegin(
+ compiler.getSourceManager().getImmediateMacroCallerLoc(
+ range.getBegin()));
+ }
+ if (range.getBegin().isMacroID()) {
+ SourceLocation loc;
+ if (Lexer::isAtStartOfMacroExpansion(
+ range.getBegin(), compiler.getSourceManager(),
+ compiler.getLangOpts(), &loc))
+ {
+ range.setBegin(loc);
+ }
+ }
+ while (compiler.getSourceManager().isMacroArgExpansion(range.getEnd())) {
+ range.setEnd(
+ compiler.getSourceManager().getImmediateMacroCallerLoc(
+ range.getEnd()));
+ }
+ if (range.getEnd().isMacroID()) {
+ SourceLocation loc;
+ if (Lexer::isAtEndOfMacroExpansion(
+ range.getEnd(), compiler.getSourceManager(),
+ compiler.getLangOpts(), &loc))
+ {
+ range.setEnd(loc);
+ }
+ }
+ return range.getBegin().isMacroID() || range.getEnd().isMacroID()
+ ? SourceRange() : range;
+}
+
+loplugin::Plugin::Registration< ComparisonWithConstant > X("comparisonwithconstant", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/constantfunction.cxx b/compilerplugins/clang/store/constantfunction.cxx
new file mode 100644
index 0000000000..a7b88704c0
--- /dev/null
+++ b/compilerplugins/clang/store/constantfunction.cxx
@@ -0,0 +1,506 @@
+/* -*- 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 <iostream>
+
+/*
+ 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 ConstantFunction:
+ public loplugin::FilteringPlugin<ConstantFunction>
+{
+ StringRef getFilename(const FunctionDecl* functionDecl);
+public:
+ explicit ConstantFunction(InstantiationData const & data): FilteringRewritePlugin(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(), "gtkinst.cxx") != 0) {
+ return;
+ }*/
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitFunctionDecl(const FunctionDecl *);
+};
+
+StringRef ConstantFunction::getFilename(const FunctionDecl* functionDecl)
+{
+ SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(functionDecl->getCanonicalDecl()->getNameInfo().getLoc());
+ StringRef name { getFilenameOfLocation(spellingLocation) };
+ return name;
+}
+
+static bool startsWith(const std::string& rStr, const char* pSubStr) {
+ return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
+}
+
+bool ConstantFunction::VisitFunctionDecl(const FunctionDecl * pFunctionDecl) {
+ if (ignoreLocation(pFunctionDecl)) {
+ return true;
+ }
+ if (!pFunctionDecl->hasBody()) {
+ return true;
+ }
+ if (!pFunctionDecl->isThisDeclarationADefinition()) {
+ return true;
+ }
+ // stuff declared extern-C is almost always used as a some kind of callback
+ if (pFunctionDecl->isExternC()) {
+ return true;
+ }
+ if (pFunctionDecl->isConstexpr()) {
+ return true;
+ }
+ if (pFunctionDecl->isMain()) {
+ return true;
+ }
+
+ StringRef aFileName = getFilename(pFunctionDecl);
+
+ // 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;
+ }
+ // 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;
+ }
+ // 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;
+ }
+
+
+ const CXXMethodDecl *pCXXMethodDecl = dyn_cast<CXXMethodDecl>(pFunctionDecl);
+ 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 (pFunctionDecl->hasAttr<OverrideAttr>()) {
+ return true;
+ }
+ }
+ // a free function with an inline body will be optimised at compile-time to a constant anyway
+ if (!pCXXMethodDecl && pFunctionDecl->isInlineSpecified()) {
+ return true;
+ }
+ if (isa<CXXConstructorDecl>(pFunctionDecl) || isa<CXXDestructorDecl>(pFunctionDecl) || isa<CXXConversionDecl>(pFunctionDecl)) {
+ return true;
+ }
+ if (isInUnoIncludeFile(pFunctionDecl)) {
+ return true;
+ }
+
+ switch (pFunctionDecl->getOverloadedOperator()) {
+ case OO_Delete:
+ case OO_EqualEqual:
+ case OO_Call:
+ return true;
+ default:
+ break;
+ }
+
+ std::string aFunctionName = pFunctionDecl->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, 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;
+ }
+ // 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;
+ }
+ // apparently this will be useful at sometime in the future
+ if (aFunctionName == "LocaleDataWrapper::getCurrZeroChar") {
+ return true;
+ }
+ // marked with TODO
+ if (aFunctionName == "oglcanvas::TextLayout::draw") {
+ return true;
+ }
+ // called from the .sdi files
+ if (aFunctionName == "SfxObjectShell::StateView_Impl") {
+ return true;
+ }
+ // gtk callback
+ if (aFunctionName == "GtkSalFrame::signalVisibility") {
+ return true;
+ }
+ // platform-version-dependent code
+ if (aFunctionName == "(anonymous namespace)::ACTIVE_TAB") {
+ return true;
+ }
+ // SMIL callbacks
+ if (aFunctionName == "boost::sp_scalar_constructor_hook" || aFunctionName == "boost::sp_scalar_destructor_hook") {
+ return true;
+ }
+
+
+
+
+ std::string aImmediateMacro = "";
+ if (compiler.getSourceManager().isMacroBodyExpansion(pFunctionDecl->getLocStart()) ) {
+ StringRef name { Lexer::getImmediateMacroName(
+ pFunctionDecl->getLocStart(), compiler.getSourceManager(), compiler.getLangOpts()) };
+ aImmediateMacro = name;
+ if (name.startswith("IMPL_LINK_") )
+ {
+ return true;
+ }
+ }
+
+ const CompoundStmt *pCompoundStmt = dyn_cast<CompoundStmt>(pFunctionDecl->getBody());
+ bool bEmptyBody = false;
+ if (pCompoundStmt) {
+ if (pCompoundStmt->size() > 1) {
+ return true;
+ }
+ if (pCompoundStmt->size() > 0) {
+ const ReturnStmt *pReturnStmt = dyn_cast<ReturnStmt>(*pCompoundStmt->body_begin());
+ if (!pReturnStmt) {
+ return true;
+ }
+ if (const UnaryOperator* unaryOp = dyn_cast<UnaryOperator>(pReturnStmt->getRetValue())) {
+ if (unaryOp->getOpcode() == UO_AddrOf) {
+ return true;
+ }
+ }
+ if (pReturnStmt->getRetValue() != nullptr) {
+ // && !pReturnStmt->getRetValue()->isEvaluatable(compiler.getASTContext())) {
+ bool aBoolResult;
+ llvm::APSInt aIntResult;
+ if (pReturnStmt->getRetValue()->isTypeDependent()
+ || (!pReturnStmt->getRetValue()->EvaluateAsBooleanCondition(aBoolResult, compiler.getASTContext())
+ && !pReturnStmt->getRetValue()->EvaluateAsInt(aIntResult, compiler.getASTContext())))
+ {
+ return true;
+ }
+ }
+ } else {
+ bEmptyBody = true;
+ }
+ }
+
+ std::string aMessage = "this ";
+ aMessage += pCXXMethodDecl ? "method" : "function";
+ if (bEmptyBody) {
+ aMessage += " is empty and should be removed, " + aFunctionName;
+ } else {
+ aMessage += " returns a constant value and should be converted to a constant "
+ "or to static inline, " + aFunctionName + ", " + aImmediateMacro;
+ }
+ report(
+ DiagnosticsEngine::Warning,
+ aMessage,
+ pFunctionDecl->getLocStart())
+ << pFunctionDecl->getSourceRange();
+ if (pFunctionDecl != pFunctionDecl->getCanonicalDecl())
+ report(
+ DiagnosticsEngine::Note,
+ aMessage,
+ pFunctionDecl->getCanonicalDecl()->getLocStart())
+ << pFunctionDecl->getCanonicalDecl()->getSourceRange();
+ return true;
+}
+
+loplugin::Plugin::Registration<ConstantFunction> X("constantfunction");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/constfields.cxx b/compilerplugins/clang/store/constfields.cxx
new file mode 100644
index 0000000000..692c84daeb
--- /dev/null
+++ b/compilerplugins/clang/store/constfields.cxx
@@ -0,0 +1,596 @@
+/* -*- 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/.
+ */
+
+#if !defined _WIN32 //TODO, #include <sys/file.h>
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <unordered_set>
+#include <vector>
+#include <algorithm>
+#include <sys/file.h>
+#include <unistd.h>
+
+#include "config_clang.h"
+
+#include "plugin.hxx"
+#include "check.hxx"
+
+#include "clang/AST/ParentMapContext.h"
+
+/**
+Look for fields that are only assigned to in the constructor using field-init, and can therefore be const.
+
+The process goes something like this:
+ $ make check
+ $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='constfields' check
+ $ ./compilerplugins/clang/constfields.py
+
+and then
+ $ for dir in *; do make $dir FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='constfieldsrewrite' $dir; done
+to auto-remove the method declarations
+
+*/
+
+namespace
+{
+struct MyFieldInfo
+{
+ const RecordDecl* parentRecord;
+ std::string parentClass;
+ std::string fieldName;
+ std::string fieldType;
+ std::string sourceLocation;
+ std::string access;
+};
+bool operator<(const MyFieldInfo& lhs, const MyFieldInfo& rhs)
+{
+ return std::tie(lhs.parentClass, lhs.fieldName) < std::tie(rhs.parentClass, rhs.fieldName);
+}
+
+// try to limit the voluminous output a little
+static std::set<MyFieldInfo> cannotBeConstSet;
+static std::set<MyFieldInfo> definitionSet;
+
+/**
+ * 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;
+
+public:
+ CallerWrapper(const CallExpr* callExpr)
+ : m_callExpr(callExpr)
+ , m_cxxConstructExpr(nullptr)
+ {
+ }
+ CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
+ : m_callExpr(nullptr)
+ , m_cxxConstructExpr(cxxConstructExpr)
+ {
+ }
+ unsigned getNumArgs() const
+ {
+ return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
+ }
+ const Expr* getArg(unsigned i) const
+ {
+ return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
+ }
+};
+class CalleeWrapper
+{
+ const FunctionDecl* m_calleeFunctionDecl = nullptr;
+ const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
+ const FunctionProtoType* m_functionPrototype = nullptr;
+
+public:
+ explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
+ : m_calleeFunctionDecl(calleeFunctionDecl)
+ {
+ }
+ explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
+ : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
+ {
+ }
+ explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
+ : m_functionPrototype(functionPrototype)
+ {
+ }
+ unsigned getNumParams() const
+ {
+ if (m_calleeFunctionDecl)
+ return m_calleeFunctionDecl->getNumParams();
+ else if (m_cxxConstructorDecl)
+ return m_cxxConstructorDecl->getNumParams();
+ else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
+ // FunctionProtoType will assert if we call getParamTypes() and it has no params
+ return 0;
+ else
+ return m_functionPrototype->getParamTypes().size();
+ }
+ const QualType getParamType(unsigned i) const
+ {
+ if (m_calleeFunctionDecl)
+ return m_calleeFunctionDecl->getParamDecl(i)->getType();
+ else if (m_cxxConstructorDecl)
+ return m_cxxConstructorDecl->getParamDecl(i)->getType();
+ else
+ return m_functionPrototype->getParamTypes()[i];
+ }
+ std::string getNameAsString() const
+ {
+ if (m_calleeFunctionDecl)
+ return m_calleeFunctionDecl->getNameAsString();
+ else if (m_cxxConstructorDecl)
+ return m_cxxConstructorDecl->getNameAsString();
+ else
+ return "";
+ }
+ CXXMethodDecl const* getAsCXXMethodDecl() const
+ {
+ if (m_calleeFunctionDecl)
+ return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
+ return nullptr;
+ }
+};
+
+class ConstFields : public RecursiveASTVisitor<ConstFields>, public loplugin::Plugin
+{
+public:
+ explicit ConstFields(loplugin::InstantiationData const& data)
+ : Plugin(data)
+ {
+ }
+
+ virtual void run() override;
+
+ bool shouldVisitTemplateInstantiations() const { return true; }
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool VisitFieldDecl(const FieldDecl*);
+ bool VisitMemberExpr(const MemberExpr*);
+ bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
+ bool TraverseCXXMethodDecl(CXXMethodDecl*);
+ bool TraverseFunctionDecl(FunctionDecl*);
+ bool TraverseIfStmt(IfStmt*);
+
+private:
+ MyFieldInfo niceName(const FieldDecl*);
+ void check(const FieldDecl* fieldDecl, const Expr* memberExpr);
+ bool isSomeKindOfZero(const Expr* arg);
+ bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
+ CalleeWrapper calleeFunctionDecl);
+ llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
+
+ 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;
+};
+
+void ConstFields::run()
+{
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ if (!isUnitTestMode())
+ {
+ // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
+ // writing to the same logfile
+ std::string output;
+ for (const MyFieldInfo& s : cannotBeConstSet)
+ output += "write-outside-constructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
+ for (const MyFieldInfo& s : definitionSet)
+ output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t"
+ + s.fieldType + "\t" + s.sourceLocation + "\n";
+ std::ofstream myfile;
+ myfile.open(WORKDIR "/loplugin.constfields.log", std::ios::app | std::ios::out);
+ myfile << output;
+ myfile.close();
+ }
+ else
+ {
+ for (const MyFieldInfo& s : cannotBeConstSet)
+ report(DiagnosticsEngine::Warning, "notconst %0", s.parentRecord->getBeginLoc())
+ << s.fieldName;
+ }
+}
+
+MyFieldInfo ConstFields::niceName(const FieldDecl* fieldDecl)
+{
+ MyFieldInfo aInfo;
+
+ const RecordDecl* recordDecl = fieldDecl->getParent();
+
+ if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
+ {
+ if (cxxRecordDecl->getTemplateInstantiationPattern())
+ cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
+ aInfo.parentRecord = cxxRecordDecl;
+ aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
+ }
+ else
+ {
+ aInfo.parentRecord = recordDecl;
+ aInfo.parentClass = recordDecl->getQualifiedNameAsString();
+ }
+
+ 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();
+
+ SourceLocation expansionLoc
+ = compiler.getSourceManager().getExpansionLoc(fieldDecl->getLocation());
+ StringRef name = getFilenameOfLocation(expansionLoc);
+ aInfo.sourceLocation
+ = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
+ + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
+ loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
+
+ 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 ConstFields::VisitFieldDecl(const FieldDecl* fieldDecl)
+{
+ fieldDecl = fieldDecl->getCanonicalDecl();
+ if (ignoreLocation(fieldDecl))
+ {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
+ {
+ return true;
+ }
+ definitionSet.insert(niceName(fieldDecl));
+ return true;
+}
+
+bool ConstFields::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 ConstFields::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 ConstFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
+{
+ auto copy2 = insideFunctionDecl;
+ insideFunctionDecl = functionDecl;
+ bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
+ insideFunctionDecl = copy2;
+ return ret;
+}
+
+bool ConstFields::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 ConstFields::VisitMemberExpr(const MemberExpr* memberExpr)
+{
+ const ValueDecl* decl = memberExpr->getMemberDecl();
+ const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
+ if (!fieldDecl)
+ {
+ return true;
+ }
+ fieldDecl = fieldDecl->getCanonicalDecl();
+ if (ignoreLocation(fieldDecl))
+ {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
+ {
+ return true;
+ }
+
+ check(fieldDecl, memberExpr);
+
+ return true;
+}
+
+void ConstFields::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;
+ }
+ else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
+ || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
+ || isa<ExprWithCleanups>(parent))
+ {
+ walkUp();
+ }
+ else if (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();
+ }
+ else if (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;
+ }
+ else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
+ {
+ bCannotBeConst = true;
+ }
+ }
+ else
+ bCannotBeConst = true; // conservative, could improve
+ walkUp();
+ }
+ else if (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();
+ }
+ else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
+ {
+ if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr,
+ CalleeWrapper(cxxConstructExpr)))
+ bCannotBeConst = true;
+ walkUp();
+ }
+ else if (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();
+ }
+ else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
+ {
+ BinaryOperator::Opcode op = binaryOp->getOpcode();
+ const bool 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;
+ else if (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();
+ }
+ else if (isa<ReturnStmt>(parent))
+ {
+ if (insideFunctionDecl)
+ {
+ auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
+ if (tc.LvalueReference().NonConst())
+ bCannotBeConst = true;
+ }
+ break;
+ }
+ else if (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 ConstFields::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())
+ return true;
+ }
+ else
+ {
+ for (unsigned i = 0; i < len; ++i)
+ if (callExpr.getArg(i) == child)
+ if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
+ .LvalueReference()
+ .NonConst())
+ return true;
+ }
+ return false;
+}
+
+llvm::Optional<CalleeWrapper> ConstFields::getCallee(CallExpr const* callExpr)
+{
+ FunctionDecl const* functionDecl = callExpr->getDirectCallee();
+ if (functionDecl)
+ return CalleeWrapper(functionDecl);
+
+ // 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>();
+}
+
+loplugin::Plugin::Registration<ConstFields> X("constfields", false);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/constfields.py b/compilerplugins/clang/store/constfields.py
new file mode 100755
index 0000000000..311b372bc9
--- /dev/null
+++ b/compilerplugins/clang/store/constfields.py
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+
+import re
+import io
+
+definitionSet = set()
+definitionToSourceLocationMap = dict()
+definitionToTypeMap = dict()
+writeFromOutsideConstructorSet = set()
+
+# clang does not always use exactly the same numbers in the type-parameter vars it generates
+# so I need to substitute them to ensure we can match correctly.
+normalizeTypeParamsRegex = re.compile(r"type-parameter-\d+-\d+")
+def normalizeTypeParams( line ):
+ return normalizeTypeParamsRegex.sub("type-parameter-?-?", line)
+
+def parseFieldInfo( tokens ):
+ if len(tokens) == 3:
+ return (normalizeTypeParams(tokens[1]), tokens[2])
+ else:
+ return (normalizeTypeParams(tokens[1]), "")
+
+with io.open("workdir/loplugin.constfields.log", "rb", buffering=1024*1024) as txt:
+ for line in txt:
+ tokens = line.strip().split("\t")
+ if tokens[0] == "definition:":
+ access = tokens[1]
+ fieldInfo = (normalizeTypeParams(tokens[2]), tokens[3])
+ srcLoc = tokens[5]
+ # ignore external source code
+ if (srcLoc.startswith("external/")):
+ continue
+ # ignore build folder
+ if (srcLoc.startswith("workdir/")):
+ continue
+ definitionSet.add(fieldInfo)
+ definitionToTypeMap[fieldInfo] = tokens[4]
+ definitionToSourceLocationMap[fieldInfo] = tokens[5]
+ elif tokens[0] == "write-outside-constructor:":
+ writeFromOutsideConstructorSet.add(parseFieldInfo(tokens))
+ else:
+ print( "unknown line: " + line)
+
+# Calculate can-be-const-field set
+canBeConstFieldSet = set()
+for d in definitionSet:
+ if d in writeFromOutsideConstructorSet:
+ continue
+ srcLoc = definitionToSourceLocationMap[d];
+ fieldType = definitionToTypeMap[d]
+ if fieldType.startswith("const "):
+ continue
+ if "std::unique_ptr" in fieldType:
+ continue
+ if "std::shared_ptr" in fieldType:
+ continue
+ if "Reference<" in fieldType:
+ continue
+ if "VclPtr<" in fieldType:
+ continue
+ if "osl::Mutex" in fieldType:
+ continue
+ if "::sfx2::sidebar::ControllerItem" in fieldType:
+ continue
+ canBeConstFieldSet.add((d[0] + " " + d[1] + " " + fieldType, srcLoc))
+
+
+# sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
+def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
+ return [int(text) if text.isdigit() else text.lower()
+ for text in re.split(_nsre, s)]
+# sort by both the source-line and the datatype, so the output file ordering is stable
+# when we have multiple items on the same source line
+def v_sort_key(v):
+ return natural_sort_key(v[1]) + [v[0]]
+
+# sort results by name and line number
+tmp6list = sorted(canBeConstFieldSet, key=lambda v: v_sort_key(v))
+
+# print out the results
+with open("compilerplugins/clang/constfields.results", "wt") as f:
+ for t in tmp6list:
+ f.write( t[1] + "\n" )
+ f.write( " " + t[0] + "\n" )
+
+
diff --git a/compilerplugins/clang/store/constfieldsrewrite.cxx b/compilerplugins/clang/store/constfieldsrewrite.cxx
new file mode 100644
index 0000000000..d72bb43aad
--- /dev/null
+++ b/compilerplugins/clang/store/constfieldsrewrite.cxx
@@ -0,0 +1,169 @@
+/* -*- 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/.
+ */
+
+#if !defined _WIN32 //TODO, #include <sys/mman.h>
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include "plugin.hxx"
+#include "check.hxx"
+#include "config_clang.h"
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <cstring>
+
+/**
+ This is intended to be run as the second stage of the "constfields" clang plugin.
+*/
+
+namespace
+{
+class ConstFieldsRewrite : public RecursiveASTVisitor<ConstFieldsRewrite>,
+ public loplugin::RewritePlugin
+{
+public:
+ explicit ConstFieldsRewrite(loplugin::InstantiationData const& data);
+ ~ConstFieldsRewrite();
+
+ virtual void run() override
+ {
+ if (rewriter)
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+ }
+
+ bool VisitFieldDecl(const FieldDecl* var);
+
+private:
+ // I use a brute-force approach - mmap the results file and do a linear search on it
+ // It works surprisingly well, because the file is small enough to fit into L2 cache on modern CPU's
+ size_t mmapFilesize;
+ int mmapFD;
+ char* mmappedData;
+};
+
+size_t getFilesize(const char* filename)
+{
+ struct stat st;
+ stat(filename, &st);
+ return st.st_size;
+}
+
+ConstFieldsRewrite::ConstFieldsRewrite(loplugin::InstantiationData const& data)
+ : RewritePlugin(data)
+{
+ static const char sInputFile[] = SRCDIR "/compilerplugins/clang/constfields.results";
+ mmapFilesize = getFilesize(sInputFile);
+ //Open file
+ mmapFD = open(sInputFile, O_RDONLY, 0);
+ assert(mmapFD != -1);
+ //Execute mmap
+ mmappedData = static_cast<char*>(mmap(NULL, mmapFilesize, PROT_READ, MAP_PRIVATE, mmapFD, 0));
+ assert(mmappedData != NULL);
+}
+
+ConstFieldsRewrite::~ConstFieldsRewrite()
+{
+ //Cleanup
+ int rc = munmap(mmappedData, mmapFilesize);
+ assert(rc == 0);
+ (void)rc;
+ close(mmapFD);
+}
+
+bool ConstFieldsRewrite::VisitFieldDecl(const FieldDecl* fieldDecl)
+{
+ if (ignoreLocation(fieldDecl))
+ return true;
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
+ fieldDecl->getCanonicalDecl()->getLocation())))
+ return true;
+ // in case we've already processed this field
+ if (fieldDecl->getType().isConstQualified())
+ return true;
+ // TODO rewriting T& is a bit trickier
+ if (loplugin::TypeCheck(fieldDecl->getType()).LvalueReference())
+ return true;
+
+ const RecordDecl* recordDecl = fieldDecl->getParent();
+ std::string parentClassName;
+ if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
+ {
+ if (cxxRecordDecl->getTemplateInstantiationPattern())
+ cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
+ parentClassName = cxxRecordDecl->getQualifiedNameAsString();
+ }
+ else
+ {
+ parentClassName = recordDecl->getQualifiedNameAsString();
+ }
+ // the extra spaces match the formatting in the results file, and help avoid false+
+ std::string aNiceName = " " + parentClassName + " " + fieldDecl->getNameAsString() + " "
+ + fieldDecl->getType().getAsString() + "\n";
+
+ // search mmap'ed file for field
+ const char* aNiceNameStr = aNiceName.c_str();
+ char* found = std::search(mmappedData, mmappedData + mmapFilesize, aNiceNameStr,
+ aNiceNameStr + aNiceName.size());
+ if (!(found < mmappedData + mmapFilesize))
+ return true;
+
+ SourceManager& SM = compiler.getSourceManager();
+ auto endLoc = fieldDecl->getTypeSourceInfo()->getTypeLoc().getEndLoc();
+ endLoc = endLoc.getLocWithOffset(Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts()));
+
+ // Calculate how much space is available after the type declaration that we can use to
+ // overwrite with the " const". This reduces the amount of formatting fixups I need to do.
+ char const* p1 = SM.getCharacterData(endLoc);
+ bool success = false;
+ if (*p1 != ' ')
+ {
+ // Sometimes there is no space at all e.g. in
+ // FastTokenHandlerBase *mpTokenHandler;
+ // between the "*" and the "mpTokenHandler", so add an extra space.
+ success = insertText(endLoc, " const ");
+ }
+ else
+ {
+ int spaceAvailable = 1;
+ ++p1;
+ for (; spaceAvailable < 6; ++spaceAvailable)
+ {
+ if (*p1 != ' ')
+ break;
+ ++p1;
+ }
+ if (spaceAvailable < 6)
+ success = replaceText(endLoc, spaceAvailable - 1, " const");
+ else
+ success = replaceText(endLoc, spaceAvailable, " const");
+ }
+
+ if (!success)
+ {
+ report(DiagnosticsEngine::Warning, "Could not mark field as const",
+ fieldDecl->getBeginLoc())
+ << fieldDecl->getSourceRange();
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<ConstFieldsRewrite> X("constfieldsrewrite", false);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/constparams.cxx b/compilerplugins/clang/store/constparams.cxx
new file mode 100644
index 0000000000..dac7322d01
--- /dev/null
+++ b/compilerplugins/clang/store/constparams.cxx
@@ -0,0 +1,618 @@
+/* -*- 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 <algorithm>
+#include <string>
+#include <unordered_set>
+#include <unordered_map>
+#include <iostream>
+
+#include "config_clang.h"
+
+#include "plugin.hxx"
+#include "compat.hxx"
+#include "check.hxx"
+#include "functionaddress.hxx"
+
+#include "clang/AST/ParentMapContext.h"
+
+/**
+ 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) {}
+
+ virtual void 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")
+ )
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ for (const ParmVarDecl *pParmVarDecl : interestingParamSet) {
+ auto functionDecl = parmToFunction[pParmVarDecl];
+ auto canonicalDecl = functionDecl->getCanonicalDecl();
+ if (getFunctionsWithAddressTaken().find(canonicalDecl)
+ != getFunctionsWithAddressTaken().end())
+ {
+ continue;
+ }
+ std::string fname = functionDecl->getQualifiedNameAsString();
+ report(
+ DiagnosticsEngine::Warning,
+ "this parameter can be const %0",
+ pParmVarDecl->getBeginLoc())
+ << fname << pParmVarDecl->getSourceRange();
+ if (canonicalDecl->getLocation() != functionDecl->getLocation()) {
+ unsigned idx = pParmVarDecl->getFunctionScopeIndex();
+ const ParmVarDecl* pOther = canonicalDecl->getParamDecl(idx);
+ report(
+ DiagnosticsEngine::Note,
+ "canonical parameter declaration here",
+ pOther->getBeginLoc())
+ << pOther->getSourceRange();
+ }
+ //functionDecl->dump();
+ }
+ }
+
+ bool TraverseFunctionDecl(FunctionDecl *);
+ bool TraverseCXXMethodDecl(CXXMethodDecl * f);
+ bool TraverseCXXConstructorDecl(CXXConstructorDecl * f);
+ bool VisitDeclRefExpr(const DeclRefExpr *);
+ bool VisitLambdaExpr(const LambdaExpr*);
+
+private:
+ bool CheckTraverseFunctionDecl(FunctionDecl *);
+ bool checkIfCanBeConst(const Stmt*, const ParmVarDecl*);
+ // integral or enumeration or const * or const &
+ bool isOkForParameter(const QualType& qt);
+ bool isPointerOrReferenceToNonConst(const QualType& qt);
+
+ std::unordered_set<const ParmVarDecl*> interestingParamSet;
+ std::unordered_map<const ParmVarDecl*, const FunctionDecl*> parmToFunction;
+ FunctionDecl* currentFunctionDecl = nullptr;
+};
+
+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()) {
+ return false;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl)) {
+ return false;
+ }
+ if (functionDecl->isDeleted())
+ return false;
+ // ignore virtual methods
+ if (isa<CXXMethodDecl>(functionDecl)
+ && dyn_cast<CXXMethodDecl>(functionDecl)->isVirtual() ) {
+ return false;
+ }
+ // ignore C main
+ if (functionDecl->isMain()) {
+ return false;
+ }
+ if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate)
+ return false;
+
+ // 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 (name.startswith("DECL_LINK") || name.startswith("DECL_STATIC_LINK"))
+ return false;
+ auto loc2 = compat::getImmediateExpansionRange(compiler.getSourceManager(), canonicalDecl->getBeginLoc()).first;
+ if (compiler.getSourceManager().isMacroBodyExpansion(loc2))
+ {
+ StringRef name2 { Lexer::getImmediateMacroName(
+ loc2, compiler.getSourceManager(), compiler.getLangOpts()) };
+ if (name2.startswith("DECL_DLLPRIVATE_LINK"))
+ return false;
+ }
+ }
+
+ if (functionDecl->getIdentifier())
+ {
+ StringRef name = functionDecl->getName();
+ if ( name == "file_write"
+ || name == "SalMainPipeExchangeSignal_impl"
+ || name.startswith("SbRtl_")
+ || name == "GoNext"
+ || name == "GoPrevious"
+ || name.startswith("Read_F_")
+ // UNO component entry points
+ || name.endswith("component_getFactory")
+ || name.endswith("_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"
+ )
+ return false;
+
+ }
+
+ std::string fqn = functionDecl->getQualifiedNameAsString();
+ if ( fqn == "connectivity::jdbc::GlobalRef::set"
+ || fqn == "(anonymous namespace)::ReorderNotifier::operator()"
+ || fqn == "static_txtattr_cast")
+ return false;
+
+ // 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;
+ auto const type = loplugin::TypeCheck(pParmVarDecl->getType());
+ if (!( type.Pointer().NonConst()
+ || type.LvalueReference().NonConst()))
+ 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)
+ return true;
+ const ParmVarDecl* parmVarDecl = dyn_cast_or_null<ParmVarDecl>(declRefExpr->getDecl());
+ if (!parmVarDecl)
+ return true;
+ if (interestingParamSet.find(parmVarDecl) == interestingParamSet.end())
+ return true;
+ if (!checkIfCanBeConst(declRefExpr, parmVarDecl))
+ interestingParamSet.erase(parmVarDecl);
+ return true;
+}
+
+bool ConstParams::VisitLambdaExpr(const LambdaExpr* lambdaExpr)
+{
+ if (ignoreLocation(lambdaExpr))
+ return true;
+ 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);
+ }
+ }
+ return true;
+}
+
+// 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
+ return true;
+ }
+ else
+ {
+ // probably base initialiser, but no simple way to look up the relevant constructor decl
+ return false;
+ }
+ }
+ }
+ }
+ 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();
+ return false;
+ }
+
+ 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) {
+ return false;
+ }
+ if (op == UO_Deref || op == UO_AddrOf) {
+ return checkIfCanBeConst(parent, parmVarDecl);
+ }
+ return true;
+ } else if (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) {
+ return true;
+ }
+ 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) {
+ return false;
+ }
+ // for pointer arithmetic need to check parent
+ if (binaryOp->getType()->isPointerType()) {
+ return checkIfCanBeConst(parent, parmVarDecl);
+ }
+ return true;
+ } else if (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());
+ }
+ }
+ } else if (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
+ return false;
+ // not all operator= take a const&
+ return isOkForParameter(calleeMethodDecl->getParamDecl(0)->getType());
+ }
+ if (operatorCallExpr->getOperator() == OO_Subscript && operatorCallExpr->getArg(1) == stmt)
+ return true;
+ if (operatorCallExpr->getOperator() == OO_EqualEqual || operatorCallExpr->getOperator() == OO_ExclaimEqual)
+ return true;
+ // binary operator
+ if (operatorCallExpr->getArg(0) == stmt)
+ return calleeMethodDecl->isConst();
+ unsigned const 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());
+ }
+ }
+ }
+ }
+ return false;
+ } else if (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()) {
+ return false;
+ }
+ if (callExpr->getCallee() == stmt) {
+ return true;
+ }
+ 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()) {
+ return false;
+ }
+ if (callExpr->getCallee() == stmt) {
+ return true;
+ }
+ for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
+ if (i >= calleeFunctionDecl->getNumParams()) // can happen in template code
+ return false;
+ if (callExpr->getArg(i) == stmt) {
+ return isOkForParameter(calleeFunctionDecl->getParamDecl(i)->getType());
+ }
+ }
+ }
+ return false;
+ } else if (auto callExpr = dyn_cast<ObjCMessageExpr>(parent)) {
+ if (callExpr->getInstanceReceiver() == stmt) {
+ return true;
+ }
+ if (auto const method = callExpr->getMethodDecl()) {
+ // TODO could do better
+ if (method->isVariadic()) {
+ return false;
+ }
+ 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());
+ }
+ }
+ }
+ } else if (isa<CXXReinterpretCastExpr>(parent)) {
+ return false;
+ } else if (isa<CXXConstCastExpr>(parent)) {
+ return false;
+ } else if (isa<CastExpr>(parent)) { // all other cast expression subtypes
+ if (auto e = dyn_cast<ExplicitCastExpr>(parent)) {
+ if (loplugin::TypeCheck(e->getTypeAsWritten()).Void()) {
+ if (auto const sub = dyn_cast<DeclRefExpr>(
+ e->getSubExpr()->IgnoreParenImpCasts()))
+ {
+ if (sub->getDecl() == parmVarDecl)
+ return false;
+ }
+ }
+ }
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (isa<MemberExpr>(parent)) {
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent)) {
+ if (arraySubscriptExpr->getIdx() == stmt)
+ return true;
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (isa<ParenExpr>(parent)) {
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (isa<DeclStmt>(parent)) {
+ // TODO could do better here, but would require tracking the target(s)
+ //return false;
+ } else if (isa<ReturnStmt>(parent)) {
+ return !isPointerOrReferenceToNonConst(currentFunctionDecl->getReturnType());
+ } else if (isa<InitListExpr>(parent)) {
+ return false;
+ } else if (isa<IfStmt>(parent)) {
+ return true;
+ } else if (isa<WhileStmt>(parent)) {
+ return true;
+ } else if (isa<ForStmt>(parent)) {
+ return true;
+ } else if (isa<CompoundStmt>(parent)) {
+ return true;
+ } else if (isa<SwitchStmt>(parent)) {
+ return true;
+ } else if (isa<DoStmt>(parent)) {
+ return true;
+ } else if (isa<CXXDeleteExpr>(parent)) {
+ return false;
+ } else if (isa<VAArgExpr>(parent)) {
+ return false;
+ } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
+ return false;
+ } else if (isa<MaterializeTemporaryExpr>(parent)) {
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (auto conditionalExpr = dyn_cast<ConditionalOperator>(parent)) {
+ if (conditionalExpr->getCond() == stmt)
+ return true;
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (isa<UnaryExprOrTypeTraitExpr>(parent)) {
+ return false; // ???
+ } else if (auto cxxNewExpr = dyn_cast<CXXNewExpr>(parent)) {
+ for (unsigned i = 0; i < cxxNewExpr->getNumPlacementArgs(); ++i)
+ if (cxxNewExpr->getPlacementArg(i) == stmt)
+ return false;
+ return true; // ???
+ } else if (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;
+ }
+ return false;
+ } else if (isa<CXXTypeidExpr>(parent)) {
+ return true;
+ } else if (isa<ParenListExpr>(parent)) {
+ return false; // could be improved, seen in constructors when calling base class constructor
+ } else if (isa<CXXUnresolvedConstructExpr>(parent)) {
+ return false;
+ } else if (isa<UnresolvedMemberExpr>(parent)) {
+ return false;
+ } else if (isa<PackExpansionExpr>(parent)) {
+ return false;
+ } else if (isa<ExprWithCleanups>(parent)) {
+ return checkIfCanBeConst(parent, parmVarDecl);
+ } else if (isa<CaseStmt>(parent)) {
+ return true;
+ } else if (isa<CXXPseudoDestructorExpr>(parent)) {
+ return false;
+ } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
+ return false;
+ } else if (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();
+ return true;
+}
+
+bool ConstParams::isOkForParameter(const QualType& qt) {
+ if (qt->isIntegralOrEnumerationType())
+ return true;
+ auto const type = loplugin::TypeCheck(qt);
+ if (type.Pointer()) {
+ return bool(type.Pointer().Const());
+ } else if (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.
+ return false;
+ } else if (type.LvalueReference()) {
+ return bool(type.LvalueReference().Const());
+ }
+ return false;
+}
+
+bool ConstParams::isPointerOrReferenceToNonConst(const QualType& qt) {
+ auto const type = loplugin::TypeCheck(qt);
+ if (type.Pointer()) {
+ return !bool(type.Pointer().Const());
+ } else if (type.LvalueReference()) {
+ return !bool(type.LvalueReference().Const());
+ }
+ return false;
+}
+
+loplugin::Plugin::Registration< ConstParams > X("constparams", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/constvars.cxx b/compilerplugins/clang/store/constvars.cxx
new file mode 100644
index 0000000000..2b06f54ea3
--- /dev/null
+++ b/compilerplugins/clang/store/constvars.cxx
@@ -0,0 +1,558 @@
+/* -*- 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/.
+ */
+
+#if !defined _WIN32 //TODO, #include <sys/file.h>
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <unordered_set>
+#include <vector>
+#include <algorithm>
+#include <sys/file.h>
+#include <unistd.h>
+
+#include "plugin.hxx"
+#include "check.hxx"
+
+#include "clang/AST/ParentMapContext.h"
+
+/**
+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;
+
+public:
+ CallerWrapper(const CallExpr* callExpr)
+ : m_callExpr(callExpr)
+ , m_cxxConstructExpr(nullptr)
+ {
+ }
+ CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
+ : m_callExpr(nullptr)
+ , m_cxxConstructExpr(cxxConstructExpr)
+ {
+ }
+ unsigned getNumArgs() const
+ {
+ return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
+ }
+ const Expr* getArg(unsigned i) const
+ {
+ return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
+ }
+};
+class CalleeWrapper
+{
+ const FunctionDecl* m_calleeFunctionDecl = nullptr;
+ const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
+ const FunctionProtoType* m_functionPrototype = nullptr;
+
+public:
+ explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
+ : m_calleeFunctionDecl(calleeFunctionDecl)
+ {
+ }
+ explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
+ : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
+ {
+ }
+ explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
+ : m_functionPrototype(functionPrototype)
+ {
+ }
+ unsigned getNumParams() const
+ {
+ if (m_calleeFunctionDecl)
+ return m_calleeFunctionDecl->getNumParams();
+ else if (m_cxxConstructorDecl)
+ return m_cxxConstructorDecl->getNumParams();
+ else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
+ // FunctionProtoType will assert if we call getParamTypes() and it has no params
+ return 0;
+ else
+ return m_functionPrototype->getParamTypes().size();
+ }
+ const QualType getParamType(unsigned i) const
+ {
+ if (m_calleeFunctionDecl)
+ return m_calleeFunctionDecl->getParamDecl(i)->getType();
+ else if (m_cxxConstructorDecl)
+ return m_cxxConstructorDecl->getParamDecl(i)->getType();
+ else
+ return m_functionPrototype->getParamTypes()[i];
+ }
+ std::string getNameAsString() const
+ {
+ if (m_calleeFunctionDecl)
+ return m_calleeFunctionDecl->getNameAsString();
+ else if (m_cxxConstructorDecl)
+ return m_cxxConstructorDecl->getNameAsString();
+ else
+ return "";
+ }
+ CXXMethodDecl const* getAsCXXMethodDecl() const
+ {
+ if (m_calleeFunctionDecl)
+ return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
+ return nullptr;
+ }
+};
+
+class ConstVars : public RecursiveASTVisitor<ConstVars>, public loplugin::Plugin
+{
+public:
+ explicit ConstVars(loplugin::InstantiationData const& data)
+ : Plugin(data)
+ {
+ }
+
+ virtual void run() override;
+
+ bool shouldVisitTemplateInstantiations() const { return true; }
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool VisitVarDecl(const VarDecl*);
+ bool VisitCXXForRangeStmt(const CXXForRangeStmt*);
+ bool VisitDeclRefExpr(const DeclRefExpr*);
+ bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
+ bool TraverseCXXMethodDecl(CXXMethodDecl*);
+ bool TraverseFunctionDecl(FunctionDecl*);
+ bool TraverseIfStmt(IfStmt*);
+
+private:
+ void check(const VarDecl* varDecl, const Expr* memberExpr);
+ bool isSomeKindOfZero(const Expr* arg);
+ bool IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child, CallerWrapper callExpr,
+ CalleeWrapper calleeFunctionDecl);
+ llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
+
+ 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;
+
+ std::set<VarDecl const*> cannotBeConstSet;
+ std::set<VarDecl const*> definitionSet;
+};
+
+void ConstVars::run()
+{
+ // clang::Expr::isCXX11ConstantExpr only works for C++
+ if (!compiler.getLangOpts().CPlusPlus)
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ 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))
+ return true;
+ if (!varDecl->hasGlobalStorage())
+ return true;
+ if (isa<ParmVarDecl>(varDecl))
+ return true;
+ if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage)
+ return true;
+ if (varDecl->getType().isConstQualified())
+ return true;
+ if (isa<ConstantArrayType>(varDecl->getType()))
+ return true;
+ if (loplugin::TypeCheck(varDecl->getType()).Pointer().Const())
+ return true;
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
+ return true;
+
+ if (!varDecl->getInit())
+ return true;
+ if (varDecl->getInit()->isInstantiationDependent())
+ return true;
+ if (!varDecl->getInit()->isCXX11ConstantExpr(compiler.getASTContext()))
+ return true;
+
+ definitionSet.insert(varDecl);
+ return true;
+}
+
+bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt* forStmt)
+{
+ if (forStmt->getBeginLoc().isValid() && ignoreLocation(forStmt))
+ return true;
+ const VarDecl* varDecl = forStmt->getLoopVariable();
+ if (!varDecl)
+ return true;
+ // we don't handle structured assignment properly
+ if (isa<DecompositionDecl>(varDecl))
+ return true;
+ auto tc = loplugin::TypeCheck(varDecl->getType());
+ if (!tc.LvalueReference())
+ return true;
+ if (tc.LvalueReference().Const())
+ return true;
+
+ definitionSet.insert(varDecl);
+ return true;
+}
+
+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::TraverseFunctionDecl(FunctionDecl* functionDecl)
+{
+ auto copy2 = insideFunctionDecl;
+ insideFunctionDecl = functionDecl;
+ bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
+ 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)
+ return true;
+ varDecl = varDecl->getCanonicalDecl();
+ if (ignoreLocation(varDecl))
+ return true;
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
+ return true;
+
+ if (definitionSet.find(varDecl) != definitionSet.end())
+ check(varDecl, declRefExpr);
+
+ return true;
+}
+
+void ConstVars::check(const VarDecl* varDecl, 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 = 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>();
+ }
+ else if (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;
+ }
+ else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
+ || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
+ || isa<ExprWithCleanups>(parent))
+ {
+ walkUp();
+ }
+ else if (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();
+ }
+ else if (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;
+ }
+ else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
+ {
+ bCannotBeConst = true;
+ }
+ }
+ else
+ bCannotBeConst = true; // conservative, could improve
+ walkUp();
+ }
+ else if (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();
+ }
+ else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
+ {
+ if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
+ CalleeWrapper(cxxConstructExpr)))
+ bCannotBeConst = true;
+ walkUp();
+ }
+ else if (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();
+ }
+ else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
+ {
+ BinaryOperator::Opcode op = binaryOp->getOpcode();
+ const bool 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;
+ else if (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();
+ }
+ else if (isa<ReturnStmt>(parent))
+ {
+ if (insideFunctionDecl)
+ {
+ auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
+ if (tc.LvalueReference().NonConst())
+ bCannotBeConst = true;
+ }
+ break;
+ }
+ else if (auto rangeStmt = dyn_cast<CXXForRangeStmt>(parent))
+ {
+ if (rangeStmt->getRangeStmt() == child)
+ {
+ auto tc = loplugin::TypeCheck(rangeStmt->getLoopVariable()->getType());
+ if (tc.LvalueReference().NonConst())
+ bCannotBeConst = true;
+ }
+ break;
+ }
+ else if (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())
+ return true;
+ }
+ 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())
+ return true;
+ }
+ }
+ return false;
+}
+
+llvm::Optional<CalleeWrapper> ConstVars::getCallee(CallExpr const* callExpr)
+{
+ FunctionDecl const* functionDecl = callExpr->getDirectCallee();
+ if (functionDecl)
+ return CalleeWrapper(functionDecl);
+
+ // 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);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/convertlong.cxx b/compilerplugins/clang/store/convertlong.cxx
new file mode 100644
index 0000000000..87b65a43f3
--- /dev/null
+++ b/compilerplugins/clang/store/convertlong.cxx
@@ -0,0 +1,139 @@
+/* -*- 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 <memory>
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include "config_clang.h"
+#include "plugin.hxx"
+#include "check.hxx"
+
+/**
+ plugin to help to when converting code from
+
+ sal_uIntPtr/sal_uLong/sal_Long/long/unsigned long
+
+ to something more precise.
+ */
+namespace
+{
+class ConvertLong : public loplugin::FilteringPlugin<ConvertLong>
+{
+public:
+ explicit ConvertLong(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ std::string fn(handler.getMainFileName());
+ loplugin::normalizeDotDotInFilePath(fn);
+ // using sal_uIntPtr as in-between type when converting void* to rtl_TextEncoding
+ if (fn == SRCDIR "/sal/osl/unx/thread.cxx")
+ return;
+ // too much magic
+ if (fn == SRCDIR "/sal/rtl/alloc_arena.cxx")
+ return;
+ if (fn == SRCDIR "/sal/rtl/alloc_cache.cxx")
+ return;
+ // TODO not sure what is going on here
+ if (fn == SRCDIR "/tools/source/generic/bigint.cxx")
+ return;
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitVarDecl(VarDecl const*);
+ bool TraverseFunctionDecl(FunctionDecl*);
+
+private:
+ bool isInterestingType(QualType qt);
+};
+
+bool ConvertLong::TraverseFunctionDecl(FunctionDecl* functionDecl)
+{
+ // ignore template stuff
+ if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate)
+ {
+ return true;
+ }
+ return RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
+}
+
+bool ConvertLong::VisitVarDecl(VarDecl const* varDecl)
+{
+ if (ignoreLocation(varDecl))
+ return true;
+ StringRef fileName{ getFilenameOfLocation(varDecl->getLocation()) };
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/tools/bigint.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/tools/solar.h"))
+ return true;
+ if (!varDecl->hasInit())
+ return true;
+ if (isa<IntegerLiteral>(varDecl->getInit()->IgnoreParenImpCasts()))
+ return true;
+ // ignore int x = -1;
+ if (isa<UnaryOperator>(varDecl->getInit()->IgnoreParenImpCasts()))
+ return true;
+ auto lhsType = varDecl->getType();
+ auto rhsType = varDecl->getInit()->IgnoreParenImpCasts()->getType();
+ if (lhsType.getLocalUnqualifiedType() == rhsType)
+ return true;
+ if (!rhsType.getTypePtrOrNull())
+ return true;
+ if (isInterestingType(rhsType))
+ return true;
+ if (!isInterestingType(lhsType))
+ return true;
+ if (rhsType->isFloatingType()) // TODO
+ return true;
+ report(DiagnosticsEngine::Warning, "rather replace type of decl %0 with %1",
+ varDecl->getLocation())
+ << lhsType << rhsType << varDecl->getSourceRange();
+ //lhsType->dump();
+ //varDecl->dump();
+ return true;
+}
+
+bool ConvertLong::isInterestingType(QualType qt)
+{
+ auto tc = loplugin::TypeCheck(qt);
+ if (tc.Typedef())
+ {
+ TypedefType const* typedefType = qt->getAs<TypedefType>();
+ auto name = typedefType->getDecl()->getName();
+ if (name == "sal_uLong")
+ return true;
+ // because this is a typedef to long on 64-bit Linux
+ if (name == "sal_Int64" || name == "sal_uInt64" || name.find("size_t") != StringRef::npos)
+ return false;
+ }
+ if (isa<AutoType>(qt.getTypePtr()))
+ return false;
+ auto unqual = qt.getUnqualifiedType();
+ if (unqual->isSpecificBuiltinType(BuiltinType::Kind::Long)
+ || unqual->isSpecificBuiltinType(BuiltinType::Kind::ULong))
+ {
+ return true;
+ }
+ if (!tc.Typedef())
+ return false;
+ TypedefType const* typedefType = qt->getAs<TypedefType>();
+ auto name = typedefType->getDecl()->getName();
+ return name == "sal_uIntPtr" || name == "sal_IntPtr";
+}
+
+loplugin::Plugin::Registration<ConvertLong> X("convertlong", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/countusersofdefaultparams.cxx b/compilerplugins/clang/store/countusersofdefaultparams.cxx
new file mode 100644
index 0000000000..c336509b3e
--- /dev/null
+++ b/compilerplugins/clang/store/countusersofdefaultparams.cxx
@@ -0,0 +1,250 @@
+/* -*- 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 <string>
+#include <set>
+#include <iostream>
+#include <fstream>
+
+#include "clang/AST/Attr.h"
+
+#include "config_clang.h"
+
+#include "plugin.hxx"
+
+/*
+ Count call sites that are actually using the defaulted values on params on methods that declare such.
+
+ The process goes something like this:
+ $ make check
+ $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='countusersofdefaultparams' check
+ $ ./compilerplugins/clang/countusersofdefaultparams.py
+*/
+
+namespace {
+
+struct MyFuncInfo
+{
+ std::string access;
+ std::string returnType;
+ std::string nameAndParams;
+ std::string sourceLocation;
+};
+bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
+{
+ return std::tie(lhs.returnType, lhs.nameAndParams)
+ < std::tie(rhs.returnType, rhs.nameAndParams);
+}
+struct MyCallInfo : public MyFuncInfo
+{
+ std::string sourceLocationOfCall;
+};
+bool operator < (const MyCallInfo &lhs, const MyCallInfo &rhs)
+{
+ return std::tie(lhs.returnType, lhs.nameAndParams, lhs.sourceLocationOfCall)
+ < std::tie(rhs.returnType, rhs.nameAndParams, rhs.sourceLocationOfCall);
+}
+
+
+// try to limit the voluminous output a little
+static std::set<MyCallInfo> callSet;
+static std::set<MyFuncInfo> definitionSet;
+
+class CountUsersOfDefaultParams:
+ public RecursiveASTVisitor<CountUsersOfDefaultParams>, public loplugin::Plugin
+{
+public:
+ explicit CountUsersOfDefaultParams(loplugin::InstantiationData const & data): Plugin(data) {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
+ // writing to the same logfile
+
+ std::string output;
+ for (const MyFuncInfo & s : definitionSet)
+ output += "defn:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\n";
+ for (const MyCallInfo & s : callSet)
+ output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocationOfCall + "\n";
+ std::ofstream myfile;
+ myfile.open( WORKDIR "/loplugin.countusersofdefaultparams.log", std::ios::app | std::ios::out);
+ myfile << output;
+ myfile.close();
+ }
+
+ bool shouldVisitTemplateInstantiations () const { return true; }
+
+ bool VisitCallExpr( const CallExpr * );
+ bool VisitFunctionDecl( const FunctionDecl* );
+ bool VisitCXXConstructExpr( const CXXConstructExpr * );
+private:
+ void niceName(const FunctionDecl* functionDecl, MyFuncInfo&);
+ std::string locationToString(const SourceLocation&);
+};
+
+void CountUsersOfDefaultParams::niceName(const FunctionDecl* functionDecl, MyFuncInfo& aInfo)
+{
+ if (functionDecl->getInstantiatedFromMemberFunction())
+ functionDecl = functionDecl->getInstantiatedFromMemberFunction();
+ else if (functionDecl->getTemplateInstantiationPattern())
+ functionDecl = functionDecl->getTemplateInstantiationPattern();
+
+ switch (functionDecl->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;
+ }
+ aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
+
+ if (isa<CXXMethodDecl>(functionDecl)) {
+ const CXXRecordDecl* recordDecl = dyn_cast<CXXMethodDecl>(functionDecl)->getParent();
+ aInfo.nameAndParams += recordDecl->getQualifiedNameAsString();
+ aInfo.nameAndParams += "::";
+ }
+ aInfo.nameAndParams += functionDecl->getNameAsString() + "(";
+ bool bFirst = true;
+ for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
+ if (bFirst)
+ bFirst = false;
+ else
+ aInfo.nameAndParams += ",";
+ aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
+ }
+ aInfo.nameAndParams += ")";
+ if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
+ aInfo.nameAndParams += " const";
+ }
+
+ aInfo.sourceLocation = locationToString(functionDecl->getLocation());
+ loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
+}
+
+bool CountUsersOfDefaultParams::VisitCallExpr(const CallExpr * callExpr) {
+ if (ignoreLocation(callExpr)) {
+ return true;
+ }
+ const FunctionDecl* functionDecl;
+ if (isa<CXXMemberCallExpr>(callExpr)) {
+ functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
+ }
+ else {
+ functionDecl = callExpr->getDirectCallee();
+ }
+ if (functionDecl == nullptr) {
+ return true;
+ }
+ functionDecl = functionDecl->getCanonicalDecl();
+ // work our way up to the root method
+ while (isa<CXXMethodDecl>(functionDecl)) {
+ const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
+ if (methodDecl->size_overridden_methods()==0)
+ break;
+ functionDecl = *methodDecl->begin_overridden_methods();
+ }
+ // work our way back to the root definition for template methods
+ if (functionDecl->getInstantiatedFromMemberFunction())
+ functionDecl = functionDecl->getInstantiatedFromMemberFunction();
+ else if (functionDecl->getTemplateInstantiationPattern())
+ functionDecl = functionDecl->getTemplateInstantiationPattern();
+ int n = functionDecl->getNumParams() - 1;
+ if (n < 0 || !functionDecl->getParamDecl(n)->hasDefaultArg()) {
+ return true;
+ }
+ while (n > 0 && functionDecl->getParamDecl(n-1)->hasDefaultArg()) {
+ --n;
+ }
+ // look for callsites that are actually using the defaulted values
+ if ( n < (int)callExpr->getNumArgs() && callExpr->getArg(n)->isDefaultArgument()) {
+ MyCallInfo callInfo;
+ niceName(functionDecl, callInfo);
+ callInfo.sourceLocationOfCall = locationToString(callExpr->getBeginLoc());
+ callSet.insert(callInfo);
+ }
+ return true;
+}
+
+bool CountUsersOfDefaultParams::VisitCXXConstructExpr(const CXXConstructExpr * constructExpr) {
+ if (ignoreLocation(constructExpr)) {
+ return true;
+ }
+ const CXXConstructorDecl* constructorDecl = constructExpr->getConstructor()->getCanonicalDecl();
+ // work our way back to the root definition for template methods
+ if (constructorDecl->getInstantiatedFromMemberFunction())
+ constructorDecl = dyn_cast<CXXConstructorDecl>(constructorDecl->getInstantiatedFromMemberFunction());
+ else if (constructorDecl->getTemplateInstantiationPattern())
+ constructorDecl = dyn_cast<CXXConstructorDecl>(constructorDecl->getTemplateInstantiationPattern());
+ int n = constructorDecl->getNumParams() - 1;
+ if (n < 0 || !constructorDecl->getParamDecl(n)->hasDefaultArg()) {
+ return true;
+ }
+ while (n > 0 && constructorDecl->getParamDecl(n-1)->hasDefaultArg()) {
+ --n;
+ }
+ // look for callsites that are actually using the defaulted values
+ if ( n < (int)constructExpr->getNumArgs() && constructExpr->getArg(n)->isDefaultArgument()) {
+ MyCallInfo callInfo;
+ niceName(constructorDecl, callInfo);
+ callInfo.sourceLocationOfCall = locationToString(constructExpr->getBeginLoc());
+ callSet.insert(callInfo);
+ }
+ return true;
+}
+
+std::string CountUsersOfDefaultParams::locationToString(const SourceLocation& sourceLoc)
+{
+ SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( sourceLoc );
+ StringRef name = getFilenameOfLocation(expansionLoc);
+ return std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
+}
+
+bool CountUsersOfDefaultParams::VisitFunctionDecl( const FunctionDecl* functionDecl )
+{
+ functionDecl = functionDecl->getCanonicalDecl();
+ if( !functionDecl || !functionDecl->getLocation().isValid() || ignoreLocation( functionDecl )) {
+ return true;
+ }
+ const CXXMethodDecl* methodDecl = dyn_cast_or_null<CXXMethodDecl>(functionDecl);
+
+ // ignore method overrides, since the call will show up as being directed to the root method
+ if (methodDecl && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr<OverrideAttr>())) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl)) {
+ return true;
+ }
+ if (isa<CXXDestructorDecl>(functionDecl)) {
+ return true;
+ }
+ if (functionDecl->isDeleted()) {
+ return true;
+ }
+ auto n = functionDecl->getNumParams();
+ if (n == 0 || !functionDecl->getParamDecl(n - 1)->hasDefaultArg()) {
+ return true;
+ }
+
+ if( functionDecl->getLocation().isValid() )
+ {
+ MyFuncInfo funcInfo;
+ niceName(functionDecl, funcInfo);
+ definitionSet.insert(funcInfo);
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration< CountUsersOfDefaultParams > X("countusersofdefaultparams", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/countusersofdefaultparams.py b/compilerplugins/clang/store/countusersofdefaultparams.py
new file mode 100755
index 0000000000..64ef6604af
--- /dev/null
+++ b/compilerplugins/clang/store/countusersofdefaultparams.py
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+
+import re
+import io
+
+definitionToSourceLocationMap = dict()
+callDict = dict()
+
+# clang does not always use exactly the same numbers in the type-parameter vars it generates
+# so I need to substitute them to ensure we can match correctly.
+normalizeTypeParamsRegex1 = re.compile(r"type-parameter-\d+-\d+")
+normalizeTypeParamsRegex2 = re.compile(r"typename enable_if<.*")
+def normalizeTypeParams( line ):
+ line = normalizeTypeParamsRegex1.sub("type-parameter-?-?", line)
+ return normalizeTypeParamsRegex2.sub("type-parameter-?-?", line)
+
+with io.open("workdir/loplugin.countusersofdefaultparams.log", "rb", buffering=1024*1024) as txt:
+ for line in txt:
+ tokens = line.strip().split("\t")
+ if tokens[0] == "defn:":
+ access = tokens[1]
+ returnType = tokens[2]
+ nameAndParams = tokens[3]
+ sourceLocation = tokens[4]
+ funcInfo = normalizeTypeParams(returnType) + " " + normalizeTypeParams(nameAndParams)
+ definitionToSourceLocationMap[funcInfo] = sourceLocation
+ if not funcInfo in callDict:
+ callDict[funcInfo] = set()
+ elif tokens[0] == "call:":
+ returnType = tokens[1]
+ nameAndParams = tokens[2]
+ sourceLocationOfCall = tokens[3]
+ funcInfo = normalizeTypeParams(returnType) + " " + normalizeTypeParams(nameAndParams)
+ if not funcInfo in callDict:
+ callDict[funcInfo] = set()
+ callDict[funcInfo].add(sourceLocationOfCall)
+ else:
+ print( "unknown line: " + line)
+
+tmp1list = list()
+for k,v in callDict.iteritems():
+ if len(v) >= 1:
+ continue
+ # created by macros
+ if k.endswith("::RegisterInterface(class SfxModule *)"):
+ continue
+ if k.endswith("::RegisterChildWindow(_Bool,class SfxModule *,enum SfxChildWindowFlags)"):
+ continue
+ if k.endswith("::RegisterControl(unsigned short,class SfxModule *)"):
+ continue
+ if k.endswith("::RegisterFactory(unsigned short)"):
+ continue
+ # windows-only stuff
+ if "ShutdownIcon::OpenURL" in k:
+ continue
+ # template magic
+ if k.startswith("void VclPtr::VclPtr(const VclPtr<type-parameter-?-?> &,typename UpCast<"):
+ continue
+ if k in definitionToSourceLocationMap:
+ tmp1list.append((k, definitionToSourceLocationMap[k]))
+
+# sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
+def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
+ return [int(text) if text.isdigit() else text.lower()
+ for text in re.split(_nsre, s)]
+# sort by both the source-line and the datatype, so the output file ordering is stable
+# when we have multiple items on the same source line
+def v_sort_key(v):
+ return natural_sort_key(v[1]) + [v[0]]
+
+# sort results by name and line number
+tmp1list.sort(key=lambda v: v_sort_key(v))
+
+# print out the results
+with open("loplugin.countusersofdefaultparams.report", "wt") as f:
+ for t in tmp1list:
+ f.write(t[1] + "\n")
+ f.write(" " + t[0] + "\n")
+
+
diff --git a/compilerplugins/clang/store/deadclass.cxx b/compilerplugins/clang/store/deadclass.cxx
new file mode 100644
index 0000000000..f055d6de28
--- /dev/null
+++ b/compilerplugins/clang/store/deadclass.cxx
@@ -0,0 +1,69 @@
+/* -*- 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"
+
+namespace {
+
+class DeadClass:
+ public loplugin::FilteringPlugin<DeadClass>
+{
+public:
+ explicit DeadClass(InstantiationData const & data): FilteringPlugin(data) {}
+
+ void run() override;
+
+ bool VisitCXXRecordDecl(CXXRecordDecl const *);
+};
+
+void DeadClass::run() {
+ if (compiler.getLangOpts().CPlusPlus) {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+}
+
+bool DeadClass::VisitCXXRecordDecl(CXXRecordDecl const * decl) {
+ if (ignoreLocation(decl) || !decl->isThisDeclarationADefinition())
+ return true;
+ if (decl->needsImplicitDefaultConstructor())
+ return true;
+ if (decl->getDescribedClassTemplate())
+ return true;
+ if (isa<ClassTemplateSpecializationDecl>(decl))
+ return true;
+ int otherCnt = 0;
+ int copyMoveCnt = 0;
+ for (auto i = decl->ctor_begin(); i != decl->ctor_end(); ++i) {
+ if (!i->isUserProvided())
+ continue;
+ if (i->isCopyOrMoveConstructor())
+ copyMoveCnt++;
+ else
+ otherCnt++;
+ }
+ if (otherCnt == 0 && copyMoveCnt > 0)
+ {
+ report(
+ DiagnosticsEngine::Warning,
+ "class has only copy/move constructors, must be dead",
+ decl->getLocStart())
+ << decl->getSourceRange();
+ for (auto i = decl->ctor_begin(); i != decl->ctor_end(); ++i) {
+ if (i->isDeleted())
+ continue;
+ }
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<DeadClass> X("deadclass");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/defaultparams.cxx b/compilerplugins/clang/store/defaultparams.cxx
new file mode 100644
index 0000000000..cb533cb190
--- /dev/null
+++ b/compilerplugins/clang/store/defaultparams.cxx
@@ -0,0 +1,128 @@
+/* -*- 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 <string>
+#include <set>
+
+#include "plugin.hxx"
+
+// Find places where we call a method with values == the values specified in the parameter defaults.
+// i.e. where the code might as well not specify anything.
+
+namespace {
+
+class DefaultParams:
+ public loplugin::FilteringPlugin<DefaultParams>
+{
+public:
+ explicit DefaultParams(InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCallExpr(CallExpr * callExpr);
+private:
+ bool evaluate(const Expr* expr, APSInt& x);
+};
+
+bool DefaultParams::VisitCallExpr(CallExpr * callExpr) {
+ if (ignoreLocation(callExpr)) {
+ return true;
+ }
+ if (callExpr->getDirectCallee() == nullptr) {
+ return true;
+ }
+ const FunctionDecl* functionDecl = callExpr->getDirectCallee()->getCanonicalDecl();
+ auto n = functionDecl->getNumParams();
+ if (n == 0 || !functionDecl->getParamDecl(n - 1)->hasDefaultArg()) {
+ return true;
+ }
+ assert(callExpr->getNumArgs() <= n); // can be < in template code
+ for (unsigned i = callExpr->getNumArgs(); i != 0;) {
+ --i;
+ Expr* arg = callExpr->getArg(i);
+ if (arg->isDefaultArgument()) {
+ continue;
+ }
+ // ignore this, it seems to trigger an infinite recursion
+ if (isa<UnaryExprOrTypeTraitExpr>(arg))
+ break;
+ const ParmVarDecl* parmVarDecl = functionDecl->getParamDecl(i);
+ if (!parmVarDecl->hasDefaultArg()
+ || parmVarDecl->hasUninstantiatedDefaultArg())
+ {
+ break;
+ }
+ const Expr* defaultArgExpr = parmVarDecl->getDefaultArg();
+ if (!defaultArgExpr) {
+ break;
+ }
+ bool found = false;
+ if (defaultArgExpr->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent)
+ && arg->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent))
+ {
+ found = true;
+ }
+ if (!found)
+ {
+ APSInt x1, x2;
+ if (evaluate(defaultArgExpr, x1) && evaluate(arg, x2) && x1 == x2)
+ {
+ found = true;
+ }
+ }
+ // catch params with defaults like "= OUString()"
+ if (!found
+ && isa<MaterializeTemporaryExpr>(arg)
+ && isa<MaterializeTemporaryExpr>(defaultArgExpr))
+ {
+ const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
+ if (strippedArg && isa<CXXTemporaryObjectExpr>(strippedArg->getSubExpr())
+ && dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr())->getNumArgs() == 0)
+ {
+ found = true;
+ }
+ }
+ if (!found)
+ break;
+ // Ignore CPPUNIT, it's macros contain some stuff that triggers us
+ StringRef aFileName = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(parmVarDecl->getLocStart()));
+ if (aFileName.find("include/cppunit") != std::string::npos)
+ break;
+ report(
+ DiagnosticsEngine::Warning,
+ "not necessary to pass this argument, it defaults to the same value",
+ arg->getSourceRange().getBegin())
+ << arg->getSourceRange();
+ report(
+ DiagnosticsEngine::Note,
+ "default method parameter declaration here",
+ parmVarDecl->getSourceRange().getBegin())
+ << parmVarDecl->getSourceRange();
+ }
+ return true;
+}
+
+bool DefaultParams::evaluate(const Expr* expr, APSInt& x)
+{
+ if (isa<CXXNullPtrLiteralExpr>(expr)) {
+ x = 0;
+ return true;
+ }
+ if (expr->EvaluateAsInt(x, compiler.getASTContext()))
+ {
+ return true;
+ }
+ return false;
+}
+
+loplugin::Plugin::Registration< DefaultParams > X("defaultparams");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/deletedspecial.cxx b/compilerplugins/clang/store/deletedspecial.cxx
new file mode 100644
index 0000000000..52e717d340
--- /dev/null
+++ b/compilerplugins/clang/store/deletedspecial.cxx
@@ -0,0 +1,153 @@
+/* -*- 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 <cassert>
+#include <iterator>
+#include <string>
+
+#include "plugin.hxx"
+
+// Second-guess that certain private special member function declarations for
+// which no definition can be found are left undefined to prevent them from
+// being implicitly declared. Such situations are better expressed by marking
+// the function as = delete (it e.g. helps compilers determine
+// whether class members are unused if all of a class's member definitions are
+// seen in a compilation unit). (Default constructors for classes with multiple
+// constructors are exempted as they would not be implicitly declared.
+// Destructors are exempted because it is likely that a destructor is defined
+// private on purpose.)
+
+namespace {
+
+CXXRecordDecl const * getClass(CXXMethodDecl const * decl) {
+ CXXRecordDecl const * cls = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
+ assert(cls != nullptr);
+ return cls;
+}
+
+class DeletedSpecial:
+ public loplugin::FilteringPlugin<DeletedSpecial>
+{
+public:
+ explicit DeletedSpecial(InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override;
+
+ bool VisitCXXMethodDecl(CXXMethodDecl const * decl);
+
+private:
+ bool allowlist(
+ CXXMethodDecl const * decl, std::string const & name,
+ std::string const & path);
+};
+
+void DeletedSpecial::run() {
+ if (compiler.getLangOpts().CPlusPlus
+ && compiler.getPreprocessor().getIdentifierInfo(
+ "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+}
+
+bool DeletedSpecial::VisitCXXMethodDecl(CXXMethodDecl const * decl) {
+ if (ignoreLocation(decl) || !decl->isFirstDecl() || decl->isDefined()
+ || decl->isDefaulted() || decl->getAccess() != AS_private)
+ {
+ return true;
+ }
+ std::string desc;
+ if (decl->isCopyAssignmentOperator()) {
+ if (allowlist(decl, "ImpGraphic", "vcl/inc/impgraph.hxx")
+ || allowlist(decl, "SwSubFont", "sw/source/core/inc/swfont.hxx"))
+ {
+ return true;
+ }
+ desc = "copy assignment operator";
+ } else if (decl->isMoveAssignmentOperator()) {
+ desc = "move assignment operator";
+ } else {
+ CXXConstructorDecl const * ctor = dyn_cast<CXXConstructorDecl>(decl);
+ CXXRecordDecl const * cls = getClass(decl);
+ if (ctor != nullptr && ctor->isCopyConstructor()) {
+ if (allowlist(decl, "ImpGraphic", "vcl/inc/impgraph.hxx")
+ || allowlist(decl, "SbMethod", "include/basic/sbmeth.hxx")
+ || allowlist(decl, "ScDBCollection::NamedDBs", "sc/inc/dbdata.hxx")
+ || allowlist(decl, "ScDrawPage", "sc/inc/drawpage.hxx")
+ || allowlist(decl, "SmEditSource", "starmath/source/accessibility.hxx")
+ || allowlist(decl, "SwChartDataSequence", "sw/inc/unochart.hxx")
+ || allowlist(decl, "SwDPage", "sw/inc/dpage.hxx")
+ || allowlist(decl, "SwRedlineExtraData_Format", "sw/inc/redline.hxx")
+ || allowlist(decl, "SwRedlineExtraData_FormattingChanges", "sw/inc/redline.hxx")
+ || allowlist(decl, "SwTextAPIEditSource", "sw/source/core/inc/textapi.hxx")
+ || allowlist(decl, "XclImpBiff5Decrypter", "sc/source/filter/inc/xistream.hxx")
+ || allowlist(decl, "XclImpBiff8Decrypter", "sc/source/filter/inc/xistream.hxx")
+ || allowlist(decl, "configmgr::LocalizedPropertyNode", "configmgr/source/localizedpropertynode.hxx")
+ || allowlist(decl, "configmgr::LocalizedValueNode", "configmgr/source/localizedvaluenode.hxx")
+ || allowlist(decl, "configmgr::PropertyNode", "configmgr/source/propertynode.hxx")
+ || allowlist(decl, "oox::xls::BiffDecoder_RCF", "sc/source/filter/inc/biffcodec.hxx")
+ || allowlist(decl, "oox::xls::BiffDecoder_XOR", "sc/source/filter/inc/biffcodec.hxx")
+ || allowlist(decl, "rptui::OReportPage", "reportdesign/inc/RptPage.hxx"))
+ {
+ return true;
+ }
+ desc = "copy constructor";
+ } else if (ctor != nullptr && ctor->isMoveConstructor()) {
+ desc = "move constructor";
+ } else if (ctor != nullptr && ctor->isDefaultConstructor()
+ && std::distance(cls->ctor_begin(), cls->ctor_end()) == 1)
+ {
+ if (allowlist(decl, "AquaA11yFocusListener", "vcl/osx/a11yfocuslistener.hxx")
+ || allowlist(decl, "DocTemplLocaleHelper", "sfx2/source/doc/doctemplateslocal.hxx")
+ || allowlist(decl, "ScViewDataTable", "sc/source/filter/excel/../../ui/inc/viewdata.hxx")
+ || allowlist(decl, "ScViewDataTable", "sc/source/ui/inc/viewdata.hxx")
+ || allowlist(decl, "SwLineInfo", "sw/source/core/text/inftxt.hxx")
+ || allowlist(decl, "XRenderPeer", "vcl/unx/generic/gdi/xrender_peer.hxx")
+ || allowlist(decl, "desktop::DispatchWatcher", "desktop/source/app/dispatchwatcher.hxx")
+ || allowlist(decl, "desktop::RequestHandler", "desktop/source/app/officeipcthread.hxx")
+ || allowlist(decl, "desktop::RequestHandler", "desktop/source/lib/../app/officeipcthread.hxx")
+ || allowlist(decl, "sd::DiscoveryService", "sd/source/ui/remotecontrol/DiscoveryService.hxx")
+ || allowlist(decl, "sd::IconCache", "sd/source/ui/inc/tools/IconCache.hxx")
+ || allowlist(decl, "sd::RemoteServer", "sd/source/ui/inc/RemoteServer.hxx")
+ || allowlist(decl, "sd::slidesorter::cache::PageCacheManager", "sd/source/ui/slidesorter/inc/cache/SlsPageCacheManager.hxx")
+ || allowlist(decl, "framework::CommandInfoProvider", "include/framework/commandinfoprovider.hxx")
+ || allowlist(decl, "vcl::SettingsConfigItem", "vcl/inc/configsettings.hxx")
+ || allowlist(decl, "writerfilter::ooxml::OOXMLFactory", "writerfilter/source/ooxml/OOXMLFactory.hxx"))
+ {
+ return true;
+ }
+ desc = "default constructor";
+ } else {
+ return true;
+ }
+ }
+ report(
+ DiagnosticsEngine::Warning,
+ ("private %0 is not defined at least in this compilation unit, maybe it"
+ " should be marked as deleted?"),
+ decl->getLocation())
+ << desc << decl->getSourceRange();
+ return true;
+}
+
+bool DeletedSpecial::allowlist(
+ CXXMethodDecl const * decl, std::string const & name,
+ std::string const & path)
+{
+ return getClass(decl)->getQualifiedNameAsString() == name
+ && (getFilenameOfLocation(
+ compiler.getSourceManager().getSpellingLoc(decl->getLocation()))
+ == SRCDIR "/" + path);
+}
+
+loplugin::Plugin::Registration<DeletedSpecial> X("deletedspecial", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/derivedclass.cxx b/compilerplugins/clang/store/derivedclass.cxx
new file mode 100644
index 0000000000..a1df0c253b
--- /dev/null
+++ b/compilerplugins/clang/store/derivedclass.cxx
@@ -0,0 +1,70 @@
+/* -*- 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"
+
+namespace {
+
+class DerivedClass:
+ public loplugin::FilteringPlugin<DerivedClass>
+{
+public:
+ explicit DerivedClass(InstantiationData const & data):
+ FilteringPlugin(data) {}
+
+ virtual void run() override
+ { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXRecordDecl(CXXRecordDecl const * decl);
+};
+
+bool BaseCheck(const CXXRecordDecl *BaseDefinition, void *BaseClassName) {
+ // print warning about deriving from this classes
+ // the name has to contain namespace, e.g. foo::bar::ClassName
+ const char *BaseClasses[] = {
+ "Dialog",
+ "ProgressBar",
+ "SfxToolBoxControl",
+ "StatusBar",
+ 0,
+ };
+ for (int i = 0; BaseClasses[i]; i++)
+ if (BaseDefinition->getQualifiedNameAsString().compare(BaseClasses[i]) == 0) {
+ *(const char **)BaseClassName = BaseClasses[i];
+ return false;
+ }
+ return true;
+}
+
+bool DerivedClass::VisitCXXRecordDecl(CXXRecordDecl const * decl) {
+ const char *BaseClassName = 0;
+ // checking for decl->hasDefinition() avoids crash in decl->forallBases
+ if (decl->hasDefinition() &&
+ // not sure what hasAnyDependentBases() does,
+ // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1
+ !decl->hasAnyDependentBases() &&
+ !decl->forallBases(BaseCheck, &BaseClassName)) {
+ string warning_msg("class %0 derives from ");
+ // no idea how BaseClassName can be 0 sometimes...
+ if (BaseClassName)
+ warning_msg += BaseClassName;
+ report(
+ DiagnosticsEngine::Warning,
+ warning_msg,
+ decl->getLocStart())
+ << decl->getQualifiedNameAsString() << decl->getSourceRange();
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<DerivedClass> X("derivedclass");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 tabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/dodgyswitch.cxx b/compilerplugins/clang/store/dodgyswitch.cxx
new file mode 100644
index 0000000000..43958f1364
--- /dev/null
+++ b/compilerplugins/clang/store/dodgyswitch.cxx
@@ -0,0 +1,78 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include "plugin.hxx"
+
+namespace {
+
+class DodgySwitch:
+ public loplugin::FilteringPlugin<DodgySwitch>
+{
+public:
+ explicit DodgySwitch(loplugin::InstantiationData const & data): FilteringPlugin(data)
+ {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitDefaultStmt(DefaultStmt const * );
+ bool VisitCaseStmt(CaseStmt const * );
+private:
+ bool IsParentSwitch(Stmt const * );
+};
+
+bool DodgySwitch::VisitDefaultStmt(DefaultStmt const * defaultStmt)
+{
+ if (ignoreLocation(defaultStmt))
+ return true;
+ if (!IsParentSwitch(defaultStmt))
+ report(
+ DiagnosticsEngine::Warning, "default statement not directly under switch",
+ defaultStmt->getBeginLoc())
+ << defaultStmt->getSourceRange();
+ return true;
+}
+
+bool DodgySwitch::VisitCaseStmt(CaseStmt const * caseStmt)
+{
+ if (ignoreLocation(caseStmt))
+ return true;
+ if (!IsParentSwitch(caseStmt))
+ {
+ //parentStmt(parentStmt(caseStmt))->dump();
+ report(
+ DiagnosticsEngine::Warning, "case statement not directly under switch",
+ caseStmt->getBeginLoc())
+ << caseStmt->getSourceRange();
+ }
+ return true;
+}
+
+bool DodgySwitch::IsParentSwitch(Stmt const * stmt)
+{
+ auto parent = getParentStmt(stmt);
+ if (isa<CaseStmt>(parent) || isa<DefaultStmt>(parent)) // daisy chain
+ return true;
+ auto compoundStmt = dyn_cast<CompoundStmt>(parent);
+ if (!compoundStmt)
+ return false;
+ return isa<SwitchStmt>(getParentStmt(compoundStmt));
+}
+
+loplugin::Plugin::Registration< DodgySwitch > X("dodgyswitch", false);
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/doubleconvert.cxx b/compilerplugins/clang/store/doubleconvert.cxx
new file mode 100644
index 0000000000..6f9cc88df7
--- /dev/null
+++ b/compilerplugins/clang/store/doubleconvert.cxx
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#ifndef LO_CLANG_SHARED_PLUGINS
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+/**
+ * Look for places where we are converting from type A through a conversion operator and back to type A,
+ * which is redundant. At the moment only look for Color, to aid my ColorData->Color conversion
+ */
+namespace
+{
+class DoubleConvert final : public loplugin::FilteringPlugin<DoubleConvert>
+{
+public:
+ explicit DoubleConvert(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+ void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr const*);
+};
+
+/**
+ The AST looks like:
+
+ CXXOperatorCallExpr 0x8e5b840 'class Color' lvalue
+|-ImplicitCastExpr 0x8e5b828 'class Color &(*)(class Color &&) noexcept' <FunctionToPointerDecay>
+| `-DeclRefExpr 0x8e5b800 'class Color &(class Color &&) noexcept' lvalue CXXMethod 0x8e59a08 'operator=' 'class Color &(class Color &&) noexcept'
+|-DeclRefExpr 0x8e5b678 'class Color' lvalue Var 0x8e5b5d0 'col2' 'class Color'
+`-MaterializeTemporaryExpr 0x8e5b7e8 'class Color' xvalue
+ `-CXXConstructExpr 0x8e5b7b0 'class Color' 'void (ColorData)'
+ `-ImplicitCastExpr 0x8e5b798 'ColorData':'unsigned int' <IntegralCast>
+ `-CXXFunctionalCastExpr 0x8e5b770 'sal_Int32':'int' functional cast to sal_Int32 <NoOp>
+ `-ImplicitCastExpr 0x8e5b758 'sal_Int32':'int' <UserDefinedConversion>
+ `-CXXMemberCallExpr 0x8e5b730 'sal_Int32':'int'
+ `-MemberExpr 0x8e5b6f8 '<bound member function type>' .operator int 0x8e51048
+ `-ImplicitCastExpr 0x8e5b6e0 'const class Color' lvalue <NoOp>
+ `-DeclRefExpr 0x8e5b6b0 'class Color' lvalue Var 0x8e5b518 'col1' 'class Color'
+*/
+bool DoubleConvert::VisitCXXConstructExpr(CXXConstructExpr const* cxxConstruct)
+{
+ if (ignoreLocation(cxxConstruct))
+ return true;
+ if (cxxConstruct->getNumArgs() == 0)
+ return true;
+ auto cxxMemberCallExpr
+ = dyn_cast<CXXMemberCallExpr>(cxxConstruct->getArg(0)->IgnoreParenCasts());
+ if (!cxxMemberCallExpr)
+ return true;
+ if (!isa_and_nonnull<CXXConversionDecl>(cxxMemberCallExpr->getMethodDecl()))
+ return true;
+ if (cxxConstruct->getType().getCanonicalType().getTypePtr()
+ != cxxMemberCallExpr->getImplicitObjectArgument()
+ ->getType()
+ .getCanonicalType()
+ .getTypePtr())
+ return true;
+ if (!loplugin::TypeCheck(cxxConstruct->getType().getCanonicalType())
+ .Class("Color")
+ .GlobalNamespace())
+ return true;
+
+ report(DiagnosticsEngine::Warning, "redundant double conversion", cxxConstruct->getExprLoc())
+ << cxxConstruct->getSourceRange();
+ return true;
+}
+
+static loplugin::Plugin::Registration<DoubleConvert> doubleconvert("doubleconvert");
+}
+
+#endif // LO_CLANG_SHARED_PLUGINS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/compilerplugins/clang/store/finalprotected.cxx b/compilerplugins/clang/store/finalprotected.cxx
new file mode 100644
index 0000000000..c7296232a2
--- /dev/null
+++ b/compilerplugins/clang/store/finalprotected.cxx
@@ -0,0 +1,84 @@
+/* -*- 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/.
+ */
+#ifndef LO_CLANG_SHARED_PLUGINS
+
+#include <string>
+#include <iostream>
+#include <map>
+#include <set>
+
+#include "plugin.hxx"
+#include "clang/AST/CXXInheritance.h"
+
+// Check for final classes that have protected members
+
+namespace
+{
+
+class FinalProtected:
+ public loplugin::FilteringPlugin<FinalProtected>
+{
+public:
+ explicit FinalProtected(loplugin::InstantiationData const & data):
+ FilteringPlugin(data) {}
+
+ virtual void run() override {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitCXXMethodDecl(CXXMethodDecl const *);
+ bool VisitFieldDecl(FieldDecl const *);
+};
+
+
+bool FinalProtected::VisitCXXMethodDecl(CXXMethodDecl const * cxxMethodDecl)
+{
+ if (ignoreLocation(cxxMethodDecl)) {
+ return true;
+ }
+ if (cxxMethodDecl->getAccess() != AS_protected) {
+ return true;
+ }
+ if (!cxxMethodDecl->getParent()->hasAttr<FinalAttr>()) {
+ return true;
+ }
+ cxxMethodDecl = cxxMethodDecl->getCanonicalDecl();
+ report(DiagnosticsEngine::Warning,
+ "final class should not have protected members - convert them to private",
+ cxxMethodDecl->getBeginLoc())
+ << cxxMethodDecl->getSourceRange();
+ return true;
+}
+
+bool FinalProtected::VisitFieldDecl(FieldDecl const * fieldDecl)
+{
+ if (ignoreLocation(fieldDecl)) {
+ return true;
+ }
+ if (fieldDecl->getAccess() != AS_protected) {
+ return true;
+ }
+ if (!fieldDecl->getParent()->hasAttr<FinalAttr>()) {
+ return true;
+ }
+ fieldDecl = fieldDecl->getCanonicalDecl();
+ report(DiagnosticsEngine::Warning,
+ "final class should not have protected members - convert them to private",
+ fieldDecl->getBeginLoc())
+ << fieldDecl->getSourceRange();
+ return true;
+}
+
+loplugin::Plugin::Registration< FinalProtected > finalprotected("finalprotected");
+
+} // namespace
+
+#endif // LO_CLANG_SHARED_PLUGINS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/findoncontainer.cxx b/compilerplugins/clang/store/findoncontainer.cxx
new file mode 100644
index 0000000000..09f51187ae
--- /dev/null
+++ b/compilerplugins/clang/store/findoncontainer.cxx
@@ -0,0 +1,77 @@
+/* -*- 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 <string>
+#include <set>
+
+#include "plugin.hxx"
+
+// Look for places calling std::find on a standard container where it should be using the container find method, which
+// is more efficient.
+//
+// This lives in /store because the implementation is a hack and is highly dependent on the inwards
+// of the libc++ library on the machine it runs on.
+//
+
+namespace {
+
+class FindOnContainer:
+ public loplugin::FilteringPlugin<FindOnContainer>
+{
+public:
+ explicit FindOnContainer(InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCallExpr(const CallExpr * expr);
+};
+
+bool FindOnContainer::VisitCallExpr(const CallExpr * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ FunctionDecl const * fdecl = expr->getDirectCallee();
+ if (fdecl == nullptr) {
+ return true;
+ }
+ std::string qname { fdecl->getQualifiedNameAsString() };
+ if (qname == "std::find")
+ {
+ std::string tname = expr->getArg(0)->getType().getAsString();
+ if (tname.find("std::_List_iterator") != std::string::npos
+ || tname.find("std::_List_const_iterator") != std::string::npos
+ || tname.find("std::vector") != std::string::npos
+ || tname.find("std::_Deque_iterator") != std::string::npos
+ || tname == "const int *"
+ || tname == "struct std::_Bit_const_iterator"
+ || tname == "const rtl::OUString *"
+ || tname == "class rtl::OUString *"
+ || tname == "const class rtl::OUString *"
+ || tname == "const sal_Int8 *"
+ || tname == "const sal_Int32 *"
+ || tname == "sal_Int32 *"
+ || tname == "sal_uInt16 *" )
+ {
+ return true;
+ }
+ expr->dump();
+ report(
+ DiagnosticsEngine::Warning,
+ ("rather use the more specific find method " + tname),
+ expr->getExprLoc());
+ return true;
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration< FindOnContainer > X("findoncontainer");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/fpcomparison.cxx b/compilerplugins/clang/store/fpcomparison.cxx
new file mode 100644
index 0000000000..84d0aaee54
--- /dev/null
+++ b/compilerplugins/clang/store/fpcomparison.cxx
@@ -0,0 +1,382 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+/**
+comparing floating point numbers using == or != is a bad idea.
+*/
+
+namespace {
+
+class FpComparison:
+ public loplugin::FilteringPlugin<FpComparison>
+{
+public:
+ explicit FpComparison(loplugin::InstantiationData const & data):
+ FilteringPlugin(data) {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitBinaryOperator(const BinaryOperator* );
+ bool TraverseFunctionDecl(FunctionDecl* );
+ bool TraverseCXXMethodDecl(CXXMethodDecl* );
+private:
+ bool ignore(FunctionDecl* );
+ enum class EState { None, TraverseProcess, TraverseIgnore };
+ EState meState = EState::None;
+};
+
+bool FpComparison::TraverseFunctionDecl(FunctionDecl* function)
+{
+ bool bIgnore = ignore(function);
+ meState = bIgnore ? EState::TraverseIgnore : EState::TraverseProcess;
+ bool bRet = RecursiveASTVisitor::TraverseFunctionDecl(function);
+ meState = EState::None;
+ return bRet;
+}
+
+bool FpComparison::TraverseCXXMethodDecl(CXXMethodDecl* function)
+{
+ bool bIgnore = ignore(function);
+ meState = bIgnore ? EState::TraverseIgnore : EState::TraverseProcess;
+ bool bRet = RecursiveASTVisitor::TraverseCXXMethodDecl(function);
+ meState = EState::None;
+ return bRet;
+}
+
+bool FpComparison::ignore(FunctionDecl* function)
+{
+ if (ignoreLocation(function)) {
+ return true;
+ }
+ // we assume that these modules know what they are doing with FP stuff
+ StringRef aFileName = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(function->getLocStart()));
+ if (loplugin::hasPathnamePrefix(aFileName, SRCDIR "/sc/")) {
+ return true;
+ }
+ if (!function->doesThisDeclarationHaveABody()) {
+ return true;
+ }
+ // Ignore operator== and operator!=
+ if (function->getOverloadedOperator() == OO_EqualEqual
+ || function->getOverloadedOperator() == OO_ExclaimEqual) {
+ return true;
+ }
+ // ignore known good functions
+ loplugin::DeclCheck dc(function);
+ if ((dc.Function("approxEqual").Namespace("math").Namespace("rtl")
+ .GlobalNamespace())
+ || dc.Function("doubleToString").AnonymousNamespace().GlobalNamespace()
+ || dc.Function("stringToDouble").AnonymousNamespace().GlobalNamespace()
+ || dc.Function("rtl_math_round").GlobalNamespace()
+ || dc.Function("rtl_math_approxEqual").GlobalNamespace()
+ || dc.Function("rtl_math_approxValue").GlobalNamespace()
+ || dc.Function("rtl_math_asinh").GlobalNamespace()
+ || dc.Function("rtl_math_acosh").GlobalNamespace()
+ || dc.Function("_equalSequence").Namespace("cppu").GlobalNamespace()
+ // cppu/source/uno/eq.hxx
+ || dc.Function("_equalData").Namespace("cppu").GlobalNamespace()
+ // cppu/source/uno/eq.hxx
+ || dc.Function("equalFont").Namespace("xmlscript").GlobalNamespace()
+ // xmlscript/source/xmldlg_imexp/xmldlg_export.cxx
+ || (dc.Function("initialize").Class("Impl2").AnonymousNamespace()
+ .GlobalNamespace())
+ // testtools/source/bridgetest/constructors.cxx
+ || (dc.Function("initialize").Class("Impl").AnonymousNamespace()
+ .GlobalNamespace())
+ // testtools/source/bridgetest/constructors.cxx
+ || dc.Function("lok_approxEqual").AnonymousNamespace().GlobalNamespace()
+ // libreofficekit/source/gtk/lokdocview.cxx
+ // These might need fixing:
+ || (dc.Function("getSmallestDistancePointToPolygon").Namespace("utils")
+ .Namespace("basegfx").GlobalNamespace())
+ // basegfx/source/polygon/b2dpolygontools.cxx
+ || (dc.Function("getSmallestDistancePointToPolyPolygon")
+ .Namespace("utils").Namespace("basegfx").GlobalNamespace())
+ // basegfx/source/polygon/b2dpolypolygontools.cxx
+ || dc.Function("performTest").Namespace("bridge_test").GlobalNamespace()
+ // testtools/source/bridgetest/bridgetest.cxx
+ || dc.Function("equals").Namespace("bridge_test").GlobalNamespace()
+ || (dc.Function("lcl_getNANInsteadDBL_MIN").AnonymousNamespace()
+ .GlobalNamespace())
+ // chart2/source/controller/chartapiwrapper/ChartDataWrapper.cxx
+ || dc.Function("compareSubstring").Class("ChapterCollator").Namespace("i18npool").GlobalNamespace()
+ || dc.Function("setDateTime").Class("Calendar_gregorian").Namespace("i18npool").GlobalNamespace()
+ || dc.Function("setLocalDateTime").Class("Calendar_gregorian").Namespace("i18npool").GlobalNamespace()
+ //TODO:
+ || dc.Function("ImpCheckCondition").Class("SvNumberformat").GlobalNamespace()
+ || dc.Function("GetTimeFormat").Class("SvNumberFormatter").GlobalNamespace()
+ || dc.Function("GuessDateTimeFormat").Class("SvNumberFormatter").GlobalNamespace()
+ || dc.Function("GetEditFormat").Class("SvNumberFormatter").GlobalNamespace()
+ || dc.Function("getSmallestDistancePointToBezierSegment").Class("B2DCubicBezier").Namespace("basegfx").GlobalNamespace()
+ || dc.Function("getLength").Class("B3DVector").Namespace("basegfx").GlobalNamespace()
+ || dc.Function("getXZLength").Class("B3DVector").Namespace("basegfx").GlobalNamespace()
+ || dc.Function("getYZLength").Class("B3DVector").Namespace("basegfx").GlobalNamespace()
+ || dc.Function("impSolve").Class("solver").AnonymousNamespace().Namespace("basegfx").GlobalNamespace()
+ || dc.Function("getB2DPolyPolygon").Class("solver").AnonymousNamespace().Namespace("basegfx").GlobalNamespace()
+ || dc.Function("getNormal").Class("CoordinateDataArray3D").GlobalNamespace()
+ || dc.Function("append").Class("ImplPolygon").AnonymousNamespace().Namespace("basegfx").GlobalNamespace()
+ || dc.Function("finish").Class("ImplPolygon").AnonymousNamespace().Namespace("basegfx").GlobalNamespace()
+ || dc.Function("convertMeasure").Class("Converter").Namespace("sax").GlobalNamespace()
+ || dc.Function("convertDouble").Class("Converter").Namespace("sax").GlobalNamespace()
+ || dc.Function("convertDuration").Class("Converter").Namespace("sax").GlobalNamespace()
+ || dc.Function("Frustum").Class("B3dTransformationSet").GlobalNamespace()
+ || dc.Function("Ortho").Class("B3dTransformationSet").GlobalNamespace()
+ || dc.Function("SetRatio").Class("B3dTransformationSet").GlobalNamespace()
+ || dc.Function("SetDeviceRectangle").Class("B3dTransformationSet").GlobalNamespace()
+ || dc.Function("HSBtoRGB").Class("Color").GlobalNamespace()
+ || dc.Function("Normalize").Class("Vector2D").Namespace("tools").GlobalNamespace()
+ || dc.Function("setAcceleration").Class("AnimationNode").Namespace("animcore").GlobalNamespace()
+ || dc.Function("setDecelerate").Class("AnimationNode").Namespace("animcore").GlobalNamespace()
+ || dc.Function("setVolume").Class("AnimationNode").Namespace("animcore").GlobalNamespace()
+ || dc.Function("setIterateInterval").Class("AnimationNode").Namespace("animcore").GlobalNamespace()
+ || dc.Function("createElement").Struct("Style").Namespace("xmlscript").GlobalNamespace()
+ || dc.Function("set_property").Class("Window").Namespace("vcl").GlobalNamespace()
+ || dc.Function("Adjust").Class("Bitmap").GlobalNamespace()
+ || dc.Function("ImplWriteActions").Class("SVMConverter").GlobalNamespace()
+ || dc.Function("Mirror").Class("GDIMetaFile").GlobalNamespace()
+ || dc.Function("Adjust").Class("GDIMetaFile").GlobalNamespace()
+ || dc.Function("Scale").Class("MetaCommentAction").GlobalNamespace()
+ || dc.Function("ImplGetGamma").Class("PNGReaderImpl").Namespace("vcl").GlobalNamespace()
+ || dc.Function("playMetafile").Class("PDFWriterImpl").Namespace("vcl").GlobalNamespace()
+ || dc.Function("invert").Class("Matrix3").Namespace("vcl").GlobalNamespace()
+ || dc.Function("emitTilings").Class("PDFWriterImpl").Namespace("vcl").GlobalNamespace()
+ || dc.Function("drawHorizontalGlyphs").Class("PDFWriterImpl").Namespace("vcl").GlobalNamespace()
+ || dc.Function("ImplWrite").Class("EMFWriter").GlobalNamespace()
+ || dc.Function("WriteRecords").Class("WMFWriter").GlobalNamespace()
+ || dc.Function("convertOneTypeEsc").Class("CffSubsetterContext").GlobalNamespace()
+ || dc.Function("ApplyGlyphTransform").Class("FreetypeFont").GlobalNamespace()
+ || dc.Function("JoinVerticalClipRectangles").Class("PrinterGfx").Namespace("psp").GlobalNamespace()
+ || dc.Function("PSSetLineWidth").Class("PrinterGfx").Namespace("psp").GlobalNamespace()
+ || dc.Function("DrawEPS").Class("PrinterGfx").Namespace("psp").GlobalNamespace()
+ || dc.Function("addDrawRectangle").Class("RenderList").GlobalNamespace()
+ || dc.Function("addDrawPolyPolygon").Class("RenderList").GlobalNamespace()
+ || dc.Function("addDrawPolyLine").Class("RenderList").GlobalNamespace()
+ || dc.Function("ApplyMatrix").Class("OpenGLProgram").GlobalNamespace()
+ || dc.Function("GuessWidth").Class("BorderWidthImpl").GlobalNamespace()
+ || dc.Function("ImplSetValue").Class("FormattedField").GlobalNamespace()
+ || dc.Function("IsAdjusted").Class("GraphicAttr").GlobalNamespace()
+ || dc.Operator(OO_Call).Struct("SpriteWeakOrder").Namespace("canvas").GlobalNamespace()
+ || dc.Function("setAlpha").Class("CanvasCustomSpriteHelper").Namespace("canvas").GlobalNamespace()
+ || dc.Function("setPriority").Class("CanvasCustomSpriteHelper").Namespace("canvas").GlobalNamespace()
+ || dc.Function("createFillGraphicAttribute").Class("SdrFillGraphicAttribute").Namespace("attribute").Namespace("drawinglayer").GlobalNamespace()
+ || dc.Function("create2DDecomposition").Class("ScenePrimitive2D").Namespace("primitive2d").Namespace("drawinglayer").GlobalNamespace()
+ || dc.Function("createAtom").Class("SvgLinearGradientPrimitive2D").Namespace("primitive2d").Namespace("drawinglayer").GlobalNamespace()
+ || dc.Function("createAtom").Class("SvgRadialGradientPrimitive2D").Namespace("primitive2d").Namespace("drawinglayer").GlobalNamespace()
+ || dc.Function("FoldConstantsBinaryNode").Class("SbiExprNode").GlobalNamespace()
+ || dc.Function("Format").Class("SbxValue").GlobalNamespace()
+ || dc.Function("Compare").Class("SbxValue").GlobalNamespace()
+ || dc.Function("SelectPlayToolBoxItem").Class("MediaControlBase").Namespace("avmedia").GlobalNamespace()
+ || dc.Function("convertDateTime").Class("SvXMLUnitConverter").GlobalNamespace()
+ || dc.Function("exportAudio").Class("AnimationsExporterImpl").Namespace("xmloff").GlobalNamespace()
+ || dc.Function("StartElement").Class("XMLEnhancedCustomShapeContext").GlobalNamespace()
+ || dc.Function("SetString").Class("SdXMLImExTransform2D").GlobalNamespace()
+ || dc.Function("SetString").Class("SdXMLImExTransform3D").GlobalNamespace()
+ || dc.Function("ExportPart_Impl").Class("SvXMLNumFmtExport").GlobalNamespace()
+ || dc.Function("AddNumber").Class("SvXMLNumFormatContext").GlobalNamespace()
+ || dc.Function("GetColorData").Class("EnhancedCustomShape2d").GlobalNamespace()
+ || dc.Function("AdaptObjColor").Class("EnhancedCustomShape2d").GlobalNamespace()
+ || dc.Function("RotateScene").Class("E3dScene").GlobalNamespace()
+ || dc.Function("createViewIndependentPrimitive2DSequence").Class("ViewContactOfSdrCaptionObj").Namespace("contact").Namespace("sdr").GlobalNamespace()
+ || dc.Function("PostItemChange").Class("E3dSceneProperties").Namespace("properties").Namespace("sdr").GlobalNamespace()
+ || dc.Function("NbcRotate").Class("SdrObject").GlobalNamespace()
+ || dc.Function("TakeObjNameSingul").Class("SdrPathObj").GlobalNamespace()
+ || dc.Function("NbcInsPoint").Class("SdrPathObj").GlobalNamespace()
+ || dc.Function("setValue").Class("Cell").Namespace("table").Namespace("sdr").GlobalNamespace()
+ || dc.Function("implSetDepth").Class("ExtrusionDepthWindow").Namespace("svx").GlobalNamespace()
+ || dc.Function("PointsToBezier").Class("XPolygon").GlobalNamespace()
+ || dc.Function("SetPosition").Class("Svx3DLightControl").GlobalNamespace()
+ || dc.Function("SetRotation").Class("Svx3DLightControl").GlobalNamespace()
+ || dc.Function("PlayToolBoxSelectHdl").Class("MediaPlaybackPanel").Namespace("sidebar").Namespace("svx").GlobalNamespace()
+ || dc.Function("CreateGraphicProperties").Class("EscherPropertyContainer").GlobalNamespace()
+ || dc.Function("renderSprite").Class("CanvasCustomSprite").Namespace("oglcanvas").GlobalNamespace()
+ || dc.Operator(OO_Call).Struct("SpriteComparator").AnonymousNamespace().Namespace("oglcanvas").GlobalNamespace()
+ || dc.Function("isHorizontalAxis").Class("TickFactory2D").Namespace("chart").GlobalNamespace()
+ || dc.Function("isVerticalAxis").Class("TickFactory2D").Namespace("chart").GlobalNamespace()
+ || dc.Function("getDistanceAxisTickToText").Class("TickFactory2D").Namespace("chart").GlobalNamespace()
+ || dc.Function("calculateExplicitIncrementAndScaleForLogarithmic").Class("ScaleAutomatism").Namespace("chart").GlobalNamespace()
+ || dc.Function("calculateExplicitIncrementAndScaleForLinear").Class("ScaleAutomatism").Namespace("chart").GlobalNamespace()
+ || dc.Function("makeTickmarkPropertiesForComplexCategories").Struct("AxisProperties").Namespace("chart").GlobalNamespace()
+ || dc.Function("createShapes").Class("BarChart").Namespace("chart").GlobalNamespace()
+ || dc.Function("transform").Class("Linear3DTransformation").Namespace("chart").GlobalNamespace()
+ || dc.Function("CalculateCubicSplines").Class("SplineCalculater").Namespace("chart").GlobalNamespace()
+ || dc.Function("setDiagramPositioning").Class("DiagramHelper").Namespace("chart").GlobalNamespace()
+ || dc.Function("centerGrow").Class("RelativePositionHelper").Namespace("chart").GlobalNamespace()
+ || dc.Function("pushToPropMap").Struct("FillProperties").Namespace("drawingml").Namespace("oox").GlobalNamespace()
+ || dc.Function("convertFromProperties").Class("AxFontDataModel").Namespace("ole").Namespace("oox").GlobalNamespace()
+ || dc.Function("isNotANumber").Class("ChartDataWrapper").Namespace("wrapper").Namespace("chart").GlobalNamespace()
+ || dc.Function("Reset").Class("ErrorBarResources").Namespace("chart").GlobalNamespace()
+ || dc.Function("ApplySpecialItem").Class("AxisItemConverter").Namespace("wrapper").Namespace("chart").GlobalNamespace()
+ || dc.Function("ApplySpecialItem").Class("DataPointItemConverter").Namespace("wrapper").Namespace("chart").GlobalNamespace()
+ || dc.Function("ApplySpecialItem").Class("TitleItemConverter").Namespace("wrapper").Namespace("chart").GlobalNamespace()
+ || dc.Function("ApplySpecialItem").Class("TextLabelItemConverter").Namespace("wrapper").Namespace("chart").GlobalNamespace()
+ || dc.Function("operate").Class("OOp_COMPARE").Namespace("file").Namespace("connectivity").GlobalNamespace()
+ || dc.Function("Write").Class("ORTFImportExport").Namespace("dbaui").GlobalNamespace()
+ || dc.Function("appendRow").Class("ORTFImportExport").Namespace("dbaui").GlobalNamespace()
+ || dc.Function("WriteCell").Class("OHTMLImportExport").Namespace("dbaui").GlobalNamespace()
+ || dc.Function("getBold").Class("VbaFontBase").GlobalNamespace()
+ || dc.Function("ModifyHdl").Class("SaneDlg").GlobalNamespace()
+ || dc.Function("EstablishNumericOption").Class("SaneDlg").GlobalNamespace()
+ || dc.Function("translatePropertiesToItems").Class("ControlCharacterDialog").Namespace("pcr").GlobalNamespace()
+ || dc.Function("writeMatrix").Class("Tag").Namespace("swf").GlobalNamespace()
+ || dc.Function("Impl_writeActions").Class("Writer").Namespace("swf").GlobalNamespace()
+ || dc.Function("Impl_quadBezierApprox").Class("Writer").Namespace("swf").GlobalNamespace()
+ || dc.Function("hasGradientOpacity").Struct("AnnotatingVisitor").AnonymousNamespace().Namespace("svgi").GlobalNamespace()
+ || dc.Function("getOdfColor").Struct("AnnotatingVisitor").AnonymousNamespace().Namespace("svgi").GlobalNamespace()
+ || dc.Function("writeStyle").Struct("AnnotatingVisitor").AnonymousNamespace().Namespace("svgi").GlobalNamespace()
+ || dc.Operator(OO_Call).Struct("ShapeWritingVisitor").AnonymousNamespace().Namespace("svgi").GlobalNamespace()
+ || dc.Function("SvgDashArray2Odf").Struct("OfficeStylesWritingVisitor").Namespace("svgi").GlobalNamespace()
+ || dc.Function("ImplWriteMask").Class("SVGActionWriter").GlobalNamespace()
+ || dc.Function("Factor").Class("FormulaCompiler").Namespace("formula").GlobalNamespace()
+ || dc.Function("setDateTime").Class("Calendar_gregorian").Namespace("i18n").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()
+ || dc.Function("setLocalDateTime").Class("Calendar_gregorian").Namespace("i18n").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()
+ || dc.Function("compareSubstring").Class("ChapterCollator").Namespace("i18n").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()
+ || dc.Function("ToXml").Class("XFPadding").GlobalNamespace()
+ || dc.Function("Equal").Class("XFCellStyle").GlobalNamespace()
+ || dc.Function("Equal").Class("XFParaStyle").GlobalNamespace()
+ || dc.Function("GetAnimationEffect").Class("EffectMigration").Namespace("sd").GlobalNamespace()
+ || dc.Function("SetAnimationSpeed").Class("EffectMigration").Namespace("sd").GlobalNamespace()
+ || dc.Function("GetAnimationSpeed").Class("EffectMigration").Namespace("sd").GlobalNamespace()
+ || dc.Function("calculateIterateDuration").Class("CustomAnimationEffect").Namespace("sd").GlobalNamespace()
+ || dc.Function("setDuration").Class("CustomAnimationEffect").Namespace("sd").GlobalNamespace()
+ || dc.Function("replaceNode").Class("CustomAnimationEffect").Namespace("sd").GlobalNamespace()
+ || dc.Function("setIterateInterval").Class("CustomAnimationEffect").Namespace("sd").GlobalNamespace()
+ || dc.Function("append").Class("EffectSequenceHelper").Namespace("sd").GlobalNamespace()
+ || dc.Function("replace").Class("EffectSequenceHelper").Namespace("sd").GlobalNamespace()
+ || dc.Function("createTextGroupParagraphEffects").Class("EffectSequenceHelper").Namespace("sd").GlobalNamespace()
+ || dc.Function("setTextGrouping").Class("EffectSequenceHelper").Namespace("sd").GlobalNamespace()
+ || dc.Function("setTextGroupingAuto").Class("EffectSequenceHelper").Namespace("sd").GlobalNamespace()
+ || dc.Function("SetPresentationPenWidth").Class("SdOptionsMisc").GlobalNamespace()
+ || dc.Function("VarLook").Class("SwCalc").GlobalNamespace()
+ || dc.Function("Prim").Class("SwCalc").GlobalNamespace()
+ || dc.Function("keycompare").Struct("SwSortElement").GlobalNamespace()
+ || dc.Function("FormatValue").Class("SwDBField").GlobalNamespace()
+ || dc.Function("Evaluate").Class("SwDBField").GlobalNamespace()
+ || dc.Function("GetValue").Class("SwTableBox").GlobalNamespace()
+ || dc.Function("Modify").Class("SwTableBoxFormat").GlobalNamespace()
+ || dc.Function("isNotANumber").Class("SwXTextTable").GlobalNamespace()
+ || dc.Function("GetNextToken").Class("CSS1Parser").GlobalNamespace()
+ || dc.Function("update").Class("FontStylePropertyBox").Namespace("sd").GlobalNamespace()
+ || dc.Function("implMenuSelectHdl").Class("FontStylePropertyBox").Namespace("sd").GlobalNamespace()
+ || dc.Function("update").Class("CustomAnimationEffectTabPage").Namespace("sd").GlobalNamespace()
+ || dc.Function("update").Class("CustomAnimationDurationTabPage").Namespace("sd").GlobalNamespace()
+ || dc.Function("update").Class("CustomAnimationTextAnimTabPage").Namespace("sd").GlobalNamespace()
+ || dc.Function("FillCalcWithMergeData").Class("SwDBManager").GlobalNamespace()
+ || dc.Function("compareWith").Struct("TransitionEffect").Namespace("impl").Namespace("sd").GlobalNamespace()
+ || dc.Function("changeSelection").Class("CustomAnimationPane").Namespace("sd").GlobalNamespace()
+ || dc.Function("ContextMenuHdl").Class("SlideshowImpl").Namespace("sd").GlobalNamespace()
+ || dc.Function("FormatBox").Class("DocxAttributeOutput").GlobalNamespace()
+ || dc.Function("GetString").Class("Complex").Namespace("analysis").Namespace("sca").GlobalNamespace()
+ || dc.Function("getDelta").Class("AnalysisAddIn").GlobalNamespace()
+ || dc.Function("DataToDoc").Class("SwInsertDBColAutoPilot").GlobalNamespace()
+ || dc.Function("convertAnimateValue").Class("AnimationExporter").Namespace("ppt").GlobalNamespace()
+ || dc.Function("GetId").Class("PPTExBulletProvider").GlobalNamespace()
+ || dc.Function("SetThumbPosition").Class("PresenterScrollBar").Namespace("presenter").Namespace("sdext").GlobalNamespace()
+ || dc.Function("SetTotalSize").Class("PresenterScrollBar").Namespace("presenter").Namespace("sdext").GlobalNamespace()
+ || dc.Function("SetThumbSize").Class("PresenterScrollBar").Namespace("presenter").Namespace("sdext").GlobalNamespace()
+ || dc.Function("Layout").Class("PresenterNotesView").Namespace("presenter").Namespace("sdext").GlobalNamespace()
+ || dc.Function("compare").Struct("lessThanShape").Class("Shape").Namespace("internal").Namespace("slideshow").GlobalNamespace()
+ || dc.Operator(OO_Call).Struct("lessThanArea").Class("HyperlinkArea").Namespace("internal").Namespace("slideshow").GlobalNamespace()
+ || dc.Function("viewsChanged").Class("PointerSymbol").Namespace("internal").Namespace("slideshow").GlobalNamespace()
+ || dc.Function("RetrieveAttrs").Struct("SmXMLContext_Helper").GlobalNamespace()
+ || dc.Function("CompareImpl").Class("SortedResultSet").GlobalNamespace()
+ || dc.Function("emit").Struct("PDFNumber").Namespace("pdfparse").GlobalNamespace()
+ || dc.Function("visit").Class("DrawXmlOptimizer").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("optimizeTextElements").Class("DrawXmlOptimizer").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("init").Class("DrawXmlFinalizer").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("visit").Class("DrawXmlFinalizer").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("GetState").Class("OReportController").Namespace("rptui").GlobalNamespace()
+ || dc.Function("isFormatCommandEnabled").Class("OReportController").Namespace("rptui").GlobalNamespace()
+ || dc.Function("resolveUnderlines").Struct("PageElement").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("visit").Class("WriterXmlOptimizer").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("optimizeTextElements").Class("WriterXmlOptimizer").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("drawGlyphs").Class("PDFIProcessor").Namespace("pdfi").GlobalNamespace()
+ || dc.Function("LTypeToDXFLineInfo").Class("DXF2GDIMetaFile").GlobalNamespace()
+ || dc.Function("DrawEntities").Class("DXF2GDIMetaFile").GlobalNamespace()
+ || dc.Function("ImplWriteActions").Class("PSWriter").GlobalNamespace()
+ || dc.Function("ImplWriteLineInfo").Class("PSWriter").GlobalNamespace()
+ || dc.Function("ImplInsert").Class("CGMBitmap").GlobalNamespace()
+ || dc.Function("GetNext").Class("CGMBitmap").GlobalNamespace()
+ || dc.Function("drawPolyLine").Class("X11SalGraphicsImpl").GlobalNamespace()
+ || dc.Function("testRefresh").Class("XDataPilotTable").Namespace("apitest").GlobalNamespace()
+ || dc.Function("testTitleManualLayoutXLSX").Class("Chart2ExportTest").GlobalNamespace()
+ || dc.Function("testPlotAreaManualLayoutXLSX").Class("Chart2ExportTest").GlobalNamespace()
+ || dc.Function("testLegendManualLayoutXLSX").Class("Chart2ExportTest").GlobalNamespace()
+ || dc.Function("SetScreenNumber").Class("AquaSalFrame").GlobalNamespace()
+ || dc.Function("Justify").Class("GenericSalLayout").GlobalNamespace()
+ || dc.Function("AdjustLayout").Class("MultiSalLayout").GlobalNamespace()
+ // vcl/headless/svpgdi.cxx, ba4a124b0c0c66fd275f5147d55eeec27ce78da9:
+ || dc.Function("drawAlphaBitmap").Class("SvpSalGraphics").GlobalNamespace()
+ || dc.Function("drawMask").Class("SvpSalGraphics").GlobalNamespace()
+ || dc.Function("renderSource").GlobalNamespace())
+ {
+ return true;
+ }
+// cout << "xxx " + function->getQualifiedNameAsString() << endl;
+ return false;
+}
+
+bool isZeroConstant(ASTContext& context, const Expr* expr)
+{
+ if (!expr->getType()->isFloatingType()) {
+ return false;
+ }
+ // prevent clang crash
+ if (!context.getLangOpts().CPlusPlus) {
+ return false;
+ }
+ APValue result;
+ if (!expr->isCXX11ConstantExpr(context, &result)) {
+ return false;
+ }
+ assert(result.isFloat());
+ return result.getFloat().isZero();
+}
+bool FpComparison::VisitBinaryOperator(const BinaryOperator* binaryOp)
+{
+ if (meState != EState::TraverseProcess || ignoreLocation(binaryOp)) {
+ return true;
+ }
+ if (binaryOp->getOpcode() != BO_EQ && binaryOp->getOpcode() != BO_NE) {
+ return true;
+ }
+ // comparison with zero is valid
+ if (isZeroConstant(compiler.getASTContext(), binaryOp->getLHS())
+ || isZeroConstant(compiler.getASTContext(), binaryOp->getRHS()))
+ {
+ return true;
+ }
+ QualType LHSStrippedType = binaryOp->getLHS()->IgnoreParenImpCasts()->getType();
+ QualType RHSStrippedType = binaryOp->getRHS()->IgnoreParenImpCasts()->getType();
+ if (LHSStrippedType->isFloatingType() && RHSStrippedType->isFloatingType()) {
+ report(
+ DiagnosticsEngine::Warning, "floating-point comparison",
+ binaryOp->getSourceRange().getBegin())
+ << binaryOp->getSourceRange();
+ }
+ return true;
+}
+
+
+loplugin::Plugin::Registration< FpComparison > X("fpcomparison", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/inlinefields.cxx b/compilerplugins/clang/store/inlinefields.cxx
new file mode 100644
index 0000000000..1573e8d521
--- /dev/null
+++ b/compilerplugins/clang/store/inlinefields.cxx
@@ -0,0 +1,251 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include "plugin.hxx"
+#include "compat.hxx"
+
+/**
+if a field is
+- a pointer
+- only assigned to in the constructor via a new expression
+- unconditionally deleted in the destructor
+then it can probably just be allocated inline in the parent object
+
+TODO check for cases where the pointer is passed by non-const reference
+
+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='inlinefields' check
+ $ ./compilerplugins/clang/inlinefields.py
+
+and then
+ $ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='inlinefieldsremove' $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 :-)
+
+*/
+
+namespace {
+
+struct MyFieldInfo
+{
+ std::string parentClass;
+ std::string fieldName;
+ std::string sourceLocation;
+};
+bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs)
+{
+ return std::tie(lhs.parentClass, lhs.fieldName)
+ < std::tie(rhs.parentClass, rhs.fieldName);
+}
+
+
+// try to limit the voluminous output a little
+static std::set<MyFieldInfo> excludedSet;
+static std::set<MyFieldInfo> definitionSet;
+static std::set<MyFieldInfo> deletedInDestructorSet;
+static std::set<MyFieldInfo> newedInConstructorSet;
+
+class InlineFields:
+ public RecursiveASTVisitor<InlineFields>, public loplugin::Plugin
+{
+public:
+ explicit InlineFields(loplugin::InstantiationData const & data):
+ Plugin(data) {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
+ // writing to the same logfile
+ std::string output;
+ for (const MyFieldInfo & s : definitionSet)
+ output += "definition:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.sourceLocation + "\n";
+ for (const MyFieldInfo & s : excludedSet)
+ output += "excluded:\t" + s.parentClass + "\t" + s.fieldName + "\n";
+ for (const MyFieldInfo & s : deletedInDestructorSet)
+ output += "deletedInDestructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
+ for (const MyFieldInfo & s : newedInConstructorSet)
+ output += "newedInConstructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
+ std::ofstream myfile;
+ myfile.open( WORKDIR "/loplugin.inlinefields.log", std::ios::app | std::ios::out);
+ myfile << output;
+ myfile.close();
+ }
+
+ bool shouldVisitTemplateInstantiations () const { return true; }
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool VisitFieldDecl( const FieldDecl* );
+ bool VisitCXXConstructorDecl( const CXXConstructorDecl* );
+ bool VisitCXXDeleteExpr( const CXXDeleteExpr* );
+ bool VisitBinaryOperator( const BinaryOperator* );
+private:
+ MyFieldInfo niceName(const FieldDecl*);
+ void checkTouched(const FieldDecl* fieldDecl, const Expr* memberExpr);
+};
+
+MyFieldInfo InlineFields::niceName(const FieldDecl* fieldDecl)
+{
+ MyFieldInfo aInfo;
+
+ const RecordDecl* recordDecl = fieldDecl->getParent();
+
+ if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
+ {
+ if (cxxRecordDecl->getTemplateInstantiationPattern())
+ cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
+ aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
+ }
+ else
+ aInfo.parentClass = recordDecl->getQualifiedNameAsString();
+
+ aInfo.fieldName = fieldDecl->getNameAsString();
+
+ SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( fieldDecl->getLocation() );
+ StringRef name = getFilenameOfLocation(expansionLoc);
+ aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
+ loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
+
+ return aInfo;
+}
+
+bool InlineFields::VisitFieldDecl( const FieldDecl* fieldDecl )
+{
+ fieldDecl = fieldDecl->getCanonicalDecl();
+ if (ignoreLocation( fieldDecl )) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
+ return true;
+ }
+ QualType type = fieldDecl->getType();
+ if (!type->isPointerType())
+ return true;
+ definitionSet.insert(niceName(fieldDecl));
+ return true;
+}
+
+bool InlineFields::VisitCXXConstructorDecl( const CXXConstructorDecl* decl )
+{
+ if( ignoreLocation( decl ) )
+ return true;
+ if (decl->isCopyOrMoveConstructor())
+ return true;
+ for(auto it = decl->init_begin(); it != decl->init_end(); ++it)
+ {
+ const CXXCtorInitializer* init = *it;
+ const FieldDecl* fieldDecl = init->getMember();
+ if( !fieldDecl || !fieldDecl->getType()->isPointerType() )
+ continue;
+ auto e = init->getInit();
+ if (auto parentListExpr = dyn_cast<ParenListExpr>(e))
+ e = parentListExpr->getExpr(0);
+ e = e->IgnoreParenImpCasts();
+ if( isa<CXXNewExpr>(e) )
+ newedInConstructorSet.insert(niceName(fieldDecl));
+ else if( isa<CXXNullPtrLiteralExpr>(e) || isa<GNUNullExpr>(e))
+ ; // ignore
+ else
+ excludedSet.insert(niceName(fieldDecl));
+ }
+ return true;
+}
+
+static bool isSameParent(const CXXMethodDecl* cxxMethodDecl, const FieldDecl* fieldDecl)
+{
+ return cxxMethodDecl->getParent() == dyn_cast<CXXRecordDecl>(fieldDecl->getParent());
+}
+
+bool InlineFields::VisitBinaryOperator(const BinaryOperator * binaryOp)
+{
+ if (binaryOp->getOpcode() != BO_Assign) {
+ return true;
+ }
+ if( ignoreLocation( binaryOp ) )
+ return true;
+ auto memberExpr = dyn_cast<MemberExpr>(binaryOp->getLHS());
+ if (!memberExpr)
+ return true;
+ auto fieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl());
+ if (!fieldDecl || !fieldDecl->getType()->isPointerType()) {
+ return true;
+ }
+ const FunctionDecl* parentFunction = getParentFunctionDecl(binaryOp);
+ if (!parentFunction) {
+ return true;
+ }
+ // if the field is being assigned from outside its own constructor or destructor, exclude
+ auto constructorDecl = dyn_cast<CXXConstructorDecl>(parentFunction);
+ if (constructorDecl && isSameParent(constructorDecl, fieldDecl)) {
+ if( isa<CXXNewExpr>(binaryOp->getRHS()) )
+ newedInConstructorSet.insert(niceName(fieldDecl));
+ else {
+ excludedSet.insert(niceName(fieldDecl));
+ std::cout << "assign in constructor:" << std::endl;
+ binaryOp->getRHS()->dump();
+ }
+ return true;
+ }
+ auto destructorDecl = dyn_cast<CXXDestructorDecl>(parentFunction);
+ if (destructorDecl && isSameParent(destructorDecl, fieldDecl)) {
+ auto e = binaryOp->getRHS()->IgnoreParenImpCasts();
+ if( !isa<CXXNullPtrLiteralExpr>(e) && !isa<GNUNullExpr>(e)) {
+ excludedSet.insert(niceName(fieldDecl));
+ std::cout << "assign in destructor:" << std::endl;
+ e->dump();
+ }
+ return true;
+ }
+ excludedSet.insert(niceName(fieldDecl));
+ return true;
+}
+
+bool InlineFields::VisitCXXDeleteExpr(const CXXDeleteExpr * deleteExpr)
+{
+ if( ignoreLocation( deleteExpr ) )
+ return true;
+ auto memberExpr = dyn_cast<MemberExpr>(deleteExpr->getArgument()->IgnoreParenImpCasts());
+ if (!memberExpr)
+ return true;
+ auto fieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl());
+ if (!fieldDecl || !fieldDecl->getType()->isPointerType()) {
+ return true;
+ }
+ // TODO for some reason, this part is not working properly, it doesn't find the parent
+ // function for delete statements properly
+ const FunctionDecl* parentFunction = getParentFunctionDecl(deleteExpr);
+ if (!parentFunction) {
+ return true;
+ }
+ auto destructorDecl = dyn_cast<CXXDestructorDecl>(parentFunction);
+ if (destructorDecl && isSameParent(destructorDecl, fieldDecl)) {
+ deletedInDestructorSet.insert(niceName(fieldDecl));
+ return true;
+ }
+ excludedSet.insert(niceName(fieldDecl));
+ return true;
+}
+
+loplugin::Plugin::Registration< InlineFields > X("inlinefields", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/inlinefields.py b/compilerplugins/clang/store/inlinefields.py
new file mode 100755
index 0000000000..e569431d37
--- /dev/null
+++ b/compilerplugins/clang/store/inlinefields.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+
+import re
+import io
+
+definitionToSourceLocationMap = dict() # dict of tuple(parentClass, fieldName) to sourceLocation
+definitionSet = set()
+excludedSet = set()
+deletedInDestructorSet = set()
+newedInConstructorSet = set();
+
+# clang does not always use exactly the same numbers in the type-parameter vars it generates
+# so I need to substitute them to ensure we can match correctly.
+normalizeTypeParamsRegex = re.compile(r"type-parameter-\d+-\d+")
+def normalizeTypeParams( line ):
+ return normalizeTypeParamsRegex.sub("type-parameter-?-?", line)
+
+# reading as binary (since we known it is pure ascii) is much faster than reading as unicode
+with io.open("workdir/loplugin.inlinefields.log", "rb", buffering=1024*1024) as txt:
+ for line in txt:
+ tokens = line.strip().split("\t")
+ if tokens[0] == "definition:":
+ parentClass = normalizeTypeParams(tokens[1])
+ fieldName = normalizeTypeParams(tokens[2])
+ sourceLocation = tokens[3]
+ fieldInfo = (parentClass, fieldName)
+ definitionSet.add(fieldInfo)
+ definitionToSourceLocationMap[fieldInfo] = sourceLocation
+ elif tokens[0] == "excluded:":
+ parentClass = normalizeTypeParams(tokens[1])
+ fieldName = normalizeTypeParams(tokens[2])
+ fieldInfo = (parentClass, fieldName)
+ excludedSet.add(fieldInfo)
+ elif tokens[0] == "deletedInDestructor:":
+ parentClass = normalizeTypeParams(tokens[1])
+ fieldName = normalizeTypeParams(tokens[2])
+ fieldInfo = (parentClass, fieldName)
+ deletedInDestructorSet.add(fieldInfo)
+ elif tokens[0] == "newedInConstructor:":
+ parentClass = normalizeTypeParams(tokens[1])
+ fieldName = normalizeTypeParams(tokens[2])
+ fieldInfo = (parentClass, fieldName)
+ newedInConstructorSet.add(fieldInfo)
+ else:
+ print( "unknown line: " + line)
+
+tmp1list = list()
+for d in definitionSet:
+# TODO see comment in InlineFields::VisitCXXDeleteExpr
+# if d in excludedSet or d not in deletedInDestructorSet or d not in newedInConstructorSet:
+ if d in excludedSet or d not in newedInConstructorSet:
+ continue
+ srcLoc = definitionToSourceLocationMap[d];
+ tmp1list.append((d[0] + " " + d[1], srcLoc))
+
+# sort results by filename:lineno
+def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
+ return [int(text) if text.isdigit() else text.lower()
+ for text in re.split(_nsre, s)]
+# sort by both the source-line and the datatype, so the output file ordering is stable
+# when we have multiple items on the same source line
+def v_sort_key(v):
+ return natural_sort_key(v[1]) + [v[0]]
+tmp1list.sort(key=lambda v: v_sort_key(v))
+
+# print out the results
+with open("loplugin.inlinefields.report", "wt") as f:
+ for v in tmp1list:
+ f.write(v[1] + "\n")
+ f.write(" " + v[0] + "\n")
+
+
diff --git a/compilerplugins/clang/store/inlinesimplememberfunctions.cxx b/compilerplugins/clang/store/inlinesimplememberfunctions.cxx
new file mode 100644
index 0000000000..760094b5a0
--- /dev/null
+++ b/compilerplugins/clang/store/inlinesimplememberfunctions.cxx
@@ -0,0 +1,294 @@
+/* -*- 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 <string>
+
+#include "plugin.hxx"
+
+// Methods that purely return a local field should be declared in the header and be declared inline.
+// So that the compiler can elide the function call and turn it into a simple fixed-offset-load instruction.
+
+namespace {
+
+class InlineSimpleMemberFunctions:
+ public loplugin::FilteringRewritePlugin<InlineSimpleMemberFunctions>
+{
+public:
+ explicit InlineSimpleMemberFunctions(loplugin::InstantiationData const & data): FilteringRewritePlugin(data) {}
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXMethodDecl(const CXXMethodDecl * decl);
+private:
+ bool rewrite(const CXXMethodDecl * functionDecl);
+};
+
+static bool oneAndOnlyOne(clang::Stmt::const_child_range range) {
+ if (range.begin() == range.end()) {
+ return false;
+ }
+ if (++range.begin() != range.end()) {
+ return false;
+ }
+ return true;
+}
+
+
+bool InlineSimpleMemberFunctions::VisitCXXMethodDecl(const CXXMethodDecl * functionDecl) {
+ if (ignoreLocation(functionDecl)) {
+ return true;
+ }
+ // no point in doing virtual methods, the compiler always has to generate a vtable entry and a method
+ if (functionDecl->isVirtual()) {
+ return true;
+ }
+ if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) {
+ return true;
+ }
+ if (!functionDecl->isInstance()) {
+ return true;
+ }
+ if (!functionDecl->isOutOfLine()) {
+ return true;
+ }
+ if( !functionDecl->hasBody()) {
+ return true;
+ }
+ if( functionDecl->isInlineSpecified()) {
+ return true;
+ }
+ if( functionDecl->getCanonicalDecl()->isInlineSpecified()) {
+ return true;
+ }
+ if( functionDecl->getNameAsString().find("Impl") != std::string::npos) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl)) {
+ return true;
+ }
+ // ignore stuff like:
+ // template<class E> E * Sequence<E>::begin() { return getArray(); }
+ if( functionDecl->getParent()->getDescribedClassTemplate() != nullptr ) {
+ return true;
+ }
+
+ /*
+ The chain here looks like
+ CompoundStmt
+ ReturnStmt
+ other stuff
+ CXXThisExpr
+ */
+
+ const CompoundStmt* compoundStmt = dyn_cast< CompoundStmt >( functionDecl->getBody() );
+ if (compoundStmt == nullptr) {
+ return true;
+ }
+ if (compoundStmt->body_begin() == compoundStmt->body_end()) {
+ return true;
+ }
+
+
+ const Stmt* childStmt = *compoundStmt->child_begin();
+
+ if (dyn_cast<ReturnStmt>( childStmt ) == nullptr) {
+ return true;
+ }
+ if (!oneAndOnlyOne(childStmt->children())) {
+ return true;
+ }
+
+
+ /* Don't warn if we see a method definition like
+ X X::a() {
+ return *this;
+ }
+ which translates to:
+ CompoundStmt
+ ReturnStmt
+ ImplicitCastExpr
+ UnaryOperator
+ CXXThisExpr
+ or:
+ CompoundStmt
+ ReturnStmt
+ UnaryOperator
+ CXXThisExpr
+ */
+ childStmt = *childStmt->child_begin();
+ if (dyn_cast<ImplicitCastExpr>( childStmt ) != nullptr
+ && oneAndOnlyOne( childStmt->children() ))
+ {
+ const Stmt* childStmt2 = *childStmt->child_begin();
+ if (dyn_cast<UnaryOperator>( childStmt2 ) != nullptr
+ && oneAndOnlyOne(childStmt2->children()))
+ {
+ childStmt2 = *childStmt2->child_begin();
+ if (dyn_cast<CXXThisExpr>( childStmt2 ) != nullptr
+ && childStmt2->children().begin() == childStmt2->children().end())
+ {
+ return true;
+ }
+ }
+ }
+ if (dyn_cast<UnaryOperator>( childStmt ) != nullptr
+ && oneAndOnlyOne( childStmt->children() ))
+ {
+ const Stmt* childStmt2 = *childStmt->child_begin();
+ if (dyn_cast<CXXThisExpr>( childStmt2 ) != nullptr
+ && childStmt2->children().begin() == childStmt2->children().end())
+ {
+ return true;
+ }
+ }
+
+ /* look for a chains like:
+ CompoundStmt
+ ReturnStmt
+ stuff
+ CXXThisExpr
+ */
+ childStmt = *(*compoundStmt->child_begin())->child_begin();
+ while (1) {
+ if (dyn_cast<CallExpr>( childStmt ) != nullptr)
+ return true;
+ if (dyn_cast<CXXNewExpr>( childStmt ) != nullptr)
+ return true;
+ if (dyn_cast<CXXConstructExpr>( childStmt ) != nullptr)
+ return true;
+ if (dyn_cast<ConditionalOperator>( childStmt ) != nullptr)
+ return true;
+ if (dyn_cast<BinaryOperator>( childStmt ) != nullptr)
+ return true;
+ // exclude methods that return fields on incomplete types .e.g the pImpl pattern
+ const MemberExpr* memberExpr = dyn_cast<MemberExpr>( childStmt );
+ if (memberExpr != nullptr && memberExpr->getMemberDecl()) {
+ const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl());
+ if (fieldDecl != nullptr)
+ {
+ // yes, a little bit of a hack. However, it is quite hard to determine if the method
+ // in question is accessing a field via a pImpl pattern.
+ if (fieldDecl->getType()->isIncompleteType())
+ return true;
+ if (fieldDecl->getNameAsString().find("Impl") != std::string::npos)
+ return true;
+ if (fieldDecl->getNameAsString().find("pImp") != std::string::npos)
+ return true;
+ // somewhere in VCL
+ if (fieldDecl->getNameAsString().find("mpGlobalSyncData") != std::string::npos)
+ return true;
+ std::string s = fieldDecl->getType().getAsString();
+ if (s.find("Impl") != std::string::npos || s.find("pImp") != std::string::npos || s.find("Internal") != std::string::npos)
+ return true;
+ }
+ }
+ if (dyn_cast<CXXThisExpr>( childStmt ) != nullptr) {
+ if (!rewrite(functionDecl))
+ {
+ report(
+ DiagnosticsEngine::Warning,
+ "inlinesimpleaccessmethods",
+ functionDecl->getSourceRange().getBegin())
+ << functionDecl->getSourceRange();
+ // display the location of the class member declaration so I don't have to search for it by hand
+ report(
+ DiagnosticsEngine::Note,
+ "inlinesimpleaccessmethods",
+ functionDecl->getCanonicalDecl()->getSourceRange().getBegin())
+ << functionDecl->getCanonicalDecl()->getSourceRange();
+ }
+ return true;
+ }
+ if ( childStmt->children().begin() == childStmt->children().end() )
+ return true;
+ childStmt = *childStmt->child_begin();
+ }
+ return true;
+}
+
+static std::string ReplaceString(std::string subject, const std::string& search,
+ const std::string& replace) {
+ size_t pos = 0;
+ while ((pos = subject.find(search, pos)) != std::string::npos) {
+ subject.replace(pos, search.length(), replace);
+ pos += replace.length();
+ }
+ return subject;
+}
+
+bool InlineSimpleMemberFunctions::rewrite(const CXXMethodDecl * functionDecl) {
+ if (rewriter == nullptr) {
+ return false;
+ }
+ // Only rewrite declarations in include files if a
+ // definition is also seen, to avoid compilation of a
+ // definition (in a main file only processed later) to fail
+ // with a "mismatch" error before the rewriter had a chance
+ // to act upon the definition.
+ if (!compiler.getSourceManager().isInMainFile(
+ compiler.getSourceManager().getSpellingLoc(
+ functionDecl->getNameInfo().getLoc())))
+ {
+ return false;
+ }
+
+ const char *p1, *p2;
+
+ // get the function body contents
+ p1 = compiler.getSourceManager().getCharacterData( functionDecl->getBody()->getBeginLoc() );
+ p2 = compiler.getSourceManager().getCharacterData( functionDecl->getBody()->getEndLoc() );
+ std::string s1( p1, p2 - p1 + 1);
+
+ /* we can't safely move around stuff containing comments, we mess up the resulting code */
+ if ( s1.find("/*") != std::string::npos || s1.find("//") != std::string::npos ) {
+ return false;
+ }
+
+ // strip linefeeds and any double-spaces, so we have a max of one space between tokens
+ s1 = ReplaceString(s1, "\r", "");
+ s1 = ReplaceString(s1, "\n", "");
+ s1 = ReplaceString(s1, "\t", " ");
+ s1 = ReplaceString(s1, " ", " ");
+ s1 = ReplaceString(s1, " ", " ");
+ s1 = ReplaceString(s1, " ", " ");
+ s1 = " " + s1;
+
+ // scan from the end of the function's body through the trailing whitespace, so we can do a nice clean remove
+// commented out because for some reason it will sometimes chomp an extra token
+// SourceLocation endOfRemoveLoc = functionDecl->getBody()->getLocEnd();
+// for (;;) {
+// endOfRemoveLoc = endOfRemoveLoc.getLocWithOffset(1);
+// p1 = compiler.getSourceManager().getCharacterData( endOfRemoveLoc );
+// if (*p1 != ' ' && *p1 != '\r' && *p1 != '\n' && *p1 != '\t')
+// break;
+// }
+
+ // remove the function's out of line body and declaration
+ RewriteOptions opts;
+ opts.RemoveLineIfEmpty = true;
+ if (!removeText(SourceRange(functionDecl->getBeginLoc(), functionDecl->getBody()->getEndLoc()), opts)) {
+ return false;
+ }
+
+ // scan forward until we find the semicolon
+ const FunctionDecl * canonicalDecl = functionDecl->getCanonicalDecl();
+ p1 = compiler.getSourceManager().getCharacterData( canonicalDecl->getEndLoc() );
+ p2 = ++p1;
+ while (*p2 != 0 && *p2 != ';') p2++;
+
+ // insert the function body into the inline function definition (i.e. the one inside the class definition)
+ return replaceText(canonicalDecl->getEndLoc().getLocWithOffset(p2 - p1 + 1), 1, s1);
+}
+
+loplugin::Plugin::Registration< InlineSimpleMemberFunctions > X("inlinesimplememberfunctions");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/lclstaticfix.cxx b/compilerplugins/clang/store/lclstaticfix.cxx
new file mode 100644
index 0000000000..01e4171fc4
--- /dev/null
+++ b/compilerplugins/clang/store/lclstaticfix.cxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "lclstaticfix.hxx"
+
+/*
+This is a rewriter.
+
+Check all lcl_ functions and prepend static if needed.
+*/
+
+namespace loplugin
+{
+
+LclStaticFix::LclStaticFix( CompilerInstance& compiler, Rewriter& rewriter )
+ : FilteringRewritePlugin( compiler, rewriter )
+ {
+ }
+
+void LclStaticFix::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool LclStaticFix::VisitFunctionDecl( const FunctionDecl* declaration )
+ {
+ if( ignoreLocation( declaration ))
+ return true;
+ if( declaration->isCXXClassMember())
+ return true;
+ if( declaration->getStorageClass() == SC_Static )
+ return true;
+ string name = declaration->getQualifiedNameAsString();
+ if( name.find( "::" ) != string::npos )
+ return true;
+ if( name.compare( 0, 4, "lcl_" ) != 0 )
+ return true;
+ insertText( declaration->getLocStart(), "static " );
+ return true;
+ }
+
+static Plugin::Registration< LclStaticFix > X( "lclstaticfix" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/lclstaticfix.hxx b/compilerplugins/clang/store/lclstaticfix.hxx
new file mode 100644
index 0000000000..16623edcbd
--- /dev/null
+++ b/compilerplugins/clang/store/lclstaticfix.hxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+class LclStaticFix
+ : public loplugin::FilteringRewritePlugin< LclStaticFix >
+ {
+ public:
+ explicit LclStaticFix( CompilerInstance& compiler, Rewriter& rewriter );
+ virtual void run() override;
+ bool VisitFunctionDecl( const FunctionDecl* declaration );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/manualrefcount.cxx b/compilerplugins/clang/store/manualrefcount.cxx
new file mode 100644
index 0000000000..c8085904fe
--- /dev/null
+++ b/compilerplugins/clang/store/manualrefcount.cxx
@@ -0,0 +1,323 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+
+#include <clang/AST/CXXInheritance.h>
+#include "compat.hxx"
+#include "plugin.hxx"
+#include "check.hxx"
+
+/**
+ Look for calls to the ref-counting methods acquire()/release(), which should only be called by classes like rtl::Reference.
+*/
+
+namespace {
+
+class ManualRefCount:
+ public RecursiveASTVisitor<ManualRefCount>, public loplugin::Plugin
+{
+public:
+ explicit ManualRefCount(InstantiationData const & data): Plugin(data) {}
+
+ virtual void run() override
+ {
+ StringRef fn( compiler.getSourceManager().getFileEntryForID(
+ compiler.getSourceManager().getMainFileID())->getName() );
+
+ // old code, no point in updating
+ if (loplugin::isSamePathname(fn, SRCDIR "/store/source/store.cxx"))
+ return;
+
+// TODO -----------------------------
+ if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/registry.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/regimpl.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/reflread.cxx"))
+ return;
+ // TODO MenuAttributes::CreateAttribute
+ if (loplugin::isSamePathname(fn, SRCDIR "/framework/source/fwe/xml/menuconfiguration.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/app/apphdl.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/core/dataaccess/ModelImpl.cxx"))
+ return;
+ // need a better replacement for vcl::EventPoster
+ if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/misc/acceleratorexecute.cxx"))
+ return;
+ // PostUserEvent stuff
+ if (loplugin::isSamePathname(fn, SRCDIR "/toolkit/source/awt/vclxwindow.cxx"))
+ return;
+ // playing games with pointers passed into combobox entries
+ if (loplugin::isSamePathname(fn, SRCDIR "/cui/source/customize/cfgutil.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/cui/source/customize/cfg.cxx"))
+ return;
+// END TODO -----------------------------
+
+ // can't fix these without breaking stable ABI
+ if (fn.startswith(SRCDIR "/sal/"))
+ return;
+ if (fn.startswith(SRCDIR "/salhelper/"))
+ return;
+ if (fn.startswith(SRCDIR "/cppu/"))
+ return;
+ if (fn.startswith(SRCDIR "/cppuhelper/"))
+ return;
+ if (fn.startswith(SRCDIR "/bridges/"))
+ return;
+
+ // lots of magic here
+ if (fn.startswith(SRCDIR "/stoc/"))
+ return;
+ if (fn.startswith(SRCDIR "/testtools/"))
+ return;
+
+ // mutex games
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/app/scheduler.cxx"))
+ return;
+ // opengl games
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/app/svdata.cxx"))
+ return;
+
+ // passing the pointer through PostUserEvent
+ if (loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/gstreamer/gstplayer.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/form/fmscriptingenv.cxx"))
+ return;
+
+ // thread games
+ if (loplugin::isSamePathname(fn, SRCDIR "/io/source/stm/opump.cxx"))
+ return;
+
+ // ??? no idea what this code is up to
+ if (loplugin::isSamePathname(fn, SRCDIR "/extensions/source/scanner/scanunx.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/stoc/source/invocation_adapterfactory/iafactory.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/fpicker/source/office/asyncfilepicker.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/FormComponent.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/file/bc.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/file/filprp.cxx"))
+ return;
+ // calling release() ?
+ if (loplugin::isSamePathname(fn, SRCDIR "/toolkit/source/helper/accessibilityclient.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/misc/svtaccessiblefactory.cxx"))
+ return;
+
+ // implementing css::uno::XInterface
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/animations/motionpathtag.cxx"))
+ return;
+ // UNO factory methods
+ if (fn.startswith(SRCDIR "/comphelper/"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/convdiclist.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/dlistimp.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/gciterator.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/lngsvcmgr.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/lngopt.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/generic/gdi/gcach_xpeer.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dbwizsetup.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dbwizsetup.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/lingucomponent/source/hyphenator/hyphen/hyphenimp.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/lingucomponent/source/spellcheck/spell/sspellimp.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/lingucomponent/source/thesaurus/libnth/nthesimp.cxx"))
+ return;
+
+
+ // some kind of complicated listener nonsense
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/framework/tools/FrameworkHelper.cxx"))
+ return;
+ // more listener nonsense
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/uno/unomailmerge.cxx"))
+ return;
+ // playing games with it's listener list
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/cellsuno.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/chart2uno.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/dapiuno.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/datauno.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/linkuno.cxx"))
+ return;
+ // PostUserEvent
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/vba/vbaeventshelper.cxx"))
+ return;
+ // thread holding itself
+ if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/EventThread.cxx"))
+ return;
+
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool shouldVisitTemplateInstantiations () const { return true; }
+
+ bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *);
+ bool TraverseCXXRecordDecl(CXXRecordDecl *);
+ bool TraverseCXXMethodDecl(CXXMethodDecl *);
+ bool TraverseFunctionDecl(FunctionDecl *);
+ bool TraverseCXXConstructorDecl(CXXConstructorDecl *);
+ bool TraverseCXXDestructorDecl(CXXDestructorDecl *);
+ bool TraverseCXXConversionDecl(CXXConversionDecl *);
+ bool TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl *);
+ bool TraverseLinkageSpecDecl(LinkageSpecDecl *);
+private:
+ bool ignoreCallerClass(CXXRecordDecl*);
+};
+
+bool ManualRefCount::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
+{
+ if (ignoreCallerClass(cxxMethodDecl->getParent()))
+ return true;
+ // disambiguating forwarding methods for XInterface subclasses
+ if (cxxMethodDecl->getIdentifier() && (cxxMethodDecl->getName() == "acquire" || cxxMethodDecl->getName() == "release"))
+ return true;
+ return RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
+}
+
+bool ManualRefCount::TraverseFunctionDecl(FunctionDecl* functionDecl)
+{
+ auto tc = loplugin::DeclCheck(functionDecl);
+ if (tc.Function("make_shared_from_UNO").Namespace("comphelper").GlobalNamespace())
+ return true;
+ return RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
+}
+
+bool ManualRefCount::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxMethodDecl)
+{
+ if (ignoreCallerClass(cxxMethodDecl->getParent()))
+ return true;
+ return RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
+}
+
+bool ManualRefCount::TraverseCXXDestructorDecl(CXXDestructorDecl*)
+{
+ // just ignore destructors, tons of places like to call acquire() on themselves in their destructor
+ // supposedly to prevent recursively calling the destructor
+ return true;
+}
+bool ManualRefCount::TraverseCXXConversionDecl(CXXConversionDecl* cxxMethodDecl)
+{
+ if (ignoreCallerClass(cxxMethodDecl->getParent()))
+ return true;
+ return RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
+}
+bool ManualRefCount::TraverseCXXRecordDecl(CXXRecordDecl* cxxRecordDecl)
+{
+ if (ignoreCallerClass(cxxRecordDecl))
+ return true;
+ return RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl);
+}
+
+bool ManualRefCount::TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl* templateDecl)
+{
+ if (ignoreCallerClass(templateDecl))
+ return true;
+ return RecursiveASTVisitor::TraverseClassTemplateSpecializationDecl(templateDecl);
+}
+
+bool ManualRefCount::TraverseLinkageSpecDecl(LinkageSpecDecl *)
+{
+ // ignore methods inside "extern ""C""" blocks, these are normally UNO constructors, which
+ // are required to raise the reference count before returning
+ return true;
+}
+
+bool ManualRefCount::ignoreCallerClass(CXXRecordDecl* cxxRecordDecl)
+{
+ auto tc = loplugin::TypeCheck(cxxRecordDecl);
+ return
+ tc.Class("Reference").Namespace("rtl").GlobalNamespace()
+ || tc.Class("cow_wrapper").Namespace("o3tl").GlobalNamespace()
+ || tc.Class("Reference").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()
+ || tc.Class("ShareGuard").Namespace("framework").GlobalNamespace()
+ || tc.Class("ControlModelLock").Namespace("frm").GlobalNamespace()
+ || tc.Struct("ReleaseFunc").Namespace("detail").Namespace("comphelper").GlobalNamespace()
+ // TODO no idea what this is up to
+ || tc.Class("SfxModelSubComponent").GlobalNamespace()
+ || tc.Class("OSubComponent").Namespace("mysqlc").Namespace("connectivity").GlobalNamespace()
+ || tc.Class("OSubComponent").Namespace("connectivity").GlobalNamespace()
+ // TODO do we really need this?
+ || tc.Class("ShareableMutex").Namespace("framework").GlobalNamespace()
+ || tc.Class("ObservableThread").GlobalNamespace()
+ ;
+}
+
+bool ManualRefCount::VisitCXXMemberCallExpr(const CXXMemberCallExpr* cxxMemberCallExpr)
+{
+ if (ignoreLocation(cxxMemberCallExpr))
+ return true;
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxMemberCallExpr->getLocStart())))
+ return true;
+
+ // first, use some heuristics to find the right kind of acquire()/release() calls
+ CXXMethodDecl const * calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
+ if (!calleeMethodDecl || !calleeMethodDecl->getIdentifier())
+ return true;
+ if (calleeMethodDecl->getName() != "acquire" && calleeMethodDecl->getName() != "release")
+ return true;
+ if (calleeMethodDecl->getNumParams() != 0)
+ return true;
+ // std::unique_ptr::release() and similar methods
+ if (calleeMethodDecl->getName() == "release" && loplugin::TypeCheck(calleeMethodDecl->getReturnType()).Pointer())
+ return true;
+
+ // these are OK
+ auto calleeRecordTC = loplugin::TypeCheck(calleeMethodDecl->getParent());
+ if (calleeRecordTC.Struct("ResourceHolder").Namespace("store").GlobalNamespace())
+ return true;
+ if (calleeRecordTC.Class("Module").Namespace("osl").GlobalNamespace())
+ return true;
+ if (calleeRecordTC.Class("Mutex").Namespace("osl").GlobalNamespace())
+ return true;
+ if (calleeRecordTC.Class("multi_type_vector").Namespace("mdds").GlobalNamespace())
+ return true;
+
+// while (calleeMethodDecl->size_overridden_methods() > 0)
+// calleeMethodDecl = *calleeMethodDecl->begin_overridden_methods();
+// auto tc2 = loplugin::TypeCheck(calleeMethodDecl->getParent());
+// if (tc2.Class("XInterface").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
+// return true;
+
+std::cout << calleeMethodDecl->getParent()->getQualifiedNameAsString() << std::endl;
+ report(
+ DiagnosticsEngine::Warning, "call to acquire/release",
+ cxxMemberCallExpr->getLocStart())
+ << cxxMemberCallExpr->getSourceRange();
+ return true;
+}
+
+
+loplugin::Plugin::Registration< ManualRefCount > X("manualrefcount", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/memoryvar.cxx b/compilerplugins/clang/store/memoryvar.cxx
new file mode 100644
index 0000000000..14c328ba40
--- /dev/null
+++ b/compilerplugins/clang/store/memoryvar.cxx
@@ -0,0 +1,238 @@
+/* -*- 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 <memory>
+#include <string>
+#include <iostream>
+#include <map>
+#include <set>
+
+#include "config_clang.h"
+#include "plugin.hxx"
+#include "clang/AST/CXXInheritance.h"
+
+// Check for local variables that we are calling delete on
+
+namespace
+{
+
+class MemoryVar:
+ public loplugin::FilteringPlugin<MemoryVar>
+{
+public:
+ explicit MemoryVar(loplugin::InstantiationData const & data): FilteringPlugin(data), mbChecking(false) {}
+
+ virtual void run() override {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool TraverseFunctionDecl(FunctionDecl*);
+ bool VisitCXXDeleteExpr(const CXXDeleteExpr*);
+ bool VisitCXXNewExpr(const CXXNewExpr* );
+ bool VisitBinaryOperator(const BinaryOperator*);
+ bool VisitReturnStmt(const ReturnStmt*);
+
+private:
+ bool mbChecking;
+ std::set<SourceLocation> maVarUsesSet;
+ std::set<SourceLocation> maVarNewSet;
+ std::set<SourceLocation> maVarIgnoreSet;
+ std::map<SourceLocation,SourceRange> maVarDeclSourceRangeMap;
+ std::map<SourceLocation,SourceRange> maVarDeleteSourceRangeMap;
+ StringRef getFilename(SourceLocation loc);
+};
+
+StringRef MemoryVar::getFilename(SourceLocation loc)
+{
+ SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(loc);
+ StringRef name { getFilenameOfLocation(spellingLocation) };
+ return name;
+}
+
+bool MemoryVar::TraverseFunctionDecl(FunctionDecl * decl)
+{
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (!decl->hasBody() || !decl->isThisDeclarationADefinition()) {
+ return true;
+ }
+
+ maVarUsesSet.clear();
+ maVarNewSet.clear();
+ maVarIgnoreSet.clear();
+ maVarDeclSourceRangeMap.clear();
+ maVarDeleteSourceRangeMap.clear();
+
+ assert(!mbChecking);
+ mbChecking = true;
+ TraverseStmt(decl->getBody());
+ mbChecking = false;
+
+ for (const auto& varLoc : maVarUsesSet)
+ {
+ // checking the location of the var instead of the function because for some reason
+ // I'm not getting accurate results from clang right now
+ StringRef aFileName = getFilename(varLoc);
+ // TODO these files are doing some weird stuff I don't know how to ignore yet
+ if (loplugin::hasPathnamePrefix(aFileName, SRCDIR "/vcl/source/filter/")) {
+ return true;
+ }
+ if (loplugin::isSamePathname(aFileName, SRCDIR "/sw/source/core/layout/frmtool.cxx")) {
+ return true;
+ }
+
+
+ if (maVarNewSet.find(varLoc) == maVarNewSet.end())
+ continue;
+ if (maVarIgnoreSet.find(varLoc) != maVarIgnoreSet.end())
+ continue;
+
+ report(DiagnosticsEngine::Warning,
+ "calling new and delete on a local var, rather use std::unique_ptr",
+ varLoc)
+ << maVarDeclSourceRangeMap[varLoc];
+ report(DiagnosticsEngine::Note,
+ "delete called here",
+ maVarDeleteSourceRangeMap[varLoc].getBegin())
+ << maVarDeleteSourceRangeMap[varLoc];
+ }
+ return true;
+}
+
+bool MemoryVar::VisitCXXDeleteExpr(const CXXDeleteExpr *deleteExpr)
+{
+ if (!mbChecking)
+ return true;
+ if (ignoreLocation(deleteExpr)) {
+ return true;
+ }
+ const Expr* argumentExpr = deleteExpr->getArgument();
+ if (isa<CastExpr>(argumentExpr)) {
+ argumentExpr = dyn_cast<CastExpr>(argumentExpr)->getSubExpr();
+ }
+ const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(argumentExpr);
+ if (!declRefExpr)
+ return true;
+ const Decl* decl = declRefExpr->getDecl();
+ if (!isa<VarDecl>(decl) || isa<ParmVarDecl>(decl)) {
+ return true;
+ }
+ const VarDecl * varDecl = dyn_cast<VarDecl>(decl)->getCanonicalDecl();
+ if (varDecl->hasGlobalStorage()) {
+ return true;
+ }
+
+ SourceLocation loc = varDecl->getLocation();
+
+ if (maVarUsesSet.insert(loc).second) {
+ maVarDeclSourceRangeMap[loc] = varDecl->getSourceRange();
+ maVarDeleteSourceRangeMap[loc] = declRefExpr->getSourceRange();
+ }
+ return true;
+}
+
+bool MemoryVar::VisitCXXNewExpr(const CXXNewExpr *newExpr)
+{
+ if (!mbChecking)
+ return true;
+ if (ignoreLocation(newExpr)) {
+ return true;
+ }
+ const Stmt* stmt = getParentStmt(newExpr);
+
+ const DeclStmt* declStmt = dyn_cast<DeclStmt>(stmt);
+ if (declStmt) {
+ const VarDecl* varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
+ if (varDecl) {
+ varDecl = varDecl->getCanonicalDecl();
+ SourceLocation loc = varDecl->getLocation();
+ maVarNewSet.insert(loc);
+ }
+ return true;
+ }
+
+ const BinaryOperator* binaryOp = dyn_cast<BinaryOperator>(stmt);
+ if (binaryOp && binaryOp->getOpcode() == BO_Assign) {
+ const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(binaryOp->getLHS());
+ if (declRefExpr) {
+ const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
+ if (varDecl) {
+ varDecl = varDecl->getCanonicalDecl();
+ SourceLocation loc = varDecl->getLocation();
+ maVarNewSet.insert(loc);
+ }
+ }
+ }
+ return true;
+}
+
+// Ignore cases where the variable in question is assigned to another variable
+bool MemoryVar::VisitBinaryOperator(const BinaryOperator *binaryOp)
+{
+ if (!mbChecking)
+ return true;
+ if (ignoreLocation(binaryOp)) {
+ return true;
+ }
+ if (binaryOp->getOpcode() != BO_Assign) {
+ return true;
+ }
+ const Expr* expr = binaryOp->getRHS();
+ // unwrap casts
+ while (isa<CastExpr>(expr)) {
+ expr = dyn_cast<CastExpr>(expr)->getSubExpr();
+ }
+ const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(expr);
+ if (!declRefExpr) {
+ return true;
+ }
+ const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
+ if (!varDecl) {
+ return true;
+ }
+ varDecl = varDecl->getCanonicalDecl();
+ maVarIgnoreSet.insert(varDecl->getLocation());
+ return true;
+}
+
+// Ignore cases where the variable in question is returned from a function
+bool MemoryVar::VisitReturnStmt(const ReturnStmt *returnStmt)
+{
+ if (!mbChecking)
+ return true;
+ if (ignoreLocation(returnStmt)) {
+ return true;
+ }
+ const Expr* expr = returnStmt->getRetValue();
+ if (!expr) {
+ return true;
+ }
+ // unwrap casts
+ while (isa<CastExpr>(expr)) {
+ expr = dyn_cast<CastExpr>(expr)->getSubExpr();
+ }
+ const DeclRefExpr* declRefExpr = dyn_cast<DeclRefExpr>(expr);
+ if (!declRefExpr) {
+ return true;
+ }
+ const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
+ if (!varDecl) {
+ return true;
+ }
+ varDecl = varDecl->getCanonicalDecl();
+ maVarIgnoreSet.insert(varDecl->getLocation());
+ return true;
+}
+
+loplugin::Plugin::Registration< MemoryVar > X("memoryvar", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/namespaceindentation.cxx b/compilerplugins/clang/store/namespaceindentation.cxx
new file mode 100644
index 0000000000..1398efc86a
--- /dev/null
+++ b/compilerplugins/clang/store/namespaceindentation.cxx
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+#ifndef LO_CLANG_SHARED_PLUGINS
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <locale>
+#include <fstream>
+#include <set>
+#include "plugin.hxx"
+
+/*
+*/
+
+namespace
+{
+class NamespaceIndentation : public loplugin::FilteringPlugin<NamespaceIndentation>
+{
+public:
+ explicit NamespaceIndentation(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual bool preRun() override { return true; }
+
+ virtual void run() override
+ {
+ if (preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitNamespaceDecl(NamespaceDecl const*);
+
+private:
+ std::string GetFullNamespace(const NamespaceDecl* nsDecl);
+};
+
+void trim(std::string& str)
+{
+ // right trim
+ auto it1 = std::find_if(str.rbegin(), str.rend(), [](char ch) {
+ return !std::isspace<char>(ch, std::locale::classic());
+ });
+ str.erase(it1.base(), str.end());
+ // left trim
+ auto it2 = std::find_if(str.begin(), str.end(), [](char ch) {
+ return !std::isspace<char>(ch, std::locale::classic());
+ });
+ str.erase(str.begin(), it2);
+}
+
+bool NamespaceIndentation::VisitNamespaceDecl(NamespaceDecl const* nsDecl)
+{
+ if (ignoreLocation(nsDecl))
+ return true;
+ if (nsDecl->isAnonymousNamespace())
+ return true;
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(nsDecl->getLocation())))
+ return true;
+
+ // right now, just fixing up the fallout from clang-tidy-modernize-namespaces, which
+ // does not touch header files
+ if (!compiler.getSourceManager().isInMainFile(nsDecl->getLocation()))
+ return true;
+
+ auto& SM = compiler.getSourceManager();
+
+ // if we have a combined ns (.e.g namespace aaa::bbb), this appears in the AST
+ // as two nested namespace sharing the same source locations, so ignore the outer decls
+ if (!nsDecl->decls_empty())
+ {
+ auto child = dyn_cast_or_null<NamespaceDecl>(*nsDecl->decls_begin());
+ if (child)
+ {
+ bool invalid1 = false;
+ bool invalid2 = false;
+ unsigned line1 = SM.getPresumedLineNumber(nsDecl->getBeginLoc(), &invalid1);
+ unsigned line2 = SM.getPresumedLineNumber(child->getBeginLoc(), &invalid2);
+ if (line1 == line2)
+ return true;
+ }
+ }
+
+ // Truly hacky way to find the actual beginning of an xxx::yyy namespace declaration
+ // if we are inside the yyy NameSpaceDecl of
+ // namespace xxx::yyy
+ // the beginLoc is just between the "xxx" and the "::"
+ auto nsDeclBeginLoc = nsDecl->getBeginLoc();
+ bool foundMultiple = false;
+ {
+ constexpr int BACKSCAN = 32;
+ auto beginLoc = nsDecl->getBeginLoc().getLocWithOffset(-BACKSCAN);
+ auto endLoc = nsDecl->getBeginLoc().getLocWithOffset(3);
+ const char* p1 = SM.getCharacterData(beginLoc);
+ const char* p2 = SM.getCharacterData(endLoc);
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ if (p2 < p1 || n > 128 || (p2 - p1 + n) > 2048)
+ return true;
+ auto s = std::string(p1, p2 - p1);
+ auto idx1 = s.rfind(" "); // find the space preceding the namespace token
+ if (idx1 != std::string::npos)
+ {
+ auto namespaceToken = s.substr(idx1);
+ if (namespaceToken.find("::") != std::string::npos)
+ {
+ auto idx = s.rfind("\n");
+ nsDeclBeginLoc = nsDecl->getBeginLoc().getLocWithOffset(idx - BACKSCAN + 1);
+ foundMultiple = true;
+ }
+ }
+ }
+
+ // for now, I am only interested in fixing the fallout from clang-tidy-modernize-namespace, not
+ // anything else
+ if (!foundMultiple)
+ return true;
+
+ bool invalid1 = false;
+ bool invalid2 = false;
+ unsigned col1 = SM.getPresumedColumnNumber(nsDeclBeginLoc, &invalid1);
+ unsigned col2 = SM.getPresumedColumnNumber(nsDecl->getRBraceLoc(), &invalid2);
+ unsigned line1 = SM.getPresumedLineNumber(nsDeclBeginLoc, &invalid1);
+ unsigned line2 = SM.getPresumedLineNumber(nsDecl->getRBraceLoc(), &invalid2);
+ if (invalid1 || invalid2)
+ return true;
+ if (line1 == line2) // single line declaration
+ return true;
+ if (col1 != col2)
+ report(DiagnosticsEngine::Warning, "statement right brace mis-aligned",
+ nsDecl->getRBraceLoc());
+
+ // no easy way to get the position of the left brace
+ auto endLoc = nsDecl->getBeginLoc().getLocWithOffset(256);
+ const char* p1 = SM.getCharacterData(SM.getExpansionLoc(nsDecl->getBeginLoc()));
+ const char* p2 = SM.getCharacterData(SM.getExpansionLoc(endLoc));
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ if (p2 < p1 || n > 128 || (p2 - p1 + n) > 2048)
+ return true;
+ auto s = std::string(p1, p2 - p1 + n);
+ auto idx1 = s.find("\n");
+ auto idx2 = s.find("{");
+ if (idx1 != std::string::npos && idx2 != std::string::npos)
+ if (idx1 < idx2)
+ {
+ auto col3 = idx2 - idx1;
+ if (col1 != col3)
+ report(DiagnosticsEngine::Warning, "statement left brace mis-aligned",
+ nsDecl->getBeginLoc());
+ }
+
+ // extract the comment following the end brace
+ auto beginLoc = nsDecl->getRBraceLoc();
+ endLoc = beginLoc.getLocWithOffset(128);
+ p1 = SM.getCharacterData(SM.getExpansionLoc(beginLoc));
+ p2 = SM.getCharacterData(SM.getExpansionLoc(endLoc));
+ n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ if (p2 < p1 || n > 128 || (p2 - p1 + n) > 2048)
+ return true;
+ s = std::string(p1, p2 - p1 + n);
+ idx1 = s.find("//");
+ idx2 = s.find("\n");
+ if (idx1 != std::string::npos && idx2 != std::string::npos && idx1 < idx2)
+ {
+ idx1 += 2;
+ s = s.substr(idx1, idx2 - idx1);
+ trim(s);
+ std::string fullNamespace = GetFullNamespace(nsDecl);
+ if (!(s == fullNamespace || s == (fullNamespace + " namespace") || s == "namespace"
+ || s == ("namespace " + fullNamespace) || s == ("namespace ::" + fullNamespace)
+ || s == ("end " + fullNamespace) || s == "end namespace"
+ || s == ("end namespace " + fullNamespace)
+ || s == ("end " + fullNamespace + " namespace") || s == "end of namespace"
+ || s == ("end of namespace " + fullNamespace)
+ || s == ("end of namespace ::" + fullNamespace)
+ || s == ("eof of namespace " + fullNamespace)))
+ {
+ report(DiagnosticsEngine::Warning, "incorrect comment at end of namespace %0",
+ nsDecl->getRBraceLoc())
+ << fullNamespace;
+ }
+ }
+
+ return true;
+}
+
+std::string NamespaceIndentation::GetFullNamespace(const NamespaceDecl* nsDecl)
+{
+ std::vector<llvm::StringRef> names;
+ auto ns = nsDecl;
+ while (ns)
+ {
+ names.push_back(ns->getName());
+ ns = dyn_cast<NamespaceDecl>(ns->getParent());
+ }
+ std::string fullNamespace;
+ for (auto it = names.rbegin(); it != names.rend(); ++it)
+ fullNamespace += "::" + it->str();
+ fullNamespace = fullNamespace.substr(2);
+ return fullNamespace;
+}
+
+// leave this off by default, so as not to annoy people
+loplugin::Plugin::Registration<NamespaceIndentation> namespaceindentation("namespaceindentation",
+ false);
+
+} // namespace
+
+#endif // LO_CLANG_SHARED_PLUGINS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/oncevar.cxx b/compilerplugins/clang/store/oncevar.cxx
new file mode 100644
index 0000000000..44fcfa9508
--- /dev/null
+++ b/compilerplugins/clang/store/oncevar.cxx
@@ -0,0 +1,414 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "plugin.hxx"
+#include "check.hxx"
+#include "config_clang.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/StmtVisitor.h"
+
+// Original idea from tml.
+// Look for variables that are (a) initialised from zero or one constants. (b) only used in one spot.
+// In which case, we might as well inline it.
+
+namespace
+{
+
+Expr const * lookThroughInitListExpr(Expr const * expr) {
+ if (auto const ile = dyn_cast<InitListExpr>(expr->IgnoreParenImpCasts())) {
+ if (ile->getNumInits() == 1) {
+ return ile->getInit(0);
+ }
+ }
+ return expr;
+}
+
+class ConstantValueDependentExpressionVisitor:
+ public ConstStmtVisitor<ConstantValueDependentExpressionVisitor, bool>
+{
+ ASTContext const & context_;
+
+public:
+ ConstantValueDependentExpressionVisitor(ASTContext const & context):
+ context_(context) {}
+
+ bool Visit(Stmt const * stmt) {
+ assert(isa<Expr>(stmt));
+ auto const expr = cast<Expr>(stmt);
+ if (!expr->isValueDependent()) {
+ return expr->isEvaluatable(context_);
+ }
+ return ConstStmtVisitor::Visit(stmt);
+ }
+
+ bool VisitParenExpr(ParenExpr const * expr)
+ { return Visit(expr->getSubExpr()); }
+
+ bool VisitCastExpr(CastExpr const * expr) {
+ return Visit(expr->getSubExpr());
+ }
+
+ bool VisitUnaryOperator(UnaryOperator const * expr)
+ { return Visit(expr->getSubExpr()); }
+
+ bool VisitBinaryOperator(BinaryOperator const * expr) {
+ return Visit(expr->getLHS()) && Visit(expr->getRHS());
+ }
+
+ bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const *) {
+ return true;
+ }
+};
+
+class OnceVar:
+ public loplugin::FilteringPlugin<OnceVar>
+{
+public:
+ explicit OnceVar(loplugin::InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override {
+ // ignore some files with problematic macros
+ std::string fn(handler.getMainFileName());
+ loplugin::normalizeDotDotInFilePath(fn);
+ // platform-specific stuff
+ if (fn == SRCDIR "/sal/osl/unx/thread.cxx"
+ || fn == SRCDIR "/sot/source/base/formats.cxx"
+ || fn == SRCDIR "/svl/source/config/languageoptions.cxx"
+ || fn == SRCDIR "/sfx2/source/appl/appdde.cxx"
+ || fn == SRCDIR "/configmgr/source/components.cxx"
+ || fn == SRCDIR "/embeddedobj/source/msole/oleembed.cxx")
+ return;
+ // some of this is necessary
+ if (loplugin::hasPathnamePrefix( fn, SRCDIR "/sal/qa/"))
+ return;
+ if (loplugin::hasPathnamePrefix( fn, SRCDIR "/comphelper/qa/"))
+ return;
+ // TODO need to check calls via function pointer
+ if (fn == SRCDIR "/i18npool/source/textconversion/textconversion_zh.cxx"
+ || fn == SRCDIR "/i18npool/source/localedata/localedata.cxx")
+ return;
+ // debugging stuff
+ if (fn == SRCDIR "/sc/source/core/data/dpcache.cxx"
+ || fn == SRCDIR "/sw/source/core/layout/dbg_lay.cxx"
+ || fn == SRCDIR "/sw/source/core/layout/ftnfrm.cxx")
+ return;
+ // TODO taking local reference to variable
+ if (fn == SRCDIR "/sc/source/filter/excel/xechart.cxx")
+ return;
+ // macros managing to generate to a valid warning
+ if (fn == SRCDIR "/solenv/bin/concat-deps.c")
+ return;
+ // TODO bug in the plugin
+ if (fn == SRCDIR "/vcl/unx/generic/app/saldisp.cxx")
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ for (auto const & varDecl : maVarDeclSet)
+ {
+ if (maVarDeclToIgnoreSet.find(varDecl) != maVarDeclToIgnoreSet.end())
+ continue;
+ int noUses = 0;
+ auto it = maVarUsesMap.find(varDecl);
+ if (it != maVarUsesMap.end())
+ noUses = it->second;
+ if (noUses > 1)
+ continue;
+ report(DiagnosticsEngine::Warning,
+ "var used only once, should be inlined or declared const",
+ varDecl->getLocation())
+ << varDecl->getSourceRange();
+ if (it != maVarUsesMap.end())
+ report(DiagnosticsEngine::Note,
+ "used here",
+ maVarUseSourceRangeMap[varDecl].getBegin())
+ << maVarUseSourceRangeMap[varDecl];
+ }
+ }
+
+ bool VisitMemberExpr(MemberExpr const * expr) {
+ // ignore cases like:
+ // const OUString("xxx") xxx;
+ // rtl_something(xxx.pData);
+ // where we cannot inline the declaration.
+ if (isa<FieldDecl>(expr->getMemberDecl())) {
+ recordIgnore(expr);
+ }
+ return true;
+ }
+
+ bool VisitUnaryOperator(UnaryOperator const * expr) {
+ // if we take the address of it, or we modify it, ignore it
+ UnaryOperator::Opcode op = expr->getOpcode();
+ if (op == UO_AddrOf || op == UO_PreInc || op == UO_PostInc
+ || op == UO_PreDec || op == UO_PostDec)
+ {
+ recordIgnore(expr->getSubExpr());
+ }
+ return true;
+ }
+
+ bool VisitBinaryOperator(BinaryOperator const * expr) {
+ // if we assign it another value, or modify it, ignore it
+ BinaryOperator::Opcode op = expr->getOpcode();
+ 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)
+ {
+ recordIgnore(expr->getLHS());
+ }
+ return true;
+ }
+
+ bool VisitCallExpr(CallExpr const * expr) {
+ unsigned firstArg = 0;
+ if (auto const cmce = dyn_cast<CXXMemberCallExpr>(expr)) {
+ if (auto const e1 = cmce->getMethodDecl()) {
+ if (!(e1->isConst() || e1->isStatic())) {
+ recordIgnore(cmce->getImplicitObjectArgument());
+ }
+ } else if (auto const e2 = dyn_cast<BinaryOperator>(
+ cmce->getCallee()->IgnoreParenImpCasts()))
+ {
+ switch (e2->getOpcode()) {
+ case BO_PtrMemD:
+ case BO_PtrMemI:
+ if (!e2->getRHS()->getType()->getAs<MemberPointerType>()
+ ->getPointeeType()->getAs<FunctionProtoType>()
+ ->isConst())
+ {
+ recordIgnore(e2->getLHS());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ } else if (auto const coce = dyn_cast<CXXOperatorCallExpr>(expr)) {
+ if (auto const cmd = dyn_cast_or_null<CXXMethodDecl>(
+ coce->getDirectCallee()))
+ {
+ if (!cmd->isStatic()) {
+ assert(coce->getNumArgs() != 0);
+ if (!cmd->isConst()) {
+ recordIgnore(coce->getArg(0));
+ }
+ firstArg = 1;
+ }
+ }
+ }
+ // ignore those ones we are passing by reference
+ const FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
+ if (calleeFunctionDecl) {
+ for (unsigned i = firstArg; i < expr->getNumArgs(); ++i) {
+ if (i < calleeFunctionDecl->getNumParams()) {
+ QualType qt { calleeFunctionDecl->getParamDecl(i)->getType() };
+ if (loplugin::TypeCheck(qt).LvalueReference().NonConst()) {
+ recordIgnore(expr->getArg(i));
+ }
+ if (loplugin::TypeCheck(qt).Pointer().NonConst()) {
+ recordIgnore(expr->getArg(i));
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
+ // ignore those ones we are passing by reference
+ const CXXConstructorDecl* cxxConstructorDecl = expr->getConstructor();
+ for (unsigned i = 0; i < expr->getNumArgs(); ++i) {
+ if (i < cxxConstructorDecl->getNumParams()) {
+ QualType qt { cxxConstructorDecl->getParamDecl(i)->getType() };
+ if (loplugin::TypeCheck(qt).LvalueReference().NonConst()) {
+ recordIgnore(expr->getArg(i));
+ }
+ if (loplugin::TypeCheck(qt).Pointer().NonConst()) {
+ recordIgnore(expr->getArg(i));
+ }
+ }
+ }
+ return true;
+ }
+
+ bool VisitDeclRefExpr( const DeclRefExpr* );
+ bool VisitVarDecl( const VarDecl* );
+ bool TraverseFunctionDecl( FunctionDecl* functionDecl );
+
+private:
+ std::unordered_set<VarDecl const *> maVarDeclSet;
+ std::unordered_set<VarDecl const *> maVarDeclToIgnoreSet;
+ std::unordered_map<VarDecl const *, int> maVarUsesMap;
+ std::unordered_map<VarDecl const *, SourceRange> maVarUseSourceRangeMap;
+
+ bool isConstantValueDependentExpression(Expr const * expr) {
+ return ConstantValueDependentExpressionVisitor(compiler.getASTContext())
+ .Visit(expr);
+ }
+
+ void recordIgnore(Expr const * expr) {
+ for (;;) {
+ expr = expr->IgnoreParenImpCasts();
+ if (auto const e = dyn_cast<MemberExpr>(expr)) {
+ if (isa<FieldDecl>(e->getMemberDecl())) {
+ expr = e->getBase();
+ continue;
+ }
+ }
+ if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
+ expr = e->getBase();
+ continue;
+ }
+ if (auto const e = dyn_cast<BinaryOperator>(expr)) {
+ if (e->getOpcode() == BO_PtrMemD) {
+ expr = e->getLHS();
+ continue;
+ }
+ }
+ break;
+ }
+ auto const dre = dyn_cast<DeclRefExpr>(expr);
+ if (dre == nullptr) {
+ return;
+ }
+ auto const var = dyn_cast<VarDecl>(dre->getDecl());
+ if (var == nullptr) {
+ return;
+ }
+ maVarDeclToIgnoreSet.insert(var);
+ }
+};
+
+bool OnceVar::TraverseFunctionDecl( FunctionDecl* functionDecl )
+{
+ // Ignore functions that contains #ifdef-ery, can be quite tricky
+ // to make useful changes when this plugin fires in such functions
+ if (containsPreprocessingConditionalInclusion(
+ functionDecl->getSourceRange()))
+ return true;
+ return RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
+}
+
+bool OnceVar::VisitVarDecl( const VarDecl* varDecl )
+{
+ if (ignoreLocation(varDecl)) {
+ return true;
+ }
+ if (auto const init = varDecl->getInit()) {
+ recordIgnore(lookThroughInitListExpr(init));
+ }
+ if (varDecl->isExceptionVariable() || isa<ParmVarDecl>(varDecl)) {
+ return true;
+ }
+ // ignore stuff in header files (which should really not be there, but anyhow)
+ if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation())) {
+ return true;
+ }
+ // Ignore macros like FD_ZERO
+ if (compiler.getSourceManager().isMacroBodyExpansion(varDecl->getBeginLoc())) {
+ return true;
+ }
+ if (varDecl->hasGlobalStorage()) {
+ return true;
+ }
+ auto const tc = loplugin::TypeCheck(varDecl->getType());
+ if (!varDecl->getType().isCXX11PODType(compiler.getASTContext())
+ && !tc.Class("OString").Namespace("rtl").GlobalNamespace()
+ && !tc.Class("OUString").Namespace("rtl").GlobalNamespace()
+ && !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()
+ && !tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
+ && !tc.Class("Color").GlobalNamespace()
+ && !tc.Class("Pair").GlobalNamespace()
+ && !tc.Class("Point").GlobalNamespace()
+ && !tc.Class("Size").GlobalNamespace()
+ && !tc.Class("Range").GlobalNamespace()
+ && !tc.Class("Selection").GlobalNamespace()
+ && !tc.Class("Rectangle").Namespace("tools").GlobalNamespace())
+ {
+ return true;
+ }
+ if (varDecl->getType()->isPointerType())
+ return true;
+ // if it's declared const, ignore it, it's there to make the code easier to read
+ if (tc.Const())
+ return true;
+
+ if (!varDecl->hasInit())
+ return true;
+
+ // check for string or scalar literals
+ bool foundStringLiteral = false;
+ const Expr * initExpr = varDecl->getInit();
+ if (auto e = dyn_cast<ExprWithCleanups>(initExpr)) {
+ initExpr = e->getSubExpr();
+ }
+ if (isa<clang::StringLiteral>(initExpr)) {
+ foundStringLiteral = true;
+ } else if (auto constructExpr = dyn_cast<CXXConstructExpr>(initExpr)) {
+ if (constructExpr->getNumArgs() == 0) {
+ foundStringLiteral = true; // i.e., empty string
+ } else {
+ auto stringLit2 = dyn_cast<clang::StringLiteral>(constructExpr->getArg(0));
+ foundStringLiteral = stringLit2 != nullptr;
+ }
+ }
+ if (!foundStringLiteral) {
+ auto const init = varDecl->getInit();
+ if (!(init->isValueDependent()
+ ? isConstantValueDependentExpression(init)
+ : init->isConstantInitializer(
+ compiler.getASTContext(), false/*ForRef*/)))
+ {
+ return true;
+ }
+ }
+
+ maVarDeclSet.insert(varDecl);
+
+ return true;
+}
+
+bool OnceVar::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
+{
+ if (ignoreLocation(declRefExpr)) {
+ return true;
+ }
+ const Decl* decl = declRefExpr->getDecl();
+ if (!isa<VarDecl>(decl) || isa<ParmVarDecl>(decl)) {
+ return true;
+ }
+ const VarDecl * varDecl = dyn_cast<VarDecl>(decl)->getCanonicalDecl();
+ // ignore stuff in header files (which should really not be there, but anyhow)
+ if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation())) {
+ return true;
+ }
+
+ if (maVarUsesMap.find(varDecl) == maVarUsesMap.end()) {
+ maVarUsesMap[varDecl] = 1;
+ maVarUseSourceRangeMap[varDecl] = declRefExpr->getSourceRange();
+ } else {
+ maVarUsesMap[varDecl]++;
+ }
+
+ return true;
+}
+
+loplugin::Plugin::Registration< OnceVar > X("oncevar", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/optmove.cxx b/compilerplugins/clang/store/optmove.cxx
new file mode 100644
index 0000000000..51b5a4b84f
--- /dev/null
+++ b/compilerplugins/clang/store/optmove.cxx
@@ -0,0 +1,161 @@
+/* -*- 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 <string>
+#include <set>
+
+/**
+ * This plugin is unfinished, abandoned because it did not find anything interesting.
+ *
+ * Look for variables that are
+ * (a) copied from
+ * (b) never used after the copy
+ * (c) have move operators
+ *
+ * The intention being to find places where we can move data (e.g. in containers) instead of copying.
+*/
+
+namespace
+{
+class OptMove : public loplugin::FilteringPlugin<OptMove>
+{
+public:
+ explicit OptMove(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ for (auto const& pair : m_Candidates)
+ {
+ //auto varDecl = pair.first;
+ auto candidate = pair.second;
+ if (!candidate.canUseExpr)
+ continue;
+ report(DiagnosticsEngine::Warning, "can std::move value instead of copy",
+ candidate.canUseExpr->getSourceRange().getBegin())
+ << candidate.canUseExpr->getSourceRange();
+ //varDecl->dump();
+ }
+ }
+
+ bool VisitVarDecl(const VarDecl*);
+ bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr*);
+ bool VisitDeclRefExpr(const DeclRefExpr*);
+ bool VisitFunctionDecl(const FunctionDecl* f)
+ {
+ if (f->getIdentifier() && f->getName() == "foo")
+ f->dump();
+ return true;
+ }
+
+private:
+ struct Candidate
+ {
+ const DeclRefExpr* operatorArg1 = nullptr;
+ const Expr* canUseExpr = nullptr;
+ };
+ std::map<const VarDecl*, Candidate> m_Candidates;
+};
+
+bool OptMove::VisitVarDecl(const VarDecl* varDecl)
+{
+ if (ignoreLocation(varDecl))
+ return true;
+ if (varDecl->hasGlobalStorage())
+ return true;
+ if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage)
+ return true;
+ if (!varDecl->getType()->isRecordType())
+ return true;
+
+ auto cxxRecord = dyn_cast<CXXRecordDecl>(varDecl->getType()->getAsRecordDecl());
+ if (!cxxRecord || !cxxRecord->hasDefinition() || !cxxRecord->hasMoveAssignment())
+ return true;
+ // ignore our simpler types for now, I'm after bigger game
+ auto typeName = cxxRecord->getName();
+ if (typeName.contains("Reference") || typeName.contains("Color") || typeName.contains("VclPtr")
+ || typeName.contains("OString") || typeName.contains("OUString")
+ || typeName.contains("Rectangle") || typeName.contains("Size")
+ || typeName.contains("Selection") || typeName.contains("Point")
+ || typeName.contains("strong_int"))
+ return true;
+ m_Candidates.emplace(varDecl, Candidate());
+
+ if (!varDecl->hasInit())
+ return true;
+ auto cons = dyn_cast<CXXConstructExpr>(varDecl->getInit());
+ if (!cons || !cons->getConstructor()->isCopyConstructor())
+ return true;
+ auto arg1 = dyn_cast<DeclRefExpr>(cons->getArg(0)->IgnoreImplicit());
+ if (!arg1)
+ return true;
+ auto varDecl1 = dyn_cast<VarDecl>(arg1->getDecl());
+ if (!varDecl1)
+ return true;
+ auto it = m_Candidates.find(varDecl1);
+ if (it == m_Candidates.end())
+ return true;
+ it->second.operatorArg1 = arg1;
+ it->second.canUseExpr = cons;
+ return true;
+}
+
+bool OptMove::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* cxxOperatorCallExpr)
+{
+ if (ignoreLocation(cxxOperatorCallExpr))
+ return true;
+ auto op = cxxOperatorCallExpr->getOperator();
+ if (op != OO_Equal)
+ return true;
+ auto arg0 = dyn_cast<DeclRefExpr>(cxxOperatorCallExpr->getArg(0)->IgnoreImplicit());
+ auto arg1 = dyn_cast<DeclRefExpr>(cxxOperatorCallExpr->getArg(1)->IgnoreImplicit());
+ if (!arg0 || !arg1)
+ return true;
+ auto varDecl0 = dyn_cast<VarDecl>(arg0->getDecl());
+ auto varDecl1 = dyn_cast<VarDecl>(arg1->getDecl());
+ if (!varDecl0 || !varDecl1)
+ return true;
+ auto cxxMethodDecl = dyn_cast_or_null<CXXMethodDecl>(cxxOperatorCallExpr->getDirectCallee());
+ if (!cxxMethodDecl || !cxxMethodDecl->isCopyAssignmentOperator())
+ return true;
+ auto it = m_Candidates.find(varDecl1);
+ if (it == m_Candidates.end())
+ return true;
+ it->second.operatorArg1 = arg1;
+ it->second.canUseExpr = cxxOperatorCallExpr;
+ return true;
+}
+
+bool OptMove::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
+{
+ if (ignoreLocation(declRefExpr))
+ return true;
+ auto varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
+ if (!varDecl)
+ return true;
+ auto it = m_Candidates.find(varDecl);
+ if (it == m_Candidates.end())
+ return true;
+ if (it->second.operatorArg1 == declRefExpr)
+ return true;
+ m_Candidates.erase(it);
+ return true;
+}
+
+loplugin::Plugin::Registration<OptMove> noexceptmove("optmove");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/optvalue.cxx b/compilerplugins/clang/store/optvalue.cxx
new file mode 100644
index 0000000000..2b703e194f
--- /dev/null
+++ b/compilerplugins/clang/store/optvalue.cxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include "config_clang.h"
+#include "plugin.hxx"
+#include "check.hxx"
+
+/*
+*/
+
+namespace
+{
+class OptValue : public loplugin::FilteringPlugin<OptValue>
+{
+public:
+ explicit OptValue(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual bool preRun() override { return true; }
+
+ virtual void run() override
+ {
+ if (preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitCXXMemberCallExpr(const CXXMemberCallExpr*);
+};
+
+bool OptValue::VisitCXXMemberCallExpr(const CXXMemberCallExpr* topExpr)
+{
+ if (ignoreLocation(topExpr))
+ return true;
+ const CXXMethodDecl* methodDecl = topExpr->getMethodDecl();
+ if (!methodDecl)
+ return true;
+ if (!methodDecl->getIdentifier() || methodDecl->getName() != "value")
+ return true;
+ auto expr1 = topExpr->getImplicitObjectArgument()->IgnoreImpCasts();
+ if (!isa<MaterializeTemporaryExpr>(expr1))
+ return true;
+
+ report(DiagnosticsEngine::Warning, "call to OptValue::value()", topExpr->getBeginLoc());
+
+ return true;
+}
+
+loplugin::Plugin::Registration<OptValue> optvalue("optvalue", false);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/paintmethodconversion.cxx b/compilerplugins/clang/store/paintmethodconversion.cxx
new file mode 100644
index 0000000000..7a394ae253
--- /dev/null
+++ b/compilerplugins/clang/store/paintmethodconversion.cxx
@@ -0,0 +1,94 @@
+/* -*- 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 "clang/Lex/Lexer.h"
+
+#include "plugin.hxx"
+#include <iostream>
+#include <fstream>
+
+/**
+ * Rewrites all Paint method on subclasses of vcl::Window to include RenderContext& as parameter.
+ *
+ * run as: make COMPILER_PLUGIN_TOOL=paintmethodconversion UPDATE_FILES=all FORCE_COMPILE=all
+ */
+
+namespace
+{
+
+bool baseCheckNotWindowSubclass(const CXXRecordDecl* aBaseDefinition, void* /*pInput*/)
+{
+ if (aBaseDefinition && aBaseDefinition->getQualifiedNameAsString() == "vcl::Window")
+ {
+ return false;
+ }
+ return true;
+}
+
+bool isDerivedFromWindow(const CXXRecordDecl* decl) {
+ if (!decl)
+ return false;
+ // skip vcl::Window
+ if (decl->getQualifiedNameAsString() == "vcl::Window")
+ return false;
+ if (!decl->forallBases(baseCheckNotWindowSubclass, nullptr, true))
+ return true;
+
+ return false;
+}
+
+class PaintMethodConversion: public loplugin::FilteringRewritePlugin<PaintMethodConversion>
+{
+public:
+ explicit PaintMethodConversion(InstantiationData const& data):
+ FilteringRewritePlugin(data)
+ {}
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool TraverseCXXMethodDecl(const CXXMethodDecl* methodDeclaration)
+ {
+ if (!rewriter)
+ return true;
+
+ if (methodDeclaration->getNameAsString() != "Paint")
+ return true;
+
+ if (!isDerivedFromWindow(methodDeclaration->getParent()))
+ {
+ return true;
+ }
+
+ unsigned int nNoOfParameters = methodDeclaration->getNumParams();
+
+ if (nNoOfParameters == 1) // we expect only one parameter Paint(Rect&)
+ {
+ const ParmVarDecl* parameterDecl = methodDeclaration->getParamDecl(0);
+ if (methodDeclaration->hasBody())
+ {
+ rewriter->InsertText(parameterDecl->getLocStart(), "vcl::RenderContext& /*rRenderContext*/, ", true, true);
+ }
+ else
+ {
+ rewriter->InsertText(parameterDecl->getLocStart(), "vcl::RenderContext& rRenderContext, ", true, true);
+ }
+ }
+ return true;
+ }
+
+};
+
+loplugin::Plugin::Registration<PaintMethodConversion> X("paintmethodconversion", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/postfixincrementfix.cxx b/compilerplugins/clang/store/postfixincrementfix.cxx
new file mode 100644
index 0000000000..eba6f35b5a
--- /dev/null
+++ b/compilerplugins/clang/store/postfixincrementfix.cxx
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "postfixincrementfix.hxx"
+
+/*
+This is a rewriter.
+
+Change all postfix ++ operators of non-trivial types to prefix if possible.
+*/
+
+namespace loplugin
+{
+
+PostfixIncrementFix::PostfixIncrementFix( const InstantiationData& data )
+ : FilteringRewritePlugin( data )
+ {
+ }
+
+void PostfixIncrementFix::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool PostfixIncrementFix::VisitCXXOperatorCallExpr( const CXXOperatorCallExpr* op )
+ {
+ if( ignoreLocation( op ))
+ return true;
+ // postfix ++ has two arguments (the operand and the hidden extra int)
+ if( op->getOperator() == OO_PlusPlus && op->getNumArgs() == 2 )
+ fixPostfixOperator( op );
+ // For primitive types it would be UnaryOperatorExpr, but probably no good reason to change those.
+ return true;
+ }
+
+void PostfixIncrementFix::fixPostfixOperator( const CXXOperatorCallExpr* op )
+ {
+ if( !canChangePostfixToPrefix( op, op ))
+ return;
+ if( !shouldDoChange( op->getArg( 0 )))
+ return;
+ // Adding spaces around the moved ++ should not be necessary
+ // (there might a problem with e.g. a+b++ -> a+++b (i.e. a++ +b),
+ // but we don't change such expressions).
+ if( insertText( op->getLocStart(), "++" )) // insert is intentionally first, in case it fails
+ removeText( op->getCallee()->getSourceRange());
+ }
+
+bool PostfixIncrementFix::canChangePostfixToPrefix( const Stmt* stmt , const CXXOperatorCallExpr* op )
+ {
+ const Stmt* parent = parentStmt( stmt );
+ if( parent == NULL )
+ return true;
+ // check if foo++ can be safely replaced by ++foo
+ switch( parent->getStmtClass())
+ {
+ case Stmt::CompoundStmtClass:
+ return true;
+ // these mean somebody is going to use it
+ case Stmt::ImplicitCastExprClass:
+ case Stmt::MaterializeTemporaryExprClass:
+ case Stmt::BinaryOperatorClass:
+ case Stmt::UnaryOperatorClass:
+ case Stmt::CallExprClass:
+ case Stmt::CXXOperatorCallExprClass:
+ return false;
+ case Stmt::CXXBindTemporaryExprClass:
+ // tricky, it may just mean the temporary will be cleaned up
+ // (by ExprWithCleanups), ignore and go up
+ return canChangePostfixToPrefix( parent, op );
+ case Stmt::ExprWithCleanupsClass:
+ // cleanup of a temporary, should be harmless (if the use
+ // of the postfix ++ operator here relies on the fact that
+ // the dtor for the object will be called, that's pretty insane
+ // code). Ignore and go up.
+ return canChangePostfixToPrefix( parent, op );
+ case Stmt::ParenExprClass: // parentheses, go up
+ return canChangePostfixToPrefix( parent, op );
+ case Stmt::IfStmtClass:
+ // cannot be changed in condition, can be changed in statements
+ return cast< IfStmt >( parent )->getCond() != stmt;
+ case Stmt::WhileStmtClass:
+ return cast< WhileStmt >( parent )->getCond() != stmt;
+ case Stmt::DoStmtClass:
+ return cast< DoStmt >( parent )->getCond() != stmt;
+ case Stmt::ForStmtClass:
+ return cast< ForStmt >( parent )->getCond() != stmt;
+ default:
+ report( DiagnosticsEngine::Fatal, "cannot analyze operator++ (plugin needs fixing)",
+ op->getLocStart()) << parent->getSourceRange();
+ parent->dump();
+ return false;
+ }
+ }
+
+bool PostfixIncrementFix::shouldDoChange( const Expr* operand )
+ {
+ // TODO Changing 'a->b++' to '++a->b' is technically the same, but the latter probably looks confusing,
+ // so either avoid that, or add parentheses. Avoid for now.
+ const Expr* expr = const_cast< Expr* >( operand )->IgnoreImplicit(); // does not have const version
+ switch( expr->getStmtClass())
+ {
+ case Stmt::ParenExprClass:
+ return true; // there are already parentheses, ok to move the ++
+ case Stmt::MemberExprClass:
+ return false; // ++a->b , avoid
+ case Stmt::DeclRefExprClass:
+ return true;
+ default:
+ {
+ report( DiagnosticsEngine::Fatal, "cannot analyze operator++ (plugin needs fixing)",
+ expr->getLocStart()) << operand->getSourceRange();
+ expr->dump();
+ operand->dump();
+ return false;
+ }
+ }
+ }
+
+static Plugin::Registration< PostfixIncrementFix > X( "postfixincrementfix" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/postfixincrementfix.hxx b/compilerplugins/clang/store/postfixincrementfix.hxx
new file mode 100644
index 0000000000..7f496662be
--- /dev/null
+++ b/compilerplugins/clang/store/postfixincrementfix.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+class PostfixIncrementFix
+ : public loplugin::FilteringRewritePlugin< PostfixIncrementFix >
+ {
+ public:
+ explicit PostfixIncrementFix( const InstantiationData& data );
+ virtual void run() override;
+ bool VisitCXXOperatorCallExpr( const CXXOperatorCallExpr* op );
+ private:
+ void fixPostfixOperator( const CXXOperatorCallExpr* op );
+ void fixPostfixOperators( const Stmt* stmt );
+ bool canChangePostfixToPrefix( const Stmt* stmt, const CXXOperatorCallExpr* op );
+ bool shouldDoChange( const Expr* op );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/putpoolitem.cxx b/compilerplugins/clang/store/putpoolitem.cxx
new file mode 100644
index 0000000000..8080599973
--- /dev/null
+++ b/compilerplugins/clang/store/putpoolitem.cxx
@@ -0,0 +1,103 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include <unordered_set>
+
+#include <clang/AST/CXXInheritance.h>
+
+#include "config_clang.h"
+
+#include "plugin.hxx"
+#include "check.hxx"
+
+/**
+
+*/
+
+namespace
+{
+class PutPoolItem : public loplugin::FilteringPlugin<PutPoolItem>
+{
+public:
+ explicit PutPoolItem(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual bool preRun() override
+ {
+ // StringRef fn(handler.getMainFileName());
+ // if (loplugin::isSamePathname(fn, WORKDIR "/YaccTarget/unoidl/source/sourceprovider-parser.cxx"))
+ // return false;
+ return true;
+ }
+ virtual void run() override
+ {
+ if (preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitCXXMemberCallExpr(const CXXMemberCallExpr*);
+ bool VisitFunctionDecl(const FunctionDecl*)
+ {
+ // if (f->getIdentifier() && f->getName() == "foo")
+ // f->dump();
+ return true;
+ }
+};
+
+bool PutPoolItem::VisitCXXMemberCallExpr(const CXXMemberCallExpr* cxxCallExpr)
+{
+ if (ignoreLocation(cxxCallExpr))
+ return true;
+ auto tc = loplugin::TypeCheck(cxxCallExpr->getObjectType());
+ if (!tc.Class("SfxItemSet"))
+ return true;
+ if (!cxxCallExpr->getMethodDecl()->getIdentifier()
+ || cxxCallExpr->getMethodDecl()->getName() != "Put")
+ return true;
+ auto argExpr = dyn_cast<CXXOperatorCallExpr>(cxxCallExpr->getArg(0)->IgnoreImplicit());
+ if (!argExpr)
+ return true;
+ if (argExpr->getOperator() != OO_Star)
+ return true;
+ auto ptrExpr = argExpr->getArg(0)->IgnoreImplicit();
+ auto tc2 = loplugin::TypeCheck(ptrExpr->getType());
+ if (!tc2.Class("unique_ptr"))
+ return true;
+ // ignore calls when we are passing a copy of a member field
+ if (isa<MemberExpr>(ptrExpr))
+ return true;
+
+ StringRef fn = getFilenameOfLocation(
+ compiler.getSourceManager().getSpellingLoc(cxxCallExpr->getBeginLoc()));
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/app/inputwin.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/dbgui/csvgrid.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/shells/basesh.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/shells/textsh.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/xml/xmlimpit.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/shells/tabsh.cxx"))
+ return true;
+
+ // argExpr->dump();
+
+ report(DiagnosticsEngine::Warning, "could use std::move?", cxxCallExpr->getBeginLoc())
+ << cxxCallExpr->getSourceRange();
+ return true;
+}
+
+loplugin::Plugin::Registration<PutPoolItem> putpoolitem("putpoolitem", true);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/refassign.cxx b/compilerplugins/clang/store/refassign.cxx
new file mode 100644
index 0000000000..6d8e28605e
--- /dev/null
+++ b/compilerplugins/clang/store/refassign.cxx
@@ -0,0 +1,151 @@
+/* -*- 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/.
+ */
+#ifndef LO_CLANG_SHARED_PLUGINS
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+
+#include <clang/AST/CXXInheritance.h>
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+/**
+ * Look for a mistake I made (a lot) at one point where we assign a reference to a reference var, which
+ * does not do at all what I thought.
+ */
+
+namespace
+{
+class RefAssign : public loplugin::FilteringPlugin<RefAssign>
+{
+public:
+ explicit RefAssign(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual bool preRun() override
+ {
+ StringRef fn(handler.getMainFileName());
+ if (loplugin::isSamePathname(fn, SRCDIR "/comphelper/source/misc/syntaxhighlight.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/numbers/zformat.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/tools/source/memtools/multisel.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/tools/source/generic/point.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/control/edit.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/control/fmtfield.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/control/field.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/control/field2.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/edit/textview.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/edit/vclmedit.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/editeng/source/editeng/editdoc.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/editeng/source/editeng/impedit2.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/svxruler.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/ribbar/inputwin.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/core/text/txtftn.cxx"))
+ return false;
+
+ return true;
+ }
+
+ virtual void run() override
+ {
+ if (preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitBinaryOperator(BinaryOperator const*);
+ // causes crashes in CallExpr::getReturnType
+ bool TraverseFunctionTemplateDecl(FunctionTemplateDecl*) { return true; }
+ bool TraverseClassTemplateDecl(ClassTemplateDecl*) { return true; }
+
+private:
+ clang::QualType ExtractType(Expr const*);
+};
+
+bool RefAssign::VisitBinaryOperator(BinaryOperator const* binaryOp)
+{
+ if (ignoreLocation(binaryOp))
+ return true;
+ if (binaryOp->getOpcode() != BO_Assign)
+ return true;
+
+ // ignore assigning to/from an element of a collection
+ if (isa<CXXOperatorCallExpr>(binaryOp->getLHS()->IgnoreParenImpCasts()))
+ return true;
+ if (isa<CXXOperatorCallExpr>(binaryOp->getRHS()->IgnoreParenImpCasts()))
+ return true;
+
+ // if we are assigning to a parameter we probably mean it
+ if (auto declRefExpr = dyn_cast<DeclRefExpr>(binaryOp->getLHS()->IgnoreParenImpCasts()))
+ if (declRefExpr->getDecl() && isa<ParmVarDecl>(declRefExpr->getDecl()))
+ return true;
+
+ if (auto callExpr = dyn_cast<CallExpr>(binaryOp->getRHS()->IgnoreParenImpCasts()))
+ if (auto functionDecl = dyn_cast_or_null<FunctionDecl>(callExpr->getCalleeDecl()))
+ if (functionDecl->getIdentifier()
+ && (functionDecl->getName() == "min" || functionDecl->getName() == "max"))
+ return true;
+
+ auto lhsType = ExtractType(binaryOp->getLHS());
+ auto rhsType = ExtractType(binaryOp->getRHS());
+ if (!loplugin::TypeCheck(lhsType).LvalueReference())
+ return true;
+ if (!loplugin::TypeCheck(rhsType).LvalueReference())
+ return true;
+ binaryOp->dump();
+ report(DiagnosticsEngine::Warning,
+ "assigning a %0 to a var of type %1 probably does not do what you think",
+ binaryOp->getBeginLoc())
+ << rhsType << lhsType << binaryOp->getSourceRange();
+ return true;
+}
+
+clang::QualType RefAssign::ExtractType(Expr const* expr)
+{
+ expr = expr->IgnoreParenImpCasts();
+ if (auto declReflExpr = dyn_cast<DeclRefExpr>(expr))
+ {
+ if (auto varDecl = dyn_cast<VarDecl>(declReflExpr->getDecl()))
+ return varDecl->getType();
+ }
+ else if (auto callExpr = dyn_cast<CallExpr>(expr))
+ {
+ if (callExpr->isTypeDependent())
+ return {};
+ // callExpr->dump();
+ return callExpr->getCallReturnType(compiler.getASTContext());
+ }
+ return expr->getType();
+}
+
+loplugin::Plugin::Registration<RefAssign> refassign("refassign");
+
+} // namespace
+
+#endif // LO_CLANG_SHARED_PLUGINS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/removeforwardstringdecl.cxx b/compilerplugins/clang/store/removeforwardstringdecl.cxx
new file mode 100644
index 0000000000..e7a546a8f5
--- /dev/null
+++ b/compilerplugins/clang/store/removeforwardstringdecl.cxx
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "removeforwardstringdecl.hxx"
+
+/*
+This is a rewriter.
+
+Remove all forward declarations of rtl strings. I.e. 'namespace rtl { class OUString; }' etc.
+*/
+
+namespace loplugin
+{
+
+RemoveForwardStringDecl::RemoveForwardStringDecl( CompilerInstance& compiler, Rewriter& rewriter )
+ : FilteringRewritePlugin( compiler, rewriter )
+ {
+ }
+
+void RemoveForwardStringDecl::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool RemoveForwardStringDecl::VisitNamespaceDecl( const NamespaceDecl* declaration )
+ {
+ if( ignoreLocation( declaration ))
+ return true;
+ if( declaration->getQualifiedNameAsString() != "rtl" )
+ return true;
+ bool canRemove = true;
+ for( NamespaceDecl::decl_iterator it = declaration->decls_begin();
+ it != declaration->decls_end();
+ ++it )
+ {
+ if( *it != NULL )
+ {
+ if( !tryRemoveStringForwardDecl( *it ))
+ canRemove = false;
+ }
+ }
+ if( canRemove ) // contained only forward decls that we removed
+ removeText( declaration->getSourceRange(), RemoveLineIfEmpty );
+ return true;
+ }
+
+bool RemoveForwardStringDecl::tryRemoveStringForwardDecl( const Decl* decl )
+ {
+ const CXXRecordDecl* classdecl = dyn_cast< CXXRecordDecl >( decl );
+ if( classdecl == NULL )
+ return false;
+ if( !classdecl->isFreeStanding() || classdecl->isCompleteDefinition())
+ return false; // not a simple forward declaration
+ if( classdecl->getName() == "OString" || classdecl->getName() == "OUString"
+ || classdecl->getName() == "OStringBuffer" || classdecl->getName() == "OUStringBuffer"
+ || classdecl->getName() == "OStringHash" || classdecl->getName() == "OUStringHash"
+ || classdecl->getName() == "OStringLiteral" || classdecl->getName() == "OUStringLiteral" )
+ {
+ removeText( SourceRange( classdecl->getOuterLocStart(), classdecl->getLocEnd()),
+ RemoveLineIfEmpty | RemoveWholeStatement );
+ return true;
+ }
+ return false;
+ }
+
+static Plugin::Registration< RemoveForwardStringDecl > X( "removeforwardstringdecl" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/removeforwardstringdecl.hxx b/compilerplugins/clang/store/removeforwardstringdecl.hxx
new file mode 100644
index 0000000000..bedd2c5343
--- /dev/null
+++ b/compilerplugins/clang/store/removeforwardstringdecl.hxx
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+class RemoveForwardStringDecl
+ : public loplugin::FilteringRewritePlugin< RemoveForwardStringDecl >
+ {
+ public:
+ explicit RemoveForwardStringDecl( CompilerInstance& compiler, Rewriter& rewriter );
+ virtual void run() override;
+ bool VisitNamespaceDecl( const NamespaceDecl* declaration );
+ private:
+ bool tryRemoveStringForwardDecl( const Decl* decl );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/removevirtuals.cxx b/compilerplugins/clang/store/removevirtuals.cxx
new file mode 100644
index 0000000000..1dc98304d2
--- /dev/null
+++ b/compilerplugins/clang/store/removevirtuals.cxx
@@ -0,0 +1,150 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include "plugin.hxx"
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <cstring>
+
+/**
+ This is intended to be run as the second stage of the "unnecessaryvirtuals" clang plugin.
+*/
+
+namespace {
+
+class RemoveVirtuals:
+ public loplugin::FilteringRewritePlugin<RemoveVirtuals>
+{
+public:
+ explicit RemoveVirtuals(InstantiationData const & data);
+ ~RemoveVirtuals();
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXMethodDecl( const CXXMethodDecl* var );
+private:
+ // I use a brute-force approach - mmap the results file and do a linear search on it
+ // It works surprisingly well, because the file is small enough to fit into L2 cache on modern CPU's
+ size_t mmapFilesize;
+ int mmapFD;
+ char* mmappedData;
+};
+
+size_t getFilesize(const char* filename)
+{
+ struct stat st;
+ stat(filename, &st);
+ return st.st_size;
+}
+
+RemoveVirtuals::RemoveVirtuals(InstantiationData const & data): FilteringRewritePlugin(data)
+{
+ static const char sInputFile[] = SRCDIR "/result.txt";
+ mmapFilesize = getFilesize(sInputFile);
+ //Open file
+ mmapFD = open(sInputFile, O_RDONLY, 0);
+ assert(mmapFD != -1);
+ //Execute mmap
+ mmappedData = static_cast<char*>(mmap(NULL, mmapFilesize, PROT_READ, MAP_PRIVATE | MAP_POPULATE, mmapFD, 0));
+ assert(mmappedData != NULL);
+}
+
+RemoveVirtuals::~RemoveVirtuals()
+{
+ //Cleanup
+ int rc = munmap(mmappedData, mmapFilesize);
+ assert(rc == 0);
+ close(mmapFD);
+}
+
+std::string niceName(const CXXMethodDecl* functionDecl)
+{
+ std::string s =
+ functionDecl->getParent()->getQualifiedNameAsString() + "::"
+ + functionDecl->getReturnType().getAsString() + "-"
+ + functionDecl->getNameAsString() + "(";
+ for (const ParmVarDecl *pParmVarDecl : functionDecl->params()) {
+ s += pParmVarDecl->getType().getAsString();
+ s += ",";
+ }
+ s += ")";
+ if (functionDecl->isConst()) {
+ s += "const";
+ }
+ return s;
+}
+
+bool RemoveVirtuals::VisitCXXMethodDecl( const CXXMethodDecl* functionDecl )
+{
+ if (rewriter == nullptr) {
+ return true;
+ }
+ if (ignoreLocation(functionDecl)) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl)) {
+ return true;
+ }
+
+ // don't mess with templates
+ if (functionDecl->getParent()->getDescribedClassTemplate() != nullptr) {
+ return true;
+ }
+ if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) {
+ return true;
+ }
+
+ if (!functionDecl->isVirtualAsWritten()) {
+ return true;
+ }
+ std::string aNiceName = "\n" + niceName(functionDecl) + "\n";
+ const char *aNiceNameStr = aNiceName.c_str();
+ char* found = std::search(mmappedData, mmappedData + mmapFilesize, aNiceNameStr, aNiceNameStr + strlen(aNiceNameStr));
+ if(!(found < mmappedData + mmapFilesize)) {
+ return true;
+ }
+ if (functionDecl->isPure()) {
+ if (!removeText(functionDecl->getSourceRange())) {
+ report(
+ DiagnosticsEngine::Warning,
+ "Could not remove unused pure virtual method",
+ functionDecl->getLocStart())
+ << functionDecl->getSourceRange();
+ }
+ } else {
+ std::string aOrigText = rewriter->getRewrittenText(functionDecl->getSourceRange());
+ size_t iVirtualTokenIndex = aOrigText.find_first_of("virtual ");
+ if (iVirtualTokenIndex == std::string::npos) {
+ return true;
+ }
+ if (!replaceText(functionDecl->getSourceRange(), aOrigText.replace(iVirtualTokenIndex, strlen("virtual "), ""))) {
+ report(
+ DiagnosticsEngine::Warning,
+ "Could not remove virtual qualifier from method",
+ functionDecl->getLocStart())
+ << functionDecl->getSourceRange();
+ }
+ }
+ return true;
+}
+
+
+loplugin::Plugin::Registration< RemoveVirtuals > X("removevirtuals", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/returnbyref.cxx b/compilerplugins/clang/store/returnbyref.cxx
new file mode 100644
index 0000000000..d4049481a4
--- /dev/null
+++ b/compilerplugins/clang/store/returnbyref.cxx
@@ -0,0 +1,138 @@
+/* -*- 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 <string>
+#include <set>
+
+#include "plugin.hxx"
+
+// Find places where we are returning a pointer to something, where we can be returning a reference.
+// e.g.
+// class A {
+// struct X x;
+// public:
+// X* getX() { return &x; }
+// }
+// which can be:
+// X& getX() { return x; }
+
+namespace {
+
+class ReturnByRef:
+ public loplugin::FilteringPlugin<ReturnByRef>
+{
+public:
+ explicit ReturnByRef(InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXMethodDecl(const CXXMethodDecl * decl);
+private:
+ std::string getFilename(SourceLocation loc);
+};
+
+bool ReturnByRef::VisitCXXMethodDecl(const CXXMethodDecl * functionDecl) {
+ if (ignoreLocation(functionDecl)) {
+ return true;
+ }
+ if (functionDecl->isVirtual()) {
+ return true;
+ }
+ if (!functionDecl->isInstance()) {
+ return true;
+ }
+ if (!functionDecl->hasBody()) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl)) {
+ return true;
+ }
+ QualType t1 { functionDecl->getReturnType() };
+ if (!t1->isPointerType()) {
+ return true;
+ }
+ // Ignore stuff like:
+ // operator vcl::Window *(){ return &m_rWindow; }
+ if (dyn_cast< CXXConversionDecl >( functionDecl ) != nullptr) {
+ return true;
+ }
+
+ std::string aFilename = getFilename(functionDecl->getCanonicalDecl()->getLocStart());
+ if (aFilename == SRCDIR "/include/o3tl/cow_wrapper.hxx")
+ {
+ return true;
+ }
+ if ( functionDecl->getNameAsString() == "operator->") {
+ return true;
+ }
+ std::string aFunctionName = functionDecl->getQualifiedNameAsString();
+ if (aFunctionName == "SbxValue::data") {
+ return true;
+ }
+ /*
+ std::string aParentName = functionDecl->getParent()->getQualifiedNameAsString();
+ std::string fqn = aParentName + "::" + functionDecl->getNameAsString();
+ if (aFilename == "TextCharAttribList::GetAttrib") {
+ return true;
+ }*/
+
+ /*
+ The AST here looks like:
+ -CompoundStmt
+ `-ReturnStmt
+ `-UnaryOperator
+ */
+
+ const CompoundStmt* compoundStmt = dyn_cast< CompoundStmt >( functionDecl->getBody() );
+ if (compoundStmt == nullptr || compoundStmt->body_begin() == compoundStmt->body_end()) {
+ return true;
+ }
+ const ReturnStmt* returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->child_begin());
+ if (returnStmt == nullptr) {
+ return true;
+ }
+
+ const Stmt* nextStmt = dyn_cast<Expr>(*returnStmt->child_begin())->IgnoreParens();
+ const UnaryOperator* unaryOperator = dyn_cast<UnaryOperator>(nextStmt);
+ if (unaryOperator == nullptr || unaryOperator->getOpcode() != UO_AddrOf) {
+ return true;
+ }
+nextStmt->dump();
+ report(
+ DiagnosticsEngine::Warning,
+ "rather return by reference ",
+ functionDecl->getSourceRange().getBegin())
+ << functionDecl->getSourceRange();
+
+ // display the location of the class member declaration so I don't have to search for it by hand
+ auto otherLoc = functionDecl->getCanonicalDecl()->getSourceRange().getBegin();
+ if (otherLoc != functionDecl->getSourceRange().getBegin())
+ {
+ report(
+ DiagnosticsEngine::Note,
+ "rather return by reference",
+ functionDecl->getCanonicalDecl()->getSourceRange().getBegin())
+ << functionDecl->getCanonicalDecl()->getSourceRange();
+ }
+
+ return true;
+}
+
+std::string ReturnByRef::getFilename(SourceLocation loc)
+{
+ SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(loc);
+ return getFilenameOfLocation(spellingLocation);
+}
+
+loplugin::Plugin::Registration< ReturnByRef > X("returnbyref");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/returnunique.cxx b/compilerplugins/clang/store/returnunique.cxx
new file mode 100644
index 0000000000..913c043a47
--- /dev/null
+++ b/compilerplugins/clang/store/returnunique.cxx
@@ -0,0 +1,83 @@
+/* -*- 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 places where a std::unique_ptr is release()'ed and returned as a raw
+// pointer. Some occurrences of that might better be rewritten to return the
+// unique_ptr is returned directly. (But other occurrences might be fine the
+// way they are, hence place this plugin into store/).
+
+#include <memory>
+#include "plugin.hxx"
+
+namespace {
+
+class ReturnUnique:
+ public loplugin::FilteringPlugin<ReturnUnique>
+{
+public:
+ explicit ReturnUnique(InstantiationData const & data): FilteringPlugin(data) {}
+
+ void run() override {
+ if (compiler.getLangOpts().CPlusPlus) {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+ }
+
+ bool VisitReturnStmt(ReturnStmt const * stmt);
+};
+
+bool ReturnUnique::VisitReturnStmt(ReturnStmt const * stmt) {
+ if (ignoreLocation(stmt)) {
+ return true;
+ }
+ auto const e1 = stmt->getRetValue();
+ if (e1 == nullptr) {
+ return true;
+ }
+ auto const e2 = dyn_cast<CXXMemberCallExpr>(e1->IgnoreParenImpCasts());
+ if (e2 == nullptr) {
+ return true;
+ }
+ auto const d1 = e2->getMethodDecl();
+ if (d1 == nullptr) { // call via ptr to member
+ return true;
+ }
+ auto const d2 = d1->getParent();
+ assert(d2 != nullptr);
+ assert(d2->getParent() != nullptr);
+ auto const d3 = dyn_cast<NamespaceDecl>(d2->getParent());
+ if (d3 == nullptr
+ /* || dyn_cast<TranslationUnitDecl>(d3->getParent()) == nullptr */)
+ {
+ return true;
+ }
+ auto const id3 = d3->getIdentifier();
+ if (id3 == nullptr /* || id3->getName() != "std" */) {
+ return true;
+ }
+ auto const id2 = d2->getIdentifier();
+ if (id2 == nullptr || id2->getName() != "unique_ptr") {
+ return true;
+ }
+ auto const id1 = d1->getIdentifier();
+ if (id1 == nullptr || id1->getName() != "release") {
+ return true;
+ }
+ report(
+ DiagnosticsEngine::Warning, "return std::unique_ptr::release",
+ e2->getLocStart())
+ << stmt->getSourceRange();
+ return true;
+}
+
+loplugin::Plugin::Registration<ReturnUnique> X("returnunique");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/revisibility.cxx b/compilerplugins/clang/store/revisibility.cxx
new file mode 100644
index 0000000000..727d7094a0
--- /dev/null
+++ b/compilerplugins/clang/store/revisibility.cxx
@@ -0,0 +1,89 @@
+/* -*- 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"
+
+// Find function declarations that redundantly re-specify a visibility attribute
+// (via SAL_DLLPUBLIC_EXPORT etc.) that was already specified with a previous
+// declaration of that function. But MSVC wants consistency of __declspec
+// across friend declarations, so just ignore those for now.
+
+namespace {
+
+bool hasExplicitVisibilityAttr(Decl const * decl) {
+ VisibilityAttr const * attr = decl->getAttr<VisibilityAttr>();
+ return attr != nullptr && !attr->isInherited();
+}
+
+bool isFriendDecl(Decl const * decl) {
+ return decl->getFriendObjectKind() != Decl::FOK_None;
+}
+
+class ReVisibility:
+ public loplugin::FilteringPlugin<ReVisibility>
+{
+public:
+ explicit ReVisibility(InstantiationData const & data): FilteringPlugin(data) {}
+
+ void run() override
+ { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitFunctionDecl(FunctionDecl const * decl);
+};
+
+bool ReVisibility::VisitFunctionDecl(FunctionDecl const * decl) {
+ if (!ignoreLocation(decl) && hasExplicitVisibilityAttr(decl)
+ && !isFriendDecl(decl))
+ {
+ Decl const * first = nullptr;
+ for (Decl const * p = decl;;) {
+ p = p->getPreviousDecl();
+ if (p == nullptr) {
+ break;
+ }
+ first = p;
+ if (hasExplicitVisibilityAttr(p) && !isFriendDecl(p)) {
+ report(
+ DiagnosticsEngine::Warning,
+ "Redundant visibility re-declaration",
+ decl->getAttr<VisibilityAttr>()->getLocation())
+ << decl->getAttr<VisibilityAttr>()->getRange();
+ report(
+ DiagnosticsEngine::Note,
+ "Previous visibility declaration is here",
+ p->getAttr<VisibilityAttr>()->getLocation())
+ << p->getAttr<VisibilityAttr>()->getRange();
+ return true;
+ }
+ }
+ if (decl->isThisDeclarationADefinition() && first != nullptr
+ && !(getFilenameOfLocation(
+ compiler.getSourceManager().getSpellingLoc(
+ decl->getLocation()))
+ .startswith(SRCDIR "/libreofficekit/")))
+ {
+ report(
+ DiagnosticsEngine::Warning,
+ "Visibility declaration on definition, not first declaration",
+ decl->getAttr<VisibilityAttr>()->getLocation())
+ << decl->getAttr<VisibilityAttr>()->getRange();
+ report(
+ DiagnosticsEngine::Note, "First declaration is here",
+ first->getLocation())
+ << first->getSourceRange();
+ }
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<ReVisibility> X("revisibility");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/rtlconstasciimacro.cxx b/compilerplugins/clang/store/rtlconstasciimacro.cxx
new file mode 100644
index 0000000000..c930fbfd1c
--- /dev/null
+++ b/compilerplugins/clang/store/rtlconstasciimacro.cxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+/*
+This is a rewriter.
+
+Remove uses of the macro RTL_CONSTASCII_USTRINGPARAM. One run is for one
+specific use (see below), modify source to remove other uses.
+*/
+
+#include "plugin.hxx"
+
+#include <clang/Lex/Preprocessor.h>
+
+namespace loplugin
+{
+
+class RtlConstAsciiMacro
+ : public loplugin::FilteringRewritePlugin< RtlConstAsciiMacro >
+ , public PPCallbacks
+ {
+ public:
+ explicit RtlConstAsciiMacro( const InstantiationData& data );
+ virtual void run() override;
+ bool VisitCXXConstructExpr( CXXConstructExpr* expr );
+ bool VisitCXXTemporaryObjectExpr( CXXTemporaryObjectExpr* expr );
+ bool VisitStringLiteral( const StringLiteral* literal );
+ virtual void MacroExpands( const Token& macro, const MacroDirective* directive,
+ SourceRange range, const MacroArgs* args ) override;
+ enum { isPPCallback = true };
+ private:
+ map< SourceLocation, SourceLocation > expansions; // start location -> end location
+ bool searchingForString;
+ bool suitableString;
+ };
+
+RtlConstAsciiMacro::RtlConstAsciiMacro( const InstantiationData& data )
+ : FilteringRewritePlugin( data )
+ , searchingForString( false )
+ {
+ compiler.getPreprocessor().addPPCallbacks( this );
+ }
+
+void RtlConstAsciiMacro::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+void RtlConstAsciiMacro::MacroExpands( const Token& macro, const MacroDirective*,
+ SourceRange range, const MacroArgs* )
+ {
+ if( macro.getIdentifierInfo()->getName() != "RTL_CONSTASCII_USTRINGPARAM" )
+ return;
+ expansions[ range.getBegin() ] = range.getEnd();
+ }
+
+/* Remove use with the following ctor:
+ OUString( const char * value, sal_Int32 length,
+ rtl_TextEncoding encoding,
+ sal_uInt32 convertFlags = OSTRING_TO_OUSTRING_CVTFLAGS )
+ This means searching for CXXConstructExpr.
+ For removal when used with functions it should check e.g. for CallExpr.
+*/
+bool RtlConstAsciiMacro::VisitCXXConstructExpr( CXXConstructExpr* expr )
+ {
+ if( ignoreLocation( expr ))
+ return true;
+ if( expr->getNumArgs() != 4 )
+ return true;
+ // The last argument should be the default one when the macro is used.
+ if( dyn_cast< CXXDefaultArgExpr >( expr->getArg( 3 )) == NULL )
+ return true;
+ if( expr->getConstructor()->getQualifiedNameAsString() != "rtl::OUString::OUString" )
+ return true;
+ const SourceManager& src = compiler.getSourceManager();
+ SourceLocation start = src.getExpansionLoc( expr->getArg( 0 )->getLocStart());
+ // Macro fills in the first 3 arguments, so they must all come from the same expansion.
+ if( start != src.getExpansionLoc( expr->getArg( 2 )->getLocEnd()))
+ return true;
+ if( expansions.find( start ) == expansions.end())
+ return true;
+ SourceLocation end = expansions[ start ];
+ // Remove the location, since sometimes the same code may be processed more than once
+ // (e.g. non-trivial default arguments).
+ expansions.erase( start );
+ // Check if the string argument to the macro is suitable.
+ searchingForString = true;
+ suitableString = false;
+ TraverseStmt( expr->getArg( 0 ));
+ searchingForString = false;
+ if( !suitableString )
+ return true;
+ // Search for '(' (don't just remove a given length to handle possible whitespace).
+ const char* text = compiler.getSourceManager().getCharacterData( start );
+ const char* pos = text;
+ while( *pos != '(' )
+ ++pos;
+ ++pos;
+ if( text[ -1 ] == ' ' && *pos == ' ' )
+ ++pos; // do not leave two spaces
+ removeText( start, pos - text, RemoveLineIfEmpty );
+ const char* textend = compiler.getSourceManager().getCharacterData( end );
+ if( textend[ -1 ] == ' ' && textend[ 1 ] == ' ' )
+ removeText( end, 2, RemoveLineIfEmpty ); // Remove ') '.
+ else
+ removeText( end, 1, RemoveLineIfEmpty ); // Remove ')'.
+ return true;
+ }
+
+bool RtlConstAsciiMacro::VisitCXXTemporaryObjectExpr( CXXTemporaryObjectExpr* expr )
+ {
+ return VisitCXXConstructExpr( expr );
+ }
+
+bool RtlConstAsciiMacro::VisitStringLiteral( const StringLiteral* literal )
+ {
+ if( !searchingForString )
+ return true;
+ if( suitableString ) // two string literals?
+ {
+ report( DiagnosticsEngine::Warning, "cannot analyze RTL_CONSTASCII_USTRINGPARAM (plugin needs fixing)" )
+ << literal->getSourceRange();
+ return true;
+ }
+ if( !literal->isAscii()) // ignore
+ return true;
+ if( !literal->containsNonAsciiOrNull())
+ suitableString = true;
+ return true;
+ }
+
+static Plugin::Registration< RtlConstAsciiMacro > X( "rtlconstasciimacro" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/sequentialassign.cxx b/compilerplugins/clang/store/sequentialassign.cxx
new file mode 100644
index 0000000000..01172df17e
--- /dev/null
+++ b/compilerplugins/clang/store/sequentialassign.cxx
@@ -0,0 +1,327 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "plugin.hxx"
+#include "check.hxx"
+#include "config_clang.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/StmtVisitor.h"
+
+/**
+ This is a kind of simplified dead-store analysis.
+
+ We are looking for patterns like:
+ Foo x = a;
+ x = b;
+ which can be simplified to
+ x = b
+
+ or
+ Foo x = a;
+ x = f(x)
+ which can be simplified to
+ Foo x = f(a)
+
+ TODO Improve this plugin to make it safer. We should really be checking the following
+ conditions inside the RHS of the second statement:
+ If the variable is having it's address taken, or a non-const method called on it,
+ on passed by non-const-ref.
+*/
+
+namespace
+{
+//static bool startswith(const std::string& rStr, const char* pSubStr) {
+// return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
+//}
+class SequentialAssign : public loplugin::FilteringPlugin<SequentialAssign>
+{
+public:
+ explicit SequentialAssign(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ std::string fn(handler.getMainFileName());
+ loplugin::normalizeDotDotInFilePath(fn);
+ // places where the existing code style just looks better
+ // TODO lots of these would be unnecessary if I taught the plugin
+ // to ignore vars which are assigned to repeatedly
+ if (fn == SRCDIR "/vcl/source/helper/commandinfoprovider.cxx"
+ || fn == SRCDIR "/basegfx/source/polygon/b2dpolygonclipper.cxx"
+ || fn == SRCDIR "/i18nlangtag/source/isolang/insys.cxx"
+ || fn == SRCDIR "/vcl/unx/generic/fontmanager/fontconfig.cxx"
+ || fn == SRCDIR "/svtools/source/filter/exportdialog.cxx"
+ || fn == SRCDIR "/svtools/source/control/ruler.cxx"
+ || fn == SRCDIR "/basic/qa/cppunit/test_scanner.cxx"
+ || fn == SRCDIR "/basic/source/uno/namecont.cxx"
+ || fn == SRCDIR "/test/source/sheet/xnamedrange.cxx"
+ || fn == SRCDIR "/i18npool/qa/cppunit/test_breakiterator.cxx"
+ || fn == SRCDIR "/i18npool/source/localedata/LocaleNode.cxx"
+ || fn == SRCDIR "/i18npool/source/transliteration/transliteration_Ignore.cxx"
+ || fn == SRCDIR "/i18npool/qa/cppunit/test_textsearch.cxx"
+ || fn == SRCDIR "/framework/source/jobs/jobdata.cxx"
+ || fn == SRCDIR "/framework/source/services/pathsettings.cxx"
+ || fn == SRCDIR "/xmloff/source/chart/SchXMLTools.cxx"
+ || fn == SRCDIR "/svx/source/tbxctrls/Palette.cxx"
+ || fn == SRCDIR "/svx/source/sdr/contact/objectcontactofpageview.cxx"
+ || fn == SRCDIR "/svx/source/form/fmservs.cxx"
+ || fn == SRCDIR "/svx/source/svdraw/svdograf.cxx"
+ || fn == SRCDIR "/svx/source/accessibility/AccessibleShape.cxx"
+ || fn == SRCDIR "/svx/source/fmcomp/fmgridcl.cxx"
+ || fn == SRCDIR "/chart2/source/tools/CharacterProperties.cxx"
+ || fn == SRCDIR "/svx/source/dialog/dialcontrol.cxx"
+ || fn == SRCDIR "/connectivity/source/commontools/TTableHelper.cxx"
+ || fn == SRCDIR "/svx/source/dialog/_bmpmask.cxx"
+ || fn == SRCDIR "/media/noel/disk2/libo4/cui/source/dialogs/SignSignatureLineDialog.cxx"
+ || fn == SRCDIR "/filter/source/msfilter/msdffimp.cxx"
+ || fn == SRCDIR "/cui/source/dialogs/SignSignatureLineDialog.cxx"
+ || fn == SRCDIR "/cui/source/dialogs/screenshotannotationdlg.cxx"
+ || fn == SRCDIR "/cui/source/options/optupdt.cxx"
+ || fn == SRCDIR "/dbaccess/source/ui/querydesign/querycontroller.cxx"
+ || fn == SRCDIR "/dbaccess/source/ui/dlg/DbAdminImpl.cxx"
+ || fn == SRCDIR "/dbaccess/source/ui/querydesign/JoinController.cxx"
+ || fn == SRCDIR "/dbaccess/source/ui/misc/TokenWriter.cxx"
+ || fn == SRCDIR "/dbaccess/source/ui/misc/TokenWriter.cxx"
+ || fn == SRCDIR "/dbaccess/source/ui/misc/dbsubcomponentcontroller.cxx"
+ || fn == SRCDIR "/dbaccess/source/core/api/querycomposer.cxx"
+ || fn == SRCDIR "/desktop/source/lib/init.cxx"
+ || fn == SRCDIR "/lotuswordpro/source/filter/lwpfribmark.cxx"
+ || fn == SRCDIR "/tools/qa/cppunit/test_color.cxx"
+ || fn == SRCDIR "/sc/qa/unit/ucalc.cxx"
+ || fn == SRCDIR "/sc/source/ui/view/printfun.cxx"
+ || fn == SRCDIR "/sc/source/ui/view/preview.cxx"
+ || fn == SRCDIR "/sw/source/core/doc/tblafmt.cxx"
+ || fn == SRCDIR "/sw/source/core/draw/dflyobj.cxx"
+ || fn == SRCDIR "/sw/source/core/doc/DocumentDrawModelManager.cxx"
+ || fn == SRCDIR "/sw/source/core/edit/edfcol.cxx"
+ || fn == SRCDIR "/sw/source/filter/ww8/ww8toolbar.cxx"
+ || fn == SRCDIR "/sw/source/ui/fldui/fldvar.cxx"
+ || fn == SRCDIR "/sw/source/filter/ww8/ww8atr.cxx"
+ || fn == SRCDIR "/sd/source/ui/accessibility/AccessiblePageShape.cxx"
+ || fn == SRCDIR "/xmlsecurity/source/xmlsec/nss/nssinitializer.cxx"
+ || fn == SRCDIR "/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx"
+ || fn == SRCDIR "/sd/source/ui/framework/configuration/ResourceId.cxx"
+ || fn == SRCDIR "/sd/source/filter/html/htmlex.cxx"
+ || fn == SRCDIR "/starmath/source/cfgitem.cxx"
+ || fn == SRCDIR "/ucb/source/ucp/ftp/ftpurl.cxx"
+ || fn == SRCDIR "/starmath/source/node.cxx"
+ || fn == SRCDIR "/starmath/source/starmathdatabase.cxx"
+ || fn == SRCDIR "/ucb/source/ucp/cmis/certvalidation_handler.cxx"
+ || fn == SRCDIR "/reportdesign/source/ui/inspection/GeometryHandler.cxx"
+ || fn == SRCDIR "/reportdesign/source/core/api/ReportDefinition.cxx"
+ || fn == SRCDIR "/test/source/table/tablerow.cxx"
+ || fn == SRCDIR "/basegfx/test/B2DHomMatrixTest.cxx"
+ || fn == SRCDIR "/comphelper/qa/unit/base64_test.cxx"
+ || fn == SRCDIR "/testtools/source/bridgetest/bridgetest.cxx"
+ || fn == SRCDIR "/comphelper/qa/string/test_string.cxx"
+ || fn == SRCDIR "/cppu/qa/test_unotype.cxx"
+ || fn == SRCDIR "/cppu/qa/cppumaker/test_cppumaker.cxx"
+ || fn == SRCDIR "/o3tl/qa/test-lru_map.cxx" || fn == SRCDIR "/svl/qa/unit/svl.cxx"
+ || fn == SRCDIR "/chart2/qa/extras/PivotChartTest.cxx"
+ || fn == SRCDIR "/chart2/qa/extras/chart2export.cxx"
+ || fn == SRCDIR "/writerfilter/qa/cppunittests/misc/misc.cxx"
+ || fn == SRCDIR "/sw/qa/extras/ww8export/ww8export.cxx"
+ || fn == SRCDIR "/sw/qa/extras/uiwriter/uiwriter.cxx")
+ return;
+ // inside ifdef
+ if (fn == SRCDIR "/vcl/source/filter/png/pngread.cxx"
+ || fn == SRCDIR "/vcl/source/window/syschild.cxx"
+ || fn == SRCDIR "/sal/osl/unx/security.cxx"
+ || fn == SRCDIR "/vcl/source/filter/png/pngwrite.cxx"
+ || fn == SRCDIR "/svtools/source/control/inettbc.cxx"
+ || fn == SRCDIR "/canvas/source/cairo/cairo_textlayout.cxx"
+ || fn == SRCDIR "/sal/qa/osl/file/osl_File.cxx")
+ return;
+ // taking address of variable
+ if (fn == SRCDIR "/vcl/unx/generic/dtrans/X11_selection.cxx")
+ return;
+ // other
+ if (fn == SRCDIR "/sc/source/core/tool/scmatrix.cxx"
+ || fn == SRCDIR "/sal/qa/rtl/oustringbuffer/test_oustringbuffer_assign.cxx")
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitCompoundStmt(CompoundStmt const*);
+
+private:
+ const VarDecl* findSimpleAssign(Stmt const*);
+ bool isSimpleRHS(Expr const*);
+ Expr const* ignore(Expr const*);
+ void checkForSecondAssign(Stmt const* stmt, VarDecl const* varDecl);
+};
+
+bool SequentialAssign::VisitCompoundStmt(CompoundStmt const* compoundStmt)
+{
+ if (ignoreLocation(compoundStmt))
+ return true;
+
+ auto it = compoundStmt->body_begin();
+ while (true)
+ {
+ if (it == compoundStmt->body_end())
+ break;
+ auto firstStmt = *it;
+ const VarDecl* foundVars = findSimpleAssign(firstStmt);
+ // reference types have slightly weird behaviour
+ if (!foundVars || foundVars->getType()->isReferenceType())
+ {
+ ++it;
+ continue;
+ }
+ ++it;
+ if (it == compoundStmt->body_end())
+ break;
+ checkForSecondAssign(*it, foundVars);
+ }
+
+ return true;
+}
+
+void SequentialAssign::checkForSecondAssign(Stmt const* stmt, VarDecl const* varDecl)
+{
+ if (auto exprCleanup = dyn_cast<ExprWithCleanups>(stmt))
+ stmt = exprCleanup->getSubExpr();
+
+ if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(stmt))
+ {
+ if (operatorCall->getOperator() == OO_Equal)
+ {
+ if (auto declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(operatorCall->getArg(0))))
+ if (declRefExprLHS->getDecl() == varDecl)
+ {
+ report(DiagnosticsEngine::Warning,
+ "simplify by merging with the preceding assignment", stmt->getBeginLoc())
+ << stmt->getSourceRange();
+ }
+ }
+ return;
+ }
+
+ if (auto binaryOp = dyn_cast<BinaryOperator>(stmt))
+ {
+ if (binaryOp->getOpcode() == BO_Assign)
+ {
+ if (auto declRefExpr = dyn_cast<DeclRefExpr>(ignore(binaryOp->getLHS())))
+ if (declRefExpr->getDecl() == varDecl)
+ {
+ report(DiagnosticsEngine::Warning,
+ "simplify by merging with the preceding assignment", stmt->getBeginLoc())
+ << stmt->getSourceRange();
+ }
+ }
+ }
+}
+
+const VarDecl* SequentialAssign::findSimpleAssign(Stmt const* stmt)
+{
+ if (auto declStmt = dyn_cast<DeclStmt>(stmt))
+ if (declStmt->isSingleDecl())
+ if (auto varDeclLHS = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl()))
+ {
+ if (varDeclLHS->getStorageDuration() == SD_Static)
+ return nullptr;
+ // if it's call-style init (e.g. OUString s("xxx")), we only treat
+ // it as simple if it only contains a variable in the call
+ // (e.g. OUString s(x))
+ if (varDeclLHS->getInitStyle() == VarDecl::InitializationStyle::CallInit)
+ {
+ auto cxxConstructExpr
+ = dyn_cast<CXXConstructExpr>(ignore(varDeclLHS->getInit()));
+ if (cxxConstructExpr)
+ {
+ if (cxxConstructExpr->getNumArgs() == 0)
+ return varDeclLHS;
+ if (cxxConstructExpr->getNumArgs() > 1)
+ return nullptr;
+ if (!isa<DeclRefExpr>(ignore(cxxConstructExpr->getArg(0))))
+ return nullptr;
+ }
+ }
+ if (auto init = varDeclLHS->getInit())
+ if (isSimpleRHS(init))
+ return varDeclLHS;
+ }
+ if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(stmt))
+ if (operatorCall->getOperator() == OO_Equal)
+ if (auto declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(operatorCall->getArg(0))))
+ if (auto varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl()))
+ if (isSimpleRHS(operatorCall->getArg(1)))
+ return varDeclLHS;
+ if (auto binaryOp = dyn_cast<BinaryOperator>(stmt))
+ if (binaryOp->getOpcode() == BO_Assign)
+ if (auto declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(binaryOp->getLHS())))
+ if (auto varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl()))
+ if (isSimpleRHS(binaryOp->getRHS()))
+ return varDeclLHS;
+ return nullptr;
+}
+
+/**
+ Does the first statement have a relatively simply RHS we can inline into the second statement?
+*/
+bool SequentialAssign::isSimpleRHS(Expr const* expr)
+{
+ expr = ignore(expr);
+
+ // code like
+ // Point aCurPos = rGlyphs
+ // always has a CXXConstructExpr wrapping the RHS
+ if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(expr))
+ if (cxxConstructExpr->getNumArgs() == 1)
+ expr = ignore(cxxConstructExpr->getArg(0));
+
+ if (!expr->isValueDependent()
+ && expr->isConstantInitializer(compiler.getASTContext(), false /*ForRef*/))
+ return true;
+ if (isa<CXXMemberCallExpr>(expr))
+ return false;
+ if (isa<CXXOperatorCallExpr>(expr))
+ return false;
+ if (isa<UserDefinedLiteral>(expr))
+ return true;
+ if (isa<CallExpr>(expr))
+ return false;
+ if (isa<CastExpr>(expr))
+ return false;
+ if (isa<ArraySubscriptExpr>(expr))
+ return false;
+ if (isa<BinaryOperator>(expr))
+ return false;
+ if (isa<ConditionalOperator>(expr))
+ return false;
+ if (isa<UnaryOperator>(expr))
+ return false;
+ if (isa<CXXNewExpr>(expr))
+ return false;
+ if (auto memberExpr = dyn_cast<MemberExpr>(expr))
+ return isSimpleRHS(memberExpr->getBase());
+
+ return true;
+}
+
+Expr const* SequentialAssign::ignore(Expr const* expr)
+{
+ return expr->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
+}
+
+// Off by default because of safety concerns, see TODO at top
+loplugin::Plugin::Registration<SequentialAssign> X("sequentialassign", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/sfxitemsetrewrite.cxx b/compilerplugins/clang/store/sfxitemsetrewrite.cxx
new file mode 100644
index 0000000000..e1fa52209b
--- /dev/null
+++ b/compilerplugins/clang/store/sfxitemsetrewrite.cxx
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <algorithm>
+#include <sstream>
+#include <tuple>
+#include <vector>
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+namespace {
+
+bool gap(APSInt n1, APSInt n2) { return n1 < n2 && n2 - n1 > 1; }
+
+class Visitor final:
+ public loplugin::FilteringRewritePlugin<Visitor>
+{
+public:
+ explicit Visitor(InstantiationData const & data): FilteringRewritePlugin(data) {}
+
+ bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto const ctor = expr->getConstructor();
+ if (!loplugin::DeclCheck(ctor->getParent()).Class("SfxItemSet")
+ .GlobalNamespace())
+ {
+ return true;
+ }
+ auto const numParams = ctor->getNumParams();
+ auto const variadic = ctor->isVariadic();
+ if (!(((numParams == 3 && !variadic) || (numParams == 4 && variadic))
+ && (loplugin::TypeCheck(ctor->getParamDecl(0)->getType())
+ .LvalueReference().Class("SfxItemPool").GlobalNamespace())))
+ {
+ return true;
+ }
+ auto const numArgs = expr->getNumArgs();
+ if (numArgs < 3) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected SfxItemPool constructor call with less than three"
+ " arguments"),
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return true;
+ }
+ rewrite(expr, variadic, false, numArgs, expr->getArgs());
+ return true;
+ }
+
+ bool VisitCallExpr(CallExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto const dre = dyn_cast<DeclRefExpr>(
+ expr->getCallee()->IgnoreParenImpCasts());
+ if (dre == nullptr
+ || !(loplugin::DeclCheck(dre->getDecl()).Function("make_unique")
+ .Namespace("o3tl").GlobalNamespace()))
+ {
+ return true;
+ }
+ auto const numTArgs = dre->getNumTemplateArgs();
+ if (numTArgs == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ "unexpected o3tl::make_unique call without template arguments",
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return true;
+ }
+ auto const tArg0 = dre->getTemplateArgs()[0].getArgument();
+ if (tArg0.getKind() != TemplateArgument::Type) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected o3tl::make_unique call with non-type first"
+ " template argument"),
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return true;
+ }
+ if (!loplugin::TypeCheck(tArg0.getAsType()).Class("SfxItemSet")
+ .GlobalNamespace())
+ {
+ return true;
+ }
+ auto const numArgs = expr->getNumArgs();
+ if (numArgs < 3) {
+ return true;
+ }
+ rewrite(expr, numArgs > 3, true, numArgs, expr->getArgs());
+ return true;
+ }
+
+ bool VisitTemplateSpecializationTypeLoc(
+ TemplateSpecializationTypeLoc typeLoc)
+ {
+ auto const loc = typeLoc.getBeginLoc();
+ if (loc.isInvalid() || ignoreLocation(loc)) {
+ return true;
+ }
+ if (!loplugin::TypeCheck(typeLoc.getType()).Struct("Items")
+ .Namespace("svl").GlobalNamespace())
+ {
+ return true;
+ }
+ unsigned const numArgs = typeLoc.getNumArgs();
+ if (numArgs == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with zero template"
+ " arguments"),
+ loc)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ if (numArgs % 2 == 1) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with odd number of"
+ " template arguments"),
+ loc)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ std::vector<Range> ranges;
+ auto good = true;
+ APSInt prev;
+ for (unsigned i = 0; i != numArgs; ++i) {
+ auto const argLoc = typeLoc.getArgLoc(i);
+ auto const & arg = argLoc.getArgument();
+ APSInt v;
+ switch (arg.getKind()) {
+ case TemplateArgument::Integral:
+ v = arg.getAsIntegral();
+ break;
+ case TemplateArgument::Expression:
+ if (arg.getAsExpr()->EvaluateAsInt(v, compiler.getASTContext()))
+ {
+ break;
+ }
+ // [[fallthrough]];
+ default:
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with non-integral"
+ " template argument %0"),
+ argLoc.getLocation())
+ << (i + 1)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ if (i % 2 == 0) {
+ good = good && (i == 0 || gap(prev, v));
+ } else {
+ if (v < prev) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with template"
+ " argument %0 smaller than previous one, %1 < %2"),
+ argLoc.getLocation())
+ << (i + 1) << v.toString(10) << prev.toString(10)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ ranges.emplace_back(prev, v, (i / 2) + 1);
+ }
+ prev = v;
+ }
+ if (good) {
+ return true;
+ }
+ std::ostringstream buf1;
+ for (auto const i: ranges) {
+ buf1 << "\n ";
+ printBegin(buf1, typeLoc, i);
+ buf1 << " ... ";
+ printEnd(buf1, typeLoc, i);
+ }
+ std::sort(ranges.begin(), ranges.end());
+ std::ostringstream buf2;
+ for (auto i = ranges.begin(); i != ranges.end();) {
+ buf2 << "\n ";
+ printBegin(buf2, typeLoc, *i);
+ buf2 << " ... ";
+ auto end = *i;
+ for (;;) {
+ auto j = i + 1;
+ if (j == ranges.end() || gap(get<1>(end), get<0>(*j))) {
+ printEnd(buf2, typeLoc, end);
+ i = j;
+ break;
+ }
+ if (get<1>(*j) >= get<1>(end)) {
+ end = *j;
+ }
+ i = j;
+ }
+ }
+ report(
+ DiagnosticsEngine::Warning,
+ ("reorder svl::Items specialization template arguments from:%0\nto:"
+ "%1"),
+ loc)
+ << buf1.str() << buf2.str() << typeLoc.getSourceRange();
+ return true;
+ }
+
+private:
+ void run() override {
+ if (compiler.getLangOpts().CPlusPlus) {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+ }
+
+ SourceLocation nextToken(SourceLocation loc) {
+ return loc.getLocWithOffset(
+ Lexer::MeasureTokenLength(
+ loc, compiler.getSourceManager(), compiler.getLangOpts()));
+ }
+
+ SourceLocation atMacroExpansionStart(SourceLocation loc) {
+ while (loc.isMacroID()
+ && (compiler.getSourceManager()
+ .isAtStartOfImmediateMacroExpansion(loc, &loc)))
+ {}
+ return loc;
+ }
+
+ SourceLocation atMacroExpansionEnd(SourceLocation loc) {
+ while (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
+ loc = compiler.getSourceManager().getImmediateExpansionRange(loc)
+ .second;
+ }
+ return loc;
+ }
+
+ using Range = std::tuple<APSInt, APSInt, unsigned>;
+
+ void printSource(
+ std::ostringstream & s, TemplateSpecializationTypeLoc typeLoc, Range r,
+ bool end)
+ {
+ auto const argLoc = typeLoc.getArgLoc(
+ 2 * (get<2>(r) - 1) + (end ? 1 : 0));
+ auto const src1 = argLoc.getSourceRange();
+ auto const src2 = SourceRange(
+ atMacroExpansionStart(src1.getBegin()),
+ Lexer::getLocForEndOfToken(
+ compiler.getSourceManager().getExpansionLoc(
+ atMacroExpansionEnd(src1.getEnd())),
+ 0, compiler.getSourceManager(), compiler.getLangOpts()));
+ s << " '" << Lexer::getSourceText(
+ Lexer::getAsCharRange(
+ src2, compiler.getSourceManager(), compiler.getLangOpts()),
+ compiler.getSourceManager(), compiler.getLangOpts()).str()
+ << "'";
+ }
+
+ void printBegin(
+ std::ostringstream & s, TemplateSpecializationTypeLoc typeLoc, Range r)
+ {
+ s << get<2>(r) << "B: " << get<0>(r).toString(10);
+ printSource(s, typeLoc, r, false);
+ }
+
+ void printEnd(
+ std::ostringstream & s, TemplateSpecializationTypeLoc typeLoc, Range r)
+ {
+ s << get<2>(r) << "E: " << get<1>(r).toString(10);
+ printSource(s, typeLoc, r, true);
+ }
+
+ void rewrite(
+ Expr const * expr, bool variadic, bool forward, unsigned numArgs,
+ Expr const * const * args)
+ {
+ bool constant = true;
+ unsigned firstZero = 0;
+ for (unsigned i = 1; i != numArgs; ++i) {
+ auto const arg = args[i];
+ constant = constant
+ && arg->isCXX11ConstantExpr(compiler.getASTContext());
+ APSInt v;
+ auto const zero
+ = ((arg->EvaluateAsInt(v, compiler.getASTContext())
+ && v == 0)
+ || (variadic && i > 4
+ && arg->isNullPointerConstant(
+ compiler.getASTContext(),
+ Expr::NPC_ValueDependentIsNotNull)));
+ if (variadic) {
+ if (zero) {
+ if (firstZero == 0) {
+ if (i == 1) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected missing non-zero arguments before"
+ " first zero argument in SfxItemPool"
+ " constructor call"),
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ if (i % 2 == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected odd number of potentially non-zero"
+ " arguments before first definitely zero"
+ " argument in SfxItemPool constructor call"),
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ firstZero = i;
+ }
+ } else if (firstZero != 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected potentially non-zero argument in"
+ " SfxItemPool constructor call, following zero"
+ " argument"),
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ } else if (zero) {
+ report(
+ DiagnosticsEngine::Warning,
+ "unexpected zero argument in SfxItemPool constructor call",
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ }
+ if (variadic && firstZero == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected SfxItemPool constructor call with no detectable"
+ " zero arguments"),
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ if (rewriter != nullptr) {
+ if (!insertTextBefore(
+ atMacroExpansionStart(args[1]->getLocStart()),
+ (constant
+ ? StringRef("svl::Items<")
+ : (forward
+ ? StringRef("std::initializer_list<SfxItemSet::Pair>{{")
+ : StringRef("{{")))))
+ {
+ goto failed;
+ }
+ auto const postLoc = atMacroExpansionEnd(
+ args[numArgs - 1]->getLocEnd());
+ auto const postStr = constant ? StringRef(">{}") : StringRef("}}");
+ if (variadic) {
+ //TODO: the replaced range can contain relevant comments:
+ if (!replaceText(
+ SourceRange(
+ nextToken(
+ atMacroExpansionEnd(
+ args[firstZero - 1]->getLocEnd())),
+ postLoc),
+ postStr))
+ {
+ goto failed;
+ }
+ } else {
+ if (!insertTextAfterToken(postLoc, postStr)) {
+ goto failed;
+ }
+ }
+ if (!constant && variadic) {
+ for (unsigned i = 2; i != firstZero - 1; ++i) {
+ auto const arg = args[i];
+ if (!(i % 2 == 0
+ ? insertTextAfterToken(
+ atMacroExpansionEnd(arg->getLocEnd()), "}")
+ : insertTextBefore(
+ atMacroExpansionStart(arg->getLocStart()), "{")))
+ {
+ goto failed;
+ }
+ }
+ }
+ return;
+ }
+ failed: //TODO: undo partial changes
+ report(
+ DiagnosticsEngine::Warning,
+ ("rewrite SfxItemPool constructor call with"
+ " %select{%select{|std::initializer_list<SfxItemSet::Pair>}1"
+ "%{%{..., ...%}, ..., %{..., ...%}%}|svl::Items<...>%{%}}0 around"
+ " the %select{|leading }2%3 WID arguments%select{| and remove the"
+ " remaining %4 zero %plural{1:argument|:arguments}4}2"),
+ expr->getExprLoc())
+ << constant << forward << variadic
+ << (variadic ? firstZero - 1 : numArgs - 1) << (numArgs - firstZero)
+ << expr->getSourceRange();
+ }
+};
+
+static loplugin::Plugin::Registration<Visitor> reg("sfxitemsetrewrite",true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/compilerplugins/clang/store/shouldreturnbool.cxx b/compilerplugins/clang/store/shouldreturnbool.cxx
new file mode 100644
index 0000000000..fa1bd4cbdf
--- /dev/null
+++ b/compilerplugins/clang/store/shouldreturnbool.cxx
@@ -0,0 +1,248 @@
+/* -*- 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 <string>
+#include <set>
+#include <iostream>
+
+#include "config_clang.h"
+
+#include "check.hxx"
+#include "compat.hxx"
+#include "plugin.hxx"
+#include "functionaddress.hxx"
+
+/*
+ Look for functions that only return 1 and/or 0, which sometimes indicates that the
+ function should be returning bool.
+
+ Note that is partly a question of taste and code style, which is why this plugin is off by default.
+*/
+
+namespace
+{
+class ShouldReturnBool
+ : public loplugin::FunctionAddress<loplugin::FilteringPlugin<ShouldReturnBool>>
+{
+public:
+ explicit ShouldReturnBool(loplugin::InstantiationData const& data)
+ : FunctionAddress(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ if (!compiler.getLangOpts().CPlusPlus)
+ return;
+ StringRef fn(handler.getMainFileName());
+ // functions used as function pointers
+ if (loplugin::isSamePathname(fn, SRCDIR "/sal/rtl/alloc_cache.cxx"))
+ return;
+ // false +, slightly odd usage, but not wrong
+ if (loplugin::isSamePathname(fn, SRCDIR "/libreofficekit/qa/tilebench/tilebench.cxx"))
+ return;
+ // template magic
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/gdi/bmpfast.cxx"))
+ return;
+ // fine
+ if (loplugin::isSamePathname(fn, SRCDIR "/svl/unx/source/svdde/ddedummy.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/opengl/OpenGLHelper.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/misc/imap2.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/docrecovery.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/lexer.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/grammar.cxx"))
+ return;
+ if (loplugin::isSamePathname(
+ fn, SRCDIR "/connectivity/source/drivers/odbc/ODatabaseMetaDataResultSet.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/browser/dsEntriesNoExp.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/explode.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/filter/source/graphicfilter/ipict/ipict.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/core/data/dptabsrc.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/docshell/docsh3.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/dlg/masterlayoutdlg.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/filter/ppt/pptinanimations.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/generic/app/i18n_im.cxx"))
+ return;
+
+ // callback
+ if (loplugin::isSamePathname(fn, SRCDIR "/sax/source/expatwrap/sax_expat.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/xmlsecurity/source/xmlsec/xmlstreamio.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/ww8/ww8par.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/ww8/ww8par2.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/ww8/ww8par5.cxx"))
+ return;
+ // SaxWriterHelper::writeSequence a little weird
+ if (loplugin::isSamePathname(fn, SRCDIR "/sax/source/expatwrap/saxwriter.cxx"))
+ return;
+ // main function
+ if (loplugin::isSamePathname(fn, SRCDIR "/xmlsecurity/workben/pdfverify.cxx"))
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ for (auto functionDecl : problemFunctions)
+ {
+ auto canonicalDecl = functionDecl->getCanonicalDecl();
+ if (getFunctionsWithAddressTaken().find(canonicalDecl)
+ != getFunctionsWithAddressTaken().end())
+ continue;
+ report(DiagnosticsEngine::Warning,
+ "only returning one or zero is an indication you want to return bool",
+ functionDecl->getBeginLoc())
+ << functionDecl->getSourceRange();
+ if (canonicalDecl->getLocation() != functionDecl->getLocation())
+ {
+ report(DiagnosticsEngine::Note, "canonical function declaration here",
+ canonicalDecl->getBeginLoc())
+ << canonicalDecl->getSourceRange();
+ }
+ }
+ }
+
+ bool TraverseFunctionDecl(FunctionDecl*);
+ bool TraverseCXXMethodDecl(CXXMethodDecl*);
+ bool VisitReturnStmt(ReturnStmt const*);
+
+private:
+ bool mbInsideFunction = false;
+ bool mbFunctionOnlyReturningOneOrZero = false;
+ std::unordered_set<FunctionDecl const*> problemFunctions;
+
+ bool IsInteresting(FunctionDecl const*);
+ void Report(FunctionDecl const*) const;
+ bool isExprOneOrZero(Expr const*) const;
+};
+
+bool ShouldReturnBool::TraverseFunctionDecl(FunctionDecl* functionDecl)
+{
+ bool ret;
+ if (IsInteresting(functionDecl))
+ {
+ mbInsideFunction = true;
+ mbFunctionOnlyReturningOneOrZero = true;
+ ret = FunctionAddress::TraverseFunctionDecl(functionDecl);
+ mbInsideFunction = false;
+ if (mbFunctionOnlyReturningOneOrZero)
+ problemFunctions.insert(functionDecl);
+ }
+ else
+ ret = FunctionAddress::TraverseFunctionDecl(functionDecl);
+ return ret;
+}
+
+bool ShouldReturnBool::TraverseCXXMethodDecl(CXXMethodDecl* methodDecl)
+{
+ bool ret;
+ if (IsInteresting(methodDecl))
+ {
+ mbInsideFunction = true;
+ mbFunctionOnlyReturningOneOrZero = true;
+ ret = FunctionAddress::TraverseCXXMethodDecl(methodDecl);
+ mbInsideFunction = false;
+ if (mbFunctionOnlyReturningOneOrZero)
+ problemFunctions.insert(methodDecl);
+ }
+ else
+ ret = FunctionAddress::TraverseCXXMethodDecl(methodDecl);
+ return ret;
+}
+
+bool ShouldReturnBool::IsInteresting(FunctionDecl const* functionDecl)
+{
+ if (ignoreLocation(functionDecl))
+ return false;
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl))
+ return false;
+ if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate)
+ return false;
+ if (!functionDecl->isThisDeclarationADefinition())
+ return false;
+ if (functionDecl->isMain())
+ return false;
+ if (functionDecl->isExternC() || functionDecl->isInExternCContext())
+ return false;
+ auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
+ if (methodDecl && methodDecl->isVirtual())
+ return false;
+ auto tc = loplugin::TypeCheck(functionDecl->getReturnType());
+ if (tc.AnyBoolean() || tc.Void())
+ return false;
+ auto returnType = functionDecl->getReturnType();
+ if (returnType->isEnumeralType() || !returnType->getUnqualifiedDesugaredType()->isIntegerType())
+ return false;
+ // Ignore functions that contains #ifdef-ery
+ if (containsPreprocessingConditionalInclusion(functionDecl->getSourceRange()))
+ return false;
+
+ // not sure what basegfx is doing here
+ StringRef fileName{ getFilenameOfLocation(functionDecl->getLocation()) };
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/basegfx/range/basicrange.hxx"))
+ return false;
+ // false +
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svl/macitem.hxx"))
+ return false;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/lotuswordpro/source/filter/lwpcharsetmgr.hxx"))
+ return false;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/inc/dptabsrc.hxx"))
+ return false;
+
+ return true;
+}
+
+bool ShouldReturnBool::VisitReturnStmt(const ReturnStmt* returnStmt)
+{
+ if (!mbInsideFunction)
+ return true;
+ if (!returnStmt->getRetValue())
+ return true;
+ if (loplugin::TypeCheck(returnStmt->getRetValue()->getType()).AnyBoolean())
+ return true;
+ if (!isExprOneOrZero(returnStmt->getRetValue()))
+ mbFunctionOnlyReturningOneOrZero = false;
+ return true;
+}
+
+bool ShouldReturnBool::isExprOneOrZero(const Expr* arg) const
+{
+ arg = arg->IgnoreParenCasts();
+ // ignore this, it seems to trigger an infinite recursion
+ if (isa<UnaryExprOrTypeTraitExpr>(arg))
+ {
+ return false;
+ }
+ APSInt x1;
+ if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
+ {
+ return x1 == 1 || x1 == 0;
+ }
+ return false;
+}
+
+loplugin::Plugin::Registration<ShouldReturnBool> X("shouldreturnbool", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/simplifybool.cxx b/compilerplugins/clang/store/simplifybool.cxx
new file mode 100644
index 0000000000..973ab6a7a4
--- /dev/null
+++ b/compilerplugins/clang/store/simplifybool.cxx
@@ -0,0 +1,1333 @@
+/* -*- 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 <cassert>
+
+#include "plugin.hxx"
+#include "clang/AST/CXXInheritance.h"
+
+namespace {
+
+// Like clang::Stmt::IgnoreImplicit (lib/AST/Stmt.cpp), but also looking through implicit
+// UserDefinedConversion's member function call:
+Expr const * ignoreAllImplicit(Expr const * expr) {
+ if (auto const e = dyn_cast<ExprWithCleanups>(expr)) {
+ expr = e->getSubExpr();
+ }
+ if (auto const e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
+ expr = e->getSubExpr();
+ }
+ if (auto const e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
+ expr = e->getSubExpr();
+ }
+ while (auto const e = dyn_cast<ImplicitCastExpr>(expr)) {
+ expr = e->getSubExpr();
+ if (e->getCastKind() == CK_UserDefinedConversion) {
+ auto const ce = cast<CXXMemberCallExpr>(expr);
+ assert(ce->getNumArgs() == 0);
+ expr = ce->getImplicitObjectArgument();
+ }
+ }
+ return expr;
+}
+
+Expr const * ignoreParenImpCastAndComma(Expr const * expr) {
+ for (;;) {
+ expr = expr->IgnoreParenImpCasts();
+ auto e = dyn_cast<BinaryOperator>(expr);
+ if (e == nullptr || e->getOpcode() != BO_Comma) {
+ return expr;
+ }
+ expr = e->getRHS();
+ }
+}
+
+Expr const * getSubExprOfLogicalNegation(Expr const * expr) {
+ auto e = dyn_cast<UnaryOperator>(ignoreParenImpCastAndComma(expr));
+ return e == nullptr || e->getOpcode() != UO_LNot
+ ? nullptr : e->getSubExpr();
+}
+
+clang::Type const * stripConstRef(clang::Type const * type) {
+ auto lvalueType = dyn_cast<LValueReferenceType>(type);
+ if (!lvalueType)
+ return type;
+ return lvalueType->getPointeeType()->getUnqualifiedDesugaredType();
+}
+
+bool isCompatibleTypeForOperator(clang::Type const * paramType, CXXRecordDecl const * argRecordDecl) {
+ paramType = stripConstRef(paramType);
+ auto paramRecordType = dyn_cast<RecordType>(paramType);
+ if (!paramRecordType)
+ return false;
+ CXXRecordDecl const * paramRecordDecl = dyn_cast<CXXRecordDecl>(paramRecordType->getDecl());
+ if (!paramRecordDecl)
+ return false;
+ return argRecordDecl == paramRecordDecl || argRecordDecl->isDerivedFrom(paramRecordDecl);
+}
+
+FunctionDecl const * findMemberOperator(CXXRecordDecl const * recordDecl, OverloadedOperatorKind ooOpcode, CXXRecordDecl const * rhs) {
+ for (auto it = recordDecl->method_begin(); it != recordDecl->method_end(); ++it) {
+ if (it->getOverloadedOperator() == ooOpcode) {
+ if (it->getNumParams() == 1 && isCompatibleTypeForOperator(it->getParamDecl(0)->getType().getTypePtr(), rhs))
+ return *it;
+ }
+ }
+ return nullptr;
+}
+
+// Magic value to indicate we assume this operator exists
+static FunctionDecl const * const ASSUME_OPERATOR_EXISTS = reinterpret_cast<FunctionDecl const *>(-1);
+
+// Search for an operator with matching parameter types; while this may miss some operators with
+// odd parameter types that would actually be used by the compiler, it is overall better to have too
+// many false negatives (i.e., miss valid loplugin:simplifybool warnings) than false positives here:
+FunctionDecl const * findOperator(CompilerInstance& compiler, BinaryOperator::Opcode opcode, clang::Type const * lhsType, clang::Type const * rhsType) {
+ auto lhsRecordType = dyn_cast<RecordType>(lhsType);
+ if (!lhsRecordType)
+ return nullptr;
+ auto rhsRecordType = dyn_cast<RecordType>(rhsType);
+ if (!rhsRecordType)
+ return nullptr;
+ CXXRecordDecl const * lhsRecordDecl = dyn_cast<CXXRecordDecl>(lhsRecordType->getDecl());
+ if (!lhsRecordDecl)
+ return nullptr;
+ CXXRecordDecl const * rhsRecordDecl = dyn_cast<CXXRecordDecl>(rhsRecordType->getDecl());
+ if (!rhsRecordDecl)
+ return nullptr;
+
+ auto ctx = lhsRecordDecl->getCanonicalDecl()->getDeclContext();
+
+ /*
+ It looks the clang Sema::LookupOverloadedOperatorName is the chunk of functionality I need,
+ but I have no idea how to call it from here.
+ Actually finding the right standard library operators requires doing conversions and other funky stuff.
+ For now, just assume that standard library operators are well-behaved, and have negated operators.
+ */
+ if (ctx->isStdNamespace())
+ return ASSUME_OPERATOR_EXISTS;
+ if (auto namespaceDecl = dyn_cast<NamespaceDecl>(ctx)) {
+ // because, of course, half the standard library is not "in the standard namespace"
+ if (namespaceDecl->getName() == "__gnu_debug")
+ return ASSUME_OPERATOR_EXISTS;
+ }
+
+ // search for member overloads
+ // (using the hard way here because DeclContext::lookup does not work for member operators)
+ auto ooOpcode = BinaryOperator::getOverloadedOperator(opcode);
+ FunctionDecl const * foundFunction = findMemberOperator(lhsRecordDecl, ooOpcode, rhsRecordDecl);
+ if (foundFunction)
+ return foundFunction;
+ auto ForallBasesCallback = [&](const CXXRecordDecl *baseCXXRecordDecl)
+ {
+ if (baseCXXRecordDecl->isInvalidDecl())
+ return false;
+ foundFunction = findMemberOperator(baseCXXRecordDecl, ooOpcode, rhsRecordDecl);
+ return false;
+ };
+
+ lhsRecordDecl->forallBases(ForallBasesCallback);
+ if (foundFunction)
+ return foundFunction;
+
+ // search for free function overloads
+ if (ctx->getDeclKind() == Decl::LinkageSpec) {
+ ctx = ctx->getParent();
+ }
+ auto operatorDeclName = compiler.getASTContext().DeclarationNames.getCXXOperatorName(ooOpcode);
+ auto res = ctx->lookup(operatorDeclName);
+ for (auto d = res.begin(); d != res.end(); ++d) {
+ FunctionDecl const * f = dyn_cast<FunctionDecl>(*d);
+ if (!f || f->getNumParams() != 2)
+ continue;
+ if (!isCompatibleTypeForOperator(f->getParamDecl(0)->getType().getTypePtr(), lhsRecordDecl))
+ continue;
+ if (!isCompatibleTypeForOperator(f->getParamDecl(1)->getType().getTypePtr(), rhsRecordDecl))
+ continue;
+ return f;
+ }
+ return nullptr;
+}
+
+enum class Value { Unknown, False, True };
+
+Value getValue(Expr const * expr) {
+ expr = ignoreParenImpCastAndComma(expr);
+ if (expr->getType()->isBooleanType()) {
+ // Instead going via Expr::isCXX11ConstantExpr would turn up exactly one
+ // additional place in svx/source/dialog/framelinkarray.cxx
+ //
+ // const bool DIAG_DBL_CLIP_DEFAULT = false;
+ // ...
+ // ... = mxImpl.get() ? mxImpl->mbDiagDblClip : DIAG_DBL_CLIP_DEFAULT;
+ //
+ // where it is unclear whether it is not actually better to consider
+ // DIAG_DBL_CLIP_DEFAULT a tunable parameter (and thus not to simplify):
+ auto lit = dyn_cast<CXXBoolLiteralExpr>(expr);
+ if (lit != nullptr) {
+ return lit->getValue() ? Value::True : Value::False;
+ }
+ }
+ return Value::Unknown;
+}
+
+class SimplifyBool:
+ public loplugin::FilteringPlugin<SimplifyBool>
+{
+public:
+ explicit SimplifyBool(loplugin::InstantiationData const & data):
+ FilteringPlugin(data) {}
+
+ void run() override;
+
+ bool VisitUnaryOperator(UnaryOperator const * expr);
+
+ bool VisitBinaryOperator(BinaryOperator const * expr);
+
+ bool VisitConditionalOperator(ConditionalOperator const * expr);
+
+ bool TraverseFunctionDecl(FunctionDecl *);
+
+ bool TraverseCXXMethodDecl(CXXMethodDecl *);
+
+private:
+ bool visitBinLT(BinaryOperator const * expr);
+
+ bool visitBinGT(BinaryOperator const * expr);
+
+ bool visitBinLE(BinaryOperator const * expr);
+
+ bool visitBinGE(BinaryOperator const * expr);
+
+ bool visitBinEQ(BinaryOperator const * expr);
+
+ bool visitBinNE(BinaryOperator const * expr);
+
+ FunctionDecl* m_insideFunctionDecl = nullptr;
+};
+
+void SimplifyBool::run() {
+ if (compiler.getLangOpts().CPlusPlus) {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+}
+
+bool SimplifyBool::VisitUnaryOperator(UnaryOperator const * expr) {
+ if (expr->getOpcode() != UO_LNot) {
+ return true;
+ }
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto e = getSubExprOfLogicalNegation(expr->getSubExpr());
+ if (e) {
+ // Ignore macros, otherwise
+ // OSL_ENSURE(!b, ...);
+ // triggers.
+ if (e->getBeginLoc().isMacroID())
+ return true;
+ // double logical not of an int is an idiom to convert to bool
+ auto const sub = ignoreAllImplicit(e);
+ if (!sub->getType()->isBooleanType())
+ return true;
+ report(
+ DiagnosticsEngine::Warning,
+ ("double logical negation expression of the form '!!A' (with A of type"
+ " %0) can %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << sub->getType()
+ << sub->getType()->isBooleanType()
+ << expr->getSourceRange();
+ return true;
+ }
+ auto sub = expr->getSubExpr()->IgnoreParenImpCasts();
+ auto reversed = false;
+ if (auto const rewritten = dyn_cast<CXXRewrittenBinaryOperator>(sub)) {
+ if (rewritten->isReversed()) {
+ if (rewritten->getOperator() == BO_EQ) {
+ auto const sem = rewritten->getSemanticForm();
+ bool match;
+ if (auto const op1 = dyn_cast<BinaryOperator>(sem)) {
+ match = op1->getOpcode() == BO_EQ;
+ } else if (auto const op2 = dyn_cast<CXXOperatorCallExpr>(sem)) {
+ match = op2->getOperator() == OO_EqualEqual;
+ } else {
+ match = false;
+ }
+ if (match) {
+ sub = sem;
+ reversed = true;
+ }
+ }
+ }
+ }
+ if (auto binaryOp = dyn_cast<BinaryOperator>(sub)) {
+ // Ignore macros, otherwise
+ // OSL_ENSURE(!b, ...);
+ // triggers.
+ if (binaryOp->getBeginLoc().isMacroID())
+ return true;
+ if (binaryOp->isComparisonOp())
+ {
+ auto t = binaryOp->getLHS()->IgnoreImpCasts()->getType()->getUnqualifiedDesugaredType();
+ if (t->isTemplateTypeParmType() || t->isDependentType() || t->isRecordType())
+ return true;
+ // for floating point (with NaN) !(x<y) need not be equivalent to x>=y
+ if (t->isFloatingType() ||
+ binaryOp->getRHS()->IgnoreImpCasts()->getType()->getUnqualifiedDesugaredType()->isFloatingType())
+ return true;
+ report(
+ DiagnosticsEngine::Warning,
+ ("logical negation of comparison operator, can be simplified by inverting operator"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ }
+ else if (binaryOp->isLogicalOp())
+ {
+ // if we find a negation condition inside, it is definitely better
+ // to expand it out
+ bool foundLNot = false;
+ auto containsNegationOrComparison = [&](Expr const * expr) {
+ expr = ignoreParenImpCastAndComma(expr);
+ if (auto unaryOp = dyn_cast<UnaryOperator>(expr))
+ if (unaryOp->getOpcode() == UO_LNot)
+ {
+ foundLNot = true;
+ return expr;
+ }
+ if (auto binaryOp = dyn_cast<BinaryOperator>(expr))
+ if (binaryOp->isComparisonOp())
+ return expr;
+ if (auto cxxOpCall = dyn_cast<CXXOperatorCallExpr>(expr))
+ if (cxxOpCall->isComparisonOp())
+ return expr;
+ return (Expr const*)nullptr;
+ };
+ auto lhs = containsNegationOrComparison(binaryOp->getLHS());
+ auto rhs = containsNegationOrComparison(binaryOp->getRHS());
+ if (foundLNot || (lhs && rhs))
+ report(
+ DiagnosticsEngine::Warning,
+ ("logical negation of logical op containing negation, can be simplified"),
+ binaryOp->getBeginLoc())
+ << binaryOp->getSourceRange();
+ }
+ }
+ if (auto binaryOp = dyn_cast<CXXOperatorCallExpr>(sub)) {
+ // Ignore macros, otherwise
+ // OSL_ENSURE(!b, ...);
+ // triggers.
+ if (binaryOp->getBeginLoc().isMacroID())
+ return true;
+ auto op = binaryOp->getOperator();
+ // Negating things like > and >= would probably not be wise, there is no guarantee the negation holds for operator overloaded types.
+ // However, == and != are normally considered ok.
+ if (!(op == OO_EqualEqual || op == OO_ExclaimEqual))
+ return true;
+ BinaryOperator::Opcode negatedOpcode = BinaryOperator::negateComparisonOp(BinaryOperator::getOverloadedOpcode(op));
+ auto lhs = binaryOp->getArg(reversed ? 1 : 0)->IgnoreImpCasts()->getType()->getUnqualifiedDesugaredType();
+ auto rhs = binaryOp->getArg(reversed ? 0 : 1)->IgnoreImpCasts()->getType()->getUnqualifiedDesugaredType();
+ auto const negOp = findOperator(compiler, negatedOpcode, lhs, rhs);
+ if (!negOp)
+ return true;
+ // if we are inside a similar operator, ignore, eg. operator!= is often defined by calling !operator==
+ if (m_insideFunctionDecl && m_insideFunctionDecl->getNumParams() >= 1) {
+ auto t = stripConstRef(m_insideFunctionDecl->getParamDecl(0)->getType().getTypePtr());
+ if (t == lhs)
+ return true;
+ }
+ // QA code
+ StringRef fn(handler.getMainFileName());
+ if (loplugin::isSamePathname(fn, SRCDIR "/testtools/source/bridgetest/bridgetest.cxx"))
+ return true;
+ report(
+ DiagnosticsEngine::Warning,
+ ("logical negation of comparison operator, can be simplified by inverting operator"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ if (negOp != ASSUME_OPERATOR_EXISTS)
+ report(
+ DiagnosticsEngine::Note, "the presumed corresponding negated operator for %0 and %1 is declared here",
+ negOp->getLocation())
+ << binaryOp->getArg(reversed ? 1 : 0)->IgnoreImpCasts()->getType()
+ << binaryOp->getArg(reversed ? 0 : 1)->IgnoreImpCasts()->getType()
+ << negOp->getSourceRange();
+ }
+ return true;
+}
+
+bool SimplifyBool::VisitBinaryOperator(BinaryOperator const * expr) {
+ switch (expr->getOpcode()) {
+ case BO_LT:
+ return visitBinLT(expr);
+ case BO_GT:
+ return visitBinGT(expr);
+ case BO_LE:
+ return visitBinLE(expr);
+ case BO_GE:
+ return visitBinGE(expr);
+ case BO_EQ:
+ return visitBinEQ(expr);
+ case BO_NE:
+ return visitBinNE(expr);
+ default:
+ return true;
+ }
+}
+
+bool SimplifyBool::visitBinLT(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!(expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ && expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()))
+ {
+ return true;
+ }
+ auto v1 = getValue(expr->getLHS());
+ auto v2 = getValue(expr->getRHS());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'A < false' (with A of type"
+ " %0) can logically be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getLHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'A < true' (with A"
+ " of type %0) can %select{logically|literally}1 be"
+ " simplified as '!A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << (expr->getLHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form '!A < true' (with A"
+ " of type %0) can %select{logically|literally}1 be"
+ " simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'false < A' (with A of type"
+ " %0) can %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'false < false' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'false < true' can"
+ " literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'true < A' (with A of type"
+ " %0) can logically be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'true < false' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than expression of the form 'true < true' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::visitBinGT(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!(expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ && expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()))
+ {
+ return true;
+ }
+ auto v1 = getValue(expr->getLHS());
+ auto v2 = getValue(expr->getRHS());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'A > false' (with A of"
+ " type %0) can %select{logically|literally}1 be simplified as"
+ " 'A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'A > true' (with A of"
+ " type %0) can logically be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'false > A' (with A of"
+ " type %0) can logically be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'false > false' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'false > true' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getRHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'true > A' (with"
+ " A of type %0) can %select{logically|literally}1 be"
+ " simplified as '!A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << (expr->getRHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'true > !A' (with"
+ " A of type %0) can %select{logically|literally}1 be"
+ " simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'true > false' can"
+ " literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than expression of the form 'true > true' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::visitBinLE(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!(expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ && expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()))
+ {
+ return true;
+ }
+ auto v1 = getValue(expr->getLHS());
+ auto v2 = getValue(expr->getRHS());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getLHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'A <="
+ " false' (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as"
+ " '!A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << (expr->getLHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form '!A <="
+ " false' (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'A <= true'"
+ " (with A of type %0) can logically be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'false <= A'"
+ " (with A of type %0) can logically be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'false <= false'"
+ " can literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'false <= true'"
+ " can literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'true <= A'"
+ " (with A of type %0) can %select{logically|literally}1 be"
+ " simplified as 'A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'true <= false'"
+ " can literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("less-than-or-equal-to expression of the form 'true <= true'"
+ " can literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::visitBinGE(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!(expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ && expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()))
+ {
+ return true;
+ }
+ auto v1 = getValue(expr->getLHS());
+ auto v2 = getValue(expr->getRHS());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'A >= false'"
+ " (with A of type %0) can logically be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'A >= true'"
+ " (with A of type %0) can %select{logically|literally}1 be"
+ " simplified as 'A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getRHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form"
+ " 'false >= A' (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as"
+ " '!A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << (expr->getRHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form"
+ " 'false >= !A' (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'false >="
+ " false' can literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'false >="
+ " true' can literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'true >= A'"
+ " (with A of type %0) can logically be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'true >="
+ " false' can literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("greater-than-or-equal-to expression of the form 'true >="
+ " true' can literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::visitBinEQ(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!(expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ && expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()))
+ {
+ return true;
+ }
+ auto v1 = getValue(expr->getLHS());
+ auto v2 = getValue(expr->getRHS());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getLHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'A == false' (with A"
+ " of type %0) can %select{logically|literally}1 be"
+ " simplified as '!A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << (expr->getLHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form '!A == false' (with A"
+ " of type %0) can %select{logically|literally}1 be"
+ " simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'A == true' (with A of type"
+ " %0) can %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getRHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'false == A' (with A"
+ " of type %0) can %select{logically|literally}1 be"
+ " simplified as '!A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << (expr->getRHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'false == !A' (with A"
+ " of type %0) can %select{logically|literally}1 be"
+ " simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'false == false' can"
+ " literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'false == true' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'true == A' (with A of type"
+ " %0) can %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'true == false' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("equal-to expression of the form 'true == true' can"
+ " literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::visitBinNE(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!(expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ && expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()))
+ {
+ return true;
+ }
+ auto v1 = getValue(expr->getLHS());
+ auto v2 = getValue(expr->getRHS());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'A != false' (with A of"
+ " type %0) can %select{logically|literally}1 be simplified as"
+ " 'A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << expr->getLHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getLHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'A != true' (with"
+ " A of type %0) can %select{logically|literally}1 be"
+ " simplified as '!A'"),
+ expr->getBeginLoc())
+ << expr->getLHS()->IgnoreImpCasts()->getType()
+ << (expr->getLHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form '!A != true'"
+ " (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'false != A' (with A of"
+ " type %0) can %select{logically|literally}1 be simplified as"
+ " 'A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << expr->getRHS()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'false != false' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'false != true' can"
+ " literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getRHS());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'true != A' (with"
+ " A of type %0) can %select{logically|literally}1 be"
+ " simplified as '!A'"),
+ expr->getBeginLoc())
+ << expr->getRHS()->IgnoreImpCasts()->getType()
+ << (expr->getRHS()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'true != !A'"
+ " (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'true != false' can"
+ " literally be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("not-equal-to expression of the form 'true != true' can"
+ " literally be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::VisitConditionalOperator(ConditionalOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto v1 = getValue(expr->getTrueExpr());
+ auto v2 = getValue(expr->getFalseExpr());
+ switch (v1) {
+ case Value::Unknown:
+ switch (v2) {
+ case Value::Unknown:
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? B : false' (with A of"
+ " type %0 and B of type %1) can %select{logically|literally}2"
+ " be simplified as 'A && B'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getTrueExpr()->IgnoreImpCasts()->getType()
+ << ((expr->getCond()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ && (expr->getTrueExpr()->IgnoreImpCasts()->getType()
+ ->isBooleanType()))
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getCond());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? B : true'"
+ " (with A of type %0 and B of type %1) can"
+ " %select{logically|literally}2 be simplified as '!A"
+ " || B'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getTrueExpr()->IgnoreImpCasts()->getType()
+ << ((expr->getCond()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ && (expr->getTrueExpr()->IgnoreImpCasts()->getType()
+ ->isBooleanType()))
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form '!A ? B : true'"
+ " (with A of type %0 and B of type %1) can"
+ " %select{logically|literally}2 be simplified as 'A ||"
+ " B'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << expr->getTrueExpr()->IgnoreImpCasts()->getType()
+ << (e->IgnoreImpCasts()->getType()->isBooleanType()
+ && (expr->getTrueExpr()->IgnoreImpCasts()
+ ->getType()->isBooleanType()))
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ }
+ break;
+ case Value::False:
+ switch (v2) {
+ case Value::Unknown:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getCond());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? false : B'"
+ " (with A of type %0 and B of type %1) can"
+ " %select{logically|literally}2 be simplified as '!A"
+ " && B'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getFalseExpr()->IgnoreImpCasts()->getType()
+ << ((expr->getCond()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ && (expr->getFalseExpr()->IgnoreImpCasts()
+ ->getType()->isBooleanType()))
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form '!A ? false : B'"
+ " (with A of type %0 and B of type %1) can"
+ " %select{logically|literally}2 be simplified as 'A &&"
+ " B'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << expr->getFalseExpr()->IgnoreImpCasts()->getType()
+ << (e->IgnoreImpCasts()->getType()->isBooleanType()
+ && (expr->getFalseExpr()->IgnoreImpCasts()
+ ->getType()->isBooleanType()))
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? false : false' (with"
+ " A of type %0) can logically be simplified as 'false'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ {
+ auto e = getSubExprOfLogicalNegation(expr->getCond());
+ if (e == nullptr) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? false : true'"
+ " (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as"
+ " '!A'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << (expr->getCond()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form '!A ? false :"
+ " true' (with A of type %0) can"
+ " %select{logically|literally}1 be simplified as 'A'"),
+ expr->getBeginLoc())
+ << e->IgnoreImpCasts()->getType()
+ << e->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ }
+ break;
+ }
+ }
+ break;
+ case Value::True:
+ switch (v2) {
+ case Value::Unknown:
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? true : B' (with A of"
+ " type %0 and B of type %1) can %select{logically|literally}2"
+ " be simplified as 'A || B'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getFalseExpr()->IgnoreImpCasts()->getType()
+ << ((expr->getCond()->IgnoreImpCasts()->getType()
+ ->isBooleanType())
+ && (expr->getFalseExpr()->IgnoreImpCasts()->getType()
+ ->isBooleanType()))
+ << expr->getSourceRange();
+ break;
+ case Value::False:
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? true : false' (with A"
+ " of type %0) can %select{logically|literally}1 be simplified"
+ " as 'A'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getCond()->IgnoreImpCasts()->getType()->isBooleanType()
+ << expr->getSourceRange();
+ break;
+ case Value::True:
+ report(
+ DiagnosticsEngine::Warning,
+ ("conditional expression of the form 'A ? true : true' (with A"
+ " of type %0) can logically be simplified as 'true'"),
+ expr->getBeginLoc())
+ << expr->getCond()->IgnoreImpCasts()->getType()
+ << expr->getSourceRange();
+ break;
+ }
+ break;
+ }
+ return true;
+}
+
+bool SimplifyBool::TraverseFunctionDecl(FunctionDecl * functionDecl) {
+ auto copy = m_insideFunctionDecl;
+ m_insideFunctionDecl = functionDecl;
+ bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
+ m_insideFunctionDecl = copy;
+ return ret;
+}
+
+bool SimplifyBool::TraverseCXXMethodDecl(CXXMethodDecl * functionDecl) {
+ auto copy = m_insideFunctionDecl;
+ m_insideFunctionDecl = functionDecl;
+ bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(functionDecl);
+ m_insideFunctionDecl = copy;
+ return ret;
+}
+
+loplugin::Plugin::Registration<SimplifyBool> X("simplifybool");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/staticvar.cxx b/compilerplugins/clang/store/staticvar.cxx
new file mode 100644
index 0000000000..21cbd0f081
--- /dev/null
+++ b/compilerplugins/clang/store/staticvar.cxx
@@ -0,0 +1,213 @@
+/* -*- 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 <cassert>
+#include <string>
+#include <iostream>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "plugin.hxx"
+#include "check.hxx"
+#include "config_clang.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/StmtVisitor.h"
+
+// Look for variables that either
+// (a) could be statically initialised, without runtime code, and warn
+// (b) variables that are statically declared, but require runtime initialisation, and warn
+//
+// e.g.
+// static const OUString[] XXX { "xxx" };
+// requires runtime initialisation, so should rather be declared as OUStringLiteral
+// and
+// static int[] XXX { 1,2 };
+// can be declared const since it does not require runtime initialisation.
+
+namespace
+{
+class StaticVar : public loplugin::FilteringPlugin<StaticVar>
+{
+public:
+ explicit StaticVar(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ std::string fn(handler.getMainFileName());
+ loplugin::normalizeDotDotInFilePath(fn);
+
+ if (
+ // uses icu::UnicodeString
+ fn == SRCDIR "/l10ntools/source/xmlparse.cxx"
+ // contains mutable state
+ || fn == SRCDIR "/sal/osl/unx/signal.cxx"
+ || fn == SRCDIR "/sal/qa/rtl/digest/rtl_digest.cxx"
+ || fn == SRCDIR "/sal/qa/rtl/strings/test_oustring_endswith.cxx"
+ || fn == SRCDIR "/sal/qa/rtl/strings/test_oustring_convert.cxx"
+ || fn == SRCDIR "/svl/qa/unit/items/test_itempool.cxx"
+ // contains mutable state
+ || fn == SRCDIR "/vcl/unx/generic/dtrans/X11_selection.cxx"
+ || fn == SRCDIR "/sax/qa/cppunit/xmlimport.cxx"
+ || fn == SRCDIR "/pyuno/source/module/pyuno.cxx"
+ || fn == SRCDIR "/pyuno/source/module/pyuno_module.cxx"
+ || fn == SRCDIR "/pyuno/source/module/pyuno_struct.cxx"
+ // TODO for this one we need a static OUString
+ || fn == SRCDIR "/xmloff/source/core/xmltoken.cxx"
+ // mutable
+ || fn == SRCDIR "/basic/source/runtime/stdobj.cxx"
+ // TODO this needs more extensive cleanup
+ || fn == SRCDIR "/connectivity/source/drivers/postgresql/pq_statics.cxx"
+ // mutable
+ || fn == SRCDIR "/hwpfilter/source/hwpreader.cxx"
+ // mutable
+ || fn == SRCDIR "/sw/source/filter/basflt/fltini.cxx"
+ // mutable
+ || fn == SRCDIR "/sw/source/uibase/docvw/srcedtw.cxx"
+ // mutable
+ || fn == SRCDIR "/forms/source/misc/limitedformats.cxx"
+ // aHTMLOptionTab is ordered by useful grouping, so let it sort at runtime
+ || fn == SRCDIR "/svtools/source/svhtml/htmlkywd.cxx"
+ // TODO sorting some of these tables will be a lot of work...
+ || fn == SRCDIR "/sw/source/filter/ww8/ww8par6.cxx"
+ // this only triggers on older versions of clang, not sure why
+ // in any case, it is actually about the array in vcl/inc/units.hrc, which we can't change
+ || fn == SRCDIR "/vcl/source/app/svdata.cxx"
+ // I tried doing this, but got very weird unit test failures, apparently sorting this table
+ // disturbs some code elsewhere
+ || fn == SRCDIR "/svx/source/unodraw/unoprov.cxx"
+ // aRTFTokenTab is ordered by useful grouping, so let it sort at runtime
+ || fn == SRCDIR "/svtools/source/svrtf/rtfkeywd.cxx")
+ return;
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitVarDecl(VarDecl const*);
+};
+
+static bool containsNonLiteral(Expr const* expr)
+{
+ expr = expr->IgnoreImplicit();
+ if (auto initList = dyn_cast<InitListExpr>(expr))
+ {
+ for (unsigned i = 0; i < initList->getNumInits(); ++i)
+ if (containsNonLiteral(initList->getInit(i)))
+ return true;
+ }
+ else if (auto constructExpr = dyn_cast<CXXConstructExpr>(expr))
+ {
+ for (Expr const* arg : constructExpr->arguments())
+ if (containsNonLiteral(arg))
+ return true;
+ }
+ else if (isa<MemberExpr>(expr))
+ return true;
+ else if (auto declRefExpr = dyn_cast<DeclRefExpr>(expr))
+ {
+ auto varDecl = dyn_cast_or_null<VarDecl>(declRefExpr->getDecl());
+ return varDecl && varDecl->isLocalVarDeclOrParm();
+ }
+ else if (isa<CXXMemberCallExpr>(expr))
+ return true;
+ else if (auto castExpr = dyn_cast<CXXFunctionalCastExpr>(expr))
+ return containsNonLiteral(castExpr->getSubExpr());
+ else if (auto unaryOp = dyn_cast<UnaryOperator>(expr))
+ return containsNonLiteral(unaryOp->getSubExpr());
+
+ return false;
+}
+
+bool StaticVar::VisitVarDecl(VarDecl const* varDecl)
+{
+ if (ignoreLocation(varDecl))
+ return true;
+ if (!varDecl->hasInit())
+ return true;
+ auto initList = dyn_cast_or_null<InitListExpr>(varDecl->getInit());
+ if (!initList)
+ return true;
+ if (varDecl->isExceptionVariable() || isa<ParmVarDecl>(varDecl))
+ return true;
+ if (!varDecl->getType()->isArrayType())
+ return true;
+ auto elementType = varDecl->getType()->getBaseElementTypeUnsafe();
+ if (!elementType->isRecordType())
+ return true;
+ auto elementRecordDecl
+ = dyn_cast_or_null<CXXRecordDecl>(elementType->getAs<RecordType>()->getDecl());
+ if (!elementRecordDecl)
+ return true;
+ if (containsNonLiteral(initList))
+ return true;
+
+ if (elementRecordDecl->hasTrivialDestructor())
+ {
+ if (varDecl->isLocalVarDecl())
+ {
+ if (varDecl->getStorageDuration() == SD_Static && varDecl->getType().isConstQualified())
+ return true;
+ }
+ else
+ {
+ if (varDecl->getType().isConstQualified())
+ return true;
+ }
+
+ // TODO cannot figure out how to make the loplugin::TypeCheck stuff match this
+ // std::string typeName = varDecl->getType().getAsString();
+ // if (typeName == "std::va_list" || typeName == "va_list")
+ // return true;
+
+ auto const tcElement = loplugin::TypeCheck(elementRecordDecl);
+ if (tcElement.Struct("ContextID_Index_Pair").GlobalNamespace())
+ return true;
+ if (tcElement.Class("SfxSlot").GlobalNamespace())
+ return true;
+
+ if (varDecl->isLocalVarDecl())
+ report(DiagnosticsEngine::Warning, "var should be static const, or allowlisted",
+ varDecl->getLocation())
+ << varDecl->getSourceRange();
+ else
+ report(DiagnosticsEngine::Warning, "var should be const, or allowlisted",
+ varDecl->getLocation())
+ << varDecl->getSourceRange();
+ }
+ else
+ {
+ if (varDecl->isLocalVarDecl())
+ {
+ if (varDecl->getStorageDuration() != SD_Static
+ || !varDecl->getType().isConstQualified())
+ return true;
+ }
+ else
+ {
+ if (!varDecl->getType().isConstQualified())
+ return true;
+ }
+
+ if (varDecl->isLocalVarDecl())
+ report(DiagnosticsEngine::Warning, "static const var requires runtime initialization?",
+ varDecl->getLocation())
+ << varDecl->getSourceRange();
+ else
+ report(DiagnosticsEngine::Warning, "static var requires runtime initialization?",
+ varDecl->getLocation())
+ << varDecl->getSourceRange();
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<StaticVar> X("staticvar", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/stdexception.cxx b/compilerplugins/clang/store/stdexception.cxx
new file mode 100644
index 0000000000..47a7d57911
--- /dev/null
+++ b/compilerplugins/clang/store/stdexception.cxx
@@ -0,0 +1,188 @@
+/* -*- 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 <algorithm>
+#include <cassert>
+#include <limits>
+#include <string>
+
+#include "plugin.hxx"
+
+namespace {
+
+bool isStdException(QualType type) {
+ //TODO:
+ std::string name { type.getAsString() };
+ return name == "std::exception" || name == "::std::exception";
+}
+
+class StdException:
+ public loplugin::FilteringRewritePlugin<StdException>
+{
+public:
+ explicit StdException(InstantiationData const & data): FilteringRewritePlugin(data)
+ {}
+
+ virtual void run() override
+ { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXMethodDecl(CXXMethodDecl const * decl);
+};
+
+bool StdException::VisitCXXMethodDecl(CXXMethodDecl const * decl) {
+ if (ignoreLocation(decl)
+ || decl->begin_overridden_methods() == decl->end_overridden_methods())
+ {
+ return true;
+ }
+ CXXMethodDecl const * over = nullptr;
+ for (auto i = decl->begin_overridden_methods();
+ i != decl->end_overridden_methods(); ++i)
+ {
+ FunctionProtoType const * t
+ = (*i)->getType()->getAs<FunctionProtoType>();
+ switch (t->getExceptionSpecType()) {
+ case EST_None:
+ continue;
+ case EST_DynamicNone:
+ case EST_BasicNoexcept:
+ return true;
+ case EST_Dynamic:
+ {
+ unsigned n = t->getNumExceptions();
+ for (unsigned j = 0; j != n; ++j) {
+ if (isStdException(t->getExceptionType(j))) {
+ over = *i;
+ goto found;
+ }
+ }
+ return true;
+ }
+ case EST_ComputedNoexcept:
+ switch (t->getNoexceptSpec(compiler.getASTContext())) {
+ case FunctionProtoType::NR_NoNoexcept:
+ case FunctionProtoType::NR_BadNoexcept:
+ assert(false);
+ // fall through
+ case FunctionProtoType::NR_Dependent:
+ break;
+ case FunctionProtoType::NR_Throw:
+ continue;
+ case FunctionProtoType::NR_Nothrow:
+ return true;
+ }
+ case EST_MSAny:
+ case EST_Unevaluated:
+ case EST_Uninstantiated:
+ continue; //TODO???
+ }
+ }
+ return true;
+found:
+ FunctionProtoType const * t = decl->getType()->getAs<FunctionProtoType>();
+ if (!t->hasDynamicExceptionSpec()) {
+ report(
+ DiagnosticsEngine::Warning,
+ "override does not have dynamic exception specification",
+ decl->getLocStart())
+ << decl->getSourceRange();
+ report(
+ DiagnosticsEngine::Note,
+ ("overridden declaration with dynamic exception specification"
+ " including std::exception is here"),
+ over->getLocStart());
+ return true;
+ }
+ unsigned n = t->getNumExceptions();
+ for (unsigned i = 0; i != n; ++i) {
+ if (isStdException(t->getExceptionType(i))) {
+ return true;
+ }
+ }
+ SourceRange r { decl->getSourceRange() };
+ SourceLocation l {
+ compiler.getSourceManager().getExpansionLoc(r.getBegin()) };
+ SourceLocation end {
+ compiler.getSourceManager().getExpansionLoc(r.getEnd()) };
+ assert(
+ l == end
+ || compiler.getSourceManager().isBeforeInTranslationUnit(l, end));
+ bool seenThrow = false;
+ unsigned parens = 0;
+ SourceLocation openParen;
+ SourceLocation loc;
+ for (;;) {
+ unsigned n = Lexer::MeasureTokenLength(
+ l, compiler.getSourceManager(), compiler.getLangOpts());
+ std::string s { compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "{" || s == ";") {
+ break;
+ }
+ if (!seenThrow) {
+ if (s == "throw") {
+ seenThrow = true;
+ }
+ } else if (s == "(") {
+ assert(parens < std::numeric_limits<unsigned>::max());
+ ++parens;
+ if (parens == 1) {
+ openParen = l;
+ }
+ loc = l;
+ } else if (s == ")") {
+ assert(parens != 0);
+ --parens;
+ if (parens == 0) {
+ assert(loc.isValid());
+ // Only rewrite declarations in include files if a definition is
+ // also seen, to avoid compilation of a definition (in a main
+ // file only processed later) to fail with a "mismatch" error
+ // before the rewriter had a chance to act upon the definition
+ // (but use the heuristic of assuming pure virtual functions do
+ // not have definitions):
+ if (rewriter != nullptr
+ && (compiler.getSourceManager().isInMainFile(
+ compiler.getSourceManager().getSpellingLoc(loc))
+ || decl->isDefined() || decl->isPure())
+ && insertTextAfterToken(
+ loc,
+ (loc == openParen
+ ? "std::exception" : ", std::exception")))
+ {
+ return true;
+ }
+ break;
+ }
+ loc = l;
+ } else if (!s.empty() && s.compare(0, 2, "/*") != 0
+ && s.compare(0, 2, "//") != 0)
+ {
+ loc = l;
+ }
+ if (l == end) {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ report(
+ DiagnosticsEngine::Warning,
+ "override dropped std::exception from dynamic exception specification",
+ openParen.isValid() ? openParen : decl->getLocStart())
+ << decl->getSourceRange();
+ report(
+ DiagnosticsEngine::Note, "overridden declaration is here",
+ over->getLocStart());
+ return true;
+}
+
+loplugin::Plugin::Registration<StdException> X("stdexception", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/stringbuffer.cxx b/compilerplugins/clang/store/stringbuffer.cxx
new file mode 100644
index 0000000000..899c9b6ac2
--- /dev/null
+++ b/compilerplugins/clang/store/stringbuffer.cxx
@@ -0,0 +1,75 @@
+/* -*- 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/.
+ */
+#ifndef LO_CLANG_SHARED_PLUGINS
+
+#include "check.hxx"
+#include "plugin.hxx"
+#include <vector>
+
+/** Look for appending result of adding OUString/OString to OUStringBuffer
+ */
+namespace
+{
+class StringBuffer : public loplugin::FilteringPlugin<StringBuffer>
+{
+public:
+ explicit StringBuffer(loplugin::InstantiationData const& rData)
+ : FilteringPlugin(rData)
+ {
+ }
+
+ bool preRun() override
+ {
+ StringRef fn(handler.getMainFileName());
+ return !loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/");
+ }
+
+ void run() override
+ {
+ if (preRun())
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+ }
+
+ bool VisitCXXMemberCallExpr(CXXMemberCallExpr const*);
+};
+
+bool StringBuffer::VisitCXXMemberCallExpr(CXXMemberCallExpr const* memberCallExpr)
+{
+ if (ignoreLocation(memberCallExpr))
+ return true;
+ if (!loplugin::DeclCheck(memberCallExpr->getRecordDecl())
+ .Class("OUStringBuffer")
+ .Namespace("rtl")
+ .GlobalNamespace())
+ return true;
+ if (!memberCallExpr->getMethodDecl()->getIdentifier())
+ return true;
+ if (memberCallExpr->getMethodDecl()->getName() != "append")
+ return true;
+ auto matTemp = dyn_cast<MaterializeTemporaryExpr>(memberCallExpr->getArg(0));
+ if (!matTemp)
+ return true;
+ if (!isa<CXXOperatorCallExpr>(matTemp->getSubExpr()))
+ return true;
+ report(DiagnosticsEngine::Warning,
+ "appending added result of OUString to OUStringBuffer, rather do .append(x).append(y)",
+ memberCallExpr->getBeginLoc())
+ << memberCallExpr->getSourceRange();
+ return true;
+}
+
+loplugin::Plugin::Registration<StringBuffer> stringbuffer("stringbuffer", false);
+
+} // namespace
+
+#endif // LO_CLANG_SHARED_PLUGINS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/stringliteraldefine.cxx b/compilerplugins/clang/store/stringliteraldefine.cxx
new file mode 100644
index 0000000000..8d7e778051
--- /dev/null
+++ b/compilerplugins/clang/store/stringliteraldefine.cxx
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 constant character array variables that are either
+// (a) passed into O[U]String constructors
+// (b) assigned to O[U]String
+// and are declared using macro names
+// and should thus be turned into O[U]StringLiteral variables.
+//
+
+#include <cassert>
+
+#include "config_clang.h"
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+namespace
+{
+class StringLiteralDefine final : public loplugin::FilteringPlugin<StringLiteralDefine>
+{
+public:
+ explicit StringLiteralDefine(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ bool TraverseInitListExpr(InitListExpr* expr, DataRecursionQueue* queue = nullptr)
+ {
+ return WalkUpFromInitListExpr(expr)
+ && TraverseSynOrSemInitListExpr(
+ expr->isSemanticForm() ? expr : expr->getSemanticForm(), queue);
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr const* expr)
+ {
+ if (ignoreLocation(expr))
+ return true;
+ loplugin::TypeCheck const tc(expr->getType());
+ if (!(tc.Class("OString").Namespace("rtl").GlobalNamespace()
+ || tc.Class("OUString").Namespace("rtl").GlobalNamespace()))
+ {
+ return true;
+ }
+ auto const ctor = expr->getConstructor();
+ if (ctor->getNumParams() != 2)
+ return true;
+
+ const Expr* arg0 = expr->getArg(0)->IgnoreParenImpCasts();
+ auto const e1 = dyn_cast<clang::StringLiteral>(arg0);
+ if (!e1)
+ return true;
+ auto argLoc = arg0->getBeginLoc();
+ // check if the arg is a macro
+ auto macroLoc = compiler.getSourceManager().getSpellingLoc(argLoc);
+ if (argLoc == macroLoc)
+ return true;
+ // check if it is the right kind of macro (not particularly reliable checks)
+ if (!macroLoc.isValid() || !compiler.getSourceManager().isInMainFile(macroLoc)
+ || compiler.getSourceManager().isInSystemHeader(macroLoc)
+ || compiler.getSourceManager().isWrittenInBuiltinFile(macroLoc)
+ || compiler.getSourceManager().isWrittenInScratchSpace(macroLoc)
+ || compiler.getSourceManager().isWrittenInCommandLineFile(macroLoc)
+ || isInUnoIncludeFile(macroLoc))
+ return true;
+ StringRef fileName = getFilenameOfLocation(macroLoc);
+ StringRef name{ Lexer::getImmediateMacroName(
+ arg0->getBeginLoc(), compiler.getSourceManager(), compiler.getLangOpts()) };
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/config_host/"))
+ return true;
+ // used in both OUString and OString context
+ if (name == "FM_COL_LISTBOX" || name == "HID_RELATIONDIALOG_LEFTFIELDCELL"
+ || name == "OOO_HELP_INDEX" || name == "IMP_PNG" || name.startswith("MNI_ACTION_"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/svx/source/stbctrls/pszctrl.cxx"))
+ return true;
+ // used as a prefix and/or concatenated with other strings
+ if (name.startswith("UNO_JAVA_JFW") || name == "SETNODE_BINDINGS" || name == "PATHDELIMITER"
+ || name == "SETNODE_ALLFILEFORMATS" || name == "SETNODE_DISABLED"
+ || name == "XMLNS_DIALOGS_PREFIX" || name == "XMLNS_LIBRARY_PREFIX"
+ || name == "XMLNS_SCRIPT_PREFIX" || name == "XMLNS_TOOLBAR" || name == "XMLNS_XLINK"
+ || name == "XMLNS_XLINK_PREFIX")
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName,
+ SRCDIR "/stoc/source/security/access_controller.cxx")
+ && (name == "SERVICE_NAME" || name == "USER_CREDS"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/stoc/source/security/file_policy.cxx")
+ && name == "IMPL_NAME")
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName,
+ SRCDIR "/desktop/source/migration/services/jvmfwk.cxx")
+ && name == "IMPL_NAME")
+ return true;
+ if (loplugin::hasPathnamePrefix(
+ fileName, SRCDIR "/xmlsecurity/source/xmlsec/xmldocumentwrapper_xmlsecimpl.cxx")
+ && name == "STRXMLNS")
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/sw/source/ui/fldui/fldvar.cxx")
+ && name == "USER_DATA_VERSION_1")
+ return true;
+ // not sure how to exclude the case where the whole block is in a macro
+ // (vs. what I am looking for - regular code with a macro name as the argument)
+ if (name == "assert" || name == "SAL_INFO" || name == "DECLIMPL_SERVICEINFO_DERIVED"
+ || name == "OSL_VERIFY" || name == "OSL_ENSURE" || name == "DECL_PROP_2"
+ || name == "DECL_PROP_3" || name == "DECL_PROP_1" || name == "DECL_DEP_PROP_2"
+ || name == "DECL_DEP_PROP_3" || name == "CALL_ELEMENT_HANDLER_AND_CARE_FOR_EXCEPTIONS"
+ || name == "IMPLEMENT_SERVICE_INFO" || name == "SQL_GET_REFERENCES"
+ || name == "SFX_IMPL_OBJECTFACTORY" || name == "IMPLEMENT_SERVICE_INFO1"
+ || name == "IMPLEMENT_SERVICE_INFO2" || name == "IMPLEMENT_SERVICE_INFO3"
+ || name == "IMPLEMENT_SERVICE_INFO_IMPLNAME" || name == "SC_SIMPLE_SERVICE_INFO"
+ || name == "SC_SIMPLE_SERVICE_INFO_COMPAT" || name == "OUT_COMMENT"
+ || name == "LOCALE_EN" || name == "LOCALE" || name == "VBAFONTBASE_PROPNAME"
+ || name == "VBAHELPER_IMPL_XHELPERINTERFACE" || name == "IMPRESS_MAP_ENTRIES"
+ || name == "DRAW_MAP_ENTRIES" || name == "DRAW_PAGE_NOTES_PROPERTIES"
+ || name == "COMMON_FLDTYP_PROPERTIES" || name == "GRAPHIC_PAGE_PROPERTIES"
+ || name == "makeDelay" || name == "makeEvent" || name == "OOO_IMPORTER"
+ || name == "DBG_ASSERT" || name.startswith("CPPUNIT_ASSERT"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR
+ "/dbaccess/source/ui/querydesign/SelectionBrowseBox.cxx")
+ && name == "DEFAULT_SIZE")
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/filter/source/t602/t602filter.cxx"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/hwpfilter/source/formula.cxx"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/hwpfilter/source/hwpreader.cxx"))
+ return true;
+ if (loplugin::hasPathnamePrefix(fileName, SRCDIR "/filter/source/svg/svgexport.cxx")
+ && name == "NSPREFIX")
+ return true;
+
+ if (!reported_.insert(macroLoc).second)
+ return true;
+
+ report(DiagnosticsEngine::Warning,
+ "change macro '%0' to 'constexpr "
+ "%select{OStringLiteral|OUStringLiteral}1'",
+ macroLoc)
+ << name << (tc.Class("OString").Namespace("rtl").GlobalNamespace() ? 0 : 1);
+ report(DiagnosticsEngine::Note, "macro used here", arg0->getBeginLoc())
+ << arg0->getSourceRange();
+ return true;
+ }
+
+ bool preRun() override { return compiler.getLangOpts().CPlusPlus; }
+
+private:
+ void run() override
+ {
+ if (preRun())
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+ }
+
+ std::set<SourceLocation> reported_;
+};
+
+// Off by default because it needs some hand-holding
+static loplugin::Plugin::Registration<StringLiteralDefine> reg("stringliteraldefine", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/compilerplugins/clang/store/stringloop.cxx b/compilerplugins/clang/store/stringloop.cxx
new file mode 100644
index 0000000000..3bae1a225b
--- /dev/null
+++ b/compilerplugins/clang/store/stringloop.cxx
@@ -0,0 +1,292 @@
+/* -*- 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 "check.hxx"
+#include "plugin.hxx"
+#include "config_clang.h"
+#include <vector>
+
+/** Look for OUString/OString being appended to inside a loop, where OUStringBuffer/OStringBuffer would be a better idea
+ */
+namespace
+{
+class StringLoop : public clang::RecursiveASTVisitor<StringLoop>, public loplugin::Plugin
+{
+public:
+ explicit StringLoop(loplugin::InstantiationData const& rData)
+ : Plugin(rData)
+ {
+ }
+
+ void run() override;
+ bool TraverseForStmt(ForStmt*);
+ bool TraverseCXXForRangeStmt(CXXForRangeStmt*);
+ bool TraverseDoStmt(DoStmt*);
+ bool TraverseWhileStmt(WhileStmt*);
+ bool VisitVarDecl(VarDecl const*);
+ bool VisitCallExpr(CallExpr const*);
+
+private:
+ int m_insideLoop = 0;
+ using VarDeclList = std::vector<VarDecl const*>;
+ std::vector<VarDeclList> m_varsPerLoopLevel;
+};
+
+void StringLoop::run()
+{
+ // Various places are not worth changing, the code becomes too awkward
+ // Just exclude stuff as I go
+ StringRef fn(handler.getMainFileName());
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/bridges/"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/cppuhelper/source/shlib.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/regimpl.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/l10ntools/source/lngmerge.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/tools/qa/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/jvmfwk/"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/passwordcontainer/passwordcontainer.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/numbers/zformat.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/numbers/zforscan.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/control/combobox.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/gdi/pdfwriter_impl.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svtools/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/idl/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/framework/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/basic/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sfx2/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/avmedia/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/connectivity/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/editeng/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svx/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/basctl/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/filter/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/chart2/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/cui/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/dbaccess/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/oox/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/writerfilter/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/desktop/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/extensions/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/dtrans/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/i18npool/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/embeddedobj/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sd/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/xmloff/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/xmlhelp/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/forms/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/core/tool/address.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/core/tool/compiler.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/ui/docshell/impex.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/ui/miscdlgs/acredlin.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/ui/pagedlg/areasdlg.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/ui/view/gridwin2.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/filter/html/htmlpars.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/core/doc/doctxm.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/core/edit/edattr.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/core/layout/dbg_lay.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/ascii/ascatr.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/html/htmlforw.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/core/unocore/unosect.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/core/unocore/unochart.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/core/unocore/unoobj.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/html/parcss1.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/html/svxcss1.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/html/swhtml.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/uibase/utlui/gloslst.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/uibase/utlui/content.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/uibase/docvw/edtwin.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/ww8/ww8atr.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/ww8/ww8scan.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/ww8/ww8par5.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/ui/fldui/fldfunc.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/ui/misc/bookmark.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/ui/dbui/mmlayoutpage.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/ui/dbui/dbinsdlg.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/ui/dbui/mmresultdialogs.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/ui/index/cnttab.cxx"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/ucb/source/ucp/file/bc.cxx"))
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+}
+
+bool StringLoop::TraverseForStmt(ForStmt* stmt)
+{
+ ++m_insideLoop;
+ m_varsPerLoopLevel.push_back({});
+ auto const ret = RecursiveASTVisitor::TraverseForStmt(stmt);
+ m_varsPerLoopLevel.pop_back();
+ --m_insideLoop;
+ return ret;
+}
+
+bool StringLoop::TraverseCXXForRangeStmt(CXXForRangeStmt* stmt)
+{
+ ++m_insideLoop;
+ m_varsPerLoopLevel.push_back({});
+ auto const ret = RecursiveASTVisitor::TraverseCXXForRangeStmt(stmt);
+ m_varsPerLoopLevel.pop_back();
+ --m_insideLoop;
+ return ret;
+}
+
+bool StringLoop::TraverseDoStmt(DoStmt* stmt)
+{
+ ++m_insideLoop;
+ m_varsPerLoopLevel.push_back({});
+ auto const ret = RecursiveASTVisitor::TraverseDoStmt(stmt);
+ m_varsPerLoopLevel.pop_back();
+ --m_insideLoop;
+ return ret;
+}
+
+bool StringLoop::TraverseWhileStmt(WhileStmt* stmt)
+{
+ ++m_insideLoop;
+ m_varsPerLoopLevel.push_back({});
+ auto const ret = RecursiveASTVisitor::TraverseWhileStmt(stmt);
+ m_varsPerLoopLevel.pop_back();
+ --m_insideLoop;
+ return ret;
+}
+
+bool StringLoop::VisitVarDecl(VarDecl const* varDecl)
+{
+ if (ignoreLocation(varDecl))
+ return true;
+ if (!m_insideLoop)
+ return true;
+ m_varsPerLoopLevel.back().push_back(varDecl);
+ return true;
+}
+
+bool StringLoop::VisitCallExpr(CallExpr const* callExpr)
+{
+ if (ignoreLocation(callExpr))
+ return true;
+ if (!m_insideLoop)
+ return true;
+ auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(callExpr);
+ if (!operatorCallExpr)
+ return true;
+ if (operatorCallExpr->getOperator() != OO_PlusEqual)
+ return true;
+
+ if (auto memberExpr = dyn_cast<MemberExpr>(callExpr->getArg(0)))
+ {
+ auto tc = loplugin::TypeCheck(memberExpr->getType());
+ if (!tc.Class("OUString").Namespace("rtl").GlobalNamespace()
+ && !tc.Class("OString").Namespace("rtl").GlobalNamespace())
+ return true;
+ auto fieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl());
+ if (isInUnoIncludeFile(
+ compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
+ return true;
+ if (ignoreLocation(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
+ return true;
+ report(DiagnosticsEngine::Warning,
+ "appending to OUString in loop, rather use OUStringBuffer",
+ operatorCallExpr->getBeginLoc())
+ << operatorCallExpr->getSourceRange();
+ report(DiagnosticsEngine::Note, "field here", fieldDecl->getBeginLoc())
+ << fieldDecl->getSourceRange();
+ }
+ else if (auto declRefExpr = dyn_cast<DeclRefExpr>(callExpr->getArg(0)))
+ {
+ if (auto varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl()))
+ {
+ auto tc = loplugin::TypeCheck(varDecl->getType());
+ if (!tc.Class("OUString").Namespace("rtl").GlobalNamespace()
+ && !tc.Class("OString").Namespace("rtl").GlobalNamespace())
+ return true;
+ // if the var is at the same block scope as the +=, not interesting
+ auto vars = m_varsPerLoopLevel.back();
+ if (std::find(vars.begin(), vars.end(), varDecl) != vars.end())
+ return true;
+ report(DiagnosticsEngine::Warning,
+ "appending to OUString in loop, rather use OUStringBuffer",
+ operatorCallExpr->getBeginLoc())
+ << operatorCallExpr->getSourceRange();
+ report(DiagnosticsEngine::Note, "var here", varDecl->getBeginLoc())
+ << varDecl->getSourceRange();
+ }
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration<StringLoop> X("stringloop", false);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/stylepolice.cxx b/compilerplugins/clang/store/stylepolice.cxx
new file mode 100644
index 0000000000..d3b2e8a440
--- /dev/null
+++ b/compilerplugins/clang/store/stylepolice.cxx
@@ -0,0 +1,196 @@
+/* -*- 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 <memory>
+#include <regex>
+#include <string>
+#include <set>
+
+#include "plugin.hxx"
+
+// Check for some basic naming mismatches which make the code harder to read
+//
+// This plugin is deliberately fairly lax, and only targets the most egregeriously faulty code,
+// since we have a broad range of styles in our code and we don't want to generate unnecessary
+// churn.
+
+namespace {
+
+class StylePolice :
+ public loplugin::FilteringPlugin<StylePolice>
+{
+public:
+ explicit StylePolice(InstantiationData const & data): FilteringPlugin(data) {}
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitVarDecl(const VarDecl *);
+private:
+ StringRef getFilename(SourceLocation loc);
+};
+
+StringRef StylePolice::getFilename(SourceLocation loc)
+{
+ SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(loc);
+ StringRef name { getFilenameOfLocation(spellingLocation) };
+ return name;
+}
+
+bool startswith(const std::string& rStr, const char* pSubStr) {
+ return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
+}
+bool isUpperLetter(char c) {
+ return c >= 'A' && c <= 'Z';
+}
+bool isLowerLetter(char c) {
+ return c >= 'a' && c <= 'z';
+}
+bool isIdentifierLetter(char c) {
+ return isUpperLetter(c) || isLowerLetter(c);
+}
+bool matchPointerVar(const std::string& s) {
+ return s.size() > 2 && s[0] == 'p' && isUpperLetter(s[1]);
+}
+bool matchRefCountedPointerVar(const std::string& s) {
+ return s.size() > 2 && s[0] == 'x' && isUpperLetter(s[1]);
+}
+bool matchMember(const std::string& s) {
+ return s.size() > 3 && s[0] == 'm'
+ && ( ( strchr("abnprsx", s[1]) && isUpperLetter(s[2]) )
+ || ( s[1] == '_' && isIdentifierLetter(s[2]) ) );
+}
+
+bool StylePolice::VisitVarDecl(const VarDecl * varDecl)
+{
+ if (ignoreLocation(varDecl)) {
+ return true;
+ }
+ StringRef aFileName = getFilename(varDecl->getLocStart());
+ std::string name = varDecl->getName();
+
+ if (!varDecl->isLocalVarDecl()) {
+ return true;
+ }
+
+ if (matchMember(name))
+ {
+ // these names appear to be taken from some scientific paper
+ if (aFileName == SRCDIR "/scaddins/source/analysis/bessel.cxx" ) {
+ }
+ // lots of places where we are storing a "method id" here
+ else if (aFileName.startswith(SRCDIR "/connectivity/source/drivers/jdbc") && name.compare(0,3,"mID") == 0) {
+ }
+ else {
+ report(
+ DiagnosticsEngine::Warning,
+ "this local variable follows our member field naming convention, which is confusing",
+ varDecl->getLocation())
+ << varDecl->getType() << varDecl->getSourceRange();
+ }
+ }
+
+ QualType qt = varDecl->getType().getDesugaredType(compiler.getASTContext()).getCanonicalType();
+ qt = qt.getNonReferenceType();
+ std::string typeName = qt.getAsString();
+ if (startswith(typeName, "const "))
+ typeName = typeName.substr(6);
+ if (startswith(typeName, "class "))
+ typeName = typeName.substr(6);
+ if (startswith(typeName, "struct "))
+ typeName = typeName.substr(7);
+ std::string aOriginalTypeName = varDecl->getType().getAsString();
+ if (startswith(aOriginalTypeName, "const "))
+ aOriginalTypeName = aOriginalTypeName.substr(6);
+
+ if (!qt->isPointerType() && !qt->isArrayType() && !qt->isFunctionPointerType() && !qt->isMemberPointerType()
+ && matchPointerVar(name)
+ && !startswith(typeName, "boost::intrusive_ptr")
+ && !startswith(typeName, "std::optional")
+ && !startswith(typeName, "boost::shared_ptr")
+ && !startswith(typeName, "com::sun::star::uno::Reference")
+ && !startswith(typeName, "cppu::OInterfaceIteratorHelper")
+ && !startswith(typeName, "formula::FormulaCompiler::CurrentFactor")
+ && aOriginalTypeName != "GLXPixmap"
+ && !startswith(typeName, "rtl::Reference")
+ && !startswith(typeName, "ScopedVclPtr")
+ && typeName.find("::mem_fun") == std::string::npos
+ && typeName.find("shared_ptr") == std::string::npos
+ && typeName.find("unique_ptr") == std::string::npos
+ && typeName.find("::weak_ptr") == std::string::npos
+ && !startswith(typeName, "_LOKDocViewPrivate")
+ && !startswith(typeName, "sw::UnoCursorPointer")
+ && !startswith(typeName, "tools::SvRef")
+ && !startswith(typeName, "VclPtr")
+ && !startswith(typeName, "vcl::ScopedBitmapAccess")
+ // lots of the code seems to regard iterator objects as being "pointer-like"
+ && typeName.find("iterator<") == std::string::npos
+ && typeName.find("iter<") == std::string::npos
+ // libc++ std::__1::__wrap_iter<...>
+ && aOriginalTypeName != "sal_IntPtr" )
+ {
+ if (aFileName.startswith(SRCDIR "/bridges/") ) {
+ } else if (aFileName.startswith(SRCDIR "/vcl/source/fontsubset/sft.cxx") ) {
+ } else {
+ report(
+ DiagnosticsEngine::Warning,
+ "this local variable of type '%0' follows our pointer naming convention, but it is not a pointer, %1",
+ varDecl->getLocation())
+ << typeName << aOriginalTypeName << varDecl->getSourceRange();
+ }
+ }
+
+
+ if (matchRefCountedPointerVar(name)
+ && !startswith(typeName, "boost::intrusive_ptr")
+ && !startswith(typeName, "com::sun::star::uno::Reference")
+ && !startswith(typeName, "com::sun::star::uno::Sequence")
+ && !startswith(typeName, "com::sun::star::uno::WeakReference")
+ && !startswith(typeName, "drawinglayer::primitive2d::Primitive2DContainer")
+ && !startswith(typeName, "drawinglayer::primitive3d::Primitive3DContainer")
+ && !startswith(typeName, "jfw::CXPathObjectPtr")
+ && !startswith(typeName, "_LOKDocViewPrivate")
+ && !startswith(typeName, "oox::dump::BinaryInputStreamRef")
+ && !startswith(typeName, "oox::drawingml::chart::ModelRef")
+ && !startswith(typeName, "rtl::Reference")
+ && !startswith(typeName, "Reference")
+ && !startswith(typeName, "SfxObjectShellLock")
+ && !startswith(typeName, "store::PageHolderObject")
+ && !startswith(typeName, "store::ResourceHolder")
+ && !startswith(typeName, "store::OStoreHandle")
+ && typeName.find("unique_ptr") == std::string::npos
+ && typeName.find("shared_ptr") == std::string::npos
+ && !startswith(typeName, "ScopedVclPtr")
+ && !startswith(typeName, "svt::EmbeddedObjectRef")
+ && !startswith(typeName, "tools::SvRef")
+ && !startswith(typeName, "tools::WeakReference")
+ && !startswith(typeName, "utl::SharedUNOComponent")
+ && !startswith(typeName, "VclPtr")
+ && !startswith(typeName, "vcl::DeleteOnDeinit")
+ && !startswith(typeName, "vcl::DeleteUnoReferenceOnDeinit")
+ // there are lots of coordinate/position vars that start with "x"
+ && !qt->isArithmeticType()
+ && !startswith(typeName, "float [")
+ )
+ {
+ report(
+ DiagnosticsEngine::Warning,
+ "this local variable of type '%0' follows our ref-counted-pointer naming convention, but it is not a ref-counted-pointer, %1",
+ varDecl->getLocation())
+ << typeName << aOriginalTypeName << varDecl->getSourceRange();
+ }
+
+
+ return true;
+}
+
+loplugin::Plugin::Registration< StylePolice > X("stylepolice");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/svstreamoutputoperators.cxx b/compilerplugins/clang/store/svstreamoutputoperators.cxx
new file mode 100644
index 0000000000..f4ac13c6e7
--- /dev/null
+++ b/compilerplugins/clang/store/svstreamoutputoperators.cxx
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+/*
+This is a rewriter.
+
+It changes the SvStream operator<< calls into calls to more explicitly named
+methods, which reduces the casting needed, and makes it less likely that we
+will accidentally write data to a file using the wrong data-type-size.
+
+TODO we don't currently cope with macro expansion e.g. if the constant on the RHS is a #define
+
+TODO we don't currently cope with code like "(*this) << 1;"
+
+TODO we don't currently cope with code like "aStream << x << endl;" the "endl" parts ends up dangling.
+
+TODO we don't currently cope with custom overloads of operator<< in some of the use-sites.
+*/
+
+#include "plugin.hxx"
+#include <clang/Lex/Lexer.h>
+#include <iostream>
+
+namespace loplugin
+{
+
+class SvStreamOutputOperators
+ : public loplugin::FilteringRewritePlugin< SvStreamOutputOperators >
+{
+ public:
+ explicit SvStreamOutputOperators( InstantiationData const & data );
+ virtual void run() override;
+ bool VisitCallExpr( const CallExpr* call );
+ private:
+ SourceLocation after(const SourceLocation& loc);
+};
+
+SvStreamOutputOperators::SvStreamOutputOperators( InstantiationData const & data )
+ : FilteringRewritePlugin( data )
+{
+}
+
+void SvStreamOutputOperators::run()
+{
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+}
+
+bool SvStreamOutputOperators::VisitCallExpr( const CallExpr* callExpr )
+{
+ if( ignoreLocation( callExpr ))
+ return true;
+ if( callExpr->getNumArgs() < 2 )
+ return true;
+ const FunctionDecl* func = dyn_cast_or_null< FunctionDecl >( callExpr->getCalleeDecl() );
+ if ( func == NULL )
+ return true;
+ if( func->getNumParams() != 1 )
+ return true;
+ string qualifiedName = func->getQualifiedNameAsString();
+ bool bOutputOperator;
+ if( qualifiedName == "SvStream::operator<<" )
+ bOutputOperator = true;
+ else if( qualifiedName == "SvStream::operator>>" )
+ bOutputOperator = false;
+ else
+ return true;
+
+ string arg0 = func->getParamDecl( 0 )->getType().getAsString();
+ string newIOMethod;
+ if (bOutputOperator)
+ {
+ if( arg0 == "sal_uInt16" )
+ newIOMethod = "WriteUInt16";
+ else if( arg0 == "sal_uInt32" )
+ newIOMethod = "WriteUInt32";
+ else if( arg0 == "sal_uInt64" )
+ newIOMethod = "WriteUInt64";
+ else if( arg0 == "sal_Int16" )
+ newIOMethod = "WriteInt16";
+ else if( arg0 == "sal_Int32" )
+ newIOMethod = "WriteInt32";
+ else if( arg0 == "sal_Int64" )
+ newIOMethod = "WriteInt64";
+ else if( arg0 == "sal_uInt8" )
+ newIOMethod = "WriteUInt8";
+ else if( arg0 == "sal_Unicode" )
+ newIOMethod = "WriteUnicode";
+ else if( arg0 == "rtl::OString" )
+ newIOMethod = "WriteOString";
+ else if( arg0 == "bool" )
+ newIOMethod = "WriteBool";
+ else if( arg0 == "signed char" )
+ newIOMethod = "WriteSChar";
+ else if( arg0 == "char" )
+ newIOMethod = "WriteChar";
+ else if( arg0 == "unsigned char" )
+ newIOMethod = "WriteUChar";
+ else if( arg0 == "float" )
+ newIOMethod = "WriteFloat";
+ else if( arg0 == "double" )
+ newIOMethod = "WriteDouble";
+ else if( arg0 == "const double &" )
+ newIOMethod = "WriteDouble";
+ else if( arg0 == "const char *" )
+ newIOMethod = "WriteOString";
+ else if( arg0 == "char *" )
+ newIOMethod = "WriteOString";
+ else if( arg0 == "const unsigned char *" )
+ newIOMethod = "WriteUCharPtr";
+ else if( arg0 == "unsigned char *" )
+ newIOMethod = "WriteUCharPtr";
+ else if( arg0 == "class SvStream &" )
+ newIOMethod = "WriteStream";
+ else
+ {
+ report( DiagnosticsEngine::Warning,
+ "found call to operator<< that I cannot convert with type: " + arg0,
+ callExpr->getLocStart());
+ return true;
+ }
+ }
+ else
+ {
+ if( arg0 == "sal_uInt16 &" )
+ newIOMethod = "ReadUInt16";
+ else if( arg0 == "sal_uInt32 &" )
+ newIOMethod = "ReadUInt32";
+ else if( arg0 == "sal_uInt64 &" )
+ newIOMethod = "ReadUInt64";
+ else if( arg0 == "sal_Int16 &" )
+ newIOMethod = "ReadInt16";
+ else if( arg0 == "sal_Int32 &" )
+ newIOMethod = "ReadInt32";
+ else if( arg0 == "sal_Int64 &" )
+ newIOMethod = "ReadInt64";
+ else if( arg0 == "sal_uInt8 &" )
+ newIOMethod = "ReadUInt8";
+ else if( arg0 == "signed char &" )
+ newIOMethod = "ReadSChar";
+ else if( arg0 == "char &" )
+ newIOMethod = "ReadChar";
+ else if( arg0 == "unsigned char &" )
+ newIOMethod = "ReadUChar";
+ else if( arg0 == "float &" )
+ newIOMethod = "ReadFloat";
+ else if( arg0 == "double &" )
+ newIOMethod = "ReadDouble";
+ else if( arg0 == "class SvStream &" )
+ newIOMethod = "ReadStream";
+ else
+ {
+ report( DiagnosticsEngine::Warning,
+ "found call to operator>> that I cannot convert with type: " + arg0,
+ callExpr->getLocStart());
+ return true;
+ }
+ }
+
+ // CallExpr overrides the children() method from Stmt, but not the const variant of it, so we need to cast const away.
+ StmtRange range = const_cast<CallExpr*>(callExpr)->children();
+ const Stmt* child1 = *range; // ImplicitCastExpr
+ ++range;
+ const Stmt* child2 = *range; // ImplicitCastExpr
+
+ if( dyn_cast_or_null< UnaryOperator >( child2 ) != NULL )
+ {
+ // remove the "*" before the stream variable
+ if( !replaceText( callExpr->getLocStart(), 1, "" ) )
+ return true;
+ if( !replaceText( child1->getLocStart().getLocWithOffset(-1), 4, "->" ) )
+ return true;
+ }
+ else
+ {
+ if( !replaceText( child1->getLocStart().getLocWithOffset(-1), 4, "." ) )
+ return true;
+ }
+
+ if( !insertTextBefore( callExpr->getArg( 1 )->getLocStart(), newIOMethod + "( " ) )
+ return true;
+ if( !insertTextAfter( after( callExpr->getLocEnd() ), " )" ) )
+ return true;
+
+//TODO for some reason this is currently removing too much text
+ // if there was a cast e.g. "r << (sal_Int32) 1", then remove the cast
+// const CStyleCastExpr* cast = dyn_cast_or_null< CStyleCastExpr >( callExpr->getArg(1) );
+// if (cast != NULL)
+// {
+// replaceText( SourceRange( cast->getLParenLoc(), cast->getRParenLoc() ), "" );
+// }
+
+ // if there was already parentheses around the expression, remove them
+ const ParenExpr* paren = dyn_cast_or_null< ParenExpr >( callExpr->getArg(1) );
+ if (paren != NULL)
+ {
+ if( !replaceText( paren->getLocStart(), 1, "" ) )
+ return true;
+ if( !replaceText( paren->getLocEnd(), 1, "" ) )
+ return true;
+ }
+
+// report( DiagnosticsEngine::Note, "found", callExpr->getLocStart());
+ return true;
+}
+
+SourceLocation SvStreamOutputOperators::after( const SourceLocation& loc )
+{
+ return Lexer::getLocForEndOfToken( loc, 0, compiler.getASTContext().getSourceManager(), compiler.getASTContext().getLangOpts() );
+}
+
+static Plugin::Registration< SvStreamOutputOperators > X( "svstreamoutputoperators" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/test/deadclass.cxx b/compilerplugins/clang/store/test/deadclass.cxx
new file mode 100644
index 0000000000..ffae241d42
--- /dev/null
+++ b/compilerplugins/clang/store/test/deadclass.cxx
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+struct Foo
+{ // expected-error {{class has only copy/move constructors, must be dead [loplugin:deadclass]}}
+ Foo(Foo&);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/compilerplugins/clang/store/toolslong.cxx b/compilerplugins/clang/store/toolslong.cxx
new file mode 100644
index 0000000000..35a7223bd8
--- /dev/null
+++ b/compilerplugins/clang/store/toolslong.cxx
@@ -0,0 +1,653 @@
+/* -*- 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 <algorithm>
+#include <cassert>
+#include <limits>
+#include <map>
+#include <string>
+#include <iostream>
+
+#include "clang/AST/Attr.h"
+#include "clang/Basic/Builtins.h"
+
+#include "config_clang.h"
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+namespace
+{
+bool isLong(QualType type)
+{
+ type = type.getNonReferenceType();
+ // ignore sal_Int64
+ if (type->getAs<TypedefType>())
+ return false;
+ // some parts of the STL have ::difference_type => long
+ if (type->getAs<AutoType>() || type->getAs<DecltypeType>())
+ return false;
+ if (type->isSpecificBuiltinType(BuiltinType::Kind::Long))
+ return true;
+ auto arrayType = type->getAsArrayTypeUnsafe();
+ if (arrayType)
+ return isLong(arrayType->getElementType());
+ if (type->isPointerType())
+ return isLong(type->getPointeeType());
+ return false;
+}
+
+enum class OverrideKind
+{
+ NO,
+ YES,
+ MAYBE
+};
+
+OverrideKind getOverrideKind(FunctionDecl const* decl)
+{
+ CXXMethodDecl const* m = dyn_cast<CXXMethodDecl>(decl);
+ if (m == nullptr)
+ return OverrideKind::NO;
+ if (m->size_overridden_methods() != 0 || m->hasAttr<OverrideAttr>())
+ return OverrideKind::YES;
+ if (!dyn_cast<CXXRecordDecl>(m->getDeclContext())->hasAnyDependentBases())
+ return OverrideKind::NO;
+ return OverrideKind::MAYBE;
+}
+
+class ToolsLong : public loplugin::FilteringRewritePlugin<ToolsLong>
+{
+public:
+ explicit ToolsLong(loplugin::InstantiationData const& data)
+ : loplugin::FilteringRewritePlugin<ToolsLong>(data)
+ {
+ }
+
+ virtual void run() override;
+
+ bool VisitCStyleCastExpr(CStyleCastExpr* expr);
+
+ bool VisitCXXStaticCastExpr(CXXStaticCastExpr* expr);
+
+ bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr);
+
+ bool WalkUpFromParmVarDecl(ParmVarDecl const* decl);
+ bool VisitParmVarDecl(ParmVarDecl const* decl);
+
+ bool WalkUpFromVarDecl(VarDecl const* decl);
+ bool VisitVarDecl(VarDecl const* decl);
+
+ bool WalkUpFromFieldDecl(FieldDecl const* decl);
+ bool VisitFieldDecl(FieldDecl const* decl);
+
+ bool WalkUpFromFunctionDecl(FunctionDecl const* decl);
+ bool VisitFunctionDecl(FunctionDecl const* decl);
+
+ bool VisitCallExpr(CallExpr const* expr);
+
+private:
+ bool rewrite(SourceLocation location);
+ bool isExcludedFile(SourceLocation spellingLocation) const;
+ /** sort by the reverse of source order, so we can do replacing from the end of the file backwards,
+ which means we reduce the chances of having overlapping changes. */
+ template <class T>
+ std::vector<std::pair<T, bool>> reverseSourceOrder(std::map<T, bool> const& map) const
+ {
+ std::vector<std::pair<T, bool>> vec(map.begin(), map.end());
+ std::sort(vec.begin(), vec.end(),
+ [&](std::pair<T, bool> const& lhs, std::pair<T, bool> const& rhs) {
+ return compiler.getSourceManager().getCharacterData(lhs.first->getBeginLoc())
+ > compiler.getSourceManager().getCharacterData(
+ rhs.first->getBeginLoc());
+ });
+ return vec;
+ }
+
+ std::map<VarDecl const*, bool> varDecls_;
+ std::map<FieldDecl const*, bool> fieldDecls_;
+ std::map<ParmVarDecl const*, bool> parmVarDecls_;
+ std::map<FunctionDecl const*, bool> functionDecls_;
+ std::map<CXXStaticCastExpr const*, bool> staticCasts_;
+ std::map<CXXFunctionalCastExpr const*, bool> functionalCasts_;
+};
+
+void ToolsLong::run()
+{
+ if (!compiler.getLangOpts().CPlusPlus)
+ return;
+
+ StringRef fn(handler.getMainFileName());
+ // sberg says this is fine
+ if (loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/framegrabber.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/manager.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/player.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/window.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/connectivity/source/drivers/ado/AStatement.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/connectivity/source/drivers/ado/Awrapado.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/desktop/win32/source/loader.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/extensions/source/activex/SOActiveX.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/pyuno/source/module/pyuno.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR
+ "/setup_native/source/win32/customactions/sellang/sellang.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR
+ "/shell/source/win32/shlxthandler/ooofilt/stream_helper.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/shell/source/win32/zipfile/zipfile.cxx")
+ || loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/webdav-curl/CurlSession.cxx"))
+ return;
+ // these are places where the external API is actually "long"
+ if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/jpeg/JpegReader.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/source/common/DirectoryStream.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/source/common/WPXSvInputStream.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn,
+ SRCDIR "/writerperfect/source/calc/MSWorksCalcImportFilter.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/qa/unit/WPXSvStreamTest.cxx"))
+ return;
+ if (loplugin::isSamePathname(fn, SRCDIR "/desktop/source/lib/init.cxx"))
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ for (auto const& dcl : reverseSourceOrder(varDecls_))
+ {
+ auto const decl = dcl.first;
+ SourceLocation loc{ decl->getBeginLoc() };
+ TypeSourceInfo* tsi = decl->getTypeSourceInfo();
+ if (tsi != nullptr)
+ {
+ SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
+ {
+ for (;;)
+ {
+ unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s{ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "long")
+ {
+ loc = l;
+ break;
+ }
+ if (l == end)
+ {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ if (!rewrite(loc))
+ {
+ report(DiagnosticsEngine::Warning, "VarDecl, use \"tools::Long\" instead of %0", loc)
+ << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange();
+ }
+ }
+ for (auto const& dcl : reverseSourceOrder(fieldDecls_))
+ {
+ auto const decl = dcl.first;
+ SourceLocation loc{ decl->getBeginLoc() };
+ TypeSourceInfo* tsi = decl->getTypeSourceInfo();
+ if (tsi != nullptr)
+ {
+ SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
+ {
+ for (;;)
+ {
+ unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s{ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "long")
+ {
+ loc = l;
+ break;
+ }
+ if (l == end)
+ {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ if (!rewrite(loc))
+ {
+ report(DiagnosticsEngine::Warning, "FieldDecl, use \"tools::Long\" instead of %0", loc)
+ << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange();
+ }
+ }
+ for (auto const& dcl : reverseSourceOrder(parmVarDecls_))
+ {
+ auto const decl = dcl.first;
+ SourceLocation loc{ decl->getBeginLoc() };
+ TypeSourceInfo* tsi = decl->getTypeSourceInfo();
+ if (tsi != nullptr)
+ {
+ SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end || (compiler.getSourceManager().isBeforeInTranslationUnit(l, end)))
+ {
+ for (;;)
+ {
+ unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s{ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "long")
+ {
+ loc = l;
+ break;
+ }
+ if (l == end)
+ {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ FunctionDecl const* f = dyn_cast_or_null<FunctionDecl>(decl->getDeclContext());
+ if (f)
+ f = f->getCanonicalDecl();
+ OverrideKind k = f ? getOverrideKind(f) : OverrideKind::NO;
+ if (k == OverrideKind::MAYBE || !rewrite(loc))
+ {
+ report(DiagnosticsEngine::Warning,
+ ("ParmVarDecl, use \"tools::Long\" instead of"
+ " %0%1"),
+ loc)
+ << decl->getType().getNonReferenceType().getLocalUnqualifiedType()
+ << (k == OverrideKind::MAYBE ? (" (unless this member function overrides a"
+ " dependent base member function, even"
+ " though it is not marked 'override')")
+ : "")
+ << decl->getSourceRange();
+ }
+ }
+ for (auto const& dcl : functionDecls_)
+ {
+ auto const decl = dcl.first;
+ SourceLocation loc{ decl->getBeginLoc() };
+ SourceLocation l{ compiler.getSourceManager().getExpansionLoc(loc) };
+ SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
+ decl->getNameInfo().getLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
+ {
+ while (l != end)
+ {
+ unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s{ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "long")
+ {
+ loc = l;
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ if (rewrite(loc))
+ continue;
+ report(DiagnosticsEngine::Warning, "use \"tools::Long\" instead of %0 as return type%1",
+ loc)
+ << decl->getReturnType().getNonReferenceType().getLocalUnqualifiedType()
+ << (getOverrideKind(decl) == OverrideKind::MAYBE
+ ? (" (unless this member function overrides a dependent"
+ " base member function, even though it is not marked"
+ " 'override')")
+ : "")
+ << decl->getSourceRange();
+ }
+
+ for (auto const& dcl : staticCasts_)
+ {
+ auto const expr = dcl.first;
+ SourceLocation loc{ expr->getBeginLoc() };
+ TypeSourceInfo* tsi = expr->getTypeInfoAsWritten();
+ if (tsi != nullptr)
+ {
+ SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
+ {
+ for (;;)
+ {
+ unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s{ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "long")
+ {
+ loc = l;
+ break;
+ }
+ if (l == end)
+ {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ if (!rewrite(loc))
+ {
+ report(DiagnosticsEngine::Warning, "CXXStaticCastExpr, suspicious cast from %0 to %1",
+ expr->getBeginLoc())
+ << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType()
+ << expr->getSourceRange();
+ }
+ }
+
+ for (auto const& dcl : functionalCasts_)
+ {
+ auto const expr = dcl.first;
+ SourceLocation loc{ expr->getBeginLoc() };
+ TypeSourceInfo* tsi = expr->getTypeInfoAsWritten();
+ if (tsi != nullptr)
+ {
+ SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
+ {
+ for (;;)
+ {
+ unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s{ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "long")
+ {
+ loc = l;
+ break;
+ }
+ if (l == end)
+ {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ if (!rewrite(loc))
+ {
+ report(DiagnosticsEngine::Warning,
+ "CXXFunctionalCastExpr, suspicious cast from %0 to %1", expr->getBeginLoc())
+ << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType()
+ << expr->getSourceRange();
+ }
+ }
+}
+
+bool ToolsLong::VisitCStyleCastExpr(CStyleCastExpr* expr)
+{
+ if (ignoreLocation(expr))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc())))
+ return true;
+ auto const k = isLong(expr->getType());
+ if (!k)
+ return true;
+ SourceLocation loc{ expr->getBeginLoc() };
+ while (compiler.getSourceManager().isMacroArgExpansion(loc))
+ loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
+ if (compiler.getSourceManager().isMacroBodyExpansion(loc)
+ && compiler.getSourceManager().isInSystemHeader(
+ compiler.getSourceManager().getSpellingLoc(loc)))
+ {
+ return true;
+ }
+ report(DiagnosticsEngine::Warning, "CStyleCastExpr, suspicious cast from %0 to %1",
+ expr->getBeginLoc())
+ << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType()
+ << expr->getSourceRange();
+ return true;
+}
+
+bool ToolsLong::VisitCXXStaticCastExpr(CXXStaticCastExpr* expr)
+{
+ if (ignoreLocation(expr))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc())))
+ return true;
+ auto const k = isLong(expr->getType());
+ if (!k)
+ return true;
+ staticCasts_.insert({ expr, k });
+ return true;
+}
+
+bool ToolsLong::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr)
+{
+ if (ignoreLocation(expr))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc())))
+ return true;
+ auto const k = isLong(expr->getType());
+ if (!k)
+ return true;
+ functionalCasts_.insert({ expr, k });
+ return true;
+}
+
+bool ToolsLong::WalkUpFromParmVarDecl(ParmVarDecl const* decl) { return VisitParmVarDecl(decl); }
+
+bool ToolsLong::VisitParmVarDecl(ParmVarDecl const* decl)
+{
+ if (ignoreLocation(decl))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
+ return true;
+ auto const fbk = isLong(decl->getType());
+ if (!fbk)
+ return true;
+ FunctionDecl const* f = dyn_cast<FunctionDecl>(decl->getDeclContext());
+ if (f) // e.g.: typedef sal_Bool (* FuncPtr )( sal_Bool );
+ {
+ // ignore the function in include/test/cppunitasserthelper.hxx
+ if (f->getIdentifier() && f->getName() == "assertEquals")
+ return true;
+ auto canonicalF = f->getCanonicalDecl();
+ if (canonicalF->isDeletedAsWritten() && isa<CXXConversionDecl>(canonicalF))
+ return true;
+ if (auto const d = dyn_cast<CXXMethodDecl>(canonicalF))
+ {
+ if (d->isVirtual())
+ {
+ return true;
+ }
+ }
+ // Only rewrite declarations in include files if a definition is
+ // also seen, to avoid compilation of a definition (in a main file
+ // only processed later) to fail with a "mismatch" error before the
+ // rewriter had a chance to act upon the definition:
+ bool ok = canonicalF->isDefined()
+ || compiler.getSourceManager().isInMainFile(
+ compiler.getSourceManager().getSpellingLoc(f->getNameInfo().getLoc()));
+ if (!ok)
+ return true;
+ }
+ parmVarDecls_.insert({ decl, fbk });
+ return true;
+}
+
+bool ToolsLong::WalkUpFromVarDecl(VarDecl const* decl) { return VisitVarDecl(decl); }
+
+bool ToolsLong::VisitVarDecl(VarDecl const* decl)
+{
+ if (ignoreLocation(decl))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
+ return true;
+ auto k = isLong(decl->getType());
+ if (!k)
+ return true;
+ varDecls_.insert({ decl, k });
+ return true;
+}
+
+bool ToolsLong::WalkUpFromFieldDecl(FieldDecl const* decl) { return VisitFieldDecl(decl); }
+
+bool ToolsLong::VisitFieldDecl(FieldDecl const* decl)
+{
+ if (ignoreLocation(decl))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
+ return true;
+ auto k = isLong(decl->getType());
+ if (!k)
+ return true;
+ TagDecl const* td = dyn_cast<TagDecl>(decl->getDeclContext());
+ if (td == nullptr)
+ {
+ //TODO: ObjCInterface
+ return true;
+ }
+ fieldDecls_.insert({ decl, k });
+ return true;
+}
+
+bool ToolsLong::WalkUpFromFunctionDecl(FunctionDecl const* decl) { return VisitFunctionDecl(decl); }
+
+bool ToolsLong::VisitFunctionDecl(FunctionDecl const* decl)
+{
+ if (ignoreLocation(decl))
+ return true;
+ if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
+ return true;
+ auto const fbk = isLong(decl->getReturnType());
+ if (!fbk)
+ return true;
+ if (decl->isDeletedAsWritten() && isa<CXXConversionDecl>(decl))
+ return true;
+ if (auto const d = dyn_cast<CXXMethodDecl>(decl))
+ {
+ if (d->isVirtual())
+ {
+ return true;
+ }
+ }
+ if (decl->isDefined()
+ || compiler.getSourceManager().isInMainFile(
+ compiler.getSourceManager().getSpellingLoc(decl->getNameInfo().getLoc())))
+ {
+ functionDecls_.insert({ decl, fbk });
+ }
+ return true;
+}
+
+bool ToolsLong::VisitCallExpr(CallExpr const* expr)
+{
+ if (ignoreLocation(expr))
+ {
+ return true;
+ }
+ auto const d1 = expr->getDirectCallee();
+ if (d1 == nullptr || !loplugin::DeclCheck(d1).Function("curl_easy_getinfo").GlobalNamespace())
+ {
+ return true;
+ }
+ if (expr->getNumArgs() != 3)
+ {
+ return true;
+ }
+ //TODO: Check expr->getArg(1) is CURLINFO_RESPONSE_CODE
+ auto const e1 = dyn_cast<UnaryOperator>(expr->getArg(2)->IgnoreParenImpCasts());
+ if (e1 == nullptr || e1->getOpcode() != UO_AddrOf)
+ {
+ return true;
+ }
+ auto const e2 = dyn_cast<DeclRefExpr>(e1->getSubExpr()->IgnoreParenImpCasts());
+ if (e2 == nullptr)
+ {
+ return true;
+ }
+ auto const d2 = e2->getDecl();
+ if (auto const d3 = dyn_cast<ParmVarDecl>(d2))
+ {
+ parmVarDecls_.erase(d3);
+ }
+ else if (auto const d4 = dyn_cast<VarDecl>(d2))
+ {
+ varDecls_.erase(d4);
+ }
+ else if (auto const d5 = dyn_cast<FieldDecl>(d2))
+ {
+ fieldDecls_.erase(d5);
+ }
+ return true;
+}
+
+bool ToolsLong::rewrite(SourceLocation location)
+{
+ if (rewriter != nullptr)
+ {
+ SourceLocation loc{ compiler.getSourceManager().getExpansionLoc(location) };
+ unsigned n
+ = Lexer::MeasureTokenLength(loc, compiler.getSourceManager(), compiler.getLangOpts());
+ if (std::string(compiler.getSourceManager().getCharacterData(loc), n) == "long")
+ {
+ return replaceText(loc, n, "tools::Long");
+ }
+ }
+ return false;
+}
+
+bool ToolsLong::isExcludedFile(SourceLocation spellingLocation) const
+{
+ if (isInUnoIncludeFile(spellingLocation))
+ return true;
+ auto f = getFilenameOfLocation(spellingLocation);
+ return loplugin::hasPathnamePrefix(f, SRCDIR "/include/cppu/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/cppuhelper/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/registry/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/osl/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/rtl/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/sal/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/salhelper/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/typelib/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/include/LibreOfficeKit/") // TODO
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/bridges/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/codemaker/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/configmgr/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/cppu/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/cppuhelper/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/external/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/libreofficekit/") // TODO
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/registry/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/rtl/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/sal/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/salhelper/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/soltools/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/unoidl/")
+ || loplugin::hasPathnamePrefix(f, SRCDIR "/workdir/");
+}
+
+loplugin::Plugin::Registration<ToolsLong> X("toolslong", true);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial1.cxx b/compilerplugins/clang/store/tutorial/tutorial1.cxx
new file mode 100644
index 0000000000..9f7c97fb77
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial1.cxx
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "tutorial1.hxx"
+
+/*
+This is a compile check.
+
+Checks all return statements and warns if they return literal false (i.e. 'return false').
+*/
+
+namespace loplugin
+{
+
+// Ctor, nothing special, pass the argument(s).
+Tutorial1::Tutorial1( const InstantiationData& data )
+ : FilteringPlugin( data )
+ {
+ }
+
+// Perform the actual action.
+void Tutorial1::run()
+ {
+ // Traverse the whole AST of the translation unit (i.e. examine the whole source file).
+ // The Clang AST helper class will call VisitReturnStmt for every return statement.
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+// This function is called for every return statement.
+// Returning true means to continue with examining the AST, false means to stop (just always return true).
+bool Tutorial1::VisitReturnStmt( const ReturnStmt* returnstmt )
+ {
+ // Helper function from the LO base plugin class, call at the very beginning to ignore sources
+ // that should not be processed (e.g. system headers).
+ if( ignoreLocation( returnstmt ))
+ return true;
+ // Get the expression in the return statement (see ReturnStmt API docs).
+ const Expr* expression = returnstmt->getRetValue();
+ if( expression == NULL )
+ return true; // plain 'return;' without expression
+ // Check if the expression is a bool literal (Clang uses dyn_cast<> instead of dynamic_cast<>).
+ if( const CXXBoolLiteralExpr* boolliteral = dyn_cast< CXXBoolLiteralExpr >( expression ))
+ { // It is.
+ if( boolliteral->getValue() == false ) // Is it 'return false;' ? (See CXXBoolLiteralExpr API docs)
+ { // Ok, warn, use LO plugin helper function.
+ report( DiagnosticsEngine::Warning, // It's just a warning.
+ "returning false", // the message
+ boolliteral->getLocStart()) // and the exact position where the message should point
+ << returnstmt->getSourceRange(); // and the full return statement to highlight (optional)
+ }
+ }
+ return true;
+ }
+
+// Register the plugin action with the LO plugin handling.
+static Plugin::Registration< Tutorial1 > tutorial1( "tutorial1" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial1.hxx b/compilerplugins/clang/store/tutorial/tutorial1.hxx
new file mode 100644
index 0000000000..10f73f04b2
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial1.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+// The class implementing the plugin action.
+class Tutorial1
+ // Inherits from the Clang class that will allow examining the Clang AST tree (i.e. syntax tree).
+ : public FilteringPlugin< Tutorial1 >
+ {
+ public:
+ // Ctor, nothing special.
+ Tutorial1( const InstantiationData& data );
+ // The function that will be called to perform the actual action.
+ virtual void run() override;
+ // Function from Clang, it will be called for every return statement in the source.
+ bool VisitReturnStmt( const ReturnStmt* returnstmt );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial1_example.cxx b/compilerplugins/clang/store/tutorial/tutorial1_example.cxx
new file mode 100644
index 0000000000..1ec0e1e59a
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial1_example.cxx
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// This is just an example file to see what AST looks like for return statements.
+// To the AST, run :
+// clang++ -fsyntax-only -Xclang -ast-dump tutorial1_example.cxx
+
+void f()
+ {
+ return;
+ }
+
+bool g()
+ {
+ return false;
+ }
+
+bool h()
+ {
+ return 3 > 2;
+ }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial2.cxx b/compilerplugins/clang/store/tutorial/tutorial2.cxx
new file mode 100644
index 0000000000..49aaaa6318
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial2.cxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "tutorial2.hxx"
+
+/*
+This is a compile check.
+
+Warns about if statements with a comparison followed by literal return false:
+if( a == 1 )
+ return false;
+*/
+
+namespace loplugin
+{
+
+Tutorial2::Tutorial2( const InstantiationData& data )
+ : FilteringPlugin( data )
+ {
+ }
+
+void Tutorial2::run()
+ {
+ // The Clang AST helper class will call VisitIfStmt for every if statement.
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+// This function is called for every if statement.
+bool Tutorial2::VisitIfStmt( const IfStmt* ifstmt )
+ {
+ if( ignoreLocation( ifstmt ))
+ return true;
+ // Check if the condition of the if statement is a binary operator.
+ if( const BinaryOperator* oper = dyn_cast< BinaryOperator >( ifstmt->getCond()))
+ {
+ // And if it's operator==.
+ if( oper->getOpcode() == BO_EQ )
+ {
+ // Now check if the sub-statement is 'return false'.
+ const Stmt* warn = NULL; // The return statement (for the warning message).
+ // Check if the sub-statement is directly 'return false;'.
+ if( isReturnFalse( ifstmt->getThen()))
+ warn = ifstmt->getThen();
+ // Check if the sub-statement is '{ return false; }'
+ else if( const CompoundStmt* compound = dyn_cast< CompoundStmt >( ifstmt->getThen()))
+ {
+ if( compound->size() == 1 ) // one statement
+ if( isReturnFalse( *compound->body_begin())) // check the one sub-statement
+ warn = *compound->body_begin();
+ }
+ if( warn != NULL ) // there is a return statement to warn about.
+ {
+ report( DiagnosticsEngine::Warning,
+ "returning false after if with equality comparison",
+ cast< ReturnStmt >( warn )->getRetValue()->getLocStart()) // the 'false' in the return
+ << warn->getSourceRange();
+ // Also add a note showing the if statement.
+ report( DiagnosticsEngine::Note,
+ "the if statement is here",
+ ifstmt->getLocStart());
+ }
+ }
+ }
+ return true;
+ }
+
+bool Tutorial2::isReturnFalse( const Stmt* stmt )
+ {
+ // Is it return statement?
+ if( const ReturnStmt* returnstmt = dyn_cast< ReturnStmt >( stmt ))
+ {
+ // dyn_cast_or_null<> can also be passed NULL, unlike dyn_cast<>
+ if( const CXXBoolLiteralExpr* boolliteral = dyn_cast_or_null< CXXBoolLiteralExpr >( returnstmt->getRetValue()))
+ {
+ if( boolliteral->getValue() == false )
+ return true;
+ }
+ }
+ return false;
+ }
+
+// Register the plugin action with the LO plugin handling.
+static Plugin::Registration< Tutorial2 > tutorial2( "tutorial2" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial2.hxx b/compilerplugins/clang/store/tutorial/tutorial2.hxx
new file mode 100644
index 0000000000..9ae2de3548
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial2.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+// The same like for Tutorial1.
+class Tutorial2
+ : public FilteringPlugin< Tutorial2 >
+ {
+ public:
+ Tutorial2( const InstantiationData& data );
+ virtual void run() override;
+ // Will be called for every if statement.
+ bool VisitIfStmt( const IfStmt* ifstmt );
+ private:
+ // Helper function to check if the statement is 'return false;'.
+ bool isReturnFalse( const Stmt* stmt );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial2_example.cxx b/compilerplugins/clang/store/tutorial/tutorial2_example.cxx
new file mode 100644
index 0000000000..7d72ff68de
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial2_example.cxx
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// This is just an example file to see what AST looks like for return statements.
+// To the AST, run :
+// clang++ -fsyntax-only -Xclang -ast-dump tutorial1_example.cxx
+
+bool g()
+ {
+ if( 1 == 2 )
+ return false;
+ if( 1 == 2 )
+ {
+ return false;
+ }
+ if( true )
+ return false;
+ }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial3.cxx b/compilerplugins/clang/store/tutorial/tutorial3.cxx
new file mode 100644
index 0000000000..33a1249a34
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial3.cxx
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "tutorial3.hxx"
+
+/*
+This is a rewriter.
+
+It looks for if statements with a comparison followed by literal return false
+and modifies the return statements to 'return maybereturntrue;'
+*/
+
+namespace loplugin
+{
+
+// Ctor, pass arguments.
+Tutorial3::Tutorial3( const InstantiationData& data )
+ : FilteringRewritePlugin( data )
+ {
+ }
+
+void Tutorial3::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool Tutorial3::VisitIfStmt( const IfStmt* ifstmt )
+ {
+ if( ignoreLocation( ifstmt ))
+ return true;
+ if( const BinaryOperator* oper = dyn_cast< BinaryOperator >( ifstmt->getCond()))
+ {
+ if( oper->getOpcode() == BO_EQ )
+ {
+ // Modify the sub-statement if it is 'return false'.
+ modifyReturnFalse( ifstmt->getThen());
+ // Modify the sub-statement if it is '{ return false; }'.
+ if( const CompoundStmt* compound = dyn_cast< CompoundStmt >( ifstmt->getThen()))
+ {
+ if( compound->size() == 1 ) // one statement
+ modifyReturnFalse( *compound->body_begin());
+ }
+ }
+ }
+ return true;
+ }
+
+void Tutorial3::modifyReturnFalse( const Stmt* stmt )
+ {
+ // Is it return statement?
+ if( const ReturnStmt* returnstmt = dyn_cast< ReturnStmt >( stmt ))
+ {
+ // dyn_cast_or_null<> can also be passed NULL, unlike dyn_cast<>
+ if( const CXXBoolLiteralExpr* boolliteral = dyn_cast_or_null< CXXBoolLiteralExpr >( returnstmt->getRetValue()))
+ {
+ if( boolliteral->getValue() == false )
+ { // It is, modify the false to true using LO plugin helper function.
+ replaceText( boolliteral->getSourceRange(), "maybereturntrue" );
+ }
+ }
+ }
+ }
+
+// Register the plugin action with the LO plugin handling.
+static Plugin::Registration< Tutorial3 > tutorial3( "tutorial3" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/tutorial/tutorial3.hxx b/compilerplugins/clang/store/tutorial/tutorial3.hxx
new file mode 100644
index 0000000000..11378ef765
--- /dev/null
+++ b/compilerplugins/clang/store/tutorial/tutorial3.hxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#pragma once
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+// Similar like for Tutorial2, but this time the base class is RewritePlugin.
+class Tutorial3
+ : public loplugin::FilteringRewritePlugin< Tutorial3 >
+ {
+ public:
+ // One more argument for ctor.
+ Tutorial3( const InstantiationData& data );
+ virtual void run() override;
+ // Will be called for every if statement.
+ bool VisitIfStmt( const IfStmt* ifstmt );
+ private:
+ // Helper function to check if the statement is 'return false;' and
+ // modify it if yes.
+ void modifyReturnFalse( const Stmt* stmt );
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/unique2optional.cxx b/compilerplugins/clang/store/unique2optional.cxx
new file mode 100644
index 0000000000..e4b8efa1e1
--- /dev/null
+++ b/compilerplugins/clang/store/unique2optional.cxx
@@ -0,0 +1,264 @@
+/* -*- 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 <string>
+#include <iostream>
+
+#include "check.hxx"
+#include "plugin.hxx"
+#include "config_clang.h"
+#include "clang/AST/CXXInheritance.h"
+
+/**
+
+Look for places where we are using std::unique_ptr to hold a small object,
+where we should rather be using std::optional.
+
+*/
+
+namespace
+{
+class Unique2Optional : public loplugin::FilteringPlugin<Unique2Optional>
+{
+public:
+ explicit Unique2Optional(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual bool preRun() override { return true; }
+
+ virtual void run() override
+ {
+ if (preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitFieldDecl(const FieldDecl*);
+ bool VisitVarDecl(const VarDecl*);
+
+private:
+ bool doDecl(const DeclaratorDecl*);
+ bool isSmall(QualType type);
+};
+
+bool Unique2Optional::VisitFieldDecl(const FieldDecl* fieldDecl) { return doDecl(fieldDecl); }
+bool Unique2Optional::VisitVarDecl(const VarDecl*)
+{
+ return true; //doDecl(varDecl);
+}
+
+bool Unique2Optional::doDecl(const DeclaratorDecl* fieldDecl)
+{
+ if (ignoreLocation(fieldDecl))
+ return true;
+
+ SourceLocation spellingLocation
+ = compiler.getSourceManager().getSpellingLoc(fieldDecl->getBeginLoc());
+ StringRef fileName = getFilenameOfLocation(spellingLocation);
+
+ // pimpl pattern
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/unotools/closeveto.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svl/svdde.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/vcl/toolkit/morebtn.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/vcl/toolkit/morebtn.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/xmloff/xmlexp.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/xmloff/txtparae.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/xmloff/controlpropertyhdl.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/cui/source/inc/cuitabarea.hxx"))
+ return true;
+
+ // std::type_info is not movable or copyable
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/bridges/source/cpp_uno/gcc3_linux_x86-64/rtti.cxx"))
+ return true;
+
+ // TODO not sure what is going on here, get a compile error
+ if (loplugin::isSamePathname(fileName, SRCDIR "/vcl/inc/unx/printerjob.hxx"))
+ return true;
+
+ // Seems in bad taste to modify these
+ if (loplugin::isSamePathname(fileName, SRCDIR "/cui/source/tabpages/macroass.cxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/cui/source/inc/cuitabline.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/cui/source/inc/page.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sd/source/ui/sidebar/SlideBackground.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/hwpfilter/source/nodes.h"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/hwpfilter/source/hwpfile.h"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sw/source/uibase/inc/bookmark.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sw/inc/viewsh.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sw/source/uibase/sidebar/PageFormatPanel.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sw/source/uibase/sidebar/PageStylesPanel.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/inc/dpsave.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/source/ui/inc/dpgroupdlg.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/source/ui/inc/pvfundlg.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/inc/document.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/inc/scmod.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svx/gallery1.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/svx/inc/textchainflow.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svx/graphctl.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svx/float3d.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/dbaccess/source/ui/dlg/generalpage.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/extensions/source/propctrlr/cellbindinghandler.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/inc/chartlis.hxx"))
+ return true;
+
+ // header ordering issues make this hard to change
+ if (loplugin::isSamePathname(fileName, SRCDIR "/lotuswordpro/source/filter/lwpdlvlist.hxx"))
+ return true;
+
+ // the classes being allocate are ref-counted
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/source/filter/inc/xeextlst.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/source/filter/inc/xestyle.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/source/ui/inc/tpcalc.hxx"))
+ return true;
+
+ // not sure
+ if (fileName.contains("QtInstance.hxx")) // "/vcl/inc/qt5/QtInstance.hxx"))
+ return true;
+
+ // class is defined inside the module, typically some kind of child/pimpl/listener thing
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/sfx2/viewfrm.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svx/linectrl.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/include/svx/sidebar/LinePropertyPanelBase.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/vcl/source/fontsubset/ttcr.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/extensions/source/propctrlr/defaultforminspection.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/extensions/source/propctrlr/propertyhandler.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/inc/spellcheckcontext.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sw/source/core/inc/layouter.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sw/source/uibase/inc/numberingtypelistbox.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/lotuswordpro/inc/xfilter/xfdrawstyle.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName, SRCDIR "/sc/source/ui/inc/scuiimoptdlg.hxx"))
+ return true;
+
+ // One of the constructors initialises the field by receiving an unique_ptr
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/lotuswordpro/source/filter/lwpbreaksoverride.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/lotuswordpro/source/filter/lwpcharborderoverride.hxx"))
+ return true;
+ if (loplugin::isSamePathname(fileName,
+ SRCDIR "/lotuswordpro/source/filter/lwpparaborderoverride.hxx"))
+ return true;
+
+ if (!loplugin::TypeCheck(fieldDecl->getType()).ClassOrStruct("unique_ptr").StdNamespace())
+ return true;
+
+ auto templateDecl = dyn_cast_or_null<ClassTemplateSpecializationDecl>(
+ fieldDecl->getType()->getAsRecordDecl());
+ if (!templateDecl)
+ return true;
+ if (templateDecl->getTemplateArgs().size() == 0)
+ return true;
+ auto firstTemplateParamType = templateDecl->getTemplateArgs()[0].getAsType();
+ if (!isSmall(firstTemplateParamType))
+ return true;
+ auto paramRecordDecl = firstTemplateParamType->getAsCXXRecordDecl();
+ if (paramRecordDecl)
+ {
+ // if the pointed-to type has a virtual destructor, then we don't know for sure
+ // what size type will be stored there
+ if (!paramRecordDecl->isEffectivelyFinal())
+ if (CXXDestructorDecl* dd = paramRecordDecl->getDestructor())
+ if (dd->isVirtual())
+ return true;
+ // If it doesn't have a move constructor, then it would be hard to assign into it
+ // TODO this condition could be relaxed in some situations
+ if (!paramRecordDecl->hasMoveConstructor())
+ return true;
+ // the weld stuff needs to be heap allocated
+ if (loplugin::DeclCheck(paramRecordDecl).Class("CustomWeld").Namespace("weld"))
+ return true;
+ // ref-counted classes should be heap allocated
+ if (loplugin::DeclCheck(paramRecordDecl).Class("SvxContourItem"))
+ return true;
+ if (loplugin::DeclCheck(paramRecordDecl).Class("SvxAdjustItem"))
+ return true;
+ if (loplugin::DeclCheck(paramRecordDecl).Class("SwFormatNoBalancedColumns"))
+ return true;
+ if (loplugin::DeclCheck(paramRecordDecl).Class("SwFormatFollowTextFlow"))
+ return true;
+ }
+ // ignore pimpl pattern
+ if (fieldDecl->getName().contains("pImpl"))
+ return true;
+ if (fieldDecl->getName().contains("impl_"))
+ return true;
+ if (fieldDecl->getName().contains("mxImpl"))
+ return true;
+ if (fieldDecl->getName().contains("m_aImpl"))
+ return true;
+ report(DiagnosticsEngine::Warning, "can use std::optional here, heap-stored type is very small",
+ fieldDecl->getLocation())
+ << fieldDecl->getSourceRange();
+ if (paramRecordDecl)
+ report(DiagnosticsEngine::Note, "class being allocated is here",
+ paramRecordDecl->getLocation())
+ << paramRecordDecl->getSourceRange();
+ return true;
+}
+
+bool Unique2Optional::isSmall(QualType type)
+{
+ if (type->isIncompleteType())
+ return false;
+ clang::Type const* t2 = type.getTypePtrOrNull();
+ if (!t2)
+ return false;
+ // 8 bytes == 1 pointer on 64-bit CPU
+ return compiler.getASTContext().getTypeSizeInChars(t2).getQuantity() <= 16;
+}
+
+loplugin::Plugin::Registration<Unique2Optional> unique2optional("unique2optional");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/unusedcode.cxx b/compilerplugins/clang/store/unusedcode.cxx
new file mode 100644
index 0000000000..32fc4d3c2d
--- /dev/null
+++ b/compilerplugins/clang/store/unusedcode.cxx
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+/*
+This is technically a rewriter, but it actually only generates data about code.
+
+This is incomplete.
+
+Checks for all function declarations for whether they are used or not. This information
+should be output to files and in a second pass it should be checked (by another tool)
+which functions are never used.
+*/
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+class UnusedCode
+ : public loplugin::FilteringRewritePlugin< UnusedCode >
+ {
+ public:
+ explicit UnusedCode( CompilerInstance& compiler, Rewriter& rewriter );
+ virtual void run() override;
+ bool VisitFunctionDecl( const FunctionDecl* declaration );
+ };
+
+UnusedCode::UnusedCode( CompilerInstance& compiler, Rewriter& rewriter )
+ : FilteringRewritePlugin( compiler, rewriter )
+ {
+ }
+
+void UnusedCode::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool UnusedCode::VisitFunctionDecl( const FunctionDecl* declaration )
+ {
+ if( ignoreLocation( declaration ))
+ return true;
+ bool isUsed = declaration->isUsed();
+ if( const CXXMethodDecl* cxxmethod = dyn_cast< CXXMethodDecl >( declaration ))
+ {
+ if( !isUsed && cxxmethod->isVirtual())
+ { // Virtual methods are used also if a method they override is used.
+ for( CXXMethodDecl::method_iterator it = cxxmethod->begin_overridden_methods();
+ it != cxxmethod->end_overridden_methods();
+ ++it )
+ {
+ if( (*it)->isUsed())
+ {
+ isUsed = true;
+ break;
+ }
+ }
+ }
+ }
+ // Fully qualified name: declaration->getQualifiedNameAsString()
+ // Is used: isUsed
+ // The main source file compiled: compiler.getSourceManager().getFileEntryForID( compiler.getSourceManager().getMainFileID())->getName()
+ return true;
+ }
+
+static Plugin::Registration< UnusedCode > X( "unusedcode" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/unusedfieldsremove.cxx b/compilerplugins/clang/store/unusedfieldsremove.cxx
new file mode 100644
index 0000000000..61df036f2c
--- /dev/null
+++ b/compilerplugins/clang/store/unusedfieldsremove.cxx
@@ -0,0 +1,137 @@
+/* -*- 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/.
+ */
+
+#if !defined _WIN32 //TODO, #include <sys/mman.h>
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include "config_clang.h"
+#include "plugin.hxx"
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <cstring>
+
+/**
+ This is intended to be run as the second stage of the "unusedfields" clang plugin.
+*/
+
+namespace {
+
+class UnusedFieldsRemove:
+ public loplugin::FilteringRewritePlugin<UnusedFieldsRemove>
+{
+public:
+ explicit UnusedFieldsRemove(loplugin::InstantiationData const & data);
+ ~UnusedFieldsRemove();
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitFieldDecl( const FieldDecl* var );
+private:
+ // I use a brute-force approach - mmap the results file and do a linear search on it
+ // It works surprisingly well, because the file is small enough to fit into L2 cache on modern CPU's
+ size_t mmapFilesize;
+ int mmapFD;
+ char* mmappedData;
+};
+
+size_t getFilesize(const char* filename)
+{
+ struct stat st;
+ stat(filename, &st);
+ return st.st_size;
+}
+
+UnusedFieldsRemove::UnusedFieldsRemove(loplugin::InstantiationData const & data): FilteringRewritePlugin(data)
+{
+ static const char sInputFile[] = SRCDIR "/result.txt";
+ mmapFilesize = getFilesize(sInputFile);
+ //Open file
+ mmapFD = open(sInputFile, O_RDONLY, 0);
+ assert(mmapFD != -1);
+ //Execute mmap
+ mmappedData = static_cast<char*>(mmap(NULL, mmapFilesize, PROT_READ, MAP_PRIVATE, mmapFD, 0));
+ assert(mmappedData != NULL);
+}
+
+UnusedFieldsRemove::~UnusedFieldsRemove()
+{
+ //Cleanup
+ int rc = munmap(mmappedData, mmapFilesize);
+ assert(rc == 0);
+ (void)rc;
+ close(mmapFD);
+}
+
+std::string niceName(const FieldDecl* fieldDecl)
+{
+ return fieldDecl->getParent()->getQualifiedNameAsString() + " " +
+ fieldDecl->getNameAsString();
+}
+
+bool UnusedFieldsRemove::VisitFieldDecl( const FieldDecl* fieldDecl )
+{
+ if (rewriter == nullptr) {
+ return true;
+ }
+ if (ignoreLocation(fieldDecl)) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
+ fieldDecl->getCanonicalDecl()->getLocation()))) {
+ return true;
+ }
+
+ // don't mess with templates
+/* if (isa<CXXRecordDecl>(fieldDecl->getParent())) {
+ if (dyn_cast<CXXRecordDecl>(fieldDecl->getParent())->getDescribedClassTemplate() != nullptr) {
+ return true;
+ }
+ }
+*/
+ std::string aNiceName = " " + niceName(fieldDecl) + "\n";
+ const char *aNiceNameStr = aNiceName.c_str();
+ char* found = std::search(mmappedData, mmappedData + mmapFilesize, aNiceNameStr, aNiceNameStr + strlen(aNiceNameStr));
+ if(!(found < mmappedData + mmapFilesize)) {
+ return true;
+ }
+ SourceRange replaceRange(fieldDecl->getSourceRange());
+ // sometimes the declaration has a semicolon just after it, and it's much neater to remove that too.
+ if (rewriter->getRewrittenText(SourceRange(replaceRange.getEnd(), replaceRange.getEnd().getLocWithOffset(1))) == ";") {
+ replaceRange.setEnd(replaceRange.getEnd().getLocWithOffset(1));
+ }
+ // remove leading spaces
+ while (rewriter->getRewrittenText(SourceRange(replaceRange.getBegin().getLocWithOffset(-1), replaceRange.getBegin())) == " ")
+ {
+ replaceRange.setBegin(replaceRange.getBegin().getLocWithOffset(-1));
+ }
+ if (!replaceText(replaceRange, "")) {
+ report(
+ DiagnosticsEngine::Warning,
+ "Could not remove unused field (" + niceName(fieldDecl) + ")",
+ fieldDecl->getBeginLoc())
+ << fieldDecl->getSourceRange();
+ }
+ return true;
+}
+
+
+loplugin::Plugin::Registration< UnusedFieldsRemove > X("unusedfieldsremove", false);
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/unusedindex.cxx b/compilerplugins/clang/store/unusedindex.cxx
new file mode 100644
index 0000000000..63b9d4dcae
--- /dev/null
+++ b/compilerplugins/clang/store/unusedindex.cxx
@@ -0,0 +1,87 @@
+
+/* -*- 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 <string>
+#include <iostream>
+#include <unordered_set>
+
+#include "plugin.hxx"
+#include "check.hxx"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/StmtVisitor.h"
+
+/*
+ Mike Kaganski found a bug where the code was looping over a block and
+ not using the index var, and the loop was unnecessary.
+ So he wanted to have a look for other places like that.
+*/
+namespace
+{
+class UnusedIndex : public loplugin::FilteringPlugin<UnusedIndex>
+{
+public:
+ explicit UnusedIndex(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool TraverseForStmt(ForStmt* stmt);
+ bool VisitDeclRefExpr(DeclRefExpr const* stmt);
+
+private:
+ std::vector<VarDecl const*> mLoopVarDecls;
+ std::unordered_set<VarDecl const*> mFoundSet;
+};
+
+bool UnusedIndex::TraverseForStmt(ForStmt* stmt)
+{
+ if (ignoreLocation(stmt))
+ return true;
+
+ VarDecl const* loopVarDecl = nullptr;
+ if (stmt->getInit())
+ {
+ auto declStmt = dyn_cast<DeclStmt>(stmt->getInit());
+ if (declStmt && declStmt->isSingleDecl())
+ {
+ loopVarDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
+ }
+ }
+ if (loopVarDecl)
+ mLoopVarDecls.push_back(loopVarDecl);
+
+ // deliberately ignore the other parts of the for stmt, except for the body
+ auto ret = RecursiveASTVisitor::TraverseStmt(stmt->getBody());
+
+ if (loopVarDecl && mFoundSet.erase(loopVarDecl) == 0)
+ report(DiagnosticsEngine::Warning, "loop variable not used", loopVarDecl->getBeginLoc())
+ << loopVarDecl->getSourceRange();
+
+ if (loopVarDecl)
+ mLoopVarDecls.pop_back();
+ return ret;
+}
+
+bool UnusedIndex::VisitDeclRefExpr(DeclRefExpr const* stmt)
+{
+ auto varDecl = dyn_cast<VarDecl>(stmt->getDecl());
+ if (!varDecl)
+ return true;
+ if (std::find(mLoopVarDecls.begin(), mLoopVarDecls.end(), varDecl) != mLoopVarDecls.end())
+ mFoundSet.insert(varDecl);
+ return true;
+}
+
+loplugin::Plugin::Registration<UnusedIndex> X("unusedindex", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/unusedmethodsremove.cxx b/compilerplugins/clang/store/unusedmethodsremove.cxx
new file mode 100644
index 0000000000..ff87c6b777
--- /dev/null
+++ b/compilerplugins/clang/store/unusedmethodsremove.cxx
@@ -0,0 +1,153 @@
+/* -*- 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/.
+ */
+
+#if !defined _WIN32 //TODO, #include <sys/mman.h>
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include "config_clang.h"
+#include "plugin.hxx"
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <cstring>
+
+/**
+ This is intended to be run as the second stage of the "unusedmethods" clang plugin.
+*/
+
+namespace {
+
+class UnusedMethodsRemove:
+ public loplugin::FilteringRewritePlugin<UnusedMethodsRemove>
+{
+public:
+ explicit UnusedMethodsRemove(loplugin::InstantiationData const & data);
+ ~UnusedMethodsRemove();
+
+ virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitCXXMethodDecl( const CXXMethodDecl* var );
+private:
+ // I use a brute-force approach - mmap the results file and do a linear search on it
+ // It works surprisingly well, because the file is small enough to fit into L2 cache on modern CPU's
+ size_t mmapFilesize;
+ int mmapFD;
+ char* mmappedData;
+};
+
+size_t getFilesize(const char* filename)
+{
+ struct stat st;
+ stat(filename, &st);
+ return st.st_size;
+}
+
+UnusedMethodsRemove::UnusedMethodsRemove(loplugin::InstantiationData const & data): FilteringRewritePlugin(data)
+{
+ static const char sInputFile[] = SRCDIR "/result.txt";
+ mmapFilesize = getFilesize(sInputFile);
+ //Open file
+ mmapFD = open(sInputFile, O_RDONLY, 0);
+ assert(mmapFD != -1);
+ //Execute mmap
+ mmappedData = static_cast<char*>(mmap(NULL, mmapFilesize, PROT_READ, MAP_PRIVATE, mmapFD, 0));
+ assert(mmappedData != NULL);
+}
+
+UnusedMethodsRemove::~UnusedMethodsRemove()
+{
+ //Cleanup
+ int rc = munmap(mmappedData, mmapFilesize);
+ assert(rc == 0);
+ (void)rc;
+ close(mmapFD);
+}
+
+std::string niceName(const CXXMethodDecl* functionDecl)
+{
+ std::string s =
+ functionDecl->getReturnType().getCanonicalType().getAsString()
+ + " " + functionDecl->getParent()->getQualifiedNameAsString()
+ + "::" + functionDecl->getNameAsString()
+ + "(";
+ bool bFirst = true;
+ for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
+ if (bFirst)
+ bFirst = false;
+ else
+ s += ",";
+ s += pParmVarDecl->getType().getCanonicalType().getAsString();
+ }
+ s += ")";
+ if (functionDecl->isConst()) {
+ s += " const";
+ }
+ return s;
+}
+
+bool UnusedMethodsRemove::VisitCXXMethodDecl( const CXXMethodDecl* functionDecl )
+{
+ if (rewriter == nullptr) {
+ return true;
+ }
+ if (ignoreLocation(functionDecl)) {
+ return true;
+ }
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(functionDecl)) {
+ return true;
+ }
+
+ // don't mess with templates
+ if (functionDecl->getParent()->getDescribedClassTemplate() != nullptr) {
+ return true;
+ }
+ if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) {
+ return true;
+ }
+
+ std::string aNiceName = "\n" + niceName(functionDecl) + "\n";
+ const char *aNiceNameStr = aNiceName.c_str();
+ char* found = std::search(mmappedData, mmappedData + mmapFilesize, aNiceNameStr, aNiceNameStr + strlen(aNiceNameStr));
+ if(!(found < mmappedData + mmapFilesize)) {
+ return true;
+ }
+ SourceRange replaceRange(functionDecl->getSourceRange());
+ // sometimes the declaration has a semicolon just after it, and it's much neater to remove that too.
+ if (rewriter->getRewrittenText(SourceRange(replaceRange.getEnd(), replaceRange.getEnd().getLocWithOffset(1))) == ";") {
+ replaceRange.setEnd(replaceRange.getEnd().getLocWithOffset(1));
+ }
+ // remove leading spaces
+ while (rewriter->getRewrittenText(SourceRange(replaceRange.getBegin().getLocWithOffset(-1), replaceRange.getBegin())) == " ")
+ {
+ replaceRange.setBegin(replaceRange.getBegin().getLocWithOffset(-1));
+ }
+ if (!replaceText(replaceRange, "")) {
+ report(
+ DiagnosticsEngine::Warning,
+ "Could not remove unused method (" + niceName(functionDecl) + ")",
+ functionDecl->getBeginLoc())
+ << functionDecl->getSourceRange();
+ }
+ return true;
+}
+
+
+loplugin::Plugin::Registration< UnusedMethodsRemove > X("unusedmethodsremove", false);
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/store/valueof.cxx b/compilerplugins/clang/store/valueof.cxx
new file mode 100644
index 0000000000..808e0c158c
--- /dev/null
+++ b/compilerplugins/clang/store/valueof.cxx
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+/*
+This is a rewriter.
+
+Replaces all calls to the deprecated O(U)String::valueOf() .
+
+*/
+
+#include "plugin.hxx"
+
+namespace loplugin
+{
+
+class ConvertValueOf
+ : public loplugin::FilteringRewritePlugin< ConvertValueOf >
+ {
+ public:
+ explicit ConvertValueOf( CompilerInstance& compiler, Rewriter& rewriter );
+ virtual void run() override;
+ bool VisitCallExpr( const CallExpr* call );
+ private:
+ void removeCast( const Expr* arg );
+ };
+
+ConvertValueOf::ConvertValueOf( CompilerInstance& compiler, Rewriter& rewriter )
+ : FilteringRewritePlugin( compiler, rewriter )
+ {
+ }
+
+void ConvertValueOf::run()
+ {
+ TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+bool ConvertValueOf::VisitCallExpr( const CallExpr* call )
+ {
+ if( ignoreLocation( call ))
+ return true;
+ // Using getDirectCallee() here means that we find only calls
+ // that call the function directly (i.e. not using a pointer, for example).
+ // Use getCallee() to include also those :
+ // if( const FunctionDecl* func = dyn_cast_or_null< FunctionDecl >( call->getCalleeDecl()))
+ if( const FunctionDecl* func = call->getDirectCallee())
+ {
+ // Optimize, getQualifiedNameAsString() is reportedly expensive,
+ // so first check fast details like number of arguments or the (unqualified)
+ // name before checking the fully qualified name.
+ // See FunctionDecl for all the API about the function.
+ if( func->getIdentifier() != NULL
+ && ( func->getName() == "valueOf" ))
+ {
+ string qualifiedName = func->getQualifiedNameAsString();
+ if( qualifiedName == "rtl::OString::valueOf" )
+ {
+ // Further checks about arguments. Check mainly ParmVarDecl, VarDecl,
+ // ValueDecl and QualType for Clang API details.
+ string arg0 = func->getParamDecl( 0 )->getType().getAsString();
+ if( arg0 == "sal_Bool" )
+ replaceText( call->getCallee()->getSourceRange(), "OString::boolean" );
+ else
+ {
+ replaceText( call->getCallee()->getSourceRange(), "OString::number" );
+ removeCast( call->getArg( 0 ));
+ }
+ }
+ if( qualifiedName == "rtl::OUString::valueOf" )
+ {
+ // Further checks about arguments. Check mainly ParmVarDecl, VarDecl,
+ // ValueDecl and QualType for Clang API details.
+ string arg0 = func->getParamDecl( 0 )->getType().getAsString();
+ if( arg0 == "sal_Bool" )
+ replaceText( call->getCallee()->getSourceRange(), "OUString::boolean" );
+ else if( arg0 == "sal_Unicode" )
+ replaceText( call->getCallee()->getSourceRange(), "OUString" );
+ else
+ {
+ replaceText( call->getCallee()->getSourceRange(), "OUString::number" );
+ removeCast( call->getArg( 0 ));
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+void ConvertValueOf::removeCast( const Expr* arg )
+ {
+ arg = arg->IgnoreImpCasts();
+ if( const ExplicitCastExpr* cast = dyn_cast< ExplicitCastExpr >( arg ))
+ {
+// Explicit casts don't seem to actually always change the type (integer promotion
+// takes place first?), so remove also preceding implicit casts:
+// void f( int );
+// char a;
+// f( int( a ));
+// |-CallExpr 0x1a84f20 <line:6:5, col:16> 'void'
+// | |-ImplicitCastExpr 0x1a84f08 <col:5> 'void (*)(int)' <FunctionToPointerDecay>
+// | | `-DeclRefExpr 0x1a84eb8 <col:5> 'void (int)' lvalue Function 0x1a58900 'f' 'void (int)'
+// | `-CXXFunctionalCastExpr 0x1a84e90 <col:8, col:15> 'int' functional cast to int <NoOp>
+// | `-ImplicitCastExpr 0x1a84e78 <col:13> 'int' <IntegralCast>
+// | `-ImplicitCastExpr 0x1a84e60 <col:13> 'char' <LValueToRValue>
+// | `-DeclRefExpr 0x1a58b88 <col:13> 'char' lvalue Var 0x1a58ab0 'a' 'char'
+ const Expr* castFrom = cast->getSubExpr()->IgnoreImpCasts();
+ if( cast->getType()->isIntegerType() && castFrom->getType()->isIntegerType())
+ {
+ string fromType = castFrom->getType().getAsString();
+ if( fromType != "sal_Bool" && fromType != "bool" && fromType != "sal_Unicode" )
+ {
+ if( const CXXFunctionalCastExpr* funcCast = dyn_cast< CXXFunctionalCastExpr >( cast ))
+ {
+ removeText( CharSourceRange::getCharRange( funcCast->getLocStart(),
+ compiler.getSourceManager().getExpansionLoc( funcCast->getSubExpr()->getLocStart())));
+ removeText( CharSourceRange::getCharRange( locationAfterToken(
+ compiler.getSourceManager().getExpansionLoc( funcCast->getSubExpr()->getLocEnd())),
+ locationAfterToken( funcCast->getLocEnd())));
+ }
+ else if( const CXXNamedCastExpr* namedCast = dyn_cast< CXXNamedCastExpr >( cast ))
+ {
+ removeText( CharSourceRange::getCharRange( namedCast->getLocStart(),
+ compiler.getSourceManager().getExpansionLoc( namedCast->getSubExpr()->getLocStart())));
+ removeText( CharSourceRange::getCharRange( locationAfterToken(
+ compiler.getSourceManager().getExpansionLoc( namedCast->getSubExpr()->getLocEnd())),
+ locationAfterToken( namedCast->getLocEnd())));
+ }
+ else if( const CStyleCastExpr* cCast = dyn_cast< CStyleCastExpr >( cast ))
+ removeText( SourceRange( cCast->getLocStart(), cCast->getRParenLoc()));
+ else
+ abort();
+ }
+ }
+ }
+ }
+
+static Plugin::Registration< ConvertValueOf > X( "convertvalueof" );
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */