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