forked from OSchip/llvm-project
504 lines
19 KiB
C++
504 lines
19 KiB
C++
//===-- 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<std::string> BuildPath("p", cl::desc("Build Path"), cl::Optional,
|
|
cl::Hidden, cl::cat(GeneralCategory));
|
|
|
|
static cl::list<std::string> SourcePaths(cl::Positional,
|
|
cl::desc("[<sources>...]"),
|
|
cl::ZeroOrMore,
|
|
cl::cat(GeneralCategory));
|
|
|
|
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),
|
|
cl::cat(GeneralCategory));
|
|
|
|
static cl::opt<bool> FinalSyntaxCheck(
|
|
"final-syntax-check",
|
|
cl::desc("Check for correct syntax after applying transformations"),
|
|
cl::init(false), cl::cat(GeneralCategory));
|
|
|
|
static cl::opt<bool> SummaryMode("summary", cl::desc("Print transform summary"),
|
|
cl::init(false), cl::cat(GeneralCategory));
|
|
|
|
static cl::opt<std::string>
|
|
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<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-separated list of <compiler>-<version>.\n"
|
|
"\t<compiler> can be any of: clang, gcc, icc, msvc\n"
|
|
"\t<version> is <major>[.<minor>]\n"),
|
|
cl::cat(GeneralCategory));
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// Format Options
|
|
static cl::opt<bool> 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<std::string>
|
|
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<std::string> 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<std::string>
|
|
IncludePaths("include",
|
|
cl::desc("Comma-separated list of paths to consider to be "
|
|
"transformed"),
|
|
cl::cat(IncludeExcludeCategory));
|
|
|
|
static cl::opt<std::string>
|
|
ExcludePaths("exclude", cl::desc("Comma-separated list of paths that can not "
|
|
"be transformed"),
|
|
cl::cat(IncludeExcludeCategory));
|
|
|
|
static cl::opt<std::string>
|
|
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<std::string>
|
|
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<bool>
|
|
SerializeOnly("serialize-replacements",
|
|
cl::desc("Serialize translation unit replacements to "
|
|
"disk instead of changing files."),
|
|
cl::init(false),
|
|
cl::cat(SerializeCategory));
|
|
|
|
static cl::opt<std::string>
|
|
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<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;
|
|
}
|
|
|
|
CompilationDatabase *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 (CompilationDatabase *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<CompileCommand> 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 new FixedCompilationDatabase(".", CommandLine);
|
|
}
|
|
|
|
ErrorMessage = "Could not determine sources to transform";
|
|
return 0;
|
|
}
|
|
|
|
// 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<cl::Option*> Options;
|
|
cl::getRegisteredOptions(Options);
|
|
const cl::OptionCategory **CategoryEnd =
|
|
VisibleCategories + llvm::array_lengthof(VisibleCategories);
|
|
for (llvm::StringMap<cl::Option *>::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.
|
|
OwningPtr<CompilationDatabase> 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.reset(autoDetectCompilations(ErrorMessage));
|
|
if (!Compilations) {
|
|
llvm::errs() << llvm::sys::path::filename(argv[0]) << ": " << ErrorMessage
|
|
<< "\n";
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Populate source files.
|
|
std::vector<std::string> 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<std::string>::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<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 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<SyntaxOnlyAction>()) != 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
|
|
};
|