/* -*- 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/. */ #ifndef LO_CLANG_SHARED_PLUGINS // Find explicit casts from signed to unsigned integer in comparison against unsigned integer, where // the cast is presumably used to avoid warnings about signed vs. unsigned comparisons, and could // thus be replaced with o3tl::make_unsigned for clarity. #include #include "plugin.hxx" namespace { // clang::Type::isSignedIntegerType returns true for more types than what C++ defines as signed // integer types: bool isSignedIntegerType(QualType type) { if (auto const t = type->getAs()) { // Assumes that the only extended signed integer type supported by Clang is Int128: switch (t->getKind()) { case BuiltinType::SChar: case BuiltinType::Short: case BuiltinType::Int: case BuiltinType::Long: case BuiltinType::LongLong: case BuiltinType::Int128: return true; default: break; } } return false; } // clang::Type::isUnsignedIntegerType returns true for more types than what C++ defines as signed // integer types: bool isUnsignedIntegerType(QualType type) { if (auto const t = type->getAs()) { // Assumes that the only extended unsigned integer type supported by Clang is UInt128: switch (t->getKind()) { case BuiltinType::UChar: case BuiltinType::UShort: case BuiltinType::UInt: case BuiltinType::ULong: case BuiltinType::ULongLong: case BuiltinType::UInt128: return true; default: break; } } return false; } int getRank(QualType type) { auto const t = type->getAs(); assert(t != nullptr); // Assumes that the only extended signed/unsigned integer types supported by Clang are Int128 // and UInt128: switch (t->getKind()) { case BuiltinType::SChar: case BuiltinType::UChar: return 0; case BuiltinType::Short: case BuiltinType::UShort: return 1; case BuiltinType::Int: case BuiltinType::UInt: return 2; case BuiltinType::Long: case BuiltinType::ULong: return 3; case BuiltinType::LongLong: case BuiltinType::ULongLong: return 4; case BuiltinType::Int128: case BuiltinType::UInt128: return 5; default: llvm_unreachable("bad integer type"); } } int orderTypes(QualType type1, QualType type2) { auto const r1 = getRank(type1); auto const r2 = getRank(type2); return r1 < r2 ? -1 : r1 == r2 ? 0 : 1; } class UnsignedCompare : public loplugin::FilteringPlugin { public: explicit UnsignedCompare(loplugin::InstantiationData const& data) : FilteringPlugin(data) { } bool VisitBinaryOperator(BinaryOperator const* expr) { if (ignoreLocation(expr)) { return true; } // o3tl::make_unsigned requires its argument to be non-negative, but this plugin doesn't // check that when it reports its finding, so will produce false positives when the cast is // actually meant to e.g. clamp from a large signed type to a small unsigned type. The // assumption is that this will only be likely the case for BO_EQ (==) and BO_NE (!=) // comparisons, so filter these out here (not sure what case BO_Cmp (<=>) will turn out to // be, so let's keep it here at least for now): switch (expr->getOpcode()) { case BO_Cmp: case BO_LT: case BO_GT: case BO_LE: case BO_GE: break; default: return true; } auto const castL = isCastToUnsigned(expr->getLHS()); auto const castR = isCastToUnsigned(expr->getRHS()); //TODO(?): Also report somewhat suspicious cases where both sides are cast to unsigned: if ((castL == nullptr) == (castR == nullptr)) { return true; } auto const cast = castL != nullptr ? castL : castR; auto const other = castL != nullptr ? expr->getRHS() : expr->getLHS(); auto const otherT = other->IgnoreImpCasts()->getType(); if (!isUnsignedIntegerType(otherT)) { return true; } auto const castFromT = cast->getSubExprAsWritten()->getType(); auto const castToT = cast->getTypeAsWritten(); report(DiagnosticsEngine::Warning, "explicit cast from %0 to %1 (of %select{smaller|equal|larger}2 rank) in comparison " "against %3: if the cast value is known to be non-negative, use o3tl::make_unsigned " "instead of the cast", cast->getExprLoc()) << castFromT << castToT << (orderTypes(castToT, castFromT) + 1) << otherT << expr->getSourceRange(); return true; } bool preRun() override { return compiler.getLangOpts().CPlusPlus && compiler.getPreprocessor() .getIdentifierInfo("LIBO_INTERNAL_ONLY") ->hasMacroDefinition(); } void run() override { if (preRun()) { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } } private: ExplicitCastExpr const* isCastToUnsigned(Expr const* expr) { auto const e = dyn_cast(expr->IgnoreParenImpCasts()); if (e == nullptr) { return nullptr; } auto const t1 = e->getTypeAsWritten(); if (!isUnsignedIntegerType(t1)) { return nullptr; } auto const e2 = e->getSubExprAsWritten(); auto const t2 = e2->getType(); if (!isSignedIntegerType(t2)) { return nullptr; } // Filter out e.g. `size_t(-1)`: if (!e2->isValueDependent()) { if (auto const val = e2->getIntegerConstantExpr(compiler.getASTContext())) { if (val->isNegative()) { return nullptr; } } } auto loc = e->getBeginLoc(); while (compiler.getSourceManager().isMacroArgExpansion(loc)) { loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc); } // This covers both "plain" code in such include files, as well as expansion of (object-like) macros like // // #define SAL_MAX_INT8 ((sal_Int8) 0x7F) // // defined in such include files: if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))) { //TODO: '#ifdef LIBO_INTERNAL_ONLY' within UNO include files return nullptr; } return e; } }; loplugin::Plugin::Registration unsignedcompare("unsignedcompare"); } #endif // LO_CLANG_SHARED_PLUGINS /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */