SFOS 5.1: Qt 5 moc generates invalid namespace-qualified QObject code after parsing C++14 digit separators in included header

REPRODUCIBILITY: 100%
OS VERSION: 5.1.0.7
HARDWARE: -
UI LANGUAGE: -
REGRESSION: Yes

DESCRIPTION:

Qt 5 moc in the Sailfish OS 5.1 build environment appears to misparse C++14 digit separators in integer literals inside an included C++ header. After parsing such a construct inside namespace absl, moc incorrectly believes the following QObject class is also inside namespace absl.

This causes moc to generate invalid code such as:

void absl::AbslMocTest::qt_static_metacall(...)

even though AbslMocTest is declared in the global namespace.

The normal C++ compiler accepts the code. The failure is specific to moc-generated code.

This was originally found when including S2Geometry/Abseil headers from a header containing Q_OBJECT. The include chain reached absl/hash/internal/hash.h, where this construct exists:

static constexpr uint64_t kStaticRandomData[] = {
    0x243f'6a88'85a3'08d3,
    ...
};

Guarding the include from moc with #ifndef Q_MOC_RUN avoids the problem.

For some reason, in earlier SFOS versions this error wasn’t triggered.

PRECONDITIONS:

A Sailfish OS 5.1 Qt 5 build environment with qmake/moc.

A Qt/QML application with a QObject-derived C++ class processed by moc.

STEPS TO REPRODUCE:

STEPS TO REPRODUCE:

  1. Create a header included before a QObject class processed by moc, e.g. minerror.h:
    #ifndef MINERROR_H
    #define MINERROR_H

    namespace absl {

    class MinimalMocTrigger
    {
        enum : unsigned long long {
            kValue = 0x243f'6a88'85a3'08d3
        };
    };

    } // namespace absl

    #endif // MINERROR_H
  1. Include it from a header containing a global QObject class:
    #include <QObject>
    #include "minerror.h"

    class AbslMocTest : public QObject
    {
        Q_OBJECT
    public:
        explicit AbslMocTest(QObject *parent = nullptr);
    };
  1. Add the header to qmake HEADERS so moc processes it and build the project.

EXPECTED RESULT:

Compiled and working code

ACTUAL RESULT:

moc generates code as if the class were inside namespace absl:

moc_abslmoctest.cpp:55:12: error: 'absl::AbslMocTest' has not been declared
   55 | void absl::AbslMocTest::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
      |            ^~~~~~~~~~~
moc_abslmoctest.cpp:63:25: error: 'absl::AbslMocTest' has not been declared
   63 | const QMetaObject absl::AbslMocTest::staticMetaObject = {
      |                         ^~~~~~~~~~~
moc_abslmoctest.cpp:69:26: error: 'absl::AbslMocTest' has not been declared
   69 | const QMetaObject *absl::AbslMocTest::metaObject() const
      |                          ^~~~~~~~~~~
moc_abslmoctest.cpp:69:52: error: non-member function 'const QMetaObject* metaObject()' cannot have cv-qualifier
   69 | const QMetaObject *absl::AbslMocTest::metaObject() const
      |                                                    ^~~~~
moc_abslmoctest.cpp: In function 'const QMetaObject* metaObject()':
moc_abslmoctest.cpp:71:21: error: 'QScopedPointer<QObjectData> QObject::d_ptr' is protected within this context
   71 |     return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
      |                     ^~~~~

If the digit separators are removed (0x243f6a8885a308d3ULL) then the project compiles.

Likewise, wrapping the problematic real-world include with:

#ifndef Q_MOC_RUN
#include "s2/mutable_s2shape_index.h"
#endif

also avoids the problem.

MODIFICATIONS:

None. Was observed initially in Pure Maps and OSM Scout Server code

ADDITIONAL INFORMATION:

This bug has been triggered by code importing abseil headers. I have tracked it to /usr/include/absl/hash/internal/hash.h .

2 Likes