Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / flatten.cxx
bloba3d57812d924e841ab045a7674688fab6144d5dc
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 "compat.hxx"
11 #include "plugin.hxx"
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <set>
17 #include <stack>
19 /**
20 Look for places where we can flatten the control flow in a method by returning early.
22 namespace {
24 class Flatten:
25 public loplugin::FilteringRewritePlugin<Flatten>
27 public:
28 explicit Flatten(loplugin::InstantiationData const & data):
29 FilteringRewritePlugin(data) {}
31 virtual void run() override
33 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
36 bool TraverseIfStmt(IfStmt *);
37 bool TraverseCXXCatchStmt(CXXCatchStmt * );
38 bool TraverseCompoundStmt(CompoundStmt *);
39 bool TraverseFunctionDecl(FunctionDecl *);
40 bool TraverseCXXMethodDecl(CXXMethodDecl *);
41 bool TraverseCXXConstructorDecl(CXXConstructorDecl *);
42 bool TraverseCXXConversionDecl(CXXConversionDecl *);
43 bool TraverseCXXDestructorDecl(CXXDestructorDecl *);
44 bool VisitIfStmt(IfStmt const * );
45 private:
46 bool rewrite1(IfStmt const * );
47 bool rewrite2(IfStmt const * );
48 bool rewriteLargeIf(IfStmt const * );
49 SourceRange ignoreMacroExpansions(SourceRange range);
50 SourceRange extendOverComments(SourceRange range);
51 std::string getSourceAsString(SourceRange range);
52 compat::optional<std::string> invertCondition(
53 Expr const * condExpr, SourceRange conditionRange);
54 bool isLargeCompoundStmt(Stmt const *);
56 Stmt const * lastStmtInCompoundStmt = nullptr;
57 FunctionDecl const * functionDecl = nullptr;
58 CompoundStmt const * functionDeclBody = nullptr;
59 Stmt const * mElseBranch = nullptr;
62 static Stmt const * containsSingleThrowExpr(Stmt const * stmt)
64 if (auto compoundStmt = dyn_cast<CompoundStmt>(stmt)) {
65 if (compoundStmt->size() != 1)
66 return nullptr;
67 stmt = *compoundStmt->body_begin();
69 if (auto exprWithCleanups = dyn_cast<ExprWithCleanups>(stmt)) {
70 stmt = exprWithCleanups->getSubExpr();
72 return dyn_cast<CXXThrowExpr>(stmt);
75 static bool containsVarDecl(Stmt const * stmt)
77 if (auto compoundStmt = dyn_cast<CompoundStmt>(stmt)) {
78 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i) {
79 auto declStmt = dyn_cast<DeclStmt>(*i);
80 if (declStmt && isa<VarDecl>(*declStmt->decl_begin()))
81 return true;
83 return false;
85 auto declStmt = dyn_cast<DeclStmt>(stmt);
86 return declStmt && isa<VarDecl>(*declStmt->decl_begin());
89 bool Flatten::TraverseCXXCatchStmt(CXXCatchStmt* )
91 // ignore stuff inside catch statements, where doing a "if...else..throw" is more natural
92 return true;
95 bool Flatten::TraverseIfStmt(IfStmt * ifStmt)
97 if (!WalkUpFromIfStmt(ifStmt)) {
98 return false;
100 auto const saved = mElseBranch;
101 mElseBranch = ifStmt->getElse();
102 auto ret = true;
103 for (auto const sub: ifStmt->children()) {
104 if (!TraverseStmt(sub)) {
105 ret = false;
106 break;
109 mElseBranch = saved;
110 return ret;
113 bool Flatten::TraverseCompoundStmt(CompoundStmt * compoundStmt)
115 auto copy = lastStmtInCompoundStmt;
116 if (compoundStmt->size() > 0)
117 lastStmtInCompoundStmt = compoundStmt->body_back();
118 else
119 lastStmtInCompoundStmt = nullptr;
121 bool rv = RecursiveASTVisitor<Flatten>::TraverseCompoundStmt(compoundStmt);
123 lastStmtInCompoundStmt = copy;
124 return rv;
127 bool Flatten::TraverseFunctionDecl(FunctionDecl * fd)
129 auto copy1 = functionDeclBody;
130 auto copy2 = fd;
131 functionDeclBody = dyn_cast_or_null<CompoundStmt>(fd->getBody());
132 functionDecl = fd;
133 bool rv = RecursiveASTVisitor<Flatten>::TraverseFunctionDecl(fd);
134 functionDeclBody = copy1;
135 functionDecl = copy2;
136 return rv;
139 bool Flatten::TraverseCXXMethodDecl(CXXMethodDecl * fd)
141 auto copy1 = functionDeclBody;
142 auto copy2 = fd;
143 functionDeclBody = dyn_cast_or_null<CompoundStmt>(fd->getBody());
144 functionDecl = fd;
145 bool rv = RecursiveASTVisitor<Flatten>::TraverseCXXMethodDecl(fd);
146 functionDeclBody = copy1;
147 functionDecl = copy2;
148 return rv;
151 bool Flatten::TraverseCXXConstructorDecl(CXXConstructorDecl * fd)
153 auto copy1 = functionDeclBody;
154 auto copy2 = fd;
155 functionDeclBody = dyn_cast_or_null<CompoundStmt>(fd->getBody());
156 functionDecl = fd;
157 bool rv = RecursiveASTVisitor<Flatten>::TraverseCXXConstructorDecl(fd);
158 functionDeclBody = copy1;
159 functionDecl = copy2;
160 return rv;
163 bool Flatten::TraverseCXXConversionDecl(CXXConversionDecl * fd)
165 auto copy1 = functionDeclBody;
166 auto copy2 = fd;
167 functionDeclBody = dyn_cast_or_null<CompoundStmt>(fd->getBody());
168 functionDecl = fd;
169 bool rv = RecursiveASTVisitor<Flatten>::TraverseCXXConversionDecl(fd);
170 functionDeclBody = copy1;
171 functionDecl = copy2;
172 return rv;
175 bool Flatten::TraverseCXXDestructorDecl(CXXDestructorDecl * fd)
177 auto copy1 = functionDeclBody;
178 auto copy2 = fd;
179 functionDeclBody = dyn_cast_or_null<CompoundStmt>(fd->getBody());
180 functionDecl = fd;
181 bool rv = RecursiveASTVisitor<Flatten>::TraverseCXXDestructorDecl(fd);
182 functionDeclBody = copy1;
183 functionDecl = copy2;
184 return rv;
188 bool Flatten::VisitIfStmt(IfStmt const * ifStmt)
190 if (ignoreLocation(ifStmt))
191 return true;
193 // ignore if we are part of an if/then/else/if chain
194 if (ifStmt == mElseBranch || (ifStmt->getElse() && isa<IfStmt>(ifStmt->getElse())))
195 return true;
197 // look for a large if(){} block at the end of a function
198 if (!ifStmt->getElse()
199 && (functionDecl->getReturnType().isNull() || functionDecl->getReturnType()->isVoidType())
200 && functionDeclBody && functionDeclBody->size()
201 && functionDeclBody->body_back() == ifStmt
202 && isLargeCompoundStmt(ifStmt->getThen()))
204 if (!rewriteLargeIf(ifStmt))
206 report(
207 DiagnosticsEngine::Warning,
208 "large if statement at end of function, rather invert the condition and exit early, and flatten the function",
209 ifStmt->getBeginLoc())
210 << ifStmt->getSourceRange();
212 return true;
215 if (!ifStmt->getElse())
216 return true;
218 auto const thenThrowExpr = containsSingleThrowExpr(ifStmt->getThen());
219 auto const elseThrowExpr = containsSingleThrowExpr(ifStmt->getElse());
220 // If neither contains a throw, nothing to do; if both contain throws, no
221 // improvement:
222 if ((thenThrowExpr == nullptr) == (elseThrowExpr == nullptr)) {
223 return true;
226 if (containsPreprocessingConditionalInclusion(ifStmt->getSourceRange())) {
227 return true;
230 if (elseThrowExpr)
232 // if the "if" statement is not the last statement in its block, and it contains
233 // var decls in its then block, we cannot de-indent the then block without
234 // extending the lifetime of some variables, which may be problematic
235 if (ifStmt != lastStmtInCompoundStmt && containsVarDecl(ifStmt->getThen()))
236 return true;
238 if (!rewrite1(ifStmt))
240 report(
241 DiagnosticsEngine::Warning,
242 "unconditional throw in else branch, rather invert the condition, throw early, and flatten the normal case",
243 elseThrowExpr->getBeginLoc())
244 << elseThrowExpr->getSourceRange();
245 report(
246 DiagnosticsEngine::Note,
247 "if condition here",
248 ifStmt->getBeginLoc())
249 << ifStmt->getSourceRange();
252 if (thenThrowExpr)
254 // if the "if" statement is not the last statement in its block, and it contains
255 // var decls in its else block, we cannot de-indent the else block without
256 // extending the lifetime of some variables, which may be problematic
257 if (ifStmt != lastStmtInCompoundStmt && containsVarDecl(ifStmt->getElse()))
258 return true;
260 if (!rewrite2(ifStmt))
262 report(
263 DiagnosticsEngine::Warning,
264 "unconditional throw in then branch, just flatten the else",
265 thenThrowExpr->getBeginLoc())
266 << thenThrowExpr->getSourceRange();
269 return true;
272 static std::string stripOpenAndCloseBrace(std::string s);
273 static std::string stripTrailingEmptyLines(std::string s);
274 static std::string deindent(std::string const & s);
275 static std::vector<std::string> split(std::string s);
276 static bool startswith(std::string const & rStr, char const * pSubStr);
277 static int countLeadingSpaces(std::string const &);
278 static std::string padSpace(int iNoSpaces);
279 static bool replace(std::string & s, std::string const & from, std::string const & to);
281 bool Flatten::rewrite1(IfStmt const * ifStmt)
283 if (!rewriter)
284 return false;
286 auto conditionRange = ignoreMacroExpansions(ifStmt->getCond()->getSourceRange());
287 if (!conditionRange.isValid()) {
288 return false;
290 auto thenRange = ignoreMacroExpansions(ifStmt->getThen()->getSourceRange());
291 if (!thenRange.isValid()) {
292 return false;
294 auto elseRange = ignoreMacroExpansions(ifStmt->getElse()->getSourceRange());
295 if (!elseRange.isValid()) {
296 return false;
298 SourceRange elseKeywordRange = ifStmt->getElseLoc();
300 thenRange = extendOverComments(thenRange);
301 elseRange = extendOverComments(elseRange);
302 elseKeywordRange = extendOverComments(elseKeywordRange);
304 // in adjusting the formatting I assume that "{" starts on a new line
306 compat::optional<std::string> conditionString = invertCondition(ifStmt->getCond(), conditionRange);
307 if (!conditionString)
308 return false;
310 std::string thenString = getSourceAsString(thenRange);
311 if (auto compoundStmt = dyn_cast<CompoundStmt>(ifStmt->getThen())) {
312 if (compoundStmt->getLBracLoc().isValid()) {
313 thenString = stripOpenAndCloseBrace(thenString);
316 thenString = deindent(thenString);
318 std::string elseString = getSourceAsString(elseRange);
320 if (!replaceText(elseRange, thenString)) {
321 return false;
323 if (!removeText(elseKeywordRange)) {
324 return false;
326 if (!replaceText(thenRange, elseString)) {
327 return false;
329 if (!replaceText(conditionRange, *conditionString)) {
330 return false;
333 return true;
336 bool Flatten::rewrite2(IfStmt const * ifStmt)
338 if (!rewriter)
339 return false;
341 auto conditionRange = ignoreMacroExpansions(ifStmt->getCond()->getSourceRange());
342 if (!conditionRange.isValid()) {
343 return false;
345 auto thenRange = ignoreMacroExpansions(ifStmt->getThen()->getSourceRange());
346 if (!thenRange.isValid()) {
347 return false;
349 auto elseRange = ignoreMacroExpansions(ifStmt->getElse()->getSourceRange());
350 if (!elseRange.isValid()) {
351 return false;
353 SourceRange elseKeywordRange = ifStmt->getElseLoc();
355 elseRange = extendOverComments(elseRange);
356 elseKeywordRange = extendOverComments(elseKeywordRange);
358 // in adjusting the formatting I assume that "{" starts on a new line
360 std::string elseString = getSourceAsString(elseRange);
361 if (auto compoundStmt = dyn_cast<CompoundStmt>(ifStmt->getElse())) {
362 if (compoundStmt->getLBracLoc().isValid()) {
363 elseString = stripOpenAndCloseBrace(elseString);
366 elseString = deindent(elseString);
368 if (!replaceText(elseRange, elseString)) {
369 return false;
371 if (!removeText(elseKeywordRange)) {
372 return false;
375 return true;
378 bool Flatten::rewriteLargeIf(IfStmt const * ifStmt)
380 if (!rewriter)
381 return false;
383 auto conditionRange = ignoreMacroExpansions(ifStmt->getCond()->getSourceRange());
384 if (!conditionRange.isValid()) {
385 return false;
387 auto thenRange = ignoreMacroExpansions(ifStmt->getThen()->getSourceRange());
388 if (!thenRange.isValid()) {
389 return false;
392 thenRange = extendOverComments(thenRange);
394 // in adjusting the formatting I assume that "{" starts on a new line
396 compat::optional<std::string> conditionString = invertCondition(ifStmt->getCond(), conditionRange);
397 if (!conditionString)
398 return false;
400 std::string thenString = getSourceAsString(thenRange);
401 if (auto compoundStmt = dyn_cast<CompoundStmt>(ifStmt->getThen())) {
402 if (compoundStmt->getLBracLoc().isValid()) {
403 thenString = stripOpenAndCloseBrace(thenString);
406 int iNoSpaces = countLeadingSpaces(thenString);
407 thenString = padSpace(iNoSpaces) + "return;\n\n" + deindent(thenString);
408 thenString = stripTrailingEmptyLines(thenString);
410 if (!replaceText(thenRange, thenString)) {
411 return false;
413 if (!replaceText(conditionRange, *conditionString)) {
414 return false;
417 return true;
420 compat::optional<std::string> Flatten::invertCondition(Expr const * condExpr, SourceRange conditionRange)
422 std::string s = getSourceAsString(conditionRange);
424 condExpr = condExpr->IgnoreImpCasts();
426 if (auto exprWithCleanups = dyn_cast<ExprWithCleanups>(condExpr))
427 condExpr = exprWithCleanups->getSubExpr()->IgnoreImpCasts();
429 // an if statement will automatically invoke a bool-conversion method
430 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(condExpr))
432 if (memberCallExpr->getMethodDecl() && isa<CXXConversionDecl>(memberCallExpr->getMethodDecl()))
433 condExpr = memberCallExpr->getImplicitObjectArgument()->IgnoreImpCasts();
436 if (auto unaryOp = dyn_cast<UnaryOperator>(condExpr))
438 if (unaryOp->getOpcode() != UO_LNot)
439 return "!(" + s + ")";
440 auto i = s.find("!");
441 assert (i != std::string::npos);
442 s = s.substr(i+1);
444 else if (auto binaryOp = dyn_cast<BinaryOperator>(condExpr))
446 bool ok = true;
447 switch (binaryOp->getOpcode())
449 case BO_LT: ok = replace(s, "<", ">="); break;
450 case BO_GT: ok = replace(s, ">", "<="); break;
451 case BO_LE: ok = replace(s, "<=", ">"); break;
452 case BO_GE: ok = replace(s, ">=", "<"); break;
453 case BO_EQ: ok = replace(s, "==", "!="); break;
454 case BO_NE: ok = replace(s, "!=", "=="); break;
455 default:
456 s = "!(" + s + ")";
458 if (!ok)
459 return compat::optional<std::string>();
461 else if (auto opCallExpr = dyn_cast<CXXOperatorCallExpr>(condExpr))
463 bool ok = true;
464 switch (opCallExpr->getOperator())
466 case OO_Less: ok = replace(s, "<", ">="); break;
467 case OO_Greater: ok = replace(s, ">", "<="); break;
468 case OO_LessEqual: ok = replace(s, "<=", ">"); break;
469 case OO_GreaterEqual: ok = replace(s, ">=", "<"); break;
470 case OO_EqualEqual: ok = replace(s, "==", "!="); break;
471 case OO_ExclaimEqual: ok = replace(s, "!=", "=="); break;
472 default:
473 s = "!(" + s + ")";
475 if (!ok)
476 return compat::optional<std::string>();
478 else if (isa<DeclRefExpr>(condExpr) || isa<CallExpr>(condExpr) || isa<MemberExpr>(condExpr))
479 s = "!" + s;
480 else
481 s = "!(" + s + ")";
482 return s;
485 std::string stripOpenAndCloseBrace(std::string s)
487 size_t i = s.find("{");
488 if (i == std::string::npos)
490 assert( !"did not find {" );
491 abort();
494 ++i;
495 // strip to line end
496 while (s[i] == ' ')
497 ++i;
498 if (s[i] == '\n')
499 ++i;
500 s = s.substr(i);
502 i = s.rfind("}");
503 if (i == std::string::npos)
505 assert( !"did not find }" );
506 abort();
508 --i;
509 while (s[i] == ' ')
510 --i;
511 s = s.substr(0,i);
512 return s;
515 std::string deindent(std::string const & s)
517 std::vector<std::string> lines = split(s);
518 std::string rv;
519 for (auto s : lines) {
520 if (startswith(s, " "))
521 rv += s.substr(4);
522 else
523 rv += s;
524 rv += "\n";
526 return rv;
529 std::vector<std::string> split(std::string s)
531 if (s.back() == '\n')
532 s = s.substr(0, s.size()-1);
533 size_t next = -1;
534 std::vector<std::string> rv;
537 size_t current = next + 1;
538 next = s.find_first_of( "\n", current );
539 rv.push_back(s.substr( current, next - current ));
541 while (next != std::string::npos);
542 return rv;
545 bool startswith(std::string const & rStr, char const * pSubStr)
547 return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
550 int countLeadingSpaces(std::string const & s)
552 int i = 0;
553 while (i < (int)s.length() && s[i] == ' ')
554 i++;
555 return i;
558 std::string padSpace(int iNoSpaces)
560 std::string s;
561 for (int i = 0; i < iNoSpaces; ++i)
562 s += " ";
563 return s;
566 std::string stripTrailingEmptyLines(std::string s)
568 while (s.back() == '\n')
569 s.resize(s.length() - 1);
570 return s;
573 bool replace(std::string & s, std::string const & from, std::string const & to)
575 auto i = s.find(from);
576 assert (i != std::string::npos);
577 s.replace(i, from.length(), to);
578 // just in case we have something really weird, like the operator token is also present in the rest of the condition somehow
579 return s.find(from) == std::string::npos;
582 SourceRange Flatten::ignoreMacroExpansions(SourceRange range) {
583 while (compiler.getSourceManager().isMacroArgExpansion(range.getBegin())) {
584 range.setBegin(
585 compiler.getSourceManager().getImmediateMacroCallerLoc(
586 range.getBegin()));
588 if (range.getBegin().isMacroID()) {
589 SourceLocation loc;
590 if (Lexer::isAtStartOfMacroExpansion(
591 range.getBegin(), compiler.getSourceManager(),
592 compiler.getLangOpts(), &loc))
594 range.setBegin(loc);
597 while (compiler.getSourceManager().isMacroArgExpansion(range.getEnd())) {
598 range.setEnd(
599 compiler.getSourceManager().getImmediateMacroCallerLoc(
600 range.getEnd()));
602 if (range.getEnd().isMacroID()) {
603 SourceLocation loc;
604 if (Lexer::isAtEndOfMacroExpansion(
605 range.getEnd(), compiler.getSourceManager(),
606 compiler.getLangOpts(), &loc))
608 range.setEnd(loc);
611 return range.getBegin().isMacroID() || range.getEnd().isMacroID()
612 ? SourceRange() : range;
616 * Extend the SourceRange to include any leading and trailing whitespace, and any comments.
618 SourceRange Flatten::extendOverComments(SourceRange range)
620 SourceManager& SM = compiler.getSourceManager();
621 SourceLocation startLoc = range.getBegin();
622 SourceLocation endLoc = range.getEnd();
623 char const *p1 = SM.getCharacterData( startLoc );
624 char const *p2 = SM.getCharacterData( endLoc );
626 // scan backwards from the beginning to include any spaces on that line
627 while (*(p1-1) == ' ')
628 --p1;
629 startLoc = startLoc.getLocWithOffset(p1 - SM.getCharacterData( startLoc ));
631 // look for trailing ";"
632 while (*(p2+1) == ';')
633 ++p2;
634 // look for trailing " "
635 while (*(p2+1) == ' ')
636 ++p2;
637 // look for single line comments attached to the end of the statement
638 if (*(p2+1) == '/' && *(p2+2) == '/')
640 p2 += 2;
641 while (*(p2+1) && *(p2+1) != '\n')
642 ++p2;
643 if (*(p2+1) == '\n')
644 ++p2;
646 else
648 // make the source code we extract include any trailing "\n"
649 if (*(p2+1) == '\n')
650 ++p2;
652 endLoc = endLoc.getLocWithOffset(p2 - SM.getCharacterData( endLoc ));
654 return SourceRange(startLoc, endLoc);
657 std::string Flatten::getSourceAsString(SourceRange range)
659 SourceManager& SM = compiler.getSourceManager();
660 SourceLocation startLoc = range.getBegin();
661 SourceLocation endLoc = range.getEnd();
662 char const *p1 = SM.getCharacterData( startLoc );
663 char const *p2 = SM.getCharacterData( endLoc );
664 p2 += Lexer::MeasureTokenLength( endLoc, SM, compiler.getLangOpts());
665 if (p2 < p1) {
666 // workaround clang weirdness, but don't return empty string
667 // in case it happens during code replacement
668 return "clang returned bad pointers";
670 if (p2 - p1 > 64 * 1024) {
671 // workaround clang weirdness, but don't return empty string
672 // in case it happens during code replacement
673 return "clang returned overlay large source range";
675 return std::string( p1, p2 - p1);
678 bool Flatten::isLargeCompoundStmt(Stmt const * stmt)
680 auto stmtRange = stmt->getSourceRange();
681 std::string s = getSourceAsString(stmtRange);
682 return std::count(s.begin(), s.end(), '\n') > 10;
685 loplugin::Plugin::Registration< Flatten > X("flatten", false);
689 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */