diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /compilerplugins/clang/store | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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: */ |