bump product version to 7.2.5.1
[LibreOffice.git] / compilerplugins / clang / simplifypointertobool.cxx
blob7afa2d01ec3e2d1570dc7a439366e3d199420480
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 "config_clang.h"
22 #include "plugin.hxx"
23 #include "check.hxx"
24 #include "compat.hxx"
26 /**
27 Simplify boolean expressions involving smart pointers e.g.
28 if (x.get())
29 can be
30 if (x)
32 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
34 namespace
36 class SimplifyPointerToBool : public loplugin::FilteringRewritePlugin<SimplifyPointerToBool>
38 public:
39 explicit SimplifyPointerToBool(loplugin::InstantiationData const& data)
40 : FilteringRewritePlugin(data)
44 virtual void run() override
46 if (preRun())
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());
59 return true;
62 bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
64 if (expr->getOpcode() == UO_LNot)
66 assert(!contextuallyConvertedExprs_.empty());
67 contextuallyConvertedExprs_.pop_back();
69 return true;
72 bool TraverseUnaryOperator(UnaryOperator* expr)
74 auto res = PreTraverseUnaryOperator(expr);
75 assert(res);
76 res = FilteringRewritePlugin::TraverseUnaryOperator(expr);
77 PostTraverseUnaryOperator(expr, res);
78 return res;
81 #if CLANG_VERSION < 110000
82 bool TraverseUnaryLNot(UnaryOperator* expr) { return TraverseUnaryOperator(expr); }
83 #endif
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());
93 return true;
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();
105 return true;
108 bool TraverseBinaryOperator(BinaryOperator* expr)
110 auto res = PreTraverseBinaryOperator(expr);
111 assert(res);
112 res = FilteringRewritePlugin::TraverseBinaryOperator(expr);
113 PostTraverseBinaryOperator(expr, res);
114 return res;
117 #if CLANG_VERSION < 110000
118 bool TraverseBinLAnd(BinaryOperator* expr) { return TraverseBinaryOperator(expr); }
119 bool TraverseBinLOr(BinaryOperator* expr) { return TraverseBinaryOperator(expr); }
120 #endif
122 bool PreTraverseConditionalOperator(ConditionalOperator* expr)
124 contextuallyConvertedExprs_.push_back(expr->getCond()->IgnoreParenImpCasts());
125 return true;
128 bool PostTraverseConditionalOperator(ConditionalOperator*, bool)
130 assert(!contextuallyConvertedExprs_.empty());
131 contextuallyConvertedExprs_.pop_back();
132 return true;
135 bool TraverseConditionalOperator(ConditionalOperator* expr)
137 auto res = PreTraverseConditionalOperator(expr);
138 assert(res);
139 res = FilteringRewritePlugin::TraverseConditionalOperator(expr);
140 PostTraverseConditionalOperator(expr, res);
141 return res;
144 bool PreTraverseIfStmt(IfStmt* stmt)
146 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
147 return true;
150 bool PostTraverseIfStmt(IfStmt*, bool)
152 assert(!contextuallyConvertedExprs_.empty());
153 contextuallyConvertedExprs_.pop_back();
154 return true;
157 bool TraverseIfStmt(IfStmt* stmt)
159 auto res = PreTraverseIfStmt(stmt);
160 assert(res);
161 res = FilteringRewritePlugin::TraverseIfStmt(stmt);
162 PostTraverseIfStmt(stmt, res);
163 return res;
166 bool PreTraverseWhileStmt(WhileStmt* stmt)
168 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
169 return true;
172 bool PostTraverseWhileStmt(WhileStmt*, bool)
174 assert(!contextuallyConvertedExprs_.empty());
175 contextuallyConvertedExprs_.pop_back();
176 return true;
179 bool TraverseWhileStmt(WhileStmt* stmt)
181 auto res = PreTraverseWhileStmt(stmt);
182 assert(res);
183 res = FilteringRewritePlugin::TraverseWhileStmt(stmt);
184 PostTraverseWhileStmt(stmt, res);
185 return res;
188 bool PreTraverseDoStmt(DoStmt* stmt)
190 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
191 return true;
194 bool PostTraverseDoStmt(DoStmt*, bool)
196 assert(!contextuallyConvertedExprs_.empty());
197 contextuallyConvertedExprs_.pop_back();
198 return true;
201 bool TraverseDoStmt(DoStmt* stmt)
203 auto res = PreTraverseDoStmt(stmt);
204 assert(res);
205 res = FilteringRewritePlugin::TraverseDoStmt(stmt);
206 PostTraverseDoStmt(stmt, res);
207 return res;
210 bool PreTraverseForStmt(ForStmt* stmt)
212 auto const e = stmt->getCond();
213 if (e != nullptr)
215 contextuallyConvertedExprs_.push_back(e->IgnoreParenImpCasts());
217 return true;
220 bool PostTraverseForStmt(ForStmt* stmt, bool)
222 if (stmt->getCond() != nullptr)
224 assert(!contextuallyConvertedExprs_.empty());
225 contextuallyConvertedExprs_.pop_back();
227 return true;
230 bool TraverseForStmt(ForStmt* stmt)
232 auto res = PreTraverseForStmt(stmt);
233 assert(res);
234 res = FilteringRewritePlugin::TraverseForStmt(stmt);
235 PostTraverseForStmt(stmt, res);
236 return res;
239 private:
240 bool isContextuallyConverted(Expr const* expr) const
242 return std::find(contextuallyConvertedExprs_.begin(), contextuallyConvertedExprs_.end(),
243 expr)
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:
256 return {};
258 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
259 auto start = compiler.getSourceManager().getSpellingLoc(expr->getExprLoc());
260 if (!start.isValid())
262 return {};
264 for (;;)
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"))
273 continue;
275 if (s != "." && s != "->")
277 return {};
279 break;
281 for (;;)
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")))
290 break;
292 start = start1;
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))
306 return true;
307 if (castExpr->getCastKind() != CK_PointerToBoolean)
308 return true;
309 auto memberCallExpr
310 = dyn_cast<CXXMemberCallExpr>(castExpr->getSubExpr()->IgnoreParenImpCasts());
311 if (!memberCallExpr)
312 return true;
313 auto methodDecl = memberCallExpr->getMethodDecl();
314 if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
315 return true;
316 // castExpr->dump();
317 // methodDecl->getParent()->getTypeForDecl()->dump();
318 if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
319 return true;
320 // if (isa<CXXOperatorCallExpr>(callExpr))
321 // return true;
322 // const FunctionDecl* functionDecl;
323 // if (isa<CXXMemberCallExpr>(callExpr))
324 // {
325 // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
326 // }
327 // else
328 // {
329 // functionDecl = callExpr->getDirectCallee();
330 // }
331 // if (!functionDecl)
332 // return true;
334 // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
335 // for (unsigned i = 0; i < len; ++i)
336 // {
337 // auto param = functionDecl->getParamDecl(i);
338 // auto paramTC = loplugin::TypeCheck(param->getType());
339 // if (!paramTC.AnyBoolean())
340 // continue;
341 // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
342 // auto argTC = loplugin::TypeCheck(arg->getType());
343 // if (argTC.AnyBoolean())
344 // continue;
345 // // sal_Bool is sometimes disguised
346 // if (isa<SubstTemplateTypeParmType>(arg->getType()))
347 // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
348 // clang::BuiltinType::UChar))
349 // continue;
350 // if (arg->getType()->isDependentType())
351 // continue;
352 // if (arg->getType()->isIntegerType())
353 // {
354 // auto ret = getCallValue(arg);
355 // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
356 // continue;
357 // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
358 // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
359 // continue;
360 // // something like: pbEmbolden ? FcTrue : FcFalse
361 // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
362 // continue;
363 // }
364 if (isContextuallyConverted(memberCallExpr))
366 if (rewriter)
368 auto const range = getCallSourceRange(memberCallExpr);
369 if (range.isValid() && removeText(range))
371 return true;
374 report(DiagnosticsEngine::Warning, "simplify, drop the get()", memberCallExpr->getExprLoc())
375 << memberCallExpr->getSourceRange();
377 else if (isa<ParenExpr>(castExpr->getSubExpr()))
379 if (rewriter)
381 auto const loc
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
387 return true;
390 report(DiagnosticsEngine::Warning,
391 "simplify, drop the get() and turn the surrounding parentheses into a functional "
392 "cast to bool",
393 memberCallExpr->getExprLoc())
394 << memberCallExpr->getSourceRange();
395 report(DiagnosticsEngine::Note, "surrounding parentheses here",
396 castExpr->getSubExpr()->getExprLoc())
397 << castExpr->getSubExpr()->getSourceRange();
399 else
401 if (rewriter)
403 auto const loc
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
410 return true;
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();
420 return true;
423 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator const* binOp)
425 if (ignoreLocation(binOp))
426 return true;
427 auto opCode = binOp->getOpcode();
428 if (opCode != BO_EQ && opCode != BO_NE)
429 return true;
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();
435 else
436 return true;
437 auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(possibleMemberCall);
438 if (!memberCallExpr)
439 return true;
440 auto methodDecl = memberCallExpr->getMethodDecl();
441 if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
442 return true;
443 if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
444 return true;
445 report(DiagnosticsEngine::Warning,
446 std::string("simplify, convert to ") + (opCode == BO_EQ ? "'!x'" : "'x'"),
447 binOp->getExprLoc())
448 << binOp->getSourceRange();
449 return true;
452 loplugin::Plugin::Registration<SimplifyPointerToBool> simplifypointertobool("simplifypointertobool",
453 true);
455 } // namespace
457 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */