[PATCH v12 0/3] MR10659: combase: Implement restricted WinRT error handling APIs.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58900 Bug 58900 This series implements restricted WinRT error handling in combase and\ adds conformance tests for the new behavior. Implemented APIs include restricted error creation / retrieval,\ error context capture, transform, matching, and unhandled error reporting\ paths. Tests were added under dlls/combase/tests and pass locally.\ The existing combase tests also continue to pass. -- v12: combase/tests: Add tests for restricted WinRT error handling. combase: Implement restricted WinRT error handling APIs. combase: Implement internal restricted error info support. https://gitlab.winehq.org/wine/wine/-/merge_requests/10659
From: Anna R Békefi <annareginabekefi@gmail.com> --- dlls/combase/errorinfo.c | 308 +++++++++++++++++++++++++++++++++++++++ dlls/combase/roapi.c | 53 ++++++- 2 files changed, 357 insertions(+), 4 deletions(-) diff --git a/dlls/combase/errorinfo.c b/dlls/combase/errorinfo.c index 9383e8d5a72..afe30e43080 100644 --- a/dlls/combase/errorinfo.c +++ b/dlls/combase/errorinfo.c @@ -27,6 +27,9 @@ #include "wine/debug.h" +#include "roerrorapi.h" + + WINE_DEFAULT_DEBUG_CHANNEL(ole); struct error_info @@ -393,3 +396,308 @@ HRESULT WINAPI SetErrorInfo(ULONG reserved, IErrorInfo *error_info) return S_OK; } + +/*********************************************************************** + * IRestrictedErrorInfo implementation / helper (combase internal) + */ +struct restricted_error_info +{ + IRestrictedErrorInfo IRestrictedErrorInfo_iface; + IErrorInfo IErrorInfo_iface; + LONG refcount; + + GUID guid; + WCHAR *source; + WCHAR *help_file; + DWORD help_context; + + HRESULT hr; + WCHAR *description; + WCHAR *restricted_description; + WCHAR *capability_sid; + WCHAR *reference; + + BOOL context_captured; + USHORT stack_frame_count; + void *stack_frames[32]; + ULONG stack_hash; +}; + +static struct restricted_error_info *impl_from_IRestrictedErrorInfo(IRestrictedErrorInfo *iface) +{ + return CONTAINING_RECORD(iface, struct restricted_error_info, IRestrictedErrorInfo_iface); +} + +static struct restricted_error_info *impl_from_restricted_IErrorInfo(IErrorInfo *iface) +{ + return CONTAINING_RECORD(iface, struct restricted_error_info, IErrorInfo_iface); +} + +static ULONG restricted_errorinfo_addref(struct restricted_error_info *info) +{ + ULONG refcount = InterlockedIncrement(&info->refcount); + + TRACE("%p, refcount %lu.\n", info, refcount); + return refcount; +} + +static ULONG restricted_errorinfo_release(struct restricted_error_info *info) +{ + ULONG refcount = InterlockedDecrement(&info->refcount); + + TRACE("%p, refcount %lu.\n", info, refcount); + + if (!refcount) + { + free(info->source); + free(info->help_file); + free(info->description); + free(info->restricted_description); + free(info->capability_sid); + free(info->reference); + free(info); + } + + return refcount; +} + +static HRESULT WINAPI restricted_errorinfo_QueryInterface(IRestrictedErrorInfo *iface, REFIID riid, void **obj) +{ + struct restricted_error_info *info = impl_from_IRestrictedErrorInfo(iface); + + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (!obj) return E_POINTER; + *obj = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IRestrictedErrorInfo)) + *obj = &info->IRestrictedErrorInfo_iface; + else if (IsEqualIID(riid, &IID_IErrorInfo)) + *obj = &info->IErrorInfo_iface; + + if (*obj) + { + restricted_errorinfo_addref(info); + return S_OK; + } + + WARN("Unsupported interface %s.\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI restricted_errorinfo_AddRef(IRestrictedErrorInfo *iface) +{ + struct restricted_error_info *info = impl_from_IRestrictedErrorInfo(iface); + return restricted_errorinfo_addref(info); +} + +static ULONG WINAPI restricted_errorinfo_Release(IRestrictedErrorInfo *iface) +{ + struct restricted_error_info *info = impl_from_IRestrictedErrorInfo(iface); + return restricted_errorinfo_release(info); +} + +static HRESULT WINAPI restricted_errorinfo_GetErrorDetails(IRestrictedErrorInfo *iface, + BSTR *description, HRESULT *error, BSTR *restricted_description, BSTR *capability_sid) +{ + struct restricted_error_info *info = impl_from_IRestrictedErrorInfo(iface); + + TRACE("%p, %p, %p, %p, %p.\n", iface, description, error, restricted_description, capability_sid); + + if (description) + *description = info->description ? SysAllocString(info->description) : NULL; + if (error) + *error = info->hr; + if (restricted_description) + *restricted_description = info->restricted_description ? SysAllocString(info->restricted_description) : NULL; + if (capability_sid) + *capability_sid = info->capability_sid ? SysAllocString(info->capability_sid) : NULL; + + return S_OK; +} + +static HRESULT WINAPI restricted_errorinfo_GetReference(IRestrictedErrorInfo *iface, BSTR *reference) +{ + struct restricted_error_info *info = impl_from_IRestrictedErrorInfo(iface); + + TRACE("%p, %p.\n", iface, reference); + + if (!reference) return E_POINTER; + *reference = info->reference ? SysAllocString(info->reference) : NULL; + return S_OK; +} + +static HRESULT WINAPI restricted_errorinfo_ierror_QueryInterface(IErrorInfo *iface, REFIID riid, void **obj) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + return IRestrictedErrorInfo_QueryInterface(&info->IRestrictedErrorInfo_iface, riid, obj); +} + +static ULONG WINAPI restricted_errorinfo_ierror_AddRef(IErrorInfo *iface) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + return restricted_errorinfo_addref(info); +} + +static ULONG WINAPI restricted_errorinfo_ierror_Release(IErrorInfo *iface) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + return restricted_errorinfo_release(info); +} + +static HRESULT WINAPI restricted_errorinfo_ierror_GetGUID(IErrorInfo *iface, GUID *guid) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + + TRACE("%p, %p.\n", iface, guid); + + if (!guid) return E_INVALIDARG; + *guid = info->guid; + return S_OK; +} + +static HRESULT WINAPI restricted_errorinfo_ierror_GetSource(IErrorInfo *iface, BSTR *source) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + + TRACE("%p, %p.\n", iface, source); + + if (!source) return E_INVALIDARG; + *source = info->source ? SysAllocString(info->source) : NULL; + return S_OK; +} + +static HRESULT WINAPI restricted_errorinfo_ierror_GetDescription(IErrorInfo *iface, BSTR *description) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + + TRACE("%p, %p.\n", iface, description); + + if (!description) return E_INVALIDARG; + *description = info->description ? SysAllocString(info->description) : NULL; + return S_OK; +} + +static HRESULT WINAPI restricted_errorinfo_ierror_GetHelpFile(IErrorInfo *iface, BSTR *helpfile) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + + TRACE("%p, %p.\n", iface, helpfile); + + if (!helpfile) return E_INVALIDARG; + *helpfile = info->help_file ? SysAllocString(info->help_file) : NULL; + return S_OK; +} + +static HRESULT WINAPI restricted_errorinfo_ierror_GetHelpContext(IErrorInfo *iface, DWORD *help_context) +{ + struct restricted_error_info *info = impl_from_restricted_IErrorInfo(iface); + + TRACE("%p, %p.\n", iface, help_context); + + if (!help_context) return E_INVALIDARG; + *help_context = info->help_context; + return S_OK; +} + +static const IErrorInfoVtbl restricted_errorinfo_ierror_vtbl = +{ + restricted_errorinfo_ierror_QueryInterface, + restricted_errorinfo_ierror_AddRef, + restricted_errorinfo_ierror_Release, + restricted_errorinfo_ierror_GetGUID, + restricted_errorinfo_ierror_GetSource, + restricted_errorinfo_ierror_GetDescription, + restricted_errorinfo_ierror_GetHelpFile, + restricted_errorinfo_ierror_GetHelpContext +}; + +static const IRestrictedErrorInfoVtbl restricted_errorinfo_vtbl = +{ + restricted_errorinfo_QueryInterface, + restricted_errorinfo_AddRef, + restricted_errorinfo_Release, + restricted_errorinfo_GetErrorDetails, + restricted_errorinfo_GetReference +}; + +HRESULT create_restricted_error_info(HRESULT hr, const WCHAR *description, + const WCHAR *restricted_description, const WCHAR *capability_sid, + const WCHAR *reference, IRestrictedErrorInfo **ret) +{ + struct restricted_error_info *info; + + TRACE("%#lx, %s, %s, %s, %s, %p.\n", hr, debugstr_w(description), + debugstr_w(restricted_description), debugstr_w(capability_sid), + debugstr_w(reference), ret); + + if (!ret) return E_INVALIDARG; + *ret = NULL; + + info = calloc(1, sizeof(*info)); + if (!info) return E_OUTOFMEMORY; + + info->IRestrictedErrorInfo_iface.lpVtbl = &restricted_errorinfo_vtbl; + info->IErrorInfo_iface.lpVtbl = &restricted_errorinfo_ierror_vtbl; + info->refcount = 1; + + info->guid = GUID_NULL; + info->source = NULL; + info->help_file = NULL; + info->help_context = 0; + + info->hr = hr; + info->description = description ? wcsdup(description) : NULL; + info->restricted_description = restricted_description ? wcsdup(restricted_description) : NULL; + info->capability_sid = capability_sid ? wcsdup(capability_sid) : NULL; + info->reference = reference ? wcsdup(reference) : NULL; + + info->context_captured = FALSE; + info->stack_frame_count = 0; + info->stack_hash = 0; + + *ret = &info->IRestrictedErrorInfo_iface; + return S_OK; +} + +HRESULT capture_restricted_error_context(IRestrictedErrorInfo *iface, HRESULT hr) +{ + struct restricted_error_info *info; + ULONG hash = 0; + + TRACE("%p, %#lx.\n", iface, hr); + + if (!iface) + return E_INVALIDARG; + + info = impl_from_IRestrictedErrorInfo(iface); + + info->hr = hr; + memset(info->stack_frames, 0, sizeof(info->stack_frames)); + info->stack_frame_count = RtlCaptureStackBackTrace(1, ARRAY_SIZE(info->stack_frames), + info->stack_frames, &hash); + info->stack_hash = hash; + info->context_captured = TRUE; + + return S_OK; +} + +HRESULT copy_restricted_error_context(IRestrictedErrorInfo *dst_iface, IRestrictedErrorInfo *src_iface) +{ + struct restricted_error_info *dst, *src; + + TRACE("%p, %p.\n", dst_iface, src_iface); + + if (!dst_iface || !src_iface) + return E_INVALIDARG; + + dst = impl_from_IRestrictedErrorInfo(dst_iface); + src = impl_from_IRestrictedErrorInfo(src_iface); + + dst->context_captured = src->context_captured; + dst->stack_frame_count = src->stack_frame_count; + memcpy(dst->stack_frames, src->stack_frames, sizeof(dst->stack_frames)); + dst->stack_hash = src->stack_hash; + + return S_OK; +} diff --git a/dlls/combase/roapi.c b/dlls/combase/roapi.c index f4f7c0aac26..ea1001b325a 100644 --- a/dlls/combase/roapi.c +++ b/dlls/combase/roapi.c @@ -498,8 +498,36 @@ HRESULT WINAPI RoRegisterActivationFactories(HSTRING *classes, PFNGETACTIVATIONF */ HRESULT WINAPI GetRestrictedErrorInfo(IRestrictedErrorInfo **info) { - FIXME( "(%p)\n", info ); - return E_NOTIMPL; + IErrorInfo *error_info = NULL; + HRESULT hr; + + TRACE("(%p)\n", info); + + if (!info) return E_POINTER; + *info = NULL; + + hr = GetErrorInfo(0, &error_info); + if (hr == S_FALSE) + return S_FALSE; + if (FAILED(hr)) + { + WARN("GetErrorInfo failed, hr %#lx.\n", hr); + return hr; + } + + if (!error_info) + return S_FALSE; + + hr = IErrorInfo_QueryInterface(error_info, &IID_IRestrictedErrorInfo, (void **)info); + IErrorInfo_Release(error_info); + + if (FAILED(hr)) + { + TRACE("Current error object does not support IRestrictedErrorInfo.\n"); + return S_FALSE; + } + + return S_OK; } /*********************************************************************** @@ -507,8 +535,25 @@ HRESULT WINAPI GetRestrictedErrorInfo(IRestrictedErrorInfo **info) */ HRESULT WINAPI SetRestrictedErrorInfo(IRestrictedErrorInfo *info) { - FIXME( "(%p)\n", info ); - return E_NOTIMPL; + IErrorInfo *error_info = NULL; + HRESULT hr; + + TRACE("(%p)\n", info); + + if (!info) + return SetErrorInfo(0, NULL); + + hr = IRestrictedErrorInfo_QueryInterface(info, &IID_IErrorInfo, (void **)&error_info); + if (FAILED(hr)) + { + WARN("Restricted error object does not expose IErrorInfo, hr %#lx.\n", hr); + return hr; + } + + hr = SetErrorInfo(0, error_info); + IErrorInfo_Release(error_info); + + return hr; } /*********************************************************************** -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10659
From: Anna R Békefi <annareginabekefi@gmail.com> --- dlls/combase/combase.spec | 10 +- dlls/combase/roapi.c | 509 +++++++++++++++++++++++++++++++++++++- 2 files changed, 505 insertions(+), 14 deletions(-) diff --git a/dlls/combase/combase.spec b/dlls/combase/combase.spec index 19249e17f87..345f48360f8 100644 --- a/dlls/combase/combase.spec +++ b/dlls/combase/combase.spec @@ -288,8 +288,8 @@ @ stdcall PropVariantCopy(ptr ptr) @ stub ReleaseFuncDescs @ stdcall RoActivateInstance(ptr ptr) -@ stub RoCaptureErrorContext -@ stub RoClearError +@ stdcall RoCaptureErrorContext(long) +@ stdcall RoClearError() @ stdcall RoFailFastWithErrorContext(long) @ stub RoFreeParameterizedTypeExtra @ stub RoGetActivatableClassRegistration @@ -297,7 +297,7 @@ @ stdcall RoGetAgileReference(long ptr ptr ptr) @ stdcall RoGetApartmentIdentifier(ptr) @ stdcall RoGetErrorReportingFlags(ptr) -@ stub RoGetMatchingRestrictedErrorInfo +@ stdcall RoGetMatchingRestrictedErrorInfo(long ptr) @ stdcall RoGetParameterizedTypeInstanceIID(long ptr ptr ptr ptr) @ stdcall RoGetServerActivatableClasses(ptr ptr ptr) @ stdcall RoInitialize(long) @@ -315,8 +315,8 @@ @ stub RoResolveRestrictedErrorInfoReference @ stub RoRevokeActivationFactories @ stdcall RoSetErrorReportingFlags(long) -@ stub RoTransformError -@ stub RoTransformErrorW +@ stdcall RoTransformError(long long ptr) +@ stdcall RoTransformErrorW(long long long ptr) @ stdcall RoUninitialize() @ stub RoUnregisterForApartmentShutdown @ stub SetCleanupFlag diff --git a/dlls/combase/roapi.c b/dlls/combase/roapi.c index ea1001b325a..53d401de685 100644 --- a/dlls/combase/roapi.c +++ b/dlls/combase/roapi.c @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define COBJMACROS +#define WINOLEAUTAPI #include "objbase.h" #include "initguid.h" #include "roapi.h" @@ -29,8 +30,26 @@ #include "wine/debug.h" +HRESULT create_restricted_error_info(HRESULT hr, const WCHAR *description, + const WCHAR *restricted_description, const WCHAR *capability_sid, + const WCHAR *reference, IRestrictedErrorInfo **ret); + +HRESULT capture_restricted_error_context(IRestrictedErrorInfo *iface, HRESULT hr); + +HRESULT copy_restricted_error_context(IRestrictedErrorInfo *dst_iface, IRestrictedErrorInfo *src_iface); + +HRESULT WINAPI RoGetMatchingRestrictedErrorInfo(HRESULT error, IRestrictedErrorInfo **info); + WINE_DEFAULT_DEBUG_CHANNEL(combase); +static UINT32 ro_error_reporting_flags = RO_ERROR_REPORTING_USESETERRORINFO; + +#define RO_ERROR_REPORTING_VALID_MASK \ + (RO_ERROR_REPORTING_SUPPRESSEXCEPTIONS | \ + RO_ERROR_REPORTING_FORCEEXCEPTIONS | \ + RO_ERROR_REPORTING_USESETERRORINFO | \ + RO_ERROR_REPORTING_SUPPRESSSETERRORINFO) + struct activatable_class_data { ULONG size; @@ -40,6 +59,83 @@ struct activatable_class_data DWORD threading_model; }; +static WCHAR *duplicate_bounded_message(const WCHAR *message, UINT cchMax) +{ + UINT len = 0, limit = 511; + WCHAR *copy; + + if (!message) + return NULL; + + if (!cchMax) + { + while (len < limit && message[len]) + len++; + } + else + { + while (len < cchMax && len < limit && message[len]) + len++; + } + + if (!len) + return NULL; + + copy = malloc((len + 1) * sizeof(*copy)); + if (!copy) + return NULL; + + memcpy(copy, message, len * sizeof(*copy)); + copy[len] = 0; + return copy; +} + +static BOOL is_current_thread_com_initialized(void) +{ + struct tlsdata *tlsdata = NtCurrentTeb()->ReservedForOle; + + return tlsdata && (tlsdata->inits || tlsdata->ole_inits); +} + +static BOOL should_use_seterrorinfo(void) +{ + if (ro_error_reporting_flags & RO_ERROR_REPORTING_SUPPRESSSETERRORINFO) + return FALSE; + if (!(ro_error_reporting_flags & RO_ERROR_REPORTING_USESETERRORINFO)) + return FALSE; + if (!is_current_thread_com_initialized()) + return FALSE; + + return TRUE; +} + +static WCHAR *get_generic_error_message(HRESULT error) +{ + WCHAR *system_message = NULL, *copy; + DWORD len, flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + + len = FormatMessageW(flags, NULL, error, 0, (WCHAR *)&system_message, 0, NULL); + if (!len) + len = FormatMessageW(flags, NULL, E_FAIL, 0, (WCHAR *)&system_message, 0, NULL); + + if (!len || !system_message) + return wcsdup(L"Unspecified error"); + + if (len > 511) + len = 511; + + copy = malloc((len + 1) * sizeof(*copy)); + if (copy) + { + memcpy(copy, system_message, len * sizeof(*copy)); + copy[len] = 0; + } + + LocalFree(system_message); + return copy; +} + static HRESULT get_library_for_classid(const WCHAR *classid, WCHAR **out) { ACTCTX_SECTION_KEYED_DATA data; @@ -556,6 +652,88 @@ HRESULT WINAPI SetRestrictedErrorInfo(IRestrictedErrorInfo *info) return hr; } +/*********************************************************************** + * RoCaptureErrorContext (combase.@) + * + * Captures the current restricted error context for the specified HRESULT. + * + * Wine stores the captured context in the restricted error object, + * including a stack backtrace. Native Windows Error Reporting (WER) + * integration is not implemented yet. + */ +HRESULT WINAPI RoCaptureErrorContext(HRESULT error) +{ + IRestrictedErrorInfo *info = NULL; + HRESULT hr, stored_error = S_OK; + + TRACE("%#lx.\n", error); + + if (!FAILED(error)) + return S_OK; + + hr = GetRestrictedErrorInfo(&info); + if (FAILED(hr) && hr != S_FALSE) + { + WARN("GetRestrictedErrorInfo failed, hr %#lx.\n", hr); + return hr; + } + + if (info) + { + hr = IRestrictedErrorInfo_GetErrorDetails(info, NULL, &stored_error, NULL, NULL); + if (FAILED(hr) || stored_error != error) + { + IRestrictedErrorInfo_Release(info); + info = NULL; + } + } + + if (!info) + { + hr = create_restricted_error_info(error, NULL, NULL, NULL, NULL, &info); + if (FAILED(hr)) + { + WARN("create_restricted_error_info failed, hr %#lx.\n", hr); + return hr; + } + } + + hr = capture_restricted_error_context(info, error); + if (FAILED(hr)) + { + WARN("capture_restricted_error_context failed, hr %#lx.\n", hr); + IRestrictedErrorInfo_Release(info); + return hr; + } + + hr = SetRestrictedErrorInfo(info); + IRestrictedErrorInfo_Release(info); + + return hr; +} + +/*********************************************************************** + * RoClearError (combase.@) + * + * Clears the current restricted error info for the calling thread. + */ +void WINAPI RoClearError(void) +{ + struct tlsdata *tlsdata = NtCurrentTeb()->ReservedForOle; + IErrorInfo *error_info; + + TRACE("()\n"); + + if (!tlsdata) + return; + + error_info = tlsdata->errorinfo; + tlsdata->errorinfo = NULL; + + if (error_info) + IErrorInfo_Release(error_info); +} + /*********************************************************************** * RoOriginateLanguageException (combase.@) */ @@ -570,8 +748,15 @@ BOOL WINAPI RoOriginateLanguageException(HRESULT error, HSTRING message, IUnknow */ BOOL WINAPI RoOriginateError(HRESULT error, HSTRING message) { - FIXME("%#lx, %s: stub\n", error, debugstr_hstring(message)); - return FALSE; + const WCHAR *str = NULL; + UINT32 len = 0; + + TRACE("%#lx, %s.\n", error, debugstr_hstring(message)); + + if (message) + str = WindowsGetStringRawBuffer(message, &len); + + return RoOriginateErrorW(error, len, str); } /*********************************************************************** @@ -579,16 +764,104 @@ BOOL WINAPI RoOriginateError(HRESULT error, HSTRING message) */ BOOL WINAPI RoOriginateErrorW(HRESULT error, UINT max_len, const WCHAR *message) { - FIXME("%#lx, %u, %p: stub\n", error, max_len, message); - return FALSE; + IRestrictedErrorInfo *info = NULL; + WCHAR *generic_text = NULL, *restricted_text = NULL; + HRESULT hr; + + TRACE("%#lx, %u, %s.\n", error, max_len, debugstr_w(message)); + + if (!FAILED(error)) + return FALSE; + + generic_text = get_generic_error_message(error); + if (!generic_text) + return FALSE; + + if (message) + restricted_text = duplicate_bounded_message(message, max_len); + else + restricted_text = wcsdup(generic_text); + + if (message && !restricted_text) + { + free(generic_text); + return FALSE; + } + + if (!restricted_text) + { + free(generic_text); + return FALSE; + } + + /* The call still succeeds even if we don't attach the error object + * to the COM channel. */ + if (!should_use_seterrorinfo()) + { + free(generic_text); + free(restricted_text); + return TRUE; + } + + hr = create_restricted_error_info(error, generic_text, restricted_text, NULL, NULL, &info); + free(generic_text); + free(restricted_text); + if (FAILED(hr)) + { + WARN("create_restricted_error_info failed, hr %#lx.\n", hr); + return FALSE; + } + + hr = SetRestrictedErrorInfo(info); + IRestrictedErrorInfo_Release(info); + + return SUCCEEDED(hr); } /*********************************************************************** - * RoReportUnhandledError (combase.@) + * RoReportUnhandledError (combase.@) + * + * Reports an unhandled error by updating the COM error channel state + * for the current thread and logging the error details. */ HRESULT WINAPI RoReportUnhandledError(IRestrictedErrorInfo *info) { - FIXME("(%p): stub\n", info); + HRESULT hr; + BSTR description = NULL, restricted = NULL, capability = NULL; + HRESULT orig_error = S_OK; + + TRACE("(%p)\n", info); + + if (!info) + return E_POINTER; + + hr = SetRestrictedErrorInfo(info); + if (FAILED(hr)) + { + WARN("SetRestrictedErrorInfo failed, hr %#lx.\n", hr); + return hr; + } + + if (SUCCEEDED(IRestrictedErrorInfo_GetErrorDetails(info, &description, &orig_error, + &restricted, &capability))) + { + WARN("Unhandled WinRT error: hr %#lx, desc %s, restricted %s, capability %s\n", + orig_error, debugstr_w(description), debugstr_w(restricted), + debugstr_w(capability)); + + SysFreeString(description); + SysFreeString(restricted); + SysFreeString(capability); + } + else + { + WARN("Unhandled WinRT error reported, but failed to get details.\n"); + } + + /* Native Windows triggers the Global Error Handler here. + * Wine currently preserves the reported restricted error object + * on the current thread for later observation. + */ return S_OK; } @@ -597,7 +870,225 @@ HRESULT WINAPI RoReportUnhandledError(IRestrictedErrorInfo *info) */ HRESULT WINAPI RoSetErrorReportingFlags(UINT32 flags) { - FIXME("(%08x): stub\n", flags); + TRACE("(%08x)\n", flags); + + if (flags & ~RO_ERROR_REPORTING_VALID_MASK) + return E_INVALIDARG; + + ro_error_reporting_flags = flags; + return S_OK; +} + +/*********************************************************************** + * RoTransformErrorW (combase.@) + * + * Transforms the current restricted error into a new one with a different + * HRESULT and (optionally) a new user-visible message. + */ + +BOOL WINAPI RoTransformErrorW(HRESULT old_error, HRESULT new_error, + UINT max_len, const WCHAR *message) +{ + IRestrictedErrorInfo *old_info = NULL, *new_info = NULL; + BSTR desc = NULL, restricted_desc = NULL, cap_sid = NULL, reference = NULL; + HRESULT hr, stored_hr = S_OK; + WCHAR *generic_text = NULL, *restricted_text = NULL; + + TRACE("%#lx, %#lx, %u, %s.\n", old_error, new_error, max_len, debugstr_w(message)); + + if (old_error == new_error) + return FALSE; + if (!FAILED(old_error) && !FAILED(new_error)) + return FALSE; + + generic_text = get_generic_error_message(new_error); + if (!generic_text) + return FALSE; + + if (message) + { + restricted_text = duplicate_bounded_message(message, max_len); + if (!restricted_text) + { + free(generic_text); + return FALSE; + } + } + else + { + restricted_text = wcsdup(generic_text); + if (!restricted_text) + { + free(generic_text); + return FALSE; + } + } + + /* As with RoOriginateErrorW(), the call can still succeed even if + * we don't attach the transformed error object to the COM channel. */ + if (!should_use_seterrorinfo()) + { + free(generic_text); + free(restricted_text); + return TRUE; + } + + hr = RoGetMatchingRestrictedErrorInfo(old_error, &old_info); + if (FAILED(hr)) + { + WARN("RoGetMatchingRestrictedErrorInfo failed, hr %#lx.\n", hr); + free(generic_text); + free(restricted_text); + return FALSE; + } + + if (old_info) + { + hr = IRestrictedErrorInfo_GetErrorDetails(old_info, &desc, &stored_hr, + &restricted_desc, &cap_sid); + if (FAILED(hr)) + { + desc = NULL; + restricted_desc = NULL; + cap_sid = NULL; + } + + hr = IRestrictedErrorInfo_GetReference(old_info, &reference); + if (FAILED(hr)) + reference = NULL; + } + + hr = create_restricted_error_info(new_error, generic_text, restricted_text, + cap_sid, reference, &new_info); + if (FAILED(hr)) + { + WARN("create_restricted_error_info failed, hr %#lx.\n", hr); + goto done; + } + + if (old_info) + { + hr = copy_restricted_error_context(new_info, old_info); + if (FAILED(hr)) + { + WARN("copy_restricted_error_context failed, hr %#lx.\n", hr); + goto done; + } + } + + hr = SetRestrictedErrorInfo(new_info); + if (FAILED(hr)) + { + WARN("SetRestrictedErrorInfo failed, hr %#lx.\n", hr); + goto done; + } + +done: + if (new_info) + IRestrictedErrorInfo_Release(new_info); + if (old_info) + IRestrictedErrorInfo_Release(old_info); + if (desc) + SysFreeString(desc); + if (restricted_desc) + SysFreeString(restricted_desc); + if (cap_sid) + SysFreeString(cap_sid); + if (reference) + SysFreeString(reference); + free(generic_text); + free(restricted_text); + + return SUCCEEDED(hr); +} + +/*********************************************************************** + * RoTransformError (combase.@) + */ +BOOL WINAPI RoTransformError(HRESULT old_error, HRESULT new_error, HSTRING message) +{ + const WCHAR *msg = NULL; + UINT32 len = 0; + + TRACE("%#lx, %#lx, %s.\n", old_error, new_error, debugstr_hstring(message)); + + if (message) + msg = WindowsGetStringRawBuffer(message, &len); + + return RoTransformErrorW(old_error, new_error, len, msg); +} + +/*********************************************************************** + * RoGetMatchingRestrictedErrorInfo (combase.@) + * + * Ensures that there is a restricted error info whose HRESULT matches + * the given error code, and returns it. + */ +HRESULT WINAPI RoGetMatchingRestrictedErrorInfo(HRESULT error, IRestrictedErrorInfo **info) +{ + IRestrictedErrorInfo *current = NULL; + HRESULT hr, stored_hr = S_OK; + WCHAR *generic_text = NULL; + + TRACE("%#lx, %p.\n", error, info); + + if (!info) return E_POINTER; + *info = NULL; + + if (!FAILED(error)) + return S_FALSE; + + hr = GetRestrictedErrorInfo(¤t); + if (FAILED(hr) && hr != S_FALSE) + { + WARN("GetRestrictedErrorInfo failed, hr %#lx.\n", hr); + return hr; + } + + if (current) + { + hr = IRestrictedErrorInfo_GetErrorDetails(current, NULL, &stored_hr, NULL, NULL); + if (SUCCEEDED(hr) && stored_hr == error) + { + hr = SetRestrictedErrorInfo(current); + if (FAILED(hr)) + { + WARN("SetRestrictedErrorInfo failed, hr %#lx.\n", hr); + IRestrictedErrorInfo_Release(current); + return hr; + } + + IRestrictedErrorInfo_AddRef(current); + *info = current; + IRestrictedErrorInfo_Release(current); + return S_OK; + } + + IRestrictedErrorInfo_Release(current); + current = NULL; + } + + generic_text = get_generic_error_message(error); + if (!generic_text) + return E_OUTOFMEMORY; + + hr = create_restricted_error_info(error, generic_text, generic_text, NULL, NULL, ¤t); + free(generic_text); + if (FAILED(hr)) + { + WARN("create_restricted_error_info failed, hr %#lx.\n", hr); + return hr; + } + + hr = SetRestrictedErrorInfo(current); + if (FAILED(hr)) + { + WARN("SetRestrictedErrorInfo failed, hr %#lx.\n", hr); + IRestrictedErrorInfo_Release(current); + return hr; + } + + *info = current; return S_OK; } @@ -606,12 +1097,12 @@ HRESULT WINAPI RoSetErrorReportingFlags(UINT32 flags) */ HRESULT WINAPI RoGetErrorReportingFlags(UINT32 *flags) { - FIXME("(%p): stub\n", flags); + TRACE("(%p)\n", flags); if (!flags) return E_POINTER; - *flags = RO_ERROR_REPORTING_USESETERRORINFO; + *flags = ro_error_reporting_flags; return S_OK; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10659
From: Anna R Békefi <annareginabekefi@gmail.com> --- dlls/combase/tests/Makefile.in | 2 +- dlls/combase/tests/roapi.c | 200 +++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/dlls/combase/tests/Makefile.in b/dlls/combase/tests/Makefile.in index 21597b38d51..85b337cceec 100644 --- a/dlls/combase/tests/Makefile.in +++ b/dlls/combase/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = combase.dll -IMPORTS = combase uuid user32 +IMPORTS = combase uuid user32 oleaut32 SOURCES = \ combase.rc \ diff --git a/dlls/combase/tests/roapi.c b/dlls/combase/tests/roapi.c index e4100cb11bc..0b4828d6e76 100644 --- a/dlls/combase/tests/roapi.c +++ b/dlls/combase/tests/roapi.c @@ -23,6 +23,7 @@ #include "winerror.h" #include "winstring.h" #include "winternl.h" +#include "oleauto.h" #include "initguid.h" #include "roapi.h" @@ -30,6 +31,11 @@ #include "wine/test.h" +HRESULT WINAPI RoCaptureErrorContext(HRESULT hr); +void WINAPI RoClearError(void); +HRESULT WINAPI RoGetMatchingRestrictedErrorInfo(HRESULT hr, IRestrictedErrorInfo **info); +BOOL WINAPI RoTransformErrorW(HRESULT old_error, HRESULT new_error, UINT cchMax, const WCHAR *message); + #define EXPECT_REF(obj,ref) _expect_ref((IUnknown*)obj, ref, __LINE__) static void _expect_ref(IUnknown* obj, ULONG ref, int line) { @@ -639,6 +645,199 @@ static void test_RoGetErrorReportingFlags(void) ok(flags == RO_ERROR_REPORTING_USESETERRORINFO, "Got unexpected flag %#x.\n", flags); } +static BOOL wstr_equal_nullsafe(const WCHAR *a, const WCHAR *b) +{ + if (!a && !b) return TRUE; + if (!a || !b) return FALSE; + return !lstrcmpW(a, b); +} + +static WCHAR *get_expected_system_message(HRESULT hr) +{ + WCHAR *msg = NULL; + DWORD len; + + len = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, 0, (WCHAR *)&msg, 0, NULL); + if (!len) + { + len = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, E_FAIL, 0, (WCHAR *)&msg, 0, NULL); + } + + ok(msg != NULL, "FormatMessageW failed for %#lx.\n", hr); + return msg; +} + +static void check_restricted_error_details_(unsigned int line, IRestrictedErrorInfo *info, + HRESULT expected_hr, const WCHAR *expected_desc, const WCHAR *expected_restricted) +{ + BSTR desc = NULL, restricted = NULL, capability = NULL; + HRESULT hr = E_FAIL, call_hr; + + ok_(__FILE__, line)(!!info, "expected restricted error info.\n"); + if (!info) return; + + call_hr = IRestrictedErrorInfo_GetErrorDetails(info, &desc, &hr, &restricted, &capability); + ok_(__FILE__, line)(call_hr == S_OK, "GetErrorDetails returned %#lx.\n", call_hr); + if (call_hr == S_OK) + { + ok_(__FILE__, line)(hr == expected_hr, "got hr %#lx, expected %#lx.\n", hr, expected_hr); + ok_(__FILE__, line)(wstr_equal_nullsafe(desc, expected_desc), + "got desc %s, expected %s.\n", wine_dbgstr_w(desc), wine_dbgstr_w(expected_desc)); + ok_(__FILE__, line)(wstr_equal_nullsafe(restricted, expected_restricted), + "got restricted %s, expected %s.\n", wine_dbgstr_w(restricted), wine_dbgstr_w(expected_restricted)); + } + + if (desc) SysFreeString(desc); + if (restricted) SysFreeString(restricted); + if (capability) SysFreeString(capability); +} + +#define check_restricted_error_details(info, hr, desc, restricted) \ + check_restricted_error_details_(__LINE__, info, hr, desc, restricted) + +static void clear_restricted_error_state(void) +{ + IRestrictedErrorInfo *info = NULL; + + RoClearError(); + while (GetRestrictedErrorInfo(&info) == S_OK && info) + { + IRestrictedErrorInfo_Release(info); + info = NULL; + } +} + +static void test_restricted_error_handling(void) +{ + static const WCHAR originate_msg[] = L"originate-one"; + static const WCHAR suppressed_msg[] = L"suppressed"; + IRestrictedErrorInfo *info = NULL; + HRESULT hr; + UINT32 flags = 0; + BOOL uninit = FALSE; + WCHAR *access_denied_msg; + WCHAR *bounds_msg; + + hr = RoInitialize(RO_INIT_MULTITHREADED); + ok(hr == S_OK || hr == S_FALSE, "RoInitialize returned %#lx.\n", hr); + if (SUCCEEDED(hr)) + uninit = TRUE; + + clear_restricted_error_state(); + + access_denied_msg = get_expected_system_message(E_ACCESSDENIED); + bounds_msg = get_expected_system_message(E_BOUNDS); + + ok(access_denied_msg != NULL, "Failed to get expected message for E_ACCESSDENIED.\n"); + ok(bounds_msg != NULL, "Failed to get expected message for E_BOUNDS.\n"); + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_FALSE, "GetRestrictedErrorInfo returned %#lx.\n", hr); + ok(!info, "got info %p.\n", info); + + ok(!RoOriginateErrorW(S_OK, 0, L"should-not-set"), + "RoOriginateErrorW should fail for success HRESULT.\n"); + + ok(RoOriginateErrorW(E_ACCESSDENIED, 0, originate_msg), + "RoOriginateErrorW failed.\n"); + + hr = RoCaptureErrorContext(E_ACCESSDENIED); + ok(hr == S_OK, "RoCaptureErrorContext returned %#lx.\n", hr); + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_OK, "GetRestrictedErrorInfo returned %#lx.\n", hr); + check_restricted_error_details(info, E_ACCESSDENIED, access_denied_msg, originate_msg); + IRestrictedErrorInfo_Release(info); + info = NULL; + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_FALSE, "GetRestrictedErrorInfo returned %#lx.\n", hr); + ok(!info, "got info %p.\n", info); + + hr = RoGetMatchingRestrictedErrorInfo(E_ACCESSDENIED, &info); + ok(hr == S_OK, "RoGetMatchingRestrictedErrorInfo returned %#lx.\n", hr); + check_restricted_error_details(info, E_ACCESSDENIED, access_denied_msg, access_denied_msg); + + hr = SetRestrictedErrorInfo(info); + ok(hr == S_OK, "SetRestrictedErrorInfo returned %#lx.\n", hr); + IRestrictedErrorInfo_Release(info); + info = NULL; + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_OK, "GetRestrictedErrorInfo returned %#lx.\n", hr); + check_restricted_error_details(info, E_ACCESSDENIED, access_denied_msg, access_denied_msg); + IRestrictedErrorInfo_Release(info); + info = NULL; + + hr = SetRestrictedErrorInfo(NULL); + ok(hr == S_OK, "SetRestrictedErrorInfo(NULL) returned %#lx.\n", hr); + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_FALSE, "GetRestrictedErrorInfo returned %#lx.\n", hr); + ok(!info, "got info %p.\n", info); + + /* ok(RoOriginateErrorW(E_FAIL, 0, old_msg), "RoOriginateErrorW failed.\n"); + hr = RoCaptureErrorContext(E_FAIL); + ok(hr == S_OK, "RoCaptureErrorContext returned %#lx.\n", hr); + + ok(RoTransformErrorW(E_FAIL, E_ACCESSDENIED, 0, new_msg), + "RoTransformErrorW failed.\n"); + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_OK, "GetRestrictedErrorInfo returned %#lx.\n", hr); + check_restricted_error_details(info, E_ACCESSDENIED, access_denied_msg, new_msg); + IRestrictedErrorInfo_Release(info); + info = NULL; + + hr = RoReportUnhandledError(NULL); + ok(hr == E_POINTER, "RoReportUnhandledError returned %#lx.\n", hr); + + hr = RoGetMatchingRestrictedErrorInfo(E_BOUNDS, &info); + ok(hr == S_OK, "RoGetMatchingRestrictedErrorInfo returned %#lx.\n", hr); + + hr = RoReportUnhandledError(info); + ok(hr == S_OK, "RoReportUnhandledError returned %#lx.\n", hr); + IRestrictedErrorInfo_Release(info); + info = NULL; + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_OK, "GetRestrictedErrorInfo returned %#lx.\n", hr); + check_restricted_error_details(info, E_BOUNDS, bounds_msg, bounds_msg); + IRestrictedErrorInfo_Release(info); + info = NULL; + */ + hr = RoSetErrorReportingFlags(RO_ERROR_REPORTING_SUPPRESSSETERRORINFO); + ok(hr == S_OK, "RoSetErrorReportingFlags returned %#lx.\n", hr); + + ok(RoOriginateErrorW(E_FAIL, 0, suppressed_msg), + "RoOriginateErrorW failed under suppressed mode.\n"); + + hr = GetRestrictedErrorInfo(&info); + ok(hr == S_FALSE, "GetRestrictedErrorInfo returned %#lx.\n", hr); + ok(!info, "got info %p.\n", info); + + hr = RoSetErrorReportingFlags(RO_ERROR_REPORTING_USESETERRORINFO); + ok(hr == S_OK, "RoSetErrorReportingFlags returned %#lx.\n", hr); + + hr = RoGetErrorReportingFlags(&flags); + ok(hr == S_OK, "RoGetErrorReportingFlags returned %#lx.\n", hr); + ok(flags == RO_ERROR_REPORTING_USESETERRORINFO, "got flags %#x.\n", flags); + + clear_restricted_error_state(); + + LocalFree(access_denied_msg); + LocalFree(bounds_msg); + + if (uninit) + RoUninitialize(); +} + START_TEST(roapi) { BOOL ret; @@ -649,6 +848,7 @@ START_TEST(roapi) test_ActivationFactories(); test_RoGetAgileReference(); test_RoGetErrorReportingFlags(); + test_restricted_error_handling(); SetLastError(0xdeadbeef); ret = DeleteFileW(L"wine.combase.test.dll"); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10659
There's still too much code in the first two commits to review. I suggest changing this MR to just deal with the contents of the first commit, once this is upstreamed you could create another MR to deal with the contents of the second commit. For the contents of the first commit, have an initial commit that creates the bare minimum, with all methods other than QueryInterface, AddRef and Release, being stubs. Then implement the remaining functionality in a series of following commits, adding tests as you go. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10659#note_139791
participants (3)
-
Anna R Békefi -
Anna R Békefi (@annareginabekefi) -
Huw Davies (@huw)