Index: include/clang/Basic/JsonSupport.h =================================================================== --- include/clang/Basic/JsonSupport.h +++ include/clang/Basic/JsonSupport.h @@ -0,0 +1,27 @@ +//===- JsonSupport.h - JSON Output Utilities --------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_BASIC_JSONSUPPORT_H +#define LLVM_CLANG_BASIC_JSONSUPPORT_H + +#include "clang/Basic/LLVM.h" +#include "llvm/Support/raw_ostream.h" + + +namespace clang { + +inline raw_ostream &Indent(raw_ostream &Out, const unsigned int Space, + bool IsDot) { + for (unsigned int I = 0; I < Space * 2; ++I) + Out << (IsDot ? " " : " "); + return Out; +} + +} // namespace clang + +#endif // LLVM_CLANG_BASIC_JSONSUPPORT_H Index: include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h =================================================================== --- include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h +++ include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h @@ -424,10 +424,12 @@ } // Pretty-printing. - void print(raw_ostream &Out, const char *nl = "\n", const char *sep = "", - const LocationContext *CurrentLC = nullptr) const; - void printDOT(raw_ostream &Out, - const LocationContext *CurrentLC = nullptr) const; + void printJson(raw_ostream &Out, const LocationContext *LCtx = nullptr, + const char *NL = "\n", const char *Sep = "", + unsigned int Space = 0, bool IsDot = false) const; + + void printDOT(raw_ostream &Out, const LocationContext *LCtx = nullptr, + unsigned int Space = 0) const; void dump() const; Index: include/clang/StaticAnalyzer/Core/PathSensitive/Store.h =================================================================== --- include/clang/StaticAnalyzer/Core/PathSensitive/Store.h +++ include/clang/StaticAnalyzer/Core/PathSensitive/Store.h @@ -253,7 +253,8 @@ virtual bool scanReachableSymbols(Store S, const MemRegion *R, ScanReachableSymbols &Visitor) = 0; - virtual void print(Store store, raw_ostream &Out, const char* nl) = 0; + virtual void printJson(raw_ostream &Out, Store S, const char *NL, + unsigned int Space, bool IsDot) const = 0; class BindingsHandler { public: Index: lib/StaticAnalyzer/Core/ProgramState.cpp =================================================================== --- lib/StaticAnalyzer/Core/ProgramState.cpp +++ lib/StaticAnalyzer/Core/ProgramState.cpp @@ -440,16 +440,16 @@ // State pretty-printing. //===----------------------------------------------------------------------===// -void ProgramState::print(raw_ostream &Out, - const char *NL, const char *Sep, - const LocationContext *LC) const { +void ProgramState::printJson(raw_ostream &Out, const LocationContext *LCtx, + const char *NL, const char *Sep, + unsigned int Space, bool IsDot) const { // Print the store. ProgramStateManager &Mgr = getStateManager(); const ASTContext &Context = getStateManager().getContext(); - Mgr.getStoreManager().print(getStore(), Out, NL); + Mgr.getStoreManager().printJson(Out, getStore(), NL, Space, IsDot); // Print out the environment. - Env.print(Out, NL, Sep, Context, LC); + Env.print(Out, NL, Sep, Context, LCtx); // Print out the constraints. Mgr.getConstraintManager().print(this, Out, NL, Sep); @@ -458,16 +458,16 @@ printDynamicTypeInfo(this, Out, NL, Sep); // Print checker-specific data. - Mgr.getOwningEngine().printState(Out, this, NL, Sep, LC); + Mgr.getOwningEngine().printState(Out, this, NL, Sep, LCtx); } -void ProgramState::printDOT(raw_ostream &Out, - const LocationContext *LC) const { - print(Out, "\\l", "\\|", LC); +void ProgramState::printDOT(raw_ostream &Out, const LocationContext *LCtx, + unsigned int Space) const { + printJson(Out, LCtx, "\\l", "\\|", Space, /*IsDot=*/true); } LLVM_DUMP_METHOD void ProgramState::dump() const { - print(llvm::errs()); + printJson(llvm::errs()); } AnalysisManager& ProgramState::getAnalysisManager() const { Index: lib/StaticAnalyzer/Core/RegionStore.cpp =================================================================== --- lib/StaticAnalyzer/Core/RegionStore.cpp +++ lib/StaticAnalyzer/Core/RegionStore.cpp @@ -19,6 +19,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Basic/JsonSupport.h" #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -120,21 +121,21 @@ } namespace llvm { - static inline - raw_ostream &operator<<(raw_ostream &os, BindingKey K) { - os << '(' << K.getRegion(); - if (!K.hasSymbolicOffset()) - os << ',' << K.getOffset(); - os << ',' << (K.isDirect() ? "direct" : "default") - << ')'; - return os; - } +static inline raw_ostream &operator<<(raw_ostream &Out, BindingKey K) { + Out << "\"kind\": \"" << (K.isDirect() ? "Direct" : "Default") + << "\", \"offset\": "; + + if (!K.hasSymbolicOffset()) + Out << K.getOffset(); + else + Out << "null"; -} // end llvm namespace + return Out; +} + +} // namespace llvm -#ifndef NDEBUG LLVM_DUMP_METHOD void BindingKey::dump() const { llvm::errs() << *this; } -#endif //===----------------------------------------------------------------------===// // Actual Store type. @@ -206,18 +207,31 @@ return asImmutableMap().getRootWithoutRetain(); } - void dump(raw_ostream &OS, const char *nl) const { - for (iterator I = begin(), E = end(); I != E; ++I) { - const ClusterBindings &Cluster = I.getData(); - for (ClusterBindings::iterator CI = Cluster.begin(), CE = Cluster.end(); - CI != CE; ++CI) { - OS << ' ' << CI.getKey() << " : " << CI.getData() << nl; - } - OS << nl; - } + void printJson(raw_ostream &Out, const char *NL = "\n", + unsigned int Space = 0, bool IsDot = false) const { + for (iterator I = begin(); I != end(); ++I) { + Indent(Out, Space, IsDot) + << "{ \"cluster\": \"" << I.getKey() << "\", \"items\": [" << NL; + + ++Space; + const ClusterBindings &CB = I.getData(); + for (ClusterBindings::iterator CI = CB.begin(); CI != CB.end(); ++CI) { + Indent(Out, Space, IsDot) << "{ " << CI.getKey() << ", \"value\": \"" + << CI.getData() << "\" }"; + if (std::next(CI) != CB.end()) + Out << ','; + Out << NL; + } + + --Space; + Indent(Out, Space, IsDot) << "]}"; + if (std::next(I) != end()) + Out << ','; + Out << NL; + } } - LLVM_DUMP_METHOD void dump() const { dump(llvm::errs(), "\n"); } + LLVM_DUMP_METHOD void dump() const { printJson(llvm::errs()); } }; } // end anonymous namespace @@ -594,7 +608,8 @@ RBFactory.getTreeFactory()); } - void print(Store store, raw_ostream &Out, const char* nl) override; + void printJson(raw_ostream &Out, Store S, const char *NL = "\n", + unsigned int Space = 0, bool IsDot = false) const override; void iterBindings(Store store, BindingsHandler& f) override { RegionBindingsRef B = getRegionBindings(store); @@ -2611,11 +2626,18 @@ // Utility methods. //===----------------------------------------------------------------------===// -void RegionStoreManager::print(Store store, raw_ostream &OS, - const char* nl) { - RegionBindingsRef B = getRegionBindings(store); - OS << "Store (direct and default bindings), " - << B.asStore() - << " :" << nl; - B.dump(OS, nl); +void RegionStoreManager::printJson(raw_ostream &Out, Store S, const char *NL, + unsigned int Space, bool IsDot) const { + RegionBindingsRef Bindings = getRegionBindings(S); + + Indent(Out, Space, IsDot) << "\"store\": "; + + if (Bindings.isEmpty()) { + Out << "null," << NL; + return; + } + + Out << '[' << NL; + Bindings.printJson(Out, NL, ++Space, IsDot); + Indent(Out, --Space, IsDot) << "]," << NL; } Index: test/Analysis/expr-inspection.c =================================================================== --- test/Analysis/expr-inspection.c +++ test/Analysis/expr-inspection.c @@ -1,4 +1,6 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection -verify %s 2>&1 | FileCheck %s +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -verify %s 2>&1 | FileCheck %s // Self-tests for the debug.ExprInspection checker. @@ -10,16 +12,26 @@ clang_analyzer_dump(x); // expected-warning{{reg_$0}} clang_analyzer_dump(x + (-1)); // expected-warning{{(reg_$0) + -1}} int y = 1; - clang_analyzer_printState(); - for (; y < 3; ++y) + for (; y < 3; ++y) { clang_analyzer_numTimesReached(); // expected-warning{{2}} + + if (y == 2) { + int z = x > 13; + if (!z) + clang_analyzer_printState(); + } + } } -// CHECK: Store (direct and default bindings) -// CHECK-NEXT: (y,0,direct) : 1 S32b +// CHECK: "store": [ +// CHECK-NEXT: { "cluster": "y", "items": [ +// CHECK-NEXT: { "kind": "Direct", "offset": 0, "value": "2 S32b" } +// CHECK-NEXT: ]} +// CHECK-NEXT: ] -// CHECK: Expressions by stack frame: +// CHECK: Expressions by stack frame: // CHECK-NEXT: #0 Calling foo -// CHECK-NEXT: clang_analyzer_printState : &code{clang_analyzer_printState} +// CHECK-NEXT: (LC1, S847) clang_analyzer_printState : &code{clang_analyzer_printState} -// CHECK: {{(Ranges are empty.)|(Constraints:[[:space:]]*$)}} +// CHECK: Ranges of symbol values: +// CHECK-NEXT: reg_$0 : { [-2147483648, 13] }