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/.
18 #include <clang/AST/CXXInheritance.h>
20 #include "config_clang.h"
27 Simplify boolean expressions involving smart pointers e.g.
32 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
36 class SimplifyPointerToBool
: public loplugin::FilteringRewritePlugin
<SimplifyPointerToBool
>
39 explicit SimplifyPointerToBool(loplugin::InstantiationData
const& data
)
40 : FilteringRewritePlugin(data
)
44 virtual void run() override
47 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
50 bool VisitImplicitCastExpr(ImplicitCastExpr
const*);
51 bool VisitBinaryOperator(BinaryOperator
const*);
53 bool PreTraverseUnaryOperator(UnaryOperator
* expr
)
55 if (expr
->getOpcode() == UO_LNot
)
57 contextuallyConvertedExprs_
.push_back(expr
->getSubExpr()->IgnoreParenImpCasts());
62 bool PostTraverseUnaryOperator(UnaryOperator
* expr
, bool)
64 if (expr
->getOpcode() == UO_LNot
)
66 assert(!contextuallyConvertedExprs_
.empty());
67 contextuallyConvertedExprs_
.pop_back();
72 bool TraverseUnaryOperator(UnaryOperator
* expr
)
74 auto res
= PreTraverseUnaryOperator(expr
);
76 res
= FilteringRewritePlugin::TraverseUnaryOperator(expr
);
77 PostTraverseUnaryOperator(expr
, res
);
81 #if CLANG_VERSION < 110000
82 bool TraverseUnaryLNot(UnaryOperator
* expr
) { return TraverseUnaryOperator(expr
); }
85 bool PreTraverseBinaryOperator(BinaryOperator
* expr
)
87 auto const op
= expr
->getOpcode();
88 if (op
== BO_LAnd
|| op
== BO_LOr
)
90 contextuallyConvertedExprs_
.push_back(expr
->getLHS()->IgnoreParenImpCasts());
91 contextuallyConvertedExprs_
.push_back(expr
->getRHS()->IgnoreParenImpCasts());
96 bool PostTraverseBinaryOperator(BinaryOperator
* expr
, bool)
98 auto const op
= expr
->getOpcode();
99 if (op
== BO_LAnd
|| op
== BO_LOr
)
101 assert(contextuallyConvertedExprs_
.size() >= 2);
102 contextuallyConvertedExprs_
.pop_back();
103 contextuallyConvertedExprs_
.pop_back();
108 bool TraverseBinaryOperator(BinaryOperator
* expr
)
110 auto res
= PreTraverseBinaryOperator(expr
);
112 res
= FilteringRewritePlugin::TraverseBinaryOperator(expr
);
113 PostTraverseBinaryOperator(expr
, res
);
117 #if CLANG_VERSION < 110000
118 bool TraverseBinLAnd(BinaryOperator
* expr
) { return TraverseBinaryOperator(expr
); }
119 bool TraverseBinLOr(BinaryOperator
* expr
) { return TraverseBinaryOperator(expr
); }
122 bool PreTraverseConditionalOperator(ConditionalOperator
* expr
)
124 contextuallyConvertedExprs_
.push_back(expr
->getCond()->IgnoreParenImpCasts());
128 bool PostTraverseConditionalOperator(ConditionalOperator
*, bool)
130 assert(!contextuallyConvertedExprs_
.empty());
131 contextuallyConvertedExprs_
.pop_back();
135 bool TraverseConditionalOperator(ConditionalOperator
* expr
)
137 auto res
= PreTraverseConditionalOperator(expr
);
139 res
= FilteringRewritePlugin::TraverseConditionalOperator(expr
);
140 PostTraverseConditionalOperator(expr
, res
);
144 bool PreTraverseIfStmt(IfStmt
* stmt
)
146 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
150 bool PostTraverseIfStmt(IfStmt
*, bool)
152 assert(!contextuallyConvertedExprs_
.empty());
153 contextuallyConvertedExprs_
.pop_back();
157 bool TraverseIfStmt(IfStmt
* stmt
)
159 auto res
= PreTraverseIfStmt(stmt
);
161 res
= FilteringRewritePlugin::TraverseIfStmt(stmt
);
162 PostTraverseIfStmt(stmt
, res
);
166 bool PreTraverseWhileStmt(WhileStmt
* stmt
)
168 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
172 bool PostTraverseWhileStmt(WhileStmt
*, bool)
174 assert(!contextuallyConvertedExprs_
.empty());
175 contextuallyConvertedExprs_
.pop_back();
179 bool TraverseWhileStmt(WhileStmt
* stmt
)
181 auto res
= PreTraverseWhileStmt(stmt
);
183 res
= FilteringRewritePlugin::TraverseWhileStmt(stmt
);
184 PostTraverseWhileStmt(stmt
, res
);
188 bool PreTraverseDoStmt(DoStmt
* stmt
)
190 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
194 bool PostTraverseDoStmt(DoStmt
*, bool)
196 assert(!contextuallyConvertedExprs_
.empty());
197 contextuallyConvertedExprs_
.pop_back();
201 bool TraverseDoStmt(DoStmt
* stmt
)
203 auto res
= PreTraverseDoStmt(stmt
);
205 res
= FilteringRewritePlugin::TraverseDoStmt(stmt
);
206 PostTraverseDoStmt(stmt
, res
);
210 bool PreTraverseForStmt(ForStmt
* stmt
)
212 auto const e
= stmt
->getCond();
215 contextuallyConvertedExprs_
.push_back(e
->IgnoreParenImpCasts());
220 bool PostTraverseForStmt(ForStmt
* stmt
, bool)
222 if (stmt
->getCond() != nullptr)
224 assert(!contextuallyConvertedExprs_
.empty());
225 contextuallyConvertedExprs_
.pop_back();
230 bool TraverseForStmt(ForStmt
* stmt
)
232 auto res
= PreTraverseForStmt(stmt
);
234 res
= FilteringRewritePlugin::TraverseForStmt(stmt
);
235 PostTraverseForStmt(stmt
, res
);
240 bool isContextuallyConverted(Expr
const* expr
) const
242 return std::find(contextuallyConvertedExprs_
.begin(), contextuallyConvertedExprs_
.end(),
244 != contextuallyConvertedExprs_
.end();
247 // Get the source range starting at the "."or "->" (plus any preceding non-comment white space):
248 SourceRange
getCallSourceRange(CXXMemberCallExpr
const* expr
) const
250 if (expr
->getImplicitObjectArgument() == nullptr)
252 //TODO: Arguably, such a call of a `get` member function from within some member
253 // function (so that syntactically no caller is mentioned) should already be handled
254 // differently when reporting it (just "drop the get()" does not make sense), instead of
255 // being filtered here:
258 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
259 auto start
= compiler
.getSourceManager().getSpellingLoc(expr
->getExprLoc());
260 if (!start
.isValid())
266 start
= Lexer::GetBeginningOfToken(start
.getLocWithOffset(-1),
267 compiler
.getSourceManager(), compiler
.getLangOpts());
268 auto const s
= StringRef(compiler
.getSourceManager().getCharacterData(start
),
269 Lexer::MeasureTokenLength(start
, compiler
.getSourceManager(),
270 compiler
.getLangOpts()));
271 if (s
.empty() || s
.startswith("\\\n"))
275 if (s
!= "." && s
!= "->")
283 auto start1
= Lexer::GetBeginningOfToken(
284 start
.getLocWithOffset(-1), compiler
.getSourceManager(), compiler
.getLangOpts());
285 auto const s
= StringRef(compiler
.getSourceManager().getCharacterData(start1
),
286 Lexer::MeasureTokenLength(start1
, compiler
.getSourceManager(),
287 compiler
.getLangOpts()));
288 if (!(s
.empty() || s
.startswith("\\\n")))
294 return SourceRange(start
,
295 compiler
.getSourceManager().getSpellingLoc(compat::getEndLoc(expr
)));
298 //TODO: There are some more places where an expression is contextually converted to bool, but
299 // those are probably not relevant for our needs here.
300 std::deque
<Expr
const*> contextuallyConvertedExprs_
;
303 bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr
const* castExpr
)
305 if (ignoreLocation(castExpr
))
307 if (castExpr
->getCastKind() != CK_PointerToBoolean
)
310 = dyn_cast
<CXXMemberCallExpr
>(castExpr
->getSubExpr()->IgnoreParenImpCasts());
313 auto methodDecl
= memberCallExpr
->getMethodDecl();
314 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
317 // methodDecl->getParent()->getTypeForDecl()->dump();
318 if (!loplugin::isSmartPointerType(memberCallExpr
->getImplicitObjectArgument()))
320 // if (isa<CXXOperatorCallExpr>(callExpr))
322 // const FunctionDecl* functionDecl;
323 // if (isa<CXXMemberCallExpr>(callExpr))
325 // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
329 // functionDecl = callExpr->getDirectCallee();
331 // if (!functionDecl)
334 // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
335 // for (unsigned i = 0; i < len; ++i)
337 // auto param = functionDecl->getParamDecl(i);
338 // auto paramTC = loplugin::TypeCheck(param->getType());
339 // if (!paramTC.AnyBoolean())
341 // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
342 // auto argTC = loplugin::TypeCheck(arg->getType());
343 // if (argTC.AnyBoolean())
345 // // sal_Bool is sometimes disguised
346 // if (isa<SubstTemplateTypeParmType>(arg->getType()))
347 // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
348 // clang::BuiltinType::UChar))
350 // if (arg->getType()->isDependentType())
352 // if (arg->getType()->isIntegerType())
354 // auto ret = getCallValue(arg);
355 // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
357 // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
358 // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
360 // // something like: pbEmbolden ? FcTrue : FcFalse
361 // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
364 if (isContextuallyConverted(memberCallExpr
))
368 auto const range
= getCallSourceRange(memberCallExpr
);
369 if (range
.isValid() && removeText(range
))
374 report(DiagnosticsEngine::Warning
, "simplify, drop the get()", memberCallExpr
->getExprLoc())
375 << memberCallExpr
->getSourceRange();
377 else if (isa
<ParenExpr
>(castExpr
->getSubExpr()))
382 = compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(memberCallExpr
));
383 auto const range
= getCallSourceRange(memberCallExpr
);
384 if (loc
.isValid() && range
.isValid() && insertText(loc
, "bool") && removeText(range
))
386 //TODO: atomically only change both or neither
390 report(DiagnosticsEngine::Warning
,
391 "simplify, drop the get() and turn the surrounding parentheses into a functional "
393 memberCallExpr
->getExprLoc())
394 << memberCallExpr
->getSourceRange();
395 report(DiagnosticsEngine::Note
, "surrounding parentheses here",
396 castExpr
->getSubExpr()->getExprLoc())
397 << castExpr
->getSubExpr()->getSourceRange();
404 = compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(memberCallExpr
));
405 auto const range
= getCallSourceRange(memberCallExpr
);
406 if (loc
.isValid() && range
.isValid() && insertText(loc
, "bool(")
407 && replaceText(range
, ")"))
409 //TODO: atomically only change both or neither
413 report(DiagnosticsEngine::Warning
,
414 "simplify, drop the get() and wrap the expression in a functional cast to bool",
415 memberCallExpr
->getExprLoc())
416 << memberCallExpr
->getSourceRange();
418 // report(DiagnosticsEngine::Note, "method here", param->getLocation())
419 // << param->getSourceRange();
423 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator
const* binOp
)
425 if (ignoreLocation(binOp
))
427 auto opCode
= binOp
->getOpcode();
428 if (opCode
!= BO_EQ
&& opCode
!= BO_NE
)
430 const Expr
* possibleMemberCall
= nullptr;
431 if (isa
<CXXNullPtrLiteralExpr
>(binOp
->getLHS()->IgnoreParenImpCasts()))
432 possibleMemberCall
= binOp
->getRHS();
433 else if (isa
<CXXNullPtrLiteralExpr
>(binOp
->getRHS()->IgnoreParenImpCasts()))
434 possibleMemberCall
= binOp
->getLHS();
437 auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(possibleMemberCall
);
440 auto methodDecl
= memberCallExpr
->getMethodDecl();
441 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
443 if (!loplugin::isSmartPointerType(memberCallExpr
->getImplicitObjectArgument()))
445 report(DiagnosticsEngine::Warning
,
446 std::string("simplify, convert to ") + (opCode
== BO_EQ
? "'!x'" : "'x'"),
448 << binOp
->getSourceRange();
452 loplugin::Plugin::Registration
<SimplifyPointerToBool
> simplifypointertobool("simplifypointertobool",
457 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */