bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / inlinesimplememberfunctions.cxx
blob668e9f252ab62d72ac318a0c9e1f3ef943b27d6a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <string>
12 #include "plugin.hxx"
13 #include "compat.hxx"
15 // Methods that purely return a local field should be declared in the header and be declared inline.
16 // So that the compiler can elide the function call and turn it into a simple fixed-offset-load instruction.
18 namespace {
20 class InlineSimpleMemberFunctions:
21 public loplugin::FilteringRewritePlugin<InlineSimpleMemberFunctions>
23 public:
24 explicit InlineSimpleMemberFunctions(loplugin::InstantiationData const & data): FilteringRewritePlugin(data) {}
26 virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
28 bool VisitCXXMethodDecl(const CXXMethodDecl * decl);
29 private:
30 bool rewrite(const CXXMethodDecl * functionDecl);
33 static bool oneAndOnlyOne(clang::Stmt::const_child_range range) {
34 if (range.begin() == range.end()) {
35 return false;
37 if (++range.begin() != range.end()) {
38 return false;
40 return true;
44 bool InlineSimpleMemberFunctions::VisitCXXMethodDecl(const CXXMethodDecl * functionDecl) {
45 if (ignoreLocation(functionDecl)) {
46 return true;
48 // no point in doing virtual methods, the compiler always has to generate a vtable entry and a method
49 if (functionDecl->isVirtual()) {
50 return true;
52 if (functionDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) {
53 return true;
55 if (!functionDecl->isInstance()) {
56 return true;
58 if (!functionDecl->isOutOfLine()) {
59 return true;
61 if( !functionDecl->hasBody()) {
62 return true;
64 if( functionDecl->isInlineSpecified()) {
65 return true;
67 if( functionDecl->getCanonicalDecl()->isInlineSpecified()) {
68 return true;
70 if( functionDecl->getNameAsString().find("Impl") != std::string::npos) {
71 return true;
73 // ignore stuff that forms part of the stable URE interface
74 if (isInUnoIncludeFile(functionDecl)) {
75 return true;
77 // ignore stuff like:
78 // template<class E> E * Sequence<E>::begin() { return getArray(); }
79 if( functionDecl->getParent()->getDescribedClassTemplate() != nullptr ) {
80 return true;
84 The chain here looks like
85 CompoundStmt
86 ReturnStmt
87 other stuff
88 CXXThisExpr
91 const CompoundStmt* compoundStmt = dyn_cast< CompoundStmt >( functionDecl->getBody() );
92 if (compoundStmt == nullptr) {
93 return true;
95 if (compoundStmt->body_begin() == compoundStmt->body_end()) {
96 return true;
100 const Stmt* childStmt = *compoundStmt->child_begin();
102 if (dyn_cast<ReturnStmt>( childStmt ) == nullptr) {
103 return true;
105 if (!oneAndOnlyOne(childStmt->children())) {
106 return true;
110 /* Don't warn if we see a method definition like
111 X X::a() {
112 return *this;
114 which translates to:
115 CompoundStmt
116 ReturnStmt
117 ImplicitCastExpr
118 UnaryOperator
119 CXXThisExpr
121 CompoundStmt
122 ReturnStmt
123 UnaryOperator
124 CXXThisExpr
126 childStmt = *childStmt->child_begin();
127 if (dyn_cast<ImplicitCastExpr>( childStmt ) != nullptr
128 && oneAndOnlyOne( childStmt->children() ))
130 const Stmt* childStmt2 = *childStmt->child_begin();
131 if (dyn_cast<UnaryOperator>( childStmt2 ) != nullptr
132 && oneAndOnlyOne(childStmt2->children()))
134 childStmt2 = *childStmt2->child_begin();
135 if (dyn_cast<CXXThisExpr>( childStmt2 ) != nullptr
136 && childStmt2->children().begin() == childStmt2->children().end())
138 return true;
142 if (dyn_cast<UnaryOperator>( childStmt ) != nullptr
143 && oneAndOnlyOne( childStmt->children() ))
145 const Stmt* childStmt2 = *childStmt->child_begin();
146 if (dyn_cast<CXXThisExpr>( childStmt2 ) != nullptr
147 && childStmt2->children().begin() == childStmt2->children().end())
149 return true;
153 /* look for a chains like:
154 CompoundStmt
155 ReturnStmt
156 stuff
157 CXXThisExpr
159 childStmt = *(*compoundStmt->child_begin())->child_begin();
160 while (1) {
161 if (dyn_cast<CallExpr>( childStmt ) != nullptr)
162 return true;
163 if (dyn_cast<CXXNewExpr>( childStmt ) != nullptr)
164 return true;
165 if (dyn_cast<CXXConstructExpr>( childStmt ) != nullptr)
166 return true;
167 if (dyn_cast<ConditionalOperator>( childStmt ) != nullptr)
168 return true;
169 if (dyn_cast<BinaryOperator>( childStmt ) != nullptr)
170 return true;
171 // exclude methods that return fields on incomplete types .e.g the pImpl pattern
172 const MemberExpr* memberExpr = dyn_cast<MemberExpr>( childStmt );
173 if (memberExpr != nullptr && memberExpr->getMemberDecl()) {
174 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl());
175 if (fieldDecl != nullptr)
177 // yes, a little bit of a hack. However, it is quite hard to determine if the method
178 // in question is accessing a field via a pImpl pattern.
179 if (fieldDecl->getType()->isIncompleteType())
180 return true;
181 if (fieldDecl->getNameAsString().find("Impl") != std::string::npos)
182 return true;
183 if (fieldDecl->getNameAsString().find("pImp") != std::string::npos)
184 return true;
185 // somewhere in VCL
186 if (fieldDecl->getNameAsString().find("mpGlobalSyncData") != std::string::npos)
187 return true;
188 std::string s = fieldDecl->getType().getAsString();
189 if (s.find("Impl") != std::string::npos || s.find("pImp") != std::string::npos || s.find("Internal") != std::string::npos)
190 return true;
193 if (dyn_cast<CXXThisExpr>( childStmt ) != nullptr) {
194 if (!rewrite(functionDecl))
196 report(
197 DiagnosticsEngine::Warning,
198 "inlinesimpleaccessmethods",
199 functionDecl->getSourceRange().getBegin())
200 << functionDecl->getSourceRange();
201 // display the location of the class member declaration so I don't have to search for it by hand
202 report(
203 DiagnosticsEngine::Note,
204 "inlinesimpleaccessmethods",
205 functionDecl->getCanonicalDecl()->getSourceRange().getBegin())
206 << functionDecl->getCanonicalDecl()->getSourceRange();
208 return true;
210 if ( childStmt->children().begin() == childStmt->children().end() )
211 return true;
212 childStmt = *childStmt->child_begin();
214 return true;
217 static std::string ReplaceString(std::string subject, const std::string& search,
218 const std::string& replace) {
219 size_t pos = 0;
220 while ((pos = subject.find(search, pos)) != std::string::npos) {
221 subject.replace(pos, search.length(), replace);
222 pos += replace.length();
224 return subject;
227 bool InlineSimpleMemberFunctions::rewrite(const CXXMethodDecl * functionDecl) {
228 if (rewriter == nullptr) {
229 return false;
231 // Only rewrite declarations in include files if a
232 // definition is also seen, to avoid compilation of a
233 // definition (in a main file only processed later) to fail
234 // with a "mismatch" error before the rewriter had a chance
235 // to act upon the definition.
236 if (!compiler.getSourceManager().isInMainFile(
237 compiler.getSourceManager().getSpellingLoc(
238 functionDecl->getNameInfo().getLoc())))
240 return false;
243 const char *p1, *p2;
245 // get the function body contents
246 p1 = compiler.getSourceManager().getCharacterData( compat::getBeginLoc(functionDecl->getBody()) );
247 p2 = compiler.getSourceManager().getCharacterData( compat::getEndLoc(functionDecl->getBody()) );
248 std::string s1( p1, p2 - p1 + 1);
250 /* we can't safely move around stuff containing comments, we mess up the resulting code */
251 if ( s1.find("/*") != std::string::npos || s1.find("//") != std::string::npos ) {
252 return false;
255 // strip linefeeds and any double-spaces, so we have a max of one space between tokens
256 s1 = ReplaceString(s1, "\r", "");
257 s1 = ReplaceString(s1, "\n", "");
258 s1 = ReplaceString(s1, "\t", " ");
259 s1 = ReplaceString(s1, " ", " ");
260 s1 = ReplaceString(s1, " ", " ");
261 s1 = ReplaceString(s1, " ", " ");
262 s1 = " " + s1;
264 // scan from the end of the function's body through the trailing whitespace, so we can do a nice clean remove
265 // commented out because for some reason it will sometimes chomp an extra token
266 // SourceLocation endOfRemoveLoc = functionDecl->getBody()->getLocEnd();
267 // for (;;) {
268 // endOfRemoveLoc = endOfRemoveLoc.getLocWithOffset(1);
269 // p1 = compiler.getSourceManager().getCharacterData( endOfRemoveLoc );
270 // if (*p1 != ' ' && *p1 != '\r' && *p1 != '\n' && *p1 != '\t')
271 // break;
272 // }
274 // remove the function's out of line body and declaration
275 RewriteOptions opts;
276 opts.RemoveLineIfEmpty = true;
277 if (!removeText(SourceRange(compat::getBeginLoc(functionDecl), compat::getEndLoc(functionDecl->getBody())), opts)) {
278 return false;
281 // scan forward until we find the semicolon
282 const FunctionDecl * canonicalDecl = functionDecl->getCanonicalDecl();
283 p1 = compiler.getSourceManager().getCharacterData( compat::getEndLoc(canonicalDecl) );
284 p2 = ++p1;
285 while (*p2 != 0 && *p2 != ';') p2++;
287 // insert the function body into the inline function definition (i.e. the one inside the class definition)
288 return replaceText(compat::getEndLoc(canonicalDecl).getLocWithOffset(p2 - p1 + 1), 1, s1);
291 loplugin::Plugin::Registration< InlineSimpleMemberFunctions > X("inlinesimplememberfunctions");
295 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */