NoQ created this revision. NoQ added reviewers: dcoughlin, xazax.hun, a_sidorin, rnkovacs, Szelethus, baloghadamsoftware. Herald added subscribers: cfe-commits, Charusso, dkrupp, donat.nagy, mikhail.ramalho, a.sidorin, szepet, kristof.beyls, javed.absar. Herald added a project: clang.
That's a pretty wonky experiment, even if a fairly easy one. I'm trying to add support for fix-it hints to the bug reporter. In the current shape the patch does the following: - Allow attaching fixit hints to Static Analyzer `BugReport`s. - Add support for fixits in text output (including the default text output that goes without notes, as the fixit "belongs" to the warning). - Add support for fixits in the plist output mode (not sure if i'm fully supporting all kinds of fixits). - Implement a fixit for the path-insensitive DeadStores checker (only one of the cases, and i'm still not sure it's a good fixit, but it was an obvious first thing to experiment with). - Implement a fixit for the path-sensitive VirtualCall checker when the virtual method is not pure virtual (in this case the "fix" is to suppress the warning by qualifying the call). More TODOs: - We don't have a good way to test removal fixits (such as the one in the dead stores checker). Testing plist files is not a good way to test them (though we definitely need a few such tests). We can't test them via text output because clang itself generally doesn't display removal fixits in text output (it's kinda obvious if you look at how it prints them). In clang-tidy they use a more sophisticated facility for these tests, testing the fixed file instead, but it's deep within their custom testing scripts. We might need something similar. - HTML output still doesn't support fixits. Not sure if they are useful in there because fixits are generally not very useful when there's no button to apply them. But there should be no harm in displaying them anyway, so i hope i'll have time to take a look. - Need more tests with macros and such stuff. Repository: rC Clang https://reviews.llvm.org/D65182 Files: clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp clang/lib/StaticAnalyzer/Core/BugReporter.cpp clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist clang/test/Analysis/Inputs/expected-plists/objc-arc.m.plist clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist
Index: clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist =================================================================== --- clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist +++ clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist @@ -2190,6 +2190,26 @@ <integer>86</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>86</integer> + <key>col</key><integer>11</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>86</integer> + <key>col</key><integer>40</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> Index: clang/test/Analysis/Inputs/expected-plists/objc-arc.m.plist =================================================================== --- clang/test/Analysis/Inputs/expected-plists/objc-arc.m.plist +++ clang/test/Analysis/Inputs/expected-plists/objc-arc.m.plist @@ -403,6 +403,26 @@ <integer>119</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>119</integer> + <key>col</key><integer>7</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>119</integer> + <key>col</key><integer>25</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -471,6 +491,26 @@ <integer>139</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>139</integer> + <key>col</key><integer>10</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>139</integer> + <key>col</key><integer>53</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -539,6 +579,26 @@ <integer>144</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>144</integer> + <key>col</key><integer>10</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>144</integer> + <key>col</key><integer>45</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -607,6 +667,26 @@ <integer>145</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>145</integer> + <key>col</key><integer>10</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>145</integer> + <key>col</key><integer>44</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -675,6 +755,26 @@ <integer>146</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>146</integer> + <key>col</key><integer>10</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>146</integer> + <key>col</key><integer>48</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -1085,6 +1185,26 @@ <integer>150</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>150</integer> + <key>col</key><integer>16</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>150</integer> + <key>col</key><integer>64</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -1153,6 +1273,26 @@ <integer>151</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>151</integer> + <key>col</key><integer>18</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>151</integer> + <key>col</key><integer>67</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -1221,6 +1361,26 @@ <integer>152</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>152</integer> + <key>col</key><integer>16</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>152</integer> + <key>col</key><integer>55</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> @@ -1289,6 +1449,26 @@ <integer>153</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>153</integer> + <key>col</key><integer>18</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>153</integer> + <key>col</key><integer>58</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> Index: clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist =================================================================== --- clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist +++ clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist @@ -11430,6 +11430,26 @@ <integer>431</integer> </array> </dict> + <key>fixits</key> + <array> + <dict> + <key>remove_range</key> + <array> + <dict> + <key>line</key><integer>431</integer> + <key>col</key><integer>11</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>431</integer> + <key>col</key><integer>40</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>insert_string</key> + <string></string> + </dict> + </array> </dict> <dict> <key>path</key> Index: clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp =================================================================== --- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -116,8 +116,9 @@ 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(); + Diag.Report(WarnLoc, WarnID) + << PD->getShortDescription() << PD->path.back()->getRanges() + << PD->getFixits(); // First, add extra notes, even if paths should not be included. for (const auto &Piece : PD->path) { Index: clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -726,6 +726,22 @@ printCoverage(D, /*IndentLevel=*/2, Fids, FM, o); + if (!D->getFixits().empty()) { + o << " <key>fixits</key>\n"; + o << " <array>\n"; + for (auto Hint : D->getFixits()) { + assert(!Hint.isNull()); + o << " <dict>\n"; + o << " <key>remove_range</key>\n"; + EmitRange(o, SM, Lexer::getAsCharRange(Hint.RemoveRange, SM, LangOpts), + FM, 4); + o << " <key>insert_string</key>\n"; + o << " <string>" << Hint.CodeToInsert << "</string>\n"; + o << " </dict>\n"; + } + o << " </array>\n"; + } + // Close up the entry. o << " </dict>\n"; } Index: clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -134,14 +134,15 @@ StringRef CheckName, const Decl *declWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, - std::unique_ptr<FilesToLineNumsMap> ExecutedLines) + std::unique_ptr<FilesToLineNumsMap> ExecutedLines, + ArrayRef<FixItHint> Fixits) : CheckName(CheckName), DeclWithIssue(declWithIssue), BugType(StripTrailingDots(bugtype)), VerboseDesc(StripTrailingDots(verboseDesc)), ShortDesc(StripTrailingDots(shortDesc)), Category(StripTrailingDots(category)), UniqueingLoc(LocationToUnique), UniqueingDecl(DeclToUnique), ExecutedLines(std::move(ExecutedLines)), - path(pathImpl) {} + Fixits(Fixits.begin(), Fixits.end()), path(pathImpl) {} static PathDiagnosticCallPiece * getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, Index: clang/lib/StaticAnalyzer/Core/BugReporter.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -1261,7 +1261,7 @@ R->getBugType().getName(), R->getDescription(), R->getShortDescription(/*Fallback=*/false), BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), - findExecutedLines(SM, R->getErrorNode())); + findExecutedLines(SM, R->getErrorNode()), R->getFixits()); } static const Stmt *getStmtParent(const Stmt *S, const ParentMap &PM) { @@ -3121,23 +3121,26 @@ const CheckerBase *Checker, StringRef Name, StringRef Category, StringRef Str, PathDiagnosticLocation Loc, - ArrayRef<SourceRange> Ranges) { + 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) { + CheckName CheckName, StringRef name, + StringRef category, StringRef str, + PathDiagnosticLocation Loc, + ArrayRef<SourceRange> Ranges, + ArrayRef<FixItHint> Fixits) { // 'BT' is owned by BugReporter. BugType *BT = getBugTypeForName(CheckName, name, category); auto R = llvm::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 (auto SR : Ranges) + R->addRange(SR); + for (auto FH : Fixits) + R->addFixItHint(FH); emitReport(std::move(R)); } Index: clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -141,6 +141,11 @@ return; auto Report = llvm::make_unique<BugReport>(BT, OS.str(), N); + if (!IsPure) { + FixItHint Fixit = FixItHint::CreateInsertion( + CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::"); + Report->addFixItHint(Fixit); + } C.emitReport(std::move(Report)); } Index: clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" @@ -202,12 +203,23 @@ llvm::raw_svector_ostream os(buf); const char *BugType = nullptr; + SmallVector<FixItHint, 1> Fixits; + switch (dsk) { - case DeadInit: + case DeadInit: { BugType = "Dead initialization"; os << "Value stored to '" << *V << "' during its initialization is never read"; + SourceLocation L1 = Lexer::findNextToken( + V->getTypeSourceInfo()->getTypeLoc().getEndLoc(), + V->getASTContext().getSourceManager(), + V->getASTContext().getLangOpts())->getEndLoc(); + SourceLocation L2 = Lexer::getLocForEndOfToken( + V->getInit()->getEndLoc(), 1, V->getASTContext().getSourceManager(), + V->getASTContext().getLangOpts()); + Fixits.push_back(FixItHint::CreateRemoval({L1, L2})); break; + } case DeadIncrement: BugType = "Dead increment"; @@ -225,7 +237,7 @@ } BR.EmitBasicReport(AC->getDecl(), Checker, BugType, "Dead store", os.str(), - L, R); + L, R, Fixits); } void CheckVarDecl(const VarDecl *VD, const Expr *Ex, const Expr *Val, Index: clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -804,13 +804,16 @@ /// Lines executed in the path. std::unique_ptr<FilesToLineNumsMap> ExecutedLines; + SmallVector<FixItHint, 4> Fixits; + public: PathDiagnostic() = delete; PathDiagnostic(StringRef CheckName, const Decl *DeclWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, - std::unique_ptr<FilesToLineNumsMap> ExecutedLines); + std::unique_ptr<FilesToLineNumsMap> ExecutedLines, + ArrayRef<FixItHint> Fixits); ~PathDiagnostic(); const PathPieces &path; @@ -864,6 +867,8 @@ StringRef getBugType() const { return BugType; } StringRef getCategory() const { return Category; } + ArrayRef<FixItHint> getFixits() const { return Fixits; } + /// Return the semantic context where an issue occurred. If the /// issue occurs along a path, this represents the "central" area /// where the bug manifests. Index: clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -107,6 +107,8 @@ ExtraTextList ExtraText; NoteList Notes; + SmallVector<FixItHint, 4> Fixits; + using Symbols = llvm::DenseSet<SymbolRef>; using Regions = llvm::DenseSet<const MemRegion *>; @@ -333,9 +335,17 @@ Ranges.push_back(R); } + void addFixItHint(FixItHint F) { + Fixits.push_back(F); + } + /// Get the SourceRanges associated with the report. virtual llvm::iterator_range<ranges_iterator> getRanges(); + virtual llvm::ArrayRef<FixItHint> getFixits() { + return Fixits; + } + /// Add custom or predefined bug report visitors to this report. /// /// The visitors should be used when the default trace is not sufficient. @@ -502,12 +512,14 @@ void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, - ArrayRef<SourceRange> Ranges = None); + ArrayRef<SourceRange> Ranges = None, + ArrayRef<FixItHint> Fixits = None); void EmitBasicReport(const Decl *DeclWithIssue, CheckName CheckName, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, - ArrayRef<SourceRange> Ranges = None); + ArrayRef<SourceRange> Ranges = None, + ArrayRef<FixItHint> Fixits = None); private: llvm::StringMap<BugType *> StrBugTypes;
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits