24 changes: 15 additions & 9 deletions clang/lib/StaticAnalyzer/Core/BugReporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2928,6 +2928,9 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) {
Pieces.push_front(*I);
}

for (const auto &I : report->getFixits())
Pieces.back()->addFixit(I);

updateExecutedLinesWithDiagnosticPieces(*PD);
Consumer->HandlePathDiagnostic(std::move(PD));
}
Expand Down Expand Up @@ -3048,26 +3051,29 @@ BugReporter::generateDiagnosticForConsumerMap(
}

void BugReporter::EmitBasicReport(const Decl *DeclWithIssue,
const CheckerBase *Checker,
StringRef Name, StringRef Category,
StringRef Str, PathDiagnosticLocation Loc,
ArrayRef<SourceRange> Ranges) {
const CheckerBase *Checker, StringRef Name,
StringRef Category, StringRef Str,
PathDiagnosticLocation Loc,
ArrayRef<SourceRange> Ranges,
ArrayRef<FixItHint> Fixits) {
EmitBasicReport(DeclWithIssue, Checker->getCheckName(), Name, Category, Str,
Loc, Ranges);
Loc, Ranges, Fixits);
}

void BugReporter::EmitBasicReport(const Decl *DeclWithIssue,
CheckName CheckName,
StringRef name, StringRef category,
StringRef str, PathDiagnosticLocation Loc,
ArrayRef<SourceRange> Ranges) {
ArrayRef<SourceRange> Ranges,
ArrayRef<FixItHint> Fixits) {
// 'BT' is owned by BugReporter.
BugType *BT = getBugTypeForName(CheckName, name, category);
auto R = std::make_unique<BugReport>(*BT, str, Loc);
R->setDeclWithIssue(DeclWithIssue);
for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), E = Ranges.end();
I != E; ++I)
R->addRange(*I);
for (const auto &SR : Ranges)
R->addRange(SR);
for (const auto &FH : Fixits)
R->addFixItHint(FH);
emitReport(std::move(R));
}

Expand Down
46 changes: 46 additions & 0 deletions clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class PlistPrinter {
void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges,
unsigned indent);
void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent);
void EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, unsigned indent);

void ReportControlFlow(raw_ostream &o,
const PathDiagnosticControlFlowPiece& P,
Expand Down Expand Up @@ -222,6 +223,33 @@ void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message,
EmitString(o, Message) << '\n';
}

void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits,
unsigned indent) {
if (fixits.size() == 0)
return;

const SourceManager &SM = PP.getSourceManager();
const LangOptions &LangOpts = PP.getLangOpts();

Indent(o, indent) << "<key>fixits</key>\n";
Indent(o, indent) << "<array>\n";
for (const auto &fixit : fixits) {
assert(!fixit.isNull());
// FIXME: Add support for InsertFromRange and BeforePreviousInsertion.
assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!");
assert(!fixit.BeforePreviousInsertions && "Not implemented yet!");
Indent(o, indent) << " <dict>\n";
Indent(o, indent) << " <key>remove_range</key>\n";
EmitRange(o, SM, Lexer::getAsCharRange(fixit.RemoveRange, SM, LangOpts),
FM, indent + 2);
Indent(o, indent) << " <key>insert_string</key>";
EmitString(o, fixit.CodeToInsert);
o << "\n";
Indent(o, indent) << " </dict>\n";
}
Indent(o, indent) << "</array>\n";
}

void PlistPrinter::ReportControlFlow(raw_ostream &o,
const PathDiagnosticControlFlowPiece& P,
unsigned indent) {
Expand Down Expand Up @@ -272,6 +300,9 @@ void PlistPrinter::ReportControlFlow(raw_ostream &o,
EmitString(o, s) << '\n';
}

assert(P.getFixits().size() == 0 &&
"Fixits on constrol flow pieces are not implemented yet!");

--indent;
Indent(o, indent) << "</dict>\n";
}
Expand Down Expand Up @@ -308,6 +339,9 @@ void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P
// Output the text.
EmitMessage(o, P.getString(), indent);

// Output the fixits.
EmitFixits(o, P.getFixits(), indent);

// Finish up.
--indent;
Indent(o, indent); o << "</dict>\n";
Expand Down Expand Up @@ -335,6 +369,9 @@ void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P,

if (auto callExit = P.getCallExitEvent())
ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true);

assert(P.getFixits().size() == 0 &&
"Fixits on call pieces are not implemented yet!");
}

void PlistPrinter::ReportMacroSubPieces(raw_ostream &o,
Expand All @@ -347,6 +384,9 @@ void PlistPrinter::ReportMacroSubPieces(raw_ostream &o,
I != E; ++I) {
ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false);
}

assert(P.getFixits().size() == 0 &&
"Fixits on constrol flow pieces are not implemented yet!");
}

void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) {
Expand Down Expand Up @@ -404,6 +444,9 @@ void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
// Output the text.
EmitMessage(o, P.getString(), indent);

// Output the fixits.
EmitFixits(o, P.getFixits(), indent);

// Finish up.
--indent;
Indent(o, indent); o << "</dict>\n";
Expand Down Expand Up @@ -432,6 +475,9 @@ void PlistPrinter::ReportPopUp(raw_ostream &o,
// Output the text.
EmitMessage(o, P.getString(), indent);

assert(P.getFixits().size() == 0 &&
"Fixits on pop-up pieces are not implemented yet!");

// Finish up.
--indent;
Indent(o, indent) << "</dict>\n";
Expand Down
60 changes: 42 additions & 18 deletions clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ void ento::createTextPathDiagnosticConsumer(
namespace {
class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer {
DiagnosticsEngine &Diag;
bool IncludePath, ShouldEmitAsError;
bool IncludePath = false, ShouldEmitAsError = false, FixitsAsRemarks = false;

public:
ClangDiagPathDiagConsumer(DiagnosticsEngine &Diag)
: Diag(Diag), IncludePath(false), ShouldEmitAsError(false) {}
: Diag(Diag) {}
~ClangDiagPathDiagConsumer() override {}
StringRef getName() const override { return "ClangDiags"; }

Expand All @@ -98,11 +98,9 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer {
return IncludePath ? Minimal : None;
}

void enablePaths() {
IncludePath = true;
}

void enablePaths() { IncludePath = true; }
void enableWerror() { ShouldEmitAsError = true; }
void enableFixitsAsRemarks() { FixitsAsRemarks = true; }

void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
FilesMade *filesMade) override {
Expand All @@ -111,22 +109,46 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer {
? Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0")
: Diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0");
unsigned NoteID = Diag.getCustomDiagID(DiagnosticsEngine::Note, "%0");

for (std::vector<const PathDiagnostic*>::iterator I = Diags.begin(),
E = Diags.end(); I != E; ++I) {
unsigned RemarkID = Diag.getCustomDiagID(DiagnosticsEngine::Remark, "%0");

auto reportPiece =
[&](unsigned ID, SourceLocation Loc, StringRef String,
ArrayRef<SourceRange> Ranges, ArrayRef<FixItHint> Fixits) {
if (!FixitsAsRemarks) {
Diag.Report(Loc, ID) << String << Ranges << Fixits;
} else {
Diag.Report(Loc, ID) << String << Ranges;
for (const FixItHint &Hint : Fixits) {
SourceManager &SM = Diag.getSourceManager();
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
// FIXME: Add support for InsertFromRange and
// BeforePreviousInsertion.
assert(!Hint.InsertFromRange.isValid() && "Not implemented yet!");
assert(!Hint.BeforePreviousInsertions && "Not implemented yet!");
OS << SM.getSpellingColumnNumber(Hint.RemoveRange.getBegin())
<< "-" << SM.getSpellingColumnNumber(Hint.RemoveRange.getEnd())
<< ": '" << Hint.CodeToInsert << "'";
Diag.Report(Loc, RemarkID) << OS.str();
}
}
};

for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(),
E = Diags.end();
I != E; ++I) {
const PathDiagnostic *PD = *I;
SourceLocation WarnLoc = PD->getLocation().asLocation();
Diag.Report(WarnLoc, WarnID) << PD->getShortDescription()
<< PD->path.back()->getRanges();
reportPiece(WarnID, PD->getLocation().asLocation(),
PD->getShortDescription(), PD->path.back()->getRanges(),
PD->path.back()->getFixits());

// First, add extra notes, even if paths should not be included.
for (const auto &Piece : PD->path) {
if (!isa<PathDiagnosticNotePiece>(Piece.get()))
continue;

SourceLocation NoteLoc = Piece->getLocation().asLocation();
Diag.Report(NoteLoc, NoteID) << Piece->getString()
<< Piece->getRanges();
reportPiece(NoteID, Piece->getLocation().asLocation(),
Piece->getString(), Piece->getRanges(), Piece->getFixits());
}

if (!IncludePath)
Expand All @@ -138,9 +160,8 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer {
if (isa<PathDiagnosticNotePiece>(Piece.get()))
continue;

SourceLocation NoteLoc = Piece->getLocation().asLocation();
Diag.Report(NoteLoc, NoteID) << Piece->getString()
<< Piece->getRanges();
reportPiece(NoteID, Piece->getLocation().asLocation(),
Piece->getString(), Piece->getRanges(), Piece->getFixits());
}
}
}
Expand Down Expand Up @@ -241,6 +262,9 @@ class AnalysisConsumer : public AnalysisASTConsumer,
if (Opts->AnalyzerWerror)
clangDiags->enableWerror();

if (Opts->ShouldEmitFixItHintsAsRemarks)
clangDiags->enableFixitsAsRemarks();

if (Opts->AnalysisDiagOpt == PD_TEXT) {
clangDiags->enablePaths();

Expand Down
5 changes: 4 additions & 1 deletion clang/test/Analysis/analyzer-config.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
// CHECK-NEXT: ctu-dir = ""
// CHECK-NEXT: ctu-import-threshold = 100
// CHECK-NEXT: ctu-index-name = externalDefMap.txt
// CHECK-NEXT: deadcode.DeadStores:ShowFixIts = false
// CHECK-NEXT: deadcode.DeadStores:WarnForDeadNestedAssignments = true
// CHECK-NEXT: debug.AnalysisOrder:* = false
// CHECK-NEXT: debug.AnalysisOrder:Bind = false
Expand All @@ -54,6 +55,7 @@
// CHECK-NEXT: experimental-enable-naive-ctu-analysis = false
// CHECK-NEXT: exploration_strategy = unexplored_first_queue
// CHECK-NEXT: faux-bodies = true
// CHECK-NEXT: fixits-as-remarks = false
// CHECK-NEXT: graph-trim-interval = 1000
// CHECK-NEXT: inline-lambdas = true
// CHECK-NEXT: ipa = dynamic-bifurcate
Expand All @@ -74,6 +76,7 @@
// CHECK-NEXT: optin.cplusplus.UninitializedObject:NotesAsWarnings = false
// CHECK-NEXT: optin.cplusplus.UninitializedObject:Pedantic = false
// CHECK-NEXT: optin.cplusplus.VirtualCall:PureOnly = false
// CHECK-NEXT: optin.cplusplus.VirtualCall:ShowFixIts = false
// CHECK-NEXT: optin.osx.cocoa.localizability.NonLocalizedStringChecker:AggressiveReport = false
// CHECK-NEXT: optin.performance.Padding:AllowedPad = 24
// CHECK-NEXT: osx.NumberObjectConversion:Pedantic = false
Expand All @@ -94,4 +97,4 @@
// CHECK-NEXT: unroll-loops = false
// CHECK-NEXT: widen-loops = false
// CHECK-NEXT: [stats]
// CHECK-NEXT: num-entries = 91
// CHECK-NEXT: num-entries = 94
32 changes: 18 additions & 14 deletions clang/test/Analysis/dead-stores.c
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \
// RUN: -analyzer-checker=core,deadcode.DeadStores \
// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\
// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested %s
//
// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \
// RUN: -analyzer-checker=core,deadcode.DeadStores \
// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\
// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested \
// RUN: -analyzer-store=region %s
//
// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \
// RUN: -analyzer-checker=core,deadcode.DeadStores \
// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested,nested %s
// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \
// RUN: -analyzer-checker=core,deadcode.DeadStores \
// RUN: -analyzer-config deadcode.DeadStores:ShowFixIts=true \
// RUN: -analyzer-config fixits-as-remarks=true \
// RUN: -analyzer-config \
// RUN: deadcode.DeadStores:WarnForDeadNestedAssignments=false \
// RUN: -verify=non-nested %s

// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \
// RUN: -analyzer-checker=core,deadcode.DeadStores \
// RUN: -analyzer-config deadcode.DeadStores:ShowFixIts=true \
// RUN: -analyzer-config fixits-as-remarks=true \
// RUN: -verify=non-nested,nested %s

void f1() {
int k, y; // non-nested-warning {{unused variable 'k'}}
// non-nested-warning@-1 {{unused variable 'y'}}
int abc = 1;
long idx = abc + 3 * 5; // non-nested-warning {{never read}}
// non-nested-warning@-1 {{unused variable 'idx'}}
// non-nested-remark@-2 {{11-24: ''}}
}

void f2(void *b) {
char *c = (char *)b; // no-warning
char *d = b + 1; // non-nested-warning {{never read}}
// non-nested-warning@-1 {{unused variable 'd'}}
// non-nested-remark@-2 {{10-17: ''}}
printf("%s", c);
// non-nested-warning@-1 {{implicitly declaring library function 'printf' with type 'int (const char *, ...)'}}
// non-nested-note@-2 {{include the header <stdio.h> or explicitly provide a declaration for 'printf'}}
Expand All @@ -50,6 +51,7 @@ void f5() {
int x = 4; // no-warning
int *p = &x; // non-nested-warning {{never read}}
// non-nested-warning@-1 {{unused variable 'p'}}
// non-nested-remark@-2 {{9-13: ''}}
}

int f6() {
Expand Down Expand Up @@ -413,6 +415,7 @@ void f23_pos(int argc, char **argv) {
int shouldLog = (argc > 1);
// non-nested-warning@-1 {{Value stored to 'shouldLog' during its initialization is never read}}
// non-nested-warning@-2 {{unused variable 'shouldLog'}}
// non-nested-remark@-3 {{16-28: ''}}
^{
f23_aux("I did too use it!\n");
}();
Expand All @@ -425,6 +428,7 @@ void f24_A(int y) {
int z = x + y;
// non-nested-warning@-1 {{Value stored to 'z' during its initialization is never read}}
// non-nested-warning@-2 {{unused variable 'z'}}
// non-nested-remark@-3 {{10-17: ''}}
}();
}

Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/edges-new.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,deadcode.DeadStores,osx.cocoa.RetainCount,unix.Malloc,unix.MismatchedDeallocator -analyzer-output=plist -o %t -w %s
// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,deadcode.DeadStores,osx.cocoa.RetainCount,unix.Malloc,unix.MismatchedDeallocator -analyzer-output=plist -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t -w %s
// RUN: %normalize_plist <%t | diff -ub %S/Inputs/expected-plists/edges-new.mm.plist -

//===----------------------------------------------------------------------===//
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/objc-arc.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,osx.cocoa.RetainCount,deadcode -verify -fblocks -analyzer-opt-analyze-nested-blocks -fobjc-arc -analyzer-output=plist-multi-file -o %t.plist %s
// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,osx.cocoa.RetainCount,deadcode -verify -fblocks -analyzer-opt-analyze-nested-blocks -fobjc-arc -analyzer-output=plist-multi-file -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t.plist %s
// RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/objc-arc.m.plist -

typedef signed char BOOL;
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/plist-output.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -analyzer-config eagerly-assume=false %s -analyzer-checker=osx.cocoa.RetainCount,deadcode.DeadStores,core -analyzer-output=plist -o %t.plist
// RUN: %clang_analyze_cc1 -analyzer-config eagerly-assume=false %s -analyzer-checker=osx.cocoa.RetainCount,deadcode.DeadStores,core -analyzer-output=plist -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t.plist
// RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/plist-output.m.plist -

void test_null_init(void) {
Expand Down
45 changes: 45 additions & 0 deletions clang/test/Analysis/virtualcall-fixits.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \
// RUN: -analyzer-config optin.cplusplus.VirtualCall:ShowFixIts=true \
// RUN: %s 2>&1 | FileCheck -check-prefix=TEXT %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \
// RUN: -analyzer-config optin.cplusplus.VirtualCall:ShowFixIts=true \
// RUN: -analyzer-config fixits-as-remarks=true \
// RUN: -analyzer-output=plist -o %t.plist -verify %s
// RUN: cat %t.plist | FileCheck -check-prefix=PLIST %s

struct S {
virtual void foo();
S() {
foo();
// expected-warning@-1{{Call to virtual method 'S::foo' during construction bypasses virtual dispatch}}
// expected-remark@-2{{5-5: 'S::'}}
}
~S();
};

// TEXT: warning: Call to virtual method 'S::foo' during construction
// TEXT-SAME: bypasses virtual dispatch
// TEXT-NEXT: foo();
// TEXT-NEXT: ^~~~~
// TEXT-NEXT: S::
// TEXT-NEXT: 1 warning generated.

// PLIST: <key>fixits</key>
// PLIST-NEXT: <array>
// PLIST-NEXT: <dict>
// PLIST-NEXT: <key>remove_range</key>
// PLIST-NEXT: <array>
// PLIST-NEXT: <dict>
// PLIST-NEXT: <key>line</key><integer>13</integer>
// PLIST-NEXT: <key>col</key><integer>5</integer>
// PLIST-NEXT: <key>file</key><integer>0</integer>
// PLIST-NEXT: </dict>
// PLIST-NEXT: <dict>
// PLIST-NEXT: <key>line</key><integer>13</integer>
// PLIST-NEXT: <key>col</key><integer>4</integer>
// PLIST-NEXT: <key>file</key><integer>0</integer>
// PLIST-NEXT: </dict>
// PLIST-NEXT: </array>
// PLIST-NEXT: <key>insert_string</key><string>S::</string>
// PLIST-NEXT: </dict>
// PLIST-NEXT: </array>