summaryrefslogtreecommitdiff
path: root/test/tx/module_manager/threadx_module_manager_object_name_range_test.c
blob: 7e6ee5e5d9213cb3032cf565a7a64f661f22f000 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
/***************************************************************************
 * Copyright (c) 2026 Eclipse ThreadX contributors
 * 
 * This program and the accompanying materials are made available under the
 * terms of the MIT License which is available at
 * https://opensource.org/licenses/MIT.
 * 
 * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
 * The AI-generated portions may be considered public domain (CC0-1.0)
 * and not subject to the project's licence. The human contributor has
 * reviewed and verified that the code is correct.
 * 
 * SPDX-License-Identifier: MIT and CC0-1.0
 **************************************************************************/


/**************************************************************************/
/**************************************************************************/
/**                                                                       */
/** ThreadX Test                                                          */
/**                                                                       */
/**   Module Manager object name range validation                         */
/**                                                                       */
/**************************************************************************/
/**************************************************************************/

/* A memory-protected module asks the Module Manager to find a kernel object by name.
   The dispatch layer decides how much of the module's name buffer the privileged
   comparison may touch. It checked one byte.

   _txm_module_manager_object_name_compare reads a character from each name at the top
   of every iteration and tests the remaining search length at the bottom, so a declared
   length N permits reads at offsets 0 through N -- N+1 bytes, the extra one being the
   terminator a length excludes. The extended service receives that length in its extra
   parameter array, validates the array correctly, and then never uses the length to
   bound the buffer it describes. The deprecated service carries no length at all: its
   manager wrapper calls the same implementation with the largest value a UINT can hold,
   so it removes the bound rather than lacking one.

   The comparison stops at the first differing character, so the walk past the end of
   the module's memory is not free -- unless the module arranges for it to match. It can:
   a create service stores the name pointer a module supplies in the control block
   rather than copying the string, so a module can register an object whose name is the
   very buffer it then searches for. Both sides of the comparison are then the same
   address, every character matches by construction, and the walk continues until it
   meets a terminator in memory the module does not own and cannot see.

   This test drives both lookup dispatchers directly and measures two things rather than
   sampling either side of a boundary, because there are two dials here and the finding
   is about the relationship between them:

     - the extent the dispatcher validates, found by anchoring the name buffer to the end
       of one of the module's regions and walking it backwards a byte at a time until the
       request is accepted; and

     - the extent the comparison reads, found by filling memory with a filler character,
       planting the only terminator at a chosen depth from the buffer, and asking for an
       object whose name is exactly the bytes up to it. The lookup returns that object
       only if the comparison read that deep, so the deepest depth it can be made to
       return is the read extent.

   The assertion is the relationship: the comparison never reads further than the
   dispatcher validated, and the dispatcher validates the length the module declared plus
   its terminator. Before the fix the read extent follows the declared length and the
   validated extent stays at one byte, which is the defect stated as a measurement.

   Two mechanics come from the queue message range test, which was the first test in the
   tree to drive a dispatcher, and both are the reason this one is possible.

   The dispatch table is a header of static functions, one per kernel service, each
   wrapped in #ifndef TXM_<SERVICE>_CALL_NOT_USED. There are 96 of those guards. Defining
   the 94 this test does not exercise compiles the header down to the two lookup services,
   which keeps the link to the manager sources under test and a handful of kernel globals
   instead of the hundred-odd kernel entry points the whole table reaches.

   The extent has to reach a check that honours it, so this test compiles against a
   Cortex-M module port's headers, whose inline data check is the shape the memory
   protected module ports share. The Cortex-A7 port's data check takes the pointer alone,
   so a test built on it would pass equally with and without this fix.

   Unlike the queue test, this one cannot stub the service behind the dispatcher: the
   comparison being measured is the service. It links the real lookup implementation and
   the real comparison, and stands up the kernel created lists they walk.  */

#include <stdio.h>
#include <string.h>

#include "threadx_module_manager_host_test_port.h"

#include "tx_thread.h"
#include "tx_timer.h"
#include "tx_queue.h"
#include "tx_event_flags.h"
#include "tx_semaphore.h"
#include "tx_mutex.h"
#include "tx_block_pool.h"
#include "tx_byte_pool.h"
#include "txm_module.h"
#include "txm_module_manager_util.h"


/* Refuse every service in the dispatch table except the two under test. The list is the
   header's own guard names; a service added to the table appears here as a link error
   naming the kernel entry point it reaches, which is the right way to find out.  */

#define TXM_BLOCK_ALLOCATE_CALL_NOT_USED
#define TXM_BLOCK_POOL_CREATE_CALL_NOT_USED
#define TXM_BLOCK_POOL_DELETE_CALL_NOT_USED
#define TXM_BLOCK_POOL_INFO_GET_CALL_NOT_USED
#define TXM_BLOCK_POOL_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_BLOCK_POOL_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_BLOCK_POOL_PRIORITIZE_CALL_NOT_USED
#define TXM_BLOCK_RELEASE_CALL_NOT_USED
#define TXM_BYTE_ALLOCATE_CALL_NOT_USED
#define TXM_BYTE_POOL_CREATE_CALL_NOT_USED
#define TXM_BYTE_POOL_DELETE_CALL_NOT_USED
#define TXM_BYTE_POOL_INFO_GET_CALL_NOT_USED
#define TXM_BYTE_POOL_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_BYTE_POOL_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_BYTE_POOL_PRIORITIZE_CALL_NOT_USED
#define TXM_BYTE_RELEASE_CALL_NOT_USED
#define TXM_EVENT_FLAGS_CREATE_CALL_NOT_USED
#define TXM_EVENT_FLAGS_DELETE_CALL_NOT_USED
#define TXM_EVENT_FLAGS_GET_CALL_NOT_USED
#define TXM_EVENT_FLAGS_INFO_GET_CALL_NOT_USED
#define TXM_EVENT_FLAGS_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_EVENT_FLAGS_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_EVENT_FLAGS_SET_CALL_NOT_USED
#define TXM_EVENT_FLAGS_SET_NOTIFY_CALL_NOT_USED
#define TXM_MUTEX_CREATE_CALL_NOT_USED
#define TXM_MUTEX_DELETE_CALL_NOT_USED
#define TXM_MUTEX_GET_CALL_NOT_USED
#define TXM_MUTEX_INFO_GET_CALL_NOT_USED
#define TXM_MUTEX_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_MUTEX_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_MUTEX_PRIORITIZE_CALL_NOT_USED
#define TXM_MUTEX_PUT_CALL_NOT_USED
#define TXM_QUEUE_CREATE_CALL_NOT_USED
#define TXM_QUEUE_DELETE_CALL_NOT_USED
#define TXM_QUEUE_FLUSH_CALL_NOT_USED
#define TXM_QUEUE_FRONT_SEND_CALL_NOT_USED
#define TXM_QUEUE_INFO_GET_CALL_NOT_USED
#define TXM_QUEUE_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_QUEUE_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_QUEUE_PRIORITIZE_CALL_NOT_USED
#define TXM_QUEUE_RECEIVE_CALL_NOT_USED
#define TXM_QUEUE_SEND_CALL_NOT_USED
#define TXM_QUEUE_SEND_NOTIFY_CALL_NOT_USED
#define TXM_SEMAPHORE_CEILING_PUT_CALL_NOT_USED
#define TXM_SEMAPHORE_CREATE_CALL_NOT_USED
#define TXM_SEMAPHORE_DELETE_CALL_NOT_USED
#define TXM_SEMAPHORE_GET_CALL_NOT_USED
#define TXM_SEMAPHORE_INFO_GET_CALL_NOT_USED
#define TXM_SEMAPHORE_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_SEMAPHORE_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_SEMAPHORE_PRIORITIZE_CALL_NOT_USED
#define TXM_SEMAPHORE_PUT_CALL_NOT_USED
#define TXM_SEMAPHORE_PUT_NOTIFY_CALL_NOT_USED
#define TXM_THREAD_CREATE_CALL_NOT_USED
#define TXM_THREAD_DELETE_CALL_NOT_USED
#define TXM_THREAD_ENTRY_EXIT_NOTIFY_CALL_NOT_USED
#define TXM_THREAD_IDENTIFY_CALL_NOT_USED
#define TXM_THREAD_INFO_GET_CALL_NOT_USED
#define TXM_THREAD_INTERRUPT_CONTROL_CALL_NOT_USED
#define TXM_THREAD_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_THREAD_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_THREAD_PREEMPTION_CHANGE_CALL_NOT_USED
#define TXM_THREAD_PRIORITY_CHANGE_CALL_NOT_USED
#define TXM_THREAD_RELINQUISH_CALL_NOT_USED
#define TXM_THREAD_RESET_CALL_NOT_USED
#define TXM_THREAD_RESUME_CALL_NOT_USED
#define TXM_THREAD_SLEEP_CALL_NOT_USED
#define TXM_THREAD_STACK_ERROR_NOTIFY_CALL_NOT_USED
#define TXM_THREAD_SUSPEND_CALL_NOT_USED
#define TXM_THREAD_SYSTEM_SUSPEND_CALL_NOT_USED
#define TXM_THREAD_TERMINATE_CALL_NOT_USED
#define TXM_THREAD_TIME_SLICE_CHANGE_CALL_NOT_USED
#define TXM_THREAD_WAIT_ABORT_CALL_NOT_USED
#define TXM_TIME_GET_CALL_NOT_USED
#define TXM_TIME_SET_CALL_NOT_USED
#define TXM_TIMER_ACTIVATE_CALL_NOT_USED
#define TXM_TIMER_CHANGE_CALL_NOT_USED
#define TXM_TIMER_CREATE_CALL_NOT_USED
#define TXM_TIMER_DEACTIVATE_CALL_NOT_USED
#define TXM_TIMER_DELETE_CALL_NOT_USED
#define TXM_TIMER_INFO_GET_CALL_NOT_USED
#define TXM_TIMER_PERFORMANCE_INFO_GET_CALL_NOT_USED
#define TXM_TIMER_PERFORMANCE_SYSTEM_INFO_GET_CALL_NOT_USED
#define TXM_TRACE_BUFFER_FULL_NOTIFY_CALL_NOT_USED
#define TXM_TRACE_DISABLE_CALL_NOT_USED
#define TXM_TRACE_ENABLE_CALL_NOT_USED
#define TXM_TRACE_EVENT_FILTER_CALL_NOT_USED
#define TXM_TRACE_EVENT_UNFILTER_CALL_NOT_USED
#define TXM_TRACE_INTERRUPT_CONTROL_CALL_NOT_USED
#define TXM_TRACE_ISR_ENTER_INSERT_CALL_NOT_USED
#define TXM_TRACE_ISR_EXIT_INSERT_CALL_NOT_USED
#define TXM_TRACE_USER_EVENT_INSERT_CALL_NOT_USED
#define TXM_MODULE_OBJECT_ALLOCATE_CALL_NOT_USED
#define TXM_MODULE_OBJECT_DEALLOCATE_CALL_NOT_USED

#include "txm_module_manager_dispatch.h"

/* The two under test have to have survived that, and the rest have to have gone.  */

#if defined(TXM_MODULE_OBJECT_POINTER_GET_CALL_NOT_USED) || defined(TXM_MODULE_OBJECT_POINTER_GET_EXTENDED_CALL_NOT_USED)
#error "the lookup services under test must be compiled in"
#endif

#ifndef TXM_QUEUE_FLUSH_CALL_NOT_USED
#error "the services this test does not exercise must be compiled out"
#endif


/* Define the stand-in interrupt lock the port shim counts through.  */

unsigned int    test_interrupt_disable_depth;
unsigned int    test_interrupt_disable_max_depth;
unsigned int    test_interrupt_restore_underflows;


/* Define the manager state the utility source under test links against. No case here
   reaches the object pool: a lookup names no control block.  */

TX_BYTE_POOL    _txm_module_manager_object_pool;
UINT            _txm_module_manager_object_pool_created;


/* Define the kernel state the lookup reaches for. It walks a created list per object
   type, holds off preemption while it does, and asks the current thread which module it
   belongs to before it will search the two pool types.  */

TX_THREAD               *_tx_thread_current_ptr;
volatile UINT           _tx_thread_preempt_disable;

TX_BLOCK_POOL           *_tx_block_pool_created_ptr;
ULONG                   _tx_block_pool_created_count;
TX_BYTE_POOL            *_tx_byte_pool_created_ptr;
ULONG                   _tx_byte_pool_created_count;
TX_EVENT_FLAGS_GROUP    *_tx_event_flags_created_ptr;
ULONG                   _tx_event_flags_created_count;
TX_MUTEX                *_tx_mutex_created_ptr;
ULONG                   _tx_mutex_created_count;
TX_QUEUE                *_tx_queue_created_ptr;
ULONG                   _tx_queue_created_count;
TX_SEMAPHORE            *_tx_semaphore_created_ptr;
ULONG                   _tx_semaphore_created_count;
TX_THREAD               *_tx_thread_created_ptr;
ULONG                   _tx_thread_created_count;
TX_TIMER                *_tx_timer_created_ptr;
ULONG                   _tx_timer_created_count;


/* Count the lookup's final preemption check, and record what it left behind.

   The lookup calls this once, on its way out, after restoring the preemption count it
   raised on the way in. Counting it is how a case tells a request the dispatch layer
   refused from one it let through: a refusal returns before the lookup is entered at
   all. Recording the preemption count as the lookup leaves it is how the run as a whole
   asserts that the hold is balanced.  */

static ULONG    test_lookup_exits;
static ULONG    test_preempt_disable_on_exit;

VOID  _tx_thread_system_preempt_check(VOID)
{
    test_lookup_exits++;
    test_preempt_disable_on_exit =  (ULONG) _tx_thread_preempt_disable;
}


/* Stand in for the port primitive a manager source under test refers to but that no case
   here exercises.  */

VOID  _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble, ULONG *code_size,
                                           ULONG *code_alignment, ULONG *data_size, ULONG *data_alignment)
{
    (VOID) module_preamble;
    (VOID) code_size;
    (VOID) code_alignment;
    (VOID) data_size;
    (VOID) data_alignment;
}


static UINT     test_failures;
static ULONG    test_checks;


/* Record the outcome of one expectation.  */
static VOID  test_expect(const char *description, ULONG actual, ULONG expected)
{
    test_checks++;

    if (actual != expected)
    {
        printf("FAIL: %s (expected %lu, got %lu)\n", description, (unsigned long) expected, (unsigned long) actual);
        test_failures++;
    }
}


/* Model the module's memory as one arena, so that the three regions a name buffer may
   legitimately live in have known addresses and known gaps between them. The gaps are
   memory the module may not reach: they are what a buffer anchored to the end of a
   region overruns into, and they are inside the arena so that the overrun a case
   describes is memory this test owns.  */

#define TEST_REGION_BYTES   ((ULONG) 256)
#define TEST_GAP_BYTES      ((ULONG) 128)

static union
{
    ALIGN_TYPE  test_arena_alignment;
    UCHAR       test_arena_bytes[(((ULONG) 3) * TEST_REGION_BYTES) + (((ULONG) 4) * TEST_GAP_BYTES)];
} test_arena;

#define TEST_DATA_START     (&test_arena.test_arena_bytes[TEST_GAP_BYTES])
#define TEST_CODE_START     (&test_arena.test_arena_bytes[(((ULONG) 2) * TEST_GAP_BYTES) + TEST_REGION_BYTES])
#define TEST_SHARED_START   (&test_arena.test_arena_bytes[(((ULONG) 3) * TEST_GAP_BYTES) + (((ULONG) 2) * TEST_REGION_BYTES)])


/* The character memory is filled with. Any non-zero value will do: what matters is that
   a case can plant the only terminator in memory where it wants one, and that the bytes
   before it match a name the module chose.  */

#define TEST_FILLER         ((UCHAR) 0x5A)


/* The module's outgoing parameters live at the front of its data region, because the
   extra parameter array must be inside module data and the result slot must be
   writable by the module. Every name buffer is anchored to the end of a region, so the
   two never meet.  */

#define TEST_EXTRA_PARAMETERS   ((ALIGN_TYPE *) TEST_DATA_START)
#define TEST_RESULT_SLOT        ((VOID **) (TEST_DATA_START + (((ULONG) 2) * sizeof(ALIGN_TYPE))))


/* Define the requesting modules. One has memory protection, and is the one every range
   case below drives; the other does not, and is how the cases show that the dispatch
   layer's checks are not reached for it at all.  */

static TXM_MODULE_INSTANCE  test_protected_module;
static TXM_MODULE_INSTANCE  test_unprotected_module;


/* Place a module's regions on the arena.  */
static VOID  test_module_setup(TXM_MODULE_INSTANCE *module_instance, ULONG property_flags)
{
    memset((VOID *) module_instance, 0, sizeof(TXM_MODULE_INSTANCE));

    module_instance -> txm_module_instance_property_flags =  property_flags;

    module_instance -> txm_module_instance_data_start =  (VOID *) TEST_DATA_START;
    module_instance -> txm_module_instance_data_end =    (VOID *) (TEST_DATA_START + (TEST_REGION_BYTES - ((ULONG) 1)));

    module_instance -> txm_module_instance_code_start =  (VOID *) TEST_CODE_START;
    module_instance -> txm_module_instance_code_end =    (VOID *) (TEST_CODE_START + (TEST_REGION_BYTES - ((ULONG) 1)));

    module_instance -> txm_module_instance_shared_memory_address =  (ULONG) TEST_SHARED_START;
    module_instance -> txm_module_instance_shared_memory_length =   TEST_REGION_BYTES;
}


/* Define the thread the lookup asks which module is calling, and the objects the created
   lists are built from. The objects are outside the arena, where a kernel object belongs.  */

static TX_THREAD        test_current_thread;
static TX_SEMAPHORE     test_semaphore;
static TX_SEMAPHORE     test_decoy_semaphore;


/* Define three control blocks of each type for the cases that search every list the
   lookup knows: the match, and two ahead of it so that a list is walked rather than
   looked at.  */

#define TEST_OBJECTS_PER_TYPE   ((UINT) 3)

static TX_THREAD                test_thread_objects[TEST_OBJECTS_PER_TYPE];
static TX_TIMER                 test_timer_objects[TEST_OBJECTS_PER_TYPE];
static TX_QUEUE                 test_queue_objects[TEST_OBJECTS_PER_TYPE];
static TX_EVENT_FLAGS_GROUP     test_event_flags_objects[TEST_OBJECTS_PER_TYPE];
static TX_SEMAPHORE             test_semaphore_objects[TEST_OBJECTS_PER_TYPE];
static TX_MUTEX                 test_mutex_objects[TEST_OBJECTS_PER_TYPE];
static TX_BLOCK_POOL            test_block_pool_objects[TEST_OBJECTS_PER_TYPE];
static TX_BYTE_POOL             test_byte_pool_objects[TEST_OBJECTS_PER_TYPE];


/* Define the name a probing case gives an object. It is outside the arena too, because
   an object's name is not the module's memory even when the module supplied the pointer;
   the aliasing cases are the ones that make it so deliberately.  */

static CHAR     test_probe_name[TEST_REGION_BYTES + TEST_GAP_BYTES];


/* Put one object on the created list for its type, newest last, the way the kernel's
   create service links a new object in ahead of the head of the ring. The name is stored
   as the pointer the caller gave, because that is what a create service does with it and
   it is the reason the aliasing cases below are possible at all.

   Every list is built the same way and the eight blocks differ only in the type they
   name, which is how the lookup itself is written.  */
static VOID  test_object_create(UINT object_type, VOID *object_ptr, CHAR *name)
{
    switch (object_type)
    {

    case TXM_THREAD_OBJECT:
    {
        TX_THREAD   *thread_ptr =  (TX_THREAD *) object_ptr;
        TX_THREAD   *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_THREAD));
        thread_ptr -> tx_thread_id =    TX_THREAD_ID;
        thread_ptr -> tx_thread_name =  name;

        if (_tx_thread_created_ptr == TX_NULL)
        {
            thread_ptr -> tx_thread_created_next =  thread_ptr;
            _tx_thread_created_ptr =                thread_ptr;
        }
        else
        {
            tail_ptr =  _tx_thread_created_ptr;
            while (tail_ptr -> tx_thread_created_next != _tx_thread_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_thread_created_next;
            }
            tail_ptr -> tx_thread_created_next =    thread_ptr;
            thread_ptr -> tx_thread_created_next =  _tx_thread_created_ptr;
        }
        _tx_thread_created_count++;
        break;
    }

    case TXM_TIMER_OBJECT:
    {
        TX_TIMER    *timer_ptr =  (TX_TIMER *) object_ptr;
        TX_TIMER    *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_TIMER));
        timer_ptr -> tx_timer_id =    TX_TIMER_ID;
        timer_ptr -> tx_timer_name =  name;

        if (_tx_timer_created_ptr == TX_NULL)
        {
            timer_ptr -> tx_timer_created_next =  timer_ptr;
            _tx_timer_created_ptr =               timer_ptr;
        }
        else
        {
            tail_ptr =  _tx_timer_created_ptr;
            while (tail_ptr -> tx_timer_created_next != _tx_timer_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_timer_created_next;
            }
            tail_ptr -> tx_timer_created_next =   timer_ptr;
            timer_ptr -> tx_timer_created_next =  _tx_timer_created_ptr;
        }
        _tx_timer_created_count++;
        break;
    }

    case TXM_QUEUE_OBJECT:
    {
        TX_QUEUE    *queue_ptr =  (TX_QUEUE *) object_ptr;
        TX_QUEUE    *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_QUEUE));
        queue_ptr -> tx_queue_id =    TX_QUEUE_ID;
        queue_ptr -> tx_queue_name =  name;

        if (_tx_queue_created_ptr == TX_NULL)
        {
            queue_ptr -> tx_queue_created_next =  queue_ptr;
            _tx_queue_created_ptr =               queue_ptr;
        }
        else
        {
            tail_ptr =  _tx_queue_created_ptr;
            while (tail_ptr -> tx_queue_created_next != _tx_queue_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_queue_created_next;
            }
            tail_ptr -> tx_queue_created_next =   queue_ptr;
            queue_ptr -> tx_queue_created_next =  _tx_queue_created_ptr;
        }
        _tx_queue_created_count++;
        break;
    }

    case TXM_EVENT_FLAGS_OBJECT:
    {
        TX_EVENT_FLAGS_GROUP    *event_flags_ptr =  (TX_EVENT_FLAGS_GROUP *) object_ptr;
        TX_EVENT_FLAGS_GROUP    *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_EVENT_FLAGS_GROUP));
        event_flags_ptr -> tx_event_flags_group_id =    TX_EVENT_FLAGS_ID;
        event_flags_ptr -> tx_event_flags_group_name =  name;

        if (_tx_event_flags_created_ptr == TX_NULL)
        {
            event_flags_ptr -> tx_event_flags_group_created_next =  event_flags_ptr;
            _tx_event_flags_created_ptr =                           event_flags_ptr;
        }
        else
        {
            tail_ptr =  _tx_event_flags_created_ptr;
            while (tail_ptr -> tx_event_flags_group_created_next != _tx_event_flags_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_event_flags_group_created_next;
            }
            tail_ptr -> tx_event_flags_group_created_next =         event_flags_ptr;
            event_flags_ptr -> tx_event_flags_group_created_next =  _tx_event_flags_created_ptr;
        }
        _tx_event_flags_created_count++;
        break;
    }

    case TXM_SEMAPHORE_OBJECT:
    {
        TX_SEMAPHORE    *semaphore_ptr =  (TX_SEMAPHORE *) object_ptr;
        TX_SEMAPHORE    *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_SEMAPHORE));
        semaphore_ptr -> tx_semaphore_id =    TX_SEMAPHORE_ID;
        semaphore_ptr -> tx_semaphore_name =  name;

        if (_tx_semaphore_created_ptr == TX_NULL)
        {
            semaphore_ptr -> tx_semaphore_created_next =  semaphore_ptr;
            _tx_semaphore_created_ptr =                   semaphore_ptr;
        }
        else
        {
            tail_ptr =  _tx_semaphore_created_ptr;
            while (tail_ptr -> tx_semaphore_created_next != _tx_semaphore_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_semaphore_created_next;
            }
            tail_ptr -> tx_semaphore_created_next =       semaphore_ptr;
            semaphore_ptr -> tx_semaphore_created_next =  _tx_semaphore_created_ptr;
        }
        _tx_semaphore_created_count++;
        break;
    }

    case TXM_MUTEX_OBJECT:
    {
        TX_MUTEX    *mutex_ptr =  (TX_MUTEX *) object_ptr;
        TX_MUTEX    *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_MUTEX));
        mutex_ptr -> tx_mutex_id =    TX_MUTEX_ID;
        mutex_ptr -> tx_mutex_name =  name;

        if (_tx_mutex_created_ptr == TX_NULL)
        {
            mutex_ptr -> tx_mutex_created_next =  mutex_ptr;
            _tx_mutex_created_ptr =               mutex_ptr;
        }
        else
        {
            tail_ptr =  _tx_mutex_created_ptr;
            while (tail_ptr -> tx_mutex_created_next != _tx_mutex_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_mutex_created_next;
            }
            tail_ptr -> tx_mutex_created_next =   mutex_ptr;
            mutex_ptr -> tx_mutex_created_next =  _tx_mutex_created_ptr;
        }
        _tx_mutex_created_count++;
        break;
    }

    case TXM_BLOCK_POOL_OBJECT:
    {
        TX_BLOCK_POOL   *block_pool_ptr =  (TX_BLOCK_POOL *) object_ptr;
        TX_BLOCK_POOL   *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_BLOCK_POOL));
        block_pool_ptr -> tx_block_pool_id =    TX_BLOCK_POOL_ID;
        block_pool_ptr -> tx_block_pool_name =  name;

        if (_tx_block_pool_created_ptr == TX_NULL)
        {
            block_pool_ptr -> tx_block_pool_created_next =  block_pool_ptr;
            _tx_block_pool_created_ptr =                    block_pool_ptr;
        }
        else
        {
            tail_ptr =  _tx_block_pool_created_ptr;
            while (tail_ptr -> tx_block_pool_created_next != _tx_block_pool_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_block_pool_created_next;
            }
            tail_ptr -> tx_block_pool_created_next =        block_pool_ptr;
            block_pool_ptr -> tx_block_pool_created_next =  _tx_block_pool_created_ptr;
        }
        _tx_block_pool_created_count++;
        break;
    }

    case TXM_BYTE_POOL_OBJECT:
    default:
    {
        TX_BYTE_POOL    *byte_pool_ptr =  (TX_BYTE_POOL *) object_ptr;
        TX_BYTE_POOL    *tail_ptr;

        memset(object_ptr, 0, sizeof(TX_BYTE_POOL));
        byte_pool_ptr -> tx_byte_pool_id =    TX_BYTE_POOL_ID;
        byte_pool_ptr -> tx_byte_pool_name =  name;

        if (_tx_byte_pool_created_ptr == TX_NULL)
        {
            byte_pool_ptr -> tx_byte_pool_created_next =  byte_pool_ptr;
            _tx_byte_pool_created_ptr =                   byte_pool_ptr;
        }
        else
        {
            tail_ptr =  _tx_byte_pool_created_ptr;
            while (tail_ptr -> tx_byte_pool_created_next != _tx_byte_pool_created_ptr)
            {
                tail_ptr =  tail_ptr -> tx_byte_pool_created_next;
            }
            tail_ptr -> tx_byte_pool_created_next =       byte_pool_ptr;
            byte_pool_ptr -> tx_byte_pool_created_next =  _tx_byte_pool_created_ptr;
        }
        _tx_byte_pool_created_count++;
        break;
    }
    }
}


/* Empty every created list.  */
static VOID  test_lists_clear(VOID)
{
    _tx_block_pool_created_ptr =    TX_NULL;
    _tx_block_pool_created_count =  ((ULONG) 0);
    _tx_byte_pool_created_ptr =     TX_NULL;
    _tx_byte_pool_created_count =   ((ULONG) 0);
    _tx_event_flags_created_ptr =   TX_NULL;
    _tx_event_flags_created_count = ((ULONG) 0);
    _tx_mutex_created_ptr =         TX_NULL;
    _tx_mutex_created_count =       ((ULONG) 0);
    _tx_queue_created_ptr =         TX_NULL;
    _tx_queue_created_count =       ((ULONG) 0);
    _tx_semaphore_created_ptr =     TX_NULL;
    _tx_semaphore_created_count =   ((ULONG) 0);
    _tx_thread_created_ptr =        TX_NULL;
    _tx_thread_created_count =      ((ULONG) 0);
    _tx_timer_created_ptr =         TX_NULL;
    _tx_timer_created_count =       ((ULONG) 0);
}


/* Fill the arena, then restore the module's outgoing parameters, which live in it.  */
static VOID  test_arena_fill(VOID)
{
    memset((VOID *) &test_arena, (int) TEST_FILLER, sizeof(test_arena));

    TEST_EXTRA_PARAMETERS[0] =  ((ALIGN_TYPE) 0);
    TEST_EXTRA_PARAMETERS[1] =  (ALIGN_TYPE) TEST_RESULT_SLOT;
    *TEST_RESULT_SLOT =         TX_NULL;
}


/* What one request came back with.  */

#define TEST_RESULT_POISON  ((VOID *) (ALIGN_TYPE) 0xD0D0D0D0)

static ALIGN_TYPE   test_return_value;
static VOID         *test_result;
static ULONG        test_reached_lookup;


/* Make one request of the extended dispatcher, the way a module makes it: the declared
   length and the result slot go in the extra parameter array, which is in the module's
   own data.  */
static VOID  test_lookup_extended_result_slot(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type,
                                              UCHAR *name, UINT name_length, ALIGN_TYPE *extra_parameters,
                                              VOID **result_slot)
{
    *TEST_RESULT_SLOT =  TEST_RESULT_POISON;
    test_lookup_exits =  ((ULONG) 0);

    /* The lookup asks the running thread which module is calling before it will search
       the two pool types, so the request has to come from one.  */
    test_current_thread.tx_thread_module_instance_ptr =  (VOID *) module_instance;
    _tx_thread_current_ptr =                             &test_current_thread;

    extra_parameters[0] =  (ALIGN_TYPE) name_length;
    extra_parameters[1] =  (ALIGN_TYPE) result_slot;

    test_return_value =   _txm_module_manager_txm_module_object_pointer_get_extended_dispatch(module_instance, object_type,
                                                                                              (ALIGN_TYPE) name, extra_parameters);
    test_result =         *TEST_RESULT_SLOT;
    test_reached_lookup = test_lookup_exits;
}


/* Make one request without writing to the extra parameter array first, for the cases
   whose whole point is an array the module could not have written to either.  */
static VOID  test_lookup_extended_raw(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type,
                                      UCHAR *name, ALIGN_TYPE *extra_parameters)
{
    *TEST_RESULT_SLOT =  TEST_RESULT_POISON;
    test_lookup_exits =  ((ULONG) 0);

    test_current_thread.tx_thread_module_instance_ptr =  (VOID *) module_instance;
    _tx_thread_current_ptr =                             &test_current_thread;

    test_return_value =   _txm_module_manager_txm_module_object_pointer_get_extended_dispatch(module_instance, object_type,
                                                                                              (ALIGN_TYPE) name, extra_parameters);
    test_result =         *TEST_RESULT_SLOT;
    test_reached_lookup = test_lookup_exits;
}


/* The same request with the result slot the module normally uses.  */
static VOID  test_lookup_extended(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type,
                                  UCHAR *name, UINT name_length, ALIGN_TYPE *extra_parameters)
{
    test_lookup_extended_result_slot(module_instance, object_type, name, name_length, extra_parameters, TEST_RESULT_SLOT);
}


/* Make one request of the deprecated dispatcher, which carries no length.  */
static VOID  test_lookup_deprecated(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_type, UCHAR *name)
{
    *TEST_RESULT_SLOT =  TEST_RESULT_POISON;
    test_lookup_exits =  ((ULONG) 0);

    test_current_thread.tx_thread_module_instance_ptr =  (VOID *) module_instance;
    _tx_thread_current_ptr =                             &test_current_thread;

    test_return_value =   _txm_module_manager_txm_module_object_pointer_get_dispatch(module_instance, object_type,
                                                                                     (ALIGN_TYPE) name, (ALIGN_TYPE) TEST_RESULT_SLOT);
    test_result =         *TEST_RESULT_SLOT;
    test_reached_lookup = test_lookup_exits;
}


/* Report whether the dispatch layer let the last request through to the lookup, checking
   as it goes that a refusal is a whole refusal: TXM_MODULE_INVALID_MEMORY, the lookup not
   entered, and the module's result slot not written.  */
static UINT  test_accepted(VOID)
{

UINT    accepted;


    if (test_reached_lookup == ((ULONG) 0))
    {
        accepted =  TX_FALSE;

        test_expect("a refused request returns TXM_MODULE_INVALID_MEMORY",
                    (ULONG) test_return_value, (ULONG) TXM_MODULE_INVALID_MEMORY);
        test_expect("...and writes nothing to the module's result slot",
                    (ULONG) (test_result == TEST_RESULT_POISON), (ULONG) TX_TRUE);
    }
    else
    {
        accepted =  TX_TRUE;

        test_expect("an accepted request enters the lookup once", test_reached_lookup, ((ULONG) 1));
        test_expect("...and the lookup gives back the preemption it took",
                    test_preempt_disable_on_exit, ((ULONG) 0));
    }

    return(accepted);
}


/* Measure the extent the dispatcher validates.

   The name buffer is anchored to the end of one of the module's regions and walked
   backwards into it a byte at a time, so that the room left between the buffer and the
   end of the region grows by one on each step. The smallest amount of room the
   dispatcher accepts is the extent it validated. TEST_NOT_ACCEPTED means it accepted
   nothing within the region.  */

#define TEST_NOT_ACCEPTED   ((ULONG) 0xFFFFFFFF)

static ULONG  test_measure_validated_extent(TXM_MODULE_INSTANCE *module_instance, UCHAR *region_start, UINT name_length)
{

ULONG   room;
ULONG   walk_limit;
ULONG   extent;
UCHAR   *region_end_plus_one;


    region_end_plus_one =  region_start + TEST_REGION_BYTES;

    /* Walking past the length plus its terminator is enough to establish that nothing
       validates more than that.  */
    walk_limit =  ((ULONG) name_length) + ((ULONG) 4);

    extent =  TEST_NOT_ACCEPTED;

    for (room = ((ULONG) 0); room <= walk_limit; room++)
    {
        test_arena_fill();
        test_lists_clear();

        test_lookup_extended(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT,
                             region_end_plus_one - room, name_length, TEST_EXTRA_PARAMETERS);

        if (test_accepted() == TX_TRUE)
        {
            extent =  room;
            break;
        }
    }

    return(extent);
}


/* Measure the extent the comparison reads.

   Memory is filled with a filler character and the only terminator in it is planted at a
   chosen depth from the name buffer. The object the module asks for is named with
   exactly the bytes up to that terminator, so the lookup returns it if and only if the
   comparison read that deep. Scanning the depth and keeping the deepest one that comes
   back is therefore the read extent, measured rather than inferred.

   TEST_NOT_READ means no depth came back, which is what a refused request looks like.  */

#define TEST_NOT_READ   ((ULONG) 0xFFFFFFFF)

static ULONG  test_measure_read_depth(TXM_MODULE_INSTANCE *module_instance, UCHAR *region_start,
                                      ULONG room, UINT name_length, ULONG max_depth)
{

ULONG   depth;
ULONG   deepest;
UCHAR   *name;


    deepest =  TEST_NOT_READ;

    for (depth = ((ULONG) 0); depth <= max_depth; depth++)
    {
        test_arena_fill();

        name =  (region_start + TEST_REGION_BYTES) - room;
        name[depth] =  ((UCHAR) 0);

        memcpy((VOID *) test_probe_name, (VOID *) name, (size_t) depth);
        test_probe_name[depth] =  ((CHAR) 0);

        test_lists_clear();
        test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore, test_probe_name);

        test_lookup_extended(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, name_length, TEST_EXTRA_PARAMETERS);

        if ((test_reached_lookup != ((ULONG) 0)) &&
            (test_return_value == ((ALIGN_TYPE) TX_SUCCESS)) &&
            (test_result == (VOID *) &test_semaphore))
        {
            deepest =  depth;
        }
    }

    return(deepest);
}


/* Measure how far the comparison walks when the object's name is the module's own search
   buffer.

   A create service stores the name pointer a module hands it rather than copying the
   string, so a module can register an object whose name is the buffer it then searches
   for. Both sides of the comparison are then the same address and every character
   matches whatever it is, so the walk is bounded by nothing the module knows or supplied:
   it runs to the first terminator in memory. This returns the deepest depth at which a
   terminator is still reached, which is that walk length.  */
static ULONG  test_measure_alias_walk(TXM_MODULE_INSTANCE *module_instance, UCHAR *region_start,
                                      ULONG room, UINT name_length, ULONG max_depth, UINT use_deprecated)
{

ULONG   depth;
ULONG   deepest;
UCHAR   *name;


    deepest =  TEST_NOT_READ;

    for (depth = ((ULONG) 0); depth <= max_depth; depth++)
    {
        test_arena_fill();

        name =  (region_start + TEST_REGION_BYTES) - room;
        name[depth] =  ((UCHAR) 0);

        test_lists_clear();
        test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore, (CHAR *) name);

        if (use_deprecated == TX_TRUE)
        {
            test_lookup_deprecated(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name);
        }
        else
        {
            test_lookup_extended(module_instance, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, name_length, TEST_EXTRA_PARAMETERS);
        }

        if ((test_reached_lookup != ((ULONG) 0)) &&
            (test_return_value == ((ALIGN_TYPE) TX_SUCCESS)) &&
            (test_result == (VOID *) &test_semaphore))
        {
            deepest =  depth;
        }
    }

    return(deepest);
}


/* The declared lengths the cases run through: none, one character, and lengths either
   side of the room a case can leave in a region.  */

static const UINT   test_name_lengths[] =
{
    ((UINT) 0),
    ((UINT) 1),
    ((UINT) 2),
    ((UINT) 3),
    ((UINT) 7),
    ((UINT) 16),
    ((UINT) 31)
};

#define TEST_NAME_LENGTH_COUNT  (sizeof(test_name_lengths) / sizeof(test_name_lengths[0]))


/* The three regions a name buffer may legitimately be in. A search name is read, never
   written, so all three are permitted -- a module searching for a literal searches for
   one out of its own read-only image.  */

#define TEST_REGION_COUNT   ((UINT) 3)

static const char *test_region_names[TEST_REGION_COUNT] =
{
    "the module's data",
    "a shared region",
    "the module's read-only image"
};

static UCHAR  *test_region_start(UINT region)
{

UCHAR   *start;


    if (region == ((UINT) 0))
    {
        start =  TEST_DATA_START;
    }
    else if (region == ((UINT) 1))
    {
        start =  TEST_SHARED_START;
    }
    else
    {
        start =  TEST_CODE_START;
    }

    return(start);
}


/* The eight object types the lookup knows, and one it does not.  */

#define TEST_OBJECT_TYPE_COUNT  ((UINT) 8)

static const ALIGN_TYPE test_object_types[TEST_OBJECT_TYPE_COUNT] =
{
    (ALIGN_TYPE) TXM_THREAD_OBJECT,
    (ALIGN_TYPE) TXM_TIMER_OBJECT,
    (ALIGN_TYPE) TXM_QUEUE_OBJECT,
    (ALIGN_TYPE) TXM_EVENT_FLAGS_OBJECT,
    (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT,
    (ALIGN_TYPE) TXM_MUTEX_OBJECT,
    (ALIGN_TYPE) TXM_BLOCK_POOL_OBJECT,
    (ALIGN_TYPE) TXM_BYTE_POOL_OBJECT
};

static const char *test_object_type_names[TEST_OBJECT_TYPE_COUNT] =
{
    "thread",
    "timer",
    "queue",
    "event flags group",
    "semaphore",
    "mutex",
    "block pool",
    "byte pool"
};

#define TEST_INVALID_OBJECT_TYPE    ((ALIGN_TYPE) 0x7EU)


/* One of the two control blocks held for each type, in the order the type table lists
   them.  */
static VOID  *test_object_block(UINT type_index, UINT which)
{

VOID    *object_ptr;


    switch (type_index)
    {
    case ((UINT) 0):    object_ptr =  (VOID *) &test_thread_objects[which];       break;
    case ((UINT) 1):    object_ptr =  (VOID *) &test_timer_objects[which];        break;
    case ((UINT) 2):    object_ptr =  (VOID *) &test_queue_objects[which];        break;
    case ((UINT) 3):    object_ptr =  (VOID *) &test_event_flags_objects[which];  break;
    case ((UINT) 4):    object_ptr =  (VOID *) &test_semaphore_objects[which];    break;
    case ((UINT) 5):    object_ptr =  (VOID *) &test_mutex_objects[which];        break;
    case ((UINT) 6):    object_ptr =  (VOID *) &test_block_pool_objects[which];   break;
    default:            object_ptr =  (VOID *) &test_byte_pool_objects[which];    break;
    }

    return(object_ptr);
}


int main(void)
{

UINT        length_index;
UINT        region;
UINT        type_index;
UINT        name_length;
UCHAR       *region_start;
UCHAR       *name;
ULONG       room;
ULONG       depth;
ULONG       expected_extent;
char        description[192];


    memset((VOID *) &test_current_thread, 0, sizeof(TX_THREAD));
    test_current_thread.tx_thread_id =  TX_THREAD_ID;

    test_module_setup(&test_protected_module, (ULONG) TXM_MODULE_MEMORY_PROTECTION);
    test_module_setup(&test_unprotected_module, ((ULONG) 0));

    printf("Module Manager object name range validation test\n");

    /**********************************************************************/
    /* The extent the extended dispatcher validates.                      */
    /**********************************************************************/

    /* A declared length excludes the terminator and the comparison reads it, so the range
       the module has to have made readable is the length plus one byte, in whichever of
       the three regions a search name may live in.  */

    for (length_index = ((UINT) 0); length_index < ((UINT) TEST_NAME_LENGTH_COUNT); length_index++)
    {
        name_length =      test_name_lengths[length_index];
        expected_extent =  ((ULONG) name_length) + ((ULONG) 1);

        for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++)
        {
            snprintf(description, sizeof(description),
                     "a declared length of %u is checked over %lu bytes of a name in %s",
                     name_length, (unsigned long) expected_extent, test_region_names[region]);
            test_expect(description,
                        test_measure_validated_extent(&test_protected_module, test_region_start(region), name_length),
                        expected_extent);
        }
    }

    /**********************************************************************/
    /* The extent the comparison reads, against the extent validated.     */
    /**********************************************************************/

    /* The two dials are the declared length and the object's name, and the finding is the
       relationship between them: the comparison reads through the declared length, so a
       request must not be accepted unless that whole range is inside the module. For every
       length and every amount of room, the request is accepted exactly when the room
       covers the length and its terminator, and when it is accepted the deepest byte the
       comparison can be made to read is the last byte of the declared name -- which the
       room then guarantees is inside the module.  */

    for (length_index = ((UINT) 0); length_index < ((UINT) TEST_NAME_LENGTH_COUNT); length_index++)
    {
        name_length =  test_name_lengths[length_index];

        for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++)
        {
            region_start =  test_region_start(region);

            for (room = ((ULONG) 1); room <= (((ULONG) name_length) + ((ULONG) 2)); room++)
            {
                depth =  test_measure_read_depth(&test_protected_module, region_start, room,
                                                 name_length, ((ULONG) name_length) + ((ULONG) 2));

                if (room >= (((ULONG) name_length) + ((ULONG) 1)))
                {
                    snprintf(description, sizeof(description),
                             "a %u-character name with %lu bytes of room in %s is read to its declared end",
                             name_length, (unsigned long) room, test_region_names[region]);
                    test_expect(description, depth, (ULONG) name_length);

                    snprintf(description, sizeof(description),
                             "...and every byte it read was inside the module (%u characters, %lu bytes of room)",
                             name_length, (unsigned long) room);
                    test_expect(description, (ULONG) (depth < room), (ULONG) TX_TRUE);
                }
                else
                {
                    snprintf(description, sizeof(description),
                             "a %u-character name with only %lu bytes of room in %s is not read at all",
                             name_length, (unsigned long) room, test_region_names[region]);
                    test_expect(description, depth, TEST_NOT_READ);
                }
            }
        }
    }

    /**********************************************************************/
    /* The name a module registered for an object can be the name it       */
    /* searches for.                                                       */
    /**********************************************************************/

    /* A create service stores the name pointer rather than the string, so a module can
       give an object the address of the buffer it then searches for. Both sides of the
       comparison are the same address, so it matches whatever is there and the walk is
       bounded by nothing the module supplied. That is the case the one-byte check could
       not survive, and it is why the length has to bound the buffer rather than the
       comparison having to discover the end of it.

       With the range checked, the walk cannot start: the only placements accepted are the
       ones whose whole declared range is inside the module, and there the walk ends inside
       it too.  */

    for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++)
    {
        region_start =  test_region_start(region);

        snprintf(description, sizeof(description),
                 "an aliased name with one byte of room in %s reaches no depth at all", test_region_names[region]);
        test_expect(description,
                    test_measure_alias_walk(&test_protected_module, region_start, ((ULONG) 1),
                                            ((UINT) 31), ((ULONG) 31), TX_FALSE),
                    TEST_NOT_READ);

        snprintf(description, sizeof(description),
                 "an aliased name with room for its whole declared range in %s walks no further than that range",
                 test_region_names[region]);
        test_expect(description,
                    test_measure_alias_walk(&test_protected_module, region_start, ((ULONG) 32),
                                            ((UINT) 31), ((ULONG) 63), TX_FALSE),
                    ((ULONG) 31));
    }

    /**********************************************************************/
    /* The deprecated service, which carries no length.                    */
    /**********************************************************************/

    /* Its manager wrapper searches with the largest length a UINT can hold, so there is no
       range to check and none can be derived from a pointer. A memory-protected module is
       refused it; the extended service is what such a module uses.  */

    for (region = ((UINT) 0); region < TEST_REGION_COUNT; region++)
    {
        region_start =  test_region_start(region);

        for (room = ((ULONG) 1); room <= ((ULONG) 64); room++)
        {
            test_arena_fill();
            test_lists_clear();

            test_lookup_deprecated(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT,
                                   (region_start + TEST_REGION_BYTES) - room);

            if (room == ((ULONG) 1))
            {
                snprintf(description, sizeof(description),
                         "a memory-protected module's lookup without a length in %s is refused",
                         test_region_names[region]);
                test_expect(description, (ULONG) test_accepted(), (ULONG) TX_FALSE);
            }
            else
            {
                test_expect("a memory-protected module's lookup without a length is refused at every placement",
                            (ULONG) test_accepted(), (ULONG) TX_FALSE);
            }
        }

        snprintf(description, sizeof(description),
                 "...including an aliased name in %s, which is the walk it cannot bound", test_region_names[region]);
        test_expect(description,
                    test_measure_alias_walk(&test_protected_module, region_start, ((ULONG) 1),
                                            ((UINT) 0), ((ULONG) 63), TX_TRUE),
                    TEST_NOT_READ);
    }

    /* A module without memory protection still has it, and there it shows what the absent
       length means: with the object's name aliased to the search buffer the walk runs to
       whatever terminator memory happens to hold, however far away, because no length
       stops it. The extended service on the same module stops exactly where the module
       said to.  */

    test_expect("an unprotected module's lookup without a length walks to any depth memory puts a terminator at",
                test_measure_alias_walk(&test_unprotected_module, TEST_DATA_START, ((ULONG) 1),
                                        ((UINT) 0), ((ULONG) 100), TX_TRUE),
                ((ULONG) 100));

    test_expect("an unprotected module's extended lookup walks exactly as far as the length it declared",
                test_measure_alias_walk(&test_unprotected_module, TEST_DATA_START, ((ULONG) 1),
                                        ((UINT) 40), ((ULONG) 100), TX_FALSE),
                ((ULONG) 40));

    /**********************************************************************/
    /* A null name, and a null result slot.                                */
    /**********************************************************************/

    /* The range check accepts a null pointer outright, as the one-byte check it replaces
       did and as the buffer checks do, because passing null is how a module declines an
       optional argument. Neither of these is optional, and it is the lookup that refuses
       them, before it dereferences either. These cases assert that sequence rather than
       the macro, so that neither side can change quietly.  */

    test_arena_fill();
    test_lists_clear();

    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TX_NULL, ((UINT) 31), TEST_EXTRA_PARAMETERS);
    test_expect("the extended dispatcher passes a null name through", (ULONG) test_return_value, (ULONG) TX_PTR_ERROR);
    test_expect("...and the lookup refuses it before dereferencing it", test_reached_lookup, ((ULONG) 0));

    test_arena_fill();
    test_lists_clear();
    TEST_EXTRA_PARAMETERS[0] =  ((ALIGN_TYPE) 31);
    TEST_EXTRA_PARAMETERS[1] =  ((ALIGN_TYPE) 0);
    test_return_value =  _txm_module_manager_txm_module_object_pointer_get_extended_dispatch(&test_protected_module,
                                                                                             (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT,
                                                                                             (ALIGN_TYPE) TEST_DATA_START,
                                                                                             TEST_EXTRA_PARAMETERS);
    test_expect("the extended dispatcher passes a null result slot through", (ULONG) test_return_value, (ULONG) TX_PTR_ERROR);

    /* The deprecated service refuses a memory-protected module before it gets that far,
       which is a change from the one-byte check and is recorded here as one.  */

    test_arena_fill();
    test_lists_clear();
    test_lookup_deprecated(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TX_NULL);
    test_expect("a memory-protected module's null name is refused by the deprecated dispatcher",
                (ULONG) test_return_value, (ULONG) TXM_MODULE_INVALID_MEMORY);

    test_arena_fill();
    test_lists_clear();
    test_lookup_deprecated(&test_unprotected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TX_NULL);
    test_expect("an unprotected module's null name still reaches the lookup", (ULONG) test_return_value, (ULONG) TX_PTR_ERROR);

    /**********************************************************************/
    /* The extra parameter array itself.                                   */
    /**********************************************************************/

    /* The length is read out of that array, so checking the name's range depends on the
       array having been checked first. Reordering the two checks is what the fix required,
       and a request that fails both still fails the same way.  */

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         ((UINT) 1), (ALIGN_TYPE *) TEST_CODE_START);
    test_expect("an extra parameter array in the module's read-only image is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT,
                         (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 1),
                         ((UINT) 31), (ALIGN_TYPE *) TEST_CODE_START);
    test_expect("an unreadable name and an unreadable array are refused the same way",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         ((UINT) 1), (ALIGN_TYPE *) ((TEST_DATA_START + TEST_REGION_BYTES) - sizeof(ALIGN_TYPE)));
    test_expect("an extra parameter array with room for one word of two is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    /* A shared region registered to the module is writable by it, so an array there is
       as good as one in its own data, and so is a result slot.  */

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                                     ((UINT) 1), (ALIGN_TYPE *) TEST_SHARED_START, (VOID **) TEST_SHARED_START);
    test_expect("an extra parameter array and result slot in a shared region are accepted",
                (ULONG) test_accepted(), (ULONG) TX_TRUE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         ((UINT) 1), (ALIGN_TYPE *) ((TEST_SHARED_START + TEST_REGION_BYTES) - sizeof(ALIGN_TYPE)));
    test_expect("an extra parameter array running off the end of a shared region is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         ((UINT) 1), (ALIGN_TYPE *) &test_arena.test_arena_bytes[0]);
    test_expect("an extra parameter array below the module's data is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended_raw(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                             (ALIGN_TYPE *) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 3)));
    test_expect("an extra parameter array whose two words wrap the address space is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    /* The module's read-only image is not, and a result slot the module cannot write is
       refused whatever else is right about the request.  */

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                                     ((UINT) 1), TEST_EXTRA_PARAMETERS, (VOID **) TEST_CODE_START);
    test_expect("a result slot in the module's read-only image is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                                     ((UINT) 1), TEST_EXTRA_PARAMETERS, (VOID **) test_probe_name);
    test_expect("a result slot outside every region the module owns is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended_result_slot(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                                     ((UINT) 1), TEST_EXTRA_PARAMETERS, (VOID **) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 1)));
    test_expect("a result slot whose word wraps the address space is refused",
                (ULONG) test_accepted(), (ULONG) TX_FALSE);

    /**********************************************************************/
    /* Lengths that cannot describe a range.                               */
    /**********************************************************************/

    /* The length plus its terminator has to be expressible, and the range has to fit
       under the top of memory. Neither is a range a module can be given.  */

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         (UINT) TXM_MODULE_MANAGER_UTIL_MAX_VALUE_OF_TYPE_UNSIGNED(UINT), TEST_EXTRA_PARAMETERS);
    test_expect("the largest length a UINT can hold is refused", (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         ((UINT) TXM_MODULE_MANAGER_UTIL_MAX_VALUE_OF_TYPE_UNSIGNED(UINT)) - ((UINT) 1), TEST_EXTRA_PARAMETERS);
    test_expect("a length one below it is refused too", (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT,
                         (UCHAR *) (~((ALIGN_TYPE) 0) - ((ALIGN_TYPE) 3)), ((UINT) 31), TEST_EXTRA_PARAMETERS);
    test_expect("a name whose range wraps the address space is refused", (ULONG) test_accepted(), (ULONG) TX_FALSE);

    /**********************************************************************/
    /* The range check is the dispatcher's, not the object type's.         */
    /**********************************************************************/

    /* It runs before the lookup and does not depend on what is being looked up, so the
       boundary is in the same place for all eight types the lookup knows and for one it
       does not. What the lookup then does with the type is its own business, and the two
       pool types it refuses a memory-protected module outright.  */

    for (type_index = ((UINT) 0); type_index < TEST_OBJECT_TYPE_COUNT; type_index++)
    {
        test_arena_fill();
        test_lists_clear();
        test_lookup_extended(&test_protected_module, test_object_types[type_index],
                             (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 4), ((UINT) 4), TEST_EXTRA_PARAMETERS);

        snprintf(description, sizeof(description), "a %s name one byte short of its range is refused",
                 test_object_type_names[type_index]);
        test_expect(description, (ULONG) test_accepted(), (ULONG) TX_FALSE);

        test_arena_fill();
        test_lists_clear();
        test_lookup_extended(&test_protected_module, test_object_types[type_index],
                             (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 5), ((UINT) 4), TEST_EXTRA_PARAMETERS);

        snprintf(description, sizeof(description), "a %s name with its whole range is accepted",
                 test_object_type_names[type_index]);
        test_expect(description, (ULONG) test_accepted(), (ULONG) TX_TRUE);

        if ((test_object_types[type_index] == ((ALIGN_TYPE) TXM_BLOCK_POOL_OBJECT)) ||
            (test_object_types[type_index] == ((ALIGN_TYPE) TXM_BYTE_POOL_OBJECT)))
        {
            snprintf(description, sizeof(description), "...and the lookup refuses a memory-protected module a %s",
                     test_object_type_names[type_index]);
            test_expect(description, (ULONG) test_return_value, (ULONG) TXM_MODULE_INVALID);
        }
        else
        {
            snprintf(description, sizeof(description), "...and the lookup searches for the %s and finds none",
                     test_object_type_names[type_index]);
            test_expect(description, (ULONG) test_return_value, (ULONG) TX_NO_INSTANCE);
        }
    }

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, TEST_INVALID_OBJECT_TYPE,
                         (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 4), ((UINT) 4), TEST_EXTRA_PARAMETERS);
    test_expect("an unknown object type is refused for its range first", (ULONG) test_accepted(), (ULONG) TX_FALSE);

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_protected_module, TEST_INVALID_OBJECT_TYPE,
                         (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 5), ((UINT) 4), TEST_EXTRA_PARAMETERS);
    test_expect("...and once its range is right, by the lookup for being unknown",
                (ULONG) test_return_value, (ULONG) TX_OPTION_ERROR);

    /**********************************************************************/
    /* Every created list the lookup walks.                                */
    /**********************************************************************/

    /* The comparison the range check protects runs once per object on the list for the
       requested type, and there are eight such lists. These cases put two objects on each
       of them and search for the second, so that every list is walked, every comparison
       is reached, and the name pointer the lookup dereferences out of each kind of control
       block is the one a create service stored there.

       The two pool types are refused to a memory-protected module by the lookup itself, so
       these run as the module without protection, which is the caller that can reach them.  */

    for (type_index = ((UINT) 0); type_index < TEST_OBJECT_TYPE_COUNT; type_index++)
    {
        test_arena_fill();
        test_lists_clear();

        name =  (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 8);
        memcpy((VOID *) name, "target", (size_t) 7);
        memcpy((VOID *) test_probe_name, "target", (size_t) 7);

        test_object_create((UINT) test_object_types[type_index], test_object_block(type_index, ((UINT) 0)), "other");
        test_object_create((UINT) test_object_types[type_index], test_object_block(type_index, ((UINT) 1)), "another");
        test_object_create((UINT) test_object_types[type_index], test_object_block(type_index, ((UINT) 2)), test_probe_name);

        test_lookup_extended(&test_unprotected_module, test_object_types[type_index], name, ((UINT) 6), TEST_EXTRA_PARAMETERS);

        snprintf(description, sizeof(description), "the %s list is walked past its earlier members to the match",
                 test_object_type_names[type_index]);
        test_expect(description, (ULONG) test_return_value, (ULONG) TX_SUCCESS);

        snprintf(description, sizeof(description), "...and the %s that comes back is the one whose name matched",
                 test_object_type_names[type_index]);
        test_expect(description,
                    (ULONG) (test_result == test_object_block(type_index, ((UINT) 2))), (ULONG) TX_TRUE);

        /* A name on no list walks the whole of it and comes back empty, which is the
           traversal running to the created count rather than to a match.  */

        memcpy((VOID *) name, "absent", (size_t) 7);

        test_lookup_extended(&test_unprotected_module, test_object_types[type_index], name, ((UINT) 6), TEST_EXTRA_PARAMETERS);

        snprintf(description, sizeof(description), "a name on no %s finds none of them", test_object_type_names[type_index]);
        test_expect(description, (ULONG) test_return_value, (ULONG) TX_NO_INSTANCE);
    }

    /* The two pool lists are also reachable from application code, which is not a module
       at all. That caller does not come through the dispatch layer, so these two cases
       call the lookup the dispatchers call.  */

    test_arena_fill();
    test_lists_clear();
    memcpy((VOID *) test_probe_name, "target", (size_t) 7);
    test_object_create((UINT) TXM_BLOCK_POOL_OBJECT, (VOID *) &test_block_pool_objects[0], test_probe_name);
    test_object_create((UINT) TXM_BYTE_POOL_OBJECT, (VOID *) &test_byte_pool_objects[0], test_probe_name);
    test_current_thread.tx_thread_module_instance_ptr =  TX_NULL;
    _tx_thread_current_ptr =                             &test_current_thread;

    test_result =  TEST_RESULT_POISON;
    test_expect("a caller that is not a module may look a block pool up by name",
                (ULONG) _txm_module_manager_object_pointer_get_extended((UINT) TXM_BLOCK_POOL_OBJECT, test_probe_name,
                                                                        ((UINT) 6), &test_result),
                (ULONG) TX_SUCCESS);
    test_expect("...and gets the block pool it named",
                (ULONG) (test_result == (VOID *) &test_block_pool_objects[0]), (ULONG) TX_TRUE);

    test_result =  TEST_RESULT_POISON;
    test_expect("a caller that is not a module may look a byte pool up by name",
                (ULONG) _txm_module_manager_object_pointer_get_extended((UINT) TXM_BYTE_POOL_OBJECT, test_probe_name,
                                                                        ((UINT) 6), &test_result),
                (ULONG) TX_SUCCESS);
    test_expect("...and gets the byte pool it named",
                (ULONG) (test_result == (VOID *) &test_byte_pool_objects[0]), (ULONG) TX_TRUE);

    /* An object with no name at all is on the list like any other, and the comparison has
       to refuse it without dereferencing the null.  */

    test_arena_fill();
    test_lists_clear();
    memcpy((VOID *) test_probe_name, "target", (size_t) 7);
    test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore_objects[0], TX_NULL);
    test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore_objects[1], test_probe_name);

    name =  (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 8);
    memcpy((VOID *) name, "target", (size_t) 7);

    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, ((UINT) 6), TEST_EXTRA_PARAMETERS);
    test_expect("an unnamed object on the list is passed over", (ULONG) test_return_value, (ULONG) TX_SUCCESS);
    test_expect("...and the named one behind it is found",
                (ULONG) (test_result == (VOID *) &test_semaphore_objects[1]), (ULONG) TX_TRUE);

    /**********************************************************************/
    /* The object the module was looking for is still found.               */
    /**********************************************************************/

    /* A name that fits, matching an object whose name is a string of that length, comes
       back -- and the first match on the list is the one that comes back, which is the
       property the probing cases above rely on.  */

    test_arena_fill();
    test_lists_clear();

    name =  (TEST_DATA_START + TEST_REGION_BYTES) - ((ULONG) 8);
    memcpy((VOID *) name, "object", (size_t) 7);
    memcpy((VOID *) test_probe_name, "object", (size_t) 7);

    test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_decoy_semaphore, "objec");
    test_object_create((UINT) TXM_SEMAPHORE_OBJECT, (VOID *) &test_semaphore, test_probe_name);

    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, ((UINT) 6), TEST_EXTRA_PARAMETERS);
    test_expect("a name that fits finds the object it names", (ULONG) test_return_value, (ULONG) TX_SUCCESS);
    test_expect("...and it is the matching object, not the one whose name is a prefix of it",
                (ULONG) (test_result == (VOID *) &test_semaphore), (ULONG) TX_TRUE);

    /* The same search declared one character short stops before the end of the name and
       finds nothing, which is the comparison obeying the length rather than the string.  */

    test_lookup_extended(&test_protected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, name, ((UINT) 5), TEST_EXTRA_PARAMETERS);
    test_expect("the same search one character short finds nothing", (ULONG) test_return_value, (ULONG) TX_NO_INSTANCE);
    test_expect("...and leaves the module's result slot null", (ULONG) (test_result == TX_NULL), (ULONG) TX_TRUE);

    /**********************************************************************/
    /* A module without memory protection is unaffected.                   */
    /**********************************************************************/

    /* Every check the fix touches is inside the dispatchers' memory protection block. A
       module loaded without protection reaches the lookup with no range check of any kind,
       before the fix and after it, and these cases are here so that a later change cannot
       quietly start refusing its requests.  */

    for (length_index = ((UINT) 0); length_index < ((UINT) TEST_NAME_LENGTH_COUNT); length_index++)
    {
        name_length =  test_name_lengths[length_index];

        snprintf(description, sizeof(description),
                 "an unprotected module's %u-character name is accepted with one byte of room", name_length);
        test_expect(description,
                    test_measure_validated_extent(&test_unprotected_module, TEST_DATA_START, name_length),
                    ((ULONG) 0));

        snprintf(description, sizeof(description),
                 "an unprotected module's %u-character name is read to its declared end wherever it sits", name_length);
        test_expect(description,
                    test_measure_read_depth(&test_unprotected_module, TEST_DATA_START, ((ULONG) 1),
                                            name_length, ((ULONG) name_length) + ((ULONG) 2)),
                    (ULONG) name_length);
    }

    test_arena_fill();
    test_lists_clear();
    test_lookup_extended(&test_unprotected_module, (ALIGN_TYPE) TXM_SEMAPHORE_OBJECT, TEST_DATA_START,
                         ((UINT) 31), (ALIGN_TYPE *) test_probe_name);
    test_expect("an unprotected module's extra parameter array is accepted outside every region it owns",
                (ULONG) test_accepted(), (ULONG) TX_TRUE);

    /**********************************************************************/
    /* What the whole run has to have held.                                */
    /**********************************************************************/

    /* The lookup raises the preemption hold and lowers it again around every search, and
       the port shim counts the interrupt lock it takes to do so. A search that returned
       without giving either back is a fault path this test cannot take, and one that never
       took them is a search that did not happen.  */

    test_expect("the interrupt lock is balanced", (ULONG) test_interrupt_disable_depth, ((ULONG) 0));
    test_expect("the interrupt lock never underflowed", (ULONG) test_interrupt_restore_underflows, ((ULONG) 0));
    test_expect("the interrupt lock was never nested", (ULONG) test_interrupt_disable_max_depth, ((ULONG) 1));
    test_expect("the preemption hold is balanced", (ULONG) _tx_thread_preempt_disable, ((ULONG) 0));

    test_expect("every case ran", (ULONG) (test_checks > ((ULONG) 1000)), (ULONG) TX_TRUE);

    if (test_failures == 0U)
    {
        printf("SUCCESS! %lu expectations\n", (unsigned long) test_checks);
        return(0);
    }

    printf("ERROR: %u expectation(s) failed of %lu\n", test_failures, (unsigned long) test_checks);
    return(1);
}