diff options
69 files changed, 1487 insertions, 315 deletions
diff --git a/bin/qt-cmake.bat.in b/bin/qt-cmake.bat.in index 5d831ebce93..934f79c2599 100644 --- a/bin/qt-cmake.bat.in +++ b/bin/qt-cmake.bat.in @@ -7,5 +7,5 @@ set script_dir_path=%~dp0 set cmake_path=@CMAKE_COMMAND@ if not exist "%cmake_path%" set cmake_path=cmake -set toolchain_path=%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\qt.toolchain.cmake -"%cmake_path%" -DCMAKE_TOOLCHAIN_FILE="%toolchain_path%" @__qt_cmake_extra@ %* +set CMAKE_TOOLCHAIN_FILE=%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\qt.toolchain.cmake +"%cmake_path%" @__qt_cmake_extra@ %* diff --git a/bin/qt-cmake.in b/bin/qt-cmake.in index 363c490960c..5203e49f878 100755 --- a/bin/qt-cmake.in +++ b/bin/qt-cmake.in @@ -16,4 +16,7 @@ toolchain_path="$script_dir_path/@__GlobalConfig_relative_path_from_bin_dir_to_c @extra_qt_cmake_code@ # Find the qt toolchain relative to the absolute bin dir path where the script is located. -exec "$cmake_path" -DCMAKE_TOOLCHAIN_FILE="$toolchain_path" @__qt_cmake_extra@ "$@" +CMAKE_TOOLCHAIN_FILE="$toolchain_path" +export CMAKE_TOOLCHAIN_FILE + +exec "$cmake_path" @__qt_cmake_extra@ "$@" diff --git a/src/3rdparty/libpng/ANNOUNCE b/src/3rdparty/libpng/ANNOUNCE index ae0b6ccc13b..10dee70d834 100644 --- a/src/3rdparty/libpng/ANNOUNCE +++ b/src/3rdparty/libpng/ANNOUNCE @@ -1,5 +1,5 @@ -libpng 1.6.51 - November 21, 2025 -================================= +libpng 1.6.52 - December 3, 2025 +================================ This is a public release of libpng, intended for use in production code. @@ -7,15 +7,12 @@ This is a public release of libpng, intended for use in production code. Files available for download ---------------------------- -Source files with LF line endings (for Unix/Linux): +Source files: - * libpng-1.6.51.tar.xz (LZMA-compressed, recommended) - * libpng-1.6.51.tar.gz (deflate-compressed) - -Source files with CRLF line endings (for Windows): - - * lpng1651.7z (LZMA-compressed, recommended) - * lpng1651.zip (deflate-compressed) + * libpng-1.6.52.tar.xz (LZMA-compressed, recommended) + * libpng-1.6.52.tar.gz (deflate-compressed) + * lpng1652.7z (LZMA-compressed) + * lpng1652.zip (deflate-compressed) Other information: @@ -25,33 +22,18 @@ Other information: * TRADEMARK.md -Changes from version 1.6.50 to version 1.6.51 +Changes from version 1.6.51 to version 1.6.52 --------------------------------------------- - * Fixed CVE-2025-64505 (moderate severity): - Heap buffer overflow in `png_do_quantize` via malformed palette index. - (Reported by Samsung; analyzed by Fabio Gritti.) - * Fixed CVE-2025-64506 (moderate severity): - Heap buffer over-read in `png_write_image_8bit` with 8-bit input and - `convert_to_8bit` enabled. - (Reported by Samsung and <weijinjinnihao@users.noreply.github.com>; - analyzed by Fabio Gritti.) - * Fixed CVE-2025-64720 (high severity): - Buffer overflow in `png_image_read_composite` via incorrect palette - premultiplication. - (Reported by Samsung; analyzed by John Bowler.) - * Fixed CVE-2025-65018 (high severity): - Heap buffer overflow in `png_combine_row` triggered via - `png_image_finish_read`. - (Reported by <yosiimich@users.noreply.github.com>.) - * Fixed a memory leak in `png_set_quantize`. - (Reported by Samsung; analyzed by Fabio Gritti.) - * Removed the experimental and incomplete ERROR_NUMBERS code. - (Contributed by Tobias Stoeckmann.) - * Improved the RISC-V vector extension support; required RVV 1.0 or newer. - (Contributed by Filip Wasil.) - * Added GitHub Actions workflows for automated testing. - * Performed various refactorings and cleanups. + * Fixed CVE-2025-66293 (high severity): + Out-of-bounds read in `png_image_read_composite`. + (Reported by flyfish101 <flyfish101@users.noreply.github.com>.) + * Fixed the Paeth filter handling in the RISC-V RVV implementation. + (Reported by Filip Wasil; fixed by Liang Junzhao.) + * Improved the performance of the RISC-V RVV implementation. + (Contributed by Liang Junzhao.) + * Added allocation failure fuzzing to oss-fuzz. + (Contributed by Philippe Antoine.) Send comments/corrections/commendations to png-mng-implement at lists.sf.net. diff --git a/src/3rdparty/libpng/CHANGES b/src/3rdparty/libpng/CHANGES index 2478fd0fc08..f8ad74bbdf3 100644 --- a/src/3rdparty/libpng/CHANGES +++ b/src/3rdparty/libpng/CHANGES @@ -6304,6 +6304,17 @@ Version 1.6.51 [November 21, 2025] Added GitHub Actions workflows for automated testing. Performed various refactorings and cleanups. +Version 1.6.52 [December 3, 2025] + Fixed CVE-2025-66293 (high severity): + Out-of-bounds read in `png_image_read_composite`. + (Reported by flyfish101 <flyfish101@users.noreply.github.com>.) + Fixed the Paeth filter handling in the RISC-V RVV implementation. + (Reported by Filip Wasil; fixed by Liang Junzhao.) + Improved the performance of the RISC-V RVV implementation. + (Contributed by Liang Junzhao.) + Added allocation failure fuzzing to oss-fuzz. + (Contributed by Philippe Antoine.) + Send comments/corrections/commendations to png-mng-implement at lists.sf.net. Subscription is required; visit https://lists.sourceforge.net/lists/listinfo/png-mng-implement diff --git a/src/3rdparty/libpng/README b/src/3rdparty/libpng/README index 5ea329ee3da..87e5f8b177e 100644 --- a/src/3rdparty/libpng/README +++ b/src/3rdparty/libpng/README @@ -1,4 +1,4 @@ -README for libpng version 1.6.51 +README for libpng version 1.6.52 ================================ See the note about version numbers near the top of `png.h`. diff --git a/src/3rdparty/libpng/libpng-manual.txt b/src/3rdparty/libpng/libpng-manual.txt index f342c18e814..f284d987ba6 100644 --- a/src/3rdparty/libpng/libpng-manual.txt +++ b/src/3rdparty/libpng/libpng-manual.txt @@ -9,7 +9,7 @@ libpng-manual.txt - A description on how to use and modify libpng Based on: - libpng version 1.6.36, December 2018, through 1.6.51 - November 2025 + libpng version 1.6.36, December 2018, through 1.6.52 - December 2025 Updated and distributed by Cosmin Truta Copyright (c) 2018-2025 Cosmin Truta diff --git a/src/3rdparty/libpng/png.c b/src/3rdparty/libpng/png.c index 380c4c19e6a..11b65d1f13e 100644 --- a/src/3rdparty/libpng/png.c +++ b/src/3rdparty/libpng/png.c @@ -13,7 +13,7 @@ #include "pngpriv.h" /* Generate a compiler error if there is an old png.h in the search path. */ -typedef png_libpng_version_1_6_51 Your_png_h_is_not_version_1_6_51; +typedef png_libpng_version_1_6_52 Your_png_h_is_not_version_1_6_52; /* Sanity check the chunks definitions - PNG_KNOWN_CHUNKS from pngpriv.h and the * corresponding macro definitions. This causes a compile time failure if @@ -817,7 +817,7 @@ png_get_copyright(png_const_structrp png_ptr) return PNG_STRING_COPYRIGHT #else return PNG_STRING_NEWLINE \ - "libpng version 1.6.51" PNG_STRING_NEWLINE \ + "libpng version 1.6.52" PNG_STRING_NEWLINE \ "Copyright (c) 2018-2025 Cosmin Truta" PNG_STRING_NEWLINE \ "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \ PNG_STRING_NEWLINE \ diff --git a/src/3rdparty/libpng/png.h b/src/3rdparty/libpng/png.h index fb93d2242b5..bceb9aa45d7 100644 --- a/src/3rdparty/libpng/png.h +++ b/src/3rdparty/libpng/png.h @@ -1,6 +1,6 @@ /* png.h - header file for PNG reference library * - * libpng version 1.6.51 + * libpng version 1.6.52 * * Copyright (c) 2018-2025 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson @@ -14,7 +14,7 @@ * libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger * libpng versions 0.97, January 1998, through 1.6.35, July 2018: * Glenn Randers-Pehrson - * libpng versions 1.6.36, December 2018, through 1.6.51, November 2025: + * libpng versions 1.6.36, December 2018, through 1.6.52, December 2025: * Cosmin Truta * See also "Contributing Authors", below. */ @@ -238,7 +238,7 @@ * ... * 1.5.30 15 10530 15.so.15.30[.0] * ... - * 1.6.51 16 10651 16.so.16.51[.0] + * 1.6.52 16 10651 16.so.16.52[.0] * * Henceforth the source version will match the shared-library major and * minor numbers; the shared-library major version number will be used for @@ -274,7 +274,7 @@ */ /* Version information for png.h - this should match the version in png.c */ -#define PNG_LIBPNG_VER_STRING "1.6.51" +#define PNG_LIBPNG_VER_STRING "1.6.52" #define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n" /* The versions of shared library builds should stay in sync, going forward */ @@ -285,7 +285,7 @@ /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */ #define PNG_LIBPNG_VER_MAJOR 1 #define PNG_LIBPNG_VER_MINOR 6 -#define PNG_LIBPNG_VER_RELEASE 51 +#define PNG_LIBPNG_VER_RELEASE 52 /* This should be zero for a public release, or non-zero for a * development version. @@ -316,7 +316,7 @@ * From version 1.0.1 it is: * XXYYZZ, where XX=major, YY=minor, ZZ=release */ -#define PNG_LIBPNG_VER 10651 /* 1.6.51 */ +#define PNG_LIBPNG_VER 10652 /* 1.6.52 */ /* Library configuration: these options cannot be changed after * the library has been built. @@ -426,7 +426,7 @@ extern "C" { /* This triggers a compiler error in png.c, if png.c and png.h * do not agree upon the version number. */ -typedef char* png_libpng_version_1_6_51; +typedef char* png_libpng_version_1_6_52; /* Basic control structions. Read libpng-manual.txt or libpng.3 for more info. * diff --git a/src/3rdparty/libpng/pngconf.h b/src/3rdparty/libpng/pngconf.h index 981df68d87a..76b5c20bdff 100644 --- a/src/3rdparty/libpng/pngconf.h +++ b/src/3rdparty/libpng/pngconf.h @@ -1,6 +1,6 @@ /* pngconf.h - machine-configurable file for libpng * - * libpng version 1.6.51 + * libpng version 1.6.52 * * Copyright (c) 2018-2025 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson diff --git a/src/3rdparty/libpng/pnglibconf.h b/src/3rdparty/libpng/pnglibconf.h index 00432d6c033..f4a993441f7 100644 --- a/src/3rdparty/libpng/pnglibconf.h +++ b/src/3rdparty/libpng/pnglibconf.h @@ -1,6 +1,6 @@ /* pnglibconf.h - library build configuration */ -/* libpng version 1.6.51 */ +/* libpng version 1.6.52 */ /* Copyright (c) 2018-2025 Cosmin Truta */ /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */ diff --git a/src/3rdparty/libpng/pngread.c b/src/3rdparty/libpng/pngread.c index 79917daaaf9..f8ca2b7e31d 100644 --- a/src/3rdparty/libpng/pngread.c +++ b/src/3rdparty/libpng/pngread.c @@ -3207,6 +3207,7 @@ png_image_read_composite(png_voidp argument) ptrdiff_t step_row = display->row_bytes; unsigned int channels = (image->format & PNG_FORMAT_FLAG_COLOR) != 0 ? 3 : 1; + int optimize_alpha = (png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0; int pass; for (pass = 0; pass < passes; ++pass) @@ -3263,20 +3264,44 @@ png_image_read_composite(png_voidp argument) if (alpha < 255) /* else just use component */ { - /* This is PNG_OPTIMIZED_ALPHA, the component value - * is a linear 8-bit value. Combine this with the - * current outrow[c] value which is sRGB encoded. - * Arithmetic here is 16-bits to preserve the output - * values correctly. - */ - component *= 257*255; /* =65535 */ - component += (255-alpha)*png_sRGB_table[outrow[c]]; + if (optimize_alpha != 0) + { + /* This is PNG_OPTIMIZED_ALPHA, the component value + * is a linear 8-bit value. Combine this with the + * current outrow[c] value which is sRGB encoded. + * Arithmetic here is 16-bits to preserve the output + * values correctly. + */ + component *= 257*255; /* =65535 */ + component += (255-alpha)*png_sRGB_table[outrow[c]]; - /* So 'component' is scaled by 255*65535 and is - * therefore appropriate for the sRGB to linear - * conversion table. - */ - component = PNG_sRGB_FROM_LINEAR(component); + /* Clamp to the valid range to defend against + * unforeseen cases where the data might be sRGB + * instead of linear premultiplied. + * (Belt-and-suspenders for GitHub Issue #764.) + */ + if (component > 255*65535) + component = 255*65535; + + /* So 'component' is scaled by 255*65535 and is + * therefore appropriate for the sRGB-to-linear + * conversion table. + */ + component = PNG_sRGB_FROM_LINEAR(component); + } + else + { + /* Compositing was already done on the palette + * entries. The data is sRGB premultiplied on black. + * Composite with the background in sRGB space. + * This is not gamma-correct, but matches what was + * done to the palette. + */ + png_uint_32 background = outrow[c]; + component += ((255-alpha) * background + 127) / 255; + if (component > 255) + component = 255; + } } outrow[c] = (png_byte)component; diff --git a/src/3rdparty/libpng/pngrtran.c b/src/3rdparty/libpng/pngrtran.c index 2f520225515..507d11381ec 100644 --- a/src/3rdparty/libpng/pngrtran.c +++ b/src/3rdparty/libpng/pngrtran.c @@ -1843,6 +1843,7 @@ png_init_read_transformations(png_structrp png_ptr) * transformations elsewhere. */ png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA); + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; } /* color_type == PNG_COLOR_TYPE_PALETTE */ /* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */ diff --git a/src/3rdparty/libpng/qt_attribution.json b/src/3rdparty/libpng/qt_attribution.json index fe8ba663881..1f942f8f564 100644 --- a/src/3rdparty/libpng/qt_attribution.json +++ b/src/3rdparty/libpng/qt_attribution.json @@ -7,8 +7,8 @@ "Description": "libpng is the official PNG reference library.", "Homepage": "http://www.libpng.org/pub/png/libpng.html", - "Version": "1.6.51", - "DownloadLocation": "https://download.sourceforge.net/libpng/libpng-1.6.51.tar.xz", + "Version": "1.6.52", + "DownloadLocation": "https://download.sourceforge.net/libpng/libpng-1.6.52.tar.xz", "PURL": "pkg:github/pnggroup/libpng@v$<VERSION>", "CPE": "cpe:2.3:a:libpng:libpng:$<VERSION>:*:*:*:*:*:*:*", diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index dec68c5f9f4..ea8cf7b9c8e 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -583,7 +583,7 @@ if(QT_FEATURE_async_io) SOURCES io/qrandomaccessasyncfile_darwin.mm ) - elseif(LINUX AND QT_FEATURE_liburing) + elseif((LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring)) qt_internal_extend_target(Core SOURCES io/qrandomaccessasyncfile_qioring.cpp @@ -763,6 +763,11 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_liburing uring ) +qt_internal_extend_target(Core CONDITION QT_FEATURE_windows_ioring + SOURCES + io/qioring.cpp io/qioring_win.cpp io/qioring_p.h +) + # Workaround for QTBUG-101411 # Remove if QCC (gcc version 8.3.0) for QNX 7.1.0 is no longer supported qt_internal_extend_target(Core CONDITION QCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "8.3.0") diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index c1d15c75054..7216f2920fe 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -605,6 +605,27 @@ int main(void) " ) +qt_config_compile_test(windows_ioring + LABEL "Windows SDK: IORing" + CODE +"#include <windows.h> +#include <ioringapi.h> + +int main(void) +{ + /* BEGIN TEST: */ + IORING_CREATE_FLAGS flags; + memset(&flags, 0, sizeof(flags)); + HIORING ioRingHandle = nullptr; + HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, 1, 1, &ioRingHandle); + if (hr == IORING_E_SUBMISSION_QUEUE_FULL) // not valid, but test that this #define exists + return 0; + /* END TEST: */ + return 0; +} +" +) + # cpp_winrt qt_config_compile_test(cpp_winrt LABEL "cpp/winrt" @@ -785,6 +806,11 @@ qt_feature("winsdkicu" PRIVATE CONDITION TEST_winsdkicu DISABLE QT_FEATURE_icu ) +qt_feature("windows_ioring" PRIVATE + LABEL "Windows I/O Ring" + AUTODETECT WIN32 AND CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 10.0.22000 + CONDITION TEST_windows_ioring +) qt_feature("inotify" PUBLIC PRIVATE LABEL "inotify" CONDITION TEST_inotify OR TEST_fsnotify @@ -1272,6 +1298,7 @@ qt_configure_add_summary_entry(ARGS "glib") qt_configure_add_summary_entry(ARGS "icu") qt_configure_add_summary_entry(ARGS "jemalloc") qt_configure_add_summary_entry(ARGS "liburing") +qt_configure_add_summary_entry(ARGS "windows_ioring") qt_configure_add_summary_entry(ARGS "timezone_tzdb") qt_configure_add_summary_entry(ARGS "system-libb2") qt_configure_add_summary_entry(ARGS "mimetype-database") diff --git a/src/corelib/doc/snippets/code/doc_src_properties.cpp b/src/corelib/doc/snippets/code/doc_src_properties.cpp index eafa7acda3b..07f574c2de2 100644 --- a/src/corelib/doc/snippets/code/doc_src_properties.cpp +++ b/src/corelib/doc/snippets/code/doc_src_properties.cpp @@ -16,6 +16,8 @@ Q_PROPERTY(type name [BINDABLE bindableProperty] [CONSTANT] [FINAL] + [VIRTUAL] + [OVERRIDE] [REQUIRED]) //! [0] diff --git a/src/corelib/doc/src/objectmodel/properties.qdoc b/src/corelib/doc/src/objectmodel/properties.qdoc index 0e66c8445c2..71e14222763 100644 --- a/src/corelib/doc/src/objectmodel/properties.qdoc +++ b/src/corelib/doc/src/objectmodel/properties.qdoc @@ -128,10 +128,18 @@ constant value may be different for different instances of the object. A constant property cannot have a WRITE method or a NOTIFY signal. - \li The presence of the \c FINAL attribute indicates that the property - will not be overridden by a derived class. This can be used for performance - optimizations in some cases, but is not enforced by moc. Care must be taken - never to override a \c FINAL property. + \li \c FINAL, \c VIRTUAL, \c OVERRIDE modifiers mirror the semantics of their C++ and + \l {Override Semantics}{QML counterparts}, allowing to make property overriding explicit at the + meta-object level. + + \note At present, these modifiers are not enforced by moc. + They are recognized syntactically and are primarily used for QML runtime enforcement and tooling + diagnostics. Future versions may introduce stricter compile-time validation and warnings for + invalid overrides across modules. + + \note If you want to change accessing behaviour for a property, use the + polymorphism provided by C++. + \li The presence of the \c REQUIRED attribute indicates that the property should be set by a user of the class. This is not enforced by moc, and is diff --git a/src/corelib/global/qnumeric.h b/src/corelib/global/qnumeric.h index 48e736ff124..4568d089590 100644 --- a/src/corelib/global/qnumeric.h +++ b/src/corelib/global/qnumeric.h @@ -627,6 +627,27 @@ QT_WARNING_DISABLE_FLOAT_COMPARE QT_WARNING_POP +namespace QtPrivate { +/* + A version of qFuzzyCompare that works for all values (qFuzzyCompare() + requires that neither argument is numerically 0). + + It's private because we need a fix for the many qFuzzyCompare() uses that + ignore the precondition, even for older branches. + + See QTBUG-142020 for discussion of a longer-term solution. +*/ +template <typename T, typename S> +[[nodiscard]] constexpr bool fuzzyCompare(const T &lhs, const S &rhs) noexcept +{ + static_assert(noexcept(qIsNull(lhs) && qIsNull(rhs) && qFuzzyIsNull(lhs - rhs) && qFuzzyCompare(lhs, rhs)), + "The operations qIsNull(), qFuzzyIsNull() and qFuzzyCompare() must be noexcept" + "for both argument types!"); + return qIsNull(lhs) || qIsNull(rhs) ? qFuzzyIsNull(lhs - rhs) : qFuzzyCompare(lhs, rhs); +} +} // namespace QtPrivate + + inline int qIntCast(double f) { return int(f); } inline int qIntCast(float f) { return int(f); } diff --git a/src/corelib/io/qioring.cpp b/src/corelib/io/qioring.cpp index 28849b49b04..2eb013e24fc 100644 --- a/src/corelib/io/qioring.cpp +++ b/src/corelib/io/qioring.cpp @@ -8,6 +8,20 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQIORing, "qt.core.ioring", QtCriticalMsg) +QIORing *QIORing::sharedInstance() +{ + thread_local QIORing instance; + if (!instance.initializeIORing()) + return nullptr; + return &instance; +} + +QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize) + : sqEntries(submissionQueueSize), cqEntries(completionQueueSize) +{ + // Destructor in respective _<platform>.cpp +} + auto QIORing::queueRequestInternal(GenericRequestType &request) -> QueuedRequestStatus { if (!ensureInitialized() || preparingRequests) { // preparingRequests protects against recursing @@ -65,12 +79,20 @@ template <typename T> constexpr bool HasResultMember = qxp::is_detected_v<DetectResult, T>; } +void QIORing::setFileErrorResult(QIORing::GenericRequestType &req, QFileDevice::FileError error) +{ + invokeOnOp(req, [error](auto *concreteRequest) { + if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>) + setFileErrorResult(*concreteRequest, error); + }); +} + void QIORing::finishRequestWithError(QIORing::GenericRequestType &req, QFileDevice::FileError error) { - invokeOnOp(req, [error](auto *req) { - if constexpr (QtPrivate::HasResultMember<decltype(*req)>) - req->result.template emplace<QFileDevice::FileError>(error); - invokeCallback(*req); + invokeOnOp(req, [error](auto *concreteRequest) { + if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>) + setFileErrorResult(*concreteRequest, error); + invokeCallback(*concreteRequest); }); } diff --git a/src/corelib/io/qioring_linux.cpp b/src/corelib/io/qioring_linux.cpp index b296b916c81..2b5865f3c2d 100644 --- a/src/corelib/io/qioring_linux.cpp +++ b/src/corelib/io/qioring_linux.cpp @@ -35,19 +35,6 @@ static io_uring_op toUringOp(QIORing::Operation op); static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request, const void *address, qsizetype size); - -QIORing *QIORing::sharedInstance() -{ - thread_local QIORing instance; - if (!instance.initializeIORing()) - return nullptr; - return &instance; -} - -QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize) - : sqEntries(submissionQueueSize), cqEntries(completionQueueSize) -{ -} QIORing::~QIORing() { if (eventDescriptor != -1) diff --git a/src/corelib/io/qioring_p.h b/src/corelib/io/qioring_p.h index d4c4308122e..0db832bc6bf 100644 --- a/src/corelib/io/qioring_p.h +++ b/src/corelib/io/qioring_p.h @@ -22,7 +22,6 @@ #include <QtCore/qspan.h> #include <QtCore/qhash.h> #include <QtCore/qfiledevice.h> -#include <QtCore/qwineventnotifier.h> #include <QtCore/qloggingcategory.h> #include <QtCore/qdeadlinetimer.h> @@ -30,10 +29,15 @@ # include <QtCore/qsocketnotifier.h> struct io_uring_sqe; struct io_uring_cqe; +#elif defined(Q_OS_WIN) +# include <QtCore/qwineventnotifier.h> +# include <qt_windows.h> +# include <ioringapi.h> #endif #include <algorithm> #include <filesystem> +#include <QtCore/qxpfunctional.h> #include <variant> #include <optional> #include <type_traits> @@ -162,6 +166,12 @@ private: template <typename Fun> static auto invokeOnOp(GenericRequestType &req, Fun fn); + template <Operation Op> + static void setFileErrorResult(QIORingRequest<Op> &req, QFileDevice::FileError error) + { + req.result.template emplace<QFileDevice::FileError>(error); + } + static void setFileErrorResult(GenericRequestType &req, QFileDevice::FileError error); static void finishRequestWithError(GenericRequestType &req, QFileDevice::FileError error); static bool verifyFd(GenericRequestType &req); @@ -205,6 +215,28 @@ private: ReadWriteStatus handleReadCompletion(const io_uring_cqe *cqe, GenericRequestType *request); template <Operation Op> ReadWriteStatus handleWriteCompletion(const io_uring_cqe *cqe, GenericRequestType *request); +#elif defined(Q_OS_WIN) + // We use UINT32 because that's the type used for size parameters in their API. + static constexpr qsizetype MaxReadWriteLen = std::numeric_limits<UINT32>::max(); + std::optional<QWinEventNotifier> notifier; + HIORING ioRingHandle = nullptr; + HANDLE eventHandle = INVALID_HANDLE_VALUE; + + bool initialized = false; + bool queueWasFull = false; + [[nodiscard]] + RequestPrepResult prepareRequest(GenericRequestType &request); + QIORing::ReadWriteStatus handleReadCompletion( + HRESULT result, quintptr information, QSpan<std::byte> *destinations, void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResult); + template <Operation Op> + ReadWriteStatus handleReadCompletion(const IORING_CQE *cqe, GenericRequestType *request); + ReadWriteStatus handleWriteCompletion( + HRESULT result, quintptr information, const QSpan<const std::byte> *sources, + void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResult); + template <Operation Op> + ReadWriteStatus handleWriteCompletion(const IORING_CQE *cqe, GenericRequestType *request); #endif }; @@ -243,6 +275,7 @@ struct QIORingRequestBase : Base template <> struct QIORingResult<QtPrivate::Operation::Open> { + // On Windows this is a HANDLE qintptr fd; }; template <> @@ -260,6 +293,7 @@ template <> struct QIORingRequest<QtPrivate::Operation::Close> final : QIORingRequestBase<QtPrivate::Operation::Close, QIORingRequestEmptyBase> { + // On Windows this is a HANDLE qintptr fd; }; @@ -318,6 +352,7 @@ struct QIORingResult<QtPrivate::Operation::Flush> final template <> struct QIORingRequest<QtPrivate::Operation::Flush> final : QIORingRequestBase<QtPrivate::Operation::Flush, QIORingRequestEmptyBase> { + // On Windows this is a HANDLE qintptr fd; }; @@ -330,6 +365,7 @@ template <> struct QIORingRequest<QtPrivate::Operation::Stat> final : QIORingRequestBase<QtPrivate::Operation::Stat, QIORingRequestEmptyBase> { + // On Windows this is a HANDLE qintptr fd; }; @@ -473,6 +509,7 @@ namespace QtPrivate { // The 'extra' struct for Read/Write operations that must be split up struct ReadWriteExtra { + qint64 totalProcessed = 0; qsizetype spanIndex = 0; qsizetype spanOffset = 0; qsizetype numSpans = 1; diff --git a/src/corelib/io/qioring_win.cpp b/src/corelib/io/qioring_win.cpp new file mode 100644 index 00000000000..42c51f428d6 --- /dev/null +++ b/src/corelib/io/qioring_win.cpp @@ -0,0 +1,754 @@ +// 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 + +#include "qioring_p.h" + +QT_REQUIRE_CONFIG(windows_ioring); + +#include <QtCore/qcompilerdetection.h> +#include <QtCore/qobject.h> +#include <QtCore/qscopedvaluerollback.h> + +#include <qt_windows.h> +#include <ioringapi.h> + +#include <QtCore/q26numeric.h> + +QT_BEGIN_NAMESPACE + +// We don't really build for 32-bit windows anymore, but this code is definitely wrong if someone +// does. +static_assert(sizeof(qsizetype) > sizeof(UINT32), + "This code is written with assuming 64-bit Windows."); + +using namespace Qt::StringLiterals; + +static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::byte> destination, + quint64 offset, quintptr userData) +{ + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef((HANDLE(fd))); + const IORING_BUFFER_REF bufferRef(destination.data()); + const auto maxSize = q26::saturate_cast<UINT32>(destination.size()); + Q_ASSERT(maxSize == destination.size()); + return BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, userData, + IOSQE_FLAGS_NONE); +} + +static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const std::byte> source, + quint64 offset, quintptr userData) +{ + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef((HANDLE(fd))); + const IORING_BUFFER_REF bufferRef(const_cast<std::byte *>(source.data())); + const auto maxSize = q26::saturate_cast<UINT32>(source.size()); + Q_ASSERT(maxSize == source.size()); + // @todo: FILE_WRITE_FLAGS can be set to write-through, could be used for Unbuffered mode. + return BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, + FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE); +} + +QIORing::~QIORing() +{ + if (initialized) { + CloseHandle(eventHandle); + CloseIoRing(ioRingHandle); + } +} + +bool QIORing::initializeIORing() +{ + if (initialized) + return true; + + IORING_CAPABILITIES capabilities; + QueryIoRingCapabilities(&capabilities); + if (capabilities.MaxVersion < IORING_VERSION_3) // 3 adds write, flush and drain + return false; + if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0) + return false; // We currently require the SET_COMPLETION_EVENT feature + + qCDebug(lcQIORing) << "Creating QIORing, requesting space for" << sqEntries + << "submission queue entries, and" << cqEntries + << "completion queue entries"; + + IORING_CREATE_FLAGS flags; + memset(&flags, 0, sizeof(flags)); + HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries, &ioRingHandle); + if (FAILED(hr)) { + qErrnoWarning(hr, "failed to initialize QIORing"); + return false; + } + auto earlyExitCleanup = qScopeGuard([this]() { + if (eventHandle != INVALID_HANDLE_VALUE) + CloseHandle(eventHandle); + CloseIoRing(ioRingHandle); + }); + eventHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (eventHandle == INVALID_HANDLE_VALUE) { + qErrnoWarning("Failed to create event handle"); + return false; + } + notifier.emplace(eventHandle); + hr = SetIoRingCompletionEvent(ioRingHandle, eventHandle); + if (FAILED(hr)) { + qErrnoWarning(hr, "Failed to assign the event handle to QIORing"); + return false; + } + IORING_INFO info; + if (SUCCEEDED(GetIoRingInfo(ioRingHandle, &info))) { + sqEntries = info.SubmissionQueueSize; + cqEntries = info.CompletionQueueSize; + qCDebug(lcQIORing) << "QIORing configured with capacity for" << sqEntries + << "submissions, and" << cqEntries << "completions."; + } + QObject::connect(std::addressof(*notifier), &QWinEventNotifier::activated, + std::addressof(*notifier), [this]() { completionReady(); }); + initialized = true; + earlyExitCleanup.dismiss(); + return true; +} + +QIORing::ReadWriteStatus QIORing::handleReadCompletion( + HRESULT result, quintptr information, QSpan<std::byte> *destinations, void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResultFn) +{ + if (FAILED(result)) { + if (result == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + return ReadWriteStatus::Finished; + + if (result == E_ABORT) + setResultFn(QFileDevice::AbortError); + else + setResultFn(QFileDevice::ReadError); + } else if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) { + const qsizetype bytesRead = q26::saturate_cast<decltype(MaxReadWriteLen)>(information); + qCDebug(lcQIORing) << "Partial read of" << bytesRead << "bytes completed"; + extra->totalProcessed = setResultFn(bytesRead); + extra->spanOffset += bytesRead; + qCDebug(lcQIORing) << "Read operation progress: span" << extra->spanIndex << "offset" + << extra->spanOffset << "of" << destinations[extra->spanIndex].size() + << "bytes. Total read:" << extra->totalProcessed << "bytes"; + // The while loop is in case there is an empty span, we skip over it: + while (extra->spanOffset == destinations[extra->spanIndex].size()) { + // Move to next span + if (++extra->spanIndex == extra->numSpans) + return ReadWriteStatus::Finished; + extra->spanOffset = 0; + } + return ReadWriteStatus::MoreToDo; + } else { + setResultFn(q26::saturate_cast<decltype(MaxReadWriteLen)>(information)); + } + return ReadWriteStatus::Finished; +} + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleReadCompletion(const IORING_CQE *cqe, + GenericRequestType *request) +{ + static_assert(Op == Operation::Read || Op == Operation::VectoredRead); + QIORingRequest<Op> *readRequest = request->requestData<Op>(); + Q_ASSERT(readRequest); + auto *destinations = [&readRequest]() { + if constexpr (Op == Operation::Read) + return &readRequest->destination; + else + return &readRequest->destinations[0]; + }(); + auto setResult = [readRequest](const std::variant<QFileDevice::FileError, qint64> &result) { + if (result.index() == 0) { // error + QIORing::setFileErrorResult(*readRequest, *std::get_if<QFileDevice::FileError>(&result)); + return 0ll; + } + // else: success + auto &readResult = [&readRequest]() -> QIORingResult<Op> & { + if (auto *result = std::get_if<QIORingResult<Op>>(&readRequest->result)) + return *result; + return readRequest->result.template emplace<QIORingResult<Op>>(); + }(); + qint64 bytesRead = *std::get_if<qint64>(&result); + readResult.bytesRead += bytesRead; + return readResult.bytesRead; + }; + QIORing::ReadWriteStatus rwstatus = handleReadCompletion( + cqe->ResultCode, cqe->Information, destinations, request->getExtra<void>(), setResult); + switch (rwstatus) { + case ReadWriteStatus::Finished: + if (request->getExtra<void>()) + --ongoingSplitOperations; + break; + case ReadWriteStatus::MoreToDo: { + // Move the request such that it is next in the list to be processed: + auto &it = addrItMap[request]; + const auto where = lastUnqueuedIterator.value_or(pendingRequests.end()); + pendingRequests.splice(where, pendingRequests, it); + it = std::prev(where); + lastUnqueuedIterator = it; + break; + } + } + return rwstatus; +} + +QIORing::ReadWriteStatus QIORing::handleWriteCompletion( + HRESULT result, quintptr information, const QSpan<const std::byte> *sources, void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResultFn) +{ + if (FAILED(result)) { + if (result == E_ABORT) + setResultFn(QFileDevice::AbortError); + else + setResultFn(QFileDevice::WriteError); + } else if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) { + const qsizetype bytesWritten = q26::saturate_cast<decltype(MaxReadWriteLen)>(information); + qCDebug(lcQIORing) << "Partial write of" << bytesWritten << "bytes completed"; + extra->totalProcessed = setResultFn(bytesWritten); + extra->spanOffset += bytesWritten; + qCDebug(lcQIORing) << "Write operation progress: span" << extra->spanIndex << "offset" + << extra->spanOffset << "of" << sources[extra->spanIndex].size() + << "bytes. Total written:" << extra->totalProcessed << "bytes"; + // The while loop is in case there is an empty span, we skip over it: + while (extra->spanOffset == sources[extra->spanIndex].size()) { + // Move to next span + if (++extra->spanIndex == extra->numSpans) + return ReadWriteStatus::Finished; + extra->spanOffset = 0; + } + return ReadWriteStatus::MoreToDo; + } else { + setResultFn(q26::saturate_cast<decltype(MaxReadWriteLen)>(information)); + } + return ReadWriteStatus::Finished; +} + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleWriteCompletion(const IORING_CQE *cqe, + GenericRequestType *request) +{ + static_assert(Op == Operation::Write || Op == Operation::VectoredWrite); + QIORingRequest<Op> *writeRequest = request->requestData<Op>(); + auto *sources = [&writeRequest]() { + if constexpr (Op == Operation::Write) + return &writeRequest->source; + else + return &writeRequest->sources[0]; + }(); + auto setResult = [writeRequest](const std::variant<QFileDevice::FileError, qint64> &result) { + if (result.index() == 0) { // error + QIORing::setFileErrorResult(*writeRequest, *std::get_if<QFileDevice::FileError>(&result)); + return 0ll; + } + // else: success + auto &writeResult = [&writeRequest]() -> QIORingResult<Op> & { + if (auto *result = std::get_if<QIORingResult<Op>>(&writeRequest->result)) + return *result; + return writeRequest->result.template emplace<QIORingResult<Op>>(); + }(); + qint64 bytesWritten = *std::get_if<qint64>(&result); + writeResult.bytesWritten += bytesWritten; + return writeResult.bytesWritten; + }; + QIORing::ReadWriteStatus rwstatus = handleWriteCompletion( + cqe->ResultCode, cqe->Information, sources, request->getExtra<void>(), setResult); + switch (rwstatus) { + case ReadWriteStatus::Finished: + if (request->getExtra<void>()) + --ongoingSplitOperations; + break; + case ReadWriteStatus::MoreToDo: { + // Move the request such that it is next in the list to be processed: + auto &it = addrItMap[request]; + const auto where = lastUnqueuedIterator.value_or(pendingRequests.end()); + pendingRequests.splice(where, pendingRequests, it); + it = std::prev(where); + lastUnqueuedIterator = it; + break; + } + } + return rwstatus; +} + +void QIORing::completionReady() +{ + ResetEvent(eventHandle); + IORING_CQE entry; + while (PopIoRingCompletion(ioRingHandle, &entry) == S_OK) { + // NOLINTNEXTLINE(performance-no-int-to-ptr) + auto *request = reinterpret_cast<GenericRequestType *>(entry.UserData); + if (!addrItMap.contains(request)) { + qCDebug(lcQIORing) << "Got completed entry, but cannot find it in the map. Likely " + "deleted, ignoring. UserData pointer:" + << request; + continue; + } + qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation() + << "- UserData pointer:" << request + << "- Result:" << qt_error_string(entry.ResultCode) << '(' + << QByteArray("0x"_ba + QByteArray::number(entry.ResultCode, 16)).data() + << ')'; + switch (request->operation()) { + case Operation::Open: // Synchronously finishes + Q_UNREACHABLE_RETURN(); + case Operation::Close: { + auto closeRequest = request->takeRequestData<Operation::Close>(); + // We ignore the result of the flush, we are closing the handle anyway. + // NOLINTNEXTLINE(performance-no-int-to-ptr) + if (CloseHandle(HANDLE(closeRequest.fd))) + closeRequest.result.emplace<QIORingResult<Operation::Close>>(); + else + closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError); + invokeCallback(closeRequest); + break; + } + case Operation::Read: { + const ReadWriteStatus status = handleReadCompletion<Operation::Read>(&entry, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto readRequest = request->takeRequestData<Operation::Read>(); + invokeCallback(readRequest); + break; + } + case Operation::Write: { + const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(&entry, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto writeRequest = request->takeRequestData<Operation::Write>(); + invokeCallback(writeRequest); + break; + } + case Operation::VectoredRead: { + const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(&entry, + request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto vectoredReadRequest = request->takeRequestData<Operation::VectoredRead>(); + invokeCallback(vectoredReadRequest); + break; + } + case Operation::VectoredWrite: { + const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(&entry, + request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto vectoredWriteRequest = request->takeRequestData<Operation::VectoredWrite>(); + invokeCallback(vectoredWriteRequest); + break; + } + case Operation::Flush: { + auto flushRequest = request->takeRequestData<Operation::Flush>(); + if (FAILED(entry.ResultCode)) { + qErrnoWarning(entry.ResultCode, "Flush operation failed"); + // @todo any FlushError? + flushRequest.result.emplace<QFileDevice::FileError>( + QFileDevice::FileError::WriteError); + } else { + flushRequest.result.emplace<QIORingResult<Operation::Flush>>(); + } + invokeCallback(flushRequest); + break; + } + case QtPrivate::Operation::Cancel: { + auto cancelRequest = request->takeRequestData<Operation::Cancel>(); + invokeCallback(cancelRequest); + break; + } + case QtPrivate::Operation::Stat: + Q_UNREACHABLE_RETURN(); // Completes synchronously + break; + case Operation::NumOperations: + Q_UNREACHABLE_RETURN(); + break; + } + auto it = addrItMap.take(request); + pendingRequests.erase(it); + --inFlightRequests; + queueWasFull = false; + } + prepareRequests(); + if (unstagedRequests > 0) + submitRequests(); +} + +bool QIORing::waitForCompletions(QDeadlineTimer deadline) +{ + notifier->setEnabled(false); + auto reactivateNotifier = qScopeGuard([this]() { + notifier->setEnabled(true); + }); + + while (!deadline.hasExpired()) { + DWORD timeout = 0; + if (deadline.isForever()) { + timeout = INFINITE; + } else { + timeout = q26::saturate_cast<DWORD>(deadline.remainingTime()); + if (timeout == INFINITE) + --timeout; + } + if (WaitForSingleObject(eventHandle, timeout) == WAIT_OBJECT_0) + return true; + } + return false; +} + +static HANDLE openFile(const QIORingRequest<QIORing::Operation::Open> &openRequest) +{ + DWORD access = 0; + if (openRequest.flags.testFlag(QIODevice::ReadOnly)) + access |= GENERIC_READ; + if (openRequest.flags.testFlag(QIODevice::WriteOnly)) + access |= GENERIC_WRITE; + + DWORD disposition = 0; + if (openRequest.flags.testFlag(QIODevice::Append)) { + qCWarning(lcQIORing, "Opening file with Append not supported for random access file"); + return INVALID_HANDLE_VALUE; + } + if (openRequest.flags.testFlag(QIODevice::NewOnly)) { + disposition = CREATE_NEW; + } else { + // If Write is specified we _may_ create a file. + // See qfsfileengine_p.h openModeCanCreate. + disposition = openRequest.flags.testFlag(QIODeviceBase::WriteOnly) + && !openRequest.flags.testFlags(QIODeviceBase::ExistingOnly) + ? OPEN_ALWAYS + : OPEN_EXISTING; + } + const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + const DWORD flagsAndAttribs = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED; + HANDLE h = CreateFile(openRequest.path.native().c_str(), access, shareMode, nullptr, + disposition, flagsAndAttribs, nullptr); + if (h != INVALID_HANDLE_VALUE && openRequest.flags.testFlag(QIODeviceBase::Truncate)) { + FILE_END_OF_FILE_INFO info; + memset(&info, 0, sizeof(info)); + SetFileInformationByHandle(h, FileEndOfFileInfo, &info, sizeof(info)); + } + return h; +} + +bool QIORing::supportsOperation(Operation op) +{ + switch (op) { + case QtPrivate::Operation::Open: + case QtPrivate::Operation::Close: + case QtPrivate::Operation::Read: + case QtPrivate::Operation::Write: + case QtPrivate::Operation::Flush: + case QtPrivate::Operation::Cancel: + case QtPrivate::Operation::Stat: + case QtPrivate::Operation::VectoredRead: + case QtPrivate::Operation::VectoredWrite: + return true; + case QtPrivate::Operation::NumOperations: + return false; + } + return false; // Not unreachable, we could allow more for io_uring +} + +void QIORing::submitRequests() +{ + stagePending = false; + if (unstagedRequests == 0) + return; + + // We perform a miniscule wait - to see if anything already in the queue is already completed - + // if we have been told the queue is full. Then we can try queuing more things right away + const bool shouldTryWait = std::exchange(queueWasFull, false); + const auto submitToRing = [this, &shouldTryWait] { + quint32 submittedEntries = 0; + HRESULT hr = SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1, &submittedEntries); + qCDebug(lcQIORing) << "Submitted" << submittedEntries << "requests"; + unstagedRequests -= submittedEntries; + if (FAILED(hr)) { + // Too noisy, not a real problem + // qErrnoWarning(hr, "Failed to submit QIORing request: %u", submittedEntries); + return false; + } + return submittedEntries > 0; + }; + if (submitToRing() && shouldTryWait) { + // We try to prepare some more request and submit more if able + prepareRequests(); + if (unstagedRequests > 0) + submitToRing(); + } +} + +void QIORing::prepareRequests() +{ + if (!lastUnqueuedIterator) + return; + Q_ASSERT(!preparingRequests); + QScopedValueRollback<bool> prepareGuard(preparingRequests, true); + + auto it = *lastUnqueuedIterator; + lastUnqueuedIterator.reset(); + const auto end = pendingRequests.end(); + while (!queueWasFull && it != end) { + auto &request = *it; + switch (prepareRequest(request)) { + case RequestPrepResult::Ok: + ++unstagedRequests; + ++inFlightRequests; + break; + case RequestPrepResult::QueueFull: + qCDebug(lcQIORing) << "Queue was reported as full, in flight requests:" + << inFlightRequests << "submission queue size:" << sqEntries + << "completion queue size:" << cqEntries; + queueWasFull = true; + lastUnqueuedIterator = it; + return; + case RequestPrepResult::Defer: + qCDebug(lcQIORing) << "Request for" << request.operation() + << "had to be deferred, will not queue any more requests at the " + "moment."; + lastUnqueuedIterator = it; + return; // + case RequestPrepResult::RequestCompleted: + // Used for requests that immediately finish. So we erase it: + qCDebug(lcQIORing) << "Request for" << request.operation() + << "completed synchronously."; + addrItMap.remove(&request); + it = pendingRequests.erase(it); + continue; // Don't increment iterator again + } + ++it; + } +} + +namespace QtPrivate { +template <typename T> +using DetectHasFd = decltype(std::declval<const T &>().fd); + +template <typename T> +constexpr bool OperationHasFd_v = qxp::is_detected_v<DetectHasFd, T>; +} // namespace QtPrivate + +auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult +{ + qCDebug(lcQIORing) << "Preparing a request with operation" << request.operation(); + HRESULT hr = -1; + + if (!verifyFd(request)) { + finishRequestWithError(request, QFileDevice::OpenError); + return RequestPrepResult::RequestCompleted; + } + + switch (request.operation()) { + case Operation::Open: { + QIORingRequest<Operation::Open> openRequest = request.takeRequestData<Operation::Open>(); + HANDLE fileDescriptor = openFile(openRequest); + if (fileDescriptor == INVALID_HANDLE_VALUE) { + openRequest.result.emplace<QFileDevice::FileError>(QFileDevice::FileError::OpenError); + } else { + auto &result = openRequest.result.emplace<QIORingResult<Operation::Open>>(); + result.fd = qintptr(fileDescriptor); + } + invokeCallback(openRequest); + return RequestPrepResult::RequestCompleted; + } + case Operation::Close: { + if (ongoingSplitOperations > 0) + return RequestPrepResult::Defer; + + // We need to wait until all previous OPS are done before we close the request. + // There is no no-op request in the Windows QIORing, so we issue a flush. + auto *closeRequest = request.requestData<Operation::Close>(); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd)); + hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA, + quintptr(std::addressof(request)), + IOSQE_FLAGS_DRAIN_PRECEDING_OPS); + break; + } + case Operation::Read: { + auto *readRequest = request.requestData<Operation::Read>(); + auto span = readRequest->destination; + auto offset = readRequest->offset; + if (span.size() > MaxReadWriteLen) { + qCDebug(lcQIORing) << "Requested Read of size" << span.size() << "has to be split"; + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0) + ++ongoingSplitOperations; + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildReadOperation(ioRingHandle, readRequest->fd, span, offset, + quintptr(std::addressof(request))); + break; + } + case Operation::VectoredRead: { + auto *vectoredReadRequest = request.requestData<Operation::VectoredRead>(); + auto span = vectoredReadRequest->destinations.front(); + auto offset = vectoredReadRequest->offset; + if (Q_LIKELY(vectoredReadRequest->destinations.size() > 1 + || span.size() > MaxReadWriteLen)) { + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0 && extra->spanIndex == 0) + ++ongoingSplitOperations; + extra->numSpans = vectoredReadRequest->destinations.size(); + + span = vectoredReadRequest->destinations[extra->spanIndex]; + + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildReadOperation(ioRingHandle, vectoredReadRequest->fd, span, + offset, quintptr(std::addressof(request))); + break; + } + case Operation::Write: { + auto *writeRequest = request.requestData<Operation::Write>(); + auto span = writeRequest->source; + auto offset = writeRequest->offset; + if (span.size() > MaxReadWriteLen) { + qCDebug(lcQIORing) << "Requested Write of size" << span.size() << "has to be split"; + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0) + ++ongoingSplitOperations; + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildWriteOperation(ioRingHandle, writeRequest->fd, span, offset, + quintptr(std::addressof(request))); + break; + } + case Operation::VectoredWrite: { + auto *vectoredWriteRequest = request.requestData<Operation::VectoredWrite>(); + auto span = vectoredWriteRequest->sources.front(); + auto offset = vectoredWriteRequest->offset; + if (Q_LIKELY(vectoredWriteRequest->sources.size() > 1 + || span.size() > MaxReadWriteLen)) { + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0 && extra->spanIndex == 0) + ++ongoingSplitOperations; + extra->numSpans = vectoredWriteRequest->sources.size(); + + span = vectoredWriteRequest->sources[extra->spanIndex]; + + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildWriteOperation(ioRingHandle, vectoredWriteRequest->fd, span, + offset, quintptr(std::addressof(request))); + break; + } + case Operation::Flush: { + if (ongoingSplitOperations > 0) + return RequestPrepResult::Defer; + auto *flushRequest = request.requestData<Operation::Flush>(); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd)); + hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT, + quintptr(std::addressof(request)), + IOSQE_FLAGS_DRAIN_PRECEDING_OPS); + break; + } + case QtPrivate::Operation::Stat: { + auto statRequest = request.takeRequestData<Operation::Stat>(); + FILE_STANDARD_INFO info; + // NOLINTNEXTLINE(performance-no-int-to-ptr) + if (!GetFileInformationByHandleEx(HANDLE(statRequest.fd), FileStandardInfo, &info, + sizeof(info))) { + DWORD winErr = GetLastError(); + QFileDevice::FileError error = QFileDevice::UnspecifiedError; + if (winErr == ERROR_FILE_NOT_FOUND || winErr == ERROR_INVALID_HANDLE) + error = QFileDevice::OpenError; + else if (winErr == ERROR_ACCESS_DENIED) + error = QFileDevice::PermissionsError; + statRequest.result.emplace<QFileDevice::FileError>(error); + } else { + auto &result = statRequest.result.emplace<QIORingResult<Operation::Stat>>(); + result.size = info.EndOfFile.QuadPart; + } + invokeCallback(statRequest); + return RequestPrepResult::RequestCompleted; + } + case Operation::Cancel: { + auto *cancelRequest = request.requestData<Operation::Cancel>(); + auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle); + if (!otherOperation || !addrItMap.contains(otherOperation)) { + qCDebug(lcQIORing, "Invalid cancel for non-existant operation"); + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + qCDebug(lcQIORing) << "Cancelling operation of type" << otherOperation->operation() + << "which was" + << (otherOperation->wasQueued() ? "queued" : "not queued"); + Q_ASSERT(&request != otherOperation); + if (!otherOperation->wasQueued()) { + // The request hasn't been queued yet, so we can just drop it from + // the pending requests and call the callback. + auto it = addrItMap.take(otherOperation); + finishRequestWithError(*otherOperation, QFileDevice::AbortError); + pendingRequests.erase(it); // otherOperation is deleted + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + qintptr fd = -1; + invokeOnOp(*otherOperation, [&fd](auto *request) { + if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) + fd = request->fd; + }); + if (fd == -1) { + qCDebug(lcQIORing, "Invalid cancel for non-existant fd"); + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef((HANDLE(fd))); + hr = BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation), + quintptr(std::addressof(request))); + break; + } + case Operation::NumOperations: + Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted); + break; + } + if (hr == IORING_E_SUBMISSION_QUEUE_FULL) + return RequestPrepResult::QueueFull; + if (FAILED(hr)) { + finishRequestWithError(request, QFileDevice::UnspecifiedError); + return RequestPrepResult::RequestCompleted; + } + request.setQueued(true); + return RequestPrepResult::Ok; +} + +bool QIORing::verifyFd(GenericRequestType &req) +{ + bool result = true; + invokeOnOp(req, [&](auto *request) { + if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) { + result = quintptr(request->fd) > 0 && quintptr(request->fd) != quintptr(INVALID_HANDLE_VALUE); + } + }); + return result; +} + +void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra) +{ + switch (op) { + case QtPrivate::Operation::Read: + case QtPrivate::Operation::VectoredRead: + case QtPrivate::Operation::Write: + case QtPrivate::Operation::VectoredWrite: + delete static_cast<QtPrivate::ReadWriteExtra *>(extra); + break; + case QtPrivate::Operation::Open: + case QtPrivate::Operation::Close: + case QtPrivate::Operation::Flush: + case QtPrivate::Operation::Stat: + case QtPrivate::Operation::Cancel: + case QtPrivate::Operation::NumOperations: + break; + } +} + +QT_END_NAMESPACE diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp index d72722f063d..f37812876ea 100644 --- a/src/corelib/itemmodels/qrangemodel.cpp +++ b/src/corelib/itemmodels/qrangemodel.cpp @@ -1385,6 +1385,7 @@ void QRangeModel::resetRoleNames() /*! \property QRangeModel::autoConnectPolicy + \since 6.11 \brief if and when the model auto-connects to property changed notifications. If QRangeModel operates on a data structure that holds the same type of diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h index 3e35ad3981c..f6b08099fe7 100644 --- a/src/corelib/itemmodels/qrangemodel_impl.h +++ b/src/corelib/itemmodels/qrangemodel_impl.h @@ -23,6 +23,7 @@ #include <QtCore/qmap.h> #include <QtCore/qscopedvaluerollback.h> #include <QtCore/qset.h> +#include <QtCore/qvarlengtharray.h> #include <algorithm> #include <functional> @@ -1203,76 +1204,48 @@ public: return std::move(result.data()); } + static constexpr bool isRangeModelRole(int role) + { + return role == Qt::RangeModelDataRole + || role == Qt::RangeModelAdapterRole; + } + + static constexpr bool isPrimaryRole(int role) + { + return role == Qt::DisplayRole || role == Qt::EditRole; + } + QMap<int, QVariant> itemData(const QModelIndex &index) const { QMap<int, QVariant> result; - bool tried = false; - const auto readItemData = [this, &index, &result, &tried](const auto &value){ - Q_UNUSED(this); - Q_UNUSED(index); - using value_type = q20::remove_cvref_t<decltype(value)>; - using multi_role = QRangeModelDetails::is_multi_role<value_type>; - using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>; - if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) { - using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>; - tried = true; - const auto roles = this->itemModel().roleNames().keys(); - for (auto &role : roles) { - if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) - continue; - QVariant data = ItemAccess::readRole(value, role); - if (data.isValid()) - result[role] = std::move(data); - } - } else if constexpr (multi_role()) { - tried = true; - if constexpr (std::is_convertible_v<value_type, decltype(result)>) { + if (index.isValid()) { + bool tried = false; + + // optimisation for items backed by a QMap<int, QVariant> or equivalent + readAt(index, [&result, &tried](const auto &value) { + if constexpr (std::is_convertible_v<decltype(value), decltype(result)>) { + tried = true; result = value; - } else { - const auto roleNames = [this]() -> QHash<int, QByteArray> { - Q_UNUSED(this); - if constexpr (!multi_role::int_key) - return this->itemModel().roleNames(); - else - return {}; - }(); - for (auto it = std::begin(value); it != std::end(value); ++it) { - const int role = [&roleNames, key = QRangeModelDetails::key(it)]() { - Q_UNUSED(roleNames); - if constexpr (multi_role::int_key) - return int(key); - else - return roleNames.key(key.toUtf8(), -1); - }(); - - if (role != -1 && role != Qt::RangeModelDataRole && role != Qt::RangeModelAdapterRole) - result.insert(role, QRangeModelDetails::value(it)); - } } - } else if constexpr (has_metaobject<value_type>) { - if (row_traits::fixed_size() <= 1) { - tried = true; - const auto roleNames = this->itemModel().roleNames(); - const auto end = roleNames.keyEnd(); - for (auto it = roleNames.keyBegin(); it != end; ++it) { - const int role = *it; - if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) - continue; - QVariant data = readRole(index, role, QRangeModelDetails::pointerTo(value)); - if (data.isValid()) - result[role] = std::move(data); - } + }); + if (!tried) { + const auto roles = this->itemModel().roleNames().keys(); + QVarLengthArray<QModelRoleData, 16> roleDataArray; + roleDataArray.reserve(roles.size()); + for (auto role : roles) { + if (isRangeModelRole(role)) + continue; + roleDataArray.emplace_back(role); } - } - }; - - if (index.isValid()) { - readAt(index, readItemData); + QModelRoleDataSpan roleDataSpan(roleDataArray); + multiData(index, roleDataSpan); - if (!tried) { // no multi-role item found - result = this->itemModel().QAbstractItemModel::itemData(index); - result.remove(Qt::RangeModelAdapterRole); + for (auto &&roleData : std::move(roleDataSpan)) { + QVariant data = roleData.data(); + if (data.isValid()) + result[roleData.role()] = std::move(data); + } } } return result; @@ -1288,27 +1261,34 @@ public: using multi_role = QRangeModelDetails::is_multi_role<value_type>; using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>; + const auto readModelData = [&value](QModelRoleData &roleData){ + const int role = roleData.role(); + if (role == Qt::RangeModelDataRole) { + // Qt QML support: "modelData" role returns the entire multi-role item. + // QML can only use raw pointers to QObject (so we unwrap), and gadgets + // only by value (so we take the reference). + if constexpr (std::is_copy_assignable_v<wrapped_value_type>) + roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value))); + else + roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); + } else if (role == Qt::RangeModelAdapterRole) { + // for QRangeModelAdapter however, we want to respect smart pointer wrappers + if constexpr (std::is_copy_assignable_v<value_type>) + roleData.setData(QVariant::fromValue(value)); + else + roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); + } else { + return false; + } + return true; + }; + if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) { using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>; tried = true; for (auto &roleData : roleDataSpan) { - if (roleData.role() == Qt::RangeModelDataRole) { - // Qt QML support: "modelData" role returns the entire multi-role item. - // QML can only use raw pointers to QObject (so we unwrap), and gadgets - // only by value (so we take the reference). - if constexpr (std::is_copy_assignable_v<wrapped_value_type>) - roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value))); - else - roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); - } else if (roleData.role() == Qt::RangeModelAdapterRole) { - // for QRangeModelAdapter however, we want to respect smart pointer wrappers - if constexpr (std::is_copy_assignable_v<value_type>) - roleData.setData(QVariant::fromValue(value)); - else - roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); - } else { + if (!readModelData(roleData)) roleData.setData(ItemAccess::readRole(value, roleData.role())); - } } } else if constexpr (multi_role()) { tried = true; @@ -1337,21 +1317,7 @@ public: if (row_traits::fixed_size() <= 1) { tried = true; for (auto &roleData : roleDataSpan) { - if (roleData.role() == Qt::RangeModelDataRole) { - // Qt QML support: "modelData" role returns the entire multi-role item. - // QML can only use raw pointers to QObject (so we unwrap), and gadgets - // only by value (so we take the reference). - if constexpr (std::is_copy_assignable_v<wrapped_value_type>) - roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value))); - else - roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); - } else if (roleData.role() == Qt::RangeModelAdapterRole) { - // for QRangeModelAdapter however, we want to respect smart pointer wrappers - if constexpr (std::is_copy_assignable_v<value_type>) - roleData.setData(QVariant::fromValue(value)); - else - roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); - } else { + if (!readModelData(roleData)) { roleData.setData(readRole(index, roleData.role(), QRangeModelDetails::pointerTo(value))); } @@ -1360,7 +1326,7 @@ public: tried = true; for (auto &roleData : roleDataSpan) { const int role = roleData.role(); - if (role == Qt::DisplayRole || role == Qt::EditRole) { + if (isPrimaryRole(role)) { roleData.setData(readProperty(index.column(), QRangeModelDetails::pointerTo(value))); } else { @@ -1372,12 +1338,10 @@ public: tried = true; for (auto &roleData : roleDataSpan) { const int role = roleData.role(); - if (role == Qt::DisplayRole || role == Qt::EditRole - || role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) { + if (isPrimaryRole(role) || isRangeModelRole(role)) roleData.setData(read(value)); - } else { + else roleData.clearData(); - } } } }); @@ -1395,7 +1359,8 @@ public: auto emitDataChanged = qScopeGuard([&success, this, &index, role]{ if (success) { Q_EMIT this->dataChanged(index, index, - role == Qt::EditRole || role == Qt::RangeModelDataRole || role == Qt::RangeModelDataRole + role == Qt::EditRole || role == Qt::RangeModelDataRole + || role == Qt::RangeModelAdapterRole ? QList<int>{} : QList<int>{role}); } }); @@ -1408,25 +1373,34 @@ public: using multi_role = QRangeModelDetails::is_multi_role<value_type>; auto setRangeModelDataRole = [&target, &data]{ - auto &targetRef = QRangeModelDetails::refTo(target); constexpr auto targetMetaType = QMetaType::fromType<value_type>(); const auto dataMetaType = data.metaType(); constexpr bool isWrapped = QRangeModelDetails::is_wrapped<value_type>(); if constexpr (!std::is_copy_assignable_v<wrapped_value_type>) { // we don't support replacing objects that are stored as raw pointers, // as this makes object ownership very messy. But we can replace objects - // stored in smart pointers. - if constexpr (isWrapped && !std::is_pointer_v<value_type> - && std::is_copy_assignable_v<value_type>) { - if (data.canConvert(targetMetaType)) { - target = data.value<value_type>(); - return true; + // stored in smart pointers, and we can initialize raw nullptr objects. + if constexpr (isWrapped) { + constexpr bool is_raw_pointer = std::is_pointer_v<value_type>; + if constexpr (!is_raw_pointer && std::is_copy_assignable_v<value_type>) { + if (data.canConvert(targetMetaType)) { + target = data.value<value_type>(); + return true; + } + } else if constexpr (is_raw_pointer) { + if (!QRangeModelDetails::isValid(target) && data.canConvert(targetMetaType)) { + target = data.value<value_type>(); + return true; + } + } else { + Q_UNUSED(target); } } // Otherwise we have a move-only or polymorph type. fall through to // error handling. } else if constexpr (isWrapped) { if (QRangeModelDetails::isValid(target)) { + auto &targetRef = QRangeModelDetails::refTo(target); // we need to get a wrapped value type out of the QVariant, which // might carry a pointer. We have to try all alternatives. if (const auto mt = QMetaType::fromType<wrapped_value_type>(); @@ -1440,10 +1414,10 @@ public: } } } else if (targetMetaType == dataMetaType) { - targetRef = data.value<value_type>(); + QRangeModelDetails::refTo(target) = data.value<value_type>(); return true; } else if (dataMetaType.flags() & QMetaType::PointerToGadget) { - targetRef = *data.value<value_type *>(); + QRangeModelDetails::refTo(target) = *data.value<value_type *>(); return true; } #ifndef QT_NO_DEBUG @@ -1455,17 +1429,16 @@ public: if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) { using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>; - if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) + if (isRangeModelRole(role)) return setRangeModelDataRole(); return ItemAccess::writeRole(target, data, role); } if constexpr (has_metaobject<value_type>) { if (row_traits::fixed_size() <= 1) { // multi-role value - if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) + if (isRangeModelRole(role)) return setRangeModelDataRole(); return writeRole(role, QRangeModelDetails::pointerTo(target), data); } else if (column <= row_traits::fixed_size() // multi-column - && (role == Qt::DisplayRole || role == Qt::EditRole - || role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole)) { + && (isPrimaryRole(role) || isRangeModelRole(role))) { return writeProperty(column, QRangeModelDetails::pointerTo(target), data); } } else if constexpr (multi_role::value) { @@ -1492,14 +1465,20 @@ public: return write(target[roleToSet], data); else return write(target[roleNames.value(roleToSet)], data); - } else if (role == Qt::DisplayRole || role == Qt::EditRole - || role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) { + } else if (isPrimaryRole(role) || isRangeModelRole(role)) { return write(target, data); } return false; }; success = writeAt(index, writeData); + + if constexpr (itemsAreQObjects) { + if (success && isRangeModelRole(role) && this->autoConnectPolicy() == AutoConnectPolicy::Full) { + if (QObject *item = data.value<QObject *>()) + Self::connectProperties(index, item, m_data.context, m_data.properties); + } + } } return success; } @@ -1607,7 +1586,7 @@ public: tried = true; auto targetCopy = makeCopy(target); for (auto &&[role, value] : data.asKeyValueRange()) { - if (role == Qt::RangeModelDataRole || role == Qt::RangeModelAdapterRole) + if (isRangeModelRole(role)) continue; if (!writeRole(role, QRangeModelDetails::pointerTo(targetCopy), value)) { const QByteArray roleName = roleNames.value(role); diff --git a/src/corelib/itemmodels/qrangemodeladapter.qdoc b/src/corelib/itemmodels/qrangemodeladapter.qdoc index 5ab128a8c5f..263bff0dd0c 100644 --- a/src/corelib/itemmodels/qrangemodeladapter.qdoc +++ b/src/corelib/itemmodels/qrangemodeladapter.qdoc @@ -118,11 +118,9 @@ would bypass the QAbstractItemModel notification protocol, those reference objects prevent direct modifications of the items. - \note Calling mutable overloads generates some overhead. Make a const copy - of the adapter (which will not copy the data), or use \c{std::as_const} to - make sure that only the const overloads are used in performance critical, - read-heavy code. This is the same technique as when accessing elements in - an implicitly shared Qt container. + \note Accessing the reference object always makes a call to the model to get + a copy of the value. This can be expensive; for performance critical access + to data, store a copy. \section3 Item access diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp index a5d34eac707..24cc58829c8 100644 --- a/src/corelib/kernel/qmetaobject.cpp +++ b/src/corelib/kernel/qmetaobject.cpp @@ -4405,6 +4405,34 @@ bool QMetaProperty::isFinal() const } /*! + \since 6.11 + Returns \c true if the property is virtual; otherwise returns \c false. + + A property is virtual if the \c{Q_PROPERTY()}'s \c VIRTUAL attribute + is set. +*/ +bool QMetaProperty::isVirtual() const +{ + if (!mobj) + return false; + return data.flags() & Virtual; +} + +/*! + \since 6.11 + Returns \c true if the property does override; otherwise returns \c false. + + A property does override if the \c{Q_PROPERTY()}'s \c OVERRIDE attribute + is set. +*/ +bool QMetaProperty::isOverride() const +{ + if (!mobj) + return false; + return data.flags() & Override; +} + +/*! \since 5.15 Returns \c true if the property is required; otherwise returns \c false. diff --git a/src/corelib/kernel/qmetaobject.h b/src/corelib/kernel/qmetaobject.h index 0f793ca753b..ff3cc751c3a 100644 --- a/src/corelib/kernel/qmetaobject.h +++ b/src/corelib/kernel/qmetaobject.h @@ -365,6 +365,8 @@ public: bool isUser() const; bool isConstant() const; bool isFinal() const; + bool isVirtual() const; + bool isOverride() const; bool isRequired() const; bool isBindable() const; diff --git a/src/corelib/kernel/qtmocconstants.h b/src/corelib/kernel/qtmocconstants.h index 79c0138bb28..260ac2fa5f8 100644 --- a/src/corelib/kernel/qtmocconstants.h +++ b/src/corelib/kernel/qtmocconstants.h @@ -39,7 +39,8 @@ enum PropertyFlags : uint { Resettable = 0x00000004, EnumOrFlag = 0x00000008, Alias = 0x00000010, - // Reserved for future usage = 0x00000020, + Virtual = 0x00000020, + Override = 0x00000040, StdCppSet = 0x00000100, Constant = 0x00000400, Final = 0x00000800, diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp index de7043e8c1d..9c26de94b6d 100644 --- a/src/corelib/mimetypes/qmimeprovider.cpp +++ b/src/corelib/mimetypes/qmimeprovider.cpp @@ -512,8 +512,8 @@ QMimeBinaryProvider::MimeTypeExtraMap::const_iterator QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName) { #if QT_CONFIG(xmlstreamreader) - auto it = m_mimetypeExtra.find(mimeName); - if (it == m_mimetypeExtra.cend()) { + auto [it, insertionOccurred] = m_mimetypeExtra.try_emplace(mimeName); + if (insertionOccurred) { // load comment and globPatterns // shared-mime-info since 1.3 lowercases the xml files @@ -523,9 +523,8 @@ QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName) QFile qfile(mimeFile); if (!qfile.open(QFile::ReadOnly)) - return m_mimetypeExtra.cend(); + return it; - it = m_mimetypeExtra.try_emplace(mimeName).first; MimeTypeExtra &extra = it->second; QString mainPattern; diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h index 5a827fb4148..bdb0e24dde8 100644 --- a/src/corelib/tools/qflatmap_p.h +++ b/src/corelib/tools/qflatmap_p.h @@ -609,14 +609,18 @@ public: T value(const Key &key) const { auto it = find(key); - return it == end() ? T() : it.value(); + if (it == end()) + return T(); + return it.value(); } template <class X, class Y = Compare, is_marked_transparent<Y> = nullptr> T value(const X &key) const { auto it = find(key); - return it == end() ? T() : it.value(); + if (it == end()) + return T(); + return it.value(); } T &operator[](const Key &key) @@ -899,12 +903,13 @@ private: T do_take(iterator it) { - if (it != end()) { + if (it == end()) + return {}; + return [&] { T result = std::move(it.value()); erase(it); return result; - } - return {}; + }(); } template <class InputIt, is_compatible_iterator<InputIt> = nullptr> diff --git a/src/corelib/tools/qmargins.h b/src/corelib/tools/qmargins.h index f833a338b16..cbdb093adc8 100644 --- a/src/corelib/tools/qmargins.h +++ b/src/corelib/tools/qmargins.h @@ -333,20 +333,13 @@ private: qreal m_right; qreal m_bottom; - QT_WARNING_PUSH - QT_WARNING_DISABLE_FLOAT_COMPARE friend constexpr bool qFuzzyCompare(const QMarginsF &lhs, const QMarginsF &rhs) noexcept { - return ((!lhs.m_left || !rhs.m_left) ? qFuzzyIsNull(lhs.m_left - rhs.m_left) - : qFuzzyCompare(lhs.m_left, rhs.m_left)) - && ((!lhs.m_top || !rhs.m_top) ? qFuzzyIsNull(lhs.m_top - rhs.m_top) - : qFuzzyCompare(lhs.m_top, rhs.m_top)) - && ((!lhs.m_right || !rhs.m_right) ? qFuzzyIsNull(lhs.m_right - rhs.m_right) - : qFuzzyCompare(lhs.m_right, rhs.m_right)) - && ((!lhs.m_bottom || !rhs.m_bottom) ? qFuzzyIsNull(lhs.m_bottom - rhs.m_bottom) - : qFuzzyCompare(lhs.m_bottom, rhs.m_bottom)); + return QtPrivate::fuzzyCompare(lhs.m_left, rhs.m_left) + && QtPrivate::fuzzyCompare(lhs.m_top, rhs.m_top) + && QtPrivate::fuzzyCompare(lhs.m_right, rhs.m_right) + && QtPrivate::fuzzyCompare(lhs.m_bottom, rhs.m_bottom); } - QT_WARNING_POP friend constexpr bool qFuzzyIsNull(const QMarginsF &m) noexcept { return qFuzzyIsNull(m.m_left) && qFuzzyIsNull(m.m_top) diff --git a/src/corelib/tools/qpoint.h b/src/corelib/tools/qpoint.h index ae896ba7079..1b767324058 100644 --- a/src/corelib/tools/qpoint.h +++ b/src/corelib/tools/qpoint.h @@ -259,14 +259,11 @@ public: } private: - QT_WARNING_PUSH - QT_WARNING_DISABLE_FLOAT_COMPARE friend constexpr bool qFuzzyCompare(const QPointF &p1, const QPointF &p2) noexcept { - return ((!p1.xp || !p2.xp) ? qFuzzyIsNull(p1.xp - p2.xp) : qFuzzyCompare(p1.xp, p2.xp)) - && ((!p1.yp || !p2.yp) ? qFuzzyIsNull(p1.yp - p2.yp) : qFuzzyCompare(p1.yp, p2.yp)); + return QtPrivate::fuzzyCompare(p1.xp, p2.xp) + && QtPrivate::fuzzyCompare(p1.yp, p2.yp); } - QT_WARNING_POP friend constexpr bool qFuzzyIsNull(const QPointF &point) noexcept { return qFuzzyIsNull(point.xp) && qFuzzyIsNull(point.yp); diff --git a/src/corelib/tools/qsize.h b/src/corelib/tools/qsize.h index 86509cb6483..1c5b02ed1f0 100644 --- a/src/corelib/tools/qsize.h +++ b/src/corelib/tools/qsize.h @@ -258,10 +258,9 @@ private: QT_WARNING_DISABLE_FLOAT_COMPARE friend constexpr bool qFuzzyCompare(const QSizeF &s1, const QSizeF &s2) noexcept { - // Cannot use qFuzzyCompare(), because it will give incorrect results // if one of the arguments is 0.0. - return ((!s1.wd || !s2.wd) ? qFuzzyIsNull(s1.wd - s2.wd) : qFuzzyCompare(s1.wd, s2.wd)) - && ((!s1.ht || !s2.ht) ? qFuzzyIsNull(s1.ht - s2.ht) : qFuzzyCompare(s1.ht, s2.ht)); + return QtPrivate::fuzzyCompare(s1.wd, s2.wd) + && QtPrivate::fuzzyCompare(s1.ht, s2.ht); } QT_WARNING_POP friend constexpr bool qFuzzyIsNull(const QSizeF &size) noexcept diff --git a/src/gui/accessible/linux/qspi_struct_marshallers.cpp b/src/gui/accessible/linux/qspi_struct_marshallers.cpp index 241bad502e3..5e171244cd0 100644 --- a/src/gui/accessible/linux/qspi_struct_marshallers.cpp +++ b/src/gui/accessible/linux/qspi_struct_marshallers.cpp @@ -28,7 +28,6 @@ QT_IMPL_METATYPE_EXTERN(QSpiRelationArray) QT_IMPL_METATYPE_EXTERN(QSpiTextRange) QT_IMPL_METATYPE_EXTERN(QSpiTextRangeList) QT_IMPL_METATYPE_EXTERN(QSpiAttributeSet) -QT_IMPL_METATYPE_EXTERN(QSpiAppUpdate) QT_IMPL_METATYPE_EXTERN(QSpiDeviceEvent) QT_IMPL_METATYPE_EXTERN(QSpiMatchRule) @@ -134,23 +133,6 @@ const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiEventListener return argument; } -/* QSpiAppUpdate */ -/*---------------------------------------------------------------------------*/ - -QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAppUpdate &update) { - argument.beginStructure(); - argument << update.type << update.address; - argument.endStructure(); - return argument; -} - -const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAppUpdate &update) { - argument.beginStructure(); - argument >> update.type >> update.address; - argument.endStructure(); - return argument; -} - /* QSpiRelationArrayEntry */ /*---------------------------------------------------------------------------*/ @@ -245,7 +227,6 @@ void qSpiInitializeStructTypes() qDBusRegisterMetaType<QSpiEventListenerArray>(); qDBusRegisterMetaType<QSpiDeviceEvent>(); qDBusRegisterMetaType<QSpiMatchRule>(); - qDBusRegisterMetaType<QSpiAppUpdate>(); qDBusRegisterMetaType<QSpiRelationArrayEntry>(); qDBusRegisterMetaType<QSpiRelationArray>(); } diff --git a/src/gui/accessible/linux/qspi_struct_marshallers_p.h b/src/gui/accessible/linux/qspi_struct_marshallers_p.h index fe2d52fb4c2..4c446a97040 100644 --- a/src/gui/accessible/linux/qspi_struct_marshallers_p.h +++ b/src/gui/accessible/linux/qspi_struct_marshallers_p.h @@ -106,21 +106,6 @@ Q_DECLARE_TYPEINFO(QSpiTextRange, Q_RELOCATABLE_TYPE); typedef QList<QSpiTextRange> QSpiTextRangeList; typedef QMap <QString, QString> QSpiAttributeSet; -enum QSpiAppUpdateType { - QSPI_APP_UPDATE_ADDED = 0, - QSPI_APP_UPDATE_REMOVED = 1 -}; -Q_DECLARE_TYPEINFO(QSpiAppUpdateType, Q_PRIMITIVE_TYPE); - -struct QSpiAppUpdate { - int type; /* Is an application added or removed */ - QString address; /* D-Bus address of application added or removed */ -}; -Q_DECLARE_TYPEINFO(QSpiAppUpdate, Q_RELOCATABLE_TYPE); - -QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAppUpdate &update); -const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAppUpdate &update); - struct QSpiDeviceEvent { unsigned int type; int id; @@ -171,7 +156,6 @@ QT_DECL_METATYPE_EXTERN(QSpiRelationArray, /* not exported */) QT_DECL_METATYPE_EXTERN(QSpiTextRange, /* not exported */) QT_DECL_METATYPE_EXTERN(QSpiTextRangeList, /* not exported */) QT_DECL_METATYPE_EXTERN(QSpiAttributeSet, /* not exported */) -QT_DECL_METATYPE_EXTERN(QSpiAppUpdate, /* not exported */) QT_DECL_METATYPE_EXTERN(QSpiDeviceEvent, /* not exported */) QT_DECL_METATYPE_EXTERN(QSpiMatchRule, /* not exported */) diff --git a/src/gui/accessible/qaccessiblecache.cpp b/src/gui/accessible/qaccessiblecache.cpp index a8255e04c02..311b53aeaa3 100644 --- a/src/gui/accessible/qaccessiblecache.cpp +++ b/src/gui/accessible/qaccessiblecache.cpp @@ -4,6 +4,7 @@ #include "qaccessiblecache_p.h" #include <QtCore/qdebug.h> #include <QtCore/qloggingcategory.h> +#include <private/qguiapplication_p.h> #if QT_CONFIG(accessibility) @@ -176,10 +177,28 @@ void QAccessibleCache::sendObjectDestroyedEvent(QObject *obj) void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj) { - QAccessibleInterface *iface = idToInterface.take(id); + const auto it = idToInterface.find(id); + if (it == idToInterface.end()) // the interface may be deleted already + return; + + QAccessibleInterface *iface = *it; qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface; - if (!iface) // the interface may be deleted already + if (!iface) { + idToInterface.erase(it); return; + } + + // QObjects sends this from their destructor, but + // the object less interfaces calls deleteInterface + // directly + if (!obj && !iface->object()) { + if (QGuiApplicationPrivate::is_app_running && !QGuiApplicationPrivate::is_app_closing && QAccessible::isActive()) { + QAccessibleObjectDestroyedEvent event(id); + QAccessible::updateAccessibility(&event); + } + } + + idToInterface.erase(it); interfaceToId.take(iface); if (!obj) obj = iface->object(); diff --git a/src/gui/image/qabstractfileiconprovider.cpp b/src/gui/image/qabstractfileiconprovider.cpp index 78777ec115a..ad646a6b89a 100644 --- a/src/gui/image/qabstractfileiconprovider.cpp +++ b/src/gui/image/qabstractfileiconprovider.cpp @@ -288,3 +288,5 @@ QString QAbstractFileIconProvider::type(const QFileInfo &info) const } QT_END_NAMESPACE + +#include "moc_qabstractfileiconprovider.cpp" diff --git a/src/gui/math3d/qquaternion.cpp b/src/gui/math3d/qquaternion.cpp index a675f59eb1f..57587322ea5 100644 --- a/src/gui/math3d/qquaternion.cpp +++ b/src/gui/math3d/qquaternion.cpp @@ -409,7 +409,7 @@ QQuaternion QQuaternion::fromAxisAndAngle (float x, float y, float z, float angle) { float length = qHypot(x, y, z); - if (!qFuzzyCompare(length, 1.0f) && !qFuzzyIsNull(length)) { + if (!qFuzzyIsNull(length) && !qFuzzyCompare(length, 1.0f)) { x /= length; y /= length; z /= length; diff --git a/src/gui/math3d/qvectornd.cpp b/src/gui/math3d/qvectornd.cpp index dcd7bdbcf80..ec836cfa56e 100644 --- a/src/gui/math3d/qvectornd.cpp +++ b/src/gui/math3d/qvectornd.cpp @@ -467,7 +467,6 @@ QDataStream &operator>>(QDataStream &stream, QVector2D &vector) float x, y; stream >> x; stream >> y; - Q_ASSERT(qIsFinite(x) && qIsFinite(y)); vector.setX(x); vector.setY(y); return stream; @@ -1098,7 +1097,6 @@ QDataStream &operator>>(QDataStream &stream, QVector3D &vector) stream >> x; stream >> y; stream >> z; - Q_ASSERT(qIsFinite(x) && qIsFinite(y) && qIsFinite(z)); vector.setX(x); vector.setY(y); vector.setZ(z); @@ -1627,7 +1625,6 @@ QDataStream &operator>>(QDataStream &stream, QVector4D &vector) stream >> y; stream >> z; stream >> w; - Q_ASSERT(qIsFinite(x) && qIsFinite(y) && qIsFinite(z) && qIsFinite(w)); vector.setX(x); vector.setY(y); vector.setZ(z); diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp index 7acc3c5218c..5bd9799ca7d 100644 --- a/src/gui/text/qrawfont.cpp +++ b/src/gui/text/qrawfont.cpp @@ -224,7 +224,7 @@ void QRawFont::loadFromData(const QByteArray &fontData, \since 6.11 */ -int QRawFont::glyphCount() const +quint32 QRawFont::glyphCount() const { return d->isValid() ? d->fontEngine->glyphCount() : 0; } diff --git a/src/gui/text/qrawfont.h b/src/gui/text/qrawfont.h index f13f04ebe37..a1522aa8048 100644 --- a/src/gui/text/qrawfont.h +++ b/src/gui/text/qrawfont.h @@ -55,7 +55,7 @@ public: inline bool operator!=(const QRawFont &other) const { return !operator==(other); } - int glyphCount() const; + quint32 glyphCount() const; QString familyName() const; QString styleName() const; diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index ede5409b112..41d2d417133 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -1746,7 +1746,7 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st // fix up clusters so that the cluster indices will be monotonic // and thus we never return out-of-order indices - while (last_cluster++ < cluster && str_pos < item_length) + for (uint j = last_cluster; j < cluster && str_pos < item_length; ++j) log_clusters[str_pos++] = last_glyph_pos; last_glyph_pos = i + glyphs_shaped; last_cluster = cluster; diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index 64c806a087b..b3c22ff051e 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -183,6 +183,9 @@ { qCDebug(lcQpaDrawing) << "Backing properties changed for" << self; + if (!m_platformWindow) + return; + [self propagateBackingProperties]; // Ideally we would plumb this situation through QPA in a way that lets diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp index ecd95f5e338..757959e5694 100644 --- a/src/plugins/platforms/wasm/qwasmdrag.cpp +++ b/src/plugins/platforms/wasm/qwasmdrag.cpp @@ -174,19 +174,21 @@ void QWasmDrag::onNativeDrop(DragEvent *event) // files, but the browser expects that accepted state is set before any // async calls. event->acceptDrop(); + std::shared_ptr<DragState> dragState = m_dragState; - const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos, + const auto dropCallback = [dragState, wasmWindow, targetWindowPos, actions, mouseButton, modifiers](QMimeData *mimeData) { - - auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction); - *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData, + if (mimeData) { + auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction); + *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData, targetWindowPos, actions, mouseButton, modifiers); - if (dropResponse->isAccepted()) - m_dragState->dropAction = dropResponse->acceptedAction(); + if (dragState && dropResponse->isAccepted()) + dragState->dropAction = dropResponse->acceptedAction(); - delete mimeData; + delete mimeData; + } }; event->dataTransfer.toMimeDataWithFile(dropCallback); diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h index 61df8155fa6..5bb8ec66a3c 100644 --- a/src/plugins/platforms/wasm/qwasmdrag.h +++ b/src/plugins/platforms/wasm/qwasmdrag.h @@ -41,7 +41,7 @@ public: private: struct DragState; - std::unique_ptr<DragState> m_dragState; + std::shared_ptr<DragState> m_dragState; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index f27943070d0..2be05625971 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -513,7 +513,6 @@ void QWaylandWindow::setGeometry(const QRect &r) mWindowDecoration->update(); QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(window(), geometry()); - mSentInitialResize = true; } // Wayland has no concept of areas being exposed or not, only the entire window, when our geometry changes, we need to flag the new area as exposed diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index 9e1bd92af30..7dda16cc776 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -334,7 +334,6 @@ protected: int mFrameCallbackTimeout = 100; QVariantMap m_properties; - bool mSentInitialResize = false; QPoint mOffset; std::optional<qreal> mScale = std::nullopt; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index db3fb160593..e2f181aa628 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -82,9 +82,12 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (event->changedStates().checked || event->changedStates().checkStateMixed) { - // Notifies states changes in checkboxes and switches. + // Notifies states changes in checkboxes, switches, and checkable item view items. if (accessible->role() == QAccessible::CheckBox - || accessible->role() == QAccessible::Switch) { + || accessible->role() == QAccessible::Switch + || accessible->role() == QAccessible::Cell + || accessible->role() == QAccessible::ListItem + || accessible->role() == QAccessible::TreeItem) { if (auto provider = providerForAccessible(accessible)) { long toggleState = ToggleState_Off; if (accessible->state().checked) diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index b4c12ed1a0c..d8e41a753ef 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -856,7 +856,7 @@ void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event) if (event->data.data32[0] && event->data.data32[0] != current_target) return; - const bool dropPossible = event->data.data32[1]; + const bool dropPossible = event->data.data32[1] & 1; setCanDrop(dropPossible); if (dropPossible) { diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp index fbd6d3154e2..6e7077b383e 100644 --- a/src/tools/moc/generator.cpp +++ b/src/tools/moc/generator.cpp @@ -770,6 +770,10 @@ void Generator::addProperties() addFlag("Constant"); if (p.final) addFlag("Final"); + if (p.virtual_) + addFlag("Virtual"); + if (p.override) + addFlag("Override"); if (p.user != "false") addFlag("User"); if (p.required) diff --git a/src/tools/moc/moc.cpp b/src/tools/moc/moc.cpp index 64af8c10fc1..baa6690350d 100644 --- a/src/tools/moc/moc.cpp +++ b/src/tools/moc/moc.cpp @@ -1434,6 +1434,9 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) next(IDENTIFIER); propDef.name = lexem(); continue; + } else if (l[0] == 'O' && l == "OVERRIDE") { + propDef.override = true; + continue; } else if (l[0] == 'R' && l == "REQUIRED") { propDef.required = true; continue; @@ -1441,6 +1444,9 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) prev(); propDef.revision = parseRevision().toEncodedVersion<int>(); continue; + } else if (l[0] == 'V' && l == "VIRTUAL") { + propDef.virtual_ = true; + continue; } QByteArray v, v2; @@ -1545,6 +1551,24 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) propDef.write = ""; warning(msg.constData()); } + if (propDef.override && propDef.virtual_) { + const QByteArray msg = "Issue with property declaration " + propDef.name + + ": VIRTUAL is redundant when overriding a property. The OVERRIDE " + "must only be used when actually overriding an existing property; using it on a " + "new property is an error."; + error(msg.constData()); + } + if (propDef.override && propDef.final) { + const QByteArray msg = "Issue with property declaration " + propDef.name + + ": OVERRIDE is redundant when property is marked FINAL"; + error(msg.constData()); + } + if (propDef.virtual_ && propDef.final) { + const QByteArray msg = "Issue with property declaration " + propDef.name + + ": The VIRTUAL cannot be combined with FINAL, as these attributes are mutually " + "exclusive"; + error(msg.constData()); + } } void Moc::parseProperty(ClassDef *def, Moc::PropertyMode mode) diff --git a/src/tools/moc/moc.h b/src/tools/moc/moc.h index aafa80d2164..a211433622a 100644 --- a/src/tools/moc/moc.h +++ b/src/tools/moc/moc.h @@ -130,6 +130,8 @@ struct PropertyDef TypeTags typeTag; bool constant = false; bool final = false; + bool virtual_ = false; + bool override = false; bool required = false; int relativeIndex = -1; // property index in current metaobject int lineNumber = 0; diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp index cc3a230f9b4..ba941012dd7 100644 --- a/src/widgets/accessible/itemviews.cpp +++ b/src/widgets/accessible/itemviews.cpp @@ -99,6 +99,21 @@ QHeaderView *QAccessibleTable::verticalHeader() const return header; } +// Normally cellAt takes row/column in the range +// [0 .. rowCount()) +// [0 .. columnCount()) +// +// As an extension we allow clients to ask for headers +// +// * Has both vertical and horizontal headers: +// (-1,-1) -> corner button +// * Has column headers: +// (-1, column) -> column header for column \a column +// * has row headers +// (row, -1) -> row header for row \a row +// +// If asking for a header that does not exist, The invalid +// index warning is logged, and nullptr is returned. QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const { const QAbstractItemView *theView = view(); @@ -107,6 +122,22 @@ QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const return nullptr; Q_ASSERT(role() != QAccessible::List); Q_ASSERT(role() != QAccessible::Tree); + + const int vHeader = verticalHeader() ? 1 : 0; + const int hHeader = horizontalHeader() ? 1 : 0; + + const int doHHeader = ((row == -1) && hHeader); + const int doVHeader = ((column == -1) && vHeader); + + if (doVHeader && doHHeader) + return child(0); + + if (doVHeader) + return child((row + hHeader) * (columnCount() + vHeader) + (column + vHeader)); + + if (doHHeader) + return child((row + hHeader) * (columnCount() + vHeader) + (column + vHeader)); + QModelIndex index = theModel->index(row, column, theView->rootIndex()); if (Q_UNLIKELY(!index.isValid())) { qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << theView; diff --git a/src/widgets/itemviews/qabstractitemview.cpp b/src/widgets/itemviews/qabstractitemview.cpp index 6288aae096a..05233ba5801 100644 --- a/src/widgets/itemviews/qabstractitemview.cpp +++ b/src/widgets/itemviews/qabstractitemview.cpp @@ -172,6 +172,43 @@ void QAbstractItemViewPrivate::checkMouseMove(const QPersistentModelIndex &index } } +#if QT_CONFIG(accessibility) +void QAbstractItemViewPrivate::updateItemAccessibility(const QModelIndex &index, + const QList<int> &roles) +{ + Q_Q(QAbstractItemView); + + if (!QAccessible::isActive()) + return; + + const int childIndex = accessibleChildIndex(index); + if (childIndex < 0) + return; + + // see QAccessibleTableCell for how role data are mapped to the a11y layer + + for (int role : roles) { + if (role == Qt::AccessibleTextRole + || (role == Qt::DisplayRole + && index.data(Qt::AccessibleTextRole).toString().isEmpty())) { + QAccessibleEvent event(q, QAccessible::NameChanged); + event.setChild(childIndex); + QAccessible::updateAccessibility(&event); + } else if (role == Qt::AccessibleDescriptionRole) { + QAccessibleEvent event(q, QAccessible::DescriptionChanged); + event.setChild(childIndex); + QAccessible::updateAccessibility(&event); + } else if (role == Qt::CheckStateRole) { + QAccessible::State state; + state.checked = true; + QAccessibleStateChangeEvent event(q, state); + event.setChild(childIndex); + QAccessible::updateAccessibility(&event); + } + } +} +#endif + #if QT_CONFIG(gestures) && QT_CONFIG(scroller) // stores and restores the selection and current item when flicking @@ -3495,6 +3532,10 @@ void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelInde accessibleEvent.setLastRow(bottomRight.row()); accessibleEvent.setLastColumn(bottomRight.column()); QAccessible::updateAccessibility(&accessibleEvent); + + // send accessibility events as needed when current item is modified + if (topLeft == bottomRight && topLeft == currentIndex()) + d->updateItemAccessibility(topLeft, roles); } #endif d->updateGeometry(); diff --git a/src/widgets/itemviews/qabstractitemview_p.h b/src/widgets/itemviews/qabstractitemview_p.h index 60799fb8a50..f9e899d7fc8 100644 --- a/src/widgets/itemviews/qabstractitemview_p.h +++ b/src/widgets/itemviews/qabstractitemview_p.h @@ -272,6 +272,18 @@ public: return isIndexValid(index) && isIndexSelectable(index); } +#if QT_CONFIG(accessibility) + virtual int accessibleChildIndex(const QModelIndex &index) const + { + Q_UNUSED(index); + return -1; + } +#endif + +#if QT_CONFIG(accessibility) + void updateItemAccessibility(const QModelIndex &index, const QList<int> &roles); +#endif + // reimplemented from QAbstractScrollAreaPrivate QPoint contentsOffset() const override { Q_Q(const QAbstractItemView); diff --git a/src/widgets/itemviews/qlistview.cpp b/src/widgets/itemviews/qlistview.cpp index e245f98151b..50b6034500d 100644 --- a/src/widgets/itemviews/qlistview.cpp +++ b/src/widgets/itemviews/qlistview.cpp @@ -1959,6 +1959,14 @@ bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QMo } #endif +#if QT_CONFIG(accessibility) +int QListViewPrivate::accessibleChildIndex(const QModelIndex &index) const +{ + Q_Q(const QListView); + return q->visualIndex(index); +} +#endif + void QListViewPrivate::removeCurrentAndDisabled(QList<QModelIndex> *indexes, const QModelIndex ¤t) const { @@ -3397,11 +3405,12 @@ void QIconModeViewBase::updateContentsSize() */ void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + Q_D(const QListView); QAbstractItemView::currentChanged(current, previous); #if QT_CONFIG(accessibility) if (QAccessible::isActive()) { if (current.isValid() && hasFocus()) { - int entry = visualIndex(current); + int entry = d->accessibleChildIndex(current); QAccessibleEvent event(this, QAccessible::Focus); event.setChild(entry); QAccessible::updateAccessibility(&event); @@ -3417,18 +3426,19 @@ void QListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { #if QT_CONFIG(accessibility) + Q_D(const QListView); if (QAccessible::isActive()) { // ### does not work properly for selection ranges. QModelIndex sel = selected.indexes().value(0); if (sel.isValid()) { - int entry = visualIndex(sel); + int entry = d->accessibleChildIndex(sel); QAccessibleEvent event(this, QAccessible::SelectionAdd); event.setChild(entry); QAccessible::updateAccessibility(&event); } QModelIndex desel = deselected.indexes().value(0); if (desel.isValid()) { - int entry = visualIndex(desel); + int entry = d->accessibleChildIndex(desel); QAccessibleEvent event(this, QAccessible::SelectionRemove); event.setChild(entry); QAccessible::updateAccessibility(&event); diff --git a/src/widgets/itemviews/qlistview_p.h b/src/widgets/itemviews/qlistview_p.h index 4475fa5461f..7e36887a65c 100644 --- a/src/widgets/itemviews/qlistview_p.h +++ b/src/widgets/itemviews/qlistview_p.h @@ -346,6 +346,10 @@ public: bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index) override; #endif +#if QT_CONFIG(accessibility) + int accessibleChildIndex(const QModelIndex &index) const override; +#endif + inline void setGridSize(const QSize &size) { grid = size; } inline QSize gridSize() const { return grid; } inline void setWrapping(bool b) { wrap = b; } diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp index 40e3fcaf91b..2d28b3d4a81 100644 --- a/src/widgets/itemviews/qtableview.cpp +++ b/src/widgets/itemviews/qtableview.cpp @@ -3593,7 +3593,7 @@ void QTableView::currentChanged(const QModelIndex ¤t, const QModelIndex &p if (QAccessible::isActive()) { if (current.isValid() && hasFocus()) { Q_D(QTableView); - int entry = d->accessibleTable2Index(current); + int entry = d->accessibleChildIndex(current); QAccessibleEvent event(this, QAccessible::Focus); event.setChild(entry); QAccessible::updateAccessibility(&event); @@ -3616,14 +3616,14 @@ void QTableView::selectionChanged(const QItemSelection &selected, // ### does not work properly for selection ranges. QModelIndex sel = selected.indexes().value(0); if (sel.isValid()) { - int entry = d->accessibleTable2Index(sel); + int entry = d->accessibleChildIndex(sel); QAccessibleEvent event(this, QAccessible::SelectionAdd); event.setChild(entry); QAccessible::updateAccessibility(&event); } QModelIndex desel = deselected.indexes().value(0); if (desel.isValid()) { - int entry = d->accessibleTable2Index(desel); + int entry = d->accessibleChildIndex(desel); QAccessibleEvent event(this, QAccessible::SelectionRemove); event.setChild(entry); QAccessible::updateAccessibility(&event); diff --git a/src/widgets/itemviews/qtableview_p.h b/src/widgets/itemviews/qtableview_p.h index 8ddb8e797a9..9a7ce229880 100644 --- a/src/widgets/itemviews/qtableview_p.h +++ b/src/widgets/itemviews/qtableview_p.h @@ -141,11 +141,14 @@ public: QStyleOptionViewItem::ViewItemPosition viewItemPosition(const QModelIndex &index) const; - inline int accessibleTable2Index(const QModelIndex &index) const { +#if QT_CONFIG(accessibility) + inline int accessibleChildIndex(const QModelIndex &index) const override + { const int vHeader = verticalHeader ? 1 : 0; return (index.row() + (horizontalHeader ? 1 : 0)) * (index.model()->columnCount() + vHeader) + index.column() + vHeader; } +#endif int sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const; int sectionSpanSize(const QHeaderView *header, int logical, int span) const; diff --git a/src/widgets/itemviews/qtreeview.cpp b/src/widgets/itemviews/qtreeview.cpp index e38d78b72f8..570566793dc 100644 --- a/src/widgets/itemviews/qtreeview.cpp +++ b/src/widgets/itemviews/qtreeview.cpp @@ -4083,13 +4083,15 @@ void QTreeViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order) model->sort(column, order); } -int QTreeViewPrivate::accessibleTree2Index(const QModelIndex &index) const +#if QT_CONFIG(accessibility) +int QTreeViewPrivate::accessibleChildIndex(const QModelIndex &index) const { Q_Q(const QTreeView); // Note that this will include the header, even if its hidden. return (q->visualIndex(index) + (q->header() ? 1 : 0)) * index.model()->columnCount() + index.column(); } +#endif void QTreeViewPrivate::updateIndentationFromStyle() { @@ -4116,7 +4118,7 @@ void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &pr Q_D(QTreeView); QAccessibleEvent event(this, QAccessible::Focus); - event.setChild(d->accessibleTree2Index(current)); + event.setChild(d->accessibleChildIndex(current)); QAccessible::updateAccessibility(&event); } #endif @@ -4136,7 +4138,7 @@ void QTreeView::selectionChanged(const QItemSelection &selected, // ### does not work properly for selection ranges. QModelIndex sel = selected.indexes().value(0); if (sel.isValid()) { - int entry = d->accessibleTree2Index(sel); + int entry = d->accessibleChildIndex(sel); Q_ASSERT(entry >= 0); QAccessibleEvent event(this, QAccessible::SelectionAdd); event.setChild(entry); @@ -4144,7 +4146,7 @@ void QTreeView::selectionChanged(const QItemSelection &selected, } QModelIndex desel = deselected.indexes().value(0); if (desel.isValid()) { - int entry = d->accessibleTree2Index(desel); + int entry = d->accessibleChildIndex(desel); Q_ASSERT(entry >= 0); QAccessibleEvent event(this, QAccessible::SelectionRemove); event.setChild(entry); diff --git a/src/widgets/itemviews/qtreeview_p.h b/src/widgets/itemviews/qtreeview_p.h index 5a4e057901c..34db2fcdacb 100644 --- a/src/widgets/itemviews/qtreeview_p.h +++ b/src/widgets/itemviews/qtreeview_p.h @@ -234,7 +234,9 @@ public: return (viewIndex(index) + (header ? 1 : 0)) * model->columnCount()+index.column(); } - int accessibleTree2Index(const QModelIndex &index) const; +#if QT_CONFIG(accessibility) + int accessibleChildIndex(const QModelIndex &index) const override; +#endif void updateIndentationFromStyle(); diff --git a/tests/auto/corelib/io/CMakeLists.txt b/tests/auto/corelib/io/CMakeLists.txt index c0d5ea3136e..10327ceaefb 100644 --- a/tests/auto/corelib/io/CMakeLists.txt +++ b/tests/auto/corelib/io/CMakeLists.txt @@ -9,7 +9,7 @@ endif() if(QT_FEATURE_private_tests) add_subdirectory(qabstractfileengine) add_subdirectory(qfileinfo) - if(LINUX AND QT_FEATURE_liburing) + if((LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring)) add_subdirectory(qioring) endif() add_subdirectory(qipaddress) diff --git a/tests/auto/corelib/io/qioring/tst_qioring.cpp b/tests/auto/corelib/io/qioring/tst_qioring.cpp index 1128bcd7979..75d4fe68c55 100644 --- a/tests/auto/corelib/io/qioring/tst_qioring.cpp +++ b/tests/auto/corelib/io/qioring/tst_qioring.cpp @@ -5,7 +5,12 @@ #include <QtCore/private/qioring_p.h> +#ifdef Q_OS_WIN +#include <QtCore/qt_windows.h> +#include <io.h> +#else #include <QtCore/private/qcore_unix_p.h> +#endif using namespace Qt::StringLiterals; using namespace std::chrono_literals; @@ -30,7 +35,13 @@ private: void tst_QIORing::closeFile(qintptr fd) { +#ifdef Q_OS_WIN + // NOLINTNEXTLINE(performance-no-int-to-ptr) + HANDLE h = HANDLE(fd); + CloseHandle(h); +#else QT_CLOSE(fd); +#endif } qintptr tst_QIORing::openHelper(QIORing *ring, const QString &path, QIODevice::OpenMode flags) @@ -109,7 +120,11 @@ void tst_QIORing::read() QFile file(QFINDTESTDATA("data/input.txt")); QVERIFY(file.open(QIODevice::ReadOnly)); int fd = file.handle(); +#ifdef Q_OS_WIN + qintptr nativeFd = _get_osfhandle(fd); +#else qintptr nativeFd = fd; +#endif QIORing ring; QVERIFY(ring.ensureInitialized()); diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 71389abc976..a4ef698d380 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -925,7 +925,7 @@ void tst_QUrl::resolving_data() QTest::newRow("/-on-empty-no-authority") << "scheme:" << "/" << "scheme:/"; QTest::newRow(".-on-empty-no-authority") << "scheme:" << "." << "scheme:"; QTest::newRow("./-on-empty-no-authority") << "scheme:" << "./" << "scheme:"; - QTest::newRow(".//-on-empty-no-authority") << "scheme:" << "./" << "scheme:"; + QTest::newRow(".//-on-empty-no-authority") << "scheme:" << ".//" << "scheme:"; QTest::newRow("..-on-empty-no-authority") << "scheme:" << ".." << "scheme:"; QTest::newRow("../-on-empty-no-authority") << "scheme:" << "../" << "scheme:"; diff --git a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp index eab791e57eb..4124b723b4c 100644 --- a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp +++ b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp @@ -2631,8 +2631,9 @@ public: auto &childRows() { return m_children; } private: - template <std::size_t I> // read-only is enough for this - friend decltype(auto) get(const ObjectTreeItem &row) { return row.m_objects[I]; } + template <std::size_t I, typename Item, + std::enable_if_t<std::is_same_v<q20::remove_cvref_t<Item>, ObjectTreeItem>, bool> = true> + friend decltype(auto) get(Item &&row) { return q23::forward_like<Item>(row.m_objects[I]); } ObjectTreeItem *m_parentRow = nullptr; std::optional<ObjectTree> m_children = std::nullopt; @@ -2684,7 +2685,7 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() Object *newGrandChild = new Object; ObjectTreeItem newBranch(newChild); newBranch.childRows() = ObjectTree{ - ObjectTreeItem(), // skip the first column + ObjectTreeItem(), // skip the first row to verify that we continue through nullptr ObjectTreeItem(newGrandChild), ObjectTreeItem() }; @@ -2698,9 +2699,11 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() QCOMPARE(dataChangedSpy.count(), 1); dataChangedSpy.clear(); - // newGrandChild = new Object; - // adapter.at({0, 2, 0}, 0) = newGrandChild; - // newGrandChild->setString("0.2.0"); + newGrandChild = new Object; + adapter.at({0, 2, 0}, 0) = newGrandChild; + QCOMPARE(dataChangedSpy.count(), 1); + newGrandChild->setString("0.2.0"); + QCOMPARE(dataChangedSpy.count(), 2); } QTEST_MAIN(tst_QRangeModelAdapter) diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index 696bcdc07d7..0fc7538c515 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -291,6 +291,8 @@ class tst_QMetaObject : public QObject Q_PROPERTY(int value8 READ value8) Q_PROPERTY(int value9 READ value9 CONSTANT) Q_PROPERTY(int value10 READ value10 FINAL) + Q_PROPERTY(int value11 READ value10 VIRTUAL) + Q_PROPERTY(int value12 READ value10 OVERRIDE) public: enum EnumType { EnumType1 }; @@ -358,6 +360,8 @@ private slots: void propertyNotify(); void propertyConstant(); void propertyFinal(); + void propertyVirtual(); + void propertyOverride(); void metaType(); @@ -2727,6 +2731,32 @@ void tst_QMetaObject::propertyFinal() QVERIFY(!prop.isFinal()); } +void tst_QMetaObject::propertyVirtual() +{ + const QMetaObject *mo = metaObject(); + + QMetaProperty prop = mo->property(mo->indexOfProperty("value11")); + QVERIFY(prop.isValid()); + QVERIFY(prop.isVirtual()); + + prop = mo->property(mo->indexOfProperty("value9")); + QVERIFY(prop.isValid()); + QVERIFY(!prop.isVirtual()); +} + +void tst_QMetaObject::propertyOverride() +{ + const QMetaObject *mo = metaObject(); + + QMetaProperty prop = mo->property(mo->indexOfProperty("value12")); + QVERIFY(prop.isValid()); + QVERIFY(prop.isOverride()); + + prop = mo->property(mo->indexOfProperty("value9")); + QVERIFY(prop.isValid()); + QVERIFY(!prop.isOverride()); +} + void tst_QMetaObject::metaType() { QCOMPARE(QObject::staticMetaObject.metaType(), QMetaType::fromType<QObject>()); diff --git a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp index 3272ffac0ee..18d8b604dff 100644 --- a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp +++ b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp @@ -4,6 +4,9 @@ #include <QVector2D> #include <QVector3D> #include <QVector4D> + +#include <QtCore/qdatastream.h> + #ifdef QVARIANT_H # error "This test requires qvector{2,3,4}d.h to not include qvariant.h" #endif @@ -162,6 +165,8 @@ private slots: void metaTypes(); void structuredBinding(); + void nonFiniteValuesStreamingRoundTrip_data(); + void nonFiniteValuesStreamingRoundTrip(); }; // Test the creation of QVector2D objects in various ways: @@ -2759,6 +2764,78 @@ void tst_QVectorND::structuredBinding() } } +void tst_QVectorND::nonFiniteValuesStreamingRoundTrip_data() +{ + QTest::addColumn<float>("value"); + + constexpr auto inf = std::numeric_limits<float>::infinity(); + constexpr auto NaN = std::numeric_limits<float>::quiet_NaN(); + + QTest::addRow("+∞") << +inf; + QTest::addRow("-∞") << -inf; + QTest::addRow("NaN") << NaN; + +} + +void tst_QVectorND::nonFiniteValuesStreamingRoundTrip() +{ + QFETCH(const float, value); + + const QVector2D i2{value, value}; + const QVector3D i3{value, value, value}; + const QVector4D i4{value, value, value, value}; + + QByteArray buffer; + + { + QDataStream s(&buffer, QIODevice::WriteOnly); + s << i2 << i3 << i4; + QCOMPARE(s.status(), QDataStream::Status::Ok); + } + + { + QVector2D o2 = {0, 0}; + QVector3D o3 = {1, 0, -1}; + QVector4D o4 = {0, 1, 2, 3}; + + QDataStream s(&buffer, QIODevice::ReadOnly); + s >> o2; + QCOMPARE(s.status(), QDataStream::Status::Ok); + s >> o3; + QCOMPARE(s.status(), QDataStream::Status::Ok); + s >> o4; + QCOMPARE(s.status(), QDataStream::Status::Ok); + + constexpr auto convert_to_binary = [](float v) { + uint r; + static_assert(sizeof v == sizeof r); + memcpy(&r, &v, sizeof v); + return r; + }; + + #define CHECK(n, what) \ + do { \ + const auto i ## n ## what = convert_to_binary(i ## n . what ()); \ + const auto o ## n ## what = convert_to_binary(o ## n . what ()); \ + QCOMPARE(i ## n ## what, o ## n ## what); \ + } while (false) + + CHECK(2, x); + CHECK(2, y); + + CHECK(3, x); + CHECK(3, y); + CHECK(3, z); + + CHECK(4, x); + CHECK(4, y); + CHECK(4, z); + CHECK(4, w); + + #undef CHECK + } +} + QTEST_APPLESS_MAIN(tst_QVectorND) #include "tst_qvectornd.moc" diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp index 305f48c95ee..c65f6645d01 100644 --- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp +++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp @@ -3582,6 +3582,22 @@ void tst_QAccessibility::tableTest() tableView->horizontalHeader()->setVisible(false); } + { + QTestAccessibility::clearEvents(); + auto cell0 = table2->cellAt(0, 2); + auto cell1 = table2->cellAt(1, 2); + auto cell2 = table2->cellAt(2, 2); + auto cell3 = table2->cellAt(3, 2); + QAccessibleObjectDestroyedEvent event0(cell0); + QAccessibleObjectDestroyedEvent event1(cell1); + QAccessibleObjectDestroyedEvent event2(cell2); + QAccessibleObjectDestroyedEvent event3(cell3); + tableView->removeColumn(2); + QVERIFY_EVENT(&event0); + QVERIFY_EVENT(&event1); + QVERIFY_EVENT(&event2); + QVERIFY_EVENT(&event3); + } tvHolder.reset(); QVERIFY(!QAccessible::accessibleInterface(id00)); QTestAccessibility::clearEvents(); diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp index fb30d66e6ec..7001d676878 100644 --- a/tests/auto/tools/moc/tst_moc.cpp +++ b/tests/auto/tools/moc/tst_moc.cpp @@ -2576,6 +2576,26 @@ void tst_Moc::warnings_data() << QString() << u"standard input:2:1: error: Parse error at \"NONSENSE\""_s; + QTest::newRow("VIRTUAL FINAL property") + << "class X { \n Q_PROPERTY(int p READ p VIRTUAL FINAL) \n };"_ba << QStringList() << 1 + << QString() + << u"standard input:2:1: error: Issue with property declaration p: " + u"The VIRTUAL cannot be combined with FINAL, as these attributes are mutually exclusive"_s; + + QTest::newRow("FINAL OVERRIDE property") + << "class X { \n Q_PROPERTY(int p READ p FINAL OVERRIDE) \n };"_ba << QStringList() << 1 + << QString() + << u"standard input:2:1: error: Issue with property declaration p: " + u"OVERRIDE is redundant when property is marked FINAL"_s; + + QTest::newRow("VIRTUAL OVERRIDE property") + << "class X { \n Q_PROPERTY(int p READ p VIRTUAL OVERRIDE) \n };"_ba << QStringList() + << 1 << QString() + << u"standard input:2:1: error: Issue with property declaration p: VIRTUAL is " + u"redundant when overriding a property." + u" The OVERRIDE must only be used when actually overriding an existing property;" + u" using it on a new property is an error."_s; + #ifdef Q_OS_UNIX // Limit to Unix because the error message is platform-dependent QTest::newRow("Q_PLUGIN_METADATA: unreadable file") << QByteArray("class X { \n Q_PLUGIN_METADATA(FILE \".\") \n };") |
