summaryrefslogtreecommitdiffstats
path: root/iwyu_globals.cc
blob: 8ba6c05134180cf5ebf831bfac4482d234975265 (plain) (blame)
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
//===--- iwyu_globals.cc - global variables for include-what-you-use ------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "iwyu_globals.h"

#include <algorithm>                    // for sort, make_pair
#include <cstdio>                       // for printf
#include <cstdlib>                      // for atoi, exit, getenv
#include <map>                          // for map
#include <set>                          // for set
#include <string>                       // for string, operator<, etc
#include <utility>                      // for make_pair, pair

#include "iwyu_cache.h"
#include "iwyu_include_picker.h"
#include "iwyu_getopt.h"
#include "iwyu_lexer_utils.h"
#include "iwyu_location_util.h"
#include "iwyu_path_util.h"
#include "iwyu_port.h"  // for CHECK_, etc
#include "iwyu_stl_util.h"
#include "iwyu_string_util.h"
#include "iwyu_verrs.h"
#include "iwyu_version.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/Version.h"
#include "clang/Lex/HeaderSearch.h"

using clang::DirectoryEntry;
using std::make_pair;
using std::map;
using std::string;
using std::vector;

namespace include_what_you_use {

static CommandlineFlags* commandline_flags = nullptr;
static clang::SourceManager* source_manager = nullptr;
static IncludePicker* include_picker = nullptr;
static const clang::LangOptions default_lang_options;
static const clang::PrintingPolicy default_print_policy(default_lang_options);
static SourceManagerCharacterDataGetter* data_getter = nullptr;
static FullUseCache* function_calls_full_use_cache = nullptr;
static FullUseCache* class_members_full_use_cache = nullptr;
static int ParseIwyuCommandlineFlags(int argc, char** argv);
static int ParseInterceptedCommandlineFlags(int argc, char** argv);

static void PrintHelp(const char* extra_msg) {
  printf("USAGE: include-what-you-use [-Xiwyu --iwyu_opt]... <clang opts>"
         " <source file>\n"
         "Here are the <iwyu_opts> you can specify (e.g. -Xiwyu --verbose=3):\n"
         "   --check_also=<glob>: tells iwyu to print iwyu-violation info\n"
         "        for all files matching the given glob pattern (in addition\n"
         "        to the default of reporting for the input .cc file and its\n"
         "        associated .h files).  This flag may be specified multiple\n"
         "        times to specify multiple glob patterns.\n"
         "   --keep=<glob>: tells iwyu to always keep these includes.\n"
         "        This flag may be specified multiple times to specify\n"
         "        multiple glob patterns.\n"
         "   --mapping_file=<filename>: gives iwyu a mapping file.\n"
         "   --no_default_mappings: do not add iwyu's default mappings.\n"
         "   --pch_in_code: mark the first include in a translation unit as a\n"
         "        precompiled header.  Use --pch_in_code to prevent IWYU from\n"
         "        removing necessary PCH includes.  Though Clang forces PCHs\n"
         "        to be listed as prefix headers, the PCH-in-code pattern can\n"
         "        be used with GCC and is standard practice on MSVC\n"
         "        (e.g. stdafx.h).\n"
         "   --prefix_header_includes=<value>: tells iwyu what to do with\n"
         "        in-source includes and forward declarations involving\n"
         "        prefix headers.  Prefix header is a file included via\n"
         "        command-line option -include.  If prefix header makes\n"
         "        include or forward declaration obsolete, presence of such\n"
         "        include can be controlled with the following values\n"
         "          add:    new lines are added\n"
         "          keep:   new lines aren't added, existing are kept intact\n"
         "          remove: new lines aren't added, existing are removed\n"
         "        Default value is 'add'.\n"
         "   --transitive_includes_only: do not suggest that a file add\n"
         "        foo.h unless foo.h is already visible in the file's\n"
         "        transitive includes.\n"
         "   --max_line_length: maximum line length for includes.\n"
         "        Note that this only affects comments and alignment thereof,\n"
         "        the maximum line length can still be exceeded with long\n"
         "        file names (default: 80).\n"
         "   --no_comments: do not add 'why' comments.\n"
         "   --update_comments: always add 'why' comments, even if no\n"
         "        #include lines need to be added or removed.\n"
         "   --no_fwd_decls: do not use forward declarations.\n"
         "   --verbose=<level>: the higher the level, the more output.\n"
         "   --quoted_includes_first: when sorting includes, place quoted\n"
         "        ones first.\n"
         "   --cxx17ns: suggests the more concise syntax introduced in C++17\n"
         "   --error[=N]: exit with N (default: 1) for iwyu violations\n"
         "   --error_always[=N]: always exit with N (default: 1) (for use\n"
         "        with 'make -k')\n"
         "\n"
         "In addition to IWYU-specific options you can specify the following\n"
         "options without -Xiwyu prefix:\n"
         "   --help: prints this help and exits.\n"
         "   --version: prints version and exits.\n");
  if (extra_msg)
    printf("\n%s\n\n", extra_msg);
}

static void PrintVersion() {
  llvm::outs() << "include-what-you-use " << IWYU_VERSION_STRING;
  // IWYU_GIT_REV should be provided by build system.
  string iwyu_rev = IWYU_GIT_REV;
  if (!iwyu_rev.empty()) {
    llvm::outs() << " (git:" << iwyu_rev << ")";
  }
  llvm::outs() << " based on " << clang::getClangFullVersion()
               << "\n";
}

static bool ParseIntegerOptarg(const char* optarg, int* res) {
  char* endptr = nullptr;
  long val = strtol(optarg, &endptr, 10);
  if (!endptr || endptr == optarg)
    return false;

  if (*endptr != '\0')
    return false;

  if (val > INT_MAX || val < INT_MIN)
    return false;

  *res = (int)val;
  return true;
}

OptionsParser::OptionsParser(int argc, char** argv) {
  // Separate out iwyu-specific, intercepted, and clang flags.  iwyu-specific
  // flags are "-Xiwyu <iwyu_flag>", intercepted flags are usual clang flags
  // like --version, --help, which we intercept to  provide custom handling.
  char** iwyu_argv = new char*[argc + 1];
  iwyu_argv[0] = argv[0];
  int iwyu_argc = 1;
  char** intercepted_argv = new char*[argc + 1];
  intercepted_argv[0] = argv[0];
  int intercepted_argc = 1;
  clang_argv_ = new const char*[argc + 1];
  clang_argv_[0] = argv[0];
  clang_argc_ = 1;
  for (int i = 1; i < argc; ++i) {
    if (i < argc - 1 && strcmp(argv[i], "-Xiwyu") == 0)
      iwyu_argv[iwyu_argc++] = argv[++i];   // the word after -Xiwyu
    else if (strcmp(argv[i], "--help") == 0)
      intercepted_argv[intercepted_argc++] = argv[i];     // intercept --help
    else if (strcmp(argv[i], "--version") == 0)
      intercepted_argv[intercepted_argc++] = argv[i];     // intercept --version
    else
      clang_argv_[clang_argc_++] = argv[i];
  }
  // argv should be nullptr-terminated
  iwyu_argv[iwyu_argc] = nullptr;
  intercepted_argv[intercepted_argc] = nullptr;
  clang_argv_[clang_argc_] = nullptr;

  ParseInterceptedCommandlineFlags(intercepted_argc, intercepted_argv);
  ParseIwyuCommandlineFlags(iwyu_argc, iwyu_argv);

  delete [] iwyu_argv;
  delete [] intercepted_argv;
}

OptionsParser::~OptionsParser() {
  delete [] clang_argv_;
}

CommandlineFlags::CommandlineFlags()
    : transitive_includes_only(false),
      verbose(getenv("IWYU_VERBOSE") ? atoi(getenv("IWYU_VERBOSE")) : 1),
      no_default_mappings(false),
      max_line_length(80),
      prefix_header_include_policy(CommandlineFlags::kAdd),
      pch_in_code(false),
      no_comments(false),
      update_comments(false),
      no_fwd_decls(false),
      quoted_includes_first(false),
      cxx17ns(false),
      exit_code_error(EXIT_SUCCESS),
      exit_code_always(EXIT_SUCCESS) {
  // Always keep Qt .moc includes; its moc compiler does its own IWYU analysis.
  keep.emplace("*.moc");
}

int CommandlineFlags::ParseArgv(int argc, char** argv) {
  static const struct option longopts[] = {
    {"check_also", required_argument, nullptr, 'c'},  // can be specified >once
    {"keep", required_argument, nullptr, 'k'},  // can be specified >once
    {"transitive_includes_only", no_argument, nullptr, 't'},
    {"verbose", required_argument, nullptr, 'v'},
    {"mapping_file", required_argument, nullptr, 'm'},
    {"no_default_mappings", no_argument, nullptr, 'n'},
    {"prefix_header_includes", required_argument, nullptr, 'x'},
    {"pch_in_code", no_argument, nullptr, 'h'},
    {"max_line_length", required_argument, nullptr, 'l'},
    {"no_comments", no_argument, nullptr, 'o'},
    {"update_comments", no_argument, nullptr, 'u'},
    {"no_fwd_decls", no_argument, nullptr, 'f'},
    {"quoted_includes_first", no_argument, nullptr, 'q' },
    {"cxx17ns", no_argument, nullptr, 'C'},
    {"error", optional_argument, nullptr, 'e'},
    {"error_always", optional_argument, nullptr, 'a'},
    {nullptr, 0, nullptr, 0}
  };
  static const char shortopts[] = "v:c:m:n";
  while (true) {
    switch (getopt_long(argc, argv, shortopts, longopts, nullptr)) {
      case 'c': AddGlobToReportIWYUViolationsFor(optarg); break;
      case 'k': AddGlobToKeepIncludes(optarg); break;
      case 't': transitive_includes_only = true; break;
      case 'v': verbose = atoi(optarg); break;
      case 'm': mapping_files.push_back(optarg); break;
      case 'n': no_default_mappings = true; break;
      case 'o': no_comments = true; break;
      case 'u': update_comments = true; break;
      case 'f': no_fwd_decls = true; break;
      case 'x':
        if (strcmp(optarg, "add") == 0) {
          prefix_header_include_policy = CommandlineFlags::kAdd;
        } else if (strcmp(optarg, "keep") == 0) {
          prefix_header_include_policy = CommandlineFlags::kKeep;
        } else if (strcmp(optarg, "remove") == 0) {
          prefix_header_include_policy = CommandlineFlags::kRemove;
        } else {
          PrintHelp("FATAL ERROR: unknown --prefix_header_includes value.");
          exit(EXIT_FAILURE);
        }
        break;
      case 'h': pch_in_code = true; break;
      case 'l':
        max_line_length = atoi(optarg);
        CHECK_((max_line_length >= 0) && "Max line length must be positive");
        break;
      case 'q': quoted_includes_first = true; break;
      case 'C': cxx17ns = true; break;
      case 'e':
        if (!optarg) {
          exit_code_error = EXIT_FAILURE;
        } else if (!ParseIntegerOptarg(optarg, &exit_code_error)) {
          PrintHelp("FATAL ERROR: --error argument must be valid integer.");
          exit(EXIT_FAILURE);
        }
        break;
      case 'a':
        if (!optarg) {
          exit_code_always = EXIT_FAILURE;
        } else if (!ParseIntegerOptarg(optarg, &exit_code_always)) {
          PrintHelp(
              "FATAL ERROR: --error_always argument must be valid "
              "integer.");
          exit(EXIT_FAILURE);
        }
        break;
      case -1: return optind;   // means 'no more input'
      default:
        PrintHelp("FATAL ERROR: unknown flag.");
        exit(EXIT_FAILURE);
        break;
    }
  }

  CHECK_UNREACHABLE_("All switches should be handled above");
}

// Though option -v prints version too, it isn't intercepted because it also
// provides other functionality like printing clang invocation, header search
// paths.
// TODO(vsapsai): provide IWYU version in Driver::PrintVersion when version
// callbacks are supported (see FIXME in Driver::PrintVersion).
static int ParseInterceptedCommandlineFlags(int argc, char** argv) {
  static const struct option longopts[] = {
    {"help", no_argument, nullptr, 'h'},
    {"version", no_argument, nullptr, 'v'},
    {nullptr, 0, nullptr, 0}
  };
  static const char shortopts[] = "";
  while (true) {
    switch (getopt_long(argc, argv, shortopts, longopts, nullptr)) {
      case 'h': PrintHelp(""); exit(EXIT_SUCCESS); break;
      case 'v': PrintVersion(); exit(EXIT_SUCCESS); break;
      case -1: return optind;   // means 'no more input'
      default:
        PrintHelp("FATAL ERROR: unknown flag.");
        exit(EXIT_FAILURE);
        break;
    }
  }
  return optind;  // unreachable
}

// Handles all iwyu-specific flags, like --verbose.  Returns the index into
// argv past all the iwyu commandline flags.
static int ParseIwyuCommandlineFlags(int argc, char** argv) {
  CHECK_(commandline_flags == nullptr && "Only parse commandline flags once");
  commandline_flags = new CommandlineFlags;
  const int retval = commandline_flags->ParseArgv(argc, argv);
  SetVerboseLevel(commandline_flags->verbose);

  VERRS(4) << "Setting verbose-level to " << commandline_flags->verbose << "\n";

  return retval;
}

// Make sure we put longer search-paths first, so iwyu will map
// /usr/include/c++/4.4/foo to <foo> rather than <c++/4.4/foo>.
static bool SortByDescendingLength(const HeaderSearchPath& left,
                                   const HeaderSearchPath& right) {
  return left.path.length() > right.path.length();
}

// Sorts them by descending length, does other kinds of cleanup.
static vector<HeaderSearchPath> NormalizeHeaderSearchPaths(
    const map<string, HeaderSearchPath::Type>& include_dirs_map) {
  vector<HeaderSearchPath> include_dirs;
  for (const auto& entry : include_dirs_map) {
    include_dirs.push_back(HeaderSearchPath(entry.first, entry.second));
  }

  sort(include_dirs.begin(), include_dirs.end(), &SortByDescendingLength);
  return include_dirs;
}

// Asks clang what the search-paths are for include files, normalizes
// them, and returns them in a vector.
static vector<HeaderSearchPath> ComputeHeaderSearchPaths(
    clang::HeaderSearch* header_search) {
  map<string, HeaderSearchPath::Type> search_path_map;
  for (auto it = header_search->system_dir_begin();
       it != header_search->system_dir_end(); ++it) {
    if (const DirectoryEntry* entry = it->getDir()) {
      const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName().str()));
      search_path_map[path] = HeaderSearchPath::kSystemPath;
    }
  }
  for (auto it = header_search->search_dir_begin();
       it != header_search->search_dir_end(); ++it) {
    if (const DirectoryEntry* entry = it->getDir()) {
      // search_dir_begin()/end() includes both system and user paths.
      // If it's a system path, it's already in the map, so everything
      // new is a user path.  The insert only 'takes' for new entries.
      const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName().str()));
      search_path_map.insert(make_pair(path, HeaderSearchPath::kUserPath));
    }
  }
  return NormalizeHeaderSearchPaths(search_path_map);
}

void InitGlobals(clang::SourceManager* sm, clang::HeaderSearch* header_search) {
  CHECK_(sm && "InitGlobals() needs a non-nullptr SourceManager");
  source_manager = sm;
  data_getter = new SourceManagerCharacterDataGetter(*source_manager);
  vector<HeaderSearchPath> search_paths =
      ComputeHeaderSearchPaths(header_search);
  SetHeaderSearchPaths(search_paths);
  include_picker = new IncludePicker(GlobalFlags().no_default_mappings);
  function_calls_full_use_cache = new FullUseCache;
  class_members_full_use_cache = new FullUseCache;

  for (const HeaderSearchPath& entry : search_paths) {
    const char* path_type_name =
        (entry.path_type == HeaderSearchPath::kSystemPath ? "system" : "user");
    VERRS(6) << "Search path: " << entry.path << " (" << path_type_name
             << ")\n";
  }

  // Add mappings.
  for (const string& mapping_file : GlobalFlags().mapping_files) {
    include_picker->AddMappingsFromFile(mapping_file);
  }
}

const CommandlineFlags& GlobalFlags() {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  return *commandline_flags;
}

CommandlineFlags* MutableGlobalFlagsForTesting() {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  return commandline_flags;
}

clang::SourceManager* GlobalSourceManager() {
  CHECK_(source_manager && "Must call InitGlobals() before calling this");
  return source_manager;
}

const IncludePicker& GlobalIncludePicker() {
  CHECK_(include_picker && "Must call InitGlobals() before calling this");
  return *include_picker;
}

IncludePicker* MutableGlobalIncludePicker() {
  CHECK_(include_picker && "Must call InitGlobals() before calling this");
  return include_picker;
}

const clang::PrintingPolicy& DefaultPrintPolicy() {
  return default_print_policy;
}

const SourceManagerCharacterDataGetter& DefaultDataGetter() {
  CHECK_(data_getter && "Must call InitGlobals() before calling this");
  return *data_getter;
}

FullUseCache* FunctionCallsFullUseCache() {
  return function_calls_full_use_cache;
}

FullUseCache* ClassMembersFullUseCache() {
  return class_members_full_use_cache;
}

void AddGlobToReportIWYUViolationsFor(const string& glob) {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  commandline_flags->check_also.insert(NormalizeFilePath(glob));
}

bool ShouldReportIWYUViolationsFor(const clang::FileEntry* file) {
  const string filepath = GetFilePath(file);
  for (const string& glob : GlobalFlags().check_also)
    if (GlobMatchesPath(glob.c_str(), filepath.c_str()))
      return true;
  return false;
}

void AddGlobToKeepIncludes(const string& glob) {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  commandline_flags->keep.insert(NormalizeFilePath(glob));
}

bool ShouldKeepIncludeFor(const clang::FileEntry* file) {
  if (GlobalFlags().keep.empty())
    return false;
  const string filepath = GetFilePath(file);
  for (const string& glob : GlobalFlags().keep)
    if (GlobMatchesPath(glob.c_str(), filepath.c_str()))
      return true;
  return false;
}

void InitGlobalsAndFlagsForTesting() {
  CHECK_(commandline_flags == nullptr && "Only parse commandline flags once");
  CHECK_(include_picker == nullptr && "Only call InitGlobals[ForTesting] once");
  commandline_flags = new CommandlineFlags;
  source_manager = nullptr;
  data_getter = nullptr;
  include_picker = new IncludePicker(GlobalFlags().no_default_mappings);
  function_calls_full_use_cache = new FullUseCache;
  class_members_full_use_cache = new FullUseCache;

  // Use a reasonable default for the -I flags.
  map<string, HeaderSearchPath::Type> search_path_map;
  search_path_map["/usr/include/"] = HeaderSearchPath::kSystemPath;
  search_path_map["/usr/include/c++/4.3/"] = HeaderSearchPath::kSystemPath;
  search_path_map["/usr/include/c++/4.2/"] = HeaderSearchPath::kSystemPath;
  search_path_map["./"] = HeaderSearchPath::kUserPath;
  search_path_map["/usr/src/linux-headers-2.6.24-gg23/include/"] =
      HeaderSearchPath::kSystemPath;

  SetHeaderSearchPaths(NormalizeHeaderSearchPaths(search_path_map));
}

}  // namespace include_what_you_use