/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again. #include #include #include #include "clang/Basic/Builtins.h" #include "check.hxx" #include "compat.hxx" #include "plugin.hxx" // Find implicit conversions from non-'bool' constants (e.g., 'sal_False') to 'bool'. namespace { class ConstToBool final : public loplugin::FilteringPlugin { public: explicit ConstToBool(loplugin::InstantiationData const& data) : FilteringPlugin(data) { } bool PreTraverseLinkageSpecDecl(LinkageSpecDecl*) { assert(externCContexts_ != std::numeric_limits::max()); //TODO ++externCContexts_; return true; } bool PostTraverseLinkageSpecDecl(LinkageSpecDecl*, bool) { assert(externCContexts_ != 0); --externCContexts_; return true; } bool TraverseLinkageSpecDecl(LinkageSpecDecl* decl) { bool ret = true; if (PreTraverseLinkageSpecDecl(decl)) { ret = FilteringPlugin::TraverseLinkageSpecDecl(decl); PostTraverseLinkageSpecDecl(decl, ret); } return ret; } bool PreTraverseUnaryOperator(UnaryOperator* expr) { if (expr->getOpcode() == UO_LNot) { ignoredInAssert_.push(expr->getSubExpr()); } return true; } bool PostTraverseUnaryOperator(UnaryOperator* expr, bool) { if (expr->getOpcode() == UO_LNot) { assert(!ignoredInAssert_.empty()); ignoredInAssert_.pop(); } return true; } bool TraverseUnaryOperator(UnaryOperator* expr) { bool ret = true; if (PreTraverseUnaryOperator(expr)) { ret = FilteringPlugin::TraverseUnaryOperator(expr); PostTraverseUnaryOperator(expr, ret); } return ret; } bool PreTraverseBinaryOperator(BinaryOperator* expr) { if (expr->getOpcode() == BO_LAnd) { ignoredInAssert_.push(expr->getRHS()); } return true; } bool PostTraverseBinaryOperator(BinaryOperator* expr, bool) { if (expr->getOpcode() == BO_LAnd) { assert(!ignoredInAssert_.empty()); ignoredInAssert_.pop(); } return true; } bool TraverseBinaryOperator(BinaryOperator* expr) { bool ret = true; if (PreTraverseBinaryOperator(expr)) { ret = FilteringPlugin::TraverseBinaryOperator(expr); PostTraverseBinaryOperator(expr, ret); } return ret; } bool VisitImplicitCastExpr(ImplicitCastExpr const* expr) { if (ignoreLocation(expr)) { return true; } if (!expr->getType()->isBooleanType()) { return true; } auto const sub = expr->getSubExpr(); auto const t = sub->getType(); if (t->isBooleanType()) { return true; } if (sub->isValueDependent()) { return true; } APValue res; if (!sub->isCXX11ConstantExpr(compiler.getASTContext(), &res)) { return true; } auto const l = expr->getExprLoc(); if (!ignoredInAssert_.empty() && expr == ignoredInAssert_.top()) { if (auto const e = dyn_cast(sub->IgnoreParenImpCasts())) { if (compat::isOrdinary(e)) // somewhat randomly restrict to plain literals { if (compiler.getSourceManager().isMacroArgExpansion(l) && Lexer::getImmediateMacroName(l, compiler.getSourceManager(), compiler.getLangOpts()) == "assert") { //TODO: only ignore outermost '!"..."' or '... && "..."' return true; } } } } auto l1 = l; if (compiler.getSourceManager().isMacroBodyExpansion(l1)) { auto const n = Lexer::getImmediateMacroName(l1, compiler.getSourceManager(), compiler.getLangOpts()); if (n == "FALSE" || n == "TRUE" || n == "sal_False" || n == "sal_True") { l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1); } // For example, /usr/include/glib-2.0/glib/gmacros.h from // glib2-devel-2.62.1-1.fc31.x86_64 has // // #define TRUE (!FALSE) // // so handle that wrapped macro body expansion, too: if (compiler.getSourceManager().isMacroBodyExpansion(l1) && Lexer::getImmediateMacroName(l1, compiler.getSourceManager(), compiler.getLangOpts()) == "TRUE") { l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1); } } if (isSharedCAndCppCode(l1)) { // Cover just enough cases to handle things like `while (0)` or the use of `sal_True` in // // #define OSL_FAIL(m) SAL_DETAIL_WARN_IF_FORMAT(sal_True, "legacy.osl", "%s", m) // // in include/osl/diagnose.h: if (auto const t1 = t->getAs()) { if (t1->getKind() == BuiltinType::Int) { auto const& v = res.getInt(); if (v == 0 || v == 1) { return true; } } } if (loplugin::TypeCheck(t).Typedef("sal_Bool").GlobalNamespace()) { return true; } } if (auto const e = dyn_cast(sub->IgnoreParenImpCasts())) { // Ignore use of `long __builtin_expect(long, long)`, as found in the definition of // `assert` on macOS: if (e->getBuiltinCallee() == Builtin::BI__builtin_expect) { return true; } } bool suggestion; bool replacement = {}; if (res.isInt()) { suggestion = true; replacement = res.getInt() != 0; } else if (res.isFloat()) { suggestion = true; replacement = !res.getFloat().isZero(); } else if (res.isNullPointer()) { suggestion = true; replacement = false; } else if (res.isLValue()) { suggestion = true; replacement = true; } else { suggestion = false; } report(DiagnosticsEngine::Warning, "implicit conversion of constant %0 of type %1 to 'bool'%select{|; use " "'%select{false|true}3' instead}2", l) << res.getAsString(compiler.getASTContext(), t) << t << suggestion << replacement << expr->getSourceRange(); return true; } bool preRun() override { return compiler.getLangOpts().CPlusPlus; } private: std::stack ignoredInAssert_; unsigned int externCContexts_ = 0; void run() override { if (preRun()) { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } } bool isFromCIncludeFile(SourceLocation spellingLocation) const { return !compiler.getSourceManager().isInMainFile(spellingLocation) && compat::ends_with(StringRef(compiler.getSourceManager() .getPresumedLoc(spellingLocation) .getFilename()), ".h"); } bool isSharedCAndCppCode(SourceLocation location) const { while (compiler.getSourceManager().isMacroArgExpansion(location)) { location = compiler.getSourceManager().getImmediateMacroCallerLoc(location); } // Assume that code is intended to be shared between C and C++ if it comes from an include // file ending in .h, and is either in an extern "C" context or the body of a macro // definition: return isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(location)) && (externCContexts_ != 0 || compiler.getSourceManager().isMacroBodyExpansion(location)); } }; loplugin::Plugin::Registration consttobool("consttobool"); } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */