Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / consttobool.cxx
blobc13bf7729e7253351ce5bfe62fa5512025d844f4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
12 #include <cassert>
13 #include <limits>
14 #include <stack>
16 #include "clang/Basic/Builtins.h"
18 #include "check.hxx"
19 #include "compat.hxx"
20 #include "plugin.hxx"
22 // Find implicit conversions from non-'bool' constants (e.g., 'sal_False') to 'bool'.
24 namespace
26 class ConstToBool final : public loplugin::FilteringPlugin<ConstToBool>
28 public:
29 explicit ConstToBool(loplugin::InstantiationData const& data)
30 : FilteringPlugin(data)
34 bool PreTraverseLinkageSpecDecl(LinkageSpecDecl*)
36 assert(externCContexts_ != std::numeric_limits<unsigned int>::max()); //TODO
37 ++externCContexts_;
38 return true;
41 bool PostTraverseLinkageSpecDecl(LinkageSpecDecl*, bool)
43 assert(externCContexts_ != 0);
44 --externCContexts_;
45 return true;
48 bool TraverseLinkageSpecDecl(LinkageSpecDecl* decl)
50 bool ret = true;
51 if (PreTraverseLinkageSpecDecl(decl))
53 ret = FilteringPlugin::TraverseLinkageSpecDecl(decl);
54 PostTraverseLinkageSpecDecl(decl, ret);
56 return ret;
59 bool PreTraverseUnaryOperator(UnaryOperator* expr)
61 if (expr->getOpcode() == UO_LNot)
63 ignoredInAssert_.push(expr->getSubExpr());
65 return true;
68 bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
70 if (expr->getOpcode() == UO_LNot)
72 assert(!ignoredInAssert_.empty());
73 ignoredInAssert_.pop();
75 return true;
78 bool TraverseUnaryOperator(UnaryOperator* expr)
80 bool ret = true;
81 if (PreTraverseUnaryOperator(expr))
83 ret = FilteringPlugin::TraverseUnaryOperator(expr);
84 PostTraverseUnaryOperator(expr, ret);
86 return ret;
89 bool PreTraverseBinaryOperator(BinaryOperator* expr)
91 if (expr->getOpcode() == BO_LAnd)
93 ignoredInAssert_.push(expr->getRHS());
95 return true;
98 bool PostTraverseBinaryOperator(BinaryOperator* expr, bool)
100 if (expr->getOpcode() == BO_LAnd)
102 assert(!ignoredInAssert_.empty());
103 ignoredInAssert_.pop();
105 return true;
108 bool TraverseBinaryOperator(BinaryOperator* expr)
110 bool ret = true;
111 if (PreTraverseBinaryOperator(expr))
113 ret = FilteringPlugin::TraverseBinaryOperator(expr);
114 PostTraverseBinaryOperator(expr, ret);
116 return ret;
119 bool VisitImplicitCastExpr(ImplicitCastExpr const* expr)
121 if (ignoreLocation(expr))
123 return true;
125 if (!expr->getType()->isBooleanType())
127 return true;
129 auto const sub = expr->getSubExpr();
130 auto const t = sub->getType();
131 if (t->isBooleanType())
133 return true;
135 if (sub->isValueDependent())
137 return true;
139 APValue res;
140 if (!sub->isCXX11ConstantExpr(compiler.getASTContext(), &res))
142 return true;
144 auto const l = expr->getExprLoc();
145 if (!ignoredInAssert_.empty() && expr == ignoredInAssert_.top())
147 if (auto const e = dyn_cast<clang::StringLiteral>(sub->IgnoreParenImpCasts()))
149 if (compat::isOrdinary(e)) // somewhat randomly restrict to plain literals
151 if (compiler.getSourceManager().isMacroArgExpansion(l)
152 && Lexer::getImmediateMacroName(l, compiler.getSourceManager(),
153 compiler.getLangOpts())
154 == "assert")
156 //TODO: only ignore outermost '!"..."' or '... && "..."'
157 return true;
162 auto l1 = l;
163 if (compiler.getSourceManager().isMacroBodyExpansion(l1))
165 auto const n = Lexer::getImmediateMacroName(l1, compiler.getSourceManager(),
166 compiler.getLangOpts());
167 if (n == "FALSE" || n == "TRUE" || n == "sal_False" || n == "sal_True")
169 l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1);
171 // For example, /usr/include/glib-2.0/glib/gmacros.h from
172 // glib2-devel-2.62.1-1.fc31.x86_64 has
174 // #define TRUE (!FALSE)
176 // so handle that wrapped macro body expansion, too:
177 if (compiler.getSourceManager().isMacroBodyExpansion(l1)
178 && Lexer::getImmediateMacroName(l1, compiler.getSourceManager(),
179 compiler.getLangOpts())
180 == "TRUE")
182 l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1);
185 if (isSharedCAndCppCode(l1))
187 // Cover just enough cases to handle things like `while (0)` or the use of `sal_True` in
189 // #define OSL_FAIL(m) SAL_DETAIL_WARN_IF_FORMAT(sal_True, "legacy.osl", "%s", m)
191 // in include/osl/diagnose.h:
192 if (auto const t1 = t->getAs<BuiltinType>())
194 if (t1->getKind() == BuiltinType::Int)
196 auto const& v = res.getInt();
197 if (v == 0 || v == 1)
199 return true;
203 if (loplugin::TypeCheck(t).Typedef("sal_Bool").GlobalNamespace())
205 return true;
208 if (auto const e = dyn_cast<CallExpr>(sub->IgnoreParenImpCasts()))
210 // Ignore use of `long __builtin_expect(long, long)`, as found in the definition of
211 // `assert` on macOS:
212 if (e->getBuiltinCallee() == Builtin::BI__builtin_expect)
214 return true;
217 bool suggestion;
218 bool replacement = {};
219 if (res.isInt())
221 suggestion = true;
222 replacement = res.getInt() != 0;
224 else if (res.isFloat())
226 suggestion = true;
227 replacement = !res.getFloat().isZero();
229 else if (res.isNullPointer())
231 suggestion = true;
232 replacement = false;
234 else if (res.isLValue())
236 suggestion = true;
237 replacement = true;
239 else
241 suggestion = false;
243 report(DiagnosticsEngine::Warning,
244 "implicit conversion of constant %0 of type %1 to 'bool'%select{|; use "
245 "'%select{false|true}3' instead}2",
247 << res.getAsString(compiler.getASTContext(), t) << t << suggestion << replacement
248 << expr->getSourceRange();
249 return true;
252 bool preRun() override { return compiler.getLangOpts().CPlusPlus; }
254 private:
255 std::stack<Expr const*> ignoredInAssert_;
256 unsigned int externCContexts_ = 0;
258 void run() override
260 if (preRun())
262 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
266 bool isFromCIncludeFile(SourceLocation spellingLocation) const
268 return !compiler.getSourceManager().isInMainFile(spellingLocation)
269 && (StringRef(
270 compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename())
271 .endswith(".h"));
274 bool isSharedCAndCppCode(SourceLocation location) const
276 while (compiler.getSourceManager().isMacroArgExpansion(location))
278 location = compiler.getSourceManager().getImmediateMacroCallerLoc(location);
280 // Assume that code is intended to be shared between C and C++ if it comes from an include
281 // file ending in .h, and is either in an extern "C" context or the body of a macro
282 // definition:
283 return isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(location))
284 && (externCContexts_ != 0
285 || compiler.getSourceManager().isMacroBodyExpansion(location));
289 loplugin::Plugin::Registration<ConstToBool> consttobool("consttobool");
292 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */