c-index-test.c revision 377adb6cffb3fdfcf659303d6f901adef4d61971
1/* c-index-test.c */ 2 3#include "clang-c/Index.h" 4#include <stdlib.h> 5#include <stdio.h> 6#include <string.h> 7 8/******************************************************************************/ 9/* Utility functions. */ 10/******************************************************************************/ 11 12#ifdef _MSC_VER 13char *basename(const char* path) 14{ 15 char* base1 = (char*)strrchr(path, '/'); 16 char* base2 = (char*)strrchr(path, '\\'); 17 if (base1 && base2) 18 return((base1 > base2) ? base1 + 1 : base2 + 1); 19 else if (base1) 20 return(base1 + 1); 21 else if (base2) 22 return(base2 + 1); 23 24 return((char*)path); 25} 26#else 27extern char *basename(const char *); 28#endif 29 30static unsigned CreateTranslationUnit(CXIndex Idx, const char *file, 31 CXTranslationUnit *TU) { 32 33 *TU = clang_createTranslationUnit(Idx, file); 34 if (!TU) { 35 fprintf(stderr, "Unable to load translation unit from '%s'!\n", file); 36 return 0; 37 } 38 return 1; 39} 40 41/******************************************************************************/ 42/* Pretty-printing. */ 43/******************************************************************************/ 44 45static void PrintCursor(CXCursor Cursor) { 46 if (clang_isInvalid(Cursor.kind)) 47 printf("Invalid Cursor => %s", clang_getCursorKindSpelling(Cursor.kind)); 48 else { 49 CXDecl DeclReferenced; 50 CXString string; 51 string = clang_getCursorSpelling(Cursor); 52 printf("%s=%s", clang_getCursorKindSpelling(Cursor.kind), 53 clang_getCString(string)); 54 clang_disposeString(string); 55 DeclReferenced = clang_getCursorDecl(Cursor); 56 if (DeclReferenced) 57 printf(":%d:%d", clang_getDeclLine(DeclReferenced), 58 clang_getDeclColumn(DeclReferenced)); 59 } 60} 61 62static const char* GetCursorSource(CXCursor Cursor) { 63 const char *source = clang_getCursorSource(Cursor); 64 if (!source) 65 return "<invalid loc>"; 66 return basename(source); 67} 68 69/******************************************************************************/ 70/* Logic for testing clang_loadTranslationUnit(). */ 71/******************************************************************************/ 72 73static const char *FileCheckPrefix = "CHECK"; 74 75static void PrintDeclExtent(CXDecl Dcl) { 76 CXSourceExtent extent = clang_getDeclExtent(Dcl); 77 printf(" [Extent=%d:%d:%d:%d]", extent.begin.line, extent.begin.column, 78 extent.end.line, extent.end.column); 79} 80 81static void DeclVisitor(CXDecl Dcl, CXCursor Cursor, CXClientData Filter) { 82 if (!Filter || (Cursor.kind == *(enum CXCursorKind *)Filter)) { 83 CXString string; 84 printf("// %s: %s:%d:%d: ", FileCheckPrefix, 85 GetCursorSource(Cursor), 86 clang_getCursorLine(Cursor), 87 clang_getCursorColumn(Cursor)); 88 PrintCursor(Cursor); 89 90 string = clang_getDeclSpelling(Dcl); 91 printf(" [Context=%s]", clang_getCString(string)); 92 clang_disposeString(string); 93 94 PrintDeclExtent(clang_getCursorDecl(Cursor)); 95 96 printf("\n"); 97 } 98} 99 100static void TranslationUnitVisitor(CXTranslationUnit Unit, CXCursor Cursor, 101 CXClientData Filter) { 102 if (!Filter || (Cursor.kind == *(enum CXCursorKind *)Filter)) { 103 CXString string; 104 printf("// %s: %s:%d:%d: ", FileCheckPrefix, 105 GetCursorSource(Cursor), clang_getCursorLine(Cursor), 106 clang_getCursorColumn(Cursor)); 107 PrintCursor(Cursor); 108 string = clang_getTranslationUnitSpelling(Unit); 109 printf(" [Context=%s]", 110 basename(clang_getCString(string))); 111 clang_disposeString(string); 112 113 PrintDeclExtent(Cursor.decl); 114 115 printf("\n"); 116 117 clang_loadDeclaration(Cursor.decl, DeclVisitor, 0); 118 } 119} 120 121static void FunctionScanVisitor(CXTranslationUnit Unit, CXCursor Cursor, 122 CXClientData Filter) { 123 const char *startBuf, *endBuf; 124 unsigned startLine, startColumn, endLine, endColumn, curLine, curColumn; 125 CXCursor Ref; 126 127 if (Cursor.kind != CXCursor_FunctionDefn) 128 return; 129 130 clang_getDefinitionSpellingAndExtent(Cursor, &startBuf, &endBuf, 131 &startLine, &startColumn, 132 &endLine, &endColumn); 133 /* Probe the entire body, looking for both decls and refs. */ 134 curLine = startLine; 135 curColumn = startColumn; 136 137 while (startBuf < endBuf) { 138 if (*startBuf == '\n') { 139 startBuf++; 140 curLine++; 141 curColumn = 1; 142 } else if (*startBuf != '\t') 143 curColumn++; 144 145 Ref = clang_getCursor(Unit, clang_getCursorSource(Cursor), 146 curLine, curColumn); 147 if (Ref.kind == CXCursor_NoDeclFound) { 148 /* Nothing found here; that's fine. */ 149 } else if (Ref.kind != CXCursor_FunctionDecl) { 150 CXString string; 151 printf("// %s: %s:%d:%d: ", FileCheckPrefix, GetCursorSource(Ref), 152 curLine, curColumn); 153 PrintCursor(Ref); 154 string = clang_getDeclSpelling(Ref.decl); 155 printf(" [Context:%s]\n", clang_getCString(string)); 156 clang_disposeString(string); 157 } 158 startBuf++; 159 } 160} 161 162static int perform_test_load(CXIndex Idx, CXTranslationUnit TU, 163 const char *filter, const char *prefix) { 164 enum CXCursorKind K = CXCursor_NotImplemented; 165 CXTranslationUnitIterator Visitor = TranslationUnitVisitor; 166 enum CXCursorKind *ck = &K; 167 168 if (prefix) 169 FileCheckPrefix = prefix; 170 171 /* Perform some simple filtering. */ 172 if (!strcmp(filter, "all") || !strcmp(filter, "local")) ck = NULL; 173 else if (!strcmp(filter, "category")) K = CXCursor_ObjCCategoryDecl; 174 else if (!strcmp(filter, "interface")) K = CXCursor_ObjCInterfaceDecl; 175 else if (!strcmp(filter, "protocol")) K = CXCursor_ObjCProtocolDecl; 176 else if (!strcmp(filter, "function")) K = CXCursor_FunctionDecl; 177 else if (!strcmp(filter, "typedef")) K = CXCursor_TypedefDecl; 178 else if (!strcmp(filter, "scan-function")) Visitor = FunctionScanVisitor; 179 else { 180 fprintf(stderr, "Unknown filter for -test-load-tu: %s\n", filter); 181 return 1; 182 } 183 184 clang_loadTranslationUnit(TU, Visitor, ck); 185 clang_disposeTranslationUnit(TU); 186 return 0; 187} 188 189int perform_test_load_tu(const char *file, const char *filter, 190 const char *prefix) { 191 CXIndex Idx; 192 CXTranslationUnit TU; 193 Idx = clang_createIndex(/* excludeDeclsFromPCH */ 194 !strcmp(filter, "local") ? 1 : 0, 195 /* displayDiagnostics */ 1); 196 197 if (!CreateTranslationUnit(Idx, file, &TU)) 198 return 1; 199 200 return perform_test_load(Idx, TU, filter, prefix); 201} 202 203int perform_test_load_source(int argc, const char **argv, const char *filter) { 204 const char *UseExternalASTs = 205 getenv("CINDEXTEST_USE_EXTERNAL_AST_GENERATION"); 206 CXIndex Idx; 207 CXTranslationUnit TU; 208 Idx = clang_createIndex(/* excludeDeclsFromPCH */ 209 !strcmp(filter, "local") ? 1 : 0, 210 /* displayDiagnostics */ 1); 211 212 if (UseExternalASTs && strlen(UseExternalASTs)) 213 clang_setUseExternalASTGeneration(Idx, 1); 214 215 TU = clang_createTranslationUnitFromSourceFile(Idx, 0, argc, argv); 216 if (!TU) { 217 fprintf(stderr, "Unable to load translation unit!\n"); 218 return 1; 219 } 220 221 return perform_test_load(Idx, TU, filter, NULL); 222} 223 224/******************************************************************************/ 225/* Logic for testing clang_getCursor(). */ 226/******************************************************************************/ 227 228static void print_cursor_file_scan(CXCursor cursor, 229 unsigned start_line, unsigned start_col, 230 unsigned end_line, unsigned end_col, 231 const char *prefix) { 232 printf("// CHECK"); 233 if (prefix) 234 printf("-%s", prefix); 235 printf("{start_line=%d start_col=%d end_line=%d end_col=%d} ", 236 start_line, start_col, end_line, end_col); 237 PrintCursor(cursor); 238 printf("\n"); 239} 240 241static int perform_file_scan(const char *ast_file, const char *source_file, 242 const char *prefix) { 243 CXIndex Idx; 244 CXTranslationUnit TU; 245 FILE *fp; 246 unsigned line; 247 CXCursor prevCursor; 248 unsigned printed; 249 unsigned start_line, start_col, last_line, last_col; 250 size_t i; 251 252 if (!(Idx = clang_createIndex(/* excludeDeclsFromPCH */ 1, 253 /* displayDiagnostics */ 1))) { 254 fprintf(stderr, "Could not create Index\n"); 255 return 1; 256 } 257 258 if (!CreateTranslationUnit(Idx, ast_file, &TU)) 259 return 1; 260 261 if ((fp = fopen(source_file, "r")) == NULL) { 262 fprintf(stderr, "Could not open '%s'\n", source_file); 263 return 1; 264 } 265 266 line = 0; 267 prevCursor = clang_getNullCursor(); 268 printed = 0; 269 start_line = last_line = 1; 270 start_col = last_col = 1; 271 272 while (!feof(fp)) { 273 size_t len = 0; 274 int c; 275 276 while ((c = fgetc(fp)) != EOF) { 277 len++; 278 if (c == '\n') 279 break; 280 } 281 282 ++line; 283 284 for (i = 0; i < len ; ++i) { 285 CXCursor cursor; 286 cursor = clang_getCursor(TU, source_file, line, i+1); 287 288 if (!clang_equalCursors(cursor, prevCursor) && 289 prevCursor.kind != CXCursor_InvalidFile) { 290 print_cursor_file_scan(prevCursor, start_line, start_col, 291 last_line, last_col, prefix); 292 printed = 1; 293 start_line = line; 294 start_col = (unsigned) i+1; 295 } 296 else { 297 printed = 0; 298 } 299 300 prevCursor = cursor; 301 last_line = line; 302 last_col = (unsigned) i+1; 303 } 304 } 305 306 if (!printed && prevCursor.kind != CXCursor_InvalidFile) { 307 print_cursor_file_scan(prevCursor, start_line, start_col, 308 last_line, last_col, prefix); 309 } 310 311 fclose(fp); 312 return 0; 313} 314 315/******************************************************************************/ 316/* Logic for testing clang_codeComplete(). */ 317/******************************************************************************/ 318 319/* Parse file:line:column from the input string. Returns 0 on success, non-zero 320 on failure. If successful, the pointer *filename will contain newly-allocated 321 memory (that will be owned by the caller) to store the file name. */ 322int parse_file_line_column(const char *input, char **filename, unsigned *line, 323 unsigned *column) { 324 /* Find the second colon. */ 325 const char *second_colon = strrchr(input, ':'), *first_colon; 326 char *endptr = 0; 327 if (!second_colon || second_colon == input) { 328 fprintf(stderr, "could not parse filename:line:column in '%s'\n", input); 329 return 1; 330 } 331 332 /* Parse the column number. */ 333 *column = strtol(second_colon + 1, &endptr, 10); 334 if (*endptr != 0) { 335 fprintf(stderr, "could not parse column in '%s'\n", input); 336 return 1; 337 } 338 339 /* Find the first colon. */ 340 first_colon = second_colon - 1; 341 while (first_colon != input && *first_colon != ':') 342 --first_colon; 343 if (first_colon == input) { 344 fprintf(stderr, "could not parse line in '%s'\n", input); 345 return 1; 346 } 347 348 /* Parse the line number. */ 349 *line = strtol(first_colon + 1, &endptr, 10); 350 if (*endptr != ':') { 351 fprintf(stderr, "could not parse line in '%s'\n", input); 352 return 1; 353 } 354 355 /* Copy the file name. */ 356 *filename = (char*)malloc(first_colon - input + 1); 357 memcpy(*filename, input, first_colon - input); 358 (*filename)[first_colon - input] = 0; 359 return 0; 360} 361 362const char * 363clang_getCompletionChunkKindSpelling(enum CXCompletionChunkKind Kind) { 364 switch (Kind) { 365 case CXCompletionChunk_Optional: return "Optional"; 366 case CXCompletionChunk_TypedText: return "TypedText"; 367 case CXCompletionChunk_Text: return "Text"; 368 case CXCompletionChunk_Placeholder: return "Placeholder"; 369 case CXCompletionChunk_Informative: return "Informative"; 370 case CXCompletionChunk_CurrentParameter: return "CurrentParameter"; 371 case CXCompletionChunk_LeftParen: return "LeftParen"; 372 case CXCompletionChunk_RightParen: return "RightParen"; 373 case CXCompletionChunk_LeftBracket: return "LeftBracket"; 374 case CXCompletionChunk_RightBracket: return "RightBracket"; 375 case CXCompletionChunk_LeftBrace: return "LeftBrace"; 376 case CXCompletionChunk_RightBrace: return "RightBrace"; 377 case CXCompletionChunk_LeftAngle: return "LeftAngle"; 378 case CXCompletionChunk_RightAngle: return "RightAngle"; 379 case CXCompletionChunk_Comma: return "Comma"; 380 case CXCompletionChunk_ResultType: return "ResultType"; 381 } 382 383 return "Unknown"; 384} 385 386void print_completion_string(CXCompletionString completion_string, FILE *file) { 387 int I, N; 388 389 N = clang_getNumCompletionChunks(completion_string); 390 for (I = 0; I != N; ++I) { 391 const char *text = 0; 392 enum CXCompletionChunkKind Kind 393 = clang_getCompletionChunkKind(completion_string, I); 394 395 if (Kind == CXCompletionChunk_Optional) { 396 fprintf(file, "{Optional "); 397 print_completion_string( 398 clang_getCompletionChunkCompletionString(completion_string, I), 399 file); 400 fprintf(file, "}"); 401 continue; 402 } 403 404 text = clang_getCompletionChunkText(completion_string, I); 405 fprintf(file, "{%s %s}", 406 clang_getCompletionChunkKindSpelling(Kind), 407 text? text : ""); 408 } 409} 410 411void print_completion_result(CXCompletionResult *completion_result, 412 CXClientData client_data) { 413 FILE *file = (FILE *)client_data; 414 fprintf(file, "%s:", 415 clang_getCursorKindSpelling(completion_result->CursorKind)); 416 print_completion_string(completion_result->CompletionString, file); 417 fprintf(file, "\n"); 418} 419 420void free_remapped_files(struct CXUnsavedFile *unsaved_files, 421 int num_unsaved_files) { 422 int i; 423 for (i = 0; i != num_unsaved_files; ++i) { 424 free((char *)unsaved_files[i].Filename); 425 free((char *)unsaved_files[i].Contents); 426 } 427} 428 429int parse_remapped_files(int argc, const char **argv, int start_arg, 430 struct CXUnsavedFile **unsaved_files, 431 int *num_unsaved_files) { 432 int i; 433 int arg; 434 int prefix_len = strlen("-remap-file="); 435 *unsaved_files = 0; 436 *num_unsaved_files = 0; 437 438 /* Count the number of remapped files. */ 439 for (arg = start_arg; arg < argc; ++arg) { 440 if (strncmp(argv[arg], "-remap-file=", prefix_len)) 441 break; 442 443 ++*num_unsaved_files; 444 } 445 446 if (*num_unsaved_files == 0) 447 return 0; 448 449 *unsaved_files 450 = (struct CXUnsavedFile *)malloc(sizeof(struct CXUnsavedFile) * 451 *num_unsaved_files); 452 for (arg = start_arg, i = 0; i != *num_unsaved_files; ++i, ++arg) { 453 struct CXUnsavedFile *unsaved = *unsaved_files + i; 454 const char *arg_string = argv[arg] + prefix_len; 455 int filename_len; 456 char *filename; 457 char *contents; 458 FILE *to_file; 459 const char *semi = strchr(arg_string, ';'); 460 if (!semi) { 461 fprintf(stderr, 462 "error: -remap-file=from;to argument is missing semicolon\n"); 463 free_remapped_files(*unsaved_files, i); 464 *unsaved_files = 0; 465 *num_unsaved_files = 0; 466 return -1; 467 } 468 469 /* Open the file that we're remapping to. */ 470 to_file = fopen(semi + 1, "r"); 471 if (!to_file) { 472 fprintf(stderr, "error: cannot open file %s that we are remapping to\n", 473 semi + 1); 474 free_remapped_files(*unsaved_files, i); 475 *unsaved_files = 0; 476 *num_unsaved_files = 0; 477 return -1; 478 } 479 480 /* Determine the length of the file we're remapping to. */ 481 fseek(to_file, 0, SEEK_END); 482 unsaved->Length = ftell(to_file); 483 fseek(to_file, 0, SEEK_SET); 484 485 /* Read the contents of the file we're remapping to. */ 486 contents = (char *)malloc(unsaved->Length + 1); 487 if (fread(contents, 1, unsaved->Length, to_file) != unsaved->Length) { 488 fprintf(stderr, "error: unexpected %s reading 'to' file %s\n", 489 (feof(to_file) ? "EOF" : "error"), semi + 1); 490 fclose(to_file); 491 free_remapped_files(*unsaved_files, i); 492 *unsaved_files = 0; 493 *num_unsaved_files = 0; 494 return -1; 495 } 496 contents[unsaved->Length] = 0; 497 unsaved->Contents = contents; 498 499 /* Close the file. */ 500 fclose(to_file); 501 502 /* Copy the file name that we're remapping from. */ 503 filename_len = semi - arg_string; 504 filename = (char *)malloc(filename_len + 1); 505 memcpy(filename, arg_string, filename_len); 506 filename[filename_len] = 0; 507 unsaved->Filename = filename; 508 } 509 510 return 0; 511} 512 513int perform_code_completion(int argc, const char **argv) { 514 const char *input = argv[1]; 515 char *filename = 0; 516 unsigned line; 517 unsigned column; 518 CXIndex CIdx; 519 int errorCode; 520 struct CXUnsavedFile *unsaved_files = 0; 521 int num_unsaved_files = 0; 522 CXCodeCompleteResults *results = 0; 523 524 input += strlen("-code-completion-at="); 525 if ((errorCode = parse_file_line_column(input, &filename, &line, &column))) 526 return errorCode; 527 528 if (parse_remapped_files(argc, argv, 2, &unsaved_files, &num_unsaved_files)) 529 return -1; 530 531 CIdx = clang_createIndex(0, 0); 532 results = clang_codeComplete(CIdx, 533 argv[argc - 1], argc - num_unsaved_files - 3, 534 argv + num_unsaved_files + 2, 535 num_unsaved_files, unsaved_files, 536 filename, line, column); 537 if (results) { 538 unsigned i, n = results->NumResults; 539 for (i = 0; i != n; ++i) 540 print_completion_result(results->Results + i, stdout); 541 clang_disposeCodeCompleteResults(results); 542 } 543 544 clang_disposeIndex(CIdx); 545 free(filename); 546 547 free_remapped_files(unsaved_files, num_unsaved_files); 548 549 return 0; 550} 551 552/******************************************************************************/ 553/* Command line processing. */ 554/******************************************************************************/ 555 556static void print_usage(void) { 557 fprintf(stderr, 558 "usage: c-index-test -code-completion-at=<site> <compiler arguments>\n" 559 " c-index-test -test-file-scan <AST file> <source file> " 560 "[FileCheck prefix]\n" 561 " c-index-test -test-load-tu <AST file> <symbol filter> " 562 "[FileCheck prefix]\n" 563 " c-index-test -test-load-source <symbol filter> {<args>}*\n\n" 564 " <symbol filter> options for -test-load-tu and -test-load-source:\n%s", 565 " all - load all symbols, including those from PCH\n" 566 " local - load all symbols except those in PCH\n" 567 " category - only load ObjC categories (non-PCH)\n" 568 " interface - only load ObjC interfaces (non-PCH)\n" 569 " protocol - only load ObjC protocols (non-PCH)\n" 570 " function - only load functions (non-PCH)\n" 571 " typedef - only load typdefs (non-PCH)\n" 572 " scan-function - scan function bodies (non-PCH)\n\n"); 573} 574 575int main(int argc, const char **argv) { 576 if (argc > 2 && strstr(argv[1], "-code-completion-at=") == argv[1]) 577 return perform_code_completion(argc, argv); 578 if (argc >= 4 && strcmp(argv[1], "-test-load-tu") == 0) 579 return perform_test_load_tu(argv[2], argv[3], 580 argc >= 5 ? argv[4] : 0); 581 if (argc >= 4 && strcmp(argv[1], "-test-load-source") == 0) 582 return perform_test_load_source(argc - 3, argv + 3, argv[2]); 583 if (argc >= 4 && strcmp(argv[1], "-test-file-scan") == 0) 584 return perform_file_scan(argv[2], argv[3], 585 argc >= 5 ? argv[4] : 0); 586 587 print_usage(); 588 return 1; 589} 590