| //===-- 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-apply-replacements/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 |
| }; |