office-gobmx/compilerplugins/clang/unsignedcompare.cxx
Andrea Gelmini 102cbf4626 Fix "lets" -> "let's"
Change-Id: I01968fc18b093dbbc27213f01c3da38ae151c62c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169748
Reviewed-by: Andrea Gelmini <andrea.gelmini@gelma.net>
Tested-by: Jenkins
2024-06-29 17:22:12 +02:00

232 lines
7.4 KiB
C++

/* -*- 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 <cassert>
#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<BuiltinType>())
{
// 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<BuiltinType>())
{
// 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<BuiltinType>();
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<UnsignedCompare>
{
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<ExplicitCastExpr>(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("unsignedcompare");
}
#endif // LO_CLANG_SHARED_PLUGINS
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */