// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // Qt-Security score:significant reason:default #ifndef QQUASIVIRTUAL_IMPL_H #define QQUASIVIRTUAL_IMPL_H #if 0 #pragma qt_sync_skip_header_check #pragma qt_sync_stop_processing #endif #include #include #include #include #include #include #ifndef Q_QDOC QT_BEGIN_NAMESPACE namespace QtPrivate { template void applyIndexSwitch(size_t index, Applier&& applier, std::index_sequence) { // Performance considerations: // The folding expression used here represents the same logic as a sequence of // linear if/else if/... statements. Experiments show that Clang, GCC, and MSVC // optimize it to essentially the same bytecode as a normal C++ switch, // ensuring O(1) lookup complexity. static_cast(((Is == index ? (applier(std::integral_constant{}), true) : false) || ...)); } template void applyIndexSwitch(size_t index, Applier&& applier) { applyIndexSwitch(index, std::forward(applier), std::make_index_sequence()); } template class QQuasiVirtualInterface { private: template static constexpr bool passArgAsValue = sizeof(Arg) <= sizeof(size_t) && std::is_trivially_destructible_v; template struct MethodImpl; template struct MethodImpl { static_assert(std::is_base_of_v, "The method must belong to the interface"); using return_type = R; using call_args = std::tuple, Args, Args&&>...>; static constexpr size_t index() { return index(std::make_index_sequence>>()); } private: template static constexpr bool matchesAt() { return std::is_base_of_v>>; } template static constexpr size_t index(std::index_sequence) { constexpr size_t matchesCount = (size_t(matchesAt()) + ...); static_assert(matchesCount == 1, "Expected exactly one match"); return ((size_t(matchesAt()) * Is) + ...); } static R invoke(I &intf /*const validation*/, Args... args) { Q_ASSERT(intf.m_callFN); auto& baseIntf = static_cast(const_cast&>(intf)); call_args callArgs(std::forward(args)...); if constexpr (std::is_void_v) { intf.m_callFN(index(), baseIntf, nullptr, &callArgs); } else { alignas(R) std::byte buf[sizeof(R)]; intf.m_callFN(index(), baseIntf, buf, &callArgs); R* result = std::launder(reinterpret_cast(buf)); QScopeGuard destroyBuffer([result]() { std::destroy_at(result); }); return std::forward(*result); } } friend class QQuasiVirtualInterface; }; template struct MethodImpl : MethodImpl { template using Overridden = R(Subclass::*)(Args...); }; template struct MethodImpl : MethodImpl { template using Overridden = R(Subclass::*)(Args...) const; }; template using Methods = typename C::template MethodTemplates; public: template struct Method : MethodImpl, decltype(prototype)> {}; template auto call(Args &&... args) const { return Method::invoke(static_cast(*this), std::forward(args)...); } template auto call(Args &&... args) { return Method::invoke(static_cast(*this), std::forward(args)...); } void destroy(); // quasi-virtual pure destructor using Destroy = Method<&QQuasiVirtualInterface::destroy>; struct Deleter { void operator () (QQuasiVirtualInterface* self) const { self->call(); } }; protected: using base_interface = QQuasiVirtualInterface; using CallFN = void (*)(size_t index, base_interface &intf, void *ret, void *args); void initCallFN(CallFN func) { m_callFN = func; } QQuasiVirtualInterface() = default; ~QQuasiVirtualInterface() = default; private: Q_DISABLE_COPY_MOVE(QQuasiVirtualInterface) CallFN m_callFN = nullptr; }; template class QQuasiVirtualSubclass : public Interface { private: template using Methods = typename C::template MethodTemplates; template static constexpr size_t interfaceMethodIndex() { return std::tuple_element_t>::index(); } template static void callImpl(size_t index, Subclass &subclass, void *ret, void *args, std::index_sequence) { constexpr auto methodIndexMask = []() { std::array result = {}; (static_cast(std::get()>(result) = true), ...); return result; }(); static_assert((methodIndexMask[Is] && ...), "Mapping between base and overridden methods is not unique"); auto doInvoke = [&](auto idxConstant) { std::tuple_element_t>::doInvoke(subclass, ret, args); }; applyIndexSwitch(index, doInvoke, std::index_sequence()...>{}); } static void callImpl(size_t index, typename Interface::base_interface &intf, void *ret, void *args) { constexpr auto seq = std::make_index_sequence>>(); callImpl(index, static_cast(intf), ret, args, seq); } template using OverridenSignature = typename BaseMethod::template Overridden; protected: template QQuasiVirtualSubclass(Args &&... args) : Interface(std::forward(args)...) { Interface::initCallFN(&QQuasiVirtualSubclass::callImpl); } public: template overridden> struct Override : BaseMethod { private: static constexpr void doInvoke(Subclass &subclass, void *ret, void *args) { using Return = typename BaseMethod::return_type; using PackedArgs = typename BaseMethod::call_args; Q_ASSERT(args); Q_ASSERT(std::is_void_v == !ret); auto invoke = [&subclass](auto &&...params) { return std::invoke(overridden, &subclass, std::forward(params)...); }; if constexpr (std::is_void_v) { std::apply(invoke, std::move(*static_cast(args))); } else { // Note, that ::new (*) Return(...) fails on Integrity. // TODO: use std::construct_at for c++20 using Alloc = std::allocator; Alloc alloc; std::allocator_traits::construct(alloc, static_cast(ret), std::apply(invoke, std::move(*static_cast(args)))); } } friend class QQuasiVirtualSubclass; }; }; } // namespace QtPrivate QT_END_NAMESPACE #endif // Q_DOC #endif // QQUASIVIRTUAL_IMPL_H