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 ========== 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" 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 { +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(R->getSymbol())) + if (isa(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(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(R)) + if (auto D = dyn_cast(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()) + 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(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: 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 class SValVisitor { +public: + +#define DISPATCH(NAME, CLASS) \ + return static_cast(this)->Visit ## NAME(V.castAs()) + + 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 class SymExprVisitor { +public: + +#define DISPATCH(CLASS) \ + return static_cast(this)->Visit ## CLASS(cast(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 class MemRegionVisitor { +public: + +#define DISPATCH(CLASS) \ + return static_cast(this)->Visit ## CLASS(cast(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 +class FullSValVisitor : public SValVisitor, + public SymExprVisitor, + public MemRegionVisitor { +public: + using SValVisitor::Visit; + using SymExprVisitor::Visit; + using MemRegionVisitor::Visit; +}; + +} // end namespace ento + +} // end namespace clang + +#endif 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(*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(*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(*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(*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(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: 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\(\)'$}}}} +}