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