summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/cpp11-migrate/tool/Cpp11Migrate.cpp
blob: e75a4754881f6761b51809f83c0e429d19dce4b4 (plain)
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
//===-- cpp11-migrate/Cpp11Migrate.cpp - Main file C++11 migration tool ---===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements the C++11 feature migration tool main function
/// and transformation framework.
///
/// See user documentation for usage instructions.
///
//===----------------------------------------------------------------------===//

#include "Core/FileOverrides.h"
#include "Core/PerfSupport.h"
#include "Core/SyntaxCheck.h"
#include "Core/Transform.h"
#include "Core/Transforms.h"
#include "Core/Reformatting.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang-replace/Tooling/ApplyReplacements.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Signals.h"

namespace cl = llvm::cl;
using namespace clang;
using namespace clang::tooling;

TransformOptions GlobalOptions;

static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
static cl::opt<std::string> BuildPath(
    "p", cl::desc("Build Path"), cl::Optional);
static cl::list<std::string> SourcePaths(
    cl::Positional, cl::desc("<source0> [... <sourceN>]"), cl::OneOrMore);
static cl::extrahelp MoreHelp(
    "EXAMPLES:\n\n"
    "Apply all transforms on a given file, no compilation database:\n\n"
    "  cpp11-migrate path/to/file.cpp -- -Ipath/to/include/\n"
    "\n"
    "Convert for loops to the new ranged-based for loops on all files in a "
    "subtree\nand reformat the code automatically using the LLVM style:\n\n"
    "  find path/in/subtree -name '*.cpp' -exec \\\n"
    "    cpp11-migrate -p build/path -format-style=LLVM -loop-convert {} ';'\n"
    "\n"
    "Make use of both nullptr and the override specifier, using git ls-files:\n"
    "\n"
    "  git ls-files '*.cpp' | xargs -I{} cpp11-migrate -p build/path \\\n"
    "    -use-nullptr -add-override -override-macros {}\n"
    "\n"
    "Apply all transforms supported by both clang >= 3.0 and gcc >= 4.7:\n\n"
    "  cpp11-migrate -for-compilers=clang-3.0,gcc-4.7 foo.cpp -- -Ibar\n");

static cl::opt<RiskLevel, /*ExternalStorage=*/true> MaxRiskLevel(
    "risk", cl::desc("Select a maximum risk level:"),
    cl::values(clEnumValN(RL_Safe, "safe", "Only safe transformations"),
               clEnumValN(RL_Reasonable, "reasonable",
                          "Enable transformations that might change "
                          "semantics (default)"),
               clEnumValN(RL_Risky, "risky",
                          "Enable transformations that are likely to "
                          "change semantics"),
               clEnumValEnd),
    cl::location(GlobalOptions.MaxRiskLevel),
    cl::init(RL_Reasonable));

static cl::opt<bool> FinalSyntaxCheck(
    "final-syntax-check",
    cl::desc("Check for correct syntax after applying transformations"),
    cl::init(false));

static cl::opt<std::string> FormatStyleOpt(
    "format-style",
    cl::desc("Coding style to use on the replacements, either a builtin style\n"
             "or a YAML config file (see: clang-format -dump-config).\n"
             "Currently supports 4 builtins style:\n"
             "  LLVM, Google, Chromium, Mozilla.\n"),
    cl::value_desc("string"));

static cl::opt<bool>
SummaryMode("summary", cl::desc("Print transform summary"),
            cl::init(false));

const char NoTiming[] = "no_timing";
static cl::opt<std::string> TimingDirectoryName(
    "perf", cl::desc("Capture performance data and output to specified "
                     "directory. Default: ./migrate_perf"),
    cl::init(NoTiming), cl::ValueOptional, cl::value_desc("directory name"));

// TODO: Remove cl::Hidden when functionality for acknowledging include/exclude
// options are implemented in the tool.
static cl::opt<std::string>
IncludePaths("include", cl::Hidden,
             cl::desc("Comma seperated list of paths to consider to be "
                      "transformed"));
static cl::opt<std::string>
ExcludePaths("exclude", cl::Hidden,
             cl::desc("Comma seperated list of paths that can not "
                      "be transformed"));
static cl::opt<std::string>
IncludeFromFile("include-from", cl::Hidden, cl::value_desc("filename"),
                cl::desc("File containing a list of paths to consider to "
                         "be transformed"));
static cl::opt<std::string>
ExcludeFromFile("exclude-from", cl::Hidden, cl::value_desc("filename"),
                cl::desc("File containing a list of paths that can not be "
                         "transforms"));

// Header modifications will probably be always on eventually. For now, they
// need to be explicitly enabled.
static cl::opt<bool, /*ExternalStorage=*/true> EnableHeaderModifications(
    "headers",
    cl::Hidden, // Experimental feature for now.
    cl::desc("Enable modifications to headers"),
    cl::location(GlobalOptions.EnableHeaderModifications),
    cl::init(false));

static cl::opt<bool>
SerializeReplacements("serialize-replacements",
                      cl::Hidden, // Associated with -headers
                      cl::desc("Serialize translation unit replacements to "
                               "disk instead of changing files."),
                      cl::init(false));

cl::opt<std::string> SupportedCompilers(
    "for-compilers", cl::value_desc("string"),
    cl::desc("Select transforms targeting the intersection of\n"
             "language features supported by the given compilers.\n"
             "Takes a comma-seperated list of <compiler>-<version>.\n"
             "\t<compiler> can be any of: clang, gcc, icc, msvc\n"
             "\t<version> is <major>[.<minor>]\n"));

/// \brief Extract the minimum compiler versions as requested on the command
/// line by the switch \c -for-compilers.
///
/// \param ProgName The name of the program, \c argv[0], used to print errors.
/// \param Error If an error occur while parsing the versions this parameter is
/// set to \c true, otherwise it will be left untouched.
static CompilerVersions handleSupportedCompilers(const char *ProgName,
                                                 bool &Error) {
  if (SupportedCompilers.getNumOccurrences() == 0)
    return CompilerVersions();
  CompilerVersions RequiredVersions;
  llvm::SmallVector<llvm::StringRef, 4> Compilers;

  llvm::StringRef(SupportedCompilers).split(Compilers, ",");

  for (llvm::SmallVectorImpl<llvm::StringRef>::iterator I = Compilers.begin(),
                                                        E = Compilers.end();
       I != E; ++I) {
    llvm::StringRef Compiler, VersionStr;
    llvm::tie(Compiler, VersionStr) = I->split('-');
    Version *V = llvm::StringSwitch<Version *>(Compiler)
        .Case("clang", &RequiredVersions.Clang)
        .Case("gcc", &RequiredVersions.Gcc).Case("icc", &RequiredVersions.Icc)
        .Case("msvc", &RequiredVersions.Msvc).Default(NULL);

    if (V == NULL) {
      llvm::errs() << ProgName << ": " << Compiler
                   << ": unsupported platform\n";
      Error = true;
      continue;
    }
    if (VersionStr.empty()) {
      llvm::errs() << ProgName << ": " << *I
                   << ": missing version number in platform\n";
      Error = true;
      continue;
    }

    Version Version = Version::getFromString(VersionStr);
    if (Version.isNull()) {
      llvm::errs()
          << ProgName << ": " << *I
          << ": invalid version, please use \"<major>[.<minor>]\" instead of \""
          << VersionStr << "\"\n";
      Error = true;
      continue;
    }
    // support the lowest version given
    if (V->isNull() || Version < *V)
      *V = Version;
  }
  return RequiredVersions;
}

/// \brief Creates the Reformatter if the format style option is provided,
/// return a null pointer otherwise.
///
/// \param ProgName The name of the program, \c argv[0], used to print errors.
/// \param Error If the \c -format-style is provided but with wrong parameters
/// this is parameter is set to \c true, left untouched otherwise. An error
/// message is printed with an explanation.
static Reformatter *handleFormatStyle(const char *ProgName, bool &Error) {
  if (FormatStyleOpt.getNumOccurrences() > 0) {
    format::FormatStyle Style;
    if (!format::getPredefinedStyle(FormatStyleOpt, &Style)) {
      llvm::StringRef ConfigFilePath = FormatStyleOpt;
      llvm::OwningPtr<llvm::MemoryBuffer> Text;
      llvm::error_code ec;

      ec = llvm::MemoryBuffer::getFile(ConfigFilePath, Text);
      if (!ec)
        ec = parseConfiguration(Text->getBuffer(), &Style);

      if (ec) {
        llvm::errs() << ProgName << ": invalid format style " << FormatStyleOpt
                     << ": " << ec.message() << "\n";
        Error = true;
        return 0;
      }
    }

    // force mode to C++11
    Style.Standard = clang::format::FormatStyle::LS_Cpp11;
    return new Reformatter(Style);
  }
  return 0;
}

/// \brief Use \c ChangesReformatter to reformat all changed regions of all
/// files stored in \c Overrides and write the result to disk.
///
/// \returns \li true if reformatting replacements were successfully applied
///              without conflicts and all files were successfully written to
///              disk.
///          \li false if reformatting could not be successfully applied or
///              if at least one file failed to write to disk.
bool reformat(Reformatter &ChangesReformatter, const FileOverrides &Overrides,
              DiagnosticsEngine &Diagnostics) {
  FileManager Files((FileSystemOptions()));
  SourceManager SM(Diagnostics, Files);

  replace::TUReplacements AllReplacements(1);
  ChangesReformatter.reformatChanges(Overrides, SM,
                                     AllReplacements.front().Replacements);

  replace::FileToReplacementsMap GroupedReplacements;
  if (!replace::mergeAndDeduplicate(AllReplacements, GroupedReplacements, SM)) {
    llvm::errs() << "Warning: Reformatting produced conflicts.\n";
    return false;
  }

  Rewriter DestRewriter(SM, LangOptions());
  if (!replace::applyReplacements(GroupedReplacements, DestRewriter)) {
    llvm::errs() << "Warning: Failed to apply reformatting conflicts!\n";
    return false;
  }

  return replace::writeFiles(DestRewriter);
}

bool serializeReplacements(const replace::TUReplacements &Replacements) {
  bool Errors = false;
  for (replace::TUReplacements::const_iterator I = Replacements.begin(),
                                               E = Replacements.end();
       I != E; ++I) {
    llvm::SmallString<128> ReplacementsFileName;
    llvm::SmallString<64> Error;
    bool Result = generateReplacementsFileName(I->MainSourceFile,
                                               ReplacementsFileName, Error);
    if (!Result) {
      llvm::errs() << "Failed to generate replacements filename:" << Error
                   << "\n";
      Errors = true;
      continue;
    }

    std::string ErrorInfo;
    llvm::raw_fd_ostream ReplacementsFile(ReplacementsFileName.c_str(),
                                          ErrorInfo, llvm::sys::fs::F_Binary);
    if (!ErrorInfo.empty()) {
      llvm::errs() << "Error opening file: " << ErrorInfo << "\n";
      Errors = true;
      continue;
    }
    llvm::yaml::Output YAML(ReplacementsFile);
    YAML << const_cast<TranslationUnitReplacements &>(*I);
  }
  return !Errors;
}

int main(int argc, const char **argv) {
  llvm::sys::PrintStackTraceOnErrorSignal();
  Transforms TransformManager;

  TransformManager.registerTransforms();

  // Parse options and generate compilations.
  OwningPtr<CompilationDatabase> Compilations(
      FixedCompilationDatabase::loadFromCommandLine(argc, argv));
  cl::ParseCommandLineOptions(argc, argv);

  if (!Compilations) {
    std::string ErrorMessage;
    if (BuildPath.getNumOccurrences() > 0) {
      Compilations.reset(CompilationDatabase::autoDetectFromDirectory(
          BuildPath, ErrorMessage));
    } else {
      Compilations.reset(CompilationDatabase::autoDetectFromSource(
          SourcePaths[0], ErrorMessage));
      // If no compilation database can be detected from source then we create
      // a new FixedCompilationDatabase with c++11 support.
      if (!Compilations) {
        std::string CommandLine[] = {"-std=c++11"};
        Compilations.reset(new FixedCompilationDatabase(".", CommandLine));
      }
    }
    if (!Compilations)
      llvm::report_fatal_error(ErrorMessage);
  }

  // Since ExecutionTimeDirectoryName could be an empty string we compare
  // against the default value when the command line option is not specified.
  GlobalOptions.EnableTiming = (TimingDirectoryName != NoTiming);

  // Check the reformatting style option
  bool CmdSwitchError = false;
  llvm::OwningPtr<Reformatter> ChangesReformatter(
      handleFormatStyle(argv[0], CmdSwitchError));

  CompilerVersions RequiredVersions =
      handleSupportedCompilers(argv[0], CmdSwitchError);
  if (CmdSwitchError)
    return 1;

  // Populate the ModifiableHeaders structure if header modifications are
  // enabled.
  if (GlobalOptions.EnableHeaderModifications) {
    GlobalOptions.ModifiableHeaders
        .readListFromString(IncludePaths, ExcludePaths);
    GlobalOptions.ModifiableHeaders
        .readListFromFile(IncludeFromFile, ExcludeFromFile);
  }

  TransformManager.createSelectedTransforms(GlobalOptions, RequiredVersions);

  llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts(
      new DiagnosticOptions());
  DiagnosticsEngine Diagnostics(
      llvm::IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
      DiagOpts.getPtr());

  // FIXME: Make this DiagnosticsEngine available to all Transforms probably via
  // GlobalOptions.

  if (TransformManager.begin() == TransformManager.end()) {
    if (SupportedCompilers.empty())
      llvm::errs() << argv[0] << ": no selected transforms\n";
    else
      llvm::errs() << argv[0]
                   << ": no transforms available for specified compilers\n";
    return 1;
  }

  // If SerializeReplacements is requested, then change reformatting must be
  // turned off and only one transform should be requested. Reformatting is
  // basically another transform so even if there's only one other transform,
  // the reformatting pass would make two.
  if (SerializeReplacements &&
      (std::distance(TransformManager.begin(), TransformManager.end()) > 1 ||
       ChangesReformatter)) {
    llvm::errs() << "Serialization of replacements requested for multiple "
                    "transforms.\nChanges from only one transform can be "
                    "serialized.\n";
    return 1;
  }

  SourcePerfData PerfData;
  FileOverrides FileStates;

  for (Transforms::const_iterator I = TransformManager.begin(),
                                  E = TransformManager.end();
       I != E; ++I) {
    Transform *T = *I;

    if (T->apply(FileStates, *Compilations, SourcePaths) != 0) {
      // FIXME: Improve ClangTool to not abort if just one file fails.
      return 1;
    }

    if (GlobalOptions.EnableTiming)
      collectSourcePerfData(*T, PerfData);

    if (SummaryMode) {
      llvm::outs() << "Transform: " << T->getName()
                   << " - Accepted: " << T->getAcceptedChanges();
      if (T->getChangesNotMade()) {
        llvm::outs() << " - Rejected: " << T->getRejectedChanges()
                     << " - Deferred: " << T->getDeferredChanges();
      }
      llvm::outs() << "\n";
    }

    // Collect all TranslationUnitReplacements generated from the translation
    // units the transform worked on and store them in AllReplacements.
    replace::TUReplacements AllReplacements;
    const TUReplacementsMap &ReplacementsMap = T->getAllReplacements();
    const TranslationUnitReplacements &(
        TUReplacementsMap::value_type::*getValue)() const =
        &TUReplacementsMap::value_type::getValue;
    std::transform(ReplacementsMap.begin(), ReplacementsMap.end(),
                   std::back_inserter(AllReplacements),
                   std::mem_fun_ref(getValue));

    if (SerializeReplacements)
      serializeReplacements(AllReplacements);

    FileManager Files((FileSystemOptions()));
    SourceManager SM(Diagnostics, Files);

    // Make sure SourceManager is updated to have the same initial state as the
    // transforms.
    FileStates.applyOverrides(SM);

    replace::FileToReplacementsMap GroupedReplacements;
    if (!replace::mergeAndDeduplicate(AllReplacements, GroupedReplacements,
                                      SM)) {
      llvm::outs() << "Transform " << T->getName()
                   << " resulted in conflicts. Discarding all "
                   << "replacements.\n";
      continue;
    }

    // Apply replacements and update FileStates with new state.
    Rewriter DestRewriter(SM, LangOptions());
    if (!replace::applyReplacements(GroupedReplacements, DestRewriter)) {
      llvm::outs() << "Some replacements failed to apply. Discarding "
                      "all replacements.\n";
      continue;
    }

    // Update contents of files in memory to serve as initial state for next
    // transform.
    FileStates.updateState(DestRewriter);

    // Update changed ranges for reformatting
    if (ChangesReformatter)
      FileStates.adjustChangedRanges(GroupedReplacements);
  }

  // Skip writing final file states to disk if we were asked to serialize
  // replacements. Otherwise reformat changes if reformatting is enabled. If
  // not enabled or if reformatting fails write un-formated changes to disk
  // instead. reformat() takes care of writing successfully formatted changes.
  if (!SerializeReplacements &&
      (!ChangesReformatter ||
       !reformat(*ChangesReformatter, FileStates, Diagnostics)))
    FileStates.writeToDisk(Diagnostics);

  if (FinalSyntaxCheck)
    if (!doSyntaxCheck(*Compilations, SourcePaths, FileStates))
      return 1;

  // Report execution times.
  if (GlobalOptions.EnableTiming && !PerfData.empty()) {
    std::string DirectoryName = TimingDirectoryName;
    // Use default directory name.
    if (DirectoryName.empty())
      DirectoryName = "./migrate_perf";
    writePerfDataJSON(DirectoryName, PerfData);
  }

  return 0;
}

// These anchors are used to force the linker to link the transforms
extern volatile int AddOverrideTransformAnchorSource;
extern volatile int LoopConvertTransformAnchorSource;
extern volatile int PassByValueTransformAnchorSource;
extern volatile int ReplaceAutoPtrTransformAnchorSource;
extern volatile int UseAutoTransformAnchorSource;
extern volatile int UseNullptrTransformAnchorSource;

static int TransformsAnchorsDestination[] = {
  AddOverrideTransformAnchorSource,
  LoopConvertTransformAnchorSource,
  PassByValueTransformAnchorSource,
  ReplaceAutoPtrTransformAnchorSource,
  UseAutoTransformAnchorSource,
  UseNullptrTransformAnchorSource
};
OpenPOWER on IntegriCloud