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>
24 Simplify boolean expressions involving smart pointers e.g.
29 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
33 class SimplifyPointerToBool
: public loplugin::FilteringRewritePlugin
<SimplifyPointerToBool
>
36 explicit SimplifyPointerToBool(loplugin::InstantiationData
const& data
)
37 : FilteringRewritePlugin(data
)
41 virtual void run() override
44 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
47 bool VisitImplicitCastExpr(ImplicitCastExpr
const*);
48 bool VisitBinaryOperator(BinaryOperator
const*);
50 bool PreTraverseUnaryOperator(UnaryOperator
* expr
)
52 if (expr
->getOpcode() == UO_LNot
)
54 contextuallyConvertedExprs_
.push_back(expr
->getSubExpr()->IgnoreParenImpCasts());
59 bool PostTraverseUnaryOperator(UnaryOperator
* expr
, bool)
61 if (expr
->getOpcode() == UO_LNot
)
63 assert(!contextuallyConvertedExprs_
.empty());
64 contextuallyConvertedExprs_
.pop_back();
69 bool TraverseUnaryOperator(UnaryOperator
* expr
)
71 auto res
= PreTraverseUnaryOperator(expr
);
73 res
= FilteringRewritePlugin::TraverseUnaryOperator(expr
);
74 PostTraverseUnaryOperator(expr
, res
);
78 bool PreTraverseBinaryOperator(BinaryOperator
* expr
)
80 auto const op
= expr
->getOpcode();
81 if (op
== BO_LAnd
|| op
== BO_LOr
)
83 contextuallyConvertedExprs_
.push_back(expr
->getLHS()->IgnoreParenImpCasts());
84 contextuallyConvertedExprs_
.push_back(expr
->getRHS()->IgnoreParenImpCasts());
89 bool PostTraverseBinaryOperator(BinaryOperator
* expr
, bool)
91 auto const op
= expr
->getOpcode();
92 if (op
== BO_LAnd
|| op
== BO_LOr
)
94 assert(contextuallyConvertedExprs_
.size() >= 2);
95 contextuallyConvertedExprs_
.pop_back();
96 contextuallyConvertedExprs_
.pop_back();
101 bool TraverseBinaryOperator(BinaryOperator
* expr
)
103 auto res
= PreTraverseBinaryOperator(expr
);
105 res
= FilteringRewritePlugin::TraverseBinaryOperator(expr
);
106 PostTraverseBinaryOperator(expr
, res
);
110 bool PreTraverseConditionalOperator(ConditionalOperator
* expr
)
112 contextuallyConvertedExprs_
.push_back(expr
->getCond()->IgnoreParenImpCasts());
116 bool PostTraverseConditionalOperator(ConditionalOperator
*, bool)
118 assert(!contextuallyConvertedExprs_
.empty());
119 contextuallyConvertedExprs_
.pop_back();
123 bool TraverseConditionalOperator(ConditionalOperator
* expr
)
125 auto res
= PreTraverseConditionalOperator(expr
);
127 res
= FilteringRewritePlugin::TraverseConditionalOperator(expr
);
128 PostTraverseConditionalOperator(expr
, res
);
132 bool PreTraverseIfStmt(IfStmt
* stmt
)
134 if (auto const cond
= stmt
->getCond())
136 contextuallyConvertedExprs_
.push_back(cond
->IgnoreParenImpCasts());
141 bool PostTraverseIfStmt(IfStmt
* stmt
, bool)
143 if (stmt
->getCond() != nullptr)
145 assert(!contextuallyConvertedExprs_
.empty());
146 contextuallyConvertedExprs_
.pop_back();
151 bool TraverseIfStmt(IfStmt
* stmt
)
153 auto res
= PreTraverseIfStmt(stmt
);
155 res
= FilteringRewritePlugin::TraverseIfStmt(stmt
);
156 PostTraverseIfStmt(stmt
, res
);
160 bool PreTraverseWhileStmt(WhileStmt
* stmt
)
162 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
166 bool PostTraverseWhileStmt(WhileStmt
*, bool)
168 assert(!contextuallyConvertedExprs_
.empty());
169 contextuallyConvertedExprs_
.pop_back();
173 bool TraverseWhileStmt(WhileStmt
* stmt
)
175 auto res
= PreTraverseWhileStmt(stmt
);
177 res
= FilteringRewritePlugin::TraverseWhileStmt(stmt
);
178 PostTraverseWhileStmt(stmt
, res
);
182 bool PreTraverseDoStmt(DoStmt
* stmt
)
184 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
188 bool PostTraverseDoStmt(DoStmt
*, bool)
190 assert(!contextuallyConvertedExprs_
.empty());
191 contextuallyConvertedExprs_
.pop_back();
195 bool TraverseDoStmt(DoStmt
* stmt
)
197 auto res
= PreTraverseDoStmt(stmt
);
199 res
= FilteringRewritePlugin::TraverseDoStmt(stmt
);
200 PostTraverseDoStmt(stmt
, res
);
204 bool PreTraverseForStmt(ForStmt
* stmt
)
206 auto const e
= stmt
->getCond();
209 contextuallyConvertedExprs_
.push_back(e
->IgnoreParenImpCasts());
214 bool PostTraverseForStmt(ForStmt
* stmt
, bool)
216 if (stmt
->getCond() != nullptr)
218 assert(!contextuallyConvertedExprs_
.empty());
219 contextuallyConvertedExprs_
.pop_back();
224 bool TraverseForStmt(ForStmt
* stmt
)
226 auto res
= PreTraverseForStmt(stmt
);
228 res
= FilteringRewritePlugin::TraverseForStmt(stmt
);
229 PostTraverseForStmt(stmt
, res
);
234 bool isContextuallyConverted(Expr
const* expr
) const
236 return std::find(contextuallyConvertedExprs_
.begin(), contextuallyConvertedExprs_
.end(),
238 != contextuallyConvertedExprs_
.end();
241 // Get the source range starting at the "."or "->" (plus any preceding non-comment white space):
242 SourceRange
getCallSourceRange(CXXMemberCallExpr
const* expr
) const
244 if (expr
->getImplicitObjectArgument() == nullptr)
246 //TODO: Arguably, such a call of a `get` member function from within some member
247 // function (so that syntactically no caller is mentioned) should already be handled
248 // differently when reporting it (just "drop the get()" does not make sense), instead of
249 // being filtered here:
252 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
253 auto start
= compiler
.getSourceManager().getSpellingLoc(expr
->getExprLoc());
254 if (!start
.isValid())
260 start
= Lexer::GetBeginningOfToken(start
.getLocWithOffset(-1),
261 compiler
.getSourceManager(), compiler
.getLangOpts());
262 auto const s
= StringRef(compiler
.getSourceManager().getCharacterData(start
),
263 Lexer::MeasureTokenLength(start
, compiler
.getSourceManager(),
264 compiler
.getLangOpts()));
265 if (s
.empty() || s
.startswith("\\\n"))
269 if (s
!= "." && s
!= "->")
277 auto start1
= Lexer::GetBeginningOfToken(
278 start
.getLocWithOffset(-1), compiler
.getSourceManager(), compiler
.getLangOpts());
279 auto const s
= StringRef(compiler
.getSourceManager().getCharacterData(start1
),
280 Lexer::MeasureTokenLength(start1
, compiler
.getSourceManager(),
281 compiler
.getLangOpts()));
282 if (!(s
.empty() || s
.startswith("\\\n")))
288 return SourceRange(start
, compiler
.getSourceManager().getSpellingLoc(expr
->getEndLoc()));
291 //TODO: There are some more places where an expression is contextually converted to bool, but
292 // those are probably not relevant for our needs here.
293 std::deque
<Expr
const*> contextuallyConvertedExprs_
;
296 bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr
const* castExpr
)
298 if (ignoreLocation(castExpr
))
300 if (castExpr
->getCastKind() != CK_PointerToBoolean
)
303 = dyn_cast
<CXXMemberCallExpr
>(castExpr
->getSubExpr()->IgnoreParenImpCasts());
306 auto methodDecl
= memberCallExpr
->getMethodDecl();
307 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
310 // methodDecl->getParent()->getTypeForDecl()->dump();
311 if (!loplugin::isSmartPointerType(memberCallExpr
->getImplicitObjectArgument()))
313 // if (isa<CXXOperatorCallExpr>(callExpr))
315 // const FunctionDecl* functionDecl;
316 // if (isa<CXXMemberCallExpr>(callExpr))
318 // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
322 // functionDecl = callExpr->getDirectCallee();
324 // if (!functionDecl)
327 // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
328 // for (unsigned i = 0; i < len; ++i)
330 // auto param = functionDecl->getParamDecl(i);
331 // auto paramTC = loplugin::TypeCheck(param->getType());
332 // if (!paramTC.AnyBoolean())
334 // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
335 // auto argTC = loplugin::TypeCheck(arg->getType());
336 // if (argTC.AnyBoolean())
338 // // sal_Bool is sometimes disguised
339 // if (isa<SubstTemplateTypeParmType>(arg->getType()))
340 // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
341 // clang::BuiltinType::UChar))
343 // if (arg->getType()->isDependentType())
345 // if (arg->getType()->isIntegerType())
347 // auto ret = getCallValue(arg);
348 // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
350 // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
351 // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
353 // // something like: pbEmbolden ? FcTrue : FcFalse
354 // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
357 if (isContextuallyConverted(memberCallExpr
))
361 auto const range
= getCallSourceRange(memberCallExpr
);
362 if (range
.isValid() && removeText(range
))
367 report(DiagnosticsEngine::Warning
, "simplify, drop the get()", memberCallExpr
->getExprLoc())
368 << memberCallExpr
->getSourceRange();
370 else if (isa
<ParenExpr
>(castExpr
->getSubExpr()))
375 = compiler
.getSourceManager().getSpellingLoc(memberCallExpr
->getBeginLoc());
376 auto const range
= getCallSourceRange(memberCallExpr
);
377 if (loc
.isValid() && range
.isValid() && insertText(loc
, "bool") && removeText(range
))
379 //TODO: atomically only change both or neither
383 report(DiagnosticsEngine::Warning
,
384 "simplify, drop the get() and turn the surrounding parentheses into a functional "
386 memberCallExpr
->getExprLoc())
387 << memberCallExpr
->getSourceRange();
388 report(DiagnosticsEngine::Note
, "surrounding parentheses here",
389 castExpr
->getSubExpr()->getExprLoc())
390 << castExpr
->getSubExpr()->getSourceRange();
397 = compiler
.getSourceManager().getSpellingLoc(memberCallExpr
->getBeginLoc());
398 auto const range
= getCallSourceRange(memberCallExpr
);
399 if (loc
.isValid() && range
.isValid() && insertText(loc
, "bool(")
400 && replaceText(range
, ")"))
402 //TODO: atomically only change both or neither
406 report(DiagnosticsEngine::Warning
,
407 "simplify, drop the get() and wrap the expression in a functional cast to bool",
408 memberCallExpr
->getExprLoc())
409 << memberCallExpr
->getSourceRange();
411 // report(DiagnosticsEngine::Note, "method here", param->getLocation())
412 // << param->getSourceRange();
416 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator
const* binOp
)
418 if (ignoreLocation(binOp
))
420 auto opCode
= binOp
->getOpcode();
421 if (opCode
!= BO_EQ
&& opCode
!= BO_NE
)
423 const Expr
* possibleMemberCall
= nullptr;
424 if (isa
<CXXNullPtrLiteralExpr
>(binOp
->getLHS()->IgnoreParenImpCasts()))
425 possibleMemberCall
= binOp
->getRHS();
426 else if (isa
<CXXNullPtrLiteralExpr
>(binOp
->getRHS()->IgnoreParenImpCasts()))
427 possibleMemberCall
= binOp
->getLHS();
430 auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(possibleMemberCall
);
433 auto methodDecl
= memberCallExpr
->getMethodDecl();
434 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
436 if (!loplugin::isSmartPointerType(memberCallExpr
->getImplicitObjectArgument()))
438 report(DiagnosticsEngine::Warning
,
439 std::string("simplify, convert to ") + (opCode
== BO_EQ
? "'!x'" : "'x'"),
441 << binOp
->getSourceRange();
445 loplugin::Plugin::Registration
<SimplifyPointerToBool
> simplifypointertobool("simplifypointertobool",
450 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */