//===-- ClangModernize.cpp - Main file for Clang modernization 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/PerfSupport.h" #include "Core/ReplacementHandling.h" #include "Core/Transform.h" #include "Core/Transforms.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/Format/Format.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Path.h" #include "llvm/Support/Signals.h" namespace cl = llvm::cl; using namespace clang; using namespace clang::tooling; TransformOptions GlobalOptions; // All options must belong to locally defined categories for them to get shown // by -help. We explicitly hide everything else (except -help and -version). static cl::OptionCategory GeneralCategory("Modernizer Options"); static cl::OptionCategory FormattingCategory("Formatting Options"); static cl::OptionCategory IncludeExcludeCategory("Inclusion/Exclusion Options"); static cl::OptionCategory SerializeCategory("Serialization Options"); const cl::OptionCategory *VisibleCategories[] = { &GeneralCategory, &FormattingCategory, &IncludeExcludeCategory, &SerializeCategory, &TransformCategory, &TransformsOptionsCategory, }; static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); static cl::extrahelp MoreHelp( "EXAMPLES:\n\n" "Apply all transforms on a file that doesn't require compilation arguments:\n\n" " clang-modernize file.cpp\n" "\n" "Convert for loops to ranged-based for loops for all files in the compilation\n" "database that belong in a project subtree and then reformat the code\n" "automatically using the LLVM style:\n\n" " clang-modernize -p build/path -include project/path -format -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{} clang-modernize -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 to\n" "foo.cpp and any included headers in bar:\n\n" " clang-modernize -for-compilers=clang-3.0,gcc-4.7 foo.cpp \\\n" " -include bar -- -std=c++11 -Ibar\n\n"); //////////////////////////////////////////////////////////////////////////////// /// General Options // This is set to hidden on purpose. The actual help text for this option is // included in CommonOptionsParser::HelpMessage. static cl::opt BuildPath("p", cl::desc("Build Path"), cl::Optional, cl::Hidden, cl::cat(GeneralCategory)); static cl::list SourcePaths(cl::Positional, cl::desc("[...]"), cl::ZeroOrMore, cl::cat(GeneralCategory)); static cl::opt 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), cl::cat(GeneralCategory)); static cl::opt FinalSyntaxCheck( "final-syntax-check", cl::desc("Check for correct syntax after applying transformations"), cl::init(false), cl::cat(GeneralCategory)); static cl::opt SummaryMode("summary", cl::desc("Print transform summary"), cl::init(false), cl::cat(GeneralCategory)); static cl::opt TimingDirectoryName("perf", cl::desc("Capture performance data and output to specified " "directory. Default: ./migrate_perf"), cl::ValueOptional, cl::value_desc("directory name"), cl::cat(GeneralCategory)); cl::opt 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-separated list of -.\n" "\t can be any of: clang, gcc, icc, msvc\n" "\t is [.]\n"), cl::cat(GeneralCategory)); //////////////////////////////////////////////////////////////////////////////// /// Format Options static cl::opt DoFormat( "format", cl::desc("Enable formatting of code changed by applying replacements.\n" "Use -style to choose formatting style.\n"), cl::cat(FormattingCategory)); static cl::opt FormatStyleOpt("style", cl::desc(format::StyleOptionHelpDescription), cl::init("LLVM"), cl::cat(FormattingCategory)); // FIXME: Consider making the default behaviour for finding a style // configuration file to start the search anew for every file being changed to // handle situations where the style is different for different parts of a // project. static cl::opt FormatStyleConfig( "style-config", cl::desc("Path to a directory containing a .clang-format file\n" "describing a formatting style to use for formatting\n" "code when -style=file.\n"), cl::init(""), cl::cat(FormattingCategory)); //////////////////////////////////////////////////////////////////////////////// /// Include/Exclude Options static cl::opt IncludePaths("include", cl::desc("Comma-separated list of paths to consider to be " "transformed"), cl::cat(IncludeExcludeCategory)); static cl::opt ExcludePaths("exclude", cl::desc("Comma-separated list of paths that can not " "be transformed"), cl::cat(IncludeExcludeCategory)); static cl::opt IncludeFromFile("include-from", cl::value_desc("filename"), cl::desc("File containing a list of paths to consider to " "be transformed"), cl::cat(IncludeExcludeCategory)); static cl::opt ExcludeFromFile("exclude-from", cl::value_desc("filename"), cl::desc("File containing a list of paths that can not be " "transformed"), cl::cat(IncludeExcludeCategory)); //////////////////////////////////////////////////////////////////////////////// /// Serialization Options static cl::opt SerializeOnly("serialize-replacements", cl::desc("Serialize translation unit replacements to " "disk instead of changing files."), cl::init(false), cl::cat(SerializeCategory)); static cl::opt SerializeLocation("serialize-dir", cl::desc("Path to an existing directory in which to write\n" "serialized replacements. Default behaviour is to\n" "write to a temporary directory.\n"), cl::cat(SerializeCategory)); //////////////////////////////////////////////////////////////////////////////// void printVersion() { llvm::outs() << "clang-modernizer version " CLANG_VERSION_STRING << "\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 Compilers; llvm::StringRef(SupportedCompilers).split(Compilers, ","); for (llvm::SmallVectorImpl::iterator I = Compilers.begin(), E = Compilers.end(); I != E; ++I) { llvm::StringRef Compiler, VersionStr; std::tie(Compiler, VersionStr) = I->split('-'); Version *V = llvm::StringSwitch(Compiler) .Case("clang", &RequiredVersions.Clang) .Case("gcc", &RequiredVersions.Gcc).Case("icc", &RequiredVersions.Icc) .Case("msvc", &RequiredVersions.Msvc).Default(nullptr); if (V == nullptr) { 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 \"[.]\" instead of \"" << VersionStr << "\"\n"; Error = true; continue; } // support the lowest version given if (V->isNull() || Version < *V) *V = Version; } return RequiredVersions; } std::unique_ptr autoDetectCompilations(std::string &ErrorMessage) { // Auto-detect a compilation database from BuildPath. if (BuildPath.getNumOccurrences() > 0) return CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); // Try to auto-detect a compilation database from the first source. if (!SourcePaths.empty()) { if (std::unique_ptr Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], ErrorMessage)) { // FIXME: just pass SourcePaths[0] once getCompileCommands supports // non-absolute paths. SmallString<64> Path(SourcePaths[0]); llvm::sys::fs::make_absolute(Path); std::vector Commands = Compilations->getCompileCommands(Path); // Ignore a detected compilation database that doesn't contain source0 // since it is probably an unrelated compilation database. if (!Commands.empty()) return Compilations; } // Reset ErrorMessage since a fix compilation database will be created if // it fails to detect one from source. ErrorMessage = ""; // If no compilation database can be detected from source then we create a // fixed compilation database with c++11 support. std::string CommandLine[] = { "-std=c++11" }; return llvm::make_unique(".", CommandLine); } ErrorMessage = "Could not determine sources to transform"; return nullptr; } // Predicate definition for determining whether a file is not included. static bool isFileNotIncludedPredicate(llvm::StringRef FilePath) { return !GlobalOptions.ModifiableFiles.isFileIncluded(FilePath); } // Predicate definition for determining if a file was explicitly excluded. static bool isFileExplicitlyExcludedPredicate(llvm::StringRef FilePath) { if (GlobalOptions.ModifiableFiles.isFileExplicitlyExcluded(FilePath)) { llvm::errs() << "Warning \"" << FilePath << "\" will not be transformed " << "because it's in the excluded list.\n"; return true; } return false; } int main(int argc, const char **argv) { llvm::sys::PrintStackTraceOnErrorSignal(); Transforms TransformManager; ReplacementHandling ReplacementHandler; TransformManager.registerTransforms(); // Hide all options we don't define ourselves. Move pre-defined 'help', // 'help-list', and 'version' to our general category. llvm::StringMap Options; cl::getRegisteredOptions(Options); const cl::OptionCategory **CategoryEnd = VisibleCategories + llvm::array_lengthof(VisibleCategories); for (llvm::StringMap::iterator I = Options.begin(), E = Options.end(); I != E; ++I) { if (I->first() == "help" || I->first() == "version" || I->first() == "help-list") I->second->setCategory(GeneralCategory); else if (std::find(VisibleCategories, CategoryEnd, I->second->Category) == CategoryEnd) I->second->setHiddenFlag(cl::ReallyHidden); } cl::SetVersionPrinter(&printVersion); // Parse options and generate compilations. std::unique_ptr Compilations( FixedCompilationDatabase::loadFromCommandLine(argc, argv)); cl::ParseCommandLineOptions(argc, argv); // Populate the ModifiableFiles structure. GlobalOptions.ModifiableFiles.readListFromString(IncludePaths, ExcludePaths); GlobalOptions.ModifiableFiles.readListFromFile(IncludeFromFile, ExcludeFromFile); if (!Compilations) { std::string ErrorMessage; Compilations = autoDetectCompilations(ErrorMessage); if (!Compilations) { llvm::errs() << llvm::sys::path::filename(argv[0]) << ": " << ErrorMessage << "\n"; return 1; } } // Populate source files. std::vector Sources; if (!SourcePaths.empty()) { // Use only files that are not explicitly excluded. std::remove_copy_if(SourcePaths.begin(), SourcePaths.end(), std::back_inserter(Sources), isFileExplicitlyExcludedPredicate); } else { if (GlobalOptions.ModifiableFiles.isIncludeListEmpty()) { llvm::errs() << llvm::sys::path::filename(argv[0]) << ": Use -include to indicate which files of " << "the compilatiion database to transform.\n"; return 1; } // Use source paths from the compilation database. // We only transform files that are explicitly included. Sources = Compilations->getAllFiles(); std::vector::iterator E = std::remove_if( Sources.begin(), Sources.end(), isFileNotIncludedPredicate); Sources.erase(E, Sources.end()); } if (Sources.empty()) { llvm::errs() << llvm::sys::path::filename(argv[0]) << ": Could not determine sources to transform.\n"; return 1; } // Enable timming. GlobalOptions.EnableTiming = TimingDirectoryName.getNumOccurrences() > 0; bool CmdSwitchError = false; CompilerVersions RequiredVersions = handleSupportedCompilers(argv[0], CmdSwitchError); if (CmdSwitchError) return 1; TransformManager.createSelectedTransforms(GlobalOptions, RequiredVersions); if (TransformManager.begin() == TransformManager.end()) { if (SupportedCompilers.empty()) llvm::errs() << llvm::sys::path::filename(argv[0]) << ": no selected transforms\n"; else llvm::errs() << llvm::sys::path::filename(argv[0]) << ": no transforms available for specified compilers\n"; return 1; } llvm::IntrusiveRefCntPtr DiagOpts( new DiagnosticOptions()); DiagnosticsEngine Diagnostics( llvm::IntrusiveRefCntPtr(new DiagnosticIDs()), DiagOpts.get()); // FIXME: Make this DiagnosticsEngine available to all Transforms probably via // GlobalOptions. // If SerializeReplacements is requested, then code reformatting must be // turned off and only one transform should be requested. if (SerializeOnly && (std::distance(TransformManager.begin(), TransformManager.end()) > 1 || DoFormat)) { llvm::errs() << "Serialization of replacements requested for multiple " "transforms.\nChanges from only one transform can be " "serialized.\n"; return 1; } // If we're asked to apply changes to files on disk, need to locate // clang-apply-replacements. if (!SerializeOnly) { if (!ReplacementHandler.findClangApplyReplacements(argv[0])) { llvm::errs() << "Could not find clang-apply-replacements\n"; return 1; } if (DoFormat) ReplacementHandler.enableFormatting(FormatStyleOpt, FormatStyleConfig); } StringRef TempDestinationDir; if (SerializeLocation.getNumOccurrences() > 0) ReplacementHandler.setDestinationDir(SerializeLocation); else TempDestinationDir = ReplacementHandler.useTempDestinationDir(); SourcePerfData PerfData; for (Transforms::const_iterator I = TransformManager.begin(), E = TransformManager.end(); I != E; ++I) { Transform *T = *I; if (T->apply(*Compilations, Sources) != 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"; } if (!ReplacementHandler.serializeReplacements(T->getAllReplacements())) return 1; if (!SerializeOnly) if (!ReplacementHandler.applyReplacements()) return 1; } // Let the user know which temporary directory the replacements got written // to. if (SerializeOnly && !TempDestinationDir.empty()) llvm::errs() << "Replacements serialized to: " << TempDestinationDir << "\n"; if (FinalSyntaxCheck) { ClangTool SyntaxTool(*Compilations, SourcePaths); if (SyntaxTool.run(newFrontendActionFactory().get()) != 0) 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 };