Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -312,6 +312,9 @@ /// \sa shouldDisplayNotesAsEvents Optional DisplayNotesAsEvents; + /// \sa shouldDisplayMacroExpansions + Optional DisplayMacroExpansions; + /// \sa shouldAggressivelySimplifyBinaryOperation Optional AggressiveBinaryOperationSimplification; @@ -687,6 +690,13 @@ /// to false when unset. bool shouldDisplayNotesAsEvents(); + /// Returns true if macros related to the bugpath should be expanded and + /// included in the plist output. + /// + /// This is controlled by the 'expand-macros' option, which defaults to false + /// when unset. + bool shouldDisplayMacroExpansions(); + /// Returns true if SValBuilder should rearrange comparisons and additive /// operations of symbolic expressions which consist of a sum of a symbol and /// a concrete integer into the format where symbols are on the left-hand Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -465,6 +465,13 @@ return DisplayNotesAsEvents.getValue(); } +bool AnalyzerOptions::shouldDisplayMacroExpansions() { + if (!DisplayMacroExpansions.hasValue()) + DisplayMacroExpansions = + getBooleanOption("expand-macros", /*Default=*/false); + return DisplayMacroExpansions.getValue(); +} + bool AnalyzerOptions::shouldAggressivelySimplifyBinaryOperation() { if (!AggressiveBinaryOperationSimplification.hasValue()) AggressiveBinaryOperationSimplification = Index: lib/StaticAnalyzer/Core/BugReporter.cpp =================================================================== --- lib/StaticAnalyzer/Core/BugReporter.cpp +++ lib/StaticAnalyzer/Core/BugReporter.cpp @@ -546,7 +546,8 @@ } } -static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM); +static void CompactMacroExpandedPieces(PathPieces &path, + const SourceManager& SM); std::shared_ptr generateDiagForSwitchOP( @@ -1972,8 +1973,6 @@ PathDiagnosticLocation::createBegin(D, SM)); } - if (!AddPathEdges && GenerateDiagnostics) - CompactPathDiagnostic(PD->getMutablePieces(), SM); // Finally, prune the diagnostic path of uninteresting stuff. if (!PD->path.empty()) { @@ -2007,6 +2006,10 @@ removeRedundantMsgs(PD->getMutablePieces()); removeEdgesToDefaultInitializers(PD->getMutablePieces()); } + + if (GenerateDiagnostics && Opts.shouldDisplayMacroExpansions()) + CompactMacroExpandedPieces(PD->getMutablePieces(), SM); + return PD; } @@ -2436,9 +2439,10 @@ return true; } -/// CompactPathDiagnostic - This function postprocesses a PathDiagnostic object -/// and collapses PathDiagosticPieces that are expanded by macros. -static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) { +/// CompactMacroExpandedPieces - This function postprocesses a PathDiagnostic +/// object and collapses PathDiagosticPieces that are expanded by macros. +static void CompactMacroExpandedPieces(PathPieces &path, + const SourceManager& SM) { using MacroStackTy = std::vector< std::pair, SourceLocation>>; @@ -2454,7 +2458,7 @@ // Recursively compact calls. if (auto *call = dyn_cast(&*piece)) { - CompactPathDiagnostic(call->path, SM); + CompactMacroExpandedPieces(call->path, SM); } // Get the location of the PathDiagnosticPiece. Index: lib/StaticAnalyzer/Core/PlistDiagnostics.cpp =================================================================== --- lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -246,7 +246,7 @@ ReportPiece(o, *callEnterWithinCaller, FM, PP, AnOpts, indent, depth, /*includeControlFlow*/ true); - for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) + for (auto I = P.path.begin(), E = P.path.end(); I!= E; ++I) ReportPiece(o, **I, FM, PP, AnOpts, indent, depth, /*includeControlFlow*/ true); @@ -257,6 +257,23 @@ /*includeControlFlow*/ true); } +namespace { + +struct ExpansionInfo { + std::string MacroName; + std::string Expansion; + ExpansionInfo(std::string N, std::string E) + : MacroName(std::move(N)), Expansion(std::move(E)) {} +}; + +} // end of anonymous namespace + +static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc, + const Preprocessor &PP) { + // TODO: Implement macro expansion. + return { "", "" }; +} + static void ReportMacro(raw_ostream &o, const PathDiagnosticMacroPiece& P, const FIDMap& FM, @@ -264,12 +281,34 @@ AnalyzerOptions &AnOpts, unsigned indent, unsigned depth) { + const SourceManager &SM = PP.getSourceManager(); + ExpansionInfo EI = getExpandedMacro(P.getLocation().asLocation(), PP); - for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); - I!=E; ++I) { - ReportPiece(o, **I, FM, PP, AnOpts, indent, depth, - /*includeControlFlow*/ false); - } + Indent(o, indent) << "\n"; + ++indent; + + // Output the location. + FullSourceLoc L = P.getLocation().asLocation(); + + Indent(o, indent) << "location\n"; + EmitLocation(o, SM, L, FM, indent); + + // Output the ranges (if any). + ArrayRef Ranges = P.getRanges(); + EmitRanges(o, Ranges, FM, PP, indent); + + // Output the macro name. + Indent(o, indent) << "name"; + EmitString(o, EI.MacroName) << '\n'; + + // Output what it expands into. + Indent(o, indent) << "expansion"; + EmitString(o, EI.Expansion) << '\n'; + + // Finish up. + --indent; + Indent(o, indent); + o << "\n"; } static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, @@ -309,6 +348,89 @@ /*includeControlFlow*/ true); } +/// The bugpath we can access is const, but we need to modify the bugpath +/// in order to flatten macros. Because copying shared_ptrs isn't cheap, we'll +/// store raw pointers to the Pieces, as their lifetime will be at least as long +/// as the PlistDiagnostics object. +using MutablePathPieces = std::list; + +/// Converts a const PathPieces object to a mutable, raw pointer storing +/// containter. +MutablePathPieces getMutablePathPieces(const PathPieces &Path) { + auto RetrievePrt = [] (const std::shared_ptr &Piece) { + return Piece.get(); + }; + + MutablePathPieces Ret(Path.size()); + std::transform(Path.begin(), Path.end(), Ret.begin(), RetrievePrt); + return Ret; +} + +/// Returns an iterator to the first non-note element in Path. +MutablePathPieces::const_iterator +getFirstNonNotePiece(const MutablePathPieces &Path) { + + auto IsNote = [](const PathDiagnosticPiece *E) { + return E->getKind() == PathDiagnosticPiece::Note; + }; + + assert(std::is_partitioned(Path.begin(), Path.end(), IsNote) && + "PathDiagnostic is not partitioned so that notes precede the rest"); + + return std::partition_point(Path.begin(), Path.end(), IsNote); +} + +/// Report the note pieces in Path. +static void ReportNotes(raw_ostream &o, + const MutablePathPieces& Path, + const FIDMap& FM, + const Preprocessor &PP, + AnalyzerOptions &AnOpts) { + auto FirstNonNote = getFirstNonNotePiece(Path); + + if (FirstNonNote != Path.begin()) { + o << " notes\n" + " \n"; + + for (auto NoteIt = Path.begin(); NoteIt != FirstNonNote; ++NoteIt) + ReportDiag(o, **NoteIt, FM, PP, AnOpts); + + o << " \n"; + } +} + +/// Report macro pieces in Path, then replace them with their subpieces. +static void ReportAndFlattenMacros(raw_ostream &o, + MutablePathPieces& Path, + const FIDMap& FM, + const Preprocessor &PP, + AnalyzerOptions &AnOpts) { + MutablePathPieces MacroPieces; + for (auto It = getFirstNonNotePiece(Path); It != Path.end();) { + + const auto *MacroPiece = dyn_cast(*It); + if (!MacroPiece) { + ++It; + continue; + } + + Path.splice(It, getMutablePathPieces(MacroPiece->subPieces)); + const auto MacroIt = It++; + MacroPieces.splice(MacroPieces.end(), Path, MacroIt); + } + + if (MacroPieces.empty()) + return; + + o << " macro_expansions\n" + " \n"; + + for (const PathDiagnosticPiece *MacroPiece : MacroPieces) + ReportDiag(o, *MacroPiece, FM, PP, AnOpts); + + o << " \n"; +} + static void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P, const FIDMap& FM, @@ -443,42 +565,23 @@ o << " diagnostics\n" " \n"; - for (std::vector::iterator DI=Diags.begin(), - DE = Diags.end(); DI!=DE; ++DI) { + for (std::vector::iterator DI = Diags.begin(), + DE = Diags.end(); DI != DE; ++DI) { o << " \n"; const PathDiagnostic *D = *DI; - const PathPieces &Path = D->path; - - assert(std::is_partitioned( - Path.begin(), Path.end(), - [](const std::shared_ptr &E) - { return E->getKind() == PathDiagnosticPiece::Note; }) && - "PathDiagnostic is not partitioned so that notes precede the rest"); - - PathPieces::const_iterator FirstNonNote = std::partition_point( - Path.begin(), Path.end(), - [](const std::shared_ptr &E) - { return E->getKind() == PathDiagnosticPiece::Note; }); + MutablePathPieces Path = getMutablePathPieces(D->path); - PathPieces::const_iterator I = Path.begin(); + ReportNotes(o, Path, FM, PP, AnOpts); + ReportAndFlattenMacros(o, Path, FM, PP, AnOpts); - if (FirstNonNote != Path.begin()) { - o << " notes\n" - " \n"; - - for (; I != FirstNonNote; ++I) - ReportDiag(o, **I, FM, PP, AnOpts); - - o << " \n"; - } + auto I = getFirstNonNotePiece(Path); o << " path\n"; - o << " \n"; - for (PathPieces::const_iterator E = Path.end(); I != E; ++I) + for (auto E = Path.end(); I != E; ++I) ReportDiag(o, **I, FM, PP, AnOpts); o << " \n"; Index: test/Analysis/Inputs/expected-plists/plist-macros-with-expansion.cpp.plist =================================================================== --- /dev/null +++ test/Analysis/Inputs/expected-plists/plist-macros-with-expansion.cpp.plist @@ -0,0 +1,419 @@ + + + + + diagnostics + + + macro_expansions + + + location + + line26 + col3 + file0 + + name + expansion + + + path + + + kindcontrol + edges + + + start + + + line25 + col3 + file0 + + + line25 + col5 + file0 + + + end + + + line26 + col3 + file0 + + + line26 + col21 + file0 + + + + + + + kindevent + location + + line26 + col3 + file0 + + ranges + + + + line26 + col3 + file0 + + + line26 + col21 + file0 + + + + depth0 + extended_message + Null pointer value stored to 'ptr' + message + Null pointer value stored to 'ptr' + + + kindcontrol + edges + + + start + + + line26 + col3 + file0 + + + line26 + col21 + file0 + + + end + + + line27 + col3 + file0 + + + line27 + col3 + file0 + + + + + + + kindcontrol + edges + + + start + + + line27 + col3 + file0 + + + line27 + col3 + file0 + + + end + + + line27 + col8 + file0 + + + line27 + col8 + file0 + + + + + + + kindevent + location + + line27 + col8 + file0 + + ranges + + + + line27 + col4 + file0 + + + line27 + col6 + file0 + + + + depth0 + extended_message + Dereference of null pointer (loaded from variable 'ptr') + message + Dereference of null pointer (loaded from variable 'ptr') + + + descriptionDereference of null pointer (loaded from variable 'ptr') + categoryLogic error + typeDereference of null pointer + check_namecore.NullDereference + + issue_hash_content_of_line_in_contextf8fbc46cc5afbb056d92bd3d3d702781 + issue_context_kindfunction + issue_contextnonFunctionLikeMacroTest + issue_hash_function_offset3 + location + + line27 + col8 + file0 + + ExecutedLines + + 0 + + 24 + 25 + 26 + 27 + + + + + macro_expansions + + + location + + line39 + col3 + file0 + + name + expansion + + + path + + + kindcontrol + edges + + + start + + + line38 + col3 + file0 + + + line38 + col5 + file0 + + + end + + + line39 + col3 + file0 + + + line39 + col39 + file0 + + + + + + + kindevent + location + + line39 + col3 + file0 + + ranges + + + + line39 + col3 + file0 + + + line39 + col39 + file0 + + + + depth0 + extended_message + Null pointer value stored to 'ptr' + message + Null pointer value stored to 'ptr' + + + kindcontrol + edges + + + start + + + line39 + col3 + file0 + + + line39 + col39 + file0 + + + end + + + line40 + col3 + file0 + + + line40 + col3 + file0 + + + + + + + kindcontrol + edges + + + start + + + line40 + col3 + file0 + + + line40 + col3 + file0 + + + end + + + line40 + col8 + file0 + + + line40 + col8 + file0 + + + + + + + kindevent + location + + line40 + col8 + file0 + + ranges + + + + line40 + col4 + file0 + + + line40 + col6 + file0 + + + + depth0 + extended_message + Dereference of null pointer (loaded from variable 'ptr') + message + Dereference of null pointer (loaded from variable 'ptr') + + + descriptionDereference of null pointer (loaded from variable 'ptr') + categoryLogic error + typeDereference of null pointer + check_namecore.NullDereference + + issue_hash_content_of_line_in_contextd5eba61193b41c27fc7b2705cbd607ba + issue_context_kindfunction + issue_contextnonFunctionLikeNestedMacroTest + issue_hash_function_offset3 + location + + line40 + col8 + file0 + + ExecutedLines + + 0 + + 37 + 38 + 39 + 40 + + + + + files + + /home/szelethus/Documents/macro_expansion/clang/test/Analysis/plist-macros-with-expansion.cpp + + + Index: test/Analysis/plist-macros-with-expansion.cpp =================================================================== --- /dev/null +++ test/Analysis/plist-macros-with-expansion.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s +// +// RUN: %clang_analyze_cc1 -analyzer-checker=core %s \ +// RUN: -analyzer-output=plist -o %t.plist \ +// RUN: -analyzer-config expand-macros=true +// +// Check the actual plist output. +// RUN: cat %t.plist | %diff_plist \ +// RUN: %S/Inputs/expected-plists/plist-macros-with-expansion.cpp.plist +// +// Check the macro expansions from the plist output here, to make the test more +// understandable. +// RUN: FileCheck --input-file=%t.plist %s + +void print(const void*); + +//===----------------------------------------------------------------------===// +// Tests for non-function-like macro expansions. +//===----------------------------------------------------------------------===// + +#define SET_PTR_VAR_TO_NULL \ + ptr = 0 + +void nonFunctionLikeMacroTest() { + int *ptr; + SET_PTR_VAR_TO_NULL; + *ptr = 5; // expected-warning{{Dereference of null pointer}} +} + +// CHECK: name +// CHECK-NEXT: expansion + +#define NULL 0 +#define SET_PTR_VAR_TO_NULL_WITH_NESTED_MACRO \ + ptr = NULL + +void nonFunctionLikeNestedMacroTest() { + int *ptr; + SET_PTR_VAR_TO_NULL_WITH_NESTED_MACRO; + *ptr = 5; // expected-warning{{Dereference of null pointer}} +} + +// CHECK: name +// CHECK-NEXT: expansion