diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b171e24f1d..85caf27626b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ if("${Boost_VERSION}" VERSION_LESS "${VCLIBS_MIN_BOOST_VERSION}") endif() option(BUILD_TESTING "Enable testing" ON) +option(STL_IS_KERNEL "Enable kernel testing" OFF) set(VCLIBS_SUFFIX "_oss" CACHE STRING "suffix for built DLL names to avoid conflicts with distributed DLLs") if(NOT DEFINED VCLIBS_TARGET_ARCHITECTURE) diff --git a/azure-devops/cmake-configure-build.yml b/azure-devops/cmake-configure-build.yml index 02e1cd93fa4..5d2cb813296 100644 --- a/azure-devops/cmake-configure-build.yml +++ b/azure-devops/cmake-configure-build.yml @@ -38,7 +38,11 @@ steps: -DCMAKE_CXX_COMPILER=cl ^ -DCMAKE_BUILD_TYPE=Release ^ -DLIT_FLAGS=$(litFlags) ^ - -DCMAKE_CXX_FLAGS=/analyze:autolog- ^ + -DCMAKE_CXX_FLAGS="/analyze:autolog-" ^ + -DSTL_IS_KERNEL=ON ^ + -DWDK_INCLUDE_DIRECTORY="C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0" ^ + -DWDK_LIB_DIRECTORY="C:/Program Files (x86)/Windows Kits/10/Lib/10.0.19041.0" ^ + -DWDK_BIN_DIRECTORY="C:/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0" ^ -S $(Build.SourcesDirectory) -B $(${{ parameters.buildOutputLocationVar }}) displayName: 'Configure the STL' timeoutInMinutes: 2 diff --git a/azure-devops/cross-build.yml b/azure-devops/cross-build.yml index 6cbcb08adb6..1cb7a20953b 100644 --- a/azure-devops/cross-build.yml +++ b/azure-devops/cross-build.yml @@ -21,7 +21,7 @@ jobs: fixedFlags: '--timeout=240;--shuffle' parallelismFlag: '-j$(testParallelism)' xmlOutputFlag: '--xunit-xml-output=$(${{ parameters.buildOutputLocationVar }})/test-results.xml' - shardFlags: '--num-shards=$(System.TotalJobsInPhase);--run-shard=$(System.JobPositionInPhase)' + shardFlags: '-vv;--num-shards=$(System.TotalJobsInPhase);--run-shard=$(System.JobPositionInPhase)' litFlags: '$(fixedFlags);$(parallelismFlag);$(xmlOutputFlag);$(shardFlags)' strategy: parallel: ${{ parameters.numShards }} diff --git a/azure-devops/native-build-test.yml b/azure-devops/native-build-test.yml index 17fa03732ae..0bbe665f651 100644 --- a/azure-devops/native-build-test.yml +++ b/azure-devops/native-build-test.yml @@ -18,7 +18,7 @@ jobs: fixedFlags: '--timeout=240;--shuffle' parallelismFlag: '-j$(testParallelism)' xmlOutputFlag: '--xunit-xml-output=$(${{ parameters.buildOutputLocationVar }})/test-results.xml' - shardFlags: '--num-shards=$(System.TotalJobsInPhase);--run-shard=$(System.JobPositionInPhase)' + shardFlags: '-vv;--num-shards=$(System.TotalJobsInPhase);--run-shard=$(System.JobPositionInPhase)' litFlags: '$(fixedFlags);$(parallelismFlag);$(xmlOutputFlag);$(shardFlags)' strategy: parallel: ${{ parameters.numShards }} diff --git a/stl/inc/ranges b/stl/inc/ranges index eb1b986d1ae..677feedf0c5 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -2489,6 +2489,49 @@ namespace ranges { }; inline constexpr _Drop_fn drop; + + // VARIABLE views::counted + class _Counted_fn { + private: + enum class _St { _Span, _Subrange, _Subrange_counted }; + + template + _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { + _STL_INTERNAL_STATIC_ASSERT(input_or_output_iterator<_It>); + if constexpr (contiguous_iterator<_It>) { + return {_St::_Span, noexcept(span{_STD to_address(_STD declval<_It>()), iter_difference_t<_It>{}})}; + } else if constexpr (random_access_iterator<_It>) { + return {_St::_Subrange, + noexcept(subrange{_STD declval<_It>(), _STD declval<_It>() + iter_difference_t<_It>{}})}; + } else { + return {_St::_Subrange_counted, + noexcept(subrange{ + counted_iterator{_STD declval<_It>(), iter_difference_t<_It>{}}, default_sentinel})}; + } + } + + template + static constexpr _Choice_t<_St> _Choice = _Choose<_It>(); + + public: + // clang-format off + template + requires input_or_output_iterator> + _NODISCARD constexpr auto operator()(_It&& _First, const iter_difference_t> _Count) const + noexcept(_Choice>._No_throw) { + // clang-format on + _STL_ASSERT(_Count >= 0, "The size passed to views::counted must be non-negative"); + if constexpr (_Choice>._Strategy == _St::_Span) { + return span{_STD to_address(_STD forward<_It>(_First)), static_cast(_Count)}; + } else if constexpr (_Choice>._Strategy == _St::_Subrange) { + return subrange{_First, _First + _Count}; + } else if constexpr (_Choice>._Strategy == _St::_Subrange_counted) { + return subrange{counted_iterator{_STD forward<_It>(_First), _Count}, default_sentinel}; + } + } + }; + + inline constexpr _Counted_fn counted; } // namespace views // CLASS TEMPLATE ranges::drop_while_view @@ -4088,6 +4131,116 @@ namespace ranges { inline constexpr auto keys = elements<0>; inline constexpr auto values = elements<1>; } // namespace views + + // CLASS TEMPLATE ranges::common_view + // clang-format off + template + requires (!common_range<_Vw> && copyable>) + class common_view : public view_interface> { + // clang-format on + private: + /* [[no_unique_address]] */ _Vw _Base{}; + + public: + common_view() = default; + constexpr explicit common_view(_Vw _Base_) noexcept(is_nothrow_move_constructible_v<_Vw>) // strengthened + : _Base(_STD move(_Base_)) {} + // converting constructor template omitted per LWG-3405 + + _NODISCARD constexpr _Vw base() const& noexcept( + is_nothrow_copy_constructible_v<_Vw>) /* strengthened */ requires copy_constructible<_Vw> { + return _Base; + } + _NODISCARD constexpr _Vw base() && noexcept(is_nothrow_move_constructible_v<_Vw>) /* strengthened */ { + return _STD move(_Base); + } + + _NODISCARD constexpr auto begin() noexcept( + noexcept(_RANGES begin(_Base)) && is_nothrow_move_constructible_v>) /* strengthened */ { + if constexpr (random_access_range<_Vw> && sized_range<_Vw>) { + return _RANGES begin(_Base); + } else { + return common_iterator, sentinel_t<_Vw>>{_RANGES begin(_Base)}; + } + } + + _NODISCARD constexpr auto begin() const noexcept( + noexcept(_RANGES begin(_Base)) + && is_nothrow_move_constructible_v>) /* strengthened */ requires range { + if constexpr (random_access_range && sized_range) { + return _RANGES begin(_Base); + } else { + return common_iterator, sentinel_t>{_RANGES begin(_Base)}; + } + } + + _NODISCARD constexpr auto end() { + if constexpr (random_access_range<_Vw> && sized_range<_Vw>) { + return _RANGES begin(_Base) + _RANGES size(_Base); + } else { + return common_iterator, sentinel_t<_Vw>>{_RANGES end(_Base)}; + } + } + + _NODISCARD constexpr auto end() const requires range { + if constexpr (random_access_range && sized_range) { + return _RANGES begin(_Base) + _RANGES size(_Base); + } else { + return common_iterator, sentinel_t>{_RANGES end(_Base)}; + } + } + + _NODISCARD constexpr auto size() noexcept( + noexcept(_RANGES size(_Base))) /* strengthened */ requires sized_range<_Vw> { + return _RANGES size(_Base); + } + _NODISCARD constexpr auto size() const + noexcept(noexcept(_RANGES size(_Base))) /* strengthened */ requires sized_range { + return _RANGES size(_Base); + } + }; + + template + common_view(_Rng &&) -> common_view>; + + namespace views { + // VARIABLE views::common + class _Common_fn : public _Pipe::_Base<_Common_fn> { + private: + enum class _St { _None, _All, _Common }; + + template + _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { + if constexpr (common_range<_Rng>) { + return {_St::_All, noexcept(views::all(_STD declval<_Rng>()))}; + } else if constexpr (copyable>) { + return {_St::_Common, noexcept(common_view{_STD declval<_Rng>()})}; + } else { + return {_St::_None}; + } + } + + template + static constexpr _Choice_t<_St> _Choice = _Choose<_Rng>(); + + public: + // clang-format off + template + requires (_Choice<_Rng>._Strategy != _St::_None) + _NODISCARD constexpr auto operator()(_Rng&& _Range) const noexcept(_Choice<_Rng>._No_throw) { + // clang-format on + if constexpr (_Choice<_Rng>._Strategy == _St::_All) { + return views::all(_STD forward<_Rng>(_Range)); + } else if constexpr (_Choice<_Rng>._Strategy == _St::_Common) { + return common_view{_STD forward<_Rng>(_Range)}; + } else { + static_assert(_Always_false<_Rng>, "Should be unreachable"); + } + } + }; + + inline constexpr _Common_fn common; + } // namespace views } // namespace ranges namespace views = ranges::views; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 76a64ab60d2..90d0577bad6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,9 @@ option(TESTS_BUILD_ONLY "Only run the build steps of tests" OFF) add_subdirectory(libcxx) add_subdirectory(std) add_subdirectory(tr1) +if(STL_IS_KERNEL) + add_subdirectory(utils/kernel) +endif() # Add the stl-lit subdirectory last so all the test directories have had a # chance to add to the config map and test directory global properties. diff --git a/tests/libcxx/lit.site.cfg.in b/tests/libcxx/lit.site.cfg.in index 0557736b467..246fb6ad12b 100644 --- a/tests/libcxx/lit.site.cfg.in +++ b/tests/libcxx/lit.site.cfg.in @@ -32,6 +32,13 @@ lit_config.cxx_runtime = '@CMAKE_RUNTIME_OUTPUT_DIRECTORY@' lit_config.target_arch = '@VCLIBS_TARGET_ARCHITECTURE@' lit_config.build_only = '@TESTS_BUILD_ONLY@'.lower() in ['1', 'true', 'on'] +lit_config.is_kernel = '@STL_IS_KERNEL@'.lower() in ['1', 'true', 'on'] +if lit_config.is_kernel: + lit_config.wdk_include = "@WDK_INCLUDE_DIRECTORY@" + lit_config.wdk_lib = "@WDK_LIB_DIRECTORY@" + lit_config.wdk_bin = "@WDK_BIN_DIRECTORY@" + lit_config.utils_dir = "@STL_TEST_UTILS_DIR@" + # Add parameters and features to the config stl.test.config.configure( stl.test.params.getDefaultParameters(config, lit_config), diff --git a/tests/std/lit.site.cfg.in b/tests/std/lit.site.cfg.in index 653f76d16a3..a5937b015d3 100644 --- a/tests/std/lit.site.cfg.in +++ b/tests/std/lit.site.cfg.in @@ -32,6 +32,13 @@ lit_config.cxx_runtime = '@CMAKE_RUNTIME_OUTPUT_DIRECTORY@' lit_config.target_arch = '@VCLIBS_TARGET_ARCHITECTURE@' lit_config.build_only = '@TESTS_BUILD_ONLY@'.lower() in ['1', 'true', 'on'] +lit_config.is_kernel = '@STL_IS_KERNEL@'.lower() in ['1', 'true', 'on'] +if lit_config.is_kernel: + lit_config.wdk_include = "@WDK_INCLUDE_DIRECTORY@" + lit_config.wdk_lib = "@WDK_LIB_DIRECTORY@" + lit_config.wdk_bin = "@WDK_BIN_DIRECTORY@" + lit_config.utils_dir = "@STL_TEST_UTILS_DIR@" + # Add parameters and features to the config stl.test.config.configure( stl.test.params.getDefaultParameters(config, lit_config), diff --git a/tests/std/tests/Dev11_0863628_atomic_compare_exchange/env.lst b/tests/std/tests/Dev11_0863628_atomic_compare_exchange/env.lst index f141421b292..79324f369a2 100644 --- a/tests/std/tests/Dev11_0863628_atomic_compare_exchange/env.lst +++ b/tests/std/tests/Dev11_0863628_atomic_compare_exchange/env.lst @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception RUNALL_INCLUDE ..\impure_matrix.lst +RUNALL_INCLUDE ..\kernel.lst diff --git a/tests/std/tests/Dev11_0863628_atomic_compare_exchange/test.cpp b/tests/std/tests/Dev11_0863628_atomic_compare_exchange/test.cpp index 6e9a163345d..9c3b6e60693 100644 --- a/tests/std/tests/Dev11_0863628_atomic_compare_exchange/test.cpp +++ b/tests/std/tests/Dev11_0863628_atomic_compare_exchange/test.cpp @@ -15,7 +15,6 @@ #include #include - using namespace std; #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) @@ -503,12 +502,25 @@ void test_layout_paranoia() { assert(b[2] == 3); } +#ifdef _KERNEL_MODE + +#define assert_bitwise_identical(MSG, LHS, RHS) \ + { \ + double lhs = LHS; \ + double rhs = RHS; \ + if (memcmp(&lhs, &rhs, sizeof(lhs)) != 0) { \ + assert(!MSG); \ + } \ + } + +#else // _KERNEL_MODE void assert_bitwise_identical(const char* const msg, const double lhs, const double rhs) { if (memcmp(&lhs, &rhs, sizeof(lhs)) != 0) { printf("counterexample found in %s: %a and %a\n", msg, lhs, rhs); abort(); } } +#endif // _KERNEL_MODE void test_double_identical_results() { #if _HAS_CXX20 diff --git a/tests/std/tests/P0896R4_views_common/test.cpp b/tests/std/tests/P0896R4_views_common/test.cpp index 65aa11ba02e..111c1eac49e 100644 --- a/tests/std/tests/P0896R4_views_common/test.cpp +++ b/tests/std/tests/P0896R4_views_common/test.cpp @@ -357,6 +357,9 @@ constexpr bool test_one(Rng&& rng, Expected&& expected) { } // Validate common_view::base() && (NB: do this last since it leaves r moved-from) +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-1159442 + (void) 42; +#endif // TRANSITION, DevCom-1159442 same_as auto b2 = move(r).base(); static_assert(noexcept(move(r).base()) == is_nothrow_move_constructible_v); if (!is_empty) { diff --git a/tests/std/tests/kernel.lst b/tests/std/tests/kernel.lst new file mode 100644 index 00000000000..3c8314e54f2 --- /dev/null +++ b/tests/std/tests/kernel.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +PM_CL="/kernel /Zc:preprocessor /std:c++latest /w14640 /Zc:threadSafeInit- /DNO_TEST_ENVIRONMENT_PREPARER" PM_LINK="/IGNORE:4210 /subsystem:native /nodefaultlib stl_kernel.lib BufferOverflowFastFailK.lib ntoskrnl.lib hal.lib wmilib.lib Ntstrsafe.lib libcpmt.lib libcmt.lib" diff --git a/tests/tr1/lit.site.cfg.in b/tests/tr1/lit.site.cfg.in index a0d226bca8c..422d39b33bc 100644 --- a/tests/tr1/lit.site.cfg.in +++ b/tests/tr1/lit.site.cfg.in @@ -31,6 +31,13 @@ lit_config.cxx_runtime = '@CMAKE_RUNTIME_OUTPUT_DIRECTORY@' lit_config.target_arch = '@VCLIBS_TARGET_ARCHITECTURE@' lit_config.build_only = '@TESTS_BUILD_ONLY@'.lower() in ['1', 'true', 'on'] +lit_config.is_kernel = '@STL_IS_KERNEL@'.lower() in ['1', 'true', 'on'] +if lit_config.is_kernel: + lit_config.wdk_include = "@WDK_INCLUDE_DIRECTORY@" + lit_config.wdk_lib = "@WDK_LIB_DIRECTORY@" + lit_config.wdk_bin = "@WDK_BIN_DIRECTORY@" + lit_config.utils_dir = "@STL_TEST_UTILS_DIR@" + # Add parameters and features to the config stl.test.config.configure( stl.test.params.getDefaultParameters(config, lit_config), diff --git a/tests/utils/kernel/CMakeLists.txt b/tests/utils/kernel/CMakeLists.txt new file mode 100644 index 00000000000..74e41579c01 --- /dev/null +++ b/tests/utils/kernel/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +set(STL_KERNEL_LOADER_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/stl_kernel_loader/testapp.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/stl_kernel_loader/install.cpp +) + +set(STL_KERNEL_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/stl_kernel/doAssert.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/stl_kernel/stl_kernel.cpp +) + +add_library(stl_kernel STATIC ${STL_KERNEL_SOURCES}) +set_target_properties(stl_kernel PROPERTIES LINKER_LANGUAGE CXX) +target_compile_definitions(stl_kernel PRIVATE "__STL_IS_KERNEL") + +# ${WDK_INCLUDE_DIRECTORY}/km/crt isn't in the files under test include paths. +# doAssert.cpp wants to use RtlStringCbPrintfExA, and that requires pulling in +# km/crt. If we don't pull in km/crt here, we erroneously pull in user mode +# crt headers, and that gets us linker errors dealing with missing user mode +# symbols in this kernel mode binary. +target_include_directories(stl_kernel PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/inc + ${WDK_INCLUDE_DIRECTORY}/km + ${WDK_INCLUDE_DIRECTORY}/km/crt + ${WDK_INCLUDE_DIRECTORY}/shared +) + +# TRANSITION, WDK needs to suppress C5040 +if(VCLIBS_TARGET_ARCHITECTURE STREQUAL "x86") + set(STL_KERNEL_CALLING_CONVENTION "/Gz") +else() + set(STL_KERNEL_CALLING_CONVENTION "") +endif() + +target_compile_options(stl_kernel PRIVATE /kernel /wd5040 ${STL_KERNEL_CALLING_CONVENTION}) + +add_executable(stl_kernel_loader ${STL_KERNEL_LOADER_SOURCES}) +target_include_directories(stl_kernel_loader PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/inc + ${WDK_INCLUDE_DIRECTORY}/ucrt + ${WDK_INCLUDE_DIRECTORY}/um + ${WDK_INCLUDE_DIRECTORY}/shared +) +target_compile_options(stl_kernel_loader PRIVATE /EHsc) +target_link_libraries(stl_kernel_loader PRIVATE + msvcrt.lib + kernel32.lib + ucrt.lib + AdvApi32.lib +) diff --git a/tests/utils/kernel/generateMsvcCert.ps1 b/tests/utils/kernel/generateMsvcCert.ps1 new file mode 100644 index 00000000000..a950ab6ef87 --- /dev/null +++ b/tests/utils/kernel/generateMsvcCert.ps1 @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +param([string]$out="MsvcStlTestingCert.pfx",[string]$pass="placeholderPassword") + +$ErrorActionPreference = 'Stop' +# Clean up old certificates +Get-ChildItem cert:\localmachine\My | +Where-Object { $_.Subject -eq 'CN=MsvcStlTestingCert' } | +Remove-Item + +Get-ChildItem cert:\localmachine\root | +Where-Object { $_.Subject -eq 'CN=MsvcStlTestingCert' } | +Remove-Item + +Get-ChildItem cert:\localmachine\trustedpublisher | +Where-Object { $_.Subject -eq 'CN=MsvcStlTestingCert' } | +Remove-Item + +# Make the new cert +$cert = New-SelfSignedCertificate -Type CodeSigningCert -DnsName "MsvcStlTestingCert" ` + -certstorelocation cert:\localmachine\my -NotAfter (Get-Date).AddDays(2) +$path = 'cert:\localMachine\my\' + $cert.thumbprint +$pwd = ConvertTo-SecureString -String $pass -Force -AsPlainText +Export-PfxCertificate -cert $path -FilePath $out -Password $pwd + +# install the cert so that we can load our drivers +Import-PfxCertificate -FilePath $out -CertStoreLocation cert:\localmachine\root -Password $pwd +Import-PfxCertificate -FilePath $out -CertStoreLocation cert:\localmachine\trustedpublisher -Password $pwd diff --git a/tests/utils/kernel/inc/assert.h b/tests/utils/kernel/inc/assert.h new file mode 100644 index 00000000000..d06c78007ad --- /dev/null +++ b/tests/utils/kernel/inc/assert.h @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include diff --git a/tests/utils/kernel/inc/cassert b/tests/utils/kernel/inc/cassert new file mode 100644 index 00000000000..6bc2b2ac56e --- /dev/null +++ b/tests/utils/kernel/inc/cassert @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int* g_test_failures; +extern char* g_output_buffer; +extern size_t g_space_available; +void doAssert(const char* file, int line, const char* expr); + +#undef assert +#define assert(x) \ + if (!(x)) { \ + doAssert(__FILE__, __LINE__, #x); \ + } + +#ifdef __cplusplus +} +#endif diff --git a/tests/utils/kernel/inc/stl_kernel/kernel_test_constants.h b/tests/utils/kernel/inc/stl_kernel/kernel_test_constants.h new file mode 100644 index 00000000000..c92d95e1cff --- /dev/null +++ b/tests/utils/kernel/inc/stl_kernel/kernel_test_constants.h @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This header is intended to be force included into the .cpp under test. +// KERNEL_TEST_NAME will then be provided as a /D switch and preprocessor +// concatenated into one big string literal. +extern const wchar_t* const STL_KERNEL_NT_DEVICE_NAME = L"\\Device\\" KERNEL_TEST_NAME; +extern const wchar_t* const STL_KERNEL_DOS_DEVICE_NAME = L"\\DosDevices\\" KERNEL_TEST_NAME; diff --git a/tests/utils/kernel/inc/stl_kernel/stl_kernel.h b/tests/utils/kernel/inc/stl_kernel/stl_kernel.h new file mode 100644 index 00000000000..43c819cebee --- /dev/null +++ b/tests/utils/kernel/inc/stl_kernel/stl_kernel.h @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#ifdef __STL_IS_KERNEL +#include +#else +#include +#endif + +#define IOCTL_SIOCTL_METHOD_RUN_TEST CTL_CODE(40000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) + +struct TestResults { + int main_return; + int tests_failed; + // arbitrary buffer size, chosen to make the structure a "round" 4K + char output[4088]; +}; diff --git a/tests/utils/kernel/src/stl_kernel/doAssert.cpp b/tests/utils/kernel/src/stl_kernel/doAssert.cpp new file mode 100644 index 00000000000..1e886d84c36 --- /dev/null +++ b/tests/utils/kernel/src/stl_kernel/doAssert.cpp @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "test_decls.h" +#include +#include + +KGUARDED_MUTEX g_assert_mutex; +extern "C" { +int* g_test_failures; +char* g_output_buffer; +size_t g_space_available; +} + +static const char ASSERTION_FAILED[] = "assertion failed "; +static const size_t ASSERTION_FAILED_LEN = sizeof(ASSERTION_FAILED) - 1; +extern "C" void doAssert(const char* file, int line, const char* expr) { + KeAcquireGuardedMutex(&g_assert_mutex); + ++(*g_test_failures); + + RtlStringCbPrintfExA(g_output_buffer, g_space_available, &g_output_buffer, &g_space_available, + 0, // flags + "assertion failed %s(%d): %s\n", file, line, expr); + + KeReleaseGuardedMutex(&g_assert_mutex); +} diff --git a/tests/utils/kernel/src/stl_kernel/stl_kernel.cpp b/tests/utils/kernel/src/stl_kernel/stl_kernel.cpp new file mode 100644 index 00000000000..812c40e33d2 --- /dev/null +++ b/tests/utils/kernel/src/stl_kernel/stl_kernel.cpp @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "test_decls.h" +#include +#include + +// This isn't the "real" cassert, but our interposed header. We want to get +// at the globals that have the error text and the failure count +#include + +#pragma comment(lib, "ntoskrnl") + +struct device_handle { + DEVICE_OBJECT* h = nullptr; + + device_handle() = default; + ~device_handle() { + if (h) { + IoDeleteDevice(h); + } + } + + DEVICE_OBJECT* release() { + DEVICE_OBJECT* retval = h; + h = nullptr; + return retval; + } +}; + +class irp_sentry { +private: + IRP* irp; + NTSTATUS& status; + +public: + irp_sentry(IRP* irp, NTSTATUS& status) : irp(irp), status(status) {} + ~irp_sentry() { + irp->IoStatus.Status = status; + IoCompleteRequest(irp, IO_NO_INCREMENT); + } + irp_sentry(const irp_sentry&) = delete; + irp_sentry& operator=(const irp_sentry&) = delete; +}; + +extern "C" { +// Device driver routine declarations. +DRIVER_INITIALIZE DriverEntry; + +_Dispatch_type_(IRP_MJ_CREATE) _Dispatch_type_(IRP_MJ_CLOSE) DRIVER_DISPATCH StlKernelCreateClose; + +_Dispatch_type_(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH StlKernelDeviceControl; + +DRIVER_UNLOAD PAGE, StlKernelUnloadDriver; + +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(PAGE, StlKernelCreateClose) +#pragma alloc_text(PAGE, StlKernelDeviceControl) +#pragma alloc_text(PAGE, StlKernelUnloadDriver) + +_Use_decl_annotations_ NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* /*reg_path*/) { + UNICODE_STRING nt_name; + RtlInitUnicodeString(&nt_name, STL_KERNEL_NT_DEVICE_NAME); + + device_handle device; + NTSTATUS status = IoCreateDevice(driver, + 0, // DeviceExtensionSize + &nt_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, + FALSE, // BOOLEAN Exclusive + &device.h); + + if (!NT_SUCCESS(status)) { + DbgPrint("stl_kernel: Couldn't create the device object\n"); + return status; + } + + driver->MajorFunction[IRP_MJ_CREATE] = StlKernelCreateClose; + driver->MajorFunction[IRP_MJ_CLOSE] = StlKernelCreateClose; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = StlKernelDeviceControl; + driver->DriverUnload = StlKernelUnloadDriver; + + UNICODE_STRING dos_name; + RtlInitUnicodeString(&dos_name, STL_KERNEL_DOS_DEVICE_NAME); + + status = IoCreateSymbolicLink(&dos_name, &nt_name); + + if (!NT_SUCCESS(status)) { + DbgPrint("stl_kernel: Couldn't create symbolic link\n"); + return status; + } + + KeInitializeGuardedMutex(&g_assert_mutex); + + device.release(); // everything worked, so "commit" the operation. + return status; +} + +NTSTATUS StlKernelCreateClose(DEVICE_OBJECT*, IRP* irp) { + PAGED_CODE(); + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = 0; + + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} + +void StlKernelUnloadDriver(DRIVER_OBJECT* driver) { + PAGED_CODE(); + + device_handle dev; + dev.h = driver->DeviceObject; + + UNICODE_STRING dos_name; + RtlInitUnicodeString(&dos_name, STL_KERNEL_DOS_DEVICE_NAME); + IoDeleteSymbolicLink(&dos_name); +} + +NTSTATUS StlKernelDeviceControl(DEVICE_OBJECT*, IRP* irp) { + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp); + ULONG outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength; + irp_sentry sentry(irp, status); + + if (outBufLength < sizeof(TestResults)) { + status = STATUS_INVALID_PARAMETER; + return status; + } + + auto control_code = irpSp->Parameters.DeviceIoControl.IoControlCode; + if (control_code != IOCTL_SIOCTL_METHOD_RUN_TEST) { + status = STATUS_INVALID_DEVICE_REQUEST; + DbgPrint("stl_kernel ERROR: unrecognized IOCTL %x\n", control_code); + return status; + } + + TestResults* outBuf = static_cast(irp->AssociatedIrp.SystemBuffer); + + // set up the assertion framework + g_test_failures = &outBuf->tests_failed; + g_output_buffer = outBuf->output; + g_space_available = sizeof(outBuf->output); + + // This is where the magic happens + outBuf->main_return = main(); + + irp->IoStatus.Information = sizeof(TestResults); + + return status; +} + +} // extern "C" diff --git a/tests/utils/kernel/src/stl_kernel/test_decls.h b/tests/utils/kernel/src/stl_kernel/test_decls.h new file mode 100644 index 00000000000..d4642facaa9 --- /dev/null +++ b/tests/utils/kernel/src/stl_kernel/test_decls.h @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#include + +extern const wchar_t* const STL_KERNEL_NT_DEVICE_NAME; +extern const wchar_t* const STL_KERNEL_DOS_DEVICE_NAME; +extern KGUARDED_MUTEX g_assert_mutex; + +extern "C" int __cdecl main(); diff --git a/tests/utils/kernel/src/stl_kernel_loader/install.cpp b/tests/utils/kernel/src/stl_kernel_loader/install.cpp new file mode 100644 index 00000000000..2794398d5d7 --- /dev/null +++ b/tests/utils/kernel/src/stl_kernel_loader/install.cpp @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "install.h" + +#include +#include + +#include + +[[noreturn]] void throw_get_last_error(const char* routine) { + auto gle = GetLastError(); + char error_buffer[1024] = {0}; + + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, // lpSource + gle, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), error_buffer, static_cast(std::size(error_buffer)), + nullptr /* va_list arguments */); + + std::string err_text = "Error: "; + err_text += routine; + err_text += " failed. GetLastError = " + std::to_string(GetLastError()); + err_text += "\n"; + err_text += error_buffer; + throw std::runtime_error(err_text); +} + +scm_handle::scm_handle() + : h(OpenSCManager(nullptr /* lpMachineName */, nullptr /* lpDatabaseName */, SC_MANAGER_ALL_ACCESS)) { + if (!h) { + throw_get_last_error(__FUNCTION__); + } +} + +scm_handle::~scm_handle() { + CloseServiceHandle(h); +} + +struct service_handle { + SC_HANDLE h; + + service_handle(SC_HANDLE scm, const char* name) noexcept : h(OpenService(scm, name, SERVICE_ALL_ACCESS)) {} + service_handle() noexcept : h(nullptr) {} + + void attach(SC_HANDLE new_h) noexcept { + if (h) + CloseServiceHandle(h); + h = new_h; + } + + ~service_handle() { + if (h) + CloseServiceHandle(h); + } +}; + +static void stop_and_remove_driver(SC_HANDLE scm, const char* name) noexcept { + service_handle driver(scm, name); + + if (driver.h == nullptr) { + // already gone. Post-condition met + return; + } + // ignore the returns, as there isn't anything we can do if the delete fails + SERVICE_STATUS serviceStatus; + ControlService(driver.h, SERVICE_CONTROL_STOP, &serviceStatus); + DeleteService(driver.h); +} + +static void install_and_start_driver(SC_HANDLE scm, const char* path, const char* name) { + service_handle driver; + driver.attach(CreateService(scm, + name, // lpServiceName + name, // lpDisplayName + SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, path, + nullptr, // lpLoadOrderGroup + nullptr, // lpdwTagId + nullptr, // lpDependencies + nullptr, // lpServiceStartName + nullptr // lpServiceStartName + )); + + if (driver.h == nullptr) { + throw_get_last_error("CreateService"); + } + + auto success = StartService(driver.h, 0 /*num args*/, nullptr /*args*/); + if (!success) { + throw_get_last_error("StartService"); + } +} + +driver_loader::driver_loader(const char* path, const char* name) : driver_name(name) { + // clean up any previous runs in case something went horribly wrong + stop_and_remove_driver(sc_handle.get(), driver_name.c_str()); + install_and_start_driver(sc_handle.get(), path, name); +} + +driver_loader::~driver_loader() { + stop_and_remove_driver(sc_handle.get(), driver_name.c_str()); +} diff --git a/tests/utils/kernel/src/stl_kernel_loader/install.h b/tests/utils/kernel/src/stl_kernel_loader/install.h new file mode 100644 index 00000000000..f80dd540a2c --- /dev/null +++ b/tests/utils/kernel/src/stl_kernel_loader/install.h @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include + +#include + +class scm_handle { +private: + SC_HANDLE h; + +public: + scm_handle(); + ~scm_handle(); + scm_handle(const scm_handle&) = delete; + scm_handle& operator=(const scm_handle&) = delete; + + SC_HANDLE get() { + return h; + } +}; + +class driver_loader { +private: + scm_handle sc_handle; + std::string driver_name; + +public: + driver_loader(const char* path, const char* name); + ~driver_loader(); + + driver_loader(const scm_handle&) = delete; + driver_loader& operator=(const scm_handle&) = delete; +}; + +[[noreturn]] void throw_get_last_error(const char* routine); diff --git a/tests/utils/kernel/src/stl_kernel_loader/testapp.cpp b/tests/utils/kernel/src/stl_kernel_loader/testapp.cpp new file mode 100644 index 00000000000..fd15dc85df2 --- /dev/null +++ b/tests/utils/kernel/src/stl_kernel_loader/testapp.cpp @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "install.h" +#include +#include +#include +#include +#include + +#include + +struct DeviceOpener { + HANDLE h = INVALID_HANDLE_VALUE; + explicit DeviceOpener(const char* driverName) { + std::string dosName = R"(\\.\)"; + dosName += driverName; + h = CreateFile( + dosName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (h == INVALID_HANDLE_VALUE) { + throw_get_last_error(__FUNCTION__); + } + } + ~DeviceOpener() { + CloseHandle(h); + } +}; + +// returns true if the test passed +bool do_test(const char* path) { + // We have a secret handshake with the driver and the build system. + // The DOS name of the driver is the same as the absolute path, + // except the \ and : are replaced with ".". Shhh. Don't tell anyone. + std::string driverName = path; + std::replace_if( + driverName.begin(), driverName.end(), [](char c) { return c == '\\' || c == ':'; }, '.'); + + driver_loader sentry(path, driverName.c_str()); + DeviceOpener dev(driverName.c_str()); + + ULONG bytesReturned = 0; + char InputBuffer[1]; + TestResults OutputBuffer; + + auto success = DeviceIoControl(dev.h, static_cast(IOCTL_SIOCTL_METHOD_RUN_TEST), &InputBuffer, + sizeof(InputBuffer), &OutputBuffer, sizeof(OutputBuffer), &bytesReturned, nullptr); + + if (!success) { + throw_get_last_error(__FUNCTION__); + } + + if (OutputBuffer.main_return != 0 || OutputBuffer.tests_failed != 0) { + printf("retval: %d\ntests failed: %d\noutput =\n%s\n", OutputBuffer.main_return, OutputBuffer.tests_failed, + OutputBuffer.output); + return false; + } + return true; +} + +int __cdecl main(int argc, char* argv[]) { + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + return 2; + } + try { + bool result = do_test(argv[1]); + return result ? 0 : 1; + } catch (const std::exception& e) { + printf("%s\n", e.what()); + } + return 2; +} diff --git a/tests/utils/stl/test/config.py b/tests/utils/stl/test/config.py index d18d8170f38..7e37bff595b 100644 --- a/tests/utils/stl/test/config.py +++ b/tests/utils/stl/test/config.py @@ -7,6 +7,8 @@ #===----------------------------------------------------------------------===## import os +import secrets +import stl.util def configure(parameters, features, config, lit_config): # Apply the actions supplied by parameters to the configuration first, since @@ -47,5 +49,18 @@ def configure(parameters, features, config, lit_config): lit_config.test_env = {'PATH' : os.path.normpath(lit_config.cxx_runtime)} + if lit_config.is_kernel: + lit_config.cert_pass = secrets.token_hex(64) + lit_config.cert_path = lit_config.cxx_runtime + '/MsvcStlTestingCert.pfx' + cmd = ['powershell', '-ExecutionPolicy', 'Bypass', + '-File', lit_config.utils_dir + '/kernel/generateMsvcCert.ps1', + '-out', lit_config.cert_path, + '-pass', lit_config.cert_pass] + + out, err, rc = stl.util.executeCommand(cmd) + if rc != 0: + report = stl.util.makeReport(cmd, out, err, rc) + raise RuntimeError(report) + # Add a parallelism group that multi-threaded tests can be marked as a part of. lit_config.parallelism_groups['multi-threaded'] = 1 diff --git a/tests/utils/stl/test/features.py b/tests/utils/stl/test/features.py index 0d9eb237f9c..7fe5a3d73d2 100644 --- a/tests/utils/stl/test/features.py +++ b/tests/utils/stl/test/features.py @@ -59,4 +59,7 @@ def getDefaultFeatures(config, litConfig): elif litConfig.target_arch.casefold() == 'arm64'.casefold(): DEFAULT_FEATURES.append(Feature(name='arm64')) + if litConfig.is_kernel: + DEFAULT_FEATURES.append(Feature(name='kernel')) + return DEFAULT_FEATURES diff --git a/tests/utils/stl/test/format.py b/tests/utils/stl/test/format.py index c9d40635dd2..9e09a6e6f2c 100644 --- a/tests/utils/stl/test/format.py +++ b/tests/utils/stl/test/format.py @@ -15,6 +15,7 @@ import os import re import shutil +import traceback import lit @@ -134,7 +135,6 @@ def runStep(self, testStep, litConfig): testStep.workDir = os.getcwd() env = _mergeEnvironments(os.environ, testStep.env) - return testStep.cmd, *stl.util.executeCommand(testStep.cmd, cwd=testStep.workDir, env=env) def getStages(self, test, litConfig): @@ -183,10 +183,38 @@ def getBuildSteps(self, test, litConfig, shared): yield TestStep(cmd, shared.execDir, shared.env, shouldFail) elif TestType.RUN in test.testType: shared.execFile = tmpBase + '.exe' + isKernel = 'kernel' in test.requires + + if isKernel: + name = str(shared.execFile).replace('\\','.').replace(':','.') + + test.compileFlags = [ + '/DKERNEL_TEST_NAME=L"' + name + '"', + '/FIstl_kernel/kernel_test_constants.h', + '/I' + litConfig.utils_dir + '/kernel/inc', + '/I' + litConfig.wdk_include + '/km', + #'/I' + litConfig.wdk_include + '/km/crt', #causes vadefs.h conflicts + '/I' + litConfig.wdk_include + '/shared', + ] + test.compileFlags + test.linkFlags.extend([ + '/LIBPATH:' + litConfig.wdk_lib + '/km/' + litConfig.target_arch, + '/machine:'+litConfig.target_arch, + ]) + + # common path for kernel and non-kernel cmd = [test.cxx, test.getSourcePath(), *test.flags, *test.compileFlags, '-Fe' + shared.execFile, '-link', *test.linkFlags] yield TestStep(cmd, shared.execDir, shared.env, False) + # only sign (and run) kernel mode tests on x64 + if isKernel and 'x64' in test.requires: + # sign the binary + cmd = [litConfig.wdk_bin + '/x86/signtool.exe', 'sign', + '/f', litConfig.cert_path, + '/p', litConfig.cert_pass, + shared.execFile] + yield TestStep(cmd, shared.execDir, shared.env, shouldFail=False) + def getTestSetupSteps(self, test, litConfig, shared): if TestType.RUN in test.testType: for dependency in test.fileDependencies: @@ -202,7 +230,16 @@ def getTestSteps(self, test, litConfig, shared): return shouldFail = TestType.FAIL in test.testType - yield TestStep([shared.execFile], shared.execDir, shared.env, shouldFail) + if 'kernel' in test.requires: + if litConfig.target_arch != 'x64'.casefold(): + # don't attempt to run kernel tests on any platform except + # x64. We only build on x64 right now. + yield from [] + return + cmd = [litConfig.cxx_runtime + "/stl_kernel_loader.exe", shared.execFile] + else: + cmd = [shared.execFile] + yield TestStep(cmd, shared.execDir, shared.env, shouldFail) def execute(self, test, litConfig): try: @@ -239,7 +276,9 @@ def execute(self, test, litConfig): return (lit.Test.PASS, '') except Exception as e: - litConfig.error(repr(e)) + errorStr = "".join(traceback.format_exception(None, e, e.__traceback__)) + litConfig.error(errorStr) + return (lit.Test.FAIL, errorStr) class LibcxxTestFormat(STLTestFormat): diff --git a/tests/utils/stl/test/tests.py b/tests/utils/stl/test/tests.py index 75dd5dbfb4c..b2f6798eeba 100644 --- a/tests/utils/stl/test/tests.py +++ b/tests/utils/stl/test/tests.py @@ -53,7 +53,7 @@ def configureTest(self, litConfig): return result self._parseTest() - self._parseFlags() + self._parseFlags(litConfig) missing_required_features = self.getMissingRequiredFeatures() if missing_required_features: @@ -226,7 +226,7 @@ def _addCustomFeature(self, name): for action in actions: action.applyTo(self.config) - def _parseFlags(self): + def _parseFlags(self, litConfig): foundStd = False for flag in chain(self.flags, self.compileFlags, self.linkFlags): if flag[1:5] == 'std:': @@ -249,6 +249,18 @@ def _parseFlags(self): self.requires.append('arch_ia32') # available for x86, see features.py elif flag[1:] == 'arch:VFPv4': self.requires.append('arch_vfpv4') # available for arm, see features.py + elif flag[1:] == 'kernel': + self.requires.append('kernel') + # TRANSITION, We should support kernel mode tests on all platforms + self.requires.append('x64') + targetArch = litConfig.target_arch.casefold() + if (targetArch == 'x86'.casefold()): + # 32-bit kernel uses stdcall by default + self.compileFlags.append('/Gz') + self.compileFlags.append('/wd4007') + self.linkFlags.append('/entry:DriverEntry@8') + else: + self.linkFlags.append('/entry:DriverEntry') if not foundStd: self._addCustomFeature('c++14')