1 /*
2  * PROJECT:     ReactOS api tests
3  * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:     Test for Rtl Critical Section API
5  * COPYRIGHT:   Copyright 2023 Timo Kreuzer <timo.kreuzer@reactos.org>
6  */
7 
8 #include "precomp.h"
9 #include <pseh/pseh2.h>
10 
11 SYSTEM_INFO g_SysInfo;
12 OSVERSIONINFOEXA g_VerInfo;
13 ULONG g_Version;
14 ULONG g_DefaultSpinCount;
15 
16 typedef
17 NTSTATUS
18 NTAPI
19 FN_RtlInitializeCriticalSectionEx(
20     _Out_ PRTL_CRITICAL_SECTION CriticalSection,
21     _In_ ULONG SpinCount,
22     _In_ ULONG Flags);
23 
24 FN_RtlInitializeCriticalSectionEx* pfnRtlInitializeCriticalSectionEx;
25 RTL_CRITICAL_SECTION CritSect;
26 HANDLE hEventThread1Ready, hEventThread1Cont;
27 HANDLE hEventThread2Ready, hEventThread2Cont;
28 
29 static
30 void
31 Test_Init(void)
32 {
33     NTSTATUS Status;
34     BOOL HasDebugInfo = (g_Version <= _WIN32_WINNT_VISTA);
35 
36     _SEH2_TRY
37     {
38         RtlInitializeCriticalSection(NULL);
39         Status = STATUS_SUCCESS;
40     }
41     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
42     {
43         Status = _SEH2_GetExceptionCode();
44     }
45     _SEH2_END;
46     ok_ntstatus(Status, STATUS_ACCESS_VIOLATION);
47 
48     Status = RtlInitializeCriticalSection(&CritSect);
49     ok_ntstatus(Status, STATUS_SUCCESS);
50     ok_long(CritSect.LockCount, -1);
51     ok_long(CritSect.RecursionCount, 0);
52     ok_ptr(CritSect.OwningThread, NULL);
53     ok_ptr(CritSect.LockSemaphore, NULL);
54     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
55     if (HasDebugInfo)
56     {
57         ok(CritSect.DebugInfo != NULL, "DebugInfo is %p\n", CritSect.DebugInfo);
58         ok(CritSect.DebugInfo != LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
59     }
60     else
61     {
62         ok(CritSect.DebugInfo == LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
63     }
64 
65     Status = RtlInitializeCriticalSectionAndSpinCount(&CritSect, 0);
66     ok_ntstatus(Status, STATUS_SUCCESS);
67     ok_long(CritSect.LockCount, -1);
68     ok_long(CritSect.RecursionCount, 0);
69     ok_ptr(CritSect.OwningThread, NULL);
70     ok_ptr(CritSect.LockSemaphore, NULL);
71     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
72     if (HasDebugInfo)
73     {
74         ok(CritSect.DebugInfo != NULL, "DebugInfo is %p\n", CritSect.DebugInfo);
75         ok(CritSect.DebugInfo != LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
76     }
77     else
78     {
79         ok(CritSect.DebugInfo == LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
80     }
81 
82     Status = RtlInitializeCriticalSectionAndSpinCount(&CritSect, 0xFF000000);
83     ok_ntstatus(Status, STATUS_SUCCESS);
84     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
85 
86     Status = RtlInitializeCriticalSectionAndSpinCount(&CritSect, 0x1234);
87     ok_ntstatus(Status, STATUS_SUCCESS);
88     ok_size_t(CritSect.SpinCount, (g_SysInfo.dwNumberOfProcessors > 1) ? 0x1234 : 0);
89 
90     if (pfnRtlInitializeCriticalSectionEx != NULL)
91     {
92         _SEH2_TRY
93         {
94             pfnRtlInitializeCriticalSectionEx(NULL, 0, 0);
95             Status = STATUS_SUCCESS;
96         }
97         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
98         {
99             Status = _SEH2_GetExceptionCode();
100         }
101         _SEH2_END;
102         ok_ntstatus(Status, STATUS_ACCESS_VIOLATION);
103 
104         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0, 0);
105         ok_ntstatus(Status, STATUS_SUCCESS);
106         ok_long(CritSect.LockCount, -1);
107         ok_long(CritSect.RecursionCount, 0);
108         ok_ptr(CritSect.OwningThread, NULL);
109         ok_ptr(CritSect.LockSemaphore, NULL);
110         ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
111         if (HasDebugInfo)
112         {
113             ok(CritSect.DebugInfo != NULL, "DebugInfo is %p\n", CritSect.DebugInfo);
114             ok(CritSect.DebugInfo != LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
115             if ((CritSect.DebugInfo != NULL) && (CritSect.DebugInfo != LongToPtr(-1)))
116             {
117                 ok_int(CritSect.DebugInfo->Type, 0);
118                 ok_int(CritSect.DebugInfo->CreatorBackTraceIndex, 0);
119                 ok_int(CritSect.DebugInfo->CreatorBackTraceIndexHigh, 0);
120                 ok_ptr(CritSect.DebugInfo->CriticalSection, &CritSect);
121                 ok(CritSect.DebugInfo->ProcessLocksList.Flink != NULL, "Flink is NULL\n");
122                 ok(CritSect.DebugInfo->ProcessLocksList.Blink != NULL, "Blink is NULL\n");
123                 if ((CritSect.DebugInfo->ProcessLocksList.Flink != NULL) &&
124                     (CritSect.DebugInfo->ProcessLocksList.Blink != NULL))
125                 {
126                     ok_ptr(CritSect.DebugInfo->ProcessLocksList.Flink->Blink,
127                         &CritSect.DebugInfo->ProcessLocksList);
128                     ok_ptr(CritSect.DebugInfo->ProcessLocksList.Blink->Flink,
129                         &CritSect.DebugInfo->ProcessLocksList);
130                 }
131                 ok_long(CritSect.DebugInfo->EntryCount, 0);
132                 ok_long(CritSect.DebugInfo->ContentionCount, 0);
133                 ok_long(CritSect.DebugInfo->Flags, 0);
134                 ok_int(CritSect.DebugInfo->SpareWORD, 0);
135             }
136         }
137         else
138         {
139             ok(CritSect.DebugInfo == LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
140         }
141 
142         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0x00FFFFFF, 0);
143         ok_size_t(CritSect.SpinCount, (g_SysInfo.dwNumberOfProcessors > 1) ? 0x00FFFFFF : 0);
144 
145         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0x01000000, 0);
146         ok_ntstatus(Status, STATUS_INVALID_PARAMETER_2);
147 
148         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0x80000000, 0);
149         ok_ntstatus(Status, STATUS_INVALID_PARAMETER_2);
150 
151         _SEH2_TRY
152         {
153             Status = pfnRtlInitializeCriticalSectionEx(NULL, 0x12345678, 0);
154         }
155         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
156         {
157             Status = _SEH2_GetExceptionCode();
158         }
159         _SEH2_END;
160         ok_ntstatus(Status, STATUS_INVALID_PARAMETER_2);
161 
162         for (ULONG i = 0; i < 32; i++)
163         {
164             ULONG Flags = 1UL << i;
165             ULONG AllowedFlags = 0x07FFFFFF;
166             if (g_Version >= _WIN32_WINNT_WIN7) AllowedFlags |= 0x18000000;
167             NTSTATUS ExpectedStatus = (Flags & ~AllowedFlags) ?
168                 STATUS_INVALID_PARAMETER_3 : STATUS_SUCCESS;
169             Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0, Flags);
170             ok(Status == ExpectedStatus, "Wrong Status (0x%lx) for Flags 0x%lx\n",
171                 Status, Flags);
172             if (NT_SUCCESS(Status))
173             {
174                 ULONG SetFlags = Flags & 0x08000000;
175                 if (g_Version >= _WIN32_WINNT_WIN7) SetFlags |= (Flags & 0x03000000);
176                 ok_size_t(CritSect.SpinCount, (g_SysInfo.dwNumberOfProcessors > 1) ? g_DefaultSpinCount | SetFlags : SetFlags);
177             }
178         }
179 
180         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0, 0xFFFFFFFF);
181         ok_ntstatus(Status, STATUS_INVALID_PARAMETER_3);
182         _SEH2_TRY
183         {
184             Status = pfnRtlInitializeCriticalSectionEx(NULL, 0, 0xFFFFFFFF);
185         }
186         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
187         {
188             Status = _SEH2_GetExceptionCode();
189         }
190         _SEH2_END;
191         ok_ntstatus(Status, STATUS_INVALID_PARAMETER_3);
192 
193         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0x12345678, 0xFFFFFFFF);
194         ok_ntstatus(Status, STATUS_INVALID_PARAMETER_3);
195 
196         Status = pfnRtlInitializeCriticalSectionEx(&CritSect, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO);
197         if (g_Version >= _WIN32_WINNT_WIN7)
198         {
199             ok_ntstatus(Status, STATUS_SUCCESS);
200             ok(CritSect.DebugInfo != NULL, "DebugInfo is %p\n", CritSect.DebugInfo);
201             ok(CritSect.DebugInfo != LongToPtr(-1), "DebugInfo is %p\n", CritSect.DebugInfo);
202             if ((CritSect.DebugInfo != NULL) && (CritSect.DebugInfo != LongToPtr(-1)))
203             {
204                 ok_int(CritSect.DebugInfo->Type, 0);
205                 ok_int(CritSect.DebugInfo->CreatorBackTraceIndex, 0);
206                 ok_int(CritSect.DebugInfo->CreatorBackTraceIndexHigh, 0);
207                 ok_ptr(CritSect.DebugInfo->CriticalSection, &CritSect);
208                 ok(CritSect.DebugInfo->ProcessLocksList.Flink != NULL, "Flink is NULL\n");
209                 ok(CritSect.DebugInfo->ProcessLocksList.Blink != NULL, "Blink is NULL\n");
210                 if ((CritSect.DebugInfo->ProcessLocksList.Flink != NULL) &&
211                     (CritSect.DebugInfo->ProcessLocksList.Blink != NULL))
212                 {
213                     ok_ptr(CritSect.DebugInfo->ProcessLocksList.Flink->Blink,
214                         &CritSect.DebugInfo->ProcessLocksList);
215                     ok_ptr(CritSect.DebugInfo->ProcessLocksList.Blink->Flink,
216                         &CritSect.DebugInfo->ProcessLocksList);
217                 }
218                 ok_long(CritSect.DebugInfo->EntryCount, 0);
219                 ok_long(CritSect.DebugInfo->ContentionCount, 0);
220                 ok_long(CritSect.DebugInfo->Flags, 0);
221                 ok_int(CritSect.DebugInfo->SpareWORD, 0);
222             }
223         }
224         else
225         {
226             ok_ntstatus(Status, STATUS_INVALID_PARAMETER_3);
227         }
228     }
229     else
230     {
231         skip("RtlInitializeCriticalSectionEx not available.\n");
232     }
233 }
234 
235 static
236 DWORD
237 WINAPI
238 ThreadProc1(
239     _In_ LPVOID lpParameter)
240 {
241     printf("ThreadProc1 starting\n");
242     RtlEnterCriticalSection(&CritSect);
243 
244     SetEvent(hEventThread1Ready);
245 
246     printf("ThreadProc1 waiting\n");
247     WaitForSingleObject(hEventThread1Cont, INFINITE);
248     printf("ThreadProc1 returned from wait\n");
249 
250     RtlLeaveCriticalSection(&CritSect);
251 
252     return 0;
253 }
254 
255 static
256 DWORD
257 WINAPI
258 ThreadProc2(
259     _In_ LPVOID lpParameter)
260 {
261     printf("ThreadProc2 starting\n");
262     RtlEnterCriticalSection(&CritSect);
263 
264     SetEvent(hEventThread2Ready);
265 
266     printf("ThreadProc2 waiting\n");
267     WaitForSingleObject(hEventThread2Cont, INFINITE);
268     printf("ThreadProc2 returned from wait\n");
269 
270     RtlLeaveCriticalSection(&CritSect);
271 
272     return 0;
273 }
274 
275 static
276 void
277 Test_Acquire(void)
278 {
279     DWORD dwThreadId1, dwThreadId2;
280     HANDLE hThread1, hThread2;
281 
282     RtlInitializeCriticalSection(&CritSect);
283 
284     // Acquire once
285     RtlEnterCriticalSection(&CritSect);
286     ok_long(CritSect.LockCount, -2);
287     ok_long(CritSect.RecursionCount, 1);
288     ok_ptr(CritSect.OwningThread, UlongToHandle(GetCurrentThreadId()));
289     ok_ptr(CritSect.LockSemaphore, NULL);
290     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
291 
292     // Acquire recursively
293     RtlEnterCriticalSection(&CritSect);
294     ok_long(CritSect.LockCount, -2);
295     ok_long(CritSect.RecursionCount, 2);
296     ok_ptr(CritSect.OwningThread, UlongToHandle(GetCurrentThreadId()));
297     ok_ptr(CritSect.LockSemaphore, NULL);
298     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
299 
300     hEventThread1Ready = CreateEvent(NULL, TRUE, FALSE, NULL);
301     hEventThread1Cont = CreateEvent(NULL, TRUE, FALSE, NULL);
302     hEventThread2Ready = CreateEvent(NULL, TRUE, FALSE, NULL);
303     hEventThread2Cont = CreateEvent(NULL, TRUE, FALSE, NULL);
304 
305     // Create thread 1 and wait to it time to try to acquire the critical section
306     hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &dwThreadId1);
307 
308     // Wait up to 10 s
309     for (ULONG i = 0; (CritSect.LockCount == -2) && (i < 1000); i++)
310     {
311         Sleep(10);
312     }
313 
314     ok_long(CritSect.LockCount, -6);
315     ok_long(CritSect.RecursionCount, 2);
316     ok_ptr(CritSect.OwningThread, UlongToHandle(GetCurrentThreadId()));
317     //ok_ptr(CritSect.LockSemaphore, LongToPtr(-1)); // TODO: this behaves differently on different OS versions
318     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
319 
320     // Create thread 2 and wait to it time to try to acquire the critical section
321     hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &dwThreadId2);
322 
323     // Wait up to 10 s
324     for (ULONG i = 0; (CritSect.LockCount == -6) && (i < 1000); i++)
325     {
326         Sleep(10);
327     }
328 
329     ok_long(CritSect.LockCount, -10);
330     ok_long(CritSect.RecursionCount, 2);
331     ok_ptr(CritSect.OwningThread, UlongToHandle(GetCurrentThreadId()));
332     //ok_ptr(CritSect.LockSemaphore, LongToPtr(-1));
333     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
334 
335     RtlLeaveCriticalSection(&CritSect);
336     ok_long(CritSect.LockCount, -10);
337     ok_long(CritSect.RecursionCount, 1);
338     RtlLeaveCriticalSection(&CritSect);
339 
340     // Wait until thread 1 has acquired the critical section
341     WaitForSingleObject(hEventThread1Ready, INFINITE);
342 
343     ok_long(CritSect.LockCount, -6);
344     ok_long(CritSect.RecursionCount, 1);
345     ok_ptr(CritSect.OwningThread, UlongToHandle(dwThreadId1));
346     //ok_ptr(CritSect.LockSemaphore, LongToPtr(-1));
347     if (g_DefaultSpinCount != 0)
348     {
349         ok(CritSect.SpinCount <= g_DefaultSpinCount, "SpinCount increased\n");
350     }
351     else
352     {
353         ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
354     }
355 
356     ok_size_t(CritSect.SpinCount, g_DefaultSpinCount ? g_DefaultSpinCount - 1 : 0);
357 
358     // Release thread 1, wait for thread 2 to acquire the critical section
359     SetEvent(hEventThread1Cont);
360     WaitForSingleObject(hEventThread2Ready, INFINITE);
361 
362     ok_long(CritSect.LockCount, -2);
363     ok_long(CritSect.RecursionCount, 1);
364     ok_ptr(CritSect.OwningThread, UlongToHandle(dwThreadId2));
365     //ok_ptr(CritSect.LockSemaphore, LongToPtr(-1));
366     if (g_DefaultSpinCount != 0)
367     {
368         ok(CritSect.SpinCount <= g_DefaultSpinCount, "SpinCount increased\n");
369     }
370     else
371     {
372         ok_size_t(CritSect.SpinCount, g_DefaultSpinCount);
373     }
374 
375     // Release thread 2
376     SetEvent(hEventThread2Cont);
377 
378     // To make Thomas happy :)
379     WaitForSingleObject(hThread1, INFINITE);
380     WaitForSingleObject(hThread2, INFINITE);
381 }
382 
383 START_TEST(RtlCriticalSection)
384 {
385     HMODULE hmodNtDll = GetModuleHandleA("ntdll.dll");
386     pfnRtlInitializeCriticalSectionEx = (FN_RtlInitializeCriticalSectionEx*)
387         GetProcAddress(hmodNtDll, "RtlInitializeCriticalSectionEx");
388 
389     g_VerInfo.dwOSVersionInfoSize = sizeof(g_VerInfo);
390     GetVersionExA((LPOSVERSIONINFOA)&g_VerInfo);
391     g_Version = g_VerInfo.dwMajorVersion << 8 | g_VerInfo.dwMinorVersion;
392     printf("g_VerInfo: %lu.%lu.%lu ('%s')\n ",
393         g_VerInfo.dwMajorVersion,
394         g_VerInfo.dwMinorVersion,
395         g_VerInfo.dwBuildNumber,
396         g_VerInfo.szCSDVersion);
397     GetSystemInfo(&g_SysInfo);
398     printf("g_SysInfo.dwNumberOfProcessors = %lu\n", g_SysInfo.dwNumberOfProcessors);
399 
400     if ((g_Version >= _WIN32_WINNT_VISTA) && (g_SysInfo.dwNumberOfProcessors > 1))
401     {
402         g_DefaultSpinCount = 0x20007d0;
403     }
404     else
405     {
406         g_DefaultSpinCount = 0;
407     }
408 
409     Test_Init();
410     Test_Acquire();
411 }
412