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/.
15 #include <unordered_set>
17 #include <clang/AST/CXXInheritance.h>
22 look for unnecessary parentheses
27 // Like clang::Stmt::IgnoreImplicit (lib/AST/Stmt.cpp), but also ignoring CXXConstructExpr and
28 // looking through implicit UserDefinedConversion's member function call:
29 Expr
const * ignoreAllImplicit(Expr
const * expr
) {
33 if (auto const e
= dyn_cast
<ExprWithCleanups
>(expr
)) {
34 expr
= e
->getSubExpr();
36 else if (auto const e
= dyn_cast
<CXXConstructExpr
>(expr
)) {
37 if (e
->getNumArgs() == 1) {
41 else if (auto const e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
42 expr
= e
->GetTemporaryExpr();
44 else if (auto const e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
45 expr
= e
->getSubExpr();
47 else if (auto const e
= dyn_cast
<ImplicitCastExpr
>(expr
)) {
48 expr
= e
->getSubExpr();
49 if (e
->getCastKind() == CK_UserDefinedConversion
) {
50 auto const ce
= cast
<CXXMemberCallExpr
>(expr
);
51 assert(ce
->getNumArgs() == 0);
52 expr
= ce
->getImplicitObjectArgument();
61 class UnnecessaryParen
:
62 public RecursiveASTVisitor
<UnnecessaryParen
>, public loplugin::RewritePlugin
65 explicit UnnecessaryParen(loplugin::InstantiationData
const & data
):
66 RewritePlugin(data
) {}
68 virtual void run() override
70 StringRef
fn( compiler
.getSourceManager().getFileEntryForID(
71 compiler
.getSourceManager().getMainFileID())->getName() );
72 // fixing this, makes the source in the .y files look horrible
73 if (loplugin::isSamePathname(fn
, WORKDIR
"/YaccTarget/unoidl/source/sourceprovider-parser.cxx"))
75 if (loplugin::isSamePathname(fn
, WORKDIR
"/YaccTarget/idlc/source/parser.cxx"))
78 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
81 bool VisitParenExpr(const ParenExpr
*);
82 bool VisitIfStmt(const IfStmt
*);
83 bool VisitDoStmt(const DoStmt
*);
84 bool VisitWhileStmt(const WhileStmt
*);
85 bool VisitForStmt(ForStmt
const * stmt
);
86 bool VisitSwitchStmt(const SwitchStmt
*);
87 bool VisitCaseStmt(const CaseStmt
*);
88 bool VisitReturnStmt(const ReturnStmt
* );
89 bool VisitCallExpr(const CallExpr
*);
90 bool VisitVarDecl(const VarDecl
*);
91 bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr
*);
92 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr
const *);
93 bool VisitConditionalOperator(ConditionalOperator
const * expr
);
94 bool VisitBinaryConditionalOperator(BinaryConditionalOperator
const * expr
);
95 bool VisitMemberExpr(const MemberExpr
*f
);
96 bool VisitCXXDeleteExpr(const CXXDeleteExpr
*);
98 void VisitSomeStmt(Stmt
const * stmt
, const Expr
* cond
, StringRef stmtName
);
100 void handleUnreachableCodeConditionParens(Expr
const * expr
);
102 // Hack for libxml2's BAD_CAST object-like macro (expanding to "(xmlChar *)"), which is
103 // typically used as if it were a function-like macro, e.g., as "BAD_CAST(pName)" in
104 // SwNode::dumpAsXml (sw/source/core/docnode/node.cxx):
105 bool isPrecededBy_BAD_CAST(Expr
const * expr
);
107 bool badCombination(SourceLocation loc
, int prevOffset
, int nextOffset
);
109 bool removeParens(ParenExpr
const * expr
);
111 std::unordered_set
<ParenExpr
const *> handled_
;
114 bool UnnecessaryParen::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr
const * expr
)
116 if (expr
->getKind() == UETT_SizeOf
&& !expr
->isArgumentType()) {
117 if (auto const e
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(expr
->getArgumentExpr()))) {
124 bool UnnecessaryParen::VisitConditionalOperator(ConditionalOperator
const * expr
) {
125 handleUnreachableCodeConditionParens(expr
->getCond());
129 bool UnnecessaryParen::VisitBinaryConditionalOperator(BinaryConditionalOperator
const * expr
) {
130 handleUnreachableCodeConditionParens(expr
->getCond());
134 bool UnnecessaryParen::VisitParenExpr(const ParenExpr
* parenExpr
)
136 if (ignoreLocation(parenExpr
))
138 if (parenExpr
->getLocStart().isMacroID())
140 if (handled_
.find(parenExpr
) != handled_
.end())
143 auto subExpr
= ignoreAllImplicit(parenExpr
->getSubExpr());
145 if (auto subParenExpr
= dyn_cast
<ParenExpr
>(subExpr
))
147 if (subParenExpr
->getLocStart().isMacroID())
150 DiagnosticsEngine::Warning
, "parentheses around parentheses",
151 parenExpr
->getLocStart())
152 << parenExpr
->getSourceRange();
153 handled_
.insert(subParenExpr
);
156 // Somewhat redundantly add parenExpr to handled_, so that issues within InitListExpr don't get
157 // reported twice (without having to change TraverseInitListExpr to only either traverse the
158 // syntactic or semantic form, as other plugins do):
160 if (isa
<DeclRefExpr
>(subExpr
)) {
161 if (!isPrecededBy_BAD_CAST(parenExpr
)) {
163 DiagnosticsEngine::Warning
, "unnecessary parentheses around identifier",
164 parenExpr
->getLocStart())
165 << parenExpr
->getSourceRange();
166 handled_
.insert(parenExpr
);
168 } else if (isa
<IntegerLiteral
>(subExpr
) || isa
<CharacterLiteral
>(subExpr
)
169 || isa
<FloatingLiteral
>(subExpr
) || isa
<ImaginaryLiteral
>(subExpr
)
170 || isa
<CXXBoolLiteralExpr
>(subExpr
) || isa
<CXXNullPtrLiteralExpr
>(subExpr
)
171 || isa
<ObjCBoolLiteralExpr
>(subExpr
))
173 auto const loc
= subExpr
->getLocStart();
174 if (loc
.isMacroID() && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(loc
))
176 // just in case the macro could also expand to something that /would/ require
181 DiagnosticsEngine::Warning
, "unnecessary parentheses around literal",
182 parenExpr
->getLocStart())
183 << parenExpr
->getSourceRange();
184 handled_
.insert(parenExpr
);
185 } else if (auto const e
= dyn_cast
<clang::StringLiteral
>(subExpr
)) {
186 if (e
->getNumConcatenated() == 1 && !isPrecededBy_BAD_CAST(parenExpr
)) {
188 DiagnosticsEngine::Warning
,
189 "unnecessary parentheses around single-token string literal",
190 parenExpr
->getLocStart())
191 << parenExpr
->getSourceRange();
192 handled_
.insert(parenExpr
);
194 } else if (auto const e
= dyn_cast
<UnaryOperator
>(subExpr
)) {
195 auto const op
= e
->getOpcode();
196 if (op
== UO_Plus
|| op
== UO_Minus
) {
197 auto const e2
= e
->getSubExpr();
198 if (isa
<IntegerLiteral
>(e2
) || isa
<FloatingLiteral
>(e2
) || isa
<ImaginaryLiteral
>(e2
)) {
200 DiagnosticsEngine::Warning
,
201 "unnecessary parentheses around signed numeric literal",
202 parenExpr
->getLocStart())
203 << parenExpr
->getSourceRange();
204 handled_
.insert(parenExpr
);
207 } else if (isa
<CXXNamedCastExpr
>(subExpr
)) {
208 if (!removeParens(parenExpr
)) {
210 DiagnosticsEngine::Warning
, "unnecessary parentheses around cast",
211 parenExpr
->getLocStart())
212 << parenExpr
->getSourceRange();
214 handled_
.insert(parenExpr
);
220 bool UnnecessaryParen::VisitIfStmt(const IfStmt
* ifStmt
)
222 handleUnreachableCodeConditionParens(ifStmt
->getCond());
223 VisitSomeStmt(ifStmt
, ifStmt
->getCond(), "if");
227 bool UnnecessaryParen::VisitDoStmt(const DoStmt
* doStmt
)
229 VisitSomeStmt(doStmt
, doStmt
->getCond(), "do");
233 bool UnnecessaryParen::VisitWhileStmt(const WhileStmt
* whileStmt
)
235 handleUnreachableCodeConditionParens(whileStmt
->getCond());
236 VisitSomeStmt(whileStmt
, whileStmt
->getCond(), "while");
240 bool UnnecessaryParen::VisitForStmt(ForStmt
const * stmt
) {
241 if (auto const cond
= stmt
->getCond()) {
242 handleUnreachableCodeConditionParens(cond
);
247 bool UnnecessaryParen::VisitSwitchStmt(const SwitchStmt
* switchStmt
)
249 VisitSomeStmt(switchStmt
, switchStmt
->getCond(), "switch");
253 bool UnnecessaryParen::VisitCaseStmt(const CaseStmt
* caseStmt
)
255 VisitSomeStmt(caseStmt
, caseStmt
->getLHS(), "case");
259 bool UnnecessaryParen::VisitReturnStmt(const ReturnStmt
* returnStmt
)
261 if (ignoreLocation(returnStmt
))
264 if (!returnStmt
->getRetValue())
266 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(returnStmt
->getRetValue()));
269 if (parenExpr
->getLocStart().isMacroID())
271 // assignments need extra parentheses or they generate a compiler warning
272 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
273 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
276 // only non-operator-calls for now
277 auto subExpr
= ignoreAllImplicit(parenExpr
->getSubExpr());
278 if (isa
<CallExpr
>(subExpr
) && !isa
<CXXOperatorCallExpr
>(subExpr
))
281 DiagnosticsEngine::Warning
, "parentheses immediately inside return statement",
282 parenExpr
->getLocStart())
283 << parenExpr
->getSourceRange();
284 handled_
.insert(parenExpr
);
289 void UnnecessaryParen::VisitSomeStmt(const Stmt
* stmt
, const Expr
* cond
, StringRef stmtName
)
291 if (ignoreLocation(stmt
))
294 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(cond
));
296 if (handled_
.find(parenExpr
) != handled_
.end()) {
299 if (parenExpr
->getLocStart().isMacroID())
301 // assignments need extra parentheses or they generate a compiler warning
302 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
303 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
305 if (auto const opCall
= dyn_cast
<CXXOperatorCallExpr
>(parenExpr
->getSubExpr())) {
306 if (opCall
->getOperator() == OO_Equal
) {
311 DiagnosticsEngine::Warning
, "parentheses immediately inside %0 statement",
312 parenExpr
->getLocStart())
314 << parenExpr
->getSourceRange();
315 handled_
.insert(parenExpr
);
319 bool UnnecessaryParen::VisitCallExpr(const CallExpr
* callExpr
)
321 if (ignoreLocation(callExpr
))
323 if (callExpr
->getNumArgs() != 1 || isa
<CXXOperatorCallExpr
>(callExpr
))
326 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(callExpr
->getArg(0)));
329 if (parenExpr
->getLocStart().isMacroID())
331 // assignments need extra parentheses or they generate a compiler warning
332 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
333 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
336 DiagnosticsEngine::Warning
, "parentheses immediately inside single-arg call",
337 parenExpr
->getLocStart())
338 << parenExpr
->getSourceRange();
339 handled_
.insert(parenExpr
);
343 bool UnnecessaryParen::VisitCXXDeleteExpr(const CXXDeleteExpr
* deleteExpr
)
345 if (ignoreLocation(deleteExpr
))
348 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(deleteExpr
->getArgument()));
351 if (parenExpr
->getLocStart().isMacroID())
353 // assignments need extra parentheses or they generate a compiler warning
354 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
355 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
358 DiagnosticsEngine::Warning
, "parentheses immediately inside delete expr",
359 parenExpr
->getLocStart())
360 << parenExpr
->getSourceRange();
361 handled_
.insert(parenExpr
);
365 bool UnnecessaryParen::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr
* callExpr
)
367 if (ignoreLocation(callExpr
))
369 if (callExpr
->getNumArgs() != 2)
372 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
374 auto Opc
= callExpr
->getOperator();
375 if (Opc
!= OO_Equal
&& Opc
!= OO_StarEqual
&&
376 Opc
!= OO_SlashEqual
&& Opc
!= OO_PercentEqual
&&
377 Opc
!= OO_PlusEqual
&& Opc
!= OO_MinusEqual
&&
378 Opc
!= OO_LessLessEqual
&& Opc
!= OO_GreaterGreaterEqual
&&
379 Opc
!= OO_AmpEqual
&& Opc
!= OO_CaretEqual
&&
383 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(callExpr
->getArg(1)));
386 if (parenExpr
->getLocStart().isMacroID())
388 // Sometimes parentheses make the RHS of an assignment easier to read by
389 // visually disambiguating the = from a call to ==
390 auto sub
= parenExpr
->getSubExpr();
391 if (isa
<BinaryOperator
>(sub
)
392 || isa
<CXXOperatorCallExpr
>(sub
)
393 || isa
<ConditionalOperator
>(sub
))
397 DiagnosticsEngine::Warning
, "parentheses immediately inside assignment",
398 parenExpr
->getLocStart())
399 << parenExpr
->getSourceRange();
400 handled_
.insert(parenExpr
);
404 bool UnnecessaryParen::VisitVarDecl(const VarDecl
* varDecl
)
406 if (ignoreLocation(varDecl
))
408 if (!varDecl
->getInit())
411 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(varDecl
->getInit()));
414 if (parenExpr
->getLocStart().isMacroID())
416 auto sub
= parenExpr
->getSubExpr();
417 if (isa
<BinaryOperator
>(sub
)
418 || isa
<CXXOperatorCallExpr
>(sub
)
419 || isa
<ConditionalOperator
>(sub
)
420 // these two are for "parentheses were disambiguated as a function declaration [-Werror,-Wvexing-parse]"
421 || isa
<CXXBindTemporaryExpr
>(sub
)
422 || isa
<CXXFunctionalCastExpr
>(sub
))
426 DiagnosticsEngine::Warning
, "parentheses immediately inside vardecl statement",
427 parenExpr
->getLocStart())
428 << parenExpr
->getSourceRange();
429 handled_
.insert(parenExpr
);
433 bool UnnecessaryParen::VisitMemberExpr(const MemberExpr
* memberExpr
)
435 if (ignoreLocation(memberExpr
))
438 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(memberExpr
->getBase()));
441 if (handled_
.find(parenExpr
) != handled_
.end())
443 if (parenExpr
->getLocStart().isMacroID())
446 auto sub
= parenExpr
->getSubExpr();
447 if (isa
<CallExpr
>(sub
)) {
448 if (isa
<CXXOperatorCallExpr
>(sub
))
450 } else if (isa
<CXXConstructExpr
>(sub
)) {
452 } else if (isa
<MemberExpr
>(sub
)) {
454 } else if (isa
<DeclRefExpr
>(sub
)) {
460 DiagnosticsEngine::Warning
, "unnecessary parentheses around member expr",
461 parenExpr
->getLocStart())
462 << parenExpr
->getSourceRange();
463 handled_
.insert(parenExpr
);
467 // Conservatively assume any parenthesised integer or Boolean (incl. Objective-C ones) literal in
468 // certain condition expressions (i.e., those for which handleUnreachableCodeConditionParens is
469 // called) to be parenthesised to silence Clang -Wunreachable-code, if that is either the whole
470 // condition expression or appears as a certain sub-expression (looking at what isConfigurationValue
471 // in Clang's lib/Analysis/ReachableCode.cpp looks for, descending into certain unary and binary
473 void UnnecessaryParen::handleUnreachableCodeConditionParens(Expr
const * expr
) {
475 auto const e
= ignoreAllImplicit(expr
);
476 if (auto const e1
= dyn_cast
<ParenExpr
>(e
)) {
477 auto const sub
= e1
->getSubExpr();
478 if (isa
<IntegerLiteral
>(sub
) || isa
<CXXBoolLiteralExpr
>(sub
)
479 || isa
<ObjCBoolLiteralExpr
>(sub
))
483 } else if (auto const e1
= dyn_cast
<UnaryOperator
>(e
)) {
484 if (e1
->getOpcode() == UO_LNot
) {
485 handleUnreachableCodeConditionParens(e1
->getSubExpr());
487 } else if (auto const e1
= dyn_cast
<BinaryOperator
>(e
)) {
488 if (e1
->isLogicalOp() || e1
->isComparisonOp()) {
489 handleUnreachableCodeConditionParens(e1
->getLHS());
490 handleUnreachableCodeConditionParens(e1
->getRHS());
495 bool UnnecessaryParen::isPrecededBy_BAD_CAST(Expr
const * expr
) {
496 if (expr
->getLocStart().isMacroID()) {
499 SourceManager
& SM
= compiler
.getSourceManager();
500 const char *p1
= SM
.getCharacterData( expr
->getLocStart().getLocWithOffset(-10) );
501 const char *p2
= SM
.getCharacterData( expr
->getLocStart() );
502 return std::string(p1
, p2
- p1
).find("BAD_CAST") != std::string::npos
;
507 bool badCombinationChar(char c
) {
508 return (c
>= 'A' && c
<= 'Z') || (c
>= 'a' && c
<= 'z') || (c
>= '0' && c
<= '9') || c
== '_'
509 || c
== '+' || c
== '-' || c
== '\'' || c
== '"';
514 bool UnnecessaryParen::badCombination(SourceLocation loc
, int prevOffset
, int nextOffset
) {
515 //TODO: check for start/end of file; take backslash-newline line concatenation into account
517 = compiler
.getSourceManager().getCharacterData(loc
.getLocWithOffset(prevOffset
))[0];
519 = compiler
.getSourceManager().getCharacterData(loc
.getLocWithOffset(nextOffset
))[0];
520 // An approximation of avoiding whatever combinations that would cause two adjacent tokens to be
521 // lexed differently, using, for now, letters (TODO: non-ASCII ones) and digits and '_'; '+' and
522 // '-' (to avoid ++, etc.); '\'' and '"' (to avoid u'x' or "foo"bar, etc.):
523 return badCombinationChar(c1
) && badCombinationChar(c2
);
526 bool UnnecessaryParen::removeParens(ParenExpr
const * expr
) {
527 if (rewriter
== nullptr) {
530 auto const firstBegin
= expr
->getLocStart();
531 auto secondBegin
= expr
->getLocEnd();
532 if (firstBegin
.isMacroID() || secondBegin
.isMacroID()) {
535 unsigned firstLen
= Lexer::MeasureTokenLength(
536 firstBegin
, compiler
.getSourceManager(), compiler
.getLangOpts());
537 for (auto l
= firstBegin
.getLocWithOffset(std::max
<unsigned>(firstLen
, 1));;
538 l
= l
.getLocWithOffset(1))
540 unsigned n
= Lexer::MeasureTokenLength(
541 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
547 unsigned secondLen
= Lexer::MeasureTokenLength(
548 secondBegin
, compiler
.getSourceManager(), compiler
.getLangOpts());
550 auto l
= secondBegin
.getLocWithOffset(-1);
551 auto const c
= compiler
.getSourceManager().getCharacterData(l
)[0];
553 if (compiler
.getSourceManager().getCharacterData(l
.getLocWithOffset(-1))[0] == '\\') {
556 } else if (!(c
== ' ' || c
== '\t' || c
== '\v' || c
== '\f')) {
562 if (!replaceText(firstBegin
, firstLen
, badCombination(firstBegin
, -1, firstLen
) ? " " : "")) {
565 DiagnosticsEngine::Fatal
,
566 "TODO: cannot rewrite opening parenthesis, needs investigation",
569 DiagnosticsEngine::Note
, "when removing these parentheses", expr
->getExprLoc())
570 << expr
->getSourceRange();
574 if (!replaceText(secondBegin
, secondLen
, badCombination(secondBegin
, -1, secondLen
) ? " " : ""))
576 //TODO: roll back first change
579 DiagnosticsEngine::Fatal
,
580 "TODO: cannot rewrite closing parenthesis, needs investigation",
583 DiagnosticsEngine::Note
, "when removing these parentheses", expr
->getExprLoc())
584 << expr
->getSourceRange();
591 loplugin::Plugin::Registration
< UnnecessaryParen
> X("unnecessaryparen", true);
595 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */