-
Notifications
You must be signed in to change notification settings - Fork 215
/
utstubs.c
1061 lines (934 loc) · 31.8 KB
/
utstubs.c
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
/*
* NASA Docket No. GSC-18,370-1, and identified as "Operating System Abstraction Layer"
*
* Copyright (c) 2019 United States Government as represented by
* the Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* \file utstubs.c
*
* Created on: Feb 25, 2015
* Author: [email protected]
*
* Types and definitions for the generic Unit Test Stub framework implemented
* as part of OSAL.
*
* Provides a means to implement stub functions WITHOUT needing any specific
* headers or knowledge about the real implementations of those functions.
*
* This is a major key difference between this implementation and the other stub
* implementations that have existed before this, as all other hook/stub implementations
* ultimately use the prototypes or are somehow tied to the functions they are stubbing out.
*
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "utstubs.h"
#include "utbsp.h"
#define UT_MAX_FUNC_STUBS 200
#define UT_APPNAME_MAX_LEN 80
#define UT_SUBSYS_MAX_LEN 5
#define UT_MODEFLAG_ALLOC_BUF 0x1U
typedef enum
{
UT_ENTRYTYPE_UNUSED, /**< Unused/available table entries */
UT_ENTRYTYPE_COUNTER, /**< Records a usage count plus most recent return code */
UT_ENTRYTYPE_FORCE_FAIL, /**< Always return a designated code from stub */
UT_ENTRYTYPE_DEFERRED_RC, /**< Return a designated code from stub after "N" calls */
UT_ENTRYTYPE_DATA_BUFFER, /**< Storage for data buffers to simulate read/write or queue ops */
UT_ENTRYTYPE_CALLBACK_HOOK, /**< A custom callback/hook function to be invoked prior to handler */
UT_ENTRYTYPE_CALLBACK_CONTEXT, /**< Context data for callback/hook function */
UT_ENTRYTYPE_CALL_ONCE, /**< Records a "call once" directive */
UT_ENTRYTYPE_FINAL_HANDLER, /**< The final handler for the stub */
UT_ENTRYTYPE_RETURN_BUFFER, /**< Storage for return value from stub */
} UT_EntryType_t;
typedef struct
{
uint32 Count;
int32 Value;
} UT_RetcodeEntry_t;
typedef struct
{
size_t Position;
size_t TotalSize;
uint8 *BasePtr;
} UT_BufferEntry_t;
typedef union
{
void * Addr;
UT_HookFunc_t SimpleHook;
UT_VaHookFunc_t VaHook;
UT_HandlerFunc_t SimpleHandler;
UT_VaHandlerFunc_t VaHandler;
} UT_HookFuncPtr_t;
typedef struct
{
UT_HookFuncPtr_t Ptr;
void * CallbackArg;
bool IsVarg;
} UT_CallbackEntry_t;
typedef union
{
UT_RetcodeEntry_t Rc;
UT_BufferEntry_t Buff;
UT_CallbackEntry_t Cb;
UT_StubContext_t Context;
} UT_EntryData_t;
/*
* Definition of internal stub table structure
*/
typedef struct
{
UT_EntryType_t EntryType;
UT_EntryKey_t FuncKey;
uint32 ModeFlags;
UT_EntryData_t Data;
} UT_StubTableEntry_t;
static UT_StubTableEntry_t UT_StubTable[UT_MAX_FUNC_STUBS] = {{0}};
static uint32 UT_MaxStubSearchLen = 0;
/**
* Helper function to clear an entry in the stub table.
* This will call "free()" on any dynamically allocated buffers within the entry,
* then zero out the entire block so it can be re-used.
*/
static void UT_ClearStubEntry(UT_StubTableEntry_t *StubPtr)
{
/* Be sure to call free() on any malloc'ed buffers before clearing */
if ((StubPtr->EntryType == UT_ENTRYTYPE_DATA_BUFFER || StubPtr->EntryType == UT_ENTRYTYPE_RETURN_BUFFER) &&
StubPtr->Data.Buff.BasePtr != NULL && (StubPtr->ModeFlags & UT_MODEFLAG_ALLOC_BUF) != 0)
{
free(StubPtr->Data.Buff.BasePtr);
}
memset(StubPtr, 0, sizeof(*StubPtr));
}
/*
** Functions
*/
static UT_StubTableEntry_t *UT_GetStubEntry(UT_EntryKey_t FuncKey, UT_EntryType_t TestMode)
{
UT_StubTableEntry_t *StubPtr = NULL;
uint32 Idx = FuncKey % (UT_MAX_FUNC_STUBS - 1); /* hash the key to determine the start point */
uint32 SearchLen = 0;
uint32 SearchLimit;
UT_EntryKey_t SearchKey;
/* If searching for an unused entry, look through the entire table.
* Otherwise bound the search */
if (TestMode == UT_ENTRYTYPE_UNUSED)
{
SearchLimit = UT_MAX_FUNC_STUBS;
SearchKey = 0;
}
else
{
SearchLimit = UT_MaxStubSearchLen;
SearchKey = FuncKey;
}
while (1)
{
if (SearchLen >= SearchLimit)
{
StubPtr = NULL;
break;
}
++SearchLen;
StubPtr = &UT_StubTable[Idx];
if (StubPtr->EntryType == TestMode && StubPtr->FuncKey == SearchKey)
{
break;
}
++Idx;
if (Idx >= UT_MAX_FUNC_STUBS)
{
Idx = 0;
}
}
/*
* Keep track of the longest search length since the last reset.
* This serves as the upper bound for future searches.
*/
if (SearchLen > UT_MaxStubSearchLen)
{
UT_MaxStubSearchLen = SearchLen;
}
return (StubPtr);
}
void UT_ResetState(UT_EntryKey_t FuncKey)
{
UT_StubTableEntry_t *StubPtr;
int32 i;
StubPtr = UT_StubTable;
for (i = 0; i < UT_MAX_FUNC_STUBS; ++i)
{
if (FuncKey == 0 || StubPtr->FuncKey == FuncKey)
{
UT_ClearStubEntry(StubPtr);
}
++StubPtr;
}
if (FuncKey == 0)
{
UT_MaxStubSearchLen = 0;
}
}
void UT_Stub_CallOnce(void (*Func)(void))
{
UT_StubTableEntry_t *StubPtr;
UT_StubTableEntry_t *OnceEnt;
UT_EntryKey_t FuncKey;
int32 i;
if (Func == NULL)
{
return;
}
FuncKey = (UT_EntryKey_t)Func;
OnceEnt = NULL;
StubPtr = UT_StubTable;
for (i = 0; i < UT_MAX_FUNC_STUBS; ++i)
{
if (StubPtr->EntryType == UT_ENTRYTYPE_UNUSED && OnceEnt == NULL)
{
OnceEnt = StubPtr;
}
else if (StubPtr->EntryType == UT_ENTRYTYPE_CALL_ONCE && StubPtr->FuncKey == FuncKey)
{
OnceEnt = StubPtr;
break;
}
++StubPtr;
}
if (OnceEnt == NULL)
{
/* should never happen -- UT_MAX_FUNC_STUBS needs increase if it does */
UtAssert_Abort("Cannot do CallOnce - UT_MAX_FUNC_STUBS too low?");
}
else if (OnceEnt->EntryType == UT_ENTRYTYPE_UNUSED)
{
OnceEnt->EntryType = UT_ENTRYTYPE_CALL_ONCE;
OnceEnt->FuncKey = FuncKey;
Func();
}
}
void UT_SetDeferredRetcode(UT_EntryKey_t FuncKey, int32 Count, int32 Retcode)
{
UT_StubTableEntry_t *StubPtr;
if (Count > 0)
{
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
if (StubPtr == NULL)
{
UtAssert_Abort("Cannot set retcode - UT_MAX_FUNC_STUBS too low?");
}
else
{
StubPtr->FuncKey = FuncKey;
StubPtr->EntryType = UT_ENTRYTYPE_DEFERRED_RC;
StubPtr->Data.Rc.Count = Count;
StubPtr->Data.Rc.Value = Retcode;
}
}
}
void UT_ClearDeferredRetcode(UT_EntryKey_t FuncKey)
{
UT_StubTableEntry_t *StubPtr;
while (true)
{
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_DEFERRED_RC);
if (StubPtr == NULL)
{
break;
}
UT_ClearStubEntry(StubPtr);
}
}
bool UT_Stub_CheckDeferredRetcode(UT_EntryKey_t FuncKey, int32 *Retcode)
{
bool Result = false;
UT_StubTableEntry_t *StubPtr;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_DEFERRED_RC);
if (StubPtr != NULL)
{
if (StubPtr->Data.Rc.Count > 0)
{
--StubPtr->Data.Rc.Count;
}
if (StubPtr->Data.Rc.Count == 0)
{
Result = true;
*Retcode = StubPtr->Data.Rc.Value;
/* Once the count has reached zero, void the entry */
UT_ClearStubEntry(StubPtr);
}
}
return (Result);
}
void UT_SetDefaultReturnValue(UT_EntryKey_t FuncKey, int32 Value)
{
UT_StubTableEntry_t *Rc;
/*
* First find an existing default return value entry for the function.
* In case one is already set we do not duplicate (unlike deferred codes)
*/
Rc = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_FORCE_FAIL);
if (Rc == NULL)
{
/* Creating default return value entry - repeat search and grab any unused slot */
Rc = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
}
if (Rc != NULL)
{
Rc->FuncKey = FuncKey;
Rc->EntryType = UT_ENTRYTYPE_FORCE_FAIL;
Rc->Data.Rc.Value = Value;
}
else
{
UtAssert_Abort("Cannot set retcode - UT_MAX_FUNC_STUBS too low?");
}
}
void UT_ClearDefaultReturnValue(UT_EntryKey_t FuncKey)
{
UT_StubTableEntry_t *StubPtr;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_FORCE_FAIL);
if (StubPtr != NULL)
{
UT_ClearStubEntry(StubPtr);
}
}
bool UT_GetStubRetcodeAndCount(UT_EntryKey_t FuncKey, int32 *Retcode, int32 *Count)
{
UT_StubTableEntry_t *StubPtr;
bool Result;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_COUNTER);
if (StubPtr != NULL)
{
*Count = StubPtr->Data.Rc.Count;
*Retcode = StubPtr->Data.Rc.Value;
Result = true;
}
else
{
Result = false;
}
return Result;
}
uint32 UT_GetStubCount(UT_EntryKey_t FuncKey)
{
UT_StubTableEntry_t *StubPtr;
uint32 Count;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_COUNTER);
if (StubPtr != NULL)
{
Count = StubPtr->Data.Rc.Count;
}
else
{
Count = 0;
}
return Count;
}
bool UT_Stub_CheckDefaultReturnValue(UT_EntryKey_t FuncKey, int32 *Value)
{
bool Result = false;
UT_StubTableEntry_t *StubPtr;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_FORCE_FAIL);
if (StubPtr != NULL)
{
/* For default return value entries, the count will reflect the number of times it was used */
++StubPtr->Data.Rc.Count;
if (Value != NULL)
{
*Value = StubPtr->Data.Rc.Value;
}
Result = true;
}
return (Result);
}
void UT_Stub_RegisterReturnType(UT_EntryKey_t FuncKey, size_t ReturnSize)
{
UT_StubTableEntry_t *StubPtr;
if (ReturnSize > 0)
{
/* Check for existing buffer and re-use if same size (should be!) */
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_RETURN_BUFFER);
if (StubPtr != NULL)
{
if (StubPtr->Data.Buff.TotalSize != ReturnSize)
{
UT_ClearStubEntry(StubPtr);
StubPtr = NULL;
}
else
{
StubPtr->Data.Buff.Position = 0;
}
}
if (StubPtr == NULL)
{
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
if (StubPtr == NULL)
{
UtAssert_Abort("Cannot set return buffer - UT_MAX_FUNC_STUBS too low?");
}
else
{
StubPtr->FuncKey = FuncKey;
StubPtr->EntryType = UT_ENTRYTYPE_RETURN_BUFFER;
StubPtr->Data.Buff.BasePtr = malloc(ReturnSize);
if (StubPtr->Data.Buff.BasePtr == NULL)
{
UtAssert_Abort("Cannot allocate data buffer - malloc() failed!");
}
else
{
memset(StubPtr->Data.Buff.BasePtr, 0, ReturnSize);
StubPtr->ModeFlags |= UT_MODEFLAG_ALLOC_BUF;
}
StubPtr->Data.Buff.TotalSize = ReturnSize;
StubPtr->Data.Buff.Position = 0;
}
}
}
}
void *UT_Stub_GetReturnValuePtr(UT_EntryKey_t FuncKey, size_t ReturnSize)
{
UT_StubTableEntry_t *StubPtr;
void * ReturnPtr;
ReturnPtr = NULL;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_RETURN_BUFFER);
/* Sanity check on the size */
if (StubPtr != NULL && StubPtr->Data.Buff.TotalSize == ReturnSize)
{
ReturnPtr = StubPtr->Data.Buff.BasePtr;
}
else
{
/* This shouldn't happen, it means the stub tried to use a
* return buffer that does not exist or does not match size.
*
* It is most likely caused by a mismatch/incompatibility between
* stub and handler. Aborting now is better than segfaulting later,
* as the errored call should still be on the stack trace */
UtAssert_Abort("Return buffer invalid");
}
return ReturnPtr;
}
void UT_SetDataBuffer(UT_EntryKey_t FuncKey, void *DataBuffer, size_t BufferSize, bool AllocateCopy)
{
UT_StubTableEntry_t *StubPtr;
if (DataBuffer != NULL && BufferSize > 0)
{
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
if (StubPtr == NULL)
{
UtAssert_Abort("Cannot set data buffer - UT_MAX_FUNC_STUBS too low?");
}
else
{
StubPtr->FuncKey = FuncKey;
StubPtr->EntryType = UT_ENTRYTYPE_DATA_BUFFER;
if (AllocateCopy)
{
StubPtr->Data.Buff.BasePtr = malloc(BufferSize);
if (StubPtr->Data.Buff.BasePtr == NULL)
{
UtAssert_Abort("Cannot allocate data buffer - malloc() failed!");
}
else
{
memcpy(StubPtr->Data.Buff.BasePtr, DataBuffer, BufferSize);
StubPtr->ModeFlags |= UT_MODEFLAG_ALLOC_BUF;
}
}
else
{
/* Use buffer directly */
StubPtr->Data.Buff.BasePtr = DataBuffer;
}
StubPtr->Data.Buff.TotalSize = BufferSize;
StubPtr->Data.Buff.Position = 0;
}
}
}
void UT_GetDataBuffer(UT_EntryKey_t FuncKey, void **DataBuffer, size_t *MaxSize, size_t *Position)
{
UT_StubTableEntry_t *StubPtr;
void * ResultDataBuffer;
size_t ResultMaxSize;
size_t ResultPosition;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_DATA_BUFFER);
if (StubPtr == NULL)
{
ResultDataBuffer = NULL;
ResultMaxSize = 0;
ResultPosition = 0;
}
else
{
ResultDataBuffer = StubPtr->Data.Buff.BasePtr;
ResultMaxSize = StubPtr->Data.Buff.TotalSize;
ResultPosition = StubPtr->Data.Buff.Position;
}
if (DataBuffer != NULL)
{
*DataBuffer = ResultDataBuffer;
}
if (MaxSize != NULL)
{
*MaxSize = ResultMaxSize;
}
if (Position != NULL)
{
*Position = ResultPosition;
}
}
size_t UT_Stub_CopyToLocal(UT_EntryKey_t FuncKey, void *LocalBuffer, size_t MaxSize)
{
size_t ActualCopy;
UT_StubTableEntry_t *StubPtr;
ActualCopy = 0;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_DATA_BUFFER);
if (StubPtr != NULL)
{
ActualCopy = StubPtr->Data.Buff.TotalSize - StubPtr->Data.Buff.Position;
if (MaxSize < ActualCopy)
{
ActualCopy = MaxSize;
}
memcpy(LocalBuffer, &StubPtr->Data.Buff.BasePtr[StubPtr->Data.Buff.Position], ActualCopy);
StubPtr->Data.Buff.Position += ActualCopy;
/*
* Once the buffer is completely copied then drop this entry.
* The test harness will either refill it or there might be
* another entry in the table.
*/
if (StubPtr->Data.Buff.Position >= StubPtr->Data.Buff.TotalSize)
{
UT_ClearStubEntry(StubPtr);
}
}
return ActualCopy;
}
size_t UT_Stub_CopyFromLocal(UT_EntryKey_t FuncKey, const void *LocalBuffer, size_t MaxSize)
{
size_t ActualCopy;
UT_StubTableEntry_t *StubPtr;
ActualCopy = 0;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_DATA_BUFFER);
if (StubPtr != NULL)
{
ActualCopy = StubPtr->Data.Buff.TotalSize - StubPtr->Data.Buff.Position;
if (MaxSize < ActualCopy)
{
ActualCopy = MaxSize;
}
memcpy(&StubPtr->Data.Buff.BasePtr[StubPtr->Data.Buff.Position], LocalBuffer, ActualCopy);
StubPtr->Data.Buff.Position += ActualCopy;
/*
* Once the buffer is completely copied then drop this entry.
* The test harness will either refill it or there might be
* another entry in the table.
*/
if (StubPtr->Data.Buff.Position >= StubPtr->Data.Buff.TotalSize)
{
UT_ClearStubEntry(StubPtr);
}
}
return ActualCopy;
}
/*
* Helper function used by UT_SetHookFunction() and UT_SetVaHookFunction()
*/
static void UT_DoSetHookFunction(UT_EntryKey_t FuncKey, UT_EntryType_t EntryType, UT_HookFuncPtr_t Value, void *UserObj,
bool IsVarg)
{
UT_StubTableEntry_t *StubPtr;
/*
* First find an existing hook entry for the function.
* In case one is already set we do not duplicate
*/
StubPtr = UT_GetStubEntry(FuncKey, EntryType);
if (StubPtr == NULL && Value.Addr != NULL)
{
/* Creating force fail entry - repeat search and grab any unused slot */
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
if (StubPtr == NULL)
{
UtAssert_Abort("Cannot set hook function - UT_MAX_FUNC_STUBS too low?");
}
}
if (Value.Addr == NULL && StubPtr != NULL)
{
/* Caller wants to delete the entry */
UT_ClearStubEntry(StubPtr);
}
else if (StubPtr != NULL && Value.Addr != NULL)
{
/* Caller wants to set the entry */
StubPtr->FuncKey = FuncKey;
StubPtr->EntryType = EntryType;
StubPtr->Data.Cb.CallbackArg = UserObj;
StubPtr->Data.Cb.Ptr = Value;
StubPtr->Data.Cb.IsVarg = IsVarg;
}
}
void UT_SetHookFunction(UT_EntryKey_t FuncKey, UT_HookFunc_t HookFunc, void *UserObj)
{
UT_HookFuncPtr_t Value;
Value.SimpleHook = HookFunc;
UT_DoSetHookFunction(FuncKey, UT_ENTRYTYPE_CALLBACK_HOOK, Value, UserObj, false);
}
void UT_SetVaHookFunction(UT_EntryKey_t FuncKey, UT_VaHookFunc_t HookFunc, void *UserObj)
{
UT_HookFuncPtr_t Value;
Value.VaHook = HookFunc;
UT_DoSetHookFunction(FuncKey, UT_ENTRYTYPE_CALLBACK_HOOK, Value, UserObj, true);
}
void UT_SetHandlerFunction(UT_EntryKey_t FuncKey, UT_HandlerFunc_t HandlerFunc, void *UserObj)
{
UT_HookFuncPtr_t Value;
Value.SimpleHandler = HandlerFunc;
UT_DoSetHookFunction(FuncKey, UT_ENTRYTYPE_FINAL_HANDLER, Value, UserObj, false);
}
void UT_SetVaHandlerFunction(UT_EntryKey_t FuncKey, UT_VaHandlerFunc_t HandlerFunc, void *UserObj)
{
UT_HookFuncPtr_t Value;
Value.VaHandler = HandlerFunc;
UT_DoSetHookFunction(FuncKey, UT_ENTRYTYPE_FINAL_HANDLER, Value, UserObj, true);
}
const void *UT_Hook_GetArgPtr(const UT_StubContext_t *ContextPtr, const char *Name, size_t ExpectedTypeSize)
{
uint32 i;
const void * Result;
const UT_StubArgMetaData_t *MetaPtr;
static const union
{
unsigned long AsInt;
void * AsPtr;
double AsFloat;
} ARG_DEFAULT_ZERO_VALUE = {0};
Result = NULL;
for (i = 0; i < ContextPtr->ArgCount; ++i)
{
MetaPtr = &ContextPtr->Meta[i];
if (MetaPtr->Name != NULL)
{
if (strcmp(MetaPtr->Name, Name) == 0 && (MetaPtr->Size == 0 || MetaPtr->Size == ExpectedTypeSize))
{
if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_DIRECT)
{
Result = &ContextPtr->ArgPtr[i];
}
else if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_INDIRECT)
{
Result = ContextPtr->ArgPtr[i];
}
break;
}
}
}
/*
* If no suitable result pointer was found, this means a mismatch
* between the stub and test case, such as a change in argument/parameter names.
* This is an error that should be corrected, so report it as a failure.
*/
if (Result == NULL)
{
UtAssert_Failed("Requested parameter %s of size %lu which was not provided by the stub", Name,
(unsigned long)ExpectedTypeSize);
if (ExpectedTypeSize <= sizeof(ARG_DEFAULT_ZERO_VALUE))
{
Result = &ARG_DEFAULT_ZERO_VALUE;
}
else
{
/*
* As the caller will likely dereference the returned pointer, should
* never return NULL. Just abort here.
*/
UtAssert_Abort("No value for parameter");
}
}
return Result;
}
void UT_Stub_RegisterContextWithMetaData(UT_EntryKey_t FuncKey, const char *Name, UT_StubContext_Arg_Type_t ParamType,
const void *ParamPtr, size_t ParamSize)
{
UT_StubTableEntry_t * StubPtr;
UT_StubArgMetaData_t *MetaPtr;
/*
* First find an existing context entry for the function.
* In case one is already set we do not duplicate
*/
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_CALLBACK_CONTEXT);
if (StubPtr == NULL)
{
/* Creating force fail entry - repeat search and grab any unused slot */
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
}
if (StubPtr == NULL)
{
UtAssert_Abort("Cannot set context - UT_MAX_FUNC_STUBS too low?");
}
else
{
StubPtr->FuncKey = FuncKey;
StubPtr->EntryType = UT_ENTRYTYPE_CALLBACK_CONTEXT;
if (StubPtr->Data.Context.ArgCount < UT_STUBCONTEXT_MAXSIZE)
{
StubPtr->Data.Context.ArgPtr[StubPtr->Data.Context.ArgCount] = ParamPtr;
MetaPtr = &StubPtr->Data.Context.Meta[StubPtr->Data.Context.ArgCount];
MetaPtr->Size = ParamSize;
MetaPtr->Type = ParamType;
/*
* If name was specified, then trim any leading address operator (&)
* and/or whitespace, keeping only the actual name part.
*/
if (Name != NULL)
{
/*
* If the _address_ of the stack variable was actually passed in,
* the mark this as indirect (i.e. hook must dereference ArgPtr
* to get actual parameter value). Otherwise assume it as direct.
*/
MetaPtr->Name = Name;
while (*MetaPtr->Name != 0)
{
if (*MetaPtr->Name == '&')
{
/* this means its a pointer to the value, not the value itself */
if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_UNSPECIFIED)
{
MetaPtr->Type = UT_STUBCONTEXT_ARG_TYPE_INDIRECT;
}
}
else if (*MetaPtr->Name != ' ')
{
/* stop at non-whitespace */
break;
}
++MetaPtr->Name;
}
if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_UNSPECIFIED)
{
MetaPtr->Type = UT_STUBCONTEXT_ARG_TYPE_DIRECT;
}
}
++StubPtr->Data.Context.ArgCount;
}
}
}
bool UT_Stub_GetInt32StatusCode(const UT_StubContext_t *Context, int32 *StatusCodeBuffer)
{
if (StatusCodeBuffer != NULL)
{
*StatusCodeBuffer = Context->Int32StatusCode;
}
return Context->Int32StatusIsSet;
}
void UT_Stub_CopyToReturnValue(UT_EntryKey_t FuncKey, const void *BufferPtr, size_t BufferSize)
{
UT_StubTableEntry_t *StubPtr;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_RETURN_BUFFER);
if (StubPtr != NULL)
{
/* If the size does not match, there is a bug */
if (StubPtr->Data.Buff.TotalSize != BufferSize)
{
UtAssert_Abort("Size mismatch in setting return value");
}
else
{
memcpy(StubPtr->Data.Buff.BasePtr, BufferPtr, BufferSize);
StubPtr->Data.Buff.Position = BufferSize;
}
}
}
/**
* Default implementation for a stub function that should be useful for most cases.
* Checks first for a deferred retcode, then for a constant retcode, and a default if neither is present.
* Prints a debug level status message to show that the function was called.
*/
int32 UT_DefaultStubImplWithArgs(const char *FunctionName, UT_EntryKey_t FuncKey, int32 DefaultRc, va_list ArgList)
{
const char * RetcodeString;
UT_StubTableEntry_t *StubPtr;
UT_StubTableEntry_t *ContextTblPtr;
UT_StubContext_t LocalContext;
uint32 Counter;
va_list ArgListCopy;
/*
* In this implementation a context is _always_ needed.
*
* First see if the stub has an already-registered context. Any non-trivial stub
* should already have registered some arguments and/or a return code buffer.
*
* To handle an old-style/incomplete stub function or simple void/void stubs that
* do not have any arguments, a blank entry can be created on the fly.
*/
ContextTblPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_CALLBACK_CONTEXT);
if (ContextTblPtr != NULL)
{
LocalContext = ContextTblPtr->Data.Context;
/* Always clear the context entry -- the next call will have a different one */
UT_ClearStubEntry(ContextTblPtr);
}
else
{
memset(&LocalContext, 0, sizeof(LocalContext));
}
LocalContext.Int32StatusIsSet = UT_Stub_CheckDeferredRetcode(FuncKey, &LocalContext.Int32StatusCode);
if (!LocalContext.Int32StatusIsSet)
{
LocalContext.Int32StatusIsSet = UT_Stub_CheckDefaultReturnValue(FuncKey, &LocalContext.Int32StatusCode);
}
if (!LocalContext.Int32StatusIsSet)
{
LocalContext.Int32StatusCode = DefaultRc;
}
if (FunctionName != NULL)
{
if (!LocalContext.Int32StatusIsSet)
{
RetcodeString = "DEFAULT";
}
else
{
/* Indicate that this invocation got a non-default return code */
RetcodeString = "*SPECIAL*";
}
UtDebug("%s called (%s,%d)", FunctionName, RetcodeString, (int)LocalContext.Int32StatusCode);
}
Counter = 0;
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_COUNTER);
if (StubPtr == NULL)
{
/* Creating counter entry - repeat search and grab any unused slot */
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_UNUSED);
}
if (StubPtr == NULL)
{
UtAssert_Abort("Cannot set counter - UT_MAX_FUNC_STUBS too low?");
}
else
{
StubPtr->EntryType = UT_ENTRYTYPE_COUNTER;
StubPtr->FuncKey = FuncKey;
Counter = StubPtr->Data.Rc.Count;
++StubPtr->Data.Rc.Count;
StubPtr->Data.Rc.Value = LocalContext.Int32StatusCode;
}
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_CALLBACK_HOOK);
if (StubPtr != NULL)
{
if (StubPtr->Data.Cb.IsVarg)
{
va_copy(ArgListCopy, ArgList);
LocalContext.Int32StatusCode = StubPtr->Data.Cb.Ptr.VaHook(
StubPtr->Data.Cb.CallbackArg, LocalContext.Int32StatusCode, Counter, &LocalContext, ArgListCopy);
va_end(ArgListCopy);
}
else
{
LocalContext.Int32StatusCode = StubPtr->Data.Cb.Ptr.SimpleHook(
StubPtr->Data.Cb.CallbackArg, LocalContext.Int32StatusCode, Counter, &LocalContext);
}
LocalContext.Int32StatusIsSet = true;
}
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_FINAL_HANDLER);
if (StubPtr != NULL)
{
if (StubPtr->Data.Cb.IsVarg)
{
StubPtr->Data.Cb.Ptr.VaHandler(StubPtr->Data.Cb.CallbackArg, FuncKey, &LocalContext, ArgList);
}
else
{
StubPtr->Data.Cb.Ptr.SimpleHandler(StubPtr->Data.Cb.CallbackArg, FuncKey, &LocalContext);
}
}
/*
* Handle propagation of return code.
*
* "old style" stubs will translate the int32 return value from this function.
* - Return Buffer will always be unset with these stubs
* - This should continue to work with no special handling.
*
* "new style" stubs will provide a return value buffer via UT_ENTRYTYPE_RETURN_BUFFER in the context.
* - In this pattern the int32 return value of this function is _IGNORED_ by the calling stub.
* - The only way to return a value to the caller is via the buffer.
* - If the handler did _not_ populate the return value, then attempt to translate the local
* Retcode here to the return value buffer. This eases the transition, as the vast majority
* of functions do return an int32, this can be the automatic case.
*/
StubPtr = UT_GetStubEntry(FuncKey, UT_ENTRYTYPE_RETURN_BUFFER);
if (StubPtr != NULL && StubPtr->Data.Buff.Position == 0)
{
/*