Version 24.2.2.2, tag libreoffice-24.2.2.2
[LibreOffice.git] / compilerplugins / clang / simplifypointertobool.cxx
blob11aed0f317b77668acb7f0da1fe4dcc8ee161170
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <algorithm>
11 #include <cassert>
12 #include <deque>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <set>
18 #include <clang/AST/CXXInheritance.h>
20 #include "plugin.hxx"
21 #include "check.hxx"
23 /**
24 Simplify boolean expressions involving smart pointers e.g.
25 if (x.get())
26 can be
27 if (x)
29 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
31 namespace
33 class SimplifyPointerToBool : public loplugin::FilteringRewritePlugin<SimplifyPointerToBool>
35 public:
36 explicit SimplifyPointerToBool(loplugin::InstantiationData const& data)
37 : FilteringRewritePlugin(data)
41 virtual void run() override
43 if (preRun())
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());
56 return true;
59 bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
61 if (expr->getOpcode() == UO_LNot)
63 assert(!contextuallyConvertedExprs_.empty());
64 contextuallyConvertedExprs_.pop_back();
66 return true;
69 bool TraverseUnaryOperator(UnaryOperator* expr)
71 auto res = PreTraverseUnaryOperator(expr);
72 assert(res);
73 res = FilteringRewritePlugin::TraverseUnaryOperator(expr);
74 PostTraverseUnaryOperator(expr, res);
75 return 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());
86 return true;
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();
98 return true;
101 bool TraverseBinaryOperator(BinaryOperator* expr)
103 auto res = PreTraverseBinaryOperator(expr);
104 assert(res);
105 res = FilteringRewritePlugin::TraverseBinaryOperator(expr);
106 PostTraverseBinaryOperator(expr, res);
107 return res;
110 bool PreTraverseConditionalOperator(ConditionalOperator* expr)
112 contextuallyConvertedExprs_.push_back(expr->getCond()->IgnoreParenImpCasts());
113 return true;
116 bool PostTraverseConditionalOperator(ConditionalOperator*, bool)
118 assert(!contextuallyConvertedExprs_.empty());
119 contextuallyConvertedExprs_.pop_back();
120 return true;
123 bool TraverseConditionalOperator(ConditionalOperator* expr)
125 auto res = PreTraverseConditionalOperator(expr);
126 assert(res);
127 res = FilteringRewritePlugin::TraverseConditionalOperator(expr);
128 PostTraverseConditionalOperator(expr, res);
129 return res;
132 bool PreTraverseIfStmt(IfStmt* stmt)
134 if (auto const cond = stmt->getCond())
136 contextuallyConvertedExprs_.push_back(cond->IgnoreParenImpCasts());
138 return true;
141 bool PostTraverseIfStmt(IfStmt* stmt, bool)
143 if (stmt->getCond() != nullptr)
145 assert(!contextuallyConvertedExprs_.empty());
146 contextuallyConvertedExprs_.pop_back();
148 return true;
151 bool TraverseIfStmt(IfStmt* stmt)
153 auto res = PreTraverseIfStmt(stmt);
154 assert(res);
155 res = FilteringRewritePlugin::TraverseIfStmt(stmt);
156 PostTraverseIfStmt(stmt, res);
157 return res;
160 bool PreTraverseWhileStmt(WhileStmt* stmt)
162 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
163 return true;
166 bool PostTraverseWhileStmt(WhileStmt*, bool)
168 assert(!contextuallyConvertedExprs_.empty());
169 contextuallyConvertedExprs_.pop_back();
170 return true;
173 bool TraverseWhileStmt(WhileStmt* stmt)
175 auto res = PreTraverseWhileStmt(stmt);
176 assert(res);
177 res = FilteringRewritePlugin::TraverseWhileStmt(stmt);
178 PostTraverseWhileStmt(stmt, res);
179 return res;
182 bool PreTraverseDoStmt(DoStmt* stmt)
184 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
185 return true;
188 bool PostTraverseDoStmt(DoStmt*, bool)
190 assert(!contextuallyConvertedExprs_.empty());
191 contextuallyConvertedExprs_.pop_back();
192 return true;
195 bool TraverseDoStmt(DoStmt* stmt)
197 auto res = PreTraverseDoStmt(stmt);
198 assert(res);
199 res = FilteringRewritePlugin::TraverseDoStmt(stmt);
200 PostTraverseDoStmt(stmt, res);
201 return res;
204 bool PreTraverseForStmt(ForStmt* stmt)
206 auto const e = stmt->getCond();
207 if (e != nullptr)
209 contextuallyConvertedExprs_.push_back(e->IgnoreParenImpCasts());
211 return true;
214 bool PostTraverseForStmt(ForStmt* stmt, bool)
216 if (stmt->getCond() != nullptr)
218 assert(!contextuallyConvertedExprs_.empty());
219 contextuallyConvertedExprs_.pop_back();
221 return true;
224 bool TraverseForStmt(ForStmt* stmt)
226 auto res = PreTraverseForStmt(stmt);
227 assert(res);
228 res = FilteringRewritePlugin::TraverseForStmt(stmt);
229 PostTraverseForStmt(stmt, res);
230 return res;
233 private:
234 bool isContextuallyConverted(Expr const* expr) const
236 return std::find(contextuallyConvertedExprs_.begin(), contextuallyConvertedExprs_.end(),
237 expr)
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:
250 return {};
252 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
253 auto start = compiler.getSourceManager().getSpellingLoc(expr->getExprLoc());
254 if (!start.isValid())
256 return {};
258 for (;;)
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"))
267 continue;
269 if (s != "." && s != "->")
271 return {};
273 break;
275 for (;;)
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")))
284 break;
286 start = start1;
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))
299 return true;
300 if (castExpr->getCastKind() != CK_PointerToBoolean)
301 return true;
302 auto memberCallExpr
303 = dyn_cast<CXXMemberCallExpr>(castExpr->getSubExpr()->IgnoreParenImpCasts());
304 if (!memberCallExpr)
305 return true;
306 auto methodDecl = memberCallExpr->getMethodDecl();
307 if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
308 return true;
309 // castExpr->dump();
310 // methodDecl->getParent()->getTypeForDecl()->dump();
311 if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
312 return true;
313 // if (isa<CXXOperatorCallExpr>(callExpr))
314 // return true;
315 // const FunctionDecl* functionDecl;
316 // if (isa<CXXMemberCallExpr>(callExpr))
317 // {
318 // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
319 // }
320 // else
321 // {
322 // functionDecl = callExpr->getDirectCallee();
323 // }
324 // if (!functionDecl)
325 // return true;
327 // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
328 // for (unsigned i = 0; i < len; ++i)
329 // {
330 // auto param = functionDecl->getParamDecl(i);
331 // auto paramTC = loplugin::TypeCheck(param->getType());
332 // if (!paramTC.AnyBoolean())
333 // continue;
334 // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
335 // auto argTC = loplugin::TypeCheck(arg->getType());
336 // if (argTC.AnyBoolean())
337 // continue;
338 // // sal_Bool is sometimes disguised
339 // if (isa<SubstTemplateTypeParmType>(arg->getType()))
340 // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
341 // clang::BuiltinType::UChar))
342 // continue;
343 // if (arg->getType()->isDependentType())
344 // continue;
345 // if (arg->getType()->isIntegerType())
346 // {
347 // auto ret = getCallValue(arg);
348 // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
349 // continue;
350 // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
351 // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
352 // continue;
353 // // something like: pbEmbolden ? FcTrue : FcFalse
354 // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
355 // continue;
356 // }
357 if (isContextuallyConverted(memberCallExpr))
359 if (rewriter)
361 auto const range = getCallSourceRange(memberCallExpr);
362 if (range.isValid() && removeText(range))
364 return true;
367 report(DiagnosticsEngine::Warning, "simplify, drop the get()", memberCallExpr->getExprLoc())
368 << memberCallExpr->getSourceRange();
370 else if (isa<ParenExpr>(castExpr->getSubExpr()))
372 if (rewriter)
374 auto const loc
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
380 return true;
383 report(DiagnosticsEngine::Warning,
384 "simplify, drop the get() and turn the surrounding parentheses into a functional "
385 "cast to bool",
386 memberCallExpr->getExprLoc())
387 << memberCallExpr->getSourceRange();
388 report(DiagnosticsEngine::Note, "surrounding parentheses here",
389 castExpr->getSubExpr()->getExprLoc())
390 << castExpr->getSubExpr()->getSourceRange();
392 else
394 if (rewriter)
396 auto const loc
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
403 return true;
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();
413 return true;
416 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator const* binOp)
418 if (ignoreLocation(binOp))
419 return true;
420 auto opCode = binOp->getOpcode();
421 if (opCode != BO_EQ && opCode != BO_NE)
422 return true;
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();
428 else
429 return true;
430 auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(possibleMemberCall);
431 if (!memberCallExpr)
432 return true;
433 auto methodDecl = memberCallExpr->getMethodDecl();
434 if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
435 return true;
436 if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
437 return true;
438 report(DiagnosticsEngine::Warning,
439 std::string("simplify, convert to ") + (opCode == BO_EQ ? "'!x'" : "'x'"),
440 binOp->getExprLoc())
441 << binOp->getSourceRange();
442 return true;
445 loplugin::Plugin::Registration<SimplifyPointerToBool> simplifypointertobool("simplifypointertobool",
446 true);
448 } // namespace
450 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */