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>
25 Simplify boolean expressions involving smart pointers e.g.
30 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
34 class SimplifyPointerToBool
: public loplugin::FilteringRewritePlugin
<SimplifyPointerToBool
>
37 explicit SimplifyPointerToBool(loplugin::InstantiationData
const& data
)
38 : FilteringRewritePlugin(data
)
42 virtual void run() override
45 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
48 bool VisitImplicitCastExpr(ImplicitCastExpr
const*);
49 bool VisitBinaryOperator(BinaryOperator
const*);
51 bool PreTraverseUnaryOperator(UnaryOperator
* expr
)
53 if (expr
->getOpcode() == UO_LNot
)
55 contextuallyConvertedExprs_
.push_back(expr
->getSubExpr()->IgnoreParenImpCasts());
60 bool PostTraverseUnaryOperator(UnaryOperator
* expr
, bool)
62 if (expr
->getOpcode() == UO_LNot
)
64 assert(!contextuallyConvertedExprs_
.empty());
65 contextuallyConvertedExprs_
.pop_back();
70 bool TraverseUnaryOperator(UnaryOperator
* expr
)
72 auto res
= PreTraverseUnaryOperator(expr
);
74 res
= FilteringRewritePlugin::TraverseUnaryOperator(expr
);
75 PostTraverseUnaryOperator(expr
, res
);
79 bool PreTraverseBinaryOperator(BinaryOperator
* expr
)
81 auto const op
= expr
->getOpcode();
82 if (op
== BO_LAnd
|| op
== BO_LOr
)
84 contextuallyConvertedExprs_
.push_back(expr
->getLHS()->IgnoreParenImpCasts());
85 contextuallyConvertedExprs_
.push_back(expr
->getRHS()->IgnoreParenImpCasts());
90 bool PostTraverseBinaryOperator(BinaryOperator
* expr
, bool)
92 auto const op
= expr
->getOpcode();
93 if (op
== BO_LAnd
|| op
== BO_LOr
)
95 assert(contextuallyConvertedExprs_
.size() >= 2);
96 contextuallyConvertedExprs_
.pop_back();
97 contextuallyConvertedExprs_
.pop_back();
102 bool TraverseBinaryOperator(BinaryOperator
* expr
)
104 auto res
= PreTraverseBinaryOperator(expr
);
106 res
= FilteringRewritePlugin::TraverseBinaryOperator(expr
);
107 PostTraverseBinaryOperator(expr
, res
);
111 bool PreTraverseConditionalOperator(ConditionalOperator
* expr
)
113 contextuallyConvertedExprs_
.push_back(expr
->getCond()->IgnoreParenImpCasts());
117 bool PostTraverseConditionalOperator(ConditionalOperator
*, bool)
119 assert(!contextuallyConvertedExprs_
.empty());
120 contextuallyConvertedExprs_
.pop_back();
124 bool TraverseConditionalOperator(ConditionalOperator
* expr
)
126 auto res
= PreTraverseConditionalOperator(expr
);
128 res
= FilteringRewritePlugin::TraverseConditionalOperator(expr
);
129 PostTraverseConditionalOperator(expr
, res
);
133 bool PreTraverseIfStmt(IfStmt
* stmt
)
135 if (auto const cond
= stmt
->getCond())
137 contextuallyConvertedExprs_
.push_back(cond
->IgnoreParenImpCasts());
142 bool PostTraverseIfStmt(IfStmt
* stmt
, bool)
144 if (stmt
->getCond() != nullptr)
146 assert(!contextuallyConvertedExprs_
.empty());
147 contextuallyConvertedExprs_
.pop_back();
152 bool TraverseIfStmt(IfStmt
* stmt
)
154 auto res
= PreTraverseIfStmt(stmt
);
156 res
= FilteringRewritePlugin::TraverseIfStmt(stmt
);
157 PostTraverseIfStmt(stmt
, res
);
161 bool PreTraverseWhileStmt(WhileStmt
* stmt
)
163 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
167 bool PostTraverseWhileStmt(WhileStmt
*, bool)
169 assert(!contextuallyConvertedExprs_
.empty());
170 contextuallyConvertedExprs_
.pop_back();
174 bool TraverseWhileStmt(WhileStmt
* stmt
)
176 auto res
= PreTraverseWhileStmt(stmt
);
178 res
= FilteringRewritePlugin::TraverseWhileStmt(stmt
);
179 PostTraverseWhileStmt(stmt
, res
);
183 bool PreTraverseDoStmt(DoStmt
* stmt
)
185 contextuallyConvertedExprs_
.push_back(stmt
->getCond()->IgnoreParenImpCasts());
189 bool PostTraverseDoStmt(DoStmt
*, bool)
191 assert(!contextuallyConvertedExprs_
.empty());
192 contextuallyConvertedExprs_
.pop_back();
196 bool TraverseDoStmt(DoStmt
* stmt
)
198 auto res
= PreTraverseDoStmt(stmt
);
200 res
= FilteringRewritePlugin::TraverseDoStmt(stmt
);
201 PostTraverseDoStmt(stmt
, res
);
205 bool PreTraverseForStmt(ForStmt
* stmt
)
207 auto const e
= stmt
->getCond();
210 contextuallyConvertedExprs_
.push_back(e
->IgnoreParenImpCasts());
215 bool PostTraverseForStmt(ForStmt
* stmt
, bool)
217 if (stmt
->getCond() != nullptr)
219 assert(!contextuallyConvertedExprs_
.empty());
220 contextuallyConvertedExprs_
.pop_back();
225 bool TraverseForStmt(ForStmt
* stmt
)
227 auto res
= PreTraverseForStmt(stmt
);
229 res
= FilteringRewritePlugin::TraverseForStmt(stmt
);
230 PostTraverseForStmt(stmt
, res
);
235 bool isContextuallyConverted(Expr
const* expr
) const
237 return std::find(contextuallyConvertedExprs_
.begin(), contextuallyConvertedExprs_
.end(),
239 != contextuallyConvertedExprs_
.end();
242 // Get the source range starting at the "."or "->" (plus any preceding non-comment white space):
243 SourceRange
getCallSourceRange(CXXMemberCallExpr
const* expr
) const
245 if (expr
->getImplicitObjectArgument() == nullptr)
247 //TODO: Arguably, such a call of a `get` member function from within some member
248 // function (so that syntactically no caller is mentioned) should already be handled
249 // differently when reporting it (just "drop the get()" does not make sense), instead of
250 // being filtered here:
253 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
254 auto start
= compiler
.getSourceManager().getSpellingLoc(expr
->getExprLoc());
255 if (!start
.isValid())
261 start
= Lexer::GetBeginningOfToken(start
.getLocWithOffset(-1),
262 compiler
.getSourceManager(), compiler
.getLangOpts());
263 auto const s
= StringRef(compiler
.getSourceManager().getCharacterData(start
),
264 Lexer::MeasureTokenLength(start
, compiler
.getSourceManager(),
265 compiler
.getLangOpts()));
266 if (s
.empty() || compat::starts_with(s
, "\\\n"))
270 if (s
!= "." && s
!= "->")
278 auto start1
= Lexer::GetBeginningOfToken(
279 start
.getLocWithOffset(-1), compiler
.getSourceManager(), compiler
.getLangOpts());
280 auto const s
= StringRef(compiler
.getSourceManager().getCharacterData(start1
),
281 Lexer::MeasureTokenLength(start1
, compiler
.getSourceManager(),
282 compiler
.getLangOpts()));
283 if (!(s
.empty() || compat::starts_with(s
, "\\\n")))
289 return SourceRange(start
, compiler
.getSourceManager().getSpellingLoc(expr
->getEndLoc()));
292 //TODO: There are some more places where an expression is contextually converted to bool, but
293 // those are probably not relevant for our needs here.
294 std::deque
<Expr
const*> contextuallyConvertedExprs_
;
297 bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr
const* castExpr
)
299 if (ignoreLocation(castExpr
))
301 if (castExpr
->getCastKind() != CK_PointerToBoolean
)
304 = dyn_cast
<CXXMemberCallExpr
>(castExpr
->getSubExpr()->IgnoreParenImpCasts());
307 auto methodDecl
= memberCallExpr
->getMethodDecl();
308 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
311 // methodDecl->getParent()->getTypeForDecl()->dump();
312 if (!loplugin::isSmartPointerType(memberCallExpr
->getImplicitObjectArgument()))
314 // if (isa<CXXOperatorCallExpr>(callExpr))
316 // const FunctionDecl* functionDecl;
317 // if (isa<CXXMemberCallExpr>(callExpr))
319 // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
323 // functionDecl = callExpr->getDirectCallee();
325 // if (!functionDecl)
328 // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
329 // for (unsigned i = 0; i < len; ++i)
331 // auto param = functionDecl->getParamDecl(i);
332 // auto paramTC = loplugin::TypeCheck(param->getType());
333 // if (!paramTC.AnyBoolean())
335 // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
336 // auto argTC = loplugin::TypeCheck(arg->getType());
337 // if (argTC.AnyBoolean())
339 // // sal_Bool is sometimes disguised
340 // if (isa<SubstTemplateTypeParmType>(arg->getType()))
341 // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
342 // clang::BuiltinType::UChar))
344 // if (arg->getType()->isDependentType())
346 // if (arg->getType()->isIntegerType())
348 // auto ret = getCallValue(arg);
349 // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
351 // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
352 // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
354 // // something like: pbEmbolden ? FcTrue : FcFalse
355 // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
358 if (isContextuallyConverted(memberCallExpr
))
362 auto const range
= getCallSourceRange(memberCallExpr
);
363 if (range
.isValid() && removeText(range
))
368 report(DiagnosticsEngine::Warning
, "simplify, drop the get()", memberCallExpr
->getExprLoc())
369 << memberCallExpr
->getSourceRange();
371 else if (isa
<ParenExpr
>(castExpr
->getSubExpr()))
376 = compiler
.getSourceManager().getSpellingLoc(memberCallExpr
->getBeginLoc());
377 auto const range
= getCallSourceRange(memberCallExpr
);
378 if (loc
.isValid() && range
.isValid() && insertText(loc
, "bool") && removeText(range
))
380 //TODO: atomically only change both or neither
384 report(DiagnosticsEngine::Warning
,
385 "simplify, drop the get() and turn the surrounding parentheses into a functional "
387 memberCallExpr
->getExprLoc())
388 << memberCallExpr
->getSourceRange();
389 report(DiagnosticsEngine::Note
, "surrounding parentheses here",
390 castExpr
->getSubExpr()->getExprLoc())
391 << castExpr
->getSubExpr()->getSourceRange();
398 = compiler
.getSourceManager().getSpellingLoc(memberCallExpr
->getBeginLoc());
399 auto const range
= getCallSourceRange(memberCallExpr
);
400 if (loc
.isValid() && range
.isValid() && insertText(loc
, "bool(")
401 && replaceText(range
, ")"))
403 //TODO: atomically only change both or neither
407 report(DiagnosticsEngine::Warning
,
408 "simplify, drop the get() and wrap the expression in a functional cast to bool",
409 memberCallExpr
->getExprLoc())
410 << memberCallExpr
->getSourceRange();
412 // report(DiagnosticsEngine::Note, "method here", param->getLocation())
413 // << param->getSourceRange();
417 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator
const* binOp
)
419 if (ignoreLocation(binOp
))
421 auto opCode
= binOp
->getOpcode();
422 if (opCode
!= BO_EQ
&& opCode
!= BO_NE
)
424 const Expr
* possibleMemberCall
= nullptr;
425 if (isa
<CXXNullPtrLiteralExpr
>(binOp
->getLHS()->IgnoreParenImpCasts()))
426 possibleMemberCall
= binOp
->getRHS();
427 else if (isa
<CXXNullPtrLiteralExpr
>(binOp
->getRHS()->IgnoreParenImpCasts()))
428 possibleMemberCall
= binOp
->getLHS();
431 auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(possibleMemberCall
);
434 auto methodDecl
= memberCallExpr
->getMethodDecl();
435 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
437 if (!loplugin::isSmartPointerType(memberCallExpr
->getImplicitObjectArgument()))
439 report(DiagnosticsEngine::Warning
,
440 std::string("simplify, convert to ") + (opCode
== BO_EQ
? "'!x'" : "'x'"),
442 << binOp
->getSourceRange();
446 loplugin::Plugin::Registration
<SimplifyPointerToBool
> simplifypointertobool("simplifypointertobool",
451 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */