george.karpenkov created this revision. Herald added subscribers: a.sidorin, szepet, xazax.hun.
This patch is work in progress, but has already shown itself to be useful in a few cases. For large traces understanding the analyzer output can be painful, especially when arrows indicating the flow start to loop around. This patch provides an alternative output method: simply iterate over all program points in a bug report, and dump them to the associated C statements (with program-line-based granularity). In the ideal case (which we currently don't aim to get) the resulting program is a refinement of an input which goes through the indicated error path. In the current case, it is a C-like visualization (as some statements are unexpectedly dropped, and some context is missing) of what the error path is. Unresolved issues: - I haven't found a way to run a visitor for all incoming reports, seems that each visitor has to attach a visitor manually. Maybe that functionality would have to be added. - This needs to be behind a flag, still haven't thought of a good name. - Always dumping to stderr is bad, this should go into a file, which should somehow get synced to the plist/html/stderr output. - Stderr output might be confusing in presence of multiple reports, as the current code will dump all counterexamples first, and then will actually start showing the warnings. - In the default driver mode, all counterexamples will be dumped twice (presumably because visitor is invoked for HTML and for PLIST output) https://reviews.llvm.org/D40809 Files: include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
Index: lib/StaticAnalyzer/Core/BugReporterVisitors.cpp =================================================================== --- lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -11,6 +11,8 @@ // enhance the diagnostics reported for a bug. // //===----------------------------------------------------------------------===// +#include <cmath> +#include <string.h> #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" @@ -24,6 +26,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/SetVector.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -1878,3 +1881,150 @@ return std::move(Piece); } + +/// Export the counterexample to C. +class PrinterVisitor final : public BugReporterVisitorImpl<PrinterVisitor> { + + /// File ID followed by a line number. + llvm::SetVector<std::pair<FileID, unsigned>> VisitedLines; + mutable bool DonePrinting = false; + + /// Output stream to write into. + raw_ostream &Ostream; + + /// Max number of chars used to display the filename. + static const unsigned MAX_FILENAME_MARGIN = 80; + static const unsigned MARGIN_SEP_LEN = 1; + +public: + PrinterVisitor(raw_ostream &Ostream) : Ostream(Ostream) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int Tag = 0; + ID.AddPointer(&Tag); + } + + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override { + SourceManager &SM = BRC.getSourceManager(); + + if (DonePrinting) { // Printing was done, do nothing. + return nullptr; + } else if (!PrevN->getFirstPred()) { // Finished report, do printing. + + // Went through the entire bug report, now print the lines in reverse + // order. + bool Status = doPrinting(SM); + if (!Status) + Ostream << "Error while printing\n"; + Ostream << "/* ============================================== */\n"; + DonePrinting = true; + return nullptr; + } else if (Optional<PostStmt> PS = N->getLocation().getAs<PostStmt>()) { + + // Statement location with an associated SourceLocation object. + SourceLocation Loc = PS->getStmt()->getSourceRange().getBegin(); + + // FIXME: might be necessary to insert the spelling location as well. + insertLoc(SM, SM.getExpansionLoc(Loc)); + } + return nullptr; + } + +private: + /// Print locations serialized to \c VisitedLines. + bool doPrinting(SourceManager &SM) { + unsigned Margin = calculateMargin(SM); + for (auto It = VisitedLines.rbegin(), + ItEnd = VisitedLines.rend(); + It != ItEnd; ++It) { + FileID FID = It->first; + unsigned LineNo = It->second; + bool Status = printLine(SM, FID, LineNo, Margin); + if (!Status) + return false; + } + return true; + } + + /// Print line \p LineNo from file \p FID, with left margin \p Margin. + /// \return \c true on success, \c false on failure. + bool printLine(SourceManager &SM, FileID FID, + unsigned LineNo, unsigned Margin) { + SourceLocation Location = SM.translateLineCol(FID, LineNo, 1); + unsigned Offset = SM.getFileOffset(Location); + + // Print location first. + int Status = printLocation(SM, Location, Margin); + if (!Status) + return false; + + bool Invalid = false; + const char *BufferStart = SM.getBufferData(FID, &Invalid).data(); + if (Invalid) + return false; + + unsigned FileEndOffset = SM.getFileOffset(SM.getLocForEndOfFile(FID)); + for (unsigned i=Offset; BufferStart[i] != '\n' && i < FileEndOffset; ++i) + Ostream << BufferStart[i]; + + Ostream << "\n"; + return true; + } + + /// Print location in left margin. + bool printLocation(SourceManager &SM, SourceLocation Loc, unsigned Margin) { + Loc = SM.getExpansionLoc(Loc); + PresumedLoc PLoc = SM.getPresumedLoc(Loc); + if (PLoc.isInvalid()) + return false; + + uint64_t SavedPos = Ostream.tell(); + Ostream << PLoc.getFilename() << ":" << PLoc.getLine(); + uint64_t Delta = Ostream.tell() - SavedPos; + assert(Margin >= Delta); + for (unsigned i=Delta; i<Margin; i++) + Ostream << " "; + Ostream << "|"; + + return true; + } + + /// Serialize source location \p Loc to \c visitedLines. + void insertLoc(SourceManager &SM, SourceLocation Loc) { + VisitedLines.insert( + std::make_pair(SM.getFileID(Loc), SM.getSpellingLineNumber(Loc))); + } + + /// \return smallest margin required to fit all filenames present + /// in the counterexample. + unsigned calculateMargin(SourceManager &SM) { + unsigned Out = 0; + for (auto Pair : VisitedLines) { + FileID FID = Pair.first; + unsigned LineNo = Pair.second; + SourceLocation Loc = SM.translateLineCol(FID, LineNo, 1); + Loc = SM.getExpansionLoc(Loc); + PresumedLoc PLoc = SM.getPresumedLoc(Loc); + if (PLoc.isInvalid()) + continue; + + const char *Filename = PLoc.getFilename(); + + // Take logarithm to figure out number of chars required. + long LineMargin = 1 + std::lround(floor(log10(PLoc.getLine()))); + assert(LineMargin > 0); + unsigned RequiredMargin = strnlen(Filename, MAX_FILENAME_MARGIN) + + LineMargin + MARGIN_SEP_LEN; + if (RequiredMargin > Out) + Out = RequiredMargin; + } + return Out; + } +}; + +void bugreporter::printCounterexample(BugReport &report) { + report.addVisitor(llvm::make_unique<PrinterVisitor>(llvm::errs())); +} Index: lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -95,6 +95,7 @@ if (ex) { R->addRange(ex->getSourceRange()); bugreporter::trackNullOrUndefValue(N, ex, *R); + bugreporter::printCounterexample(*R); } C.emitReport(std::move(R)); } Index: include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h =================================================================== --- include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -365,6 +365,9 @@ bool IsArg = false, bool EnableNullFPSuppression = true); +/// Print the counterexample as a C file. +void printCounterexample(BugReport &report); + const Expr *getDerefExpr(const Stmt *S); const Stmt *GetDenomExpr(const ExplodedNode *N); const Stmt *GetRetValExpr(const ExplodedNode *N);
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits