Signed-off-by: Stefan Dösinger stefan@codeweavers.com
---
This supersedes "ddraw: Don't read clipper methods from the vtable." aka patch 160690.
I have not modified unsafe_impl_from_IDirectDrawClipper and I'm not checking the clipper passed to Surface::SetClipper because Windows crashes when it receives an invalid pointer (although it accepts a clipper with a modified vtable). --- dlls/ddraw/clipper.c | 2 +- dlls/ddraw/ddraw_private.h | 1 + dlls/ddraw/surface.c | 13 ++++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/dlls/ddraw/clipper.c b/dlls/ddraw/clipper.c index e4b7ec2bd50..68a0a7fb7b5 100644 --- a/dlls/ddraw/clipper.c +++ b/dlls/ddraw/clipper.c @@ -33,7 +33,7 @@ static inline struct ddraw_clipper *impl_from_IDirectDrawClipper(IDirectDrawClip return CONTAINING_RECORD(iface, struct ddraw_clipper, IDirectDrawClipper_iface); }
-static BOOL ddraw_clipper_is_valid(const struct ddraw_clipper *clipper) +BOOL ddraw_clipper_is_valid(const struct ddraw_clipper *clipper) { /* Native is very lenient when you invoke the clipper methods with a clipper pointer that * points to something that is either not accessible or not a clipper, or if you break diff --git a/dlls/ddraw/ddraw_private.h b/dlls/ddraw/ddraw_private.h index 77e28662724..087c7092f98 100644 --- a/dlls/ddraw/ddraw_private.h +++ b/dlls/ddraw/ddraw_private.h @@ -391,6 +391,7 @@ struct ddraw_clipper
HRESULT ddraw_clipper_init(struct ddraw_clipper *clipper) DECLSPEC_HIDDEN; struct ddraw_clipper *unsafe_impl_from_IDirectDrawClipper(IDirectDrawClipper *iface) DECLSPEC_HIDDEN; +BOOL ddraw_clipper_is_valid(const struct ddraw_clipper *clipper) DECLSPEC_HIDDEN;
/***************************************************************************** * IDirectDrawPalette implementation structure diff --git a/dlls/ddraw/surface.c b/dlls/ddraw/surface.c index b8cc2d2195d..4e2faef867d 100644 --- a/dlls/ddraw/surface.c +++ b/dlls/ddraw/surface.c @@ -1549,6 +1549,12 @@ static HRESULT ddraw_surface_blt_clipped(struct ddraw_surface *dst_surface, cons return hr; }
+ if (!ddraw_clipper_is_valid(dst_surface->clipper)) + { + FIXME("Attempting to blit with an invalid clipper.\n"); + return DDERR_INVALIDPARAMS; + } + scale_x = (float)(src_rect.right - src_rect.left) / (float)(dst_rect.right - dst_rect.left); scale_y = (float)(src_rect.bottom - src_rect.top) / (float)(dst_rect.bottom - dst_rect.top);
@@ -4426,7 +4432,8 @@ static HRESULT WINAPI ddraw_surface7_GetClipper(IDirectDrawSurface7 *iface, IDir }
*clipper = &surface->clipper->IDirectDrawClipper_iface; - IDirectDrawClipper_AddRef(*clipper); + if (ddraw_clipper_is_valid(surface->clipper)) + IDirectDrawClipper_AddRef(*clipper); wined3d_mutex_unlock();
return DD_OK; @@ -4501,7 +4508,7 @@ static HRESULT WINAPI ddraw_surface7_SetClipper(IDirectDrawSurface7 *iface,
if (clipper != NULL) IDirectDrawClipper_AddRef(iclipper); - if (old_clipper) + if (old_clipper && ddraw_clipper_is_valid(old_clipper)) IDirectDrawClipper_Release(&old_clipper->IDirectDrawClipper_iface);
if ((This->surface_desc.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) && This->ddraw->wined3d_swapchain) @@ -5782,7 +5789,7 @@ static void STDMETHODCALLTYPE ddraw_surface_wined3d_object_destroyed(void *paren /* Reduce the ddraw surface count. */ list_remove(&surface->surface_list_entry);
- if (surface->clipper) + if (surface->clipper && ddraw_clipper_is_valid(surface->clipper)) IDirectDrawClipper_Release(&surface->clipper->IDirectDrawClipper_iface);
if (surface == surface->ddraw->primary)
Signed-off-by: Stefan Dösinger stefan@codeweavers.com
---
Version 2: Adjust for different Wine behavior due to using the vtable to identify broken clippers.
Add a comment that surface->SetClipper with an unreadable pointer crashes. I don't know why I previously had the idea that this works. --- dlls/ddraw/tests/ddraw1.c | 151 ++++++++++++++++++++++++++++++++++++ dlls/ddraw/tests/ddraw2.c | 158 ++++++++++++++++++++++++++++++++++++++ dlls/ddraw/tests/ddraw4.c | 157 +++++++++++++++++++++++++++++++++++++ dlls/ddraw/tests/ddraw7.c | 157 +++++++++++++++++++++++++++++++++++++ 4 files changed, 623 insertions(+)
diff --git a/dlls/ddraw/tests/ddraw1.c b/dlls/ddraw/tests/ddraw1.c index d0ed33dbc41..cf53cc1f945 100644 --- a/dlls/ddraw/tests/ddraw1.c +++ b/dlls/ddraw/tests/ddraw1.c @@ -12293,6 +12293,156 @@ static void test_alphatest(void) DestroyWindow(window); }
+static void test_clipper_refcount(void) +{ + IDirectDrawSurface *surface; + IDirectDrawClipper *clipper, *clipper2; + DDSURFACEDESC surface_desc; + IDirectDraw *ddraw; + ULONG refcount; + HWND window; + HRESULT hr; + BOOL changed; + const IDirectDrawClipperVtbl *orig_vtbl; + + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + hr = IDirectDraw_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS; + surface_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + hr = IDirectDraw_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + hr = IDirectDraw_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Show that clipper validation doesn't somehow happen through per-clipper vtable + * pointers. */ + hr = IDirectDraw_CreateClipper(ddraw, 0, &clipper2, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + ok(clipper->lpVtbl == clipper2->lpVtbl, "Got different clipper vtables %p and %p.\n", + clipper->lpVtbl, clipper2->lpVtbl); + orig_vtbl = clipper->lpVtbl; + IDirectDrawClipper_Release(clipper2); + + /* Surfaces hold a reference to clippers. No surprises there. */ + hr = IDirectDrawSurface_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + refcount = IDirectDrawClipper_Release(clipper2); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface_SetClipper(surface, NULL); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + refcount = IDirectDrawSurface_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* SetClipper with an invalid pointer crashes. */ + + /* Clipper methods work with a broken vtable, with the exception of Release. */ + clipper->lpVtbl = (void *)0xdeadbeef; + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + + clipper->lpVtbl = orig_vtbl; + refcount = orig_vtbl->Release(clipper); + todo_wine ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Fix the refcount difference because Wine did not increase the ref in the + * AddRef call above. */ + if (refcount) + { + refcount = IDirectDrawClipper_Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + } + + /* Steal the reference and see what happens - releasing the surface works fine. + * The clipper is destroyed and not kept alive by a hidden refcount - trying to + * release it after the GetClipper call is likely to crash, and certain to crash + * if we allocate and zero as much heap memory as we can get. */ + hr = IDirectDraw_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + hr = IDirectDraw_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + hr = IDirectDrawSurface_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + + IDirectDrawClipper_Release(clipper); + IDirectDrawClipper_Release(clipper); + + hr = IDirectDrawSurface_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + + /* Show that invoking the Release method does not crash, but don't get the + * vtable through the clipper pointer because it is no longer pointing to + * valid memory. */ + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "%u references left.\n", refcount); + + refcount = IDirectDrawSurface_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + + /* It looks like the protection against invalid thispointers is part of + * the IDirectDrawClipper method implementation, not IDirectDrawSurface. */ + clipper = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000); + ok(!!clipper, "failed to allocate memory\n"); + + /* Assigning the vtable to our fake clipper does NOT make a difference on + * native - there is a different member of the clipper implementation struct + * that is used to determine if a clipper is valid. */ + clipper->lpVtbl = orig_vtbl; + + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(!refcount, "Got refcount %u.\n", refcount); + refcount = orig_vtbl->AddRef((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef); + ok(!refcount, "Got refcount %u.\n", refcount); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged(clipper, &changed); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + todo_wine ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef, &changed); + ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + /* Nope, we can't initialize our fake clipper. */ + hr = orig_vtbl->Initialize(clipper, ddraw, 0); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + + HeapFree(GetProcessHeap(), 0, clipper); + + refcount = IDirectDraw_Release(ddraw); + ok(!refcount, "%u references left.\n", refcount); + DestroyWindow(window); +} + START_TEST(ddraw1) { DDDEVICEIDENTIFIER identifier; @@ -12401,4 +12551,5 @@ START_TEST(ddraw1) test_killfocus(); test_gdi_surface(); test_alphatest(); + test_clipper_refcount(); } diff --git a/dlls/ddraw/tests/ddraw2.c b/dlls/ddraw/tests/ddraw2.c index 37238e162fb..7bf6dca422a 100644 --- a/dlls/ddraw/tests/ddraw2.c +++ b/dlls/ddraw/tests/ddraw2.c @@ -13552,6 +13552,163 @@ static void test_alphatest(void) DestroyWindow(window); }
+static void test_clipper_refcount(void) +{ + IDirectDrawSurface *surface; + IDirectDrawClipper *clipper, *clipper2; + DDSURFACEDESC surface_desc; + IDirectDraw2 *ddraw; + IDirectDraw *ddraw1; + ULONG refcount; + HWND window; + HRESULT hr; + BOOL changed; + const IDirectDrawClipperVtbl *orig_vtbl; + + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + hr = IDirectDraw2_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS; + surface_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + hr = IDirectDraw2_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + hr = IDirectDraw2_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Show that clipper validation doesn't somehow happen through per-clipper vtable + * pointers. */ + hr = IDirectDraw2_CreateClipper(ddraw, 0, &clipper2, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + ok(clipper->lpVtbl == clipper2->lpVtbl, "Got different clipper vtables %p and %p.\n", + clipper->lpVtbl, clipper2->lpVtbl); + orig_vtbl = clipper->lpVtbl; + IDirectDrawClipper_Release(clipper2); + + /* Surfaces hold a reference to clippers. No surprises there. */ + hr = IDirectDrawSurface_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + refcount = IDirectDrawClipper_Release(clipper2); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface_SetClipper(surface, NULL); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + refcount = IDirectDrawSurface_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* SetClipper with an invalid pointer crashes. */ + + /* Clipper methods work with a broken vtable, with the exception of Release. */ + clipper->lpVtbl = (void *)0xdeadbeef; + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + + clipper->lpVtbl = orig_vtbl; + refcount = orig_vtbl->Release(clipper); + todo_wine ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Fix the refcount difference because Wine did not increase the ref in the + * AddRef call above. */ + if (refcount) + { + refcount = IDirectDrawClipper_Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + } + + /* Steal the reference and see what happens - releasing the surface works fine. + * The clipper is destroyed and not kept alive by a hidden refcount - trying to + * release it after the GetClipper call is likely to crash, and certain to crash + * if we allocate and zero as much heap memory as we can get. */ + hr = IDirectDraw2_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + hr = IDirectDraw2_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + hr = IDirectDrawSurface_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + + IDirectDrawClipper_Release(clipper); + IDirectDrawClipper_Release(clipper); + + hr = IDirectDrawSurface_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + + /* Show that invoking the Release method does not crash, but don't get the + * vtable through the clipper pointer because it is no longer pointing to + * valid memory. */ + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "%u references left.\n", refcount); + + refcount = IDirectDrawSurface_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + + /* It looks like the protection against invalid thispointers is part of + * the IDirectDrawClipper method implementation, not IDirectDrawSurface. */ + clipper = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000); + ok(!!clipper, "failed to allocate memory\n"); + + /* Assigning the vtable to our fake clipper does NOT make a difference on + * native - there is a different member of the clipper implementation struct + * that is used to determine if a clipper is valid. */ + clipper->lpVtbl = orig_vtbl; + + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(!refcount, "Got refcount %u.\n", refcount); + refcount = orig_vtbl->AddRef((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef); + ok(!refcount, "Got refcount %u.\n", refcount); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged(clipper, &changed); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + todo_wine ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef, &changed); + ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + /* Nope, we can't initialize our fake clipper. */ + hr = IDirectDraw2_QueryInterface(ddraw, &IID_IDirectDraw, (void **)&ddraw1); + ok(SUCCEEDED(hr), "Failed to get ddraw1 interface, hr %#x.\n", hr); + + hr = orig_vtbl->Initialize(clipper, ddraw1, 0); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + + IDirectDraw_Release(ddraw1); + + HeapFree(GetProcessHeap(), 0, clipper); + + refcount = IDirectDraw2_Release(ddraw); + ok(!refcount, "%u references left.\n", refcount); + DestroyWindow(window); +} + + START_TEST(ddraw2) { DDDEVICEIDENTIFIER identifier; @@ -13668,4 +13825,5 @@ START_TEST(ddraw2) test_killfocus(); test_gdi_surface(); test_alphatest(); + test_clipper_refcount(); } diff --git a/dlls/ddraw/tests/ddraw4.c b/dlls/ddraw/tests/ddraw4.c index b60606fca3a..51052fb3568 100644 --- a/dlls/ddraw/tests/ddraw4.c +++ b/dlls/ddraw/tests/ddraw4.c @@ -15798,6 +15798,162 @@ static void test_alphatest(void) DestroyWindow(window); }
+static void test_clipper_refcount(void) +{ + IDirectDrawSurface4 *surface; + IDirectDrawClipper *clipper, *clipper2; + DDSURFACEDESC2 surface_desc; + IDirectDraw4 *ddraw; + IDirectDraw *ddraw1; + ULONG refcount; + HWND window; + HRESULT hr; + BOOL changed; + const IDirectDrawClipperVtbl *orig_vtbl; + + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + hr = IDirectDraw4_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS; + surface_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + hr = IDirectDraw4_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + hr = IDirectDraw4_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Show that clipper validation doesn't somehow happen through per-clipper vtable + * pointers. */ + hr = IDirectDraw4_CreateClipper(ddraw, 0, &clipper2, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + ok(clipper->lpVtbl == clipper2->lpVtbl, "Got different clipper vtables %p and %p.\n", + clipper->lpVtbl, clipper2->lpVtbl); + orig_vtbl = clipper->lpVtbl; + IDirectDrawClipper_Release(clipper2); + + /* Surfaces hold a reference to clippers. No surprises there. */ + hr = IDirectDrawSurface4_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface4_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + refcount = IDirectDrawClipper_Release(clipper2); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface4_SetClipper(surface, NULL); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface4_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + refcount = IDirectDrawSurface4_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* SetClipper with an invalid pointer crashes. */ + + /* Clipper methods work with a broken vtable, with the exception of Release. */ + clipper->lpVtbl = (void *)0xdeadbeef; + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + + clipper->lpVtbl = orig_vtbl; + refcount = orig_vtbl->Release(clipper); + todo_wine ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Fix the refcount difference because Wine did not increase the ref in the + * AddRef call above. */ + if (refcount) + { + refcount = IDirectDrawClipper_Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + } + + /* Steal the reference and see what happens - releasing the surface works fine. + * The clipper is destroyed and not kept alive by a hidden refcount - trying to + * release it after the GetClipper call is likely to crash, and certain to crash + * if we allocate and zero as much heap memory as we can get. */ + hr = IDirectDraw4_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + hr = IDirectDraw4_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + hr = IDirectDrawSurface4_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + + IDirectDrawClipper_Release(clipper); + IDirectDrawClipper_Release(clipper); + + hr = IDirectDrawSurface4_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + + /* Show that invoking the Release method does not crash, but don't get the + * vtable through the clipper pointer because it is no longer pointing to + * valid memory. */ + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "%u references left.\n", refcount); + + refcount = IDirectDrawSurface4_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + + /* It looks like the protection against invalid thispointers is part of + * the IDirectDrawClipper method implementation, not IDirectDrawSurface. */ + clipper = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000); + ok(!!clipper, "failed to allocate memory\n"); + + /* Assigning the vtable to our fake clipper does NOT make a difference on + * native - there is a different member of the clipper implementation struct + * that is used to determine if a clipper is valid. */ + clipper->lpVtbl = orig_vtbl; + + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(!refcount, "Got refcount %u.\n", refcount); + refcount = orig_vtbl->AddRef((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef); + ok(!refcount, "Got refcount %u.\n", refcount); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged(clipper, &changed); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + todo_wine ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef, &changed); + ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + /* Nope, we can't initialize our fake clipper. */ + hr = IDirectDraw4_QueryInterface(ddraw, &IID_IDirectDraw, (void **)&ddraw1); + ok(SUCCEEDED(hr), "Failed to get ddraw1 interface, hr %#x.\n", hr); + + hr = orig_vtbl->Initialize(clipper, ddraw1, 0); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + + IDirectDraw_Release(ddraw1); + + HeapFree(GetProcessHeap(), 0, clipper); + + refcount = IDirectDraw4_Release(ddraw); + ok(!refcount, "%u references left.\n", refcount); + DestroyWindow(window); +} + START_TEST(ddraw4) { DDDEVICEIDENTIFIER identifier; @@ -15929,4 +16085,5 @@ START_TEST(ddraw4) test_sysmem_draw(); test_gdi_surface(); test_alphatest(); + test_clipper_refcount(); } diff --git a/dlls/ddraw/tests/ddraw7.c b/dlls/ddraw/tests/ddraw7.c index 3339b32d200..c199994d520 100644 --- a/dlls/ddraw/tests/ddraw7.c +++ b/dlls/ddraw/tests/ddraw7.c @@ -15599,6 +15599,162 @@ static void test_alphatest(void) DestroyWindow(window); }
+static void test_clipper_refcount(void) +{ + IDirectDrawSurface7 *surface; + IDirectDrawClipper *clipper, *clipper2; + DDSURFACEDESC2 surface_desc; + IDirectDraw7 *ddraw; + IDirectDraw *ddraw1; + ULONG refcount; + HWND window; + HRESULT hr; + BOOL changed; + const IDirectDrawClipperVtbl *orig_vtbl; + + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + hr = IDirectDraw7_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS; + surface_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + hr = IDirectDraw7_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + + hr = IDirectDraw7_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Show that clipper validation doesn't somehow happen through per-clipper vtable + * pointers. */ + hr = IDirectDraw7_CreateClipper(ddraw, 0, &clipper2, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + ok(clipper->lpVtbl == clipper2->lpVtbl, "Got different clipper vtables %p and %p.\n", + clipper->lpVtbl, clipper2->lpVtbl); + orig_vtbl = clipper->lpVtbl; + IDirectDrawClipper_Release(clipper2); + + /* Surfaces hold a reference to clippers. No surprises there. */ + hr = IDirectDrawSurface7_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface7_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + refcount = IDirectDrawClipper_Release(clipper2); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface7_SetClipper(surface, NULL); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + hr = IDirectDrawSurface7_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + + refcount = IDirectDrawSurface7_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + refcount = get_refcount((IUnknown *)clipper); + ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* SetClipper with an invalid pointer crashes. */ + + /* Clipper methods work with a broken vtable, with the exception of Release. */ + clipper->lpVtbl = (void *)0xdeadbeef; + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(refcount == 2, "Got unexpected refcount %u.\n", refcount); + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + + clipper->lpVtbl = orig_vtbl; + refcount = orig_vtbl->Release(clipper); + todo_wine ok(refcount == 1, "Got unexpected refcount %u.\n", refcount); + + /* Fix the refcount difference because Wine did not increase the ref in the + * AddRef call above. */ + if (refcount) + { + refcount = IDirectDrawClipper_Release(clipper); + ok(!refcount, "Got unexpected refcount %u.\n", refcount); + } + + /* Steal the reference and see what happens - releasing the surface works fine. + * The clipper is destroyed and not kept alive by a hidden refcount - trying to + * release it after the GetClipper call is likely to crash, and certain to crash + * if we allocate and zero as much heap memory as we can get. */ + hr = IDirectDraw7_CreateSurface(ddraw, &surface_desc, &surface, NULL); + ok(hr == DD_OK, "Got unexpected hr %#x.\n", hr); + hr = IDirectDraw7_CreateClipper(ddraw, 0, &clipper, NULL); + ok(SUCCEEDED(hr), "Failed to create clipper, hr %#x.\n", hr); + hr = IDirectDrawSurface7_SetClipper(surface, clipper); + ok(SUCCEEDED(hr), "Failed to set clipper, hr %#x.\n", hr); + + IDirectDrawClipper_Release(clipper); + IDirectDrawClipper_Release(clipper); + + hr = IDirectDrawSurface7_GetClipper(surface, &clipper2); + ok(SUCCEEDED(hr), "Failed to get clipper, hr %#x.\n", hr); + ok(clipper == clipper2, "Got clipper %p, expected %p.\n", clipper2, clipper); + + /* Show that invoking the Release method does not crash, but don't get the + * vtable through the clipper pointer because it is no longer pointing to + * valid memory. */ + refcount = orig_vtbl->Release(clipper); + ok(!refcount, "%u references left.\n", refcount); + + refcount = IDirectDrawSurface7_Release(surface); + ok(!refcount, "%u references left.\n", refcount); + + /* It looks like the protection against invalid thispointers is part of + * the IDirectDrawClipper method implementation, not IDirectDrawSurface. */ + clipper = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000); + ok(!!clipper, "failed to allocate memory\n"); + + /* Assigning the vtable to our fake clipper does NOT make a difference on + * native - there is a different member of the clipper implementation struct + * that is used to determine if a clipper is valid. */ + clipper->lpVtbl = orig_vtbl; + + refcount = orig_vtbl->AddRef(clipper); + todo_wine ok(!refcount, "Got refcount %u.\n", refcount); + refcount = orig_vtbl->AddRef((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef); + ok(!refcount, "Got refcount %u.\n", refcount); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged(clipper, &changed); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + todo_wine ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + changed = 0x1234; + hr = orig_vtbl->IsClipListChanged((IDirectDrawClipper *)(ULONG_PTR)0xdeadbeef, &changed); + ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + ok(changed == 0x1234, "'changed' changed: %x.\n", changed); + + /* Nope, we can't initialize our fake clipper. */ + hr = IDirectDraw7_QueryInterface(ddraw, &IID_IDirectDraw, (void **)&ddraw1); + ok(SUCCEEDED(hr), "Failed to get ddraw1 interface, hr %#x.\n", hr); + + hr = orig_vtbl->Initialize(clipper, ddraw1, 0); + todo_wine ok(hr == DDERR_INVALIDPARAMS, "Got unexpected hr %#x.\n", hr); + + IDirectDraw_Release(ddraw1); + + HeapFree(GetProcessHeap(), 0, clipper); + + refcount = IDirectDraw7_Release(ddraw); + ok(!refcount, "%u references left.\n", refcount); + DestroyWindow(window); +} + START_TEST(ddraw7) { DDDEVICEIDENTIFIER2 identifier; @@ -15742,4 +15898,5 @@ START_TEST(ddraw7) test_gdi_surface(); test_multiply_transform(); test_alphatest(); + test_clipper_refcount(); }
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=49747
Your paranoid android.
=== wvistau64_zh_CN (32 bit report) ===
ddraw: ddraw2.c:2892: Test failed: Expected message 0x46, but didn't receive it. ddraw2.c:2894: Test failed: Expected screen size 1024x768, got 0x0. ddraw2.c:2900: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:2930: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:2937: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:2963: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:2986: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3015: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3041: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3061: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3097: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3107: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3133: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3156: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3178: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3204: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3224: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745). ddraw2.c:3261: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,745).
=== wvistau64 (task log) ===
Task errors: TestBot process got stuck or died unexpectedly The previous 1 run(s) terminated abnormally
=== wvistau64 (32 bit report) ===
ddraw: ddraw7.c:2719: Test failed: Expected message 0x46, but didn't receive it. ddraw7.c:2721: Test failed: Expected screen size 1024x768, got 0x0. ddraw7.c:2727: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2757: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2764: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2790: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2813: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2835: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2861: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2881: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2917: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2927: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2953: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2976: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:2998: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:3024: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:3044: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746). ddraw7.c:3081: Test failed: Expected (0,0)-(1024,768), got (-8,-8)-(1032,746).
=== debian9 (build log) ===
X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig)
=== debian9b (build log) ===
X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig)
Signed-off-by: Henri Verbeet hverbeet@codeweavers.com
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=49746
Your paranoid android.
=== debian9 (build log) ===
X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig)
=== debian9b (build log) ===
X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig) X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 140 (RANDR) Minor opcode of failed request: 21 (RRSetCrtcConfig)