diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b01591..5adbebb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.15) project(WIL) include(GNUInstallDirs) diff --git a/cmake/common_build_flags.cmake b/cmake/common_build_flags.cmake index 16da9f5..0e6839b 100644 --- a/cmake/common_build_flags.cmake +++ b/cmake/common_build_flags.cmake @@ -1,5 +1,6 @@ -# E.g. replace_cxx_flag("/W[0-4]", "/W4") +# This is unfortunately still needed to disable exceptions/RTTI since modern CMake still has no builtin support... +# E.g. replace_cxx_flag("/EHsc", "/EHs-c-") macro(replace_cxx_flag pattern text) foreach (flag CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE @@ -10,66 +11,61 @@ macro(replace_cxx_flag pattern text) endforeach() endmacro() -macro(append_cxx_flag text) - foreach (flag - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - - string(APPEND ${flag} " ${text}") - - endforeach() -endmacro() - # Fixup default compiler settings +add_compile_options( + # Be as strict as reasonably possible, since we want to support consumers using strict warning levels + /W4 /WX + ) -# Be as strict as reasonably possible, since we want to support consumers using strict warning levels -replace_cxx_flag("/W[0-4]" "/W4") -append_cxx_flag("/WX") +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options( + # Ignore some pedantic warnings enabled by '-Wextra' + -Wno-missing-field-initializers -# We want to be as conformant as possible, so tell MSVC to not be permissive (note that this has no effect on clang-cl) -append_cxx_flag("/permissive-") + # Ignore some pedantic warnings enabled by '-Wpedantic' + -Wno-language-extension-token + -Wno-c++17-attribute-extensions + -Wno-gnu-zero-variadic-macro-arguments + -Wno-extra-semi -# wistd::function has padding due to alignment. This is expected -append_cxx_flag("/wd4324") + # For tests, we want to be able to test self assignment, so disable this warning + -Wno-self-assign-overloaded + -Wno-self-move -if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") - # Ignore a few Clang warnings. We may want to revisit in the future to see if any of these can/should be removed - append_cxx_flag("-Wno-switch") - append_cxx_flag("-Wno-c++17-compat-mangling") - append_cxx_flag("-Wno-missing-field-initializers") + # clang needs this to enable _InterlockedCompareExchange128 + -mcx16 - # For tests, we want to be able to test self assignment, so disable this warning - append_cxx_flag("-Wno-self-assign-overloaded") - append_cxx_flag("-Wno-self-move") + # We don't want legacy MSVC conformance + -fno-delayed-template-parsing - # C++/WinRT does not declare 'override' in a number of places - append_cxx_flag("-Wno-inconsistent-missing-override") + # NOTE: Windows headers not clean enough for us to realistically attempt to start fixing these errors yet. That + # said, errors that originate from WIL headers may benefit + # -fno-ms-compatibility + # -ferror-limit=999 + # -fmacro-backtrace-limit=0 - # clang-cl does not understand the /permissive- flag (or at least it opts to ignore it). We can achieve similar - # results through the following flags. - append_cxx_flag("-fno-delayed-template-parsing") - - # clang-cl needs this to enable _InterlockedCompareExchange128 - append_cxx_flag("-mcx16") - - # NOTE: Windows headers not clean enough for us to realistically attempt to start fixing these errors yet. That - # said, errors that originate from WIL headers may benefit - # append_cxx_flag("-fno-ms-compatibility") - # append_cxx_flag("-ferror-limit=999") - # append_cxx_flag("-fmacro-backtrace-limit=0") - # -fno-ms-compatibility turns off preprocessor compatability, which currently only works when __VA_OPT__ support is - # available (i.e. >= C++20) - # append_cxx_flag("-Xclang -std=c++2a") + # -fno-ms-compatibility turns off preprocessor compatability, which currently only works when __VA_OPT__ support + # is available (i.e. >= C++20) + # -Xclang -std=c++2a + ) else() - # Flags that are either ignored or unrecognized by clang-cl - # TODO: https://github.com/Microsoft/wil/issues/6 - # append_cxx_flag("/experimental:preprocessor") + add_compile_options( + # We want to be as conformant as possible, so tell MSVC to not be permissive (note that this has no effect on clang-cl) + /permissive- - # CRT headers are not yet /experimental:preprocessor clean, so work around the known issues - # append_cxx_flag("/Wv:18") + # wistd::function has padding due to alignment. This is expected + /wd4324 - append_cxx_flag("/bigobj") + # TODO: https://github.com/Microsoft/wil/issues/6 + # /experimental:preprocessor - # NOTE: Temporary workaround while https://github.com/microsoft/wil/issues/102 is being investigated - append_cxx_flag("/d2FH4-") + # CRT headers are not yet /experimental:preprocessor clean, so work around the known issues + # /Wv:18 + + # Some tests have a LOT of template instantiations + /bigobj + + # NOTE: Temporary workaround while https://github.com/microsoft/wil/issues/102 is being investigated + /d2FH4- + ) endif() diff --git a/include/wil/wistd_functional.h b/include/wil/wistd_functional.h index f734626..7f86ee9 100644 --- a/include/wil/wistd_functional.h +++ b/include/wil/wistd_functional.h @@ -250,21 +250,24 @@ namespace wistd // ("Windows Implementation" std) return _Invoker::__call(__f_, wistd::forward<_ArgTypes>(__arg)...); } - } // __function - - template - class __WI_LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)> - : public __function::__maybe_derive_from_unary_function<_Rp(_ArgTypes...)>, - public __function::__maybe_derive_from_binary_function<_Rp(_ArgTypes...)> - { // 'wistd::function' is most similar to 'inplace_function' in that it _only_ permits holding function objects // that can fit within its internal buffer. Therefore, we expand this size to accommodate space for at least 12 // pointers (__base vtable takes an additional one). - static constexpr size_t __buffer_size = 13 * sizeof(void*); + constexpr const size_t __buffer_size = 13 * sizeof(void*); + } // __function + + // NOTE: The extra 'alignas' here is to work around the x86 compiler bug mentioned in + // https://github.com/microsoft/STL/issues/1533 to force alignment on the stack + template + class __WI_LIBCPP_TEMPLATE_VIS __WI_ALIGNAS(typename aligned_storage<__function::__buffer_size>::type) + function<_Rp(_ArgTypes...)> + : public __function::__maybe_derive_from_unary_function<_Rp(_ArgTypes...)>, + public __function::__maybe_derive_from_binary_function<_Rp(_ArgTypes...)> + { using __base = __function::__base<_Rp(_ArgTypes...)>; __WI_LIBCPP_SUPPRESS_NONINIT_ANALYSIS - typename aligned_storage<__buffer_size>::type __buf_; + typename aligned_storage<__function::__buffer_size>::type __buf_; __base* __f_; __WI_LIBCPP_NO_CFI static __base *__as_base(void *p) { diff --git a/include/wil/wistd_memory.h b/include/wil/wistd_memory.h index 3db69c4..0a5be28 100644 --- a/include/wil/wistd_memory.h +++ b/include/wil/wistd_memory.h @@ -149,8 +149,8 @@ namespace wistd // ("Windows Implementation" std) struct __second_tag {}; template - class __compressed_pair : private __compressed_pair_elem<_T1, 0>, - private __compressed_pair_elem<_T2, 1> { + class __declspec(empty_bases) __compressed_pair : private __compressed_pair_elem<_T1, 0>, + private __compressed_pair_elem<_T2, 1> { using _Base1 = __compressed_pair_elem<_T1, 0>; using _Base2 = __compressed_pair_elem<_T2, 1>; diff --git a/scripts/azure-pipelines.yml b/scripts/azure-pipelines.yml index 5b89d3e..8e0a4f7 100644 --- a/scripts/azure-pipelines.yml +++ b/scripts/azure-pipelines.yml @@ -18,7 +18,7 @@ jobs: displayName: 'Install Clang' - script: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat" + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat" if %ERRORLEVEL% NEQ 0 goto :eof call scripts\init_all.cmd --fast @@ -37,5 +37,9 @@ jobs: call scripts\build_all.cmd displayName: 'Build x64' - - script: call scripts\runtests.cmd ~[LocalOnly] + # NOTE: We run the tests in the 32-bit cross-tools window out of convenience as this adds all necessary directories to + # the PATH that are necessary for finding the ASan/UBSan DLLs + - script: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat"" + call scripts\runtests.cmd ~[LocalOnly] displayName: 'Run Tests' diff --git a/scripts/build_all.cmd b/scripts/build_all.cmd index 32d88f4..3b94896 100644 --- a/scripts/build_all.cmd +++ b/scripts/build_all.cmd @@ -1,4 +1,5 @@ @echo off +setlocal setlocal EnableDelayedExpansion set BUILD_ROOT=%~dp0\..\build @@ -15,23 +16,15 @@ if "%Platform%"=="x64" ( exit /B 1 ) -call :build clang debug -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :build clang release -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :build clang relwithdebinfo -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :build clang minsizerel -if %ERRORLEVEL% NEQ 0 ( goto :eof ) +set COMPILERS=clang msvc +set BUILD_TYPES=debug release relwithdebinfo minsizerel -call :build msvc debug -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :build msvc release -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :build msvc relwithdebinfo -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :build msvc minsizerel -if %ERRORLEVEL% NEQ 0 ( goto :eof ) +for %%c in (%COMPILERS%) do ( + for %%b in (%BUILD_TYPES%) do ( + call :build %%c %%b + if !ERRORLEVEL! NEQ 0 ( goto :eof ) + ) +) echo All build completed successfully! @@ -47,5 +40,6 @@ if not exist %BUILD_DIR% ( pushd %BUILD_DIR% echo Building from %CD% ninja +set EXIT_CODE=%ERRORLEVEL% popd -goto :eof +exit /B %EXIT_CODE% diff --git a/scripts/init.cmd b/scripts/init.cmd index 53e41a3..fc2ba01 100644 --- a/scripts/init.cmd +++ b/scripts/init.cmd @@ -10,8 +10,8 @@ goto :init :usage echo USAGE: echo init.cmd [--help] [-c^|--compiler ^] [-g^|--generator ^] - echo [-b^|--build-type ^] [-l^|--linker link^|lld-link] - echo [-v^|--version X.Y.Z] [--cppwinrt ^] [--fast] + echo [-b^|--build-type ^] [-v^|--version X.Y.Z] + echo [--cppwinrt ^] [--fast] echo. echo ARGUMENTS echo -c^|--compiler Controls the compiler used, either 'clang' (the default) or 'msvc' @@ -31,7 +31,6 @@ goto :init set COMPILER= set GENERATOR= set BUILD_TYPE= - set LINKER= set CMAKE_ARGS= set BITNESS= set VERSION= @@ -90,21 +89,6 @@ goto :init goto :parse ) - set LINKER_SET=0 - if /I "%~1"=="-l" set LINKER_SET=1 - if /I "%~1"=="--linker" set LINKER_SET=1 - if %LINKER_SET%==1 ( - if "%LINKER%" NEQ "" echo ERROR: Linker already specified & call :usage & exit /B 1 - - if /I "%~2"=="link" set LINKER=link - if /I "%~2"=="lld-link" set LINKER=lld-link - if "!LINKER!"=="" echo ERROR: Unrecognized/missing linker %~2 & call :usage & exit /B 1 - - shift - shift - goto :parse - ) - set VERSION_SET=0 if /I "%~1"=="-v" set VERSION_SET=1 if /I "%~1"=="--version" set VERSION_SET=1 @@ -145,9 +129,6 @@ goto :init :: Check for conflicting arguments if "%GENERATOR%"=="msbuild" ( if "%COMPILER%"=="clang" echo ERROR: Cannot use Clang with MSBuild & exit /B 1 - - :: While CMake won't give an error, specifying the linker won't actually have any effect with the VS generator - if "%LINKER%"=="lld-link" echo ERROR: Cannot use lld-link with MSBuild & exit /B 1 ) :: Select defaults @@ -158,8 +139,6 @@ goto :init if "%BUILD_TYPE%"=="" set BUILD_TYPE=debug - if "%LINKER%"=="" set LINKER=link - :: Formulate CMake arguments if %GENERATOR%==ninja set CMAKE_ARGS=%CMAKE_ARGS% -G Ninja @@ -180,10 +159,6 @@ goto :init set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_SYSTEM_VERSION=10.0 ) - if %LINKER%==lld-link ( - set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_LINKER=lld-link - ) - if "%VERSION%" NEQ "" set CMAKE_ARGS=%CMAKE_ARGS% -DWIL_BUILD_VERSION=%VERSION% if "%CPPWINRT_VERSION%" NEQ "" set CMAKE_ARGS=%CMAKE_ARGS% -DCPPWINRT_VERSION=%CPPWINRT_VERSION% @@ -208,7 +183,6 @@ goto :init :: Run CMake pushd %BUILD_DIR% echo Using compiler....... %COMPILER% - echo Using linker......... %LINKER% echo Using architecture... %Platform% echo Using build type..... %BUILD_TYPE% echo Using build root..... %CD% diff --git a/scripts/init_all.cmd b/scripts/init_all.cmd index 9fc1417..a179755 100644 --- a/scripts/init_all.cmd +++ b/scripts/init_all.cmd @@ -1,13 +1,14 @@ @echo off +setlocal +setlocal EnableDelayedExpansion :: NOTE: Architecture is picked up from the command window, so we can't control that here :( +set COMPILERS=clang msvc +set BUILD_TYPES=debug relwithdebinfo -call %~dp0\init.cmd -c clang -g ninja -b debug %* -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call %~dp0\init.cmd -c clang -g ninja -b relwithdebinfo %* -if %ERRORLEVEL% NEQ 0 ( goto :eof ) - -call %~dp0\init.cmd -c msvc -g ninja -b debug %* -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call %~dp0\init.cmd -c msvc -g ninja -b relwithdebinfo %* -if %ERRORLEVEL% NEQ 0 ( goto :eof ) +for %%c in (%COMPILERS%) do ( + for %%b in (%BUILD_TYPES%) do ( + call %~dp0\init.cmd -c %%c -g ninja -b %%b %* + if !ERRORLEVEL! NEQ 0 ( goto :eof ) + ) +) diff --git a/scripts/runtests.cmd b/scripts/runtests.cmd index 9ef0dfa..c0d3002 100644 --- a/scripts/runtests.cmd +++ b/scripts/runtests.cmd @@ -7,41 +7,18 @@ set TEST_ARGS=%* set BUILD_ROOT=%~dp0\..\build :: Unlike building, we don't need to limit ourselves to the Platform of the command window -call :execute_tests clang64debug -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests clang64release -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests clang64relwithdebinfo -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests clang64minsizerel -if %ERRORLEVEL% NEQ 0 ( goto :eof ) +set COMPILERS=clang msvc +set ARCHITECTURES=32 64 +set BUILD_TYPES=debug release relwithdebinfo minsizerel -call :execute_tests clang32debug -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests clang32release -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests clang32relwithdebinfo -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests clang32minsizerel -if %ERRORLEVEL% NEQ 0 ( goto :eof ) - -call :execute_tests msvc64debug -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests msvc64release -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests msvc64relwithdebinfo -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests msvc64minsizerel -if %ERRORLEVEL% NEQ 0 ( goto :eof ) - -call :execute_tests msvc32debug -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests msvc32release -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests msvc32relwithdebinfo -if %ERRORLEVEL% NEQ 0 ( goto :eof ) -call :execute_tests msvc32minsizerel -if %ERRORLEVEL% NEQ 0 ( goto :eof ) +for %%c in (%COMPILERS%) do ( + for %%a in (%ARCHITECTURES%) do ( + for %%b in (%BUILD_TYPES%) do ( + call :execute_tests %%c%%a%%b + if !ERRORLEVEL! NEQ 0 ( goto :eof ) + ) + ) +) goto :eof @@ -52,18 +29,24 @@ if not exist %BUILD_DIR% ( goto :eof ) pushd %BUILD_DIR% echo Running tests from %CD% call :execute_test app witest.app.exe -if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) call :execute_test cpplatest witest.cpplatest.exe -if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) call :execute_test noexcept witest.noexcept.exe -if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) call :execute_test normal witest.exe -if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) +call :execute_test sanitize-address witest.asan.exe +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) +call :execute_test sanitize-undefined-behavior witest.ubsan.exe +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) call :execute_test win7 witest.win7.exe -if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) -popd +if %ERRORLEVEL% NEQ 0 ( goto :execute_tests_done ) -goto :eof +:execute_tests_done +set EXIT_CODE=%ERRORLEVEL% +popd +exit /B %EXIT_CODE% :execute_test if not exist tests\%1\%2 ( goto :eof ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 82caa31..214b708 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ + include(${PROJECT_SOURCE_DIR}/cmake/common_build_flags.cmake) -cmake_minimum_required(VERSION 3.11) # All projects need to reference the WIL headers include_directories(${PROJECT_SOURCE_DIR}/include) @@ -38,6 +38,14 @@ if (${FAST_BUILD}) add_definitions(-DCATCH_CONFIG_FAST_COMPILE -DWIL_FAST_BUILD) endif() +# For some unknown reason, 'RelWithDebInfo' compiles with '/Ob1' as opposed to '/Ob2' which prevents inlining of +# functions not marked 'inline'. The reason we prefer 'RelWithDebInfo' over 'Release' is to get debug info, so manually +# revert to the desired (and default) inlining behavior as that exercises more interesting code paths +if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") + # TODO: This is currently blocked by an apparent Clang bug: https://github.com/llvm/llvm-project/issues/59690 + # replace_cxx_flag("/Ob1" "/Ob2") +endif() + set(COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CommonTests.cpp @@ -58,3 +66,32 @@ add_subdirectory(cpplatest) add_subdirectory(noexcept) add_subdirectory(normal) add_subdirectory(win7) + +set(DEBUG_BUILD FALSE) +set(HAS_DEBUG_INFO FALSE) + +if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(DEBUG_BUILD TRUE) + set(HAS_DEBUG_INFO TRUE) +elseif(${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") + set(HAS_DEBUG_INFO TRUE) +endif() + +set(ASAN_AVAILABLE FALSE) +set(UBSAN_AVAILABLE FALSE) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Address Sanitizer is available for all architectures and build types, but warns/errors if debug info is not enabled + set(ASAN_AVAILABLE ${HAS_DEBUG_INFO}) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Address Sanitizer is not available with debug libraries + set(ASAN_AVAILABLE NOT ${DEBUG_BUILD}) + set(UBSAN_AVAILABLE NOT ${DEBUG_BUILD}) +endif() + +if (${ASAN_AVAILABLE}) + add_subdirectory(sanitize-address) +endif() + +if (${UBSAN_AVAILABLE}) + add_subdirectory(sanitize-undefined-behavior) +endif() diff --git a/tests/CommonTests.cpp b/tests/CommonTests.cpp index d8eb4b4..7027a79 100644 --- a/tests/CommonTests.cpp +++ b/tests/CommonTests.cpp @@ -159,7 +159,7 @@ enum class EClassTest }; DEFINE_ENUM_FLAG_OPERATORS(EClassTest); -enum ERawTest +enum ERawTest : unsigned int { ER_None = 0x0, ER_One = 0x1, diff --git a/tests/WinRTTests.cpp b/tests/WinRTTests.cpp index 543f9a7..4e1ecf2 100644 --- a/tests/WinRTTests.cpp +++ b/tests/WinRTTests.cpp @@ -240,8 +240,8 @@ void DoHStringDifferentValueComparisonTest(const wchar_t (&lhs)[LhsSize], const DoHStringComparisonTest(lhsUniqueStr, rhsUniqueStr, 1); #ifdef WIL_ENABLE_EXCEPTIONS - std::wstring lhsWstr(lhs, 7); - std::wstring rhsWstr(rhs, 7); + std::wstring lhsWstr(lhs); + std::wstring rhsWstr(rhs); DoHStringComparisonTest(lhsWstr, rhsWstr, 1); DoHStringComparisonTest(lhsWstr, rhs, 1); DoHStringComparisonTest(lhsWstr, rhsNonConstArray, 1); @@ -710,7 +710,7 @@ TEST_CASE("WinRTTests::VectorToVectorTest", "[winrt][to_vector]") ComPtr oneItem; THROW_IF_FAILED(ints->GetAt(i, &oneItem)); REQUIRE(cast_to(vec[i]) == cast_to(oneItem)); - } + } #endif } diff --git a/tests/app/CMakeLists.txt b/tests/app/CMakeLists.txt index 3e13974..97a412e 100644 --- a/tests/app/CMakeLists.txt +++ b/tests/app/CMakeLists.txt @@ -1,10 +1,11 @@ -project(witest.app) add_executable(witest.app) -add_definitions(-DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP) +target_compile_definitions(witest.app PRIVATE + -DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP + ) -target_sources(witest.app PUBLIC +target_sources(witest.app PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index fb6700d..7ae64d5 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -1,12 +1,11 @@ +add_executable(witest.cpplatest) + # Compilers often don't use the latest C++ standard as the default. Periodically update this value (possibly conditioned # on compiler) as new standards are ratified/support is available -set(CMAKE_CXX_STANDARD 20) +target_compile_features(witest.cpplatest PRIVATE cxx_std_20) -project(witest.cpplatest) -add_executable(witest.cpplatest) - -if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # Clang is not compatible with the experimental coroutine header, so temporarily disable some headers until full # C++20 support is available set(COROUTINE_SOURCES) @@ -15,7 +14,7 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/../ComApartmentVariableTests.cpp) endif() -target_sources(witest.cpplatest PUBLIC +target_sources(witest.cpplatest PRIVATE ${COMMON_SOURCES} ${COROUTINE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTTests.cpp diff --git a/tests/main.cpp b/tests/main.cpp index 37b9d7c..7a96db4 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -6,3 +6,15 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" + +#if WITEST_ADDRESS_SANITIZER +extern "C" __declspec(dllexport) +const char* __asan_default_options() +{ + return + // Tests validate OOM, so this is expected + "allocator_may_return_null=1" + // Some structs in Windows have dynamic size where we over-allocate for extra data past the end + ":new_delete_type_mismatch=0"; +} +#endif diff --git a/tests/noexcept/CMakeLists.txt b/tests/noexcept/CMakeLists.txt index 6362991..d0d364b 100644 --- a/tests/noexcept/CMakeLists.txt +++ b/tests/noexcept/CMakeLists.txt @@ -1,16 +1,19 @@ -project(witest.noexcept) add_executable(witest.noexcept) # Turn off exceptions for this test -replace_cxx_flag("/EHsc" "") -add_definitions(-DCATCH_CONFIG_DISABLE_EXCEPTIONS) +replace_cxx_flag("/EHsc" "/EHs-c-") +target_compile_definitions(witest.noexcept PRIVATE + -DCATCH_CONFIG_DISABLE_EXCEPTIONS + ) # Catch2 has a no exceptions mode (configured above), however still includes STL headers which contain try...catch # statements... Thankfully MSVC just gives us a warning that we can disable -append_cxx_flag("/wd4530") +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_compile_options(witest.noexcept PRIVATE /wd4530) +endif() -target_sources(witest.noexcept PUBLIC +target_sources(witest.noexcept PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/normal/CMakeLists.txt b/tests/normal/CMakeLists.txt index 6abcda2..810deb6 100644 --- a/tests/normal/CMakeLists.txt +++ b/tests/normal/CMakeLists.txt @@ -1,8 +1,7 @@ -project(witest) add_executable(witest) -target_sources(witest PUBLIC +target_sources(witest PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp diff --git a/tests/sanitize-address/CMakeLists.txt b/tests/sanitize-address/CMakeLists.txt new file mode 100644 index 0000000..73a4231 --- /dev/null +++ b/tests/sanitize-address/CMakeLists.txt @@ -0,0 +1,49 @@ + +add_executable(witest.asan) + +target_compile_options(witest.asan PRIVATE -fsanitize=address) + +target_compile_definitions(witest.asan PRIVATE + -DCATCH_CONFIG_NO_WINDOWS_SEH # ASAN relies on first chance AVs + -DWITEST_ADDRESS_SANITIZER # To conditionally enable/disable code + ) + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_definitions(witest.asan PRIVATE + # Not compatible with using lld-link + -D_DISABLE_VECTOR_ANNOTATION + # See below; not compatible with exceptions + -DCATCH_CONFIG_DISABLE_EXCEPTIONS + ) + + # Clang ASan on Windows has issues with exceptions: https://github.com/google/sanitizers/issues/749 + replace_cxx_flag("/EHsc" "/EHs-c-") + + if ($ENV{Platform} STREQUAL "x86") + target_link_libraries(witest.asan PRIVATE + clang_rt.asan_dynamic-i386.lib + clang_rt.asan_dynamic_runtime_thunk-i386.lib + ) + else() + target_link_libraries(witest.asan PRIVATE + clang_rt.asan_dynamic-x86_64.lib + clang_rt.asan_dynamic_runtime_thunk-x86_64.lib + ) + endif() +else() + # Using exceptions, so we can compile the STL tests + set(EXTRA_SOURCES + ${EXTRA_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp + ) +endif() + +target_sources(witest.asan PUBLIC + ${COMMON_SOURCES} + ${EXTRA_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../WinVerifyTrustTest.cpp + ) diff --git a/tests/sanitize-undefined-behavior/CMakeLists.txt b/tests/sanitize-undefined-behavior/CMakeLists.txt new file mode 100644 index 0000000..6dd9745 --- /dev/null +++ b/tests/sanitize-undefined-behavior/CMakeLists.txt @@ -0,0 +1,25 @@ + +add_executable(witest.ubsan) + +target_compile_options(witest.ubsan PRIVATE + -fsanitize=undefined + -fno-sanitize-recover=undefined # So we get test failures + ) + +target_compile_definitions(witest.ubsan PRIVATE + -DWITEST_UB_SANITIZER # To conditionally enable/disable code + ) + +# UBSan libraries were built assuming static linking +set_property(TARGET witest.ubsan + PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") + +target_sources(witest.ubsan PUBLIC + ${COMMON_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../WinVerifyTrustTest.cpp + ) diff --git a/tests/win7/CMakeLists.txt b/tests/win7/CMakeLists.txt index 221837d..3f9c2c8 100644 --- a/tests/win7/CMakeLists.txt +++ b/tests/win7/CMakeLists.txt @@ -1,10 +1,11 @@ -project(witest.win7) add_executable(witest.win7) -add_definitions("-D_WIN32_WINNT=0x0601") +target_compile_definitions(witest.win7 PRIVATE + -D_WIN32_WINNT=0x0601 + ) -target_sources(witest.win7 PUBLIC +target_sources(witest.win7 PRIVATE ${COMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp diff --git a/tests/workarounds/wrl/wrl/implements.h b/tests/workarounds/wrl/wrl/implements.h index 01ea69a..da6300f 100644 --- a/tests/workarounds/wrl/wrl/implements.h +++ b/tests/workarounds/wrl/wrl/implements.h @@ -12,14 +12,6 @@ #pragma once #endif // _MSC_VER -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpragma-pack" -#pragma clang diagnostic ignored "-Wunused-value" -#pragma clang diagnostic ignored "-Wmicrosoft-sealed" -#pragma clang diagnostic ignored "-Winaccessible-base" -#endif - #pragma region includes #include @@ -599,6 +591,8 @@ struct VerifyInheritanceHelper } // namespace Details +// note: Due to potential shutdown ordering issues, the results of GetModuleBase +// should always be checked for null on reference counting and cleanup operations. inline Details::ModuleBase* GetModuleBase() throw() { return Details::ModuleBase::module_; @@ -1523,6 +1517,8 @@ private: #define UnknownInterlockedCompareExchangePointer InterlockedCompareExchangePointer #define UnknownInterlockedCompareExchangePointerForIncrement InterlockedCompareExchangePointer #define UnknownInterlockedCompareExchangePointerForRelease InterlockedCompareExchangePointer +#define UnknownInterlockedCompareExchangeForIncrement InterlockedCompareExchange +#define UnknownInterlockedCompareExchangeForRelease InterlockedCompareExchange #elif defined(_ARM_) @@ -1532,6 +1528,8 @@ private: #define UnknownInterlockedCompareExchangePointer InterlockedCompareExchangePointer #define UnknownInterlockedCompareExchangePointerForIncrement InterlockedCompareExchangePointerNoFence #define UnknownInterlockedCompareExchangePointerForRelease InterlockedCompareExchangePointerRelease +#define UnknownInterlockedCompareExchangeForIncrement InterlockedCompareExchangeNoFence +#define UnknownInterlockedCompareExchangeForRelease InterlockedCompareExchangeRelease #elif defined(_ARM64_) @@ -1541,6 +1539,8 @@ private: #define UnknownInterlockedCompareExchangePointer InterlockedCompareExchangePointer #define UnknownInterlockedCompareExchangePointerForIncrement InterlockedCompareExchangePointerNoFence #define UnknownInterlockedCompareExchangePointerForRelease InterlockedCompareExchangePointerRelease +#define UnknownInterlockedCompareExchangeForIncrement InterlockedCompareExchangeNoFence +#define UnknownInterlockedCompareExchangeForRelease InterlockedCompareExchangeRelease #else @@ -1562,6 +1562,37 @@ class __declspec(novtable) RuntimeClassImpl; // PREFast cannot see through template instantiation for AsIID() #pragma warning(disable: 6388) +// Reference counting functions that check overflow. If overflow is detected, ref count value will stop at LONG_MAX, and the object being +// reference-counted will be leaked. +inline unsigned long SafeUnknownIncrementReference(long volatile &refcount) throw() +{ + long oldValue = refcount; + while (oldValue != LONG_MAX && (UnknownInterlockedCompareExchangeForIncrement(&refcount, oldValue + 1, oldValue) != oldValue)) + { + oldValue = refcount; + } + + if (oldValue != LONG_MAX) + { + return oldValue + 1; + } + else + { + return LONG_MAX; + } +} + +inline unsigned long SafeUnknownDecrementReference(long volatile &refcount) throw() +{ + long oldValue = refcount; + while (oldValue != LONG_MAX && (UnknownInterlockedCompareExchangeForRelease(&refcount, oldValue - 1, oldValue) != oldValue)) + { + oldValue = refcount; + } + + return oldValue - 1; +} + template class __declspec(novtable) RuntimeClassImpl : public Details::AdjustImplements::Type, @@ -1624,7 +1655,7 @@ protected: #ifdef _PERF_COUNTERS IncrementAddRefCount(); #endif - return UnknownIncrementReference(&refcount_); + return SafeUnknownIncrementReference(refcount_); } unsigned long InternalRelease() throw() @@ -1634,7 +1665,7 @@ protected: #endif // A release fence is required to ensure all guarded memory accesses are // complete before any thread can begin destroying the object. - unsigned long newValue = UnknownDecrementReference(&refcount_); + unsigned long newValue = SafeUnknownDecrementReference(refcount_); if (newValue == 0) { // An acquire fence is required before object destruction to ensure @@ -1786,7 +1817,7 @@ protected: #ifdef _PERF_COUNTERS IncrementAddRefCount(); #endif - return UnknownIncrementReference(&refcount_); + return SafeUnknownIncrementReference(refcount_); } unsigned long InternalRelease() throw() @@ -1796,7 +1827,7 @@ protected: #endif // A release fence is required to ensure all guarded memory accesses are // complete before any thread can begin destroying the object. - unsigned long newValue = UnknownDecrementReference(&refcount_); + unsigned long newValue = SafeUnknownDecrementReference(refcount_); if (newValue == 0) { // An acquire fence is required before object destruction to ensure @@ -1828,14 +1859,14 @@ public: unsigned long IncrementStrongReference() throw() { - return UnknownIncrementReference(&strongRefCount_); + return SafeUnknownIncrementReference(strongRefCount_); } unsigned long DecrementStrongReference() throw() { // A release fence is required to ensure all guarded memory accesses are // complete before any thread can begin destroying the object. - unsigned long newValue = UnknownDecrementReference(&strongRefCount_); + unsigned long newValue = SafeUnknownDecrementReference(strongRefCount_); if (newValue == 0) { // An acquire fence is required before object destruction to ensure @@ -2089,7 +2120,7 @@ inline INT_PTR EncodeWeakReferencePointer(Microsoft::WRL::Details::WeakReference inline Microsoft::WRL::Details::WeakReferenceImpl* DecodeWeakReferencePointer(INT_PTR value) { - return reinterpret_cast(value << 1); + return reinterpret_cast(static_cast(value) << 1); } #pragma warning(pop) // C6388 @@ -2134,6 +2165,7 @@ class RuntimeClass, RuntimeClassFlagsT, impl public RuntimeClassImpl { protected: +#pragma warning(suppress: 6101) // Function only used internally and the value of 'ppvObject' is only used if *handled is true HRESULT CustomQueryInterface(REFIID /*riid*/, _Outptr_result_nullonfailure_ void** /*ppvObject*/, _Out_ bool *handled) { *handled = false; @@ -2153,6 +2185,7 @@ class RuntimeClass : RuntimeClass(const RuntimeClass&); RuntimeClass& operator=(const RuntimeClass&); protected: +#pragma warning(suppress: 6101) // Function only used internally and the value of 'ppvObject' is only used if *handled is true HRESULT CustomQueryInterface(REFIID /*riid*/, _Outptr_result_nullonfailure_ void** /*ppvObject*/, _Out_ bool *handled) { *handled = false; @@ -2177,6 +2210,7 @@ class RuntimeClass, TInterfaces...> : RuntimeClass(const RuntimeClass&); RuntimeClass& operator=(const RuntimeClass&); protected: +#pragma warning(suppress: 6101) // Function only used internally and the value of 'ppvObject' is only used if *handled is true HRESULT CustomQueryInterface(REFIID /*riid*/, _Outptr_result_nullonfailure_ void** /*ppvObject*/, _Out_ bool *handled) { *handled = false; @@ -2196,8 +2230,8 @@ public: namespace Details { -//Weak reference implementation - class WeakReferenceImpl sealed: + // Weak reference implementation + class WeakReferenceImpl final : public ::Microsoft::WRL::RuntimeClass, IWeakReference>, public StrongReference { @@ -2289,6 +2323,11 @@ unsigned long RuntimeClassImpl(currentValue.refCount) == LONG_MAX) + { + return LONG_MAX; + } + UINT_PTR updateValue = currentValue.refCount + 1; #ifdef __WRL_UNITTEST__ @@ -2324,6 +2363,11 @@ unsigned long RuntimeClassImpl(currentValue.refCount) == LONG_MAX) + { + return LONG_MAX - 1; + } + UINT_PTR updateValue = currentValue.refCount - 1; #ifdef __WRL_UNITTEST__ @@ -2432,8 +2476,13 @@ public: // Allocate memory with operator new(size, nothrow) only // This will allow developer to override one operator only // to enable different memory allocation model - buffer_ = (char*) (operator new (sizeof(T), std::nothrow)); - return buffer_; +#ifdef __cpp_aligned_new + if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + return buffer_ = (char*) operator new (sizeof(T), static_cast(alignof(T)), ::std::nothrow); + } +#endif // /std:c++17 or later + return buffer_ = (char*) operator new (sizeof(T), ::std::nothrow); } void Detach() throw() @@ -2526,7 +2575,7 @@ namespace Details { \ return trustLevel; \ } \ - STDMETHOD(GetRuntimeClassName)(_Out_ HSTRING* runtimeName) \ + STDMETHOD(GetRuntimeClassName)(_Out_ HSTRING* runtimeName) override \ { \ *runtimeName = nullptr; \ HRESULT hr = S_OK; \ @@ -2537,7 +2586,7 @@ namespace Details } \ return hr; \ } \ - STDMETHOD(GetTrustLevel)(_Out_ ::TrustLevel* trustLvl) \ + STDMETHOD(GetTrustLevel)(_Out_ ::TrustLevel* trustLvl) override \ { \ *trustLvl = trustLevel; \ return S_OK; \ @@ -2545,22 +2594,22 @@ namespace Details STDMETHOD(GetIids)(_Out_ ULONG *iidCount, \ _When_(*iidCount == 0, _At_(*iids, _Post_null_)) \ _When_(*iidCount > 0, _At_(*iids, _Post_notnull_)) \ - _Result_nullonfailure_ IID **iids) \ + _Result_nullonfailure_ IID **iids) override \ { \ return RuntimeClassT::GetIids(iidCount, iids); \ } \ - STDMETHOD(QueryInterface)(REFIID riid, _Outptr_result_nullonfailure_ void **ppvObject) \ + STDMETHOD(QueryInterface)(REFIID riid, _Outptr_result_nullonfailure_ void **ppvObject) override \ { \ bool handled = false; \ HRESULT hr = this->CustomQueryInterface(riid, ppvObject, &handled); \ if (FAILED(hr) || handled) return hr; \ return RuntimeClassT::QueryInterface(riid, ppvObject); \ } \ - STDMETHOD_(ULONG, Release)() \ + STDMETHOD_(ULONG, Release)() override \ { \ return RuntimeClassT::Release(); \ } \ - STDMETHOD_(ULONG, AddRef)() \ + STDMETHOD_(ULONG, AddRef)() override \ { \ return RuntimeClassT::AddRef(); \ } \ @@ -2648,8 +2697,4 @@ namespace Details // Restore packing #include -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - #endif // _WRL_IMPLEMENTS_H_