tdf#161878 Ignore nested SYMBOL field inside IF field
[LibreOffice.git] / compilerplugins / clang / simplifypointertobool.cxx
blob1a9471fcf877f82ed6d0c3609c3da9deb335e051
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"
22 #include "compat.hxx"
24 /**
25 Simplify boolean expressions involving smart pointers e.g.
26 if (x.get())
27 can be
28 if (x)
30 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
32 namespace
34 class SimplifyPointerToBool : public loplugin::FilteringRewritePlugin<SimplifyPointerToBool>
36 public:
37 explicit SimplifyPointerToBool(loplugin::InstantiationData const& data)
38 : FilteringRewritePlugin(data)
42 virtual void run() override
44 if (preRun())
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());
57 return true;
60 bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
62 if (expr->getOpcode() == UO_LNot)
64 assert(!contextuallyConvertedExprs_.empty());
65 contextuallyConvertedExprs_.pop_back();
67 return true;
70 bool TraverseUnaryOperator(UnaryOperator* expr)
72 auto res = PreTraverseUnaryOperator(expr);
73 assert(res);
74 res = FilteringRewritePlugin::TraverseUnaryOperator(expr);
75 PostTraverseUnaryOperator(expr, res);
76 return 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());
87 return true;
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();
99 return true;
102 bool TraverseBinaryOperator(BinaryOperator* expr)
104 auto res = PreTraverseBinaryOperator(expr);
105 assert(res);
106 res = FilteringRewritePlugin::TraverseBinaryOperator(expr);
107 PostTraverseBinaryOperator(expr, res);
108 return res;
111 bool PreTraverseConditionalOperator(ConditionalOperator* expr)
113 contextuallyConvertedExprs_.push_back(expr->getCond()->IgnoreParenImpCasts());
114 return true;
117 bool PostTraverseConditionalOperator(ConditionalOperator*, bool)
119 assert(!contextuallyConvertedExprs_.empty());
120 contextuallyConvertedExprs_.pop_back();
121 return true;
124 bool TraverseConditionalOperator(ConditionalOperator* expr)
126 auto res = PreTraverseConditionalOperator(expr);
127 assert(res);
128 res = FilteringRewritePlugin::TraverseConditionalOperator(expr);
129 PostTraverseConditionalOperator(expr, res);
130 return res;
133 bool PreTraverseIfStmt(IfStmt* stmt)
135 if (auto const cond = stmt->getCond())
137 contextuallyConvertedExprs_.push_back(cond->IgnoreParenImpCasts());
139 return true;
142 bool PostTraverseIfStmt(IfStmt* stmt, bool)
144 if (stmt->getCond() != nullptr)
146 assert(!contextuallyConvertedExprs_.empty());
147 contextuallyConvertedExprs_.pop_back();
149 return true;
152 bool TraverseIfStmt(IfStmt* stmt)
154 auto res = PreTraverseIfStmt(stmt);
155 assert(res);
156 res = FilteringRewritePlugin::TraverseIfStmt(stmt);
157 PostTraverseIfStmt(stmt, res);
158 return res;
161 bool PreTraverseWhileStmt(WhileStmt* stmt)
163 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
164 return true;
167 bool PostTraverseWhileStmt(WhileStmt*, bool)
169 assert(!contextuallyConvertedExprs_.empty());
170 contextuallyConvertedExprs_.pop_back();
171 return true;
174 bool TraverseWhileStmt(WhileStmt* stmt)
176 auto res = PreTraverseWhileStmt(stmt);
177 assert(res);
178 res = FilteringRewritePlugin::TraverseWhileStmt(stmt);
179 PostTraverseWhileStmt(stmt, res);
180 return res;
183 bool PreTraverseDoStmt(DoStmt* stmt)
185 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
186 return true;
189 bool PostTraverseDoStmt(DoStmt*, bool)
191 assert(!contextuallyConvertedExprs_.empty());
192 contextuallyConvertedExprs_.pop_back();
193 return true;
196 bool TraverseDoStmt(DoStmt* stmt)
198 auto res = PreTraverseDoStmt(stmt);
199 assert(res);
200 res = FilteringRewritePlugin::TraverseDoStmt(stmt);
201 PostTraverseDoStmt(stmt, res);
202 return res;
205 bool PreTraverseForStmt(ForStmt* stmt)
207 auto const e = stmt->getCond();
208 if (e != nullptr)
210 contextuallyConvertedExprs_.push_back(e->IgnoreParenImpCasts());
212 return true;
215 bool PostTraverseForStmt(ForStmt* stmt, bool)
217 if (stmt->getCond() != nullptr)
219 assert(!contextuallyConvertedExprs_.empty());
220 contextuallyConvertedExprs_.pop_back();
222 return true;
225 bool TraverseForStmt(ForStmt* stmt)
227 auto res = PreTraverseForStmt(stmt);
228 assert(res);
229 res = FilteringRewritePlugin::TraverseForStmt(stmt);
230 PostTraverseForStmt(stmt, res);
231 return res;
234 private:
235 bool isContextuallyConverted(Expr const* expr) const
237 return std::find(contextuallyConvertedExprs_.begin(), contextuallyConvertedExprs_.end(),
238 expr)
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:
251 return {};
253 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
254 auto start = compiler.getSourceManager().getSpellingLoc(expr->getExprLoc());
255 if (!start.isValid())
257 return {};
259 for (;;)
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"))
268 continue;
270 if (s != "." && s != "->")
272 return {};
274 break;
276 for (;;)
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")))
285 break;
287 start = start1;
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))
300 return true;
301 if (castExpr->getCastKind() != CK_PointerToBoolean)
302 return true;
303 auto memberCallExpr
304 = dyn_cast<CXXMemberCallExpr>(castExpr->getSubExpr()->IgnoreParenImpCasts());
305 if (!memberCallExpr)
306 return true;
307 auto methodDecl = memberCallExpr->getMethodDecl();
308 if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
309 return true;
310 // castExpr->dump();
311 // methodDecl->getParent()->getTypeForDecl()->dump();
312 if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
313 return true;
314 // if (isa<CXXOperatorCallExpr>(callExpr))
315 // return true;
316 // const FunctionDecl* functionDecl;
317 // if (isa<CXXMemberCallExpr>(callExpr))
318 // {
319 // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
320 // }
321 // else
322 // {
323 // functionDecl = callExpr->getDirectCallee();
324 // }
325 // if (!functionDecl)
326 // return true;
328 // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
329 // for (unsigned i = 0; i < len; ++i)
330 // {
331 // auto param = functionDecl->getParamDecl(i);
332 // auto paramTC = loplugin::TypeCheck(param->getType());
333 // if (!paramTC.AnyBoolean())
334 // continue;
335 // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
336 // auto argTC = loplugin::TypeCheck(arg->getType());
337 // if (argTC.AnyBoolean())
338 // continue;
339 // // sal_Bool is sometimes disguised
340 // if (isa<SubstTemplateTypeParmType>(arg->getType()))
341 // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
342 // clang::BuiltinType::UChar))
343 // continue;
344 // if (arg->getType()->isDependentType())
345 // continue;
346 // if (arg->getType()->isIntegerType())
347 // {
348 // auto ret = getCallValue(arg);
349 // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
350 // continue;
351 // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
352 // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
353 // continue;
354 // // something like: pbEmbolden ? FcTrue : FcFalse
355 // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
356 // continue;
357 // }
358 if (isContextuallyConverted(memberCallExpr))
360 if (rewriter)
362 auto const range = getCallSourceRange(memberCallExpr);
363 if (range.isValid() && removeText(range))
365 return true;
368 report(DiagnosticsEngine::Warning, "simplify, drop the get()", memberCallExpr->getExprLoc())
369 << memberCallExpr->getSourceRange();
371 else if (isa<ParenExpr>(castExpr->getSubExpr()))
373 if (rewriter)
375 auto const loc
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
381 return true;
384 report(DiagnosticsEngine::Warning,
385 "simplify, drop the get() and turn the surrounding parentheses into a functional "
386 "cast to bool",
387 memberCallExpr->getExprLoc())
388 << memberCallExpr->getSourceRange();
389 report(DiagnosticsEngine::Note, "surrounding parentheses here",
390 castExpr->getSubExpr()->getExprLoc())
391 << castExpr->getSubExpr()->getSourceRange();
393 else
395 if (rewriter)
397 auto const loc
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
404 return true;
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();
414 return true;
417 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator const* binOp)
419 if (ignoreLocation(binOp))
420 return true;
421 auto opCode = binOp->getOpcode();
422 if (opCode != BO_EQ && opCode != BO_NE)
423 return true;
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();
429 else
430 return true;
431 auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(possibleMemberCall);
432 if (!memberCallExpr)
433 return true;
434 auto methodDecl = memberCallExpr->getMethodDecl();
435 if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
436 return true;
437 if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
438 return true;
439 report(DiagnosticsEngine::Warning,
440 std::string("simplify, convert to ") + (opCode == BO_EQ ? "'!x'" : "'x'"),
441 binOp->getExprLoc())
442 << binOp->getSourceRange();
443 return true;
446 loplugin::Plugin::Registration<SimplifyPointerToBool> simplifypointertobool("simplifypointertobool",
447 true);
449 } // namespace
451 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */