-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstubfactory.cpp
472 lines (407 loc) · 16.6 KB
/
stubfactory.cpp
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
/*!
* MIT License
*
* Copyright (c) 2017 jonathanreeves
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <clang-c/Index.h>
#include <iostream>
#include <vector>
/*!
* @brief Stub function/method meta data class. Mainly used for
* collecting information about a function or method as it
* is encountered while traversing the AST
*/
class FunctionInfo
{
public:
FunctionInfo() {}
~FunctionInfo() {}
CXCursorKind kind;
const char *name;
const char *retType;
CXTypeKind retTypeKind;
const char *className; // NULL for plain C functions
std::vector<const char *> argNames;
std::vector<const char *> argTypes;
};
static std::vector<CXString> g_namespaces;
static std::vector<FunctionInfo> g_functions;
/*!
* @brief Clang AST node callback for function arguments (children of a function)
* @param[in] cursor See libclang documentation
* @param[in] parent See libclang documentation
* @param[in] client_data See libclang documentation
*
* This function is called by libclang when a function or method declaration is
* traversed. The children of a function or method declaration will usually be
* its parameters (arguments), thus the main purpose of this function is to
* capture the parameter info for a given function or method.
*/
CXChildVisitResult functionDeclParamVisitor(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
CXCursorKind kind = clang_getCursorKind(cursor);
if (kind == CXCursor_ParmDecl){
FunctionInfo *info = reinterpret_cast<FunctionInfo *>(client_data);
CXType type = clang_getCursorType(cursor);
CXString typeName = clang_getTypeSpelling(type);
CXString paramName = clang_getCursorSpelling(cursor);
info->argNames.push_back(clang_getCString(paramName));
info->argTypes.push_back(clang_getCString(typeName));
}
return CXChildVisit_Continue;
}
/*!
* @brief Given a clang AST cursor at a function or method declaration, fill
* in a FunctionInfo meta data class with critical information
* @param[in] cursor A libclang AST cursor at the function or method of interest
* @param[in] kind A libclang cursor "kind" describing what type of method or
* function this is (i.e. method, function, constructor, destructor)
* @param[out] info A FunctionInfo class instance to fill in with info
* @param[in] className The name of the class to which this method belongs. If it's
* a pure C function, className will be NULL
*
* This function will capture the top-level function info (name, return type, etc.)
* and also ask libclang to traverse the functions "children", which will provide
* function argument names and types.
*/
void fillFunctionInfo(CXCursor cursor, CXCursorKind kind, FunctionInfo &info, const char *className)
{
CXType funcType = clang_getCursorType(cursor);
CXType returnType = clang_getResultType(funcType);
CXString typeName = clang_getTypeSpelling(returnType);
CXString cursorName = clang_getCursorSpelling(cursor);
info.kind = kind;
info.className = className;
info.name = clang_getCString(cursorName);
info.retType = clang_getCString(typeName);
info.retTypeKind = returnType.kind; // needed for determining default
// visit function children (looking for params)
clang_visitChildren(cursor, *functionDeclParamVisitor, &info);
}
/*!
* @brief Generate and print all required "helper" variables for a given function
* stub
* @param[in] stubName the string name for this unit test stub
* @param[in] functions reference to a FunctionInfo struct for a declared function
*
* For any function stub, a series of global variables are generated which can be
* used in writing unit tests to manipulate the behavior of the function. For
* example the return value can be set, and argument values for a function can
* be captured for checking in the test. This function emits the declarations
* for these variables.
*
* Note that since these variables must be easily manipulable, all const qualifiers
* should be removed in the declarations.
*/
void declareFunctionGlobalVariables(const char *stubName, const FunctionInfo &info)
{
const char *funcPrefix = stubName;
const char *funcNameForStubVariables = info.name;
if (info.kind == CXCursor_Constructor) {
funcNameForStubVariables = "constructor";
} else if (info.kind == CXCursor_Destructor) {
funcNameForStubVariables = "destructor";
}
if (info.className != NULL) {
funcPrefix = info.className;
}
// print the return variable if it exists
// TODO: figure out default initializer
if (info.retTypeKind != CXType_Void) {
printf("%s g_%s_%s_return;\n", info.retType, funcPrefix, funcNameForStubVariables);
}
printf("uint32_t g_%s_%s_callCount = 0;\n", funcPrefix, funcNameForStubVariables);
// print all argument capture variables
// TODO: figure out default initializer
for (size_t i = 0; i < info.argTypes.size(); i++) {
printf("%s g_%s_%s_%s;\n", info.argTypes[i], funcPrefix, funcNameForStubVariables, info.argNames[i]);
}
// print the hook declaration
if (info.retTypeKind != CXType_Void) {
printf("%s ", info.retType);
} else {
printf("void ");
}
printf("(*g_%s_%s_hook)(", funcPrefix, funcNameForStubVariables);
if (info.argTypes.size() > 0) {
for (size_t i = 0; i < info.argTypes.size() - 1; i++) {
printf("%s, ", info.argTypes[i]);
}
printf("%s", info.argTypes[info.argTypes.size() - 1]);
} else {
printf("void");
}
printf(");\n");
printf("\n");
}
/*!
* @brief Generate and print a global stub function implementation based on
* declarations encountered during parsing
* @param[in] stubName the string name for this unit test stub
* @param[in] functions reference to a FunctionInfo struct for a declared function
*/
void printFunctionInfo(const char *stubName, const FunctionInfo &info)
{
const char *funcPrefix = stubName;
// the function name for stub-related variables may be different than the
// name of the function itself. This is because constructors and destructors
// in a class may require special handling
const char *funcNameForStubVariables = info.name;
const char *funcName = info.name;
if (info.kind == CXCursor_Constructor) {
funcNameForStubVariables = "constructor";
} else if (info.kind == CXCursor_Destructor) {
funcNameForStubVariables = "destructor";
}
if (info.className != NULL) {
funcPrefix = info.className;
}
// don't print a return type for constructors and destructors
if ((info.kind != CXCursor_Constructor) && (info.kind != CXCursor_Destructor)) {
printf("%s ", info.retType);
}
if (info.className != NULL) {
printf("%s::", info.className);
}
printf("%s(", funcName);
if (info.argTypes.size() > 0) {
for (size_t i = 0; i < info.argTypes.size() - 1; i++) {
printf("%s %s, ", info.argTypes[i], info.argNames[i]);
}
printf("%s %s", info.argTypes.back(), info.argNames.back());
}
printf(")\n{\n");
if (info.retTypeKind != CXType_Void) {
printf(" %s ret = g_%s_%s_return;\n", info.retType, funcPrefix, funcNameForStubVariables);
}
printf(" g_%s_%s_callCount++;\n", funcPrefix, funcNameForStubVariables);
for (size_t i = 0; i < info.argTypes.size(); i++) {
printf(" g_%s_%s_%s = %s;\n", funcPrefix, funcNameForStubVariables, info.argNames[i], info.argNames[i]);
}
printf(" if (g_%s_%s_hook != NULL) {\n", funcPrefix, funcNameForStubVariables);
printf(" ");
if (info.retTypeKind != CXType_Void) {
printf("ret = ");
}
printf("g_%s_%s_hook(", funcPrefix, funcNameForStubVariables);
if (info.argNames.size() > 0) {
for (size_t i = 0; i < info.argNames.size() - 1; i++) {
printf("%s, ", info.argNames[i]);
}
printf("%s", info.argNames.back());
}
printf(");\n } \n");
if (info.retTypeKind != CXType_Void) {
printf(" return ret;\n");
}
printf("}\n");
}
/*!
* @brief Generate and print a global stub reset() function which will return
* the stub to its default state when called in unit tests
* @param[in] stubName the string name for this unit test stub
* @param[in] functions reference to an array of FunctionInfo structs for all
* functions stubbed in this unit test stub
*/
void printResetFunction(const char *stubName, const std::vector<FunctionInfo> &functions)
{
printf("void stub_%s_reset(void)\n{\n", stubName);
// TODO: set all returns to their defaults:
// TODO: set all function args to their defaults:
// set all call counts to 0
for (unsigned int i = 0; i < functions.size(); i++) {
const char *funcPrefix = "file";
const char *funcNameForStubVariables = functions[i].name;
if (functions[i].kind == CXCursor_Constructor) {
funcNameForStubVariables = "constructor";
} else if (functions[i].kind == CXCursor_Destructor) {
funcNameForStubVariables = "destructor";
}
if (functions[i].className != NULL) {
funcPrefix = functions[i].className;
}
printf(" g_%s_%s_callCount = 0;\n", funcPrefix, funcNameForStubVariables);
}
printf("\n");
// set all hooks to NULL
for (unsigned int i = 0; i < functions.size(); i++) {
const char *funcPrefix = "file";
const char *funcNameForStubVariables = functions[i].name;
if (functions[i].kind == CXCursor_Constructor) {
funcNameForStubVariables = "constructor";
} else if (functions[i].kind == CXCursor_Destructor) {
funcNameForStubVariables = "destructor";
}
if (functions[i].className != NULL) {
funcPrefix = functions[i].className;
}
printf(" g_%s_%s_hook = NULL;\n", funcPrefix, funcNameForStubVariables);
}
printf("}\n\n");
}
/*!
* @brief Clang AST node callback for C++ class method declarations
* @param[in] cursor See libclang documentation
* @param[in] parent See libclang documentation
* @param[in] client_data See libclang documentation
*
* This function is called by libclang when a function declaration is
* encountered in the AST. Our job is to read the information about the
* function encountered and put it into our own data structure that
* can be used for printing our stub code later.
*/
CXChildVisitResult classMethodVisitor(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
CXCursorKind kind = clang_getCursorKind(cursor);
if(kind == CXCursorKind::CXCursor_CXXMethod ||
kind == CXCursorKind::CXCursor_Constructor ||
kind == CXCursorKind::CXCursor_Destructor) {
CXString *className = reinterpret_cast<CXString *>(client_data);
FunctionInfo info;
fillFunctionInfo(cursor, kind, info, clang_getCString(*className));
g_functions.push_back(info);
}
return CXChildVisit_Continue;
}
/*!
* @brief Clang AST node callback for the top level node
* @param[in] cursor See libclang documentation
* @param[in] parent See libclang documentation
* @param[in] client_data See libclang documentation
*
* This is the top level AST parser function. We will look for namespaces,
* C-function declarations and class method declarations from which we will
* generate stubs.
*/
CXChildVisitResult nodeVisitor(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
CXChildVisitResult res = CXChildVisit_Continue;
// skip all classes and functions not comming from the immediate file
if (clang_Location_isFromMainFile(clang_getCursorLocation(cursor)) != 0) {
CXCursorKind kind = clang_getCursorKind(cursor);
// Capture namespaces so they can be "used" with a using directive
if (kind == CXCursorKind::CXCursor_Namespace) {
CXString namespaceName = clang_getCursorSpelling(cursor);
g_namespaces.push_back(namespaceName);
}
// Explore a class
if (kind == CXCursorKind::CXCursor_ClassDecl) {
CXString className = clang_getCursorSpelling(cursor);
// visit function children (looking for methods)
clang_visitChildren(cursor, *classMethodVisitor, &className);
}
// Explore "bare" C functions (outside of a class)
if (kind == CXCursorKind::CXCursor_FunctionDecl) {
FunctionInfo info;
fillFunctionInfo(cursor, kind, info, NULL);
g_functions.push_back(info);
}
res = CXChildVisit_Recurse;
}
return res;
}
/*!
* @brief stubfactory main
*/
int main(int argc, char **argv)
{
CXErrorCode parseRes;
CXTranslationUnit translationUnit;
int retVal = 0;
if (argc < 2) {
retVal = -1;
return retVal;
}
// Create an index with excludeDeclsFromPCH = 1, displayDiagnostics = 0
CXIndex index = clang_createIndex(1, 0);
// Skip function bodies, pass args as if clang were invoked directly
// from the command line
parseRes = clang_parseTranslationUnit2FullArgv(
index,
NULL,
argv,
argc,
NULL,
0,
CXTranslationUnit_SkipFunctionBodies,
&translationUnit);
if (parseRes != CXError_Success) {
fprintf(stderr, "ERROR: parser returned code %d\n", parseRes);
retVal = -1;
return retVal;
}
for (unsigned int i = 0; i < clang_getNumDiagnostics(translationUnit); i++) {
CXDiagnostic diag = clang_getDiagnostic(translationUnit, i);
CXDiagnosticSeverity severity = clang_getDiagnosticSeverity(diag);
if (severity > CXDiagnostic_Warning) {
CXString diagText = clang_getDiagnosticSpelling(diag);
fprintf(stderr, "ERROR: %s\n", clang_getCString(diagText));
clang_disposeString(diagText);
retVal = -1;
}
clang_disposeDiagnostic(diag);
}
if (retVal != 0) {
// don't continue
return retVal;
}
// Visit all the nodes in the AST
CXCursor cursor = clang_getTranslationUnitCursor(translationUnit);
clang_visitChildren(cursor, nodeVisitor, 0);
// Get file name and stub name info from the translation unit
CXString filePath = clang_getTranslationUnitSpelling(translationUnit);
std::string filePathStr(clang_getCString(filePath));
size_t extStart = filePathStr.find_last_of("\\/");
if (extStart == std::string::npos) {
extStart = 0;
} else {
extStart++;
}
std::string fileNameStr = filePathStr.substr(extStart);
// strip extension
std::string stubNameStr = fileNameStr.substr(0, fileNameStr.find_last_of("."));
// print results:
// includes
printf("#include <stdint.h>\n");
printf("#include <stdlib.h>\n");
printf("#include \"%s\"\n\n", filePathStr.c_str());
// namespaces
for (size_t i = 0; i < g_namespaces.size(); i++) {
if (strcmp("", clang_getCString(g_namespaces[i])) != 0) {
printf("using namespace %s;\n", clang_getCString(g_namespaces[i]));
}
}
printf("\n");
// variable declarations
for (size_t i = 0; i < g_functions.size(); i++) {
declareFunctionGlobalVariables(stubNameStr.c_str(), g_functions[i]);
}
// global stub reset function
printResetFunction(stubNameStr.c_str(), g_functions);
// function/method implementations
for (size_t i = 0; i < g_functions.size(); i++) {
printFunctionInfo(stubNameStr.c_str(), g_functions[i]);
}
// Release memory
clang_disposeTranslationUnit(translationUnit);
clang_disposeIndex(index);
return 0;
}