summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/inlinesimplememberfunctions.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /compilerplugins/clang/inlinesimplememberfunctions.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compilerplugins/clang/inlinesimplememberfunctions.cxx')
-rw-r--r--compilerplugins/clang/inlinesimplememberfunctions.cxx295
1 files changed, 295 insertions, 0 deletions
diff --git a/compilerplugins/clang/inlinesimplememberfunctions.cxx b/compilerplugins/clang/inlinesimplememberfunctions.cxx
new file mode 100644
index 000000000..668e9f252
--- /dev/null
+++ b/compilerplugins/clang/inlinesimplememberfunctions.cxx
@@ -0,0 +1,295 @@
+/* -*- 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"
+#include "compat.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( compat::getBeginLoc(functionDecl->getBody()) );
+ p2 = compiler.getSourceManager().getCharacterData( compat::getEndLoc(functionDecl->getBody()) );
+ 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(compat::getBeginLoc(functionDecl), compat::getEndLoc(functionDecl->getBody())), opts)) {
+ return false;
+ }
+
+ // scan forward until we find the semicolon
+ const FunctionDecl * canonicalDecl = functionDecl->getCanonicalDecl();
+ p1 = compiler.getSourceManager().getCharacterData( compat::getEndLoc(canonicalDecl) );
+ 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(compat::getEndLoc(canonicalDecl).getLocWithOffset(p2 - p1 + 1), 1, s1);
+}
+
+loplugin::Plugin::Registration< InlineSimpleMemberFunctions > X("inlinesimplememberfunctions");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */