QBinaryJson: fix UB (uninit'ed read) in MutableData ctor


The old code called Base::setIs{Object,Array}() on a Base object whose
lifetime had implicitly started. Whether that in itself is permissible
is an interesting question, but doesn't need to be answered, since
that's the fix we'll apply: placement new. Suffice to say that GCC, in
ubsan mode, in a unity build, detected that there was a potential
uninitialized read:

  In member function ‘QSpecialIntegerAccessor<S, pos, width, T>& QSpecialIntegerAccessor<S, pos, width, T>::operator=(Type) [with S = QLittleEndianStorageType<unsigned int>; int pos = 0; int width = 1; T = unsigned int]’,
    inlined from ‘void QSpecialIntegerBitfieldUnion<S, Accessors>::set(typename A::Type) [with A = QSpecialIntegerAccessor<QLittleEndianStorageType<unsigned int>, 0, 1, unsigned int>; S = QLittleEndianStorageType<unsigned int>; Accessors = {QSpecialIntegerAccessor<QLittleEndianStorageType<unsigned int>, 0, 1, unsigned int>, QSpecialIntegerAccessor<QLittleEndianStorageType<unsigned int>, 1, 31, unsigned int>}]’ at qendian_p.h:145:21,
    inlined from ‘void QBinaryJsonPrivate::Base::setIsObject()’ at qbinaryjson_p.h:304:65,
    inlined from ‘QBinaryJsonPrivate::MutableData::MutableData(uint, QJsonValue::Type)’ at qbinaryjson_p.h:600:27:
  qendian_p.h:110:52: error: ‘((Storage*)<unknown>)[3].QSpecialIntegerStorage<QLittleEndianStorageType<unsigned int> >::val’ may be used uninitialized [-Werror=maybe-uninitialized]
    110 |         UnsignedType i = S::fromSpecial(m_storage->val);
        |                                         ~~~~~~~~~~~^~~
  In member function ‘QSpecialIntegerAccessor<S, pos, width, T>& QSpecialIntegerAccessor<S, pos, width, T>::operator=(Type) [with S = QLittleEndianStorageType<unsigned int>; int pos = 0; int width = 1; T = unsigned int]’,
    inlined from ‘void QSpecialIntegerBitfieldUnion<S, Accessors>::set(typename A::Type) [with A = QSpecialIntegerAccessor<QLittleEndianStorageType<unsigned int>, 0, 1, unsigned int>; S = QLittleEndianStorageType<unsigned int>; Accessors = {QSpecialIntegerAccessor<QLittleEndianStorageType<unsigned int>, 0, 1, unsigned int>, QSpecialIntegerAccessor<QLittleEndianStorageType<unsigned int>, 1, 31, unsigned int>}]’ at qendian_p.h:145:21,
    inlined from ‘void QBinaryJsonPrivate::Base::setIsArray()’ at qbinaryjson_p.h:307:64,
    inlined from ‘QBinaryJsonPrivate::MutableData::MutableData(uint, QJsonValue::Type)’ at qbinaryjson_p.h:602:26:
  qendian_p.h:110:52: error: ‘((Storage*)<unknown>)[3].QSpecialIntegerStorage<QLittleEndianStorageType<unsigned int> >::val’ may be used uninitialized [-Werror=maybe-uninitialized]
    110 |         UnsignedType i = S::fromSpecial(m_storage->val);
        |                                         ~~~~~~~~~~~^~~

Note that this is a -Werror'ed compiler-time warning, not a runtime
ubsan report, though it seems that either ubsan mode or unity build is
required to make it see the issue.

After sifting through the templates, what it boils down to is that
qle_bitfield::set() (used by the setIs() functions) reads the storage,
folds in the change in the affected bits, and writes the new value
back to storage. If this was a language-level bitfield, assigning to
the fields consecutively would be well-defined. But since this
user-defined class implements it manually, the result is UB.

Fix by using placement new. Not only does this properly start the
lifetime of the object, by using the ctor, we also avoid the use of
set(), which cannot legally operate on a qle_bitfield that wasn't
value-initialized.

If there is a compact way to init a qle_bitfield, I didn't find it. So
use {} which sets all bits to zero (isObject == false, length == 0),
and write to isObject only when it has to change.

While Header doesn't include qle_bitfields, and is therefore not
affected, apply placement-new there, too. But only default-construct,
not value-construct, since the fields are ok to write in vacuous
state.

Amends qtbase/37a7b035f8feb248d25741b22e22ffb0ec7463d6 (5.0), the
initial import of json support from a playground project.

Pick-to: 6.12 6.11 6.8 6.5 5.15
Change-Id: If68ae096b792d473490f63c4f735e6700d278b38
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
commitbb066610c32d3e638deb5ab3bf6dc117c8afbe49
parenta2a158316afbbaa46f45174bcfd4c5cbbf1ed954
authorMarc Mutz <marc.mutz@qt.io>
committerMarc Mutz <marc.mutz@qt.io>
commit timeWed, 2 Sep 2026 06:35:25 +0000