1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
20 Look for places where we can flatten the control flow in a method by returning early.
25 public loplugin::FilteringRewritePlugin
<Flatten
>
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 * );
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)
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()))
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
95 bool Flatten::TraverseIfStmt(IfStmt
* ifStmt
)
97 if (!WalkUpFromIfStmt(ifStmt
)) {
100 auto const saved
= mElseBranch
;
101 mElseBranch
= ifStmt
->getElse();
103 for (auto const sub
: ifStmt
->children()) {
104 if (!TraverseStmt(sub
)) {
113 bool Flatten::TraverseCompoundStmt(CompoundStmt
* compoundStmt
)
115 auto copy
= lastStmtInCompoundStmt
;
116 if (compoundStmt
->size() > 0)
117 lastStmtInCompoundStmt
= compoundStmt
->body_back();
119 lastStmtInCompoundStmt
= nullptr;
121 bool rv
= RecursiveASTVisitor
<Flatten
>::TraverseCompoundStmt(compoundStmt
);
123 lastStmtInCompoundStmt
= copy
;
127 bool Flatten::TraverseFunctionDecl(FunctionDecl
* fd
)
129 auto copy1
= functionDeclBody
;
131 functionDeclBody
= dyn_cast_or_null
<CompoundStmt
>(fd
->getBody());
133 bool rv
= RecursiveASTVisitor
<Flatten
>::TraverseFunctionDecl(fd
);
134 functionDeclBody
= copy1
;
135 functionDecl
= copy2
;
139 bool Flatten::TraverseCXXMethodDecl(CXXMethodDecl
* fd
)
141 auto copy1
= functionDeclBody
;
143 functionDeclBody
= dyn_cast_or_null
<CompoundStmt
>(fd
->getBody());
145 bool rv
= RecursiveASTVisitor
<Flatten
>::TraverseCXXMethodDecl(fd
);
146 functionDeclBody
= copy1
;
147 functionDecl
= copy2
;
151 bool Flatten::TraverseCXXConstructorDecl(CXXConstructorDecl
* fd
)
153 auto copy1
= functionDeclBody
;
155 functionDeclBody
= dyn_cast_or_null
<CompoundStmt
>(fd
->getBody());
157 bool rv
= RecursiveASTVisitor
<Flatten
>::TraverseCXXConstructorDecl(fd
);
158 functionDeclBody
= copy1
;
159 functionDecl
= copy2
;
163 bool Flatten::TraverseCXXConversionDecl(CXXConversionDecl
* fd
)
165 auto copy1
= functionDeclBody
;
167 functionDeclBody
= dyn_cast_or_null
<CompoundStmt
>(fd
->getBody());
169 bool rv
= RecursiveASTVisitor
<Flatten
>::TraverseCXXConversionDecl(fd
);
170 functionDeclBody
= copy1
;
171 functionDecl
= copy2
;
175 bool Flatten::TraverseCXXDestructorDecl(CXXDestructorDecl
* fd
)
177 auto copy1
= functionDeclBody
;
179 functionDeclBody
= dyn_cast_or_null
<CompoundStmt
>(fd
->getBody());
181 bool rv
= RecursiveASTVisitor
<Flatten
>::TraverseCXXDestructorDecl(fd
);
182 functionDeclBody
= copy1
;
183 functionDecl
= copy2
;
188 bool Flatten::VisitIfStmt(IfStmt
const * ifStmt
)
190 if (ignoreLocation(ifStmt
))
193 // ignore if we are part of an if/then/else/if chain
194 if (ifStmt
== mElseBranch
|| (ifStmt
->getElse() && isa
<IfStmt
>(ifStmt
->getElse())))
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
))
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();
215 if (!ifStmt
->getElse())
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
222 if ((thenThrowExpr
== nullptr) == (elseThrowExpr
== nullptr)) {
226 if (containsPreprocessingConditionalInclusion(ifStmt
->getSourceRange())) {
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()))
238 if (!rewrite1(ifStmt
))
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();
246 DiagnosticsEngine::Note
,
248 ifStmt
->getBeginLoc())
249 << ifStmt
->getSourceRange();
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()))
260 if (!rewrite2(ifStmt
))
263 DiagnosticsEngine::Warning
,
264 "unconditional throw in then branch, just flatten the else",
265 thenThrowExpr
->getBeginLoc())
266 << thenThrowExpr
->getSourceRange();
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
)
286 auto conditionRange
= ignoreMacroExpansions(ifStmt
->getCond()->getSourceRange());
287 if (!conditionRange
.isValid()) {
290 auto thenRange
= ignoreMacroExpansions(ifStmt
->getThen()->getSourceRange());
291 if (!thenRange
.isValid()) {
294 auto elseRange
= ignoreMacroExpansions(ifStmt
->getElse()->getSourceRange());
295 if (!elseRange
.isValid()) {
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
)
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
)) {
323 if (!removeText(elseKeywordRange
)) {
326 if (!replaceText(thenRange
, elseString
)) {
329 if (!replaceText(conditionRange
, *conditionString
)) {
336 bool Flatten::rewrite2(IfStmt
const * ifStmt
)
341 auto conditionRange
= ignoreMacroExpansions(ifStmt
->getCond()->getSourceRange());
342 if (!conditionRange
.isValid()) {
345 auto thenRange
= ignoreMacroExpansions(ifStmt
->getThen()->getSourceRange());
346 if (!thenRange
.isValid()) {
349 auto elseRange
= ignoreMacroExpansions(ifStmt
->getElse()->getSourceRange());
350 if (!elseRange
.isValid()) {
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
)) {
371 if (!removeText(elseKeywordRange
)) {
378 bool Flatten::rewriteLargeIf(IfStmt
const * ifStmt
)
383 auto conditionRange
= ignoreMacroExpansions(ifStmt
->getCond()->getSourceRange());
384 if (!conditionRange
.isValid()) {
387 auto thenRange
= ignoreMacroExpansions(ifStmt
->getThen()->getSourceRange());
388 if (!thenRange
.isValid()) {
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
)
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
)) {
413 if (!replaceText(conditionRange
, *conditionString
)) {
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
);
444 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(condExpr
))
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;
459 return compat::optional
<std::string
>();
461 else if (auto opCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(condExpr
))
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;
476 return compat::optional
<std::string
>();
478 else if (isa
<DeclRefExpr
>(condExpr
) || isa
<CallExpr
>(condExpr
) || isa
<MemberExpr
>(condExpr
))
485 std::string
stripOpenAndCloseBrace(std::string s
)
487 size_t i
= s
.find("{");
488 if (i
== std::string::npos
)
490 assert( !"did not find {" );
503 if (i
== std::string::npos
)
505 assert( !"did not find }" );
515 std::string
deindent(std::string
const & s
)
517 std::vector
<std::string
> lines
= split(s
);
519 for (auto s
: lines
) {
520 if (startswith(s
, " "))
529 std::vector
<std::string
> split(std::string s
)
531 if (s
.back() == '\n')
532 s
= s
.substr(0, s
.size()-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
);
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
)
553 while (i
< (int)s
.length() && s
[i
] == ' ')
558 std::string
padSpace(int iNoSpaces
)
561 for (int i
= 0; i
< iNoSpaces
; ++i
)
566 std::string
stripTrailingEmptyLines(std::string s
)
568 while (s
.back() == '\n')
569 s
.resize(s
.length() - 1);
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())) {
585 compiler
.getSourceManager().getImmediateMacroCallerLoc(
588 if (range
.getBegin().isMacroID()) {
590 if (Lexer::isAtStartOfMacroExpansion(
591 range
.getBegin(), compiler
.getSourceManager(),
592 compiler
.getLangOpts(), &loc
))
597 while (compiler
.getSourceManager().isMacroArgExpansion(range
.getEnd())) {
599 compiler
.getSourceManager().getImmediateMacroCallerLoc(
602 if (range
.getEnd().isMacroID()) {
604 if (Lexer::isAtEndOfMacroExpansion(
605 range
.getEnd(), compiler
.getSourceManager(),
606 compiler
.getLangOpts(), &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) == ' ')
629 startLoc
= startLoc
.getLocWithOffset(p1
- SM
.getCharacterData( startLoc
));
631 // look for trailing ";"
632 while (*(p2
+1) == ';')
634 // look for trailing " "
635 while (*(p2
+1) == ' ')
637 // look for single line comments attached to the end of the statement
638 if (*(p2
+1) == '/' && *(p2
+2) == '/')
641 while (*(p2
+1) && *(p2
+1) != '\n')
648 // make the source code we extract include any trailing "\n"
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());
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: */