[arcmt] Add some additional driver flags to optionally emit or save the pre-migration ARC errors.

-arcmt-migrate-emit-errors : Emits the pre-migration ARC errors but it doesn't affect anything else
-arcmt-migrate-report-output : Writes out the pre-migration ARC errors to the provided plist file

rdar://9791454

llvm-svn: 135491
This commit is contained in:
Argyrios Kyrtzidis 2011-07-19 17:20:03 +00:00
parent 08636b4633
commit d571363e45
14 changed files with 377 additions and 14 deletions

View File

@ -28,10 +28,19 @@ namespace arcmt {
/// It then checks the AST and produces errors/warning for ARC migration issues
/// that the user needs to handle manually.
///
/// \param emitPremigrationARCErrors if true all ARC errors will get emitted
/// even if the migrator can fix them, but the function will still return false
/// if all ARC errors can be fixed.
///
/// \param plistOut if non-empty, it is the file path to store the plist with
/// the pre-migration ARC diagnostics.
///
/// \returns false if no error is produced, true otherwise.
bool checkForManualIssues(CompilerInvocation &CI,
llvm::StringRef Filename, InputKind Kind,
DiagnosticClient *DiagClient);
DiagnosticClient *DiagClient,
bool emitPremigrationARCErrors = false,
llvm::StringRef plistOut = llvm::StringRef());
/// \brief Works similar to checkForManualIssues but instead of checking, it
/// applies automatic modifications to source files to conform to ARC.
@ -44,11 +53,20 @@ bool applyTransformations(CompilerInvocation &origCI,
/// \brief Applies automatic modifications and produces temporary files
/// and metadata into the \arg outputDir path.
///
/// \param emitPremigrationARCErrors if true all ARC errors will get emitted
/// even if the migrator can fix them, but the function will still return false
/// if all ARC errors can be fixed.
///
/// \param plistOut if non-empty, it is the file path to store the plist with
/// the pre-migration ARC diagnostics.
///
/// \returns false if no error is produced, true otherwise.
bool migrateWithTemporaryFiles(CompilerInvocation &origCI,
llvm::StringRef Filename, InputKind Kind,
DiagnosticClient *DiagClient,
llvm::StringRef outputDir);
llvm::StringRef outputDir,
bool emitPremigrationARCErrors,
llvm::StringRef plistOut);
/// \brief Get the set of file remappings from the \arg outputDir path that
/// migrateWithTemporaryFiles produced.

View File

@ -34,11 +34,15 @@ public:
class MigrateAction : public WrapperFrontendAction {
std::string MigrateDir;
std::string PlistOut;
bool EmitPremigrationARCErros;
protected:
virtual bool BeginInvocation(CompilerInstance &CI);
public:
MigrateAction(FrontendAction *WrappedAction, llvm::StringRef migrateDir);
MigrateAction(FrontendAction *WrappedAction, llvm::StringRef migrateDir,
llvm::StringRef plistOut,
bool emitPremigrationARCErrors);
};
}

View File

@ -394,6 +394,10 @@ def arcmt_migrate : Flag<"-arcmt-migrate">,
HelpText<"Apply modifications and produces temporary files that conform to ARC">;
def arcmt_migrate_directory : Separate<"-arcmt-migrate-directory">,
HelpText<"Directory for temporary files produced during ARC migration">;
def arcmt_migrate_report_output : Separate<"-arcmt-migrate-report-output">,
HelpText<"Output path for the plist report">;
def arcmt_migrate_emit_arc_errors : Flag<"-arcmt-migrate-emit-errors">,
HelpText<"Emit ARC errors even if the migrator can fix them">;
def import_module : Separate<"-import-module">,
HelpText<"Import a module definition file">;

View File

@ -122,6 +122,10 @@ def ccc_arcmt_migrate : Separate<"-ccc-arcmt-migrate">, CCCDriverOpt,
HelpText<"Apply modifications and produces temporary files that conform to ARC">;
def ccc_arcmt_migrate_EQ : Joined<"-ccc-arcmt-migrate=">, CCCDriverOpt,
Alias<ccc_arcmt_migrate>;
def arcmt_migrate_report_output : Separate<"-arcmt-migrate-report-output">,
HelpText<"Output path for the plist report">;
def arcmt_migrate_emit_arc_errors : Flag<"-arcmt-migrate-emit-errors">,
HelpText<"Emit ARC errors even if the migrator can fix them">;
// Make sure all other -ccc- options are rejected.
def ccc_ : Joined<"-ccc-">, Group<ccc_Group>, Flags<[Unsupported]>;

View File

@ -75,6 +75,8 @@ public:
unsigned ShowVersion : 1; ///< Show the -version text.
unsigned FixWhatYouCan : 1; ///< Apply fixes even if there are
/// unfixable errors.
unsigned ARCMTMigrateEmitARCErrors : 1; /// Emit ARC errors even if the
/// migrator can fix them
enum {
ARCMT_None,
@ -84,6 +86,7 @@ public:
} ARCMTAction;
std::string ARCMTMigrateDir;
std::string ARCMTMigrateReportOut;
/// The input files and their types.
std::vector<std::pair<InputKind, std::string> > Inputs;
@ -140,6 +143,7 @@ public:
ShowTimers = 0;
ShowVersion = 0;
ARCMTAction = ARCMT_None;
ARCMTMigrateEmitARCErrors = 0;
}
/// getInputKindForExtension - Return the appropriate input kind for a file

View File

@ -10,6 +10,7 @@
#include "Internals.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Rewrite/Rewriter.h"
@ -194,13 +195,29 @@ CompilerInvocation *createInvocationForMigration(CompilerInvocation &origCI) {
return CInvok.take();
}
void emitPremigrationErrors(const CapturedDiagList &arcDiags,
const DiagnosticOptions &diagOpts,
Preprocessor &PP) {
TextDiagnosticPrinter printer(llvm::errs(), diagOpts);
llvm::IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
llvm::IntrusiveRefCntPtr<Diagnostic> Diags(
new Diagnostic(DiagID, &printer, /*ShouldOwnClient=*/false));
Diags->setSourceManager(&PP.getSourceManager());
printer.BeginSourceFile(PP.getLangOptions(), &PP);
arcDiags.reportDiagnostics(*Diags);
printer.EndSourceFile();
}
//===----------------------------------------------------------------------===//
// checkForManualIssues.
//===----------------------------------------------------------------------===//
bool arcmt::checkForManualIssues(CompilerInvocation &origCI,
llvm::StringRef Filename, InputKind Kind,
DiagnosticClient *DiagClient) {
DiagnosticClient *DiagClient,
bool emitPremigrationARCErrors,
llvm::StringRef plistOut) {
if (!origCI.getLangOpts().ObjC1)
return false;
@ -241,6 +258,18 @@ bool arcmt::checkForManualIssues(CompilerInvocation &origCI,
return true;
}
if (emitPremigrationARCErrors)
emitPremigrationErrors(capturedDiags, origCI.getDiagnosticOpts(),
Unit->getPreprocessor());
if (!plistOut.empty()) {
llvm::SmallVector<StoredDiagnostic, 8> arcDiags;
for (CapturedDiagList::iterator
I = capturedDiags.begin(), E = capturedDiags.end(); I != E; ++I)
arcDiags.push_back(*I);
writeARCDiagsToPlist(plistOut, arcDiags,
Ctx.getSourceManager(), Ctx.getLangOptions());
}
// After parsing of source files ended, we want to reuse the
// diagnostics objects to emit further diagnostics.
// We call BeginSourceFile because DiagnosticClient requires that
@ -276,13 +305,16 @@ bool arcmt::checkForManualIssues(CompilerInvocation &origCI,
static bool applyTransforms(CompilerInvocation &origCI,
llvm::StringRef Filename, InputKind Kind,
DiagnosticClient *DiagClient,
llvm::StringRef outputDir) {
llvm::StringRef outputDir,
bool emitPremigrationARCErrors,
llvm::StringRef plistOut) {
if (!origCI.getLangOpts().ObjC1)
return false;
// Make sure checking is successful first.
CompilerInvocation CInvokForCheck(origCI);
if (arcmt::checkForManualIssues(CInvokForCheck, Filename, Kind, DiagClient))
if (arcmt::checkForManualIssues(CInvokForCheck, Filename, Kind, DiagClient,
emitPremigrationARCErrors, plistOut))
return true;
CompilerInvocation CInvok(origCI);
@ -317,15 +349,19 @@ static bool applyTransforms(CompilerInvocation &origCI,
bool arcmt::applyTransformations(CompilerInvocation &origCI,
llvm::StringRef Filename, InputKind Kind,
DiagnosticClient *DiagClient) {
return applyTransforms(origCI, Filename, Kind, DiagClient, llvm::StringRef());
return applyTransforms(origCI, Filename, Kind, DiagClient,
llvm::StringRef(), false, llvm::StringRef());
}
bool arcmt::migrateWithTemporaryFiles(CompilerInvocation &origCI,
llvm::StringRef Filename, InputKind Kind,
DiagnosticClient *DiagClient,
llvm::StringRef outputDir) {
llvm::StringRef outputDir,
bool emitPremigrationARCErrors,
llvm::StringRef plistOut) {
assert(!outputDir.empty() && "Expected output directory path");
return applyTransforms(origCI, Filename, Kind, DiagClient, outputDir);
return applyTransforms(origCI, Filename, Kind, DiagClient,
outputDir, emitPremigrationARCErrors, plistOut);
}
bool arcmt::getFileRemappings(std::vector<std::pair<std::string,std::string> > &

View File

@ -38,16 +38,26 @@ ModifyAction::ModifyAction(FrontendAction *WrappedAction)
: WrapperFrontendAction(WrappedAction) {}
bool MigrateAction::BeginInvocation(CompilerInstance &CI) {
return !arcmt::migrateWithTemporaryFiles(CI.getInvocation(),
if (arcmt::migrateWithTemporaryFiles(CI.getInvocation(),
getCurrentFile(),
getCurrentFileKind(),
CI.getDiagnostics().getClient(),
MigrateDir);
MigrateDir,
EmitPremigrationARCErros,
PlistOut))
return false; // errors, stop the action.
// We only want to see diagnostics emitted by migrateWithTemporaryFiles.
CI.getDiagnostics().setIgnoreAllWarnings(true);
return true;
}
MigrateAction::MigrateAction(FrontendAction *WrappedAction,
llvm::StringRef migrateDir)
: WrapperFrontendAction(WrappedAction), MigrateDir(migrateDir) {
llvm::StringRef migrateDir,
llvm::StringRef plistOut,
bool emitPremigrationARCErrors)
: WrapperFrontendAction(WrappedAction), MigrateDir(migrateDir),
PlistOut(plistOut), EmitPremigrationARCErros(emitPremigrationARCErrors) {
if (MigrateDir.empty())
MigrateDir = "."; // user current directory if none is given.
}

View File

@ -32,8 +32,16 @@ public:
void reportDiagnostics(Diagnostic &diags) const;
bool hasErrors() const;
typedef ListTy::const_iterator iterator;
iterator begin() const { return List.begin(); }
iterator end() const { return List.end(); }
};
void writeARCDiagsToPlist(const std::string &outPath,
llvm::ArrayRef<StoredDiagnostic> diags,
SourceManager &SM, const LangOptions &LangOpts);
class TransformActions {
Diagnostic &Diags;
CapturedDiagList &CapturedDiags;

View File

@ -0,0 +1,197 @@
//===--- PlistReporter.cpp - ARC Migrate Tool Plist Reporter ----*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Internals.h"
#include "clang/Lex/Lexer.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/FileManager.h"
using namespace clang;
using namespace arcmt;
using llvm::StringRef;
// FIXME: This duplicates significant functionality from PlistDiagnostics.cpp,
// it would be jolly good if there was a reusable PlistWriter or something.
typedef llvm::DenseMap<FileID, unsigned> FIDMap;
static void AddFID(FIDMap &FIDs, llvm::SmallVectorImpl<FileID> &V,
const SourceManager &SM, SourceLocation L) {
FileID FID = SM.getFileID(SM.getInstantiationLoc(L));
FIDMap::iterator I = FIDs.find(FID);
if (I != FIDs.end()) return;
FIDs[FID] = V.size();
V.push_back(FID);
}
static unsigned GetFID(const FIDMap& FIDs, const SourceManager &SM,
SourceLocation L) {
FileID FID = SM.getFileID(SM.getInstantiationLoc(L));
FIDMap::const_iterator I = FIDs.find(FID);
assert(I != FIDs.end());
return I->second;
}
static llvm::raw_ostream& Indent(llvm::raw_ostream& o, const unsigned indent) {
for (unsigned i = 0; i < indent; ++i) o << ' ';
return o;
}
static void EmitLocation(llvm::raw_ostream& o, const SourceManager &SM,
const LangOptions &LangOpts,
SourceLocation L, const FIDMap &FM,
unsigned indent, bool extend = false) {
FullSourceLoc Loc(SM.getInstantiationLoc(L), const_cast<SourceManager&>(SM));
// Add in the length of the token, so that we cover multi-char tokens.
unsigned offset =
extend ? Lexer::MeasureTokenLength(Loc, SM, LangOpts) - 1 : 0;
Indent(o, indent) << "<dict>\n";
Indent(o, indent) << " <key>line</key><integer>"
<< Loc.getInstantiationLineNumber() << "</integer>\n";
Indent(o, indent) << " <key>col</key><integer>"
<< Loc.getInstantiationColumnNumber() + offset << "</integer>\n";
Indent(o, indent) << " <key>file</key><integer>"
<< GetFID(FM, SM, Loc) << "</integer>\n";
Indent(o, indent) << "</dict>\n";
}
static void EmitRange(llvm::raw_ostream& o, const SourceManager &SM,
const LangOptions &LangOpts,
CharSourceRange R, const FIDMap &FM,
unsigned indent) {
Indent(o, indent) << "<array>\n";
EmitLocation(o, SM, LangOpts, R.getBegin(), FM, indent+1);
EmitLocation(o, SM, LangOpts, R.getEnd(), FM, indent+1, R.isTokenRange());
Indent(o, indent) << "</array>\n";
}
static llvm::raw_ostream& EmitString(llvm::raw_ostream& o,
StringRef s) {
o << "<string>";
for (StringRef::const_iterator I=s.begin(), E=s.end(); I!=E; ++I) {
char c = *I;
switch (c) {
default: o << c; break;
case '&': o << "&amp;"; break;
case '<': o << "&lt;"; break;
case '>': o << "&gt;"; break;
case '\'': o << "&apos;"; break;
case '\"': o << "&quot;"; break;
}
}
o << "</string>";
return o;
}
void arcmt::writeARCDiagsToPlist(const std::string &outPath,
llvm::ArrayRef<StoredDiagnostic> diags,
SourceManager &SM,
const LangOptions &LangOpts) {
DiagnosticIDs DiagIDs;
// Build up a set of FIDs that we use by scanning the locations and
// ranges of the diagnostics.
FIDMap FM;
llvm::SmallVector<FileID, 10> Fids;
for (llvm::ArrayRef<StoredDiagnostic>::iterator
I = diags.begin(), E = diags.end(); I != E; ++I) {
const StoredDiagnostic &D = *I;
AddFID(FM, Fids, SM, D.getLocation());
for (StoredDiagnostic::range_iterator
RI = D.range_begin(), RE = D.range_end(); RI != RE; ++RI) {
AddFID(FM, Fids, SM, RI->getBegin());
AddFID(FM, Fids, SM, RI->getEnd());
}
}
std::string errMsg;
llvm::raw_fd_ostream o(outPath.c_str(), errMsg);
if (!errMsg.empty()) {
llvm::errs() << "error: could not create file: " << outPath << '\n';
return;
}
// Write the plist header.
o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n";
// Write the root object: a <dict> containing...
// - "files", an <array> mapping from FIDs to file names
// - "diagnostics", an <array> containing the diagnostics
o << "<dict>\n"
" <key>files</key>\n"
" <array>\n";
for (llvm::SmallVectorImpl<FileID>::iterator I=Fids.begin(), E=Fids.end();
I!=E; ++I) {
o << " ";
EmitString(o, SM.getFileEntryForID(*I)->getName()) << '\n';
}
o << " </array>\n"
" <key>diagnostics</key>\n"
" <array>\n";
for (llvm::ArrayRef<StoredDiagnostic>::iterator
DI = diags.begin(), DE = diags.end(); DI != DE; ++DI) {
const StoredDiagnostic &D = *DI;
if (D.getLevel() == Diagnostic::Ignored)
continue;
o << " <dict>\n";
// Output the diagnostic.
o << " <key>description</key>";
EmitString(o, D.getMessage()) << '\n';
o << " <key>category</key>";
EmitString(o, DiagIDs.getCategoryNameFromID(
DiagIDs.getCategoryNumberForDiag(D.getID()))) << '\n';
o << " <key>type</key>";
if (D.getLevel() >= Diagnostic::Error)
EmitString(o, "error") << '\n';
else if (D.getLevel() == Diagnostic::Warning)
EmitString(o, "warning") << '\n';
else
EmitString(o, "note") << '\n';
// Output the location of the bug.
o << " <key>location</key>\n";
EmitLocation(o, SM, LangOpts, D.getLocation(), FM, 2);
// Output the ranges (if any).
StoredDiagnostic::range_iterator RI = D.range_begin(), RE = D.range_end();
if (RI != RE) {
o << " <key>ranges</key>\n";
o << " <array>\n";
for (; RI != RE; ++RI)
EmitRange(o, SM, LangOpts, *RI, FM, 4);
o << " </array>\n";
}
// Close up the entry.
o << " </dict>\n";
}
o << " </array>\n";
// Finish.
o << "</dict>\n</plist>";
}

View File

@ -1444,6 +1444,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("-arcmt-migrate");
CmdArgs.push_back("-arcmt-migrate-directory");
CmdArgs.push_back(A->getValue(Args));
Args.AddLastArg(CmdArgs, options::OPT_arcmt_migrate_report_output);
Args.AddLastArg(CmdArgs, options::OPT_arcmt_migrate_emit_arc_errors);
break;
}
}

View File

@ -439,6 +439,12 @@ static void FrontendOptsToArgs(const FrontendOptions &Opts,
Res.push_back("-arcmt-migrate-directory");
Res.push_back(Opts.ARCMTMigrateDir);
}
if (!Opts.ARCMTMigrateReportOut.empty()) {
Res.push_back("-arcmt-migrate-report-output");
Res.push_back(Opts.ARCMTMigrateReportOut);
}
if (Opts.ARCMTMigrateEmitARCErrors)
Res.push_back("-arcmt-migrate-emit-errors");
bool NeedLang = false;
for (unsigned i = 0, e = Opts.Inputs.size(); i != e; ++i)
@ -1278,6 +1284,10 @@ static InputKind ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
}
}
Opts.ARCMTMigrateDir = Args.getLastArgValue(OPT_arcmt_migrate_directory);
Opts.ARCMTMigrateReportOut
= Args.getLastArgValue(OPT_arcmt_migrate_report_output);
Opts.ARCMTMigrateEmitARCErrors
= Args.hasArg(OPT_arcmt_migrate_emit_arc_errors);
InputKind DashX = IK_None;
if (const Arg *A = Args.getLastArg(OPT_x)) {

View File

@ -100,7 +100,10 @@ static FrontendAction *CreateFrontendAction(CompilerInstance &CI) {
Act = new arcmt::ModifyAction(Act);
break;
case FrontendOptions::ARCMT_Migrate:
Act = new arcmt::MigrateAction(Act, CI.getFrontendOpts().ARCMTMigrateDir);
Act = new arcmt::MigrateAction(Act,
CI.getFrontendOpts().ARCMTMigrateDir,
CI.getFrontendOpts().ARCMTMigrateReportOut,
CI.getFrontendOpts().ARCMTMigrateEmitARCErrors);
break;
}

View File

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -arcmt-migrate -arcmt-migrate-directory %t -arcmt-migrate-emit-errors %s -fobjc-nonfragile-abi 2>&1 | FileCheck %s
// RUN: rm -rf %t
@protocol NSObject
- (oneway void)release;
@end
void test(id p) {
[p release];
}
// CHECK: error: ARC forbids explicit message send of 'release'

View File

@ -0,0 +1,50 @@
// RUN: %clang_cc1 -arcmt-migrate -arcmt-migrate-directory %t.dir -arcmt-migrate-report-output %t.plist %s -fobjc-nonfragile-abi
// RUN: FileCheck %s -input-file=%t.plist
// RUN: rm -rf %t.dir
@protocol NSObject
- (oneway void)release;
@end
void test(id p) {
[p release];
}
// CHECK: <?xml version="1.0" encoding="UTF-8"?>
// CHECK: <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
// CHECK: <plist version="1.0">
// CHECK: <dict>
// CHECK: <key>files</key>
// CHECK: <array>
// CHECK: </array>
// CHECK: <key>diagnostics</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>description</key><string>ARC forbids explicit message send of &apos;release&apos;</string>
// CHECK: <key>category</key><string>Automatic Reference Counting Issue</string>
// CHECK: <key>type</key><string>error</string>
// CHECK: <key>location</key>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>4</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <key>ranges</key>
// CHECK: <array>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>6</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>12</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </array>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </dict>
// CHECK: </plist>