1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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 */
9
10 #include <algorithm>
11 #include <cassert>
12 #include <deque>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <set>
17
18 #include <clang/AST/CXXInheritance.h>
19
20 #include "config_clang.h"
21
22 #include "plugin.hxx"
23 #include "check.hxx"
24 #include "compat.hxx"
25
26 /**
27 Simplify boolean expressions involving smart pointers e.g.
28 if (x.get())
29 can be
30 if (x)
31 */
32 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
33
34 namespace
35 {
36 class SimplifyPointerToBool : public loplugin::FilteringRewritePlugin<SimplifyPointerToBool>
37 {
38 public:
SimplifyPointerToBool(loplugin::InstantiationData const & data)39 explicit SimplifyPointerToBool(loplugin::InstantiationData const& data)
40 : FilteringRewritePlugin(data)
41 {
42 }
43
run()44 virtual void run() override
45 {
46 if (preRun())
47 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
48 }
49
50 bool VisitImplicitCastExpr(ImplicitCastExpr const*);
51 bool VisitBinaryOperator(BinaryOperator const*);
52
PreTraverseUnaryOperator(UnaryOperator * expr)53 bool PreTraverseUnaryOperator(UnaryOperator* expr)
54 {
55 if (expr->getOpcode() == UO_LNot)
56 {
57 contextuallyConvertedExprs_.push_back(expr->getSubExpr()->IgnoreParenImpCasts());
58 }
59 return true;
60 }
61
PostTraverseUnaryOperator(UnaryOperator * expr,bool)62 bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
63 {
64 if (expr->getOpcode() == UO_LNot)
65 {
66 assert(!contextuallyConvertedExprs_.empty());
67 contextuallyConvertedExprs_.pop_back();
68 }
69 return true;
70 }
71
TraverseUnaryOperator(UnaryOperator * expr)72 bool TraverseUnaryOperator(UnaryOperator* expr)
73 {
74 auto res = PreTraverseUnaryOperator(expr);
75 assert(res);
76 res = FilteringRewritePlugin::TraverseUnaryOperator(expr);
77 PostTraverseUnaryOperator(expr, res);
78 return res;
79 }
80
81 #if CLANG_VERSION < 110000
TraverseUnaryLNot(UnaryOperator * expr)82 bool TraverseUnaryLNot(UnaryOperator* expr) { return TraverseUnaryOperator(expr); }
83 #endif
84
PreTraverseBinaryOperator(BinaryOperator * expr)85 bool PreTraverseBinaryOperator(BinaryOperator* expr)
86 {
87 auto const op = expr->getOpcode();
88 if (op == BO_LAnd || op == BO_LOr)
89 {
90 contextuallyConvertedExprs_.push_back(expr->getLHS()->IgnoreParenImpCasts());
91 contextuallyConvertedExprs_.push_back(expr->getRHS()->IgnoreParenImpCasts());
92 }
93 return true;
94 }
95
PostTraverseBinaryOperator(BinaryOperator * expr,bool)96 bool PostTraverseBinaryOperator(BinaryOperator* expr, bool)
97 {
98 auto const op = expr->getOpcode();
99 if (op == BO_LAnd || op == BO_LOr)
100 {
101 assert(contextuallyConvertedExprs_.size() >= 2);
102 contextuallyConvertedExprs_.pop_back();
103 contextuallyConvertedExprs_.pop_back();
104 }
105 return true;
106 }
107
TraverseBinaryOperator(BinaryOperator * expr)108 bool TraverseBinaryOperator(BinaryOperator* expr)
109 {
110 auto res = PreTraverseBinaryOperator(expr);
111 assert(res);
112 res = FilteringRewritePlugin::TraverseBinaryOperator(expr);
113 PostTraverseBinaryOperator(expr, res);
114 return res;
115 }
116
117 #if CLANG_VERSION < 110000
TraverseBinLAnd(BinaryOperator * expr)118 bool TraverseBinLAnd(BinaryOperator* expr) { return TraverseBinaryOperator(expr); }
TraverseBinLOr(BinaryOperator * expr)119 bool TraverseBinLOr(BinaryOperator* expr) { return TraverseBinaryOperator(expr); }
120 #endif
121
PreTraverseConditionalOperator(ConditionalOperator * expr)122 bool PreTraverseConditionalOperator(ConditionalOperator* expr)
123 {
124 contextuallyConvertedExprs_.push_back(expr->getCond()->IgnoreParenImpCasts());
125 return true;
126 }
127
PostTraverseConditionalOperator(ConditionalOperator *,bool)128 bool PostTraverseConditionalOperator(ConditionalOperator*, bool)
129 {
130 assert(!contextuallyConvertedExprs_.empty());
131 contextuallyConvertedExprs_.pop_back();
132 return true;
133 }
134
TraverseConditionalOperator(ConditionalOperator * expr)135 bool TraverseConditionalOperator(ConditionalOperator* expr)
136 {
137 auto res = PreTraverseConditionalOperator(expr);
138 assert(res);
139 res = FilteringRewritePlugin::TraverseConditionalOperator(expr);
140 PostTraverseConditionalOperator(expr, res);
141 return res;
142 }
143
PreTraverseIfStmt(IfStmt * stmt)144 bool PreTraverseIfStmt(IfStmt* stmt)
145 {
146 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
147 return true;
148 }
149
PostTraverseIfStmt(IfStmt *,bool)150 bool PostTraverseIfStmt(IfStmt*, bool)
151 {
152 assert(!contextuallyConvertedExprs_.empty());
153 contextuallyConvertedExprs_.pop_back();
154 return true;
155 }
156
TraverseIfStmt(IfStmt * stmt)157 bool TraverseIfStmt(IfStmt* stmt)
158 {
159 auto res = PreTraverseIfStmt(stmt);
160 assert(res);
161 res = FilteringRewritePlugin::TraverseIfStmt(stmt);
162 PostTraverseIfStmt(stmt, res);
163 return res;
164 }
165
PreTraverseWhileStmt(WhileStmt * stmt)166 bool PreTraverseWhileStmt(WhileStmt* stmt)
167 {
168 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
169 return true;
170 }
171
PostTraverseWhileStmt(WhileStmt *,bool)172 bool PostTraverseWhileStmt(WhileStmt*, bool)
173 {
174 assert(!contextuallyConvertedExprs_.empty());
175 contextuallyConvertedExprs_.pop_back();
176 return true;
177 }
178
TraverseWhileStmt(WhileStmt * stmt)179 bool TraverseWhileStmt(WhileStmt* stmt)
180 {
181 auto res = PreTraverseWhileStmt(stmt);
182 assert(res);
183 res = FilteringRewritePlugin::TraverseWhileStmt(stmt);
184 PostTraverseWhileStmt(stmt, res);
185 return res;
186 }
187
PreTraverseDoStmt(DoStmt * stmt)188 bool PreTraverseDoStmt(DoStmt* stmt)
189 {
190 contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
191 return true;
192 }
193
PostTraverseDoStmt(DoStmt *,bool)194 bool PostTraverseDoStmt(DoStmt*, bool)
195 {
196 assert(!contextuallyConvertedExprs_.empty());
197 contextuallyConvertedExprs_.pop_back();
198 return true;
199 }
200
TraverseDoStmt(DoStmt * stmt)201 bool TraverseDoStmt(DoStmt* stmt)
202 {
203 auto res = PreTraverseDoStmt(stmt);
204 assert(res);
205 res = FilteringRewritePlugin::TraverseDoStmt(stmt);
206 PostTraverseDoStmt(stmt, res);
207 return res;
208 }
209
PreTraverseForStmt(ForStmt * stmt)210 bool PreTraverseForStmt(ForStmt* stmt)
211 {
212 auto const e = stmt->getCond();
213 if (e != nullptr)
214 {
215 contextuallyConvertedExprs_.push_back(e->IgnoreParenImpCasts());
216 }
217 return true;
218 }
219
PostTraverseForStmt(ForStmt * stmt,bool)220 bool PostTraverseForStmt(ForStmt* stmt, bool)
221 {
222 if (stmt->getCond() != nullptr)
223 {
224 assert(!contextuallyConvertedExprs_.empty());
225 contextuallyConvertedExprs_.pop_back();
226 }
227 return true;
228 }
229
TraverseForStmt(ForStmt * stmt)230 bool TraverseForStmt(ForStmt* stmt)
231 {
232 auto res = PreTraverseForStmt(stmt);
233 assert(res);
234 res = FilteringRewritePlugin::TraverseForStmt(stmt);
235 PostTraverseForStmt(stmt, res);
236 return res;
237 }
238
239 private:
isContextuallyConverted(Expr const * expr) const240 bool isContextuallyConverted(Expr const* expr) const
241 {
242 return std::find(contextuallyConvertedExprs_.begin(), contextuallyConvertedExprs_.end(),
243 expr)
244 != contextuallyConvertedExprs_.end();
245 }
246
247 // Get the source range starting at the "."or "->" (plus any preceding non-comment white space):
getCallSourceRange(CXXMemberCallExpr const * expr) const248 SourceRange getCallSourceRange(CXXMemberCallExpr const* expr) const
249 {
250 if (expr->getImplicitObjectArgument() == nullptr)
251 {
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 {};
257 }
258 // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
259 auto start = compiler.getSourceManager().getSpellingLoc(expr->getExprLoc());
260 if (!start.isValid())
261 {
262 return {};
263 }
264 for (;;)
265 {
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"))
272 {
273 continue;
274 }
275 if (s != "." && s != "->")
276 {
277 return {};
278 }
279 break;
280 }
281 for (;;)
282 {
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")))
289 {
290 break;
291 }
292 start = start1;
293 }
294 return SourceRange(start,
295 compiler.getSourceManager().getSpellingLoc(compat::getEndLoc(expr)));
296 }
297
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_;
301 };
302
VisitImplicitCastExpr(ImplicitCastExpr const * castExpr)303 bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr const* castExpr)
304 {
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;
333 //
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))
365 {
366 if (rewriter)
367 {
368 auto const range = getCallSourceRange(memberCallExpr);
369 if (range.isValid() && removeText(range))
370 {
371 return true;
372 }
373 }
374 report(DiagnosticsEngine::Warning, "simplify, drop the get()", memberCallExpr->getExprLoc())
375 << memberCallExpr->getSourceRange();
376 }
377 else if (isa<ParenExpr>(castExpr->getSubExpr()))
378 {
379 if (rewriter)
380 {
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))
385 {
386 //TODO: atomically only change both or neither
387 return true;
388 }
389 }
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();
398 }
399 else
400 {
401 if (rewriter)
402 {
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, ")"))
408 {
409 //TODO: atomically only change both or neither
410 return true;
411 }
412 }
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();
417 }
418 // report(DiagnosticsEngine::Note, "method here", param->getLocation())
419 // << param->getSourceRange();
420 return true;
421 }
422
VisitBinaryOperator(BinaryOperator const * binOp)423 bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator const* binOp)
424 {
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;
450 }
451
452 loplugin::Plugin::Registration<SimplifyPointerToBool> simplifypointertobool("simplifypointertobool",
453 true);
454
455 } // namespace
456
457 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
458