NoQ created this revision. NoQ added reviewers: zaks.anna, dcoughlin, xazax.hun. NoQ added a subscriber: cfe-commits.
It seems that in several places in the code Clang Static Analyzer tries to recursively traverse the `SVal` hierarchy, so i made a visitor for `SVal`, `SymExpr`, and `MemRegion` hierarchies. Actually, three separate visitors, but they're rarely useful on their own, so there's `FullSValVisitor` to merge the three for visiting the whole thing. The approach was literally copied from `StmtVisitor` etc in an obvious manner. One thing that could make the visitor a lot more useful, which i'd probably love to implement, is a simple re-usable `VisitChildren()` method (in case the visitor's return type is `void`). Because we cannot write such method in every visitor as easily as we do it for, say, `StmtVisitor` (we don't have a full-featured iterator for child values/symbols/regions). This would allow a trivial implementation of methods like "find all `ElementRegion`'s inside this `SVal`, and mark their indices"). To-think: how should such method handle lazy compound values? This review is a bit green, in a sense that there's not much actually delivered yet, apart from the visitor header itself. Some further todo's would be: - Refactor some pieces of the code to use the visitor. In fact, we already have the `SymbolVisitor` class somewhere. Probably `SymbolReaper` could be simplified. - Some checkers, that rely on exploring the hierarchy, may be making use of the visitor. Even if existing checkers don't use it, developers of new checkers may like it. - The object responsible for this alpha-renaming thing would most likely look like a `FullSValVisitor` that returns an `SVal`. - Not sure, maybe split the three visitors into three different header files? In order to make sure that the visitor header compiles, i started a simple example visitor - the `SValExplainer`. It explains symbolic values in a human-readable manner, returning an `std::string`. `SValExplainer` can be used: - for pretty-printing values to the analyzer's end-user, eg. in checker warning messages, or even in "[assuming ...]" diagnostic pieces instead of pretty-printed expressions. - for deep-testing analyzer internals, when the test needs to ensure that a particular kind of `SVal` is produced durign analysis. In fact, one of the tests a FIXME test that exposes a certain problem in the core. - as a documentation for `SVal` kinds (because novice checker developers are often confused about the meaning of different `SVal` kinds). Users may also rely on it to understand how the analyzer works during debugging, eg. quickly explain what does this particular `SVal` they obtained in a certain callback actually means. Todos for `SValExplainer` include: - Explaining more values. In particular, i could use a bit of advice for Objective-C-specific values, because i know very little about this language. I might have also forgotten something. Memory spaces are worth it, most likely. - Improving natural language. Probably some bugs would be exposed later. Not sure if the long "of"-chains the explainer produces sound naturally. - Probably add various constructor-time flags if there are multiple users of the explainer having different expectations. In order to test `SValExplainer`, a new callback was added to the `debug.ExprInspectionChecker`, namely `clang_analyzer_explain()`, that causes an explanation of its argument value to be printed as a warning message. I also added another callback - `clang_analyzer_getExtent()` in order to obtain `SymbolExtent` for testing. Testing how extents are modeled would probably be useful later as well. Regexps are used in the tests in order to match the start and the end of the warning message. So, essentially, i'm humbly requesting a quick glance on this code, if this facility is useful, if some stuff is clearly useless, and whether any of the todos are actually wanted. I'd probably make more updates in the process. http://reviews.llvm.org/D15448 Files: docs/analyzer/DebugChecks.rst include/clang/StaticAnalyzer/Checkers/SValExplainer.h include/clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp test/Analysis/explain-svals.cpp
Index: test/Analysis/explain-svals.cpp =================================================================== --- /dev/null +++ test/Analysis/explain-svals.cpp @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core.builtin,debug.ExprInspection,unix.cstring -verify %s + +typedef unsigned long size_t; + +struct S { + struct S3 { + int y[10]; + }; + struct S2 : S3 { + int *x; + } s2[10]; + int z; +}; + + +void clang_analyzer_explain(int); +void clang_analyzer_explain(void *); +void clang_analyzer_explain(S); + +size_t clang_analyzer_getExtent(void *); + +size_t strlen(const char *); + +int conjure(); +S conjure_S(); + +int glob; +static int stat_glob; +void *glob_ptr; + +// Test strings are regex'ed because we need to match exact string +// rather than a substring. + +void test_1(int param, void *ptr) { + clang_analyzer_explain(&glob); // expected-warning-re{{{{^pointer to global variable 'glob'$}}}} + clang_analyzer_explain(param); // expected-warning-re{{{{^argument 'param'$}}}} + clang_analyzer_explain(ptr); // expected-warning-re{{{{^argument 'ptr'$}}}} + if (param == 42) + clang_analyzer_explain(param); // expected-warning-re{{{{^signed 32-bit integer '42'$}}}} +} + +void test_2(char *ptr, int ext) { + clang_analyzer_explain((void *) "asdf"); // expected-warning-re{{{{^pointer to element of type 'char' with index 0 of string literal "asdf"$}}}} + clang_analyzer_explain(strlen(ptr)); // expected-warning-re{{{{^metadata of type 'unsigned long' tied to pointee of argument 'ptr'$}}}} + clang_analyzer_explain(conjure()); // expected-warning-re{{{{^symbol of type 'int' conjured at statement 'conjure\(\)'$}}}} + clang_analyzer_explain(glob); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at statement 'conjure\(\)'\) for global variable 'glob'$}}}} + clang_analyzer_explain(glob_ptr); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at statement 'conjure\(\)'\) for global variable 'glob_ptr'$}}}} + clang_analyzer_explain(clang_analyzer_getExtent(ptr)); // expected-warning-re{{{{^extent of pointee of argument 'ptr'$}}}} + int *x = new int[ext]; + clang_analyzer_explain(x); // expected-warning-re{{{{^pointer to element of type 'int' with index 0 of pointee of symbol of type 'int \*' conjured at statement 'new int \[ext\]'$}}}} + // Sic! What gets computed is the extent of the element-region. + clang_analyzer_explain(clang_analyzer_getExtent(x)); // expected-warning-re{{{{^signed 32-bit integer '4'$}}}} + delete[] x; +} + +void test_3(S s) { + clang_analyzer_explain(&s); // expected-warning-re{{{{^pointer to parameter 's'$}}}} + clang_analyzer_explain(s.z); // expected-warning-re{{{{^initial value of field 'z' of parameter 's'$}}}} + clang_analyzer_explain(&s.s2[5].y[3]); // expected-warning-re{{{{^pointer to element of type 'int' with index 3 of field 'y' of base object 'S::S3' inside element of type 'struct S::S2' with index 5 of field 's2' of parameter 's'$}}}} + if (!s.s2[7].x) { + clang_analyzer_explain(s.s2[7].x); // expected-warning-re{{{{^concrete memory address '0'$}}}} + // FIXME: we need to be explaining '1' rather than '0' here; not explainer bug. + clang_analyzer_explain(s.s2[7].x + 1); // expected-warning-re{{{{^concrete memory address '0'$}}}} + } +} + +void test_4(int x, int y) { + int z; + static int stat; + clang_analyzer_explain(x + 1); // expected-warning-re{{{{^\(argument 'x'\) \+ 1$}}}} + clang_analyzer_explain(1 + y); // expected-warning-re{{{{^\(argument 'y'\) \+ 1$}}}} + clang_analyzer_explain(x + y); // expected-warning-re{{{{^unknown value$}}}} + clang_analyzer_explain(z); // expected-warning-re{{{{^undefined value$}}}} + clang_analyzer_explain(&z); // expected-warning-re{{{{^pointer to local variable 'z'$}}}} + clang_analyzer_explain(stat); // expected-warning-re{{{{^signed 32-bit integer '0'$}}}} + clang_analyzer_explain(&stat); // expected-warning-re{{{{^pointer to static local variable 'stat'$}}}} + clang_analyzer_explain(stat_glob); // expected-warning-re{{{{^initial value of global variable 'stat_glob'$}}}} + clang_analyzer_explain(&stat_glob); // expected-warning-re{{{{^pointer to global variable 'stat_glob'$}}}} + clang_analyzer_explain((int[]){1, 2, 3}); // expected-warning-re{{{{^pointer to element of type 'int' with index 0 of compound literal \(int \[3\]\)\{1, 2, 3\}$}}}} +} + +namespace { +class C { + int x[10]; + +public: + void test_5(int i) { + clang_analyzer_explain(this); // expected-warning-re{{{{^pointer to 'this' object$}}}} + clang_analyzer_explain(&x[i]); // expected-warning-re{{{{^pointer to element of type 'int' with index 'argument 'i'' of field 'x' of 'this' object$}}}} + clang_analyzer_explain(__builtin_alloca(i)); // expected-warning-re{{{{^pointer to region allocated by '__builtin_alloca\(i\)'$}}}} + } +}; +} // end of anonymous namespace + +void test_6() { + clang_analyzer_explain(conjure_S()); // expected-warning-re{{{{^frozen compound value of temporary object constructed at statement 'conjure_S\(\)'$}}}} + clang_analyzer_explain(conjure_S().z); // expected-warning-re{{{{^value derived from \(symbol of type 'struct S' conjured at statement 'conjure_S\(\)'\) for field 'z' of temporary object constructed at statement 'conjure_S\(\)'$}}}} +} Index: lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -11,6 +11,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Checkers/SValExplainer.h" #include "llvm/ADT/StringSwitch.h" using namespace clang; @@ -25,10 +26,14 @@ void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const; void analyzerCrash(const CallExpr *CE, CheckerContext &C) const; void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const; + void analyzerExplain(const CallExpr *CE, CheckerContext &C) const; + void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const; typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *, CheckerContext &C) const; + void reportBug(llvm::StringRef Msg, CheckerContext &C) const; + public: bool evalCall(const CallExpr *CE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; @@ -50,6 +55,8 @@ &ExprInspectionChecker::analyzerWarnIfReached) .Case("clang_analyzer_warnOnDeadSymbol", &ExprInspectionChecker::analyzerWarnOnDeadSymbol) + .Case("clang_analyzer_explain", &ExprInspectionChecker::analyzerExplain) + .Case("clang_analyzer_getExtent", &ExprInspectionChecker::analyzerGetExtent) .Default(nullptr); if (!Handler) @@ -91,6 +98,18 @@ } } +void ExprInspectionChecker::reportBug(llvm::StringRef Msg, + CheckerContext &C) const { + if (!BT) + BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + C.emitReport(llvm::make_unique<BugReport>(*BT, Msg, N)); +} + void ExprInspectionChecker::analyzerEval(const CallExpr *CE, CheckerContext &C) const { const LocationContext *LC = C.getPredecessor()->getLocationContext(); @@ -100,26 +119,12 @@ if (LC->getCurrentStackFrame()->getParent() != nullptr) return; - if (!BT) - BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - - ExplodedNode *N = C.generateNonFatalErrorNode(); - if (!N) - return; - C.emitReport( - llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N)); + reportBug(getArgumentValueString(CE, C), C); } void ExprInspectionChecker::analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const { - - if (!BT) - BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - - ExplodedNode *N = C.generateNonFatalErrorNode(); - if (!N) - return; - C.emitReport(llvm::make_unique<BugReport>(*BT, "REACHABLE", N)); + reportBug("REACHABLE", C); } void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE, @@ -134,14 +139,32 @@ if (LC->getCurrentStackFrame()->getParent() == nullptr) return; - if (!BT) - BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); + reportBug(getArgumentValueString(CE, C), C); +} - ExplodedNode *N = C.generateNonFatalErrorNode(); - if (!N) - return; - C.emitReport( - llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N)); +void ExprInspectionChecker::analyzerExplain(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() == 0) + reportBug("Missing argument for explaining", C); + + SVal V = C.getSVal(CE->getArg(0)); + SValExplainer Ex(C.getASTContext()); + reportBug(Ex.Visit(V), C); +} + +void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() == 0) + reportBug("Missing region for obtaining extent", C); + + auto MR = dyn_cast_or_null<SubRegion>(C.getSVal(CE->getArg(0)).getAsRegion()); + if (!MR) + reportBug("Obtaining extent of a non-region", C); + + ProgramStateRef State = C.getState(); + State = State->BindExpr(CE, C.getLocationContext(), + MR->getExtent(C.getSValBuilder())); + C.addTransition(State); } void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE, Index: include/clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h =================================================================== --- /dev/null +++ include/clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h @@ -0,0 +1,277 @@ +//===--- SValVisitor.h - Visitor for SVal subclasses ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the SValVisitor, SymExprVisitor, and MemRegionVisitor +// interfaces, and also FullSValVisitor, which visits all three hierarchies. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_SVALVISITOR_H +#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_SVALVISITOR_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" + +namespace clang { + +namespace ento { + +/// SValVisitor - this class implements a simple visitor for SVal +/// subclasses. +template <typename ImplClass, typename RetTy = void> class SValVisitor { +public: + +#define DISPATCH(NAME, CLASS) \ + return static_cast<ImplClass *>(this)->Visit ## NAME(V.castAs<CLASS>()) + + RetTy Visit(SVal V) { + // Dispatch to VisitFooSVal for each FooSVal. + switch (V.getBaseKind()) { + case SVal::UndefinedKind: DISPATCH(UndefinedVal, UndefinedVal); + case SVal::UnknownKind: DISPATCH(UnknownVal, UnknownVal); + case SVal::LocKind: + switch (V.getSubKind()) { +#define LOC_DISPATCH(CLASS) DISPATCH(Loc ## CLASS, loc::CLASS) + case loc::GotoLabelKind: LOC_DISPATCH(GotoLabel); + case loc::MemRegionKind: LOC_DISPATCH(MemRegionVal); + case loc::ConcreteIntKind: LOC_DISPATCH(ConcreteInt); +#undef LOC_DISPATCH + default: + llvm_unreachable("Unknown Loc sub-kind!"); + } + case SVal::NonLocKind: + switch (V.getSubKind()) { +#define NONLOC_DISPATCH(CLASS) DISPATCH(NonLoc ## CLASS, nonloc::CLASS) + case nonloc::ConcreteIntKind: NONLOC_DISPATCH(ConcreteInt); + case nonloc::SymbolValKind: NONLOC_DISPATCH(SymbolVal); + case nonloc::LocAsIntegerKind: NONLOC_DISPATCH(LocAsInteger); + case nonloc::CompoundValKind: NONLOC_DISPATCH(CompoundVal); + case nonloc::LazyCompoundValKind: NONLOC_DISPATCH(LazyCompoundVal); +#undef NONLOC_DISPATCH + default: + llvm_unreachable("Unknown NonLoc sub-kind!"); + } + default: + llvm_unreachable("Unknown SVal kind!"); + } + } + + // If the implementation chooses not to implement a certain VisitLoc[...]() + // method, fall back on plain VisitLoc(). +#define LOC_FALLBACK(CLASS) \ + RetTy VisitLoc ## CLASS(loc::CLASS V) { DISPATCH(Loc, Loc); } + LOC_FALLBACK(ConcreteInt) + LOC_FALLBACK(GotoLabel) + LOC_FALLBACK(MemRegionVal) +#undef LOC_FALLBACK + + // If the implementation chooses not to implement a certain VisitNonLoc[...]() + // method, fall back on plain VisitNonLoc(). +#define NONLOC_FALLBACK(CLASS) \ + RetTy VisitNonLoc ## CLASS(nonloc::CLASS V) { DISPATCH(NonLoc, NonLoc); } + NONLOC_FALLBACK(ConcreteInt) + NONLOC_FALLBACK(SymbolVal) + NONLOC_FALLBACK(LocAsInteger) + NONLOC_FALLBACK(CompoundVal) + NONLOC_FALLBACK(LazyCompoundVal) +#undef NONLOC_FALLBACK + + // If the implementation chooses not to implement a certain Visit[...]() + // method, fall back on visiting the superclass. +#define FALLBACK(CLASS, PARENT) \ + RetTy Visit ## CLASS(CLASS V) { DISPATCH(PARENT, CLASS); } + FALLBACK(Loc, DefinedSVal) + FALLBACK(NonLoc, DefinedSVal) + FALLBACK(DefinedSVal, DefinedOrUnknownSVal) + FALLBACK(UnknownVal, DefinedOrUnknownSVal) + FALLBACK(DefinedOrUnknownSVal, SVal) + FALLBACK(KnownSVal, SVal) + FALLBACK(UndefinedVal, SVal) +#undef FALLBACK + + // Base case, ignore it. :) + RetTy VisitSVal(SVal V) { return RetTy(); } + +#undef DISPATCH +}; + +/// SymExprVisitor - this class implements a simple visitor for SymExpr +/// subclasses. +template <typename ImplClass, typename RetTy = void> class SymExprVisitor { +public: + +#define DISPATCH(CLASS) \ + return static_cast<ImplClass *>(this)->Visit ## CLASS(cast<CLASS>(S)) + + RetTy Visit(SymbolRef S) { + // Dispatch to VisitSymbolFoo for each SymbolFoo. + switch (S->getKind()) { + case SymExpr::RegionValueKind: DISPATCH(SymbolRegionValue); + case SymExpr::ConjuredKind: DISPATCH(SymbolConjured); + case SymExpr::DerivedKind: DISPATCH(SymbolDerived); + case SymExpr::ExtentKind: DISPATCH(SymbolExtent); + case SymExpr::MetadataKind: DISPATCH(SymbolMetadata); + case SymExpr::SymIntKind: DISPATCH(SymIntExpr); + case SymExpr::IntSymKind: DISPATCH(IntSymExpr); + case SymExpr::SymSymKind: DISPATCH(SymSymExpr); + case SymExpr::CastSymbolKind: DISPATCH(SymbolCast); + default: + llvm_unreachable("Unknown SymExpr kind!"); + } + } + + // If the implementation chooses not to implement a certain visit method, fall + // back on visiting the superclass. +#define FALLBACK(CLASS, PARENT) \ + RetTy Visit ## CLASS(const CLASS *S) { DISPATCH(PARENT); } + FALLBACK(SymbolRegionValue, SymbolData) + FALLBACK(SymbolConjured, SymbolData) + FALLBACK(SymbolDerived, SymbolData) + FALLBACK(SymbolExtent, SymbolData) + FALLBACK(SymbolMetadata, SymbolData) + FALLBACK(SymIntExpr, BinarySymExpr) + FALLBACK(IntSymExpr, BinarySymExpr) + FALLBACK(SymSymExpr, BinarySymExpr) + FALLBACK(SymbolData, SymExpr) + FALLBACK(BinarySymExpr, SymExpr) + FALLBACK(SymbolCast, SymExpr) +#undef FALLBACK + + // Base case, ignore it. :) + RetTy VisitSymExpr(SymbolRef S) { return RetTy(); } + +#undef DISPATCH +}; + +/// MemRegionVisitor - this class implements a simple visitor for MemRegion +/// subclasses. +template <typename ImplClass, typename RetTy = void> class MemRegionVisitor { +public: + +#define DISPATCH(CLASS) \ + return static_cast<ImplClass *>(this)->Visit ## CLASS(cast<CLASS>(R)) + + RetTy Visit(const MemRegion *R) { + // Dispatch to VisitFooRegion for each FooRegion. + switch (R->getKind()) { + case MemRegion::GenericMemSpaceRegionKind: + DISPATCH(MemSpaceRegion); + case MemRegion::StackLocalsSpaceRegionKind: + DISPATCH(StackLocalsSpaceRegion); + case MemRegion::StackArgumentsSpaceRegionKind: + DISPATCH(StackArgumentsSpaceRegion); + case MemRegion::HeapSpaceRegionKind: + DISPATCH(HeapSpaceRegion); + case MemRegion::UnknownSpaceRegionKind: + DISPATCH(UnknownSpaceRegion); + case MemRegion::StaticGlobalSpaceRegionKind: + DISPATCH(StaticGlobalSpaceRegion); + case MemRegion::GlobalInternalSpaceRegionKind: + DISPATCH(GlobalInternalSpaceRegion); + case MemRegion::GlobalSystemSpaceRegionKind: + DISPATCH(GlobalSystemSpaceRegion); + case MemRegion::GlobalImmutableSpaceRegionKind: + DISPATCH(GlobalImmutableSpaceRegion); + case MemRegion::SymbolicRegionKind: + DISPATCH(SymbolicRegion); + case MemRegion::AllocaRegionKind: + DISPATCH(AllocaRegion); + case MemRegion::FunctionTextRegionKind: + DISPATCH(FunctionTextRegion); + case MemRegion::BlockTextRegionKind: + DISPATCH(BlockTextRegion); + case MemRegion::BlockDataRegionKind: + DISPATCH(BlockDataRegion); + case MemRegion::CompoundLiteralRegionKind: + DISPATCH(CompoundLiteralRegion); + case MemRegion::CXXThisRegionKind: + DISPATCH(CXXThisRegion); + case MemRegion::StringRegionKind: + DISPATCH(StringRegion); + case MemRegion::ObjCStringRegionKind: + DISPATCH(ObjCStringRegion); + case MemRegion::ElementRegionKind: + DISPATCH(ElementRegion); + case MemRegion::VarRegionKind: + DISPATCH(VarRegion); + case MemRegion::FieldRegionKind: + DISPATCH(FieldRegion); + case MemRegion::ObjCIvarRegionKind: + DISPATCH(ObjCIvarRegion); + case MemRegion::CXXTempObjectRegionKind: + DISPATCH(CXXTempObjectRegion); + case MemRegion::CXXBaseObjectRegionKind: + DISPATCH(CXXBaseObjectRegion); + default: + llvm_unreachable("Unknown MemRegion kind!"); + } + } + + // If the implementation chooses not to implement a certain visit method, fall + // back on visiting the superclass. +#define FALLBACK(CLASS, PARENT) \ + RetTy Visit ## CLASS(const CLASS *R) { DISPATCH(PARENT); } + FALLBACK(GlobalImmutableSpaceRegion, NonStaticGlobalSpaceRegion) + FALLBACK(GlobalInternalSpaceRegion, NonStaticGlobalSpaceRegion) + FALLBACK(GlobalSystemSpaceRegion, NonStaticGlobalSpaceRegion) + FALLBACK(StaticGlobalSpaceRegion, GlobalsSpaceRegion) + FALLBACK(NonStaticGlobalSpaceRegion, GlobalsSpaceRegion) + FALLBACK(StackArgumentsSpaceRegion, StackSpaceRegion) + FALLBACK(StackLocalsSpaceRegion, StackSpaceRegion) + FALLBACK(HeapSpaceRegion, MemSpaceRegion) + FALLBACK(UnknownSpaceRegion, MemSpaceRegion) + FALLBACK(GlobalsSpaceRegion, MemSpaceRegion) + FALLBACK(StackSpaceRegion, MemSpaceRegion) + FALLBACK(VarRegion, DeclRegion) + FALLBACK(FieldRegion, DeclRegion) + FALLBACK(ObjCIvarRegion, DeclRegion) + FALLBACK(DeclRegion, TypedValueRegion) + FALLBACK(CompoundLiteralRegion, TypedValueRegion) + FALLBACK(CXXBaseObjectRegion, TypedValueRegion) + FALLBACK(CXXTempObjectRegion, TypedValueRegion) + FALLBACK(CXXThisRegion, TypedValueRegion) + FALLBACK(ElementRegion, TypedValueRegion) + FALLBACK(StringRegion, TypedValueRegion) + FALLBACK(ObjCStringRegion, TypedValueRegion) + FALLBACK(BlockTextRegion, CodeTextRegion) + FALLBACK(FunctionTextRegion, CodeTextRegion) + FALLBACK(BlockDataRegion, TypedRegion) + FALLBACK(CodeTextRegion, TypedRegion) + FALLBACK(TypedValueRegion, TypedRegion) + FALLBACK(AllocaRegion, SubRegion) + FALLBACK(SymbolicRegion, SubRegion) + FALLBACK(TypedRegion, SubRegion) + FALLBACK(MemSpaceRegion, MemRegion) + FALLBACK(SubRegion, MemRegion) +#undef FALLBACK + + // Base case, ignore it. :) + RetTy VisitMemRegion(const MemRegion *R) { return RetTy(); } + +#undef DISPATCH +}; + +/// FullSValVisitor - a convenient mixed visitor for all three: +/// SVal, SymExpr and MemRegion subclasses. +template <typename ImplClass, typename RetTy = void> +class FullSValVisitor : public SValVisitor<ImplClass, RetTy>, + public SymExprVisitor<ImplClass, RetTy>, + public MemRegionVisitor<ImplClass, RetTy> { +public: + using SValVisitor<ImplClass, RetTy>::Visit; + using SymExprVisitor<ImplClass, RetTy>::Visit; + using MemRegionVisitor<ImplClass, RetTy>::Visit; +}; + +} // end namespace ento + +} // end namespace clang + +#endif Index: include/clang/StaticAnalyzer/Checkers/SValExplainer.h =================================================================== --- /dev/null +++ include/clang/StaticAnalyzer/Checkers/SValExplainer.h @@ -0,0 +1,233 @@ +//== SValExplainer.h - Symbolic value explainer -----------------*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines SValExplainer, a class for pretty-printing a +// human-readable description of a symbolic value. For example, +// "reg_$0<x>" is turned into "initial value of variable 'x'". +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_CHECKERS_SVALEXPLAINER_H +#define LLVM_CLANG_STATICANALYZER_CHECKERS_SVALEXPLAINER_H + +#include "clang/AST/DeclCXX.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" + +namespace clang { + +namespace ento { + +class SValExplainer : public FullSValVisitor<SValExplainer, std::string> { +private: + ASTContext &ACtx; + + std::string printStmt(const Stmt *S) { + std::string Str; + llvm::raw_string_ostream OS(Str); + S->printPretty(OS, nullptr, PrintingPolicy(ACtx.getLangOpts())); + return OS.str(); + } + + bool isThisObject(const SymbolicRegion *R) { + if (auto S = dyn_cast<SymbolRegionValue>(R->getSymbol())) + if (isa<CXXThisRegion>(S->getRegion())) + return true; + return false; + } + +public: + SValExplainer(ASTContext &Ctx) : ACtx(Ctx) {} + + std::string VisitUnknownVal(UnknownVal V) { + return "unknown value"; + } + + std::string VisitUndefinedVal(UndefinedVal V) { + return "undefined value"; + } + + std::string VisitLocMemRegionVal(loc::MemRegionVal V) { + const MemRegion *R = V.getRegion(); + // Avoid the weird "pointer to pointee of ...". + if (auto SR = dyn_cast<SymbolicRegion>(R)) { + // However, "pointer to 'this' object" is fine. + if (!isThisObject(SR)) + return Visit(SR->getSymbol()); + } + return "pointer to " + Visit(R); + } + + std::string VisitLocConcreteInt(loc::ConcreteInt V) { + llvm::APSInt I = V.getValue(); + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << "concrete memory address '" << I << "'"; + return OS.str(); + } + + std::string VisitNonLocSymbolVal(nonloc::SymbolVal V) { + return Visit(V.getSymbol()); + } + + std::string VisitNonLocConcreteInt(nonloc::ConcreteInt V) { + llvm::APSInt I = V.getValue(); + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << (I.isSigned() ? "signed " : "unsigned ") << I.getBitWidth() + << "-bit integer '" << I << "'"; + return OS.str(); + } + + std::string VisitNonLocLazyCompoundVal(nonloc::LazyCompoundVal V) { + return "frozen compound value of " + Visit(V.getRegion()); + } + + std::string VisitSymbolRegionValue(const SymbolRegionValue *S) { + const MemRegion *R = S->getRegion(); + // Special handling for argument values. + if (auto V = dyn_cast<VarRegion>(R)) + if (auto D = dyn_cast<ParmVarDecl>(V->getDecl())) + return "argument '" + D->getQualifiedNameAsString() + "'"; + return "initial value of " + Visit(R); + } + + std::string VisitSymbolConjured(const SymbolConjured *S) { + return "symbol of type '" + S->getType().getAsString() + + "' conjured at statement '" + printStmt(S->getStmt()) + "'"; + } + + std::string VisitSymbolDerived(const SymbolDerived *S) { + return "value derived from (" + Visit(S->getParentSymbol()) + + ") for " + Visit(S->getRegion()); + } + + std::string VisitSymbolExtent(const SymbolExtent *S) { + return "extent of " + Visit(S->getRegion()); + } + + std::string VisitSymbolMetadata(const SymbolMetadata *S) { + return "metadata of type '" + S->getType().getAsString() + "' tied to " + + Visit(S->getRegion()); + } + + std::string VisitSymIntExpr(const SymIntExpr *S) { + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << "(" << Visit(S->getLHS()) << ") " + << std::string(BinaryOperator::getOpcodeStr(S->getOpcode())) << " " + << S->getRHS(); + return OS.str(); + } + + // TODO: IntSymExpr doesn't appear in practice. + // Add the relevant code once it does. + + std::string VisitSymSymExpr(const SymSymExpr *S) { + return "(" + Visit(S->getLHS()) + ") " + + std::string(BinaryOperator::getOpcodeStr(S->getOpcode())) + + " (" + Visit(S->getRHS()) + ")"; + } + + // TODO: SymbolCast doesn't appear in practice. + // Add the relevant code once it does. + + std::string VisitSymbolicRegion(const SymbolicRegion *R) { + // Explain 'this' object here. + // TODO: Explain CXXThisRegion itself, find a way to test it. + if (isThisObject(R)) + return "'this' object"; + return "pointee of " + Visit(R->getSymbol()); + } + + std::string VisitAllocaRegion(const AllocaRegion *R) { + return "region allocated by '" + printStmt(R->getExpr()) + "'"; + } + + std::string VisitCompoundLiteralRegion(const CompoundLiteralRegion *R) { + return "compound literal " + printStmt(R->getLiteralExpr()); + } + + std::string VisitStringRegion(const StringRegion *R) { + return "string literal " + R->getString(); + } + + std::string VisitElementRegion(const ElementRegion *R) { + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << "element of type '" << R->getElementType().getAsString() + << "' with index "; + // For concrete index: omit type of the index integer. + if (auto I = R->getIndex().getAs<nonloc::ConcreteInt>()) + OS << I->getValue(); + else + OS << "'" << Visit(R->getIndex()) << "'"; + OS << " of " + Visit(R->getSuperRegion()); + return OS.str(); + } + + std::string VisitVarRegion(const VarRegion *R) { + const VarDecl *VD = R->getDecl(); + std::string Name = VD->getQualifiedNameAsString(); + if (isa<ParmVarDecl>(VD)) + return "parameter '" + Name + "'"; + else if (VD->hasLocalStorage()) + return "local variable '" + Name + "'"; + else if (VD->isStaticLocal()) + return "static local variable '" + Name + "'"; + else if (VD->hasGlobalStorage()) + return "global variable '" + Name + "'"; + else + llvm_unreachable("A variable is either local or global"); + } + + std::string VisitFieldRegion(const FieldRegion *R) { + return "field '" + R->getDecl()->getNameAsString() + "' of " + + Visit(R->getSuperRegion()); + } + + std::string VisitCXXTempObjectRegion(const CXXTempObjectRegion *R) { + return "temporary object constructed at statement '" + + printStmt(R->getExpr()) + "'"; + } + + std::string VisitCXXBaseObjectRegion(const CXXBaseObjectRegion *R) { + return "base object '" + R->getDecl()->getQualifiedNameAsString() + + "' inside " + Visit(R->getSuperRegion()); + } + + std::string VisitSVal(SVal V) { + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << V; + return "a value unsupported by the explainer: (" + + std::string(OS.str()) + ")"; + } + + std::string VisitSymExpr(SymbolRef S) { + std::string Str; + llvm::raw_string_ostream OS(Str); + S->dumpToStream(OS); + return "a symbolic expression unsupported by the explainer: (" + + std::string(OS.str()) + ")"; + } + + std::string VisitMemRegion(const MemRegion *R) { + std::string Str; + llvm::raw_string_ostream OS(Str); + OS << R; + return "a memory region unsupported by the explainer (" + + std::string(OS.str()) + ")"; + } +}; + +} // end namespace ento + +} // end namespace clang + +#endif Index: docs/analyzer/DebugChecks.rst =================================================================== --- docs/analyzer/DebugChecks.rst +++ docs/analyzer/DebugChecks.rst @@ -162,6 +162,41 @@ } while(0); // expected-warning{{SYMBOL DEAD}} +- void clang_analyzer_explain(a single argument of any type); + + This function explains the value of its argument in a human-readable manner + in the warning message. You can make as many overrides of its prototype + in the test code as necessary to explain various integral, pointer, + or even record-type values. + + Example usage:: + + void clang_analyzer_explain(int); + void clang_analyzer_explain(void *); + + void foo(int param, void *ptr) { + clang_analyzer_explain(param); // expected-warning{{argument 'param'}} + if (!ptr) + clang_analyzer_explain(ptr); // expected-warning{{memory address '0'}} + } + +- size_t clang_analyzer_getExtent(void *); + + This function returns the value that represents the extent of a memory region + pointed to by the argument. This value is often difficult to obtain otherwise, + because no valid code that produces this value. However, it may be useful + for testing purposes, to see how well does the analyzer model region extents. + + Example usage:: + + void foo() { + int x, *y; + size_t xs = clang_analyzer_getExtent(&x); + clang_analyzer_explain(xs); // expected-warning{{'4'}} + size_t ys = clang_analyzer_getExtent(&y); + clang_analyzer_explain(ys); // expected-warning{{'8'}} + } + Statistics ==========
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits