office-gobmx/compilerplugins/clang/nullptr.cxx
Stephan Bergmann 56e6d683db double operator < is not a strict weak ordering due to NaN
...so recent LLVM 19 trunk libc++ in debug mode complained during
CppunitTest_chart2_export2 about

> ~/llvm/inst/bin/../include/c++/v1/__debug_utils/strict_weak_ordering_check.h:59: assertion __comp(*(__first + __a), *(__first + __b)) failed: Your comparator is not a valid strict-weak ordering

at

> 5   libsystem_c.dylib                   0x0000000183279a40 abort + 180
> 6   libc++.1.0.dylib                    0x00000001030f9d98 _ZNSt3__123__cxx_atomic_notify_oneEPVKv + 0
> 7   libchartcorelo.dylib                0x00000002f817f0ec _ZNSt3__135__check_strict_weak_ordering_sortedB8de190000INS_11__wrap_iterIPNS_6vectorIdNS_9allocatorIdEEEEEEN5chart12_GLOBAL__N_116lcl_LessXOfPointEEEvT_SB_RT0_ + 960
> 8   libchartcorelo.dylib                0x00000002f817e6cc _ZNSt3__118__stable_sort_implB8de190000INS_17_ClassicAlgPolicyENS_11__wrap_iterIPNS_6vectorIdNS_9allocatorIdEEEEEEN5chart12_GLOBAL__N_116lcl_LessXOfPointEEEvT0_SC_RT1_ + 268
> 9   libchartcorelo.dylib                0x00000002f8172a90 _ZNSt3__111stable_sortB8de190000INS_11__wrap_iterIPNS_6vectorIdNS_9allocatorIdEEEEEEN5chart12_GLOBAL__N_116lcl_LessXOfPointEEEvT_SB_T0_ + 68
> 10  libchartcorelo.dylib                0x00000002f8172820 _ZN5chart11VDataSeries15doSortByXValuesEv + 508
> 11  libchartcorelo.dylib                0x00000002f8064c44 _ZN5chart9AreaChart12createShapesEv + 1528
> 12  libchartcorelo.dylib                0x00000002f80f2ae0 _ZN5chart9ChartView28impl_createDiagramAndContentERKNS_18CreateShapeParam2DERKN3com3sun4star3awt4SizeE + 4440
> 13  libchartcorelo.dylib                0x00000002f80f77ac _ZN5chart9ChartView14createShapes2DERKN3com3sun4star3awt4SizeE + 2728
> 14  libchartcorelo.dylib                0x00000002f80f58ec _ZN5chart9ChartView12createShapesEv + 692
> 15  libchartcorelo.dylib                0x00000002f80f4598 _ZN5chart9ChartView15impl_updateViewEb + 288

But the introduced use of `std::strong_order(first[0], second[0]) < 0` then
triggered a false

> lo/core/chart2/source/view/main/VDataSeries.cxx:105:61: error: NullToMemberPointer ValueDependentIsNotNull ZeroLiteral -> nullptr [loplugin:nullptr]
>   105 |             return std::strong_order(first[0], second[0]) < 0;
>       |                                                             ^

so needed some hack in loplugin:nullptr.

And old versions of libc++, still used at least on Android, do not have any
implementations of std::strong_order.  So detect those cases in configure.ac
(checking for std::strong_order for double, which is what is actually being used
in the code) and fall back to operator <=> for now, even if that will not
provide a strict weak ordering and will thus continue to violate the
requirements of std::sort.

And then our venerable clang-format 5.0.0 would have broken the token `<=>` into
`<= >`, so exclude include/o3tl/compare.hxx from its mis-treatment.

Change-Id: I7a64a630eb5f560dce59f3ff9d51ca3d1adc70be
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163075
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
2024-02-07 21:03:46 +01:00

437 lines
14 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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/.
*/
#include <cassert>
#include <cstdlib>
#include <limits>
#include <set>
#include "check.hxx"
#include "compat.hxx"
#include "plugin.hxx"
namespace {
char const * kindName(Expr::NullPointerConstantKind kind) {
switch (kind) {
case Expr::NPCK_ZeroExpression:
return "ZeroExpression";
case Expr::NPCK_ZeroLiteral:
return "ZeroLiteral";
case Expr::NPCK_CXX11_nullptr:
return "CXX11_nullptr";
case Expr::NPCK_GNUNull:
return "GNUNull";
case Expr::NPCK_NotNull:
break; // cannot happen
}
llvm_unreachable("unknown null pointer kind");
}
bool isAnyKindOfPointerType(QualType type) {
return type->isAnyPointerType() || type->isFunctionPointerType()
|| type->isMemberPointerType();
}
bool isNullPointerCast(CastExpr const * expr) {
switch (expr->getCastKind()) {
case CK_NullToPointer:
case CK_NullToMemberPointer:
return true;
default:
return false;
}
}
class Nullptr:
public loplugin::FilteringRewritePlugin<Nullptr>
{
public:
explicit Nullptr(loplugin::InstantiationData const & data):
FilteringRewritePlugin(data) {}
void run() override
{ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
bool VisitImplicitCastExpr(CastExpr const * expr);
bool VisitGNUNullExpr(GNUNullExpr const * expr);
bool VisitBinaryOperator(BinaryOperator const * expr);
bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const * expr);
bool VisitParmVarDecl(ParmVarDecl const * decl);
bool TraverseConstructorInitializer(CXXCtorInitializer * init);
bool TraverseLinkageSpecDecl(LinkageSpecDecl * decl);
bool TraverseInitListExpr(InitListExpr * expr, DataRecursionQueue * queue = nullptr);
bool shouldVisitTemplateInstantiations() const { return true; }
private:
bool isInLokIncludeFile(SourceLocation spellingLocation) const;
bool isFromCIncludeFile(SourceLocation spellingLocation) const;
bool isSharedCAndCppCode(SourceLocation location) const;
void visitCXXCtorInitializer(CXXCtorInitializer const * init);
void handleZero(Expr const * expr);
void handleNull(
Expr const * expr, char const * castKind,
Expr::NullPointerConstantKind nullPointerKind);
void rewriteOrWarn(
Expr const * expr, char const * castKind,
Expr::NullPointerConstantKind nullPointerKind,
char const * replacement);
std::set<Expr const *> gnuNulls_;
unsigned int externCContexts_ = 0;
};
bool Nullptr::VisitImplicitCastExpr(CastExpr const * expr) {
if (ignoreLocation(expr)) {
return true;
}
if (!isNullPointerCast(expr)) {
return true;
}
Expr::NullPointerConstantKind k = expr->isNullPointerConstant(
compiler.getASTContext(), Expr::NPC_ValueDependentIsNotNull);
switch (k) {
case Expr::NPCK_NotNull:
k = expr->isNullPointerConstant(
compiler.getASTContext(), Expr::NPC_ValueDependentIsNull);
switch (k) {
case Expr::NPCK_NotNull:
break;
case Expr::NPCK_ZeroExpression:
case Expr::NPCK_ZeroLiteral:
report(
DiagnosticsEngine::Warning,
"suspicious ValueDependentIsNull %0", expr->getBeginLoc())
<< kindName(k) << expr->getSourceRange();
break;
default:
assert(false); // cannot happen
}
break;
case Expr::NPCK_CXX11_nullptr:
break;
default:
auto const tc = loplugin::TypeCheck(expr->getType());
if (tc.Typedef("locale_t")
.GlobalNamespace())
{
break; // POSIX locale_t is left unspecified
}
// Hack to handle libc++ and stdlibc++ `std::strong_ordering x; x < 0` etc.:
if (tc.MemberPointerOf().ClassOrStruct("_CmpUnspecifiedParam").StdNamespace()
|| tc.Pointer().ClassOrStruct("__unspec").Namespace("__cmp_cat").StdNamespace())
{
break;
}
handleNull(expr->getSubExpr(), expr->getCastKindName(), k);
break;
}
return true;
}
bool Nullptr::VisitGNUNullExpr(GNUNullExpr const * expr) {
if (ignoreLocation(expr)) {
return true;
}
handleNull(expr, nullptr, Expr::NPCK_GNUNull);
return true;
}
bool Nullptr::VisitBinaryOperator(BinaryOperator const * expr) {
if (ignoreLocation(expr)) {
return true;
}
Expr const * e;
switch (expr->getOpcode()) {
case BO_EQ:
case BO_NE:
if (isAnyKindOfPointerType(expr->getRHS()->getType())) {
e = expr->getLHS();
break;
}
// fall through
case BO_Assign:
if (isAnyKindOfPointerType(expr->getLHS()->getType())) {
e = expr->getRHS();
break;
}
// fall through
default:
return true;
}
handleZero(e);
return true;
}
bool Nullptr::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const * expr) {
if (ignoreLocation(expr)) {
return true;
}
Expr const * e;
switch (expr->getOperator()) {
case OO_EqualEqual:
case OO_ExclaimEqual:
if (isAnyKindOfPointerType(expr->getArg(1)->getType())) {
e = expr->getArg(0);
break;
}
// fall through
case OO_Equal:
if (isAnyKindOfPointerType(expr->getArg(0)->getType())) {
e = expr->getArg(1);
break;
}
// fall through
default:
return true;
}
handleZero(e);
return true;
}
bool Nullptr::VisitParmVarDecl(ParmVarDecl const * decl) {
if (ignoreLocation(decl)) {
return true;
}
if (!isAnyKindOfPointerType(decl->getType())) {
return true;
}
if (decl->hasUninstantiatedDefaultArg()) {
return true; //TODO
}
auto e = decl->getDefaultArg();
if (e == nullptr) {
return true;
}
handleZero(e);
return true;
}
bool Nullptr::TraverseConstructorInitializer(CXXCtorInitializer * init) {
visitCXXCtorInitializer(init);
return RecursiveASTVisitor::TraverseConstructorInitializer(init);
}
bool Nullptr::TraverseLinkageSpecDecl(LinkageSpecDecl * decl) {
assert(externCContexts_ != std::numeric_limits<unsigned int>::max()); //TODO
++externCContexts_;
bool ret = RecursiveASTVisitor::TraverseLinkageSpecDecl(decl);
assert(externCContexts_ != 0);
--externCContexts_;
return ret;
}
bool Nullptr::TraverseInitListExpr(InitListExpr * expr, DataRecursionQueue * queue) {
return WalkUpFromInitListExpr(expr)
&& TraverseSynOrSemInitListExpr(
expr->isSemanticForm() ? expr : expr->getSemanticForm(), queue);
}
bool Nullptr::isInLokIncludeFile(SourceLocation spellingLocation) const {
return loplugin::hasPathnamePrefix(
getFilenameOfLocation(spellingLocation),
SRCDIR "/include/LibreOfficeKit/");
}
bool Nullptr::isFromCIncludeFile(SourceLocation spellingLocation) const {
return !compiler.getSourceManager().isInMainFile(spellingLocation)
&& compat::ends_with(
StringRef(compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename()),
".h");
}
bool Nullptr::isSharedCAndCppCode(SourceLocation location) const {
// 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));
}
void Nullptr::visitCXXCtorInitializer(CXXCtorInitializer const * init) {
if (!init->isWritten()) {
return;
}
auto e = init->getInit();
if (ignoreLocation(e)) {
return;
}
auto d = init->getAnyMember();
if (d == nullptr || !isAnyKindOfPointerType(d->getType())) {
return;
}
if (auto e2 = dyn_cast<ParenListExpr>(e)) {
if (e2->getNumExprs() != 1) {
return;
}
e = e2->getExpr(0);
} else if (auto e2 = dyn_cast<InitListExpr>(e)) {
if (e2->getNumInits() != 1) {
return;
}
e = e2->getInit(0);
}
handleZero(e);
}
void Nullptr::handleZero(Expr const * expr) {
//TODO: detect NPCK_ZeroExpression where appropriate
// Filter out ImplicitCastExpr that will be handled by
// VisitImplicitCastExpr:
if (auto ice = dyn_cast<ImplicitCastExpr>(expr)) {
if (isNullPointerCast(ice)) {
return;
}
}
auto const lit = dyn_cast<IntegerLiteral>(expr->IgnoreParenImpCasts());
if (lit != nullptr && !lit->getValue().getBoolValue()) {
handleNull(expr, nullptr, Expr::NPCK_ZeroLiteral);
}
}
void Nullptr::handleNull(
Expr const * expr, char const * castKind,
Expr::NullPointerConstantKind nullPointerKind)
{
auto e = expr;
SourceLocation loc;
for (;;) {
e = e->IgnoreImpCasts();
loc = e->getBeginLoc();
while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
}
if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
if (Lexer::getImmediateMacroName(
loc, compiler.getSourceManager(), compiler.getLangOpts())
== "NULL")
{
if (!compiler.getLangOpts().CPlusPlus) {
//TODO: if !castKind, warn if NULL is passed into fn call
// ellipsis, cast to void*
return;
}
loc = compat::getImmediateExpansionRange(compiler.getSourceManager(), loc).first;
if (ignoreLocation(
compiler.getSourceManager().getSpellingLoc(loc)))
{
return;
}
if (isInUnoIncludeFile(
compiler.getSourceManager().getSpellingLoc(loc))
|| isInLokIncludeFile(
compiler.getSourceManager().getSpellingLoc(loc))
|| isSharedCAndCppCode(loc))
{
//TODO: if !castKind, warn if NULL is passed into fn call
// ellipsis, cast to void*
return;
}
} else if (ignoreLocation(
compiler.getSourceManager().getSpellingLoc(loc)))
{
return;
}
}
ParenExpr const * pe = dyn_cast<ParenExpr>(e);
if (pe == nullptr) {
break;
}
e = pe->getSubExpr();
}
if (nullPointerKind == Expr::NPCK_GNUNull) {
if (castKind == nullptr) {
if (gnuNulls_.erase(expr) == 1) {
return;
}
} else {
auto const ok = gnuNulls_.insert(expr).second;
assert(ok); (void) ok;
}
}
auto const asMacro = !compiler.getLangOpts().CPlusPlus
|| isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))
|| isInLokIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))
|| isSharedCAndCppCode(loc);
assert(!asMacro || nullPointerKind != Expr::NPCK_GNUNull);
rewriteOrWarn(e, castKind, nullPointerKind, asMacro ? "NULL" : "nullptr");
}
void Nullptr::rewriteOrWarn(
Expr const * expr, char const * castKind,
Expr::NullPointerConstantKind nullPointerKind, char const * replacement)
{
if (rewriter != nullptr) {
SourceLocation locStart(expr->getBeginLoc());
while (compiler.getSourceManager().isMacroArgExpansion(locStart)) {
locStart = compiler.getSourceManager()
.getImmediateMacroCallerLoc(locStart);
}
if (compiler.getLangOpts().CPlusPlus
&& compiler.getSourceManager().isMacroBodyExpansion(locStart)
&& (Lexer::getImmediateMacroName(
locStart, compiler.getSourceManager(),
compiler.getLangOpts())
== "NULL"))
{
locStart = compat::getImmediateExpansionRange(compiler.getSourceManager(), locStart)
.first;
}
SourceLocation locEnd(expr->getEndLoc());
while (compiler.getSourceManager().isMacroArgExpansion(locEnd)) {
locEnd = compiler.getSourceManager()
.getImmediateMacroCallerLoc(locEnd);
}
if (compiler.getLangOpts().CPlusPlus
&& compiler.getSourceManager().isMacroBodyExpansion(locEnd)
&& (Lexer::getImmediateMacroName(
locEnd, compiler.getSourceManager(),
compiler.getLangOpts())
== "NULL"))
{
locEnd = compat::getImmediateExpansionRange(compiler.getSourceManager(), locEnd).first;
}
if (replaceText(SourceRange(compiler.getSourceManager().getSpellingLoc(locStart), compiler.getSourceManager().getSpellingLoc(locEnd)), replacement)) {
return;
}
}
if (castKind == nullptr) {
report(DiagnosticsEngine::Warning, "%0 -> %1", expr->getBeginLoc())
<< kindName(nullPointerKind) << replacement
<< expr->getSourceRange();
} else {
report(
DiagnosticsEngine::Warning, "%0 ValueDependentIsNotNull %1 -> %2",
expr->getBeginLoc())
<< castKind << kindName(nullPointerKind) << replacement
<< expr->getSourceRange();
}
}
loplugin::Plugin::Registration<Nullptr> X("nullptr", true);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */