47 changes: 26 additions & 21 deletions Externals/WIL/include/wil/token_helpers.h
Expand Up @@ -21,7 +21,9 @@
#include <processthreadsapi.h>

// for GetUserNameEx()
#ifndef SECURITY_WIN32
#define SECURITY_WIN32
#endif
#include <Security.h>

namespace wil
Expand All @@ -34,25 +36,25 @@ namespace wil
// be an info class value that uses the same structure. That is the case for the file
// system information.
template<typename T> struct MapTokenStructToInfoClass;
template<> struct MapTokenStructToInfoClass<TOKEN_ACCESS_INFORMATION> { static const TOKEN_INFORMATION_CLASS infoClass = TokenAccessInformation; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_APPCONTAINER_INFORMATION> { static const TOKEN_INFORMATION_CLASS infoClass = TokenAppContainerSid; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_DEFAULT_DACL> { static const TOKEN_INFORMATION_CLASS infoClass = TokenDefaultDacl; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_GROUPS_AND_PRIVILEGES> { static const TOKEN_INFORMATION_CLASS infoClass = TokenGroupsAndPrivileges; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_MANDATORY_LABEL> { static const TOKEN_INFORMATION_CLASS infoClass = TokenIntegrityLevel; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_OWNER> { static const TOKEN_INFORMATION_CLASS infoClass = TokenOwner; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_PRIMARY_GROUP> { static const TOKEN_INFORMATION_CLASS infoClass = TokenPrimaryGroup; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_PRIVILEGES> { static const TOKEN_INFORMATION_CLASS infoClass = TokenPrivileges; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_USER> { static const TOKEN_INFORMATION_CLASS infoClass = TokenUser; static const bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_ACCESS_INFORMATION> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenAccessInformation; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_APPCONTAINER_INFORMATION> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenAppContainerSid; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_DEFAULT_DACL> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenDefaultDacl; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_GROUPS_AND_PRIVILEGES> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenGroupsAndPrivileges; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_MANDATORY_LABEL> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenIntegrityLevel; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_OWNER> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenOwner; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_PRIMARY_GROUP> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenPrimaryGroup; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_PRIVILEGES> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenPrivileges; static constexpr bool FixedSize = false; };
template<> struct MapTokenStructToInfoClass<TOKEN_USER> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenUser; static constexpr bool FixedSize = false; };

// fixed size cases
template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION_TYPE> { static const TOKEN_INFORMATION_CLASS infoClass = TokenElevationType; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_MANDATORY_POLICY> { static const TOKEN_INFORMATION_CLASS infoClass = TokenMandatoryPolicy; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_ORIGIN> { static const TOKEN_INFORMATION_CLASS infoClass = TokenOrigin; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_SOURCE> { static const TOKEN_INFORMATION_CLASS infoClass = TokenSource; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_STATISTICS> { static const TOKEN_INFORMATION_CLASS infoClass = TokenStatistics; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_TYPE> { static const TOKEN_INFORMATION_CLASS infoClass = TokenType; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<SECURITY_IMPERSONATION_LEVEL> { static const TOKEN_INFORMATION_CLASS infoClass = TokenImpersonationLevel; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION> { static const TOKEN_INFORMATION_CLASS infoClass = TokenElevation; static const bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION_TYPE> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenElevationType; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_MANDATORY_POLICY> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenMandatoryPolicy; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_ORIGIN> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenOrigin; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_SOURCE> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenSource; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_STATISTICS> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenStatistics; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_TYPE> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenType; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<SECURITY_IMPERSONATION_LEVEL> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenImpersonationLevel; static constexpr bool FixedSize = true; };
template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION> { static constexpr TOKEN_INFORMATION_CLASS infoClass = TokenElevation; static constexpr bool FixedSize = true; };
}
/// @endcond

Expand Down Expand Up @@ -85,13 +87,12 @@ namespace wil
or privilege-adjustment are examples of uses.
~~~~
wil::unique_handle callerToken;
RETURN_IF_FAILED(wil::open_current_access_token_nothrow(&theToken, TOKEN_QUERY | TOKEN_IMPERSONATE, true));
RETURN_IF_FAILED(wil::open_current_access_token_nothrow(&theToken, TOKEN_QUERY | TOKEN_IMPERSONATE, OpenThreadTokenAs::Self));
~~~~
@param tokenHandle Receives the token opened during the operation. Must be CloseHandle'd by the caller, or
(preferably) stored in a wil::unique_handle
@param access Bits from the TOKEN_* access mask which are passed to OpenThreadToken/OpenProcessToken
@param asSelf When true, and if the thread is impersonating, the thread token is opened using the
process token's rights.
@param openAs Current to use current thread security context, or Self to use process security context.
*/
inline HRESULT open_current_access_token_nothrow(_Out_ HANDLE* tokenHandle, unsigned long access = TOKEN_QUERY, OpenThreadTokenAs openAs = OpenThreadTokenAs::Current)
{
Expand Down Expand Up @@ -122,6 +123,7 @@ namespace wil
}
#endif // WIL_ENABLE_EXCEPTIONS

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
// Returns tokenHandle or the effective thread token if tokenHandle is null.
// Note, this returns an token handle who's lifetime is managed independently
// and it may be a pseudo token, don't free it!
Expand Down Expand Up @@ -287,11 +289,12 @@ namespace wil
return tokenInfo;
}
#endif
#endif // _WIN32_WINNT >= _WIN32_WINNT_WIN8

/// @cond
namespace details
{
inline void RevertImpersonateToken(_Pre_opt_valid_ _Frees_ptr_opt_ HANDLE oldToken)
inline void RevertImpersonateToken(_In_ _Post_ptr_invalid_ HANDLE oldToken)
{
FAIL_FAST_IMMEDIATE_IF(!::SetThreadToken(nullptr, oldToken));

Expand Down Expand Up @@ -524,6 +527,7 @@ namespace wil
return S_OK;
}

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
/** Determine whether a token represents an app container
This method uses the passed in token and emits a boolean indicating that
whether TokenIsAppContainer is true.
Expand Down Expand Up @@ -573,6 +577,7 @@ namespace wil
return value;
}
#endif // WIL_ENABLE_EXCEPTIONS
#endif // _WIN32_WINNT >= _WIN32_WINNT_WIN8

template<typename... Ts> bool test_token_membership_failfast(_In_opt_ HANDLE token,
const SID_IDENTIFIER_AUTHORITY& sidAuthority, Ts&&... subAuthorities)
Expand Down
71 changes: 71 additions & 0 deletions Externals/WIL/include/wil/traceloggingconfig.h
@@ -0,0 +1,71 @@
#pragma once
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT.
//
//*********************************************************

#ifndef __WIL_TRACELOGGING_CONFIG_H
#define __WIL_TRACELOGGING_CONFIG_H

// Configuration macro for use in TRACELOGGING_DEFINE_PROVIDER. The definition
// in this file configures the provider as a normal (non-telemetry) provider.
#define TraceLoggingOptionMicrosoftTelemetry() \
// Empty definition for TraceLoggingOptionMicrosoftTelemetry

// Configuration macro for use in TRACELOGGING_DEFINE_PROVIDER. The definition
// in this file configures the provider as a normal (non-telemetry) provider.
#define TraceLoggingOptionWindowsCoreTelemetry() \
// Empty definition for TraceLoggingOptionWindowsCoreTelemetry

// Event privacy tags. Use the PDT macro values for the tag parameter, e.g.:
// TraceLoggingWrite(...,
// TelemetryPrivacyDataTag(PDT_BrowsingHistory | PDT_ProductAndServiceUsage),
// ...);
#define TelemetryPrivacyDataTag(tag) TraceLoggingUInt64((tag), "PartA_PrivTags")
#define PDT_BrowsingHistory 0x0000000000000002u
#define PDT_DeviceConnectivityAndConfiguration 0x0000000000000800u
#define PDT_InkingTypingAndSpeechUtterance 0x0000000000020000u
#define PDT_ProductAndServicePerformance 0x0000000001000000u
#define PDT_ProductAndServiceUsage 0x0000000002000000u
#define PDT_SoftwareSetupAndInventory 0x0000000080000000u

// Event categories specified via keywords, e.g.:
// TraceLoggingWrite(...,
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
// ...);
#define MICROSOFT_KEYWORD_CRITICAL_DATA 0x0000800000000000 // Bit 47
#define MICROSOFT_KEYWORD_MEASURES 0x0000400000000000 // Bit 46
#define MICROSOFT_KEYWORD_TELEMETRY 0x0000200000000000 // Bit 45
#define MICROSOFT_KEYWORD_RESERVED_44 0x0000100000000000 // Bit 44 (reserved for future assignment)

// Event categories specified via event tags, e.g.:
// TraceLoggingWrite(...,
// TraceLoggingEventTag(MICROSOFT_EVENTTAG_REALTIME_LATENCY),
// ...);
#define MICROSOFT_EVENTTAG_DROP_USER_IDS 0x00008000
#define MICROSOFT_EVENTTAG_AGGREGATE 0x00010000
#define MICROSOFT_EVENTTAG_DROP_PII_EXCEPT_IP 0x00020000
#define MICROSOFT_EVENTTAG_COSTDEFERRED_LATENCY 0x00040000
#define MICROSOFT_EVENTTAG_CORE_DATA 0x00080000
#define MICROSOFT_EVENTTAG_INJECT_XTOKEN 0x00100000
#define MICROSOFT_EVENTTAG_REALTIME_LATENCY 0x00200000
#define MICROSOFT_EVENTTAG_NORMAL_LATENCY 0x00400000
#define MICROSOFT_EVENTTAG_CRITICAL_PERSISTENCE 0x00800000
#define MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE 0x01000000
#define MICROSOFT_EVENTTAG_DROP_PII 0x02000000
#define MICROSOFT_EVENTTAG_HASH_PII 0x04000000
#define MICROSOFT_EVENTTAG_MARK_PII 0x08000000

// Field categories specified via field tags, e.g.:
// TraceLoggingWrite(...,
// TraceLoggingString(szUser, "UserName", "User's name", MICROSOFT_FIELDTAG_HASH_PII),
// ...);
#define MICROSOFT_FIELDTAG_DROP_PII 0x04000000
#define MICROSOFT_FIELDTAG_HASH_PII 0x08000000
#endif // __WIL_TRACELOGGING_CONFIG_H
493 changes: 396 additions & 97 deletions Externals/WIL/include/wil/win32_helpers.h

Large diffs are not rendered by default.

104 changes: 104 additions & 0 deletions Externals/WIL/include/wil/win32_result_macros.h
@@ -0,0 +1,104 @@
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT.
//
//*********************************************************
#ifndef __WIL_WIN32_RESULTMACROS_INCLUDED
#define __WIL_WIN32_RESULTMACROS_INCLUDED

#include "result_macros.h"

// Helpers for return macros
#define __WIN32_RETURN_WIN32(error, str) __WI_SUPPRESS_4127_S do { const auto __error = (error); if (FAILED_WIN32(__error)) { __R_FN(Return_Win32)(__R_INFO(str) __error); } return __error; } __WI_SUPPRESS_4127_E while ((void)0, 0)
#define __WIN32_RETURN_GLE_FAIL(str) return __R_FN(Win32_Return_GetLastError)(__R_INFO_ONLY(str))

FORCEINLINE long __WIN32_FROM_HRESULT(HRESULT hr)
{
if (SUCCEEDED(hr))
{
return ERROR_SUCCESS;
}
return HRESULT_FACILITY(hr) == FACILITY_WIN32 ? HRESULT_CODE(hr) : hr;
}

//*****************************************************************************
// Macros for returning failures as WIN32 error codes
//*****************************************************************************

// Always returns a known result (WIN32 error code) - always logs failures
#define WIN32_RETURN_WIN32(error) __WIN32_RETURN_WIN32(wil::verify_win32(error), #error)
#define WIN32_RETURN_LAST_ERROR() __WIN32_RETURN_GLE_FAIL(nullptr)

// Conditionally returns failures (WIN32 error code) - always logs failures
#define WIN32_RETURN_IF_WIN32_ERROR(error) __WI_SUPPRESS_4127_S do { const auto __errorRet = wil::verify_win32(error); if (FAILED_WIN32(__errorRet)) { __WIN32_RETURN_WIN32(__errorRet, #error); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_WIN32_IF(error, condition) __WI_SUPPRESS_4127_S do { if (wil::verify_bool(condition)) { __WIN32_RETURN_WIN32(wil::verify_win32(error), #condition); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_WIN32_IF_NULL(error, ptr) __WI_SUPPRESS_4127_S do { if ((ptr) == nullptr) { __WIN32_RETURN_WIN32(wil::verify_win32(error), #ptr); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_LAST_ERROR_IF(condition) __WI_SUPPRESS_4127_S do { if (wil::verify_bool(condition)) { __WIN32_RETURN_GLE_FAIL(#condition); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_LAST_ERROR_IF_NULL(ptr) __WI_SUPPRESS_4127_S do { if ((ptr) == nullptr) { __WIN32_RETURN_GLE_FAIL(#ptr); }} __WI_SUPPRESS_4127_E while ((void)0, 0)

// Conditionally returns failures (WIN32 error code) - use for failures that are expected in common use - failures are not logged - macros are only for control flow pattern
#define WIN32_RETURN_IF_WIN32_ERROR_EXPECTED(error) __WI_SUPPRESS_4127_S do { const auto __errorRet = wil::verify_win32(error); if (FAILED_WIN32(__errorRet)) { return __errorRet; }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_WIN32_IF_EXPECTED(error, condition) __WI_SUPPRESS_4127_S do { if (wil::verify_bool(condition)) { return wil::verify_win32(error); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_WIN32_IF_NULL_EXPECTED(error, ptr) __WI_SUPPRESS_4127_S do { if ((ptr) == nullptr) { return wil::verify_win32(error); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_LAST_ERROR_IF_EXPECTED(condition) __WI_SUPPRESS_4127_S do { if (wil::verify_bool(condition)) { return wil::verify_win32(wil::details::GetLastErrorFail()); }} __WI_SUPPRESS_4127_E while ((void)0, 0)
#define WIN32_RETURN_LAST_ERROR_IF_NULL_EXPECTED(ptr) __WI_SUPPRESS_4127_S do { if ((ptr) == nullptr) { return wil::verify_win32(wil::details::GetLastErrorFail()); }} __WI_SUPPRESS_4127_E while ((void)0, 0)


//*****************************************************************************
// Macros to catch and convert exceptions on failure
//*****************************************************************************

// Use these macros *within* a catch (...) block to handle exceptions
#define WIN32_RETURN_CAUGHT_EXCEPTION() return __R_FN(Win32_Return_CaughtException)(__R_INFO_ONLY(nullptr))

// Use these macros in place of a catch block to handle exceptions
#define WIN32_CATCH_RETURN() catch (...) { WIN32_RETURN_CAUGHT_EXCEPTION(); }

namespace wil
{
//*****************************************************************************
// Public Helpers that catch -- mostly only enabled when exceptions are enabled
//*****************************************************************************

// Win32ErrorFromCaughtException is a function that is meant to be called from within a catch(...) block. Internally
// it re-throws and catches the exception to convert it to a WIN32 error code. If an exception is of an unrecognized type
// the function will fail fast.
//
// try
// {
// // Code
// }
// catch (...)
// {
// status = wil::Win32ErrorFromCaughtException();
// }
_Always_(_Post_satisfies_(return > 0))
__declspec(noinline) inline long Win32ErrorFromCaughtException() WI_NOEXCEPT
{
return __WIN32_FROM_HRESULT(ResultFromCaughtException());
}

namespace details::__R_NS_NAME
{
#ifdef WIL_ENABLE_EXCEPTIONS
__R_DIRECT_METHOD(long, Win32_Return_CaughtException)(__R_DIRECT_FN_PARAMS_ONLY) WI_NOEXCEPT
{
__R_FN_LOCALS;
return __WIN32_FROM_HRESULT(wil::details::ReportFailure_CaughtException<FailureType::Return>(__R_DIRECT_FN_CALL_ONLY));
}
#endif

__R_DIRECT_METHOD(long, Win32_Return_GetLastError)(__R_DIRECT_FN_PARAMS_ONLY) WI_NOEXCEPT
{
__R_FN_LOCALS;
return __WIN32_FROM_HRESULT(wil::details::ReportFailure_GetLastErrorHr<FailureType::Return>(__R_DIRECT_FN_CALL_ONLY));
}
}
}

#endif // __WIL_WIN32_RESULTMACROS_INCLUDED
199 changes: 150 additions & 49 deletions Externals/WIL/include/wil/winrt.h

Large diffs are not rendered by default.

39 changes: 31 additions & 8 deletions Externals/WIL/include/wil/wistd_config.h
Expand Up @@ -39,7 +39,7 @@
#define _WISTD_CONFIG_H_

// DO NOT add *any* additional includes to this file -- there should be no dependencies from its usage
#include <stddef.h> // For size_t and other necessary types
#include <cstddef> // For size_t and other necessary types

/// @cond
#if defined(_MSC_VER) && !defined(__clang__)
Expand Down Expand Up @@ -107,6 +107,29 @@
# define __WI_LIBCPP_COMPILER_IBM
#endif

#if defined(__WI_LIBCPP_COMPILER_MSVC)
#define __WI_PUSH_WARNINGS __pragma(warning(push))
#define __WI_POP_WARNINGS __pragma(warning(pop))
#elif defined(__WI_LIBCPP_COMPILER_CLANG)
#define __WI_PUSH_WARNINGS __pragma(clang diagnostic push)
#define __WI_POP_WARNINGS __pragma(clang diagnostic pop)
#else
#define __WI_PUSH_WARNINGS
#define __WI_POP_WARNINGS
#endif

#ifdef __WI_LIBCPP_COMPILER_MSVC
#define __WI_MSVC_DISABLE_WARNING(id) __pragma(warning(disable: id))
#else
#define __WI_MSVC_DISABLE_WARNING(id)
#endif

#ifdef __WI_LIBCPP_COMPILER_CLANG
#define __WI_CLANG_DISABLE_WARNING(warning) __pragma(clang diagnostic ignored #warning)
#else
#define __WI_CLANG_DISABLE_WARNING(warning)
#endif

// NOTE: MSVC, which is what we primarily target, is severly underrepresented in libc++ and checks such as
// __has_feature(...) are always false for MSVC, even when the feature being tested _is_ present in MSVC. Therefore, we
// instead modify all checks to be __WI_HAS_FEATURE_IS_UNION, etc., which provides the correct value for MSVC and falls
Expand Down Expand Up @@ -448,7 +471,7 @@

namespace wistd // ("Windows Implementation" std)
{
typedef decltype(__nullptr) nullptr_t;
using nullptr_t = decltype(__nullptr);

template <class _T1, class _T2 = _T1>
struct __less
Expand Down Expand Up @@ -531,18 +554,18 @@ namespace wistd // ("Windows Implementation" std)
template <class _Arg, class _Result>
struct __WI_LIBCPP_TEMPLATE_VIS unary_function
{
typedef _Arg argument_type;
typedef _Result result_type;
using argument_type = _Arg;
using result_type = _Result;
};

template <class _Arg1, class _Arg2, class _Result>
struct __WI_LIBCPP_TEMPLATE_VIS binary_function
{
typedef _Arg1 first_argument_type;
typedef _Arg2 second_argument_type;
typedef _Result result_type;
using first_argument_type = _Arg1;
using second_argument_type = _Arg2;
using result_type = _Result;
};
}
/// @endcond

#endif _WISTD_CONFIG_H_
#endif // _WISTD_CONFIG_H_
39 changes: 22 additions & 17 deletions Externals/WIL/include/wil/wistd_functional.h
Expand Up @@ -46,6 +46,7 @@

#pragma warning(push)
#pragma warning(disable: 4324)
#pragma warning(disable: 4800)

/// @cond
namespace wistd // ("Windows Implementation" std)
Expand Down Expand Up @@ -249,25 +250,28 @@ namespace wistd // ("Windows Implementation" std)
return _Invoker::__call(__f_, wistd::forward<_ArgTypes>(__arg)...);
}

// '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).
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 _Rp, class ..._ArgTypes>
class __WI_LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)>
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...)>
{
// '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*);

typedef __function::__base<_Rp(_ArgTypes...)> __base;
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) {
return reinterpret_cast<__base*>(p);
return static_cast<__base*>(p);
}

template <class _Fp, bool>
Expand All @@ -281,7 +285,7 @@ namespace wistd // ("Windows Implementation" std)
template <class _Fp>
struct __callable_imp<_Fp, false>
{
static const bool value = false;
static constexpr bool value = false;
};

template <class _Fp>
Expand All @@ -296,11 +300,12 @@ namespace wistd // ("Windows Implementation" std)
template <class _Fp>
using _EnableIfCallable = typename enable_if<__callable<_Fp>::value>::type;
public:
typedef _Rp result_type;
using result_type = _Rp;

// construct/copy/destroy:
__WI_LIBCPP_INLINE_VISIBILITY __WI_LIBCPP_SUPPRESS_NONINIT_ANALYSIS
function() WI_NOEXCEPT : __f_(0) {}

__WI_LIBCPP_INLINE_VISIBILITY
function(nullptr_t) WI_NOEXCEPT : __f_(0) {}
function(const function&);
Expand Down Expand Up @@ -340,7 +345,7 @@ namespace wistd // ("Windows Implementation" std)
__WI_LIBCPP_SUPPRESS_NONINIT_ANALYSIS
function<_Rp(_ArgTypes...)>::function(const function& __f)
{
if (__f.__f_ == 0)
if (__f.__f_ == nullptr)
__f_ = 0;
else
{
Expand All @@ -353,7 +358,7 @@ namespace wistd // ("Windows Implementation" std)
__WI_LIBCPP_SUPPRESS_NONINIT_ANALYSIS __WI_LIBCPP_SUPPRESS_NOEXCEPT_ANALYSIS
function<_Rp(_ArgTypes...)>::function(function&& __f)
{
if (__f.__f_ == 0)
if (__f.__f_ == nullptr)
__f_ = 0;
else
{
Expand All @@ -368,14 +373,14 @@ namespace wistd // ("Windows Implementation" std)
template <class _Fp, class>
__WI_LIBCPP_SUPPRESS_NONINIT_ANALYSIS
function<_Rp(_ArgTypes...)>::function(_Fp __f)
: __f_(0)
: __f_(nullptr)
{
if (__function::__not_null(__f))
{
typedef __function::__func<_Fp, _Rp(_ArgTypes...)> _FF;
static_assert(sizeof(_FF) <= sizeof(__buf_),
"The sizeof(wistd::function) has grown too large for the reserved buffer (12 pointers). Refactor to reduce size of the capture.");
__f_ = ::new((void*)&__buf_) _FF(wistd::move(__f));
__f_ = ::new(static_cast<void*>(&__buf_)) _FF(wistd::move(__f));
}
}

Expand Down Expand Up @@ -429,7 +434,7 @@ namespace wistd // ("Windows Implementation" std)
typedef __function::__func<typename decay<_Fp>::type, _Rp(_ArgTypes...)> _FF;
static_assert(sizeof(_FF) <= sizeof(__buf_),
"The sizeof(wistd::function) has grown too large for the reserved buffer (12 pointers). Refactor to reduce size of the capture.");
__f_ = ::new((void*)&__buf_) _FF(wistd::move(__f));
__f_ = ::new(static_cast<void*>(&__buf_)) _FF(wistd::move(__f));
}

return *this;
Expand Down Expand Up @@ -483,7 +488,7 @@ namespace wistd // ("Windows Implementation" std)
_Rp
function<_Rp(_ArgTypes...)>::operator()(_ArgTypes... __arg) const
{
if (__f_ == 0)
if (__f_ == nullptr)
__throw_bad_function_call();
return (*__f_)(wistd::forward<_ArgTypes>(__arg)...);
}
Expand Down
66 changes: 33 additions & 33 deletions Externals/WIL/include/wil/wistd_memory.h
Expand Up @@ -60,30 +60,30 @@ namespace wistd // ("Windows Implementation" std)
template <class _Tp, class _Dp, bool = __has_pointer_type<_Dp>::value>
struct __pointer_type
{
typedef typename _Dp::pointer type;
using type = typename _Dp::pointer;
};

template <class _Tp, class _Dp>
struct __pointer_type<_Tp, _Dp, false>
{
typedef _Tp* type;
using type = _Tp*;
};

} // __pointer_type_imp

template <class _Tp, class _Dp>
struct __pointer_type
{
typedef typename __pointer_type_imp::__pointer_type<_Tp, typename remove_reference<_Dp>::type>::type type;
using type = typename __pointer_type_imp::__pointer_type<_Tp, typename remove_reference<_Dp>::type>::type;
};

template <class _Tp, int _Idx,
bool _CanBeEmptyBase =
is_empty<_Tp>::value && !__libcpp_is_final<_Tp>::value>
struct __compressed_pair_elem {
typedef _Tp _ParamT;
typedef _Tp& reference;
typedef const _Tp& const_reference;
using _ParamT = _Tp;
using reference = _Tp&;
using const_reference = const _Tp&;

#ifndef __WI_LIBCPP_CXX03_LANG
__WI_LIBCPP_INLINE_VISIBILITY constexpr __compressed_pair_elem() : __value_() {}
Expand Down Expand Up @@ -115,10 +115,10 @@ namespace wistd // ("Windows Implementation" std)

template <class _Tp, int _Idx>
struct __compressed_pair_elem<_Tp, _Idx, true> : private _Tp {
typedef _Tp _ParamT;
typedef _Tp& reference;
typedef const _Tp& const_reference;
typedef _Tp __value_type;
using _ParamT = _Tp;
using reference = _Tp&;
using const_reference = const _Tp&;
using __value_type = _Tp;

#ifndef __WI_LIBCPP_CXX03_LANG
__WI_LIBCPP_INLINE_VISIBILITY constexpr __compressed_pair_elem() = default;
Expand Down Expand Up @@ -149,10 +149,10 @@ namespace wistd // ("Windows Implementation" std)
struct __second_tag {};

template <class _T1, class _T2>
class __compressed_pair : private __compressed_pair_elem<_T1, 0>,
private __compressed_pair_elem<_T2, 1> {
typedef __compressed_pair_elem<_T1, 0> _Base1;
typedef __compressed_pair_elem<_T2, 1> _Base2;
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>;

// NOTE: This static assert should never fire because __compressed_pair
// is *almost never* used in a scenario where it's possible for T1 == T2.
Expand Down Expand Up @@ -271,7 +271,7 @@ namespace wistd // ("Windows Implementation" std)
__WI_LIBCPP_INLINE_VISIBILITY
default_delete(const default_delete<_Up>&,
typename enable_if<is_convertible<_Up*, _Tp*>::value>::type* =
0) WI_NOEXCEPT {}
nullptr) WI_NOEXCEPT {}

__WI_LIBCPP_INLINE_VISIBILITY void operator()(_Tp* __ptr) const WI_NOEXCEPT {
static_assert(sizeof(_Tp) > 0,
Expand Down Expand Up @@ -299,7 +299,7 @@ namespace wistd // ("Windows Implementation" std)
template <class _Up>
__WI_LIBCPP_INLINE_VISIBILITY
default_delete(const default_delete<_Up[]>&,
typename _EnableIfConvertible<_Up>::type* = 0) WI_NOEXCEPT {}
typename _EnableIfConvertible<_Up>::type* = nullptr) WI_NOEXCEPT {}

template <class _Up>
__WI_LIBCPP_INLINE_VISIBILITY
Expand All @@ -319,32 +319,32 @@ namespace wistd // ("Windows Implementation" std)
template <class _Deleter>
struct __unique_ptr_deleter_sfinae {
static_assert(!is_reference<_Deleter>::value, "incorrect specialization");
typedef const _Deleter& __lval_ref_type;
typedef _Deleter&& __good_rval_ref_type;
typedef true_type __enable_rval_overload;
using __lval_ref_type = const _Deleter&;
using __good_rval_ref_type = _Deleter&&;
using __enable_rval_overload = true_type;
};

template <class _Deleter>
struct __unique_ptr_deleter_sfinae<_Deleter const&> {
typedef const _Deleter& __lval_ref_type;
typedef const _Deleter&& __bad_rval_ref_type;
typedef false_type __enable_rval_overload;
using __lval_ref_type = const _Deleter&;
using __bad_rval_ref_type = const _Deleter&&;
using __enable_rval_overload = false_type;
};

template <class _Deleter>
struct __unique_ptr_deleter_sfinae<_Deleter&> {
typedef _Deleter& __lval_ref_type;
typedef _Deleter&& __bad_rval_ref_type;
typedef false_type __enable_rval_overload;
using __lval_ref_type = _Deleter&;
using __bad_rval_ref_type = _Deleter&&;
using __enable_rval_overload = false_type;
};
#endif // !defined(__WI_LIBCPP_CXX03_LANG)

template <class _Tp, class _Dp = default_delete<_Tp> >
class __WI_LIBCPP_TEMPLATE_VIS unique_ptr {
public:
typedef _Tp element_type;
typedef _Dp deleter_type;
typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
using element_type = _Tp;
using deleter_type = _Dp;
using pointer = typename __pointer_type<_Tp, deleter_type>::type;

static_assert(!is_rvalue_reference<deleter_type>::value,
"the specified deleter type cannot be an rvalue reference");
Expand All @@ -355,7 +355,7 @@ namespace wistd // ("Windows Implementation" std)
struct __nat { int __for_bool_; };

#ifndef __WI_LIBCPP_CXX03_LANG
typedef __unique_ptr_deleter_sfinae<_Dp> _DeleterSFINAE;
using _DeleterSFINAE = __unique_ptr_deleter_sfinae<_Dp>;

template <bool _Dummy>
using _LValRefType =
Expand Down Expand Up @@ -582,9 +582,9 @@ namespace wistd // ("Windows Implementation" std)
template <class _Tp, class _Dp>
class __WI_LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> {
public:
typedef _Tp element_type;
typedef _Dp deleter_type;
typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
using element_type = _Tp;
using deleter_type = _Dp;
using pointer = typename __pointer_type<_Tp, deleter_type>::type;

private:
__compressed_pair<pointer, deleter_type> __ptr_;
Expand All @@ -602,7 +602,7 @@ namespace wistd // ("Windows Implementation" std)
{};

#ifndef __WI_LIBCPP_CXX03_LANG
typedef __unique_ptr_deleter_sfinae<_Dp> _DeleterSFINAE;
using _DeleterSFINAE = __unique_ptr_deleter_sfinae<_Dp>;

template <bool _Dummy>
using _LValRefType =
Expand Down
43 changes: 43 additions & 0 deletions Externals/WIL/include/wil/wrl.h
Expand Up @@ -14,6 +14,11 @@
#include <wrl.h>
#include "result.h"
#include "common.h" // wistd type_traits helpers
#include <libloaderapi.h> // GetModuleHandleW

/// @cond
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
/// @endcond

namespace wil
{
Expand Down Expand Up @@ -79,6 +84,44 @@ namespace wil
return result;
}
#endif // WIL_ENABLE_EXCEPTIONS

/** Holds a reference to the host WRL module to prevent it from being unloaded.
Normally, the reference is held implicitly because you are a member function
of a DLL-hosted COM object, or because you retain a strong reference
to some DLL-hosted COM object, but if those do not apply to you, then you
will need to hold a reference explicitly. For examples (and for the C++/WinRT
equivalent), see winrt_module_reference.
*/
struct [[nodiscard]] wrl_module_reference
{
wrl_module_reference()
{
if (auto modulePtr = ::Microsoft::WRL::GetModuleBase())
{
modulePtr->IncrementObjectCount();
}
else
{
#ifdef GET_MODULE_HANDLE_EX_FLAG_PIN
// If this assertion fails, then you are using wrl_module_reference
// from a DLL that does not host WRL objects, and the module reference
// has no effect.
WI_ASSERT(reinterpret_cast<HMODULE>(&__ImageBase) == GetModuleHandleW(nullptr));
#endif
}
}

wrl_module_reference(wrl_module_reference const&) : wrl_module_reference() {}

~wrl_module_reference()
{
if (auto modulePtr = ::Microsoft::WRL::GetModuleBase())
{
modulePtr->DecrementObjectCount();
}
}
};

} // namespace wil

#endif // __WIL_WRL_INCLUDED
114 changes: 114 additions & 0 deletions Externals/WIL/natvis/wil.natvis
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) Microsoft. All rights reserved.
This code is licensed under the MIT License.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT.
-->
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="wistd::_Func_impl&lt;*&gt;">
<DisplayString>{_Callee._Object}</DisplayString>
<Expand>
</Expand>
</Type>

<Type Name="wistd::function&lt;*&gt;">
<DisplayString Condition="_Impl == 0">empty</DisplayString>
<DisplayString Condition="_Impl != 0">{*_Impl}</DisplayString>
<Expand>
<ExpandedItem Condition="_Impl != 0">*_Impl</ExpandedItem>
</Expand>
</Type>

<Type Name="wistd::unique_ptr&lt;*&gt;">
<DisplayString Condition="_Myptr == 0">empty</DisplayString>
<DisplayString Condition="_Myptr != 0">{*_Myptr}</DisplayString>
<StringView>_Myptr</StringView>
<Expand>
<Item Name="[pointer]" Condition="_Myptr != 0">_Myptr</Item>
</Expand>
</Type>

<Type Name="wistd::unique_ptr&lt;wchar_t[*],*&gt;">
<DisplayString Condition="_Myptr == 0">empty</DisplayString>
<DisplayString Condition="_Myptr != 0">{_Myptr,su}</DisplayString>
<StringView>_Myptr</StringView>
<Expand>
<Item Name="[pointer]" Condition="_Myptr != 0">_Myptr</Item>
<Item Name="[length]" Condition="_Myptr != 0">wcslen(_Myptr)</Item>
</Expand>
</Type>

<Type Name="wistd::unique_ptr&lt;char[*],*&gt;">
<DisplayString Condition="_Myptr == 0">empty</DisplayString>
<DisplayString Condition="_Myptr != 0">{_Myptr,s}</DisplayString>
<StringView>_Myptr</StringView>
<Expand>
<Item Name="[pointer]" Condition="_Myptr != 0">_Myptr</Item>
<Item Name="[length]" Condition="_Myptr != 0">strlen(_Myptr)</Item>
</Expand>
</Type>

<Type Name="wil::details::shared_storage&lt;*&gt;">
<DisplayString Condition="m_ptr == 0">empty</DisplayString>
<DisplayString Condition="m_ptr != 0">{*m_ptr}</DisplayString>
<StringView>m_ptr</StringView>
<Expand>
<Item Name="[pointer]" Condition="m_ptr != 0">m_ptr</Item>
</Expand>
</Type>

<Type Name="wil::details::unique_storage&lt;wil::details::handle_null_resource_policy&lt;*&gt;&gt;">
<DisplayString Condition="m_ptr == 0">empty</DisplayString>
<DisplayString Condition="m_ptr != 0">{m_ptr}</DisplayString>
<Expand>
</Expand>
</Type>
<Type Name="wil::details::unique_storage&lt;wil::details::handle_invalid_resource_policy&lt;*&gt;&gt;">
<DisplayString Condition="m_ptr == 0">empty</DisplayString>
<DisplayString Condition="m_ptr != 0">{m_ptr}</DisplayString>
<Expand>
</Expand>
</Type>
<Type Name="wil::details::unique_storage&lt;wil::details::resource_policy&lt;*&gt;&gt;">
<DisplayString Condition="m_ptr == 0">empty</DisplayString>
<DisplayString Condition="m_ptr != 0">{m_ptr}</DisplayString>
<StringView>m_ptr</StringView>
<Expand>
</Expand>
</Type>
<Type Name="wil::details::unique_storage&lt;wil::details::resource_policy&lt;wchar_t *,*&gt;&gt;">
<DisplayString Condition="m_ptr == 0">empty</DisplayString>
<DisplayString Condition="m_ptr != 0">{m_ptr,su}</DisplayString>
<StringView>m_ptr</StringView>
<Expand>
<Item Name="[length]" Condition="m_ptr != 0">wcslen(m_ptr)</Item>
</Expand>
</Type>
<Type Name="wil::details::unique_storage&lt;wil::details::resource_policy&lt;char *,*&gt;&gt;">
<DisplayString Condition="m_ptr == 0">empty</DisplayString>
<DisplayString Condition="m_ptr != 0">{m_ptr,s}</DisplayString>
<StringView>m_ptr</StringView>
<Expand>
<Item Name="[length]" Condition="m_ptr != 0">strlen(m_ptr)</Item>
</Expand>
</Type>

<Type Name="wil::com_ptr_t&lt;*&gt;">
<DisplayString>{m_ptr}</DisplayString>
<Expand>
<ExpandedItem>m_ptr</ExpandedItem>
</Expand>
</Type>

<Type Name="wil::basic_zstring_view&lt;*&gt;">
<Intrinsic Name="size" Expression="_Mysize" />
<Intrinsic Name="data" Expression="_Mydata" />
<DisplayString>{_Mydata,[_Mysize]}</DisplayString>
<Expand>
<Item Name="[size]" ExcludeView="simple">size()</Item>
</Expand>
</Type>
</AutoVisualizer>
2 changes: 2 additions & 0 deletions Externals/WIL/packaging/nuget/CMakeLists.txt
Expand Up @@ -7,13 +7,15 @@ file(TO_NATIVE_PATH "${nupkg_dir}/Microsoft.Windows.ImplementationLibrary.${WIL_
file(DOWNLOAD https://dist.nuget.org/win-x86-commandline/latest/nuget.exe ${nuget_exe})

file(GLOB_RECURSE wil_headers ${CMAKE_SOURCE_DIR}/include/*.h)
file(GLOB_RECURSE wil_natvis ${CMAKE_SOURCE_DIR}/natvis/*.natvis)

add_custom_command(OUTPUT ${wil_nupkg}
COMMAND ${nuget_exe} pack ${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.Windows.ImplementationLibrary.nuspec -OutputDirectory ${nupkg_dir} -Version ${WIL_BUILD_VERSION} -NonInteractive
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.Windows.ImplementationLibrary.nuspec
${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.Windows.ImplementationLibrary.targets
${wil_headers}
${wil_natvis}
${CMAKE_SOURCE_DIR}/LICENSE
${CMAKE_SOURCE_DIR}/ThirdPartyNotices.txt)

Expand Down
Expand Up @@ -16,6 +16,7 @@
<file src="..\..\LICENSE"/>
<file src="..\..\ThirdPartyNotices.txt"/>
<file src="..\..\include\**" target="include\" />
<file src="..\..\natvis\wil.natvis" target="natvis\" />
<file src="Microsoft.Windows.ImplementationLibrary.targets" target="build\native\" />
</files>
</package>
Expand Up @@ -5,4 +5,7 @@
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis"/>
</ItemGroup>
</Project>
12 changes: 8 additions & 4 deletions Externals/WIL/scripts/azure-pipelines.yml
Expand Up @@ -8,7 +8,7 @@ jobs:
timeoutInMinutes: 360

pool:
vmImage: 'windows-2019'
vmImage: 'windows-2022'

steps:
- script: |
Expand All @@ -18,7 +18,7 @@ jobs:
displayName: 'Install Clang'
- script: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\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
Expand All @@ -28,7 +28,7 @@ jobs:
displayName: 'Build x86'
- script: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
if %ERRORLEVEL% NEQ 0 goto :eof
call scripts\init_all.cmd --fast
Expand All @@ -37,5 +37,9 @@ jobs:
call scripts\build_all.cmd
displayName: 'Build x64'
- script: call scripts\runtests.cmd
# 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'
30 changes: 12 additions & 18 deletions Externals/WIL/scripts/build_all.cmd
@@ -1,4 +1,5 @@
@echo off
setlocal
setlocal EnableDelayedExpansion

set BUILD_ROOT=%~dp0\..\build
Expand All @@ -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 )

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 )
set COMPILERS=clang msvc
set BUILD_TYPES=debug release relwithdebinfo minsizerel

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!

Expand All @@ -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%
47 changes: 19 additions & 28 deletions Externals/WIL/scripts/init.cmd
Expand Up @@ -10,8 +10,8 @@ goto :init
:usage
echo USAGE:
echo init.cmd [--help] [-c^|--compiler ^<clang^|msvc^>] [-g^|--generator ^<ninja^|msbuild^>]
echo [-b^|--build-type ^<debug^|release^|relwithdebinfo^|minsizerel^>] [-l^|--linker link^|lld-link]
echo [--fast] [-v^|--version X.Y.Z]
echo [-b^|--build-type ^<debug^|release^|relwithdebinfo^|minsizerel^>] [-v^|--version X.Y.Z]
echo [--cppwinrt ^<version^>] [--fast]
echo.
echo ARGUMENTS
echo -c^|--compiler Controls the compiler used, either 'clang' (the default) or 'msvc'
Expand All @@ -20,6 +20,7 @@ goto :init
echo 'relwithdebinfo', or 'minsizerel'
echo -v^|--version Specifies the version of the NuGet package produced. Primarily only used by the CI
echo build and is typically not necessary when building locally
echo --cppwinrt Manually specifies the version of C++/WinRT to use for generating headers
echo --fast Used to (slightly) reduce compile times and build output size. This is primarily
echo used by the CI build machines where resources are more constrained. This switch is
echo temporary and will be removed once https://github.com/microsoft/wil/issues/9 is fixed
Expand All @@ -30,10 +31,10 @@ goto :init
set COMPILER=
set GENERATOR=
set BUILD_TYPE=
set LINKER=
set CMAKE_ARGS=
set BITNESS=
set VERSION=
set CPPWINRT_VERSION=
set FAST_BUILD=0

:parse
Expand Down Expand Up @@ -88,29 +89,25 @@ 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
set VERSION_SET=0
if /I "%~1"=="-v" set VERSION_SET=1
if /I "%~1"=="--version" set VERSION_SET=1
if %VERSION_SET%==1 (
if "%VERSION%" NEQ "" echo ERROR: Version already specified & call :usage & exit /B 1
if /I "%~2"=="" echo ERROR: Version string missing & 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
set VERSION=%~2

shift
shift
goto :parse
)

set VERSION_SET=0
if /I "%~1"=="-v" set VERSION_SET=1
if /I "%~1"=="--version" set VERSION_SET=1
if %VERSION_SET%==1 (
if "%VERSION%" NEQ "" echo ERROR: Version alread specified & call :usage & exit /B 1
if /I "%~2"=="" echo ERROR: Version string missing & call :usage & exit /B 1
if /I "%~1"=="--cppwinrt" (
if "%CPPWINRT_VERSION%" NEQ "" echo ERROR: C++/WinRT version already specified & call :usage & exit /B 1
if /I "%~2"=="" echo ERROR: C++/WinRT version string missing & call :usage & exit /B 1

set VERSION=%~2
set CPPWINRT_VERSION=%~2

shift
shift
Expand All @@ -132,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
Expand All @@ -145,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

Expand All @@ -167,14 +159,14 @@ 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%

if %FAST_BUILD%==1 set CMAKE_ARGS=%CMAKE_ARGS% -DFAST_BUILD=ON

set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

:: Figure out the platform
if "%Platform%"=="" echo ERROR: The init.cmd script must be run from a Visual Studio command window & exit /B 1
if "%Platform%"=="x86" (
Expand All @@ -191,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%
Expand Down
19 changes: 10 additions & 9 deletions Externals/WIL/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 )
)
)
72 changes: 30 additions & 42 deletions Externals/WIL/scripts/runtests.cmd
@@ -1,44 +1,24 @@
@echo off
setlocal
setlocal EnableDelayedExpansion

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 )

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 )
set COMPILERS=clang msvc
set ARCHITECTURES=32 64
set BUILD_TYPES=debug release relwithdebinfo minsizerel

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

Expand All @@ -49,19 +29,27 @@ 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 ( goto :execute_tests_done )

:execute_tests_done
set EXIT_CODE=%ERRORLEVEL%
popd

goto :eof
exit /B %EXIT_CODE%

:execute_test
if not exist tests\%1\%2 ( goto :eof )
echo Running %1 tests...
tests\%1\%2
tests\%1\%2 %TEST_ARGS%
goto :eof
83 changes: 81 additions & 2 deletions Externals/WIL/tests/CMakeLists.txt
@@ -1,19 +1,98 @@

include(${CMAKE_SOURCE_DIR}/cmake/common_build_flags.cmake)
include(${PROJECT_SOURCE_DIR}/cmake/common_build_flags.cmake)

# All projects need to reference the WIL headers
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/include)

# TODO: Might be worth trying to conditionally do this on SDK version, assuming there's a semi-easy way to detect that
include_directories(BEFORE SYSTEM ./workarounds/wrl)

# Because we don't always use msbuild, we need to run nuget manually
find_program(NUGET nuget)
if (NOT NUGET)
message(FATAL_ERROR "Unable to find the nuget CLI tool. Please install it from https://www.nuget.org/downloads and ensure it has been added to the PATH")
endif()

execute_process(COMMAND
${NUGET} install Microsoft.Windows.CppWinRT -Version ${CPPWINRT_VERSION} -OutputDirectory packages
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
RESULT_VARIABLE ret)
if (NOT ret EQUAL 0)
message(FATAL_ERROR "Failed to install nuget package Microsoft.Windows.CppWinRT.${CPPWINRT_VERSION}")
endif()

set(CPPWINRT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT.${CPPWINRT_VERSION}/bin/cppwinrt.exe)
execute_process(COMMAND
${CPPWINRT} -input sdk -output include
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
RESULT_VARIABLE ret)
if (NOT ret EQUAL 0)
message(FATAL_ERROR "Failed to run cppwinrt.exe")
endif()

include_directories(BEFORE SYSTEM ${CMAKE_BINARY_DIR}/include)

# The build pipelines have limitations that local development environments do not, so turn a few knobs
if (${FAST_BUILD})
replace_cxx_flag("/GR" "/GR-") # Disables RTTI
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
${CMAKE_CURRENT_SOURCE_DIR}/ComTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/FileSystemTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NTResultTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ResourceTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ResultTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Rpc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SafeCastTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TraceLoggingTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/WistdTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/wiTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../natvis/wil.natvis
)

add_subdirectory(app)
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()
401 changes: 401 additions & 0 deletions Externals/WIL/tests/ComApartmentVariableTests.cpp

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions Externals/WIL/tests/ComTests.cpp
Expand Up @@ -6,6 +6,8 @@

#include "common.h"

#include <Bits.h>

using namespace Microsoft::WRL;

// avoid including #include <shobjidl.h>, it fails to compile in noprivateapis
Expand Down Expand Up @@ -130,6 +132,25 @@ TEST_CASE("ComTests::Test_Constructors", "[com][com_ptr]")
REQUIRE(ptrMove2.get() == &helper4);
REQUIRE(ptr2.get() == nullptr);
}

#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201907L)
SECTION("CTAD pointer construction")
{
wil::com_ptr_nothrow ptr(&helper); // explicit
REQUIRE(IUnknownFake::GetAddRef() == 1);
REQUIRE(ptr.get() == &helper);
}
#endif
}

TEST_CASE("ComTests::Test_Make", "[com][com_ptr]")
{
IUnknownFake::Clear();
IUnknownFake helper;

auto ptr = wil::make_com_ptr_nothrow(&helper); // CTAD workaround for pre-C++20
REQUIRE(IUnknownFake::GetAddRef() == 1);
REQUIRE(ptr.get() == &helper);
}

TEST_CASE("ComTests::Test_Assign", "[com][com_ptr]")
Expand Down Expand Up @@ -2231,6 +2252,90 @@ TEST_CASE("ComTests::VerifyCoGetClassObject", "[com][CoGetClassObject]")
}
#endif

#if defined(__IBackgroundCopyManager_INTERFACE_DEFINED__) && (__WI_LIBCPP_STD_VER >= 17)
TEST_CASE("ComTests::VerifyCoCreateEx", "[com][CoCreateInstance]")
{
auto init = wil::CoInitializeEx_failfast();

{
#ifdef WIL_ENABLE_EXCEPTIONS
auto [sp1, ps1] = wil::CoCreateInstanceEx<IBackgroundCopyManager, IUnknown>(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE((sp1 && ps1));
#endif
auto [hr, unk] = wil::CoCreateInstanceExNoThrow<IBackgroundCopyManager, IUnknown>(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE_SUCCEEDED(hr);
auto sp = std::get<0>(unk);
auto ps = std::get<1>(unk);
REQUIRE((sp && ps));
auto [sp3, ps3] = wil::CoCreateInstanceExFailFast<IBackgroundCopyManager, IUnknown>(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE((sp3 && ps3));
}

#ifdef WIL_ENABLE_EXCEPTIONS
{
auto [ps, pf] = wil::CoCreateInstanceEx<IPersistStream, IPersistFile>(__uuidof(ShellLink), CLSCTX_INPROC_SERVER);
std::ignore = ps->IsDirty();
std::ignore = pf->IsDirty();
}
#endif
}

TEST_CASE("ComTests::VerifyCoCreateInstanceExNoThrowMissingInterface", "[com][CoCreateInstance]")
{
auto init = wil::CoInitializeEx_failfast();

{
// IPropertyBag is not implemented
auto [error, result] = wil::CoCreateInstanceExNoThrow<IBackgroundCopyManager, IUnknown, IPropertyBag>
(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE(error == E_NOINTERFACE);
REQUIRE(std::get<0>(result).get() == nullptr);
REQUIRE(std::get<1>(result).get() == nullptr);
REQUIRE(std::get<2>(result).get() == nullptr);
}
}

TEST_CASE("ComTests::VerifyTryCoCreateInstanceMissingInterface", "[com][CoCreateInstance]")
{
auto init = wil::CoInitializeEx_failfast();

// request some implemented, one not (IPropertyBag), partial results enabled
{
auto [sp, pb] = wil::TryCoCreateInstanceEx<IBackgroundCopyManager, IPropertyBag>
(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE(sp != nullptr);
REQUIRE(pb == nullptr);
}
{
auto [sp, pb] = wil::TryCoCreateInstanceExNoThrow<IBackgroundCopyManager, IPropertyBag>
(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE(sp != nullptr);
REQUIRE(pb == nullptr);
}
{
auto [sp, pb] = wil::TryCoCreateInstanceExFailFast<IBackgroundCopyManager, IPropertyBag>
(__uuidof(BackgroundCopyManager), CLSCTX_LOCAL_SERVER);
REQUIRE(sp != nullptr);
REQUIRE(pb == nullptr);
}
}

#ifdef WIL_ENABLE_EXCEPTIONS
TEST_CASE("ComTests::VerifyQueryMultipleInterfaces", "[com][com_multi_query]")
{
auto init = wil::CoInitializeEx_failfast();

auto mgr = wil::CoCreateInstance<BackgroundCopyManager>(CLSCTX_LOCAL_SERVER);
auto [sp, ps] = wil::com_multi_query<IBackgroundCopyManager, IUnknown>(mgr.get());
REQUIRE(sp);
REQUIRE(ps);
auto [sp1, pb] = wil::try_com_multi_query<IBackgroundCopyManager, IPropertyBag>(mgr.get());
REQUIRE(sp1);
REQUIRE(!pb);
}
#endif
#endif // __IBackgroundCopyManager_INTERFACE_DEFINED__

#ifdef __IObjectWithSite_INTERFACE_DEFINED__
TEST_CASE("ComTests::VerifyComSetSiteNullIsMoveOnly", "[com][com_set_site]")
{
Expand Down
2 changes: 1 addition & 1 deletion Externals/WIL/tests/CommonTests.cpp
Expand Up @@ -159,7 +159,7 @@ enum class EClassTest
};
DEFINE_ENUM_FLAG_OPERATORS(EClassTest);

enum ERawTest
enum ERawTest : unsigned int
{
ER_None = 0x0,
ER_One = 0x1,
Expand Down
2 changes: 2 additions & 0 deletions Externals/WIL/tests/CppWinRT20Tests.cpp
Expand Up @@ -3,6 +3,8 @@
// However, since we're going to link into the same executable as 'CppWinRTTests.cpp', the 'winrt_to_hresult_handler'
// global function pointer should be set, so these should all run successfully

#include <inspectable.h> // Must be included before base.h

#include <winrt/base.h>
#include <wil/result.h>

Expand Down
300 changes: 300 additions & 0 deletions Externals/WIL/tests/CppWinRTTests.cpp
@@ -1,5 +1,10 @@

#include <wil/cppwinrt.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <wil/cppwinrt_helpers.h>
#include <winrt/Windows.System.h>
#include <wil/cppwinrt_helpers.h> // Verify can include a second time to unlock more features

#include "catch.hpp"

Expand All @@ -22,6 +27,145 @@ static const HRESULT cppwinrt_mapped_hresults[] =
E_OUTOFMEMORY,
};

template<typename T> auto copy_thing(T const& src)
{
return std::decay_t<T>(src);
}

template<typename T, typename K>
void CheckMapVector(std::vector<winrt::Windows::Foundation::Collections::IKeyValuePair<T, K>> const& test, std::map<T, K> const& src)
{
REQUIRE(test.size() == src.size());
for (auto&& i : test)
{
REQUIRE(i.Value() == src.at(i.Key()));
}
}

struct vector_like
{
uint32_t Size() const { return 100; }
int GetAt(uint32_t) const { return 15; }

uint32_t GetMany(uint32_t start, winrt::array_view<int> items) const
{
if (start > 0)
{
throw winrt::hresult_out_of_bounds();
}
uint32_t const to_fill = (std::min)(items.size(), Size());
std::fill_n(items.begin(), to_fill, GetAt(0));
return to_fill;
}
};

struct iterator_like
{
static const uint32_t total = 20;
mutable uint32_t remaining = total;
int Current() const { return 3; }

uint32_t GetMany(winrt::array_view<int> items) const
{
auto to_copy = (std::min)(items.size(), remaining);
std::fill_n(items.begin(), to_copy, Current());
remaining -= to_copy;
return to_copy;
}
};


struct iterable_like
{
auto First() const { return iterator_like{}; }
};

struct unstable_vector : winrt::implements<unstable_vector, winrt::Windows::Foundation::Collections::IVectorView<int>>
{
auto Size() { return 4; }
int GetAt(uint32_t) { return 7; }

uint32_t GetMany(uint32_t, winrt::array_view<int> items)
{
std::fill(items.begin(), items.end(), GetAt(0));
return items.size();
}

bool IndexOf(int, uint32_t) { throw winrt::hresult_not_implemented(); }
};

TEST_CASE("CppWinRTTests::VectorToVector", "[cppwinrt]")
{
winrt::init_apartment();
{
std::vector<winrt::hstring> src_vector = { L"foo", L"bar", L"bas" };
auto sv = winrt::single_threaded_vector(copy_thing(src_vector));
REQUIRE(wil::to_vector(sv) == src_vector);
REQUIRE(wil::to_vector(sv.GetView()) == src_vector);
REQUIRE(wil::to_vector(sv.First()) == src_vector);
REQUIRE(wil::to_vector(sv.First()) == src_vector);
REQUIRE(wil::to_vector(sv.as<winrt::Windows::Foundation::Collections::IIterable<winrt::hstring>>()) == src_vector);
}
{
std::vector<uint32_t> src_vector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
auto sv = winrt::single_threaded_vector(copy_thing(src_vector));
REQUIRE(wil::to_vector(sv) == src_vector);
REQUIRE(wil::to_vector(sv.GetView()) == src_vector);
REQUIRE(wil::to_vector(sv.First()) == src_vector);
REQUIRE(wil::to_vector(sv.as<winrt::Windows::Foundation::Collections::IIterable<uint32_t>>()) == src_vector);
}
{
std::vector<float> src_vector;
auto sv = winrt::single_threaded_vector(copy_thing(src_vector));
REQUIRE(wil::to_vector(sv) == src_vector);
REQUIRE(wil::to_vector(sv.GetView()) == src_vector);
REQUIRE(wil::to_vector(sv.First()) == src_vector);
REQUIRE(wil::to_vector(sv.as<winrt::Windows::Foundation::Collections::IIterable<float>>()) == src_vector);
}
{
std::map<winrt::hstring, winrt::hstring> src_map{{L"kittens", L"fluffy"}, {L"puppies", L"cute"}};
auto sm = winrt::single_threaded_map(copy_thing(src_map));
CheckMapVector(wil::to_vector(sm), src_map);
CheckMapVector(wil::to_vector(sm.GetView()), src_map);
CheckMapVector(wil::to_vector(sm.First()), src_map);
}
{
winrt::Windows::Foundation::Collections::PropertySet props;
props.Insert(L"kitten", winrt::box_value(L"fluffy"));
props.Insert(L"puppy", winrt::box_value<uint32_t>(25));
auto converted = wil::to_vector(props);
REQUIRE(converted.size() == props.Size());
for (auto&& kv : converted)
{
if (kv.Key() == L"kitten")
{
REQUIRE(kv.Value().as<winrt::hstring>() == L"fluffy");
}
else if (kv.Key() == L"puppy")
{
REQUIRE(kv.Value().as<uint32_t>() == 25);
}
else
{
REQUIRE(false);
}
}
}

REQUIRE_THROWS(wil::to_vector(winrt::make<unstable_vector>()));

auto ilike = wil::to_vector(iterable_like{});
REQUIRE(ilike.size() == iterator_like::total);
for (auto&& i : ilike) REQUIRE(i == iterator_like{}.Current());

auto vlike = wil::to_vector(vector_like{});
REQUIRE(vlike.size() == vector_like{}.Size());
for (auto&& i : vlike) REQUIRE(i == vector_like{}.GetAt(0));

winrt::clear_factory_cache();
winrt::uninit_apartment();
}

TEST_CASE("CppWinRTTests::WilToCppWinRTExceptionTranslationTest", "[cppwinrt]")
{
auto test = [](HRESULT hr)
Expand Down Expand Up @@ -142,3 +286,159 @@ TEST_CASE("CppWinRTTests::CppWinRTConsistencyTest", "[cppwinrt]")
// NOTE: C++/WinRT maps other 'std::exception' derived exceptions to E_FAIL, however we preserve the WIL behavior
// that such exceptions become HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION)
}

TEST_CASE("CppWinRTTests::ModuleReference", "[cppwinrt]")
{
auto peek_module_ref_count = []()
{
++winrt::get_module_lock();
return --winrt::get_module_lock();
};

auto initial = peek_module_ref_count();

// Basic test: Construct and destruct.
{
auto module_ref = wil::winrt_module_reference();
REQUIRE(peek_module_ref_count() == initial + 1);
}
REQUIRE(peek_module_ref_count() == initial);

// Fancy test: Copy object with embedded reference.
{
struct object_with_ref
{
wil::winrt_module_reference ref;
};
object_with_ref o1;
REQUIRE(peek_module_ref_count() == initial + 1);
auto o2 = o1;
REQUIRE(peek_module_ref_count() == initial + 2);
o1 = o2;
REQUIRE(peek_module_ref_count() == initial + 2);
o2 = std::move(o1);
REQUIRE(peek_module_ref_count() == initial + 2);
}
REQUIRE(peek_module_ref_count() == initial);
}

#if (!defined(__clang__) && defined(__cpp_lib_coroutine) && (__cpp_lib_coroutine >= 201902L)) || defined(_RESUMABLE_FUNCTIONS_SUPPORTED)

// Define our own custom dispatcher that we can force it to behave in certain ways.
// wil::resume_foreground supports any dispatcher that has a dispatcher_traits.

namespace test
{
enum class TestDispatcherPriority
{
Normal = 0,
Weird = 1,
};

using TestDispatcherHandler = winrt::delegate<>;

enum class TestDispatcherMode
{
Dispatch,
RaceDispatch,
Orphan,
Fail,
};

struct TestDispatcher
{
TestDispatcher() = default;
TestDispatcher(TestDispatcher const&) = delete;

TestDispatcherMode mode = TestDispatcherMode::Dispatch;
TestDispatcherPriority expected_priority = TestDispatcherPriority::Normal;

void TryEnqueue(TestDispatcherPriority priority, TestDispatcherHandler const& handler) const
{
REQUIRE(priority == expected_priority);

if (mode == TestDispatcherMode::Fail)
{
throw winrt::hresult_not_implemented();
}

if (mode == TestDispatcherMode::RaceDispatch)
{
handler();
return;
}

std::ignore = [](auto mode, auto handler) ->winrt::fire_and_forget
{
co_await winrt::resume_background();
if (mode == TestDispatcherMode::Dispatch)
{
handler();
}
}(mode, handler);
}
};
}

namespace wil::details
{
template<>
struct dispatcher_traits<test::TestDispatcher>
{
using Priority = test::TestDispatcherPriority;
using Handler = test::TestDispatcherHandler;
using Scheduler = dispatcher_TryEnqueue;
};
}

TEST_CASE("CppWinRTTests::ResumeForegroundTests", "[cppwinrt]")
{
// Verify that the DispatcherQueue version has been unlocked.
using Verify = decltype(wil::resume_foreground(winrt::Windows::System::DispatcherQueue{ nullptr }));
static_assert(wistd::is_trivial_v<Verify> || !wistd::is_trivial_v<Verify>);

[]() -> winrt::Windows::Foundation::IAsyncAction
{
test::TestDispatcher dispatcher;

// Normal case: Resumes on new thread.
dispatcher.mode = test::TestDispatcherMode::Dispatch;
co_await wil::resume_foreground(dispatcher);

// Race case: Resumes before TryEnqueue returns.
dispatcher.mode = test::TestDispatcherMode::RaceDispatch;
co_await wil::resume_foreground(dispatcher);

// Orphan case: Never resumes, detected when handler is destructed without ever being invoked.
dispatcher.mode = test::TestDispatcherMode::Orphan;
bool seen = false;
try
{
co_await wil::resume_foreground(dispatcher);
}
catch (winrt::hresult_error const& e)
{
seen = e.code() == HRESULT_FROM_WIN32(HRESULT_FROM_WIN32(ERROR_NO_TASK_QUEUE));
}
REQUIRE(seen);

// Fail case: Can't even schedule the resumption.
dispatcher.mode = test::TestDispatcherMode::Fail;
seen = false;
try
{
co_await wil::resume_foreground(dispatcher);
}
catch (winrt::hresult_not_implemented const&)
{
seen = true;
}
REQUIRE(seen);

// Custom priority.
dispatcher.mode = test::TestDispatcherMode::Dispatch;
dispatcher.expected_priority = test::TestDispatcherPriority::Weird;
co_await wil::resume_foreground(dispatcher, test::TestDispatcherPriority::Weird);
}().get();
}
#endif // coroutines
12 changes: 6 additions & 6 deletions Externals/WIL/tests/FakeWinRTTypes.h
Expand Up @@ -42,7 +42,7 @@ struct WinRTStorage<HSTRING>
{
Microsoft::WRL::Wrappers::HString value;

HRESULT CopyTo(HSTRING* result)
HRESULT CopyTo(HSTRING* result) const
{
return value.CopyTo(result);
}
Expand All @@ -63,7 +63,7 @@ struct WinRTStorage<HSTRING>
value = {};
}

bool Equals(HSTRING val)
bool Equals(HSTRING val) const
{
return value == val;
}
Expand Down Expand Up @@ -207,7 +207,7 @@ struct FakeAsyncOperation : Microsoft::WRL::RuntimeClass<
WinRTStorage<Abi> m_storage;
};

template <typename Logical, typename Abi = Logical, size_t MaxSize = 42>
template <typename Logical, typename Abi = Logical, size_t MaxSize = 250>
struct FakeVector : Microsoft::WRL::RuntimeClass<
ABI::Windows::Foundation::Collections::IVector<Logical>,
ABI::Windows::Foundation::Collections::IVectorView<Logical>>
Expand Down Expand Up @@ -292,7 +292,7 @@ struct FakeVector : Microsoft::WRL::RuntimeClass<

for (size_t i = index + 1; i < m_size; ++i)
{
wistd::swap_wil(m_data[i - 1], m_data[i]);
wistd::swap_wil(m_data[i], m_data[i - 1]);
}

m_data[--m_size].Reset();
Expand Down Expand Up @@ -349,8 +349,8 @@ struct FakeVector : Microsoft::WRL::RuntimeClass<
count = (count > capacity) ? capacity : count;

HRESULT hr = S_OK;
unsigned i = 0;
for (; (i < count) && SUCCEEDED(hr); ++i)
unsigned i;
for (i = 0; (i < count) && SUCCEEDED(hr); ++i)
{
hr = m_data[startIndex + i].CopyTo(value + i);
}
Expand Down
172 changes: 172 additions & 0 deletions Externals/WIL/tests/FileSystemTests.cpp
Expand Up @@ -34,6 +34,13 @@ bool DirectoryExists(_In_ PCWSTR path)
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}

bool FileExists(_In_ PCWSTR path)
{
DWORD dwAttrib = GetFileAttributesW(path);

return (dwAttrib != INVALID_FILE_ATTRIBUTES);
}

TEST_CASE("FileSystemTests::CreateDirectory", "[filesystem]")
{
wchar_t basePath[MAX_PATH];
Expand Down Expand Up @@ -80,6 +87,99 @@ TEST_CASE("FileSystemTests::CreateDirectory", "[filesystem]")
REQUIRE_FALSE(DirectoryExists(absoluteTestPath4));
}

TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveDoesNotTraverseWithoutAHandle", "[filesystem]")
{
auto CreateRelativePath = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
REQUIRE_SUCCEEDED(PathAllocCombine(root, name, PATHCCH_ALLOW_LONG_PATHS, &path));
return path;
};

wil::unique_cotaskmem_string tempPath;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(LR"(%TEMP%)", tempPath));
const auto basePath = CreateRelativePath(tempPath.get(), L"FileSystemTests");
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath.get()));

auto scopeGuard = wil::scope_exit([&]
{
wil::RemoveDirectoryRecursiveNoThrow(basePath.get());
});

// Try to delete a directory whose handle is already taken.
const auto folderToRecurse = CreateRelativePath(basePath.get(), L"folderToRecurse");
REQUIRE(::CreateDirectoryW(folderToRecurse.get(), nullptr));

const auto subfolderWithHandle = CreateRelativePath(folderToRecurse.get(), L"subfolderWithHandle");
REQUIRE(::CreateDirectoryW(subfolderWithHandle.get(), nullptr));

const auto childOfSubfolder = CreateRelativePath(subfolderWithHandle.get(), L"childOfSubfolder");
REQUIRE(::CreateDirectoryW(childOfSubfolder.get(), nullptr));

// Passing a 0 in share flags only allows metadata query on this file by other processes.
// This should fail with a sharing violation error when any other action is taken.
wil::unique_hfile subFolderHandle(::CreateFileW(subfolderWithHandle.get(), GENERIC_ALL,
0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
REQUIRE(subFolderHandle);

REQUIRE(wil::RemoveDirectoryRecursiveNoThrow(folderToRecurse.get()) == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION));

// Release the handle to allow cleanup.
subFolderHandle.reset();
}

TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveCanDeleteReadOnlyFiles", "[filesystem]")
{
auto CreateRelativePath = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
REQUIRE_SUCCEEDED(PathAllocCombine(root, name, PATHCCH_ALLOW_LONG_PATHS, &path));
return path;
};

auto CreateReadOnlyFile = [](PCWSTR path)
{
wil::unique_hfile fileHandle(CreateFileW(path, 0,
0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_READONLY, nullptr));
REQUIRE(fileHandle);
};

wil::unique_cotaskmem_string tempPath;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(LR"(%TEMP%)", tempPath));
const auto basePath = CreateRelativePath(tempPath.get(), L"FileSystemTests");
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath.get()));

auto scopeGuard = wil::scope_exit([&]
{
wil::RemoveDirectoryRecursiveNoThrow(basePath.get(), wil::RemoveDirectoryOptions::RemoveReadOnly);
});

// Create a reparse point and a target folder that shouldn't get deleted
auto folderToDelete = CreateRelativePath(basePath.get(), L"folderToDelete");
REQUIRE(::CreateDirectoryW(folderToDelete.get(), nullptr));

auto topLevelReadOnly = CreateRelativePath(folderToDelete.get(), L"topLevelReadOnly.txt");
CreateReadOnlyFile(topLevelReadOnly.get());

auto subLevel = CreateRelativePath(folderToDelete.get(), L"subLevel");
REQUIRE(::CreateDirectoryW(subLevel.get(), nullptr));

auto subLevelReadOnly = CreateRelativePath(subLevel.get(), L"subLevelReadOnly.txt");
CreateReadOnlyFile(subLevelReadOnly.get());

// Delete will fail without the RemoveReadOnlyFlag
REQUIRE_FAILED(wil::RemoveDirectoryRecursiveNoThrow(folderToDelete.get()));
REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(folderToDelete.get(), wil::RemoveDirectoryOptions::RemoveReadOnly));

// Verify all files have been deleted
REQUIRE_FALSE(FileExists(subLevelReadOnly.get()));
REQUIRE_FALSE(DirectoryExists(subLevel.get()));

REQUIRE_FALSE(FileExists(topLevelReadOnly.get()));
REQUIRE_FALSE(DirectoryExists(folderToDelete.get()));
}

#ifdef WIL_ENABLE_EXCEPTIONS
// Learn about the Win32 API normalization here: https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/
// This test verifies the ability of RemoveDirectoryRecursive to be able to delete files
Expand Down Expand Up @@ -440,8 +540,28 @@ TEST_CASE("FileSystemTests::VerifyGetModuleFileNameW", "[filesystem]")
REQUIRE(wcscmp(path.get(), path2.get()) == 0);

REQUIRE_FAILED(wil::GetModuleFileNameW((HMODULE)INVALID_HANDLE_VALUE, path));

#ifdef WIL_ENABLE_EXCEPTIONS
auto wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
#endif
}

#ifdef WIL_ENABLE_EXCEPTIONS
wil::unique_cotaskmem_string NativeGetModuleFileNameWrap(HANDLE processHandle, HMODULE moduleHandle)
{
DWORD size = MAX_PATH * 4;
auto path = wil::make_cotaskmem_string_nothrow(nullptr, size);

DWORD copied = processHandle ?
::GetModuleFileNameExW(processHandle, moduleHandle, path.get(), size) :
::GetModuleFileNameW(moduleHandle, path.get(), size);
REQUIRE(copied < size);

return path;
}
#endif

TEST_CASE("FileSystemTests::VerifyGetModuleFileNameExW", "[filesystem]")
{
wil::unique_cotaskmem_string path;
Expand All @@ -455,6 +575,58 @@ TEST_CASE("FileSystemTests::VerifyGetModuleFileNameExW", "[filesystem]")
REQUIRE(wcscmp(path.get(), path2.get()) == 0);

REQUIRE_FAILED(wil::GetModuleFileNameExW(nullptr, (HMODULE)INVALID_HANDLE_VALUE, path));

#ifdef WIL_ENABLE_EXCEPTIONS
auto wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(nullptr, nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, nullptr).get());

wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(GetCurrentProcess(), nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(GetCurrentProcess(), nullptr).get());

wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, nullptr).get());

HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll");

wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(nullptr, kernel32);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, kernel32).get());

wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(GetCurrentProcess(), kernel32);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(GetCurrentProcess(), kernel32).get());

wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(kernel32);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, kernel32).get());
#endif
}

TEST_CASE("FileSystemTests::QueryFullProcessImageNameW and GetModuleFileNameW", "[filesystem]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
auto procName = wil::QueryFullProcessImageNameW<std::wstring>();
auto moduleName = wil::GetModuleFileNameW<std::wstring>();
REQUIRE(procName == moduleName);
#endif
}

TEST_CASE("FileSystemTests::QueryFullProcessImageNameW", "[filesystem]")
{
WCHAR fullName[MAX_PATH * 4];
DWORD fullNameSize = ARRAYSIZE(fullName);
REQUIRE(::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, fullName, &fullNameSize));

wil::unique_cotaskmem_string path;
REQUIRE_SUCCEEDED(wil::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, path));
REQUIRE(wcscmp(fullName, path.get()) == 0);

wil::unique_cotaskmem nativePath;
REQUIRE_SUCCEEDED((wil::QueryFullProcessImageNameW<wil::unique_cotaskmem_string, 15>(::GetCurrentProcess(), 0, path)));
}


#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
165 changes: 165 additions & 0 deletions Externals/WIL/tests/NtResultTests.cpp
@@ -0,0 +1,165 @@

#include <wil/result.h>
#include <wil/nt_result_macros.h>

#include "common.h"

#define STATUS_OBJECT_PATH_NOT_FOUND ((NTSTATUS)0xC000003AL)
#define STATUS_INTERNAL_ERROR ((NTSTATUS)0xC00000E5L)
#define STATUS_INVALID_CONNECTION ((NTSTATUS)0xC0000140L)
#define E_LOAD_NAMESERVICE_FAILED ((HRESULT)0x80000140L)

TEST_CASE("NtResultTests::NtReturn", "[result]")
{
auto status = []()
{
NT_RETURN_NTSTATUS(STATUS_INVALID_CONNECTION);
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
NT_RETURN_NTSTATUS_MSG(STATUS_INVALID_CONNECTION, "Test NT_RETURN_NTSTATUS_MSG");
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
NT_RETURN_NTSTATUS_MSG(STATUS_INVALID_CONNECTION, "Test NT_RETURN_NTSTATUS_MSG %s", L"with parameter");
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
NT_RETURN_IF_NTSTATUS_FAILED(STATUS_INVALID_CONNECTION);
return STATUS_SUCCESS;
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
NT_RETURN_IF_NTSTATUS_FAILED_MSG(STATUS_INVALID_CONNECTION, "Test NT_RETURN_NTSTATUS_MSG %s", L"with parameter");
return STATUS_SUCCESS;
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
NT_RETURN_IF_NTSTATUS_FAILED(STATUS_SUCCESS);
return STATUS_INVALID_CONNECTION;
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);
}

#ifdef WIL_ENABLE_EXCEPTIONS
TEST_CASE("NtResultTests::NtThrowCatch", "[result]")
{
// Throw NTSTATUS with immediate conversion to HRESULT. HRESULT would appear in the logs.
auto hr = []()
{
try
{
THROW_NTSTATUS(STATUS_INVALID_CONNECTION);
}
CATCH_RETURN();
}();
// THROW_NTSTATUS converts NTSTATUS to HRESULT through WIN32 error code.
REQUIRE(hr == wil::details::NtStatusToHr(STATUS_INVALID_CONNECTION));

// Verify that conversion NTSTATUS -> HRESULT -> NTSTATUS is not 1:1.
auto status = []()
{
try
{
THROW_HR(wil::details::NtStatusToHr(STATUS_INVALID_CONNECTION));
}
NT_CATCH_RETURN();
}();
if (wil::details::g_pfnRtlNtStatusToDosErrorNoTeb)
{
REQUIRE(status != STATUS_INVALID_CONNECTION);
}
else
{
REQUIRE(status == STATUS_INVALID_CONNECTION);
}

// Throw HRESULT with conversion to NTSTATUS on a best effort. NTSTATUS would appear in the logs.
status = []()
{
try
{
THROW_HR(__HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
}
NT_CATCH_RETURN();
}();
REQUIRE(status == STATUS_OBJECT_PATH_NOT_FOUND);

// Throw HRESULT with conversion to NTSTATUS on a best effort that maps to generic error. NTSTATUS would appear in the logs.
status = []()
{
try
{
THROW_HR(E_LOAD_NAMESERVICE_FAILED);
}
NT_CATCH_RETURN();
}();
REQUIRE(status == STATUS_INTERNAL_ERROR);

// Throw NTSTATUS without conversion. NTSTATUS would appear in the logs.
status = []()
{
try
{
THROW_NTSTATUS(STATUS_INVALID_CONNECTION);
}
NT_CATCH_RETURN();
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
try
{
THROW_NTSTATUS_MSG(STATUS_INVALID_CONNECTION, "Throw STATUS_INVALID_CONNECTION as NTSTATUS");
}
NT_CATCH_RETURN();
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

status = []()
{
try
{
THROW_NTSTATUS_MSG(STATUS_INVALID_CONNECTION, "Throw STATUS_INVALID_CONNECTION as NTSTATUS with custom catch");
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();

return wil::StatusFromCaughtException();
}
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);

hr = []()
{
try
{
THROW_NTSTATUS_MSG(STATUS_INVALID_CONNECTION, "Throw STATUS_INVALID_CONNECTION as NTSTATUS");
}
CATCH_RETURN();
}();
REQUIRE(hr == wil::details::NtStatusToHr(STATUS_INVALID_CONNECTION));

status = []()
{
try
{
THROW_NTSTATUS_MSG(STATUS_INVALID_CONNECTION, "Throw STATUS_INVALID_CONNECTION as NTSTATUS");
}
NT_CATCH_RETURN_MSG("Catching STATUS_INVALID_CONNECTION thrown by NT_THROW_NTSTATUS_MSG");
}();
REQUIRE(status == STATUS_INVALID_CONNECTION);
}
#endif
129 changes: 129 additions & 0 deletions Externals/WIL/tests/ResourceTests.cpp
Expand Up @@ -9,6 +9,7 @@
#include <memory>
#include <roapi.h>
#include <winstring.h>
#include <WinUser.h>

#include <wil/resource.h>
#include <wrl/implements.h>
Expand Down Expand Up @@ -59,6 +60,14 @@ TEST_CASE("ResourceTests::TestLastErrorContext", "[resource][last_error_context]
SetLastError(1);
}
REQUIRE(GetLastError() == 1);

// The value in the context is unimpacted by other things changing the last error
{
SetLastError(42);
auto error42 = wil::last_error_context();
SetLastError(1);
REQUIRE(error42.value() == 42);
}
}

TEST_CASE("ResourceTests::TestScopeExit", "[resource][scope_exit]")
Expand Down Expand Up @@ -267,6 +276,40 @@ void UniqueProcessInfo()
}
#endif

// Compilation only test...
#ifdef WIL_ENABLE_EXCEPTIONS
void NoexceptConstructibleTest()
{
using BaseStorage = wil::details::unique_storage<wil::details::handle_resource_policy>;

struct ThrowingConstructor : BaseStorage
{
ThrowingConstructor() = default;
explicit ThrowingConstructor(HANDLE) __WI_NOEXCEPT_(false) {}
};

struct ProtectedConstructor : BaseStorage
{
protected:
ProtectedConstructor() = default;
explicit ProtectedConstructor(HANDLE) WI_NOEXCEPT {}
};

// wil::unique_handle is one of the many types which are expected to be noexcept
// constructible since they don't perform any "advanced" initialization.
static_assert(wistd::is_nothrow_default_constructible_v<wil::unique_handle>, "wil::unique_any_t should always be nothrow default constructible");
static_assert(wistd::is_nothrow_constructible_v<wil::unique_handle, HANDLE>, "wil::unique_any_t should be noexcept if the storage is");

// The inverse: A throwing storage constructor.
static_assert(wistd::is_nothrow_default_constructible_v<wil::unique_any_t<ThrowingConstructor>>, "wil::unique_any_t should always be nothrow default constructible");
static_assert(!wistd::is_nothrow_constructible_v<wil::unique_any_t<ThrowingConstructor>, HANDLE>, "wil::unique_any_t shouldn't be noexcept if the storage isn't");

// With a protected constructor wil::unique_any_t will be unable to correctly
// "forward" the noexcept attribute, but the code should still compile.
wil::unique_any_t<ProtectedConstructor> p{ INVALID_HANDLE_VALUE };
}
#endif

struct FakeComInterface
{
void AddRef()
Expand Down Expand Up @@ -732,4 +775,90 @@ TEST_CASE("DefaultTemplateParamCompiles", "[resource]")

wil::unique_midl_ptr<> g;
wil::unique_cotaskmem_ptr<> h;
wil::unique_mapview_ptr<> i;
}

TEST_CASE("UniqueInvokeCleanupMembers", "[resource]")
{
// Case 1 - unique_ptr<> for a T* that has a "destroy" member
struct ThingWithDestroy
{
bool destroyed = false;
void destroy() { destroyed = true; };
};
ThingWithDestroy toDestroy;
wil::unique_any<ThingWithDestroy*, decltype(&ThingWithDestroy::destroy), &ThingWithDestroy::destroy> p(&toDestroy);
p.reset();
REQUIRE(!p);
REQUIRE(toDestroy.destroyed);

// Case 2 - unique_struct calling a member, like above
struct ThingToDestroy2
{
bool* destroyed;
void destroy() { *destroyed = true; };
};
bool structDestroyed = false;
{
wil::unique_struct<ThingToDestroy2, decltype(&ThingToDestroy2::destroy), &ThingToDestroy2::destroy> other;
other.destroyed = &structDestroyed;
REQUIRE(!structDestroyed);
}
REQUIRE(structDestroyed);
}

struct ITokenTester : IUnknown
{
virtual void DirectClose(DWORD_PTR token) = 0;
};

struct TokenTester : ITokenTester
{
IFACEMETHOD_(ULONG, AddRef)() override { return 2; }
IFACEMETHOD_(ULONG, Release)() override { return 1; }
IFACEMETHOD(QueryInterface)(REFIID, void**) { return E_NOINTERFACE; }
void DirectClose(DWORD_PTR token) override {
m_closed = (token == m_closeToken);
}
bool m_closed = false;
DWORD_PTR m_closeToken;
};

void MyTokenTesterCloser(ITokenTester* tt, DWORD_PTR token)
{
tt->DirectClose(token);
}

TEST_CASE("ComTokenCloser", "[resource]")
{
using token_tester_t = wil::unique_com_token<ITokenTester, DWORD_PTR, decltype(MyTokenTesterCloser), &MyTokenTesterCloser>;

TokenTester tt;
tt.m_closeToken = 4;
{
token_tester_t tmp{ &tt, 4 };
}
REQUIRE(tt.m_closed);
}

TEST_CASE("ComTokenDirectCloser", "[resource]")
{
using token_tester_t = wil::unique_com_token<ITokenTester, DWORD_PTR, decltype(&ITokenTester::DirectClose), &ITokenTester::DirectClose>;

TokenTester tt;
tt.m_closeToken = 4;
{
token_tester_t tmp{ &tt, 4 };
}
REQUIRE(tt.m_closed);
}

TEST_CASE("UniqueCloseClipboardCall", "[resource]")
{
#if defined(__WIL__WINUSER_) && !defined(NOCLIPBOARD)
if (auto clip = wil::open_clipboard(nullptr))
{
REQUIRE(::EmptyClipboard());
}
#endif
}
30 changes: 23 additions & 7 deletions Externals/WIL/tests/ResultTests.cpp
@@ -1,7 +1,10 @@

#include <wil/com.h>
#include <wil/result.h>

#if (NTDDI_VERSION >= NTDDI_WIN8)
#include <wil/result_originate.h>
#endif

#include <roerrorapi.h>

Expand Down Expand Up @@ -323,7 +326,7 @@ TEST_CASE("ResultTests::ExceptionHandling", "[result]")
RETURN_CAUGHT_EXCEPTION_EXPECTED();
}
}();
REQUIRE(failures.size() == 0);
REQUIRE(failures.empty());
REQUIRE(hr == E_OUTOFMEMORY);
}
failures.clear();
Expand All @@ -339,7 +342,7 @@ TEST_CASE("ResultTests::ExceptionHandling", "[result]")
{
throw std::bad_alloc();
});
REQUIRE(failures.size() == 0);
REQUIRE(failures.empty());
REQUIRE(hr == E_OUTOFMEMORY);
}
failures.clear();
Expand Down Expand Up @@ -436,16 +439,16 @@ void ExceptionHandlingCompilationTest()
try { try { throw std::bad_alloc(); } catch (...) { THROW_NORMALIZED_CAUGHT_EXCEPTION(); } } catch (...) {}
try { try { throw std::bad_alloc(); } catch (...) { THROW_NORMALIZED_CAUGHT_EXCEPTION_MSG("train: %d", 42); } } catch (...) {}

HRESULT hr = wil::ResultFromExceptionDebug(WI_DIAGNOSTICS_INFO, wil::SupportedExceptions::All, [&]
wil::ResultFromExceptionDebug(WI_DIAGNOSTICS_INFO, wil::SupportedExceptions::All, [&]
{
THROW_HR(E_FAIL);
});

hr = wil::ResultFromException(WI_DIAGNOSTICS_INFO, wil::SupportedExceptions::None, [&]
wil::ResultFromException(WI_DIAGNOSTICS_INFO, wil::SupportedExceptions::None, [&]
{
});

hr = wil::ResultFromException([&]
wil::ResultFromException([&]
{
});

Expand Down Expand Up @@ -479,7 +482,7 @@ TEST_CASE("ResultTests::ErrorMacros", "[result]")
}

// The originate helper isn't compatible with CX so don't test it in that mode.
#ifndef __cplusplus_winrt
#if !defined(__cplusplus_winrt) && (NTDDI_VERSION >= NTDDI_WIN8)
TEST_CASE("ResultTests::NoOriginationByDefault", "[result]")
{
::wil::SetOriginateErrorCallback(nullptr);
Expand Down Expand Up @@ -572,4 +575,17 @@ TEST_CASE("ResultTests::AutomaticOriginationOnFailure", "[result]")
}();
REQUIRE(S_FALSE == GetRestrictedErrorInfo(&restrictedErrorInformation));
}
#endif // __cplusplus_winrt
#endif

TEST_CASE("ResultTests::ReportDoesNotChangeLastError", "[result]")
{
decltype(wil::details::g_pfnLoggingCallback) oopsie = [](wil::FailureInfo const&) noexcept
{
::SetLastError(ERROR_ABANDON_HIBERFILE);
};
auto swap = witest::AssignTemporaryValue(&wil::details::g_pfnLoggingCallback, oopsie);

::SetLastError(ERROR_ABIOS_ERROR);
LOG_IF_WIN32_BOOL_FALSE(FALSE);
REQUIRE(::GetLastError() == ERROR_ABIOS_ERROR);
}
4 changes: 2 additions & 2 deletions Externals/WIL/tests/Rpc.cpp
Expand Up @@ -11,8 +11,8 @@ void RpcMethodReturnsVoid(ULONG toRaise)
}

struct FOO_CONTEXT_T {};
typedef FOO_CONTEXT_T* FOO_CONTEXT;
typedef FOO_CONTEXT* PFOO_CONTEXT;
using FOO_CONTEXT = FOO_CONTEXT_T*;
using PFOO_CONTEXT = FOO_CONTEXT*;

void CloseContextHandle(_Inout_ PFOO_CONTEXT)
{
Expand Down